From 06d3bb2a34aaaf0455a7b4fc7bf3e8fd2d5ceec0 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 26 Aug 2019 15:19:23 -0700 Subject: [PATCH 001/203] start qt launcher work --- launchers/qt/CMakeLists.txt | 38 +++++++++++++++++++++++++++++++++++++ launchers/qt/src/main.cpp | 7 +++++++ 2 files changed, 45 insertions(+) create mode 100644 launchers/qt/CMakeLists.txt create mode 100644 launchers/qt/src/main.cpp diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt new file mode 100644 index 0000000000..1db62859e6 --- /dev/null +++ b/launchers/qt/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.11) +project(HQLauncher) +set(CMAKE_CXX_STANDARD_REQUIRED_ON) +find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED) +find_package(OpenGL) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +#set(CMAKE_AUTOUIC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + +foreach(plugin ${Qt5Gui_PLUGINS}) + get_target_property(_loc ${plugin} LOCATION) + message("Plugin ${plugin} is at location ${_loc}") + set(plugin_libs ${plugin_libs} ${_loc}) +endforeach() + +set(src_files + src/main.cpp) + +add_executable(${PROJECT_NAME} ${src_file}) + +target_link_libraries(${PROJECT_NAME} PUBLIC + Qt5::Core + Qt5::Quick + Qt5::QuickControls2 + Qt5::Qml + Qt5::Gui + ${OPENGL_LIBRARIES} + ${plugin_libs}) + +target_include_directories(${PROJECT_NAME} PUBLIC + ${Qt5Core_INCLUDE_DIRS} + ${Qt5Quick_INCLUDE_DIRS} + ${Qt5Gui_INCLUDE_DIRS} + ${Qt5Qml_INCLUDE_DIRS}) diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp new file mode 100644 index 0000000000..7ac9d7c330 --- /dev/null +++ b/launchers/qt/src/main.cpp @@ -0,0 +1,7 @@ +//main.cpp +//#include + +int main(int argc, char *argv[]) { + //std::cout << "Hello World \n"; + return 0; +} From 0a2b5460173a781cdcbd8c03bdd748aeaceb83a2 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 26 Aug 2019 16:26:23 -0700 Subject: [PATCH 002/203] working on windows cmake version --- launchers/qt/CMakeLists.txt | 2 +- launchers/qt/src/main.cpp | 29 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 1db62859e6..46adde6763 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -20,7 +20,7 @@ endforeach() set(src_files src/main.cpp) -add_executable(${PROJECT_NAME} ${src_file}) +add_executable(${PROJECT_NAME} ${src_files}) target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 7ac9d7c330..1cfa444aa3 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -1,7 +1,26 @@ -//main.cpp -//#include +#include +#include +#include +#include +#include +/*Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +Q_IMPORT_PLUGIN(QtQuick2Plugin); +Q_IMPORT_PLUGIN(QtQuickControls2Plugin); +Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin);*/ +int main(int argc, char *argv[]) +{ + QString name { "QtExamples" }; + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setOrganizationName(name); -int main(int argc, char *argv[]) { - //std::cout << "Hello World \n"; - return 0; + QGuiApplication app(argc, argv); + + QQuickView view; + view.setFlags(Qt::FramelessWindowHint); + view.setSource(QUrl("/Users/danteruiz/github/test/qml/root.qml")); + if (view.status() == QQuickView::Error) + return -1; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.show(); + return app.exec(); } From d5bfe49cd035acb5ecddd40108e4b86ed0c2e45b Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 27 Aug 2019 12:05:14 -0700 Subject: [PATCH 003/203] get windows qt cmake working --- launchers/qt/CMakeLists.txt | 26 ++++++++++++++++++++++---- launchers/qt/src/main.cpp | 10 +++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 46adde6763..05b89c27f1 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -8,15 +8,16 @@ set(CMAKE_AUTORCC ON) #set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") - foreach(plugin ${Qt5Gui_PLUGINS}) get_target_property(_loc ${plugin} LOCATION) message("Plugin ${plugin} is at location ${_loc}") set(plugin_libs ${plugin_libs} ${_loc}) endforeach() +foreach(qt_lib_name ${Qt_LIBRARIES}) + message("lib ${qt_lib_name} is at location") +endforeach() + set(src_files src/main.cpp) @@ -28,8 +29,25 @@ target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::QuickControls2 Qt5::Qml Qt5::Gui + Qt5::Network + ${Qt_LIBRARIES} ${OPENGL_LIBRARIES} - ${plugin_libs}) + ${plugin_libs} + wsock32 ws2_32 Winmm version imm32 dwmapi + "${_qt5Core_install_prefix}/lib/qtpcre2.lib" + "${_qt5Core_install_prefix}/lib/qtlibpng.lib" + "${_qt5Core_install_prefix}/lib/qtfreetype.lib" + "${_qt5Core_install_prefix}/lib/Qt5AccessibilitySupport.lib" + "${_qt5Core_install_prefix}/lib/Qt5FbSupport.lib" + "${_qt5Core_install_prefix}/lib/Qt5OpenGLExtensions.lib" + "${_qt5Core_install_prefix}/lib/Qt5QuickTemplates2.lib" + "${_qt5Core_install_prefix}/lib/Qt5FontDatabaseSupport.lib" + "${_qt5Core_install_prefix}/lib/Qt5ThemeSupport.lib" +# "${_qt5Core_install_prefix}/lib/Qt5Bootstrap.lib" + "${_qt5Core_install_prefix}/lib/Qt5EventDispatcherSupport.lib" + "${_qt5Core_install_prefix}/qml/QtQuick.2/qtquick2plugin.lib" + "${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/qtquickcontrols2plugin.lib" + "${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib") target_include_directories(${PROJECT_NAME} PUBLIC ${Qt5Core_INCLUDE_DIRS} diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 1cfa444aa3..9d0afc98a9 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -3,13 +3,17 @@ #include #include #include -/*Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +//Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); -Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin);*/ +Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); int main(int argc, char *argv[]) { QString name { "QtExamples" }; + + + std::cout << "Hello world\n"; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); @@ -17,7 +21,7 @@ int main(int argc, char *argv[]) QQuickView view; view.setFlags(Qt::FramelessWindowHint); - view.setSource(QUrl("/Users/danteruiz/github/test/qml/root.qml")); + //view.setSource(QUrl("/Users/danteruiz/github/test/qml/root.qml")); if (view.status() == QQuickView::Error) return -1; view.setResizeMode(QQuickView::SizeRootObjectToView); From 96e0b0cf21e58c7e3e9fd3375c7c2a9d033aae2a Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 27 Aug 2019 16:11:34 -0700 Subject: [PATCH 004/203] fixing up resources --- launchers/qt/CMakeLists.txt | 27 +++++- launchers/qt/cmake/init.cmake | 14 +++ launchers/qt/cmake/macros/GenerateQrc.cmake | 31 ++++++ launchers/qt/cmake/templates/resources.qrc.in | 5 + .../resources}/images/HiFi_Voxel.png | Bin .../resources}/images/HiFi_Window.png | Bin .../resources}/images/hifi_logo_large@2x.png | Bin .../resources}/images/hifi_logo_small@2x.png | Bin .../resources}/images/hifi_window@2x.png | Bin .../resources}/images/interface.icns | Bin launchers/qt/resources/qml/root.qml | 89 ++++++++++++++++++ launchers/qt/src/main.cpp | 7 +- 12 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 launchers/qt/cmake/init.cmake create mode 100644 launchers/qt/cmake/macros/GenerateQrc.cmake create mode 100644 launchers/qt/cmake/templates/resources.qrc.in rename launchers/{darwin => qt/resources}/images/HiFi_Voxel.png (100%) rename launchers/{darwin => qt/resources}/images/HiFi_Window.png (100%) rename launchers/{darwin => qt/resources}/images/hifi_logo_large@2x.png (100%) rename launchers/{darwin => qt/resources}/images/hifi_logo_small@2x.png (100%) rename launchers/{darwin => qt/resources}/images/hifi_window@2x.png (100%) rename launchers/{darwin => qt/resources}/images/interface.icns (100%) create mode 100644 launchers/qt/resources/qml/root.qml diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 05b89c27f1..d95ff79aa8 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.11) project(HQLauncher) set(CMAKE_CXX_STANDARD_REQUIRED_ON) +include("cmake/init.cmake") find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED) find_package(OpenGL) set(CMAKE_AUTOMOC ON) @@ -8,16 +9,26 @@ set(CMAKE_AUTORCC ON) #set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CUSTOM_LAUNCHER_QRC_PATHS "") +set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) +set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc) +generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_LAUNCHER_QRC_PATHS} GLOBS *) + +add_custom_command( + OUTPUT ${RESOURCES_RCC} + DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} + COMMAND "${_qt5Core_install_prefix}/bin/rcc" + ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}) + +list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC}) +add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) + foreach(plugin ${Qt5Gui_PLUGINS}) get_target_property(_loc ${plugin} LOCATION) message("Plugin ${plugin} is at location ${_loc}") set(plugin_libs ${plugin_libs} ${_loc}) endforeach() -foreach(qt_lib_name ${Qt_LIBRARIES}) - message("lib ${qt_lib_name} is at location") -endforeach() - set(src_files src/main.cpp) @@ -54,3 +65,11 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${Qt5Quick_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Qml_INCLUDE_DIRS}) + + +set(INTERFACE_EXEC_DIR "$") +set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${RESOURCES_RCC}" + "${INTERFACE_EXEC_DIR}") diff --git a/launchers/qt/cmake/init.cmake b/launchers/qt/cmake/init.cmake new file mode 100644 index 0000000000..5b88c933a0 --- /dev/null +++ b/launchers/qt/cmake/init.cmake @@ -0,0 +1,14 @@ + +# Hide automoc folders (for IDEs) +set(AUTOGEN_TARGETS_FOLDER "hidden/generated") +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# CMAKE_CURRENT_SOURCE_DIR is the parent folder here +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") + +file(GLOB LAUNCHER_CUSTOM_MACROS "cmake/macros/*.cmake") +foreach(CUSTOM_MACRO ${LAUNCHER_CUSTOM_MACROS}) + include(${CUSTOM_MACRO}) +endforeach() +unset(LAUNCHER_CUSTOM_MACROS) diff --git a/launchers/qt/cmake/macros/GenerateQrc.cmake b/launchers/qt/cmake/macros/GenerateQrc.cmake new file mode 100644 index 0000000000..95a075916f --- /dev/null +++ b/launchers/qt/cmake/macros/GenerateQrc.cmake @@ -0,0 +1,31 @@ +function(GENERATE_QRC) + set(oneValueArgs OUTPUT PREFIX PATH) + set(multiValueArgs CUSTOM_PATHS GLOBS) + cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + if ("${GENERATE_QRC_PREFIX}" STREQUAL "") + set(QRC_PREFIX_PATH /) + else() + set(QRC_PREFIX_PATH ${GENERATE_QRC_PREFIX}) + endif() + + foreach(GLOB ${GENERATE_QRC_GLOBS}) + file(GLOB_RECURSE FOUND_FILES RELATIVE ${GENERATE_QRC_PATH} ${GLOB}) + foreach(FILENAME ${FOUND_FILES}) + if (${FILENAME} MATCHES "^\\.\\.") + continue() + endif() + list(APPEND ALL_FILES "${GENERATE_QRC_PATH}/${FILENAME}") + set(QRC_CONTENTS "${QRC_CONTENTS}${GENERATE_QRC_PATH}/${FILENAME}\n") + endforeach() + endforeach() + + foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS}) + string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH}) + list(GET CUSTOM_PATH 0 IMPORT_PATH) + list(GET CUSTOM_PATH 1 LOCAL_PATH) + set(QRC_CONTENTS "${QRC_CONTENTS}${IMPORT_PATH}\n") + endforeach() + + set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT}) +endfunction() diff --git a/launchers/qt/cmake/templates/resources.qrc.in b/launchers/qt/cmake/templates/resources.qrc.in new file mode 100644 index 0000000000..6466b9ec51 --- /dev/null +++ b/launchers/qt/cmake/templates/resources.qrc.in @@ -0,0 +1,5 @@ + + +@QRC_CONTENTS@ + + diff --git a/launchers/darwin/images/HiFi_Voxel.png b/launchers/qt/resources/images/HiFi_Voxel.png similarity index 100% rename from launchers/darwin/images/HiFi_Voxel.png rename to launchers/qt/resources/images/HiFi_Voxel.png diff --git a/launchers/darwin/images/HiFi_Window.png b/launchers/qt/resources/images/HiFi_Window.png similarity index 100% rename from launchers/darwin/images/HiFi_Window.png rename to launchers/qt/resources/images/HiFi_Window.png diff --git a/launchers/darwin/images/hifi_logo_large@2x.png b/launchers/qt/resources/images/hifi_logo_large@2x.png similarity index 100% rename from launchers/darwin/images/hifi_logo_large@2x.png rename to launchers/qt/resources/images/hifi_logo_large@2x.png diff --git a/launchers/darwin/images/hifi_logo_small@2x.png b/launchers/qt/resources/images/hifi_logo_small@2x.png similarity index 100% rename from launchers/darwin/images/hifi_logo_small@2x.png rename to launchers/qt/resources/images/hifi_logo_small@2x.png diff --git a/launchers/darwin/images/hifi_window@2x.png b/launchers/qt/resources/images/hifi_window@2x.png similarity index 100% rename from launchers/darwin/images/hifi_window@2x.png rename to launchers/qt/resources/images/hifi_window@2x.png diff --git a/launchers/darwin/images/interface.icns b/launchers/qt/resources/images/interface.icns similarity index 100% rename from launchers/darwin/images/interface.icns rename to launchers/qt/resources/images/interface.icns diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml new file mode 100644 index 0000000000..1b0ab8123d --- /dev/null +++ b/launchers/qt/resources/qml/root.qml @@ -0,0 +1,89 @@ +// root.qml + +import QtQuick 2.3 +import QtQuick.Controls 2.1 +Image { + id: root + width: 515 + height: 450 + source: "../images/Hifi_window@2x.png" + + Text { + id: text + width: 325 + height: 26 + anchors { + left: root.left + leftMargin: 95 + top: root.top + topMargin: 29 + } + text: "Please log in" + font.pointSize: 24 + color: "#FFFFFF" + } + + TextField { + id: textField + background: Rectangle { + color: "#00000000" + } + anchors { + left: root.left + leftMargin: 40 + top: text.bottom + topMargin: 20 + } + echoMode: TextInput.Password + placeholderText: "Organization" + } + + Rectangle { + id: seperator + anchors { + left: textField.left + right: textField.right + top: textField.bottom + topMargin: 1 + } + + height: 1 + color: "#FFFFFF" + } + + + Button { + anchors { + left: root.left + leftMargin: 40 + top: textField.bottom + topMargin: 20 + } + id: button + text: "NEXT" + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + color: "#00000000" + opacity: 1 + border.color: "#FFFFFF" + border.width: 4 + radius: 2 + } + } + + + Image { + id: spinner + source: "../images/HiFi_Voxel.png" + anchors.bottom: root.bottom + RotationAnimator { + target: spinner; + loops: Animation.Infinite + from: 0; + to: 360; + duration: 5000 + running: true + } + } +} diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 9d0afc98a9..947ee242c6 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include //Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QtQuick2Plugin); @@ -19,9 +21,12 @@ int main(int argc, char *argv[]) QGuiApplication app(argc, argv); + QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; + QResource::registerResource(resourceBinaryLocation); + QQuickView view; view.setFlags(Qt::FramelessWindowHint); - //view.setSource(QUrl("/Users/danteruiz/github/test/qml/root.qml")); + view.setSource(QUrl("qrc:/qml/root.qml")); if (view.status() == QQuickView::Error) return -1; view.setResizeMode(QQuickView::SizeRootObjectToView); From 6d626138883398a0df7f72f3b38c27c542ed0ba4 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 27 Aug 2019 16:15:10 -0700 Subject: [PATCH 005/203] fixing root.qml --- launchers/qt/resources/qml/root.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 1b0ab8123d..462c8bef00 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -6,7 +6,7 @@ Image { id: root width: 515 height: 450 - source: "../images/Hifi_window@2x.png" + source: "../images/hifi_window@2x.png" Text { id: text From bb5e62dafd1f3a7009e42d7beb6b24468d747563 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 27 Aug 2019 16:17:11 -0700 Subject: [PATCH 006/203] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4b0251156e..8061c2140d 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,7 @@ npm-debug.log # Resource binary file interface/compiledResources +*.rcc # GPUCache interface/resources/GPUCache/* From 2a6252584f23920d5f867cf3281b8610a0f27685 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 28 Aug 2019 13:10:55 -0700 Subject: [PATCH 007/203] starting HFControls --- launchers/qt/CMakeLists.txt | 4 ++- .../qt/resources/qml/HFControls/HFButton.qml | 30 +++++++++++++++++ .../resources/qml/HFControls/HFTextField.qml | 27 ++++++++++++++++ launchers/qt/resources/qml/root.qml | 32 +++---------------- launchers/qt/src/LauncherWindow.cpp | 31 ++++++++++++++++++ launchers/qt/src/LauncherWindow.h | 10 ++++++ launchers/qt/src/main.cpp | 21 ++++++------ 7 files changed, 116 insertions(+), 39 deletions(-) create mode 100644 launchers/qt/resources/qml/HFControls/HFButton.qml create mode 100644 launchers/qt/resources/qml/HFControls/HFTextField.qml create mode 100644 launchers/qt/src/LauncherWindow.cpp create mode 100644 launchers/qt/src/LauncherWindow.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index d95ff79aa8..2f5e9131c1 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -30,7 +30,9 @@ foreach(plugin ${Qt5Gui_PLUGINS}) endforeach() set(src_files - src/main.cpp) + src/main.cpp + src/LauncherWindow.h + src/LauncherWindow.cpp) add_executable(${PROJECT_NAME} ${src_files}) diff --git a/launchers/qt/resources/qml/HFControls/HFButton.qml b/launchers/qt/resources/qml/HFControls/HFButton.qml new file mode 100644 index 0000000000..3e44d74c7e --- /dev/null +++ b/launchers/qt/resources/qml/HFControls/HFButton.qml @@ -0,0 +1,30 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 + +Button { + id: control + property string backgroundColor: "#00000000" + property string borderColor: "#FFFFFF" + property string textColor: "#FFFFFF" + property int backgroundOpacity: 1 + property int backgroundRadius: 1 + property int backgroundWidth: 2 + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + color: control.backgroundColor + opacity: control.backgroundOpacity + border.color: control.borderColor + border.width: control.backgroundWidth + radius: control.backgroundRadius + } + + contentItem: Text { + text: control.text + font: control.font + color: control.textColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } +} diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml new file mode 100644 index 0000000000..b9317451d5 --- /dev/null +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -0,0 +1,27 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 + +TextField { + id: control + color: "#FFFFFF" + font.family: "Graphik Medium" + font.pixelSize: 22 + verticalAlignment: TextInput.AlignVCenter + horizontalAlignment: TextInput.AlignLeft + placeholderText: "PlaceHolder" + echoMode: TextInput.Password + property string seperatorColor: "#FFFFFF" + background: Item { + anchors.fill: parent + Rectangle { + anchors { + bottom: parent.bottom + left: parent.left + leftMargin: 7 + right: parent.right + } + height: 1 + color: control.seperatorColor + } + } +} diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 462c8bef00..13e2770636 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -2,6 +2,7 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 +import "HFControls" Image { id: root width: 515 @@ -23,11 +24,10 @@ Image { color: "#FFFFFF" } - TextField { + HFTextField { id: textField - background: Rectangle { - color: "#00000000" - } + width: 150 + height: 50 anchors { left: root.left leftMargin: 40 @@ -38,21 +38,8 @@ Image { placeholderText: "Organization" } - Rectangle { - id: seperator - anchors { - left: textField.left - right: textField.right - top: textField.bottom - topMargin: 1 - } - height: 1 - color: "#FFFFFF" - } - - - Button { + HFButton { anchors { left: root.left leftMargin: 40 @@ -61,15 +48,6 @@ Image { } id: button text: "NEXT" - background: Rectangle { - implicitWidth: 100 - implicitHeight: 40 - color: "#00000000" - opacity: 1 - border.color: "#FFFFFF" - border.width: 4 - radius: 2 - } } diff --git a/launchers/qt/src/LauncherWindow.cpp b/launchers/qt/src/LauncherWindow.cpp new file mode 100644 index 0000000000..433d67a5fe --- /dev/null +++ b/launchers/qt/src/LauncherWindow.cpp @@ -0,0 +1,31 @@ +#include "LauncherWindow.h" + +#include + +void LauncherWindow::keyPressEvent(QKeyEvent* event) { + QQuickView::keyPressEvent(event); + if (!event->isAccepted()) { + std::cout << "Key press event\n"; + } +} + +void LauncherWindow::mousePressEvent(QMouseEvent* event) { + QQuickView::mousePressEvent(event); + if (!event->isAccepted()) { + std::cout << "mouse press event\n"; + } +} + +void LauncherWindow::mouseReleaseEvent(QMouseEvent* event) { + QQuickView::mouseReleaseEvent(event); + if (!event->isAccepted()) { + std::cout << "mouse release event\n"; + } +} + +void LauncherWindow::mouseMoveEvent(QMouseEvent* event) { + QQuickView::mouseMoveEvent(event); + if (!event->isAccepted()) { + std::cout << "mouse move event\n"; + } +} diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h new file mode 100644 index 0000000000..903b0397dc --- /dev/null +++ b/launchers/qt/src/LauncherWindow.h @@ -0,0 +1,10 @@ +#include + + +class LauncherWindow : public QQuickView { +public: + void keyPressEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; +}; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 947ee242c6..1a0d9339c0 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -1,10 +1,11 @@ #include #include -#include #include #include #include #include + +#include "LauncherWindow.h" //Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QtQuick2Plugin); @@ -12,10 +13,7 @@ Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); int main(int argc, char *argv[]) { - QString name { "QtExamples" }; - - - std::cout << "Hello world\n"; + QString name { "HQLauncher" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); @@ -24,12 +22,13 @@ int main(int argc, char *argv[]) QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); - QQuickView view; - view.setFlags(Qt::FramelessWindowHint); - view.setSource(QUrl("qrc:/qml/root.qml")); - if (view.status() == QQuickView::Error) + LauncherWindow launcherWindow; + launcherWindow.setFlags(Qt::FramelessWindowHint); + launcherWindow.setSource(QUrl("qrc:/qml/root.qml")); + if (launcherWindow.status() == QQuickView::Error) { return -1; - view.setResizeMode(QQuickView::SizeRootObjectToView); - view.show(); + } + launcherWindow.setResizeMode(QQuickView::SizeRootObjectToView); + launcherWindow.show(); return app.exec(); } From 1689096967ea7f103bd0be994068abc395995807 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 28 Aug 2019 14:24:13 -0700 Subject: [PATCH 008/203] working on launcher app --- launchers/qt/CMakeLists.txt | 2 ++ launchers/qt/src/Launcher.cpp | 7 +++++++ launchers/qt/src/Launcher.h | 7 +++++++ launchers/qt/src/main.cpp | 7 ++++--- 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 launchers/qt/src/Launcher.cpp create mode 100644 launchers/qt/src/Launcher.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 2f5e9131c1..e70b001cbe 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -31,6 +31,8 @@ endforeach() set(src_files src/main.cpp + src/Launcher.h + src/Launcher.cpp src/LauncherWindow.h src/LauncherWindow.cpp) diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp new file mode 100644 index 0000000000..24a020dc92 --- /dev/null +++ b/launchers/qt/src/Launcher.cpp @@ -0,0 +1,7 @@ +#include "Launcher.h" + +Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { +} + +Launcher::~Launcher() { +} diff --git a/launchers/qt/src/Launcher.h b/launchers/qt/src/Launcher.h new file mode 100644 index 0000000000..7a049b0a1d --- /dev/null +++ b/launchers/qt/src/Launcher.h @@ -0,0 +1,7 @@ +#include + +class Launcher : public QGuiApplication { +public: + Launcher(int& argc, char** argv); + ~Launcher(); +}; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 1a0d9339c0..ac83951056 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -1,11 +1,12 @@ #include -#include + #include #include #include #include #include "LauncherWindow.h" +#include "Launcher.h" //Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QtQuick2Plugin); @@ -17,7 +18,7 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); - QGuiApplication app(argc, argv); + Launcher launcher(argc, argv); QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); @@ -30,5 +31,5 @@ int main(int argc, char *argv[]) } launcherWindow.setResizeMode(QQuickView::SizeRootObjectToView); launcherWindow.show(); - return app.exec(); + return launcher.exec(); } From 476b7605d59345505a40ba67f92a6961172d9504 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 28 Aug 2019 20:18:51 -0700 Subject: [PATCH 009/203] working on ui pages --- launchers/qt/CMakeLists.txt | 2 + launchers/qt/resources/qml/SplashScreen.qml | 13 +++++ launchers/qt/resources/qml/root.qml | 62 ++++----------------- launchers/qt/src/Launcher.cpp | 17 ++++++ launchers/qt/src/Launcher.h | 8 +++ launchers/qt/src/LauncherState.cpp | 33 +++++++++++ launchers/qt/src/LauncherState.h | 45 +++++++++++++++ launchers/qt/src/LauncherWindow.cpp | 8 +-- launchers/qt/src/main.cpp | 17 ------ 9 files changed, 132 insertions(+), 73 deletions(-) create mode 100644 launchers/qt/resources/qml/SplashScreen.qml create mode 100644 launchers/qt/src/LauncherState.cpp create mode 100644 launchers/qt/src/LauncherState.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index e70b001cbe..bd19e10341 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -33,6 +33,8 @@ set(src_files src/main.cpp src/Launcher.h src/Launcher.cpp + src/LauncherState.h + src/LauncherState.cpp src/LauncherWindow.h src/LauncherWindow.cpp) diff --git a/launchers/qt/resources/qml/SplashScreen.qml b/launchers/qt/resources/qml/SplashScreen.qml new file mode 100644 index 0000000000..793536205a --- /dev/null +++ b/launchers/qt/resources/qml/SplashScreen.qml @@ -0,0 +1,13 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 + +Item { + anchors.fill: parent + + Image { + anchors.centerIn: parent + width: 225 + height: 205 + source: "../images/hifi_logo_large@2x.png" + } +} diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 13e2770636..5a0e9fc5a4 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -2,6 +2,7 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 +import HQLauncher 1.0 import "HFControls" Image { id: root @@ -9,59 +10,16 @@ Image { height: 450 source: "../images/hifi_window@2x.png" - Text { - id: text - width: 325 - height: 26 - anchors { - left: root.left - leftMargin: 95 - top: root.top - topMargin: 29 - } - text: "Please log in" - font.pointSize: 24 - color: "#FFFFFF" + Loader { + anchors.centerIn: parent + anchors.fill: parent + id: loader } - HFTextField { - id: textField - width: 150 - height: 50 - anchors { - left: root.left - leftMargin: 40 - top: text.bottom - topMargin: 20 - } - echoMode: TextInput.Password - placeholderText: "Organization" - } - - - HFButton { - anchors { - left: root.left - leftMargin: 40 - top: textField.bottom - topMargin: 20 - } - id: button - text: "NEXT" - } - - - Image { - id: spinner - source: "../images/HiFi_Voxel.png" - anchors.bottom: root.bottom - RotationAnimator { - target: spinner; - loops: Animation.Infinite - from: 0; - to: 360; - duration: 5000 - running: true - } + Component.onCompleted: { + loader.source = LauncherState.getCurrentUISource(); + LauncherState.updateSourceUrl.connect(function(url) { + loader.source = url; + }); } } diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 24a020dc92..64a3005b28 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -1,6 +1,23 @@ #include "Launcher.h" +#include +#include +#include + +#include "LauncherWindow.h" +#include "LauncherState.h" + Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { + QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; + QResource::registerResource(resourceBinaryLocation); + _launcherState = std::make_shared(); + _launcherWindow = std::make_unique(); + _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); + _launcherWindow->setFlags(Qt::FramelessWindowHint); + LauncherState::declareQML(); + _launcherWindow->setSource(QUrl("qrc:/qml/root.qml")); + _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); + _launcherWindow->show(); } Launcher::~Launcher() { diff --git a/launchers/qt/src/Launcher.h b/launchers/qt/src/Launcher.h index 7a049b0a1d..2310c6ba08 100644 --- a/launchers/qt/src/Launcher.h +++ b/launchers/qt/src/Launcher.h @@ -1,7 +1,15 @@ +#include + #include +class LauncherWindow; +class LauncherState; class Launcher : public QGuiApplication { public: Launcher(int& argc, char** argv); ~Launcher(); + +private: + std::unique_ptr _launcherWindow; + std::shared_ptr _launcherState; }; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp new file mode 100644 index 0000000000..73197de9c1 --- /dev/null +++ b/launchers/qt/src/LauncherState.cpp @@ -0,0 +1,33 @@ +#include "LauncherState.h" + +#include + +#include + +static const std::array QML_FILE_FOR_UI_STATE = + { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml", + "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } }; + +QString LauncherState::getCurrentUISource() const { + return QML_FILE_FOR_UI_STATE[getUIState()]; +} + +void LauncherState::declareQML() { + qmlRegisterType("HQLauncher", 1, 0, "LauncherStateEnums"); +} + +void LauncherState::setUIState(UIState state) { + _uiState = state; +} + +LauncherState::UIState LauncherState::getUIState() const { + return _uiState; +} + +void LauncherState::setLastLoginError(LastLoginError lastLoginError) { + _lastLoginError = lastLoginError; +} + +LauncherState::LastLoginError LauncherState::getLastLoginError() const { + return _lastLoginError; +} diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h new file mode 100644 index 0000000000..081d6acfca --- /dev/null +++ b/launchers/qt/src/LauncherState.h @@ -0,0 +1,45 @@ +#include +#include + +class LauncherState : public QObject { + Q_OBJECT + +public: + LauncherState() = default; + ~LauncherState() = default; + + enum UIState { + SPLASH_SCREEN = 0, + LOGIN_SCREEN, + DISPLAY_NAME_SCREEN, + DOWNLOAD_SCREEN, + DOWNLOAD_FINSISHED, + ERROR_SCREEN, + UI_STATE_NUM + }; + Q_ENUMS(UIState); + + enum LastLoginError { + NONE = 0, + ORGINIZATION, + CREDENTIALS, + LAST_ERROR_NUM + }; + Q_ENUMS(LastLoginError); + Q_INVOKABLE QString getCurrentUISource() const; + + static void declareQML(); + + void setUIState(UIState state); + UIState getUIState() const; + + void setLastLoginError(LastLoginError lastLoginError); + LastLoginError getLastLoginError() const; + +signals: + void updateSourceUrl(QString sourceUrl); + +private: + UIState _uiState { SPLASH_SCREEN }; + LastLoginError _lastLoginError { NONE }; +}; diff --git a/launchers/qt/src/LauncherWindow.cpp b/launchers/qt/src/LauncherWindow.cpp index 433d67a5fe..c647ac6e4e 100644 --- a/launchers/qt/src/LauncherWindow.cpp +++ b/launchers/qt/src/LauncherWindow.cpp @@ -5,27 +5,27 @@ void LauncherWindow::keyPressEvent(QKeyEvent* event) { QQuickView::keyPressEvent(event); if (!event->isAccepted()) { - std::cout << "Key press event\n"; + // std::cout << "Key press event\n"; } } void LauncherWindow::mousePressEvent(QMouseEvent* event) { QQuickView::mousePressEvent(event); if (!event->isAccepted()) { - std::cout << "mouse press event\n"; + //std::cout << "mouse press event\n"; } } void LauncherWindow::mouseReleaseEvent(QMouseEvent* event) { QQuickView::mouseReleaseEvent(event); if (!event->isAccepted()) { - std::cout << "mouse release event\n"; + //std::cout << "mouse release event\n"; } } void LauncherWindow::mouseMoveEvent(QMouseEvent* event) { QQuickView::mouseMoveEvent(event); if (!event->isAccepted()) { - std::cout << "mouse move event\n"; + // std::cout << "mouse move event\n"; } } diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index ac83951056..bf4e303e94 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -1,9 +1,4 @@ -#include - -#include #include -#include -#include #include "LauncherWindow.h" #include "Launcher.h" @@ -19,17 +14,5 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName(name); Launcher launcher(argc, argv); - - QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; - QResource::registerResource(resourceBinaryLocation); - - LauncherWindow launcherWindow; - launcherWindow.setFlags(Qt::FramelessWindowHint); - launcherWindow.setSource(QUrl("qrc:/qml/root.qml")); - if (launcherWindow.status() == QQuickView::Error) { - return -1; - } - launcherWindow.setResizeMode(QQuickView::SizeRootObjectToView); - launcherWindow.show(); return launcher.exec(); } From a7e1efc763e0c91af9a76ea45c6ac444c76776c2 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 29 Aug 2019 15:46:58 -0700 Subject: [PATCH 010/203] UI Pages --- launchers/qt/resources/qml/DisplayName.qml | 76 ++++++++++++ launchers/qt/resources/qml/Download.qml | 101 ++++++++++++++++ .../resources/qml/HFControls/HFTextField.qml | 1 - launchers/qt/resources/qml/Login.qml | 109 ++++++++++++++++++ launchers/qt/resources/qml/root.qml | 1 - launchers/qt/src/Launcher.cpp | 1 + 6 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 launchers/qt/resources/qml/DisplayName.qml create mode 100644 launchers/qt/resources/qml/Download.qml create mode 100644 launchers/qt/resources/qml/Login.qml diff --git a/launchers/qt/resources/qml/DisplayName.qml b/launchers/qt/resources/qml/DisplayName.qml new file mode 100644 index 0000000000..af688e549e --- /dev/null +++ b/launchers/qt/resources/qml/DisplayName.qml @@ -0,0 +1,76 @@ +// login + +import QtQuick 2.3 +import QtQuick 2.1 +import "HFControls" + +Item { + id: root + anchors.fill: parent + Text { + id: title + width: 325 + height: 26 + font.family: "Graphik" + font.pixelSize: 28 + color: "#FFFFFF" + text: "Choose a display name" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors { + top: root.top + topMargin: 29 + horizontalCenter: root.horizontalCenter + } + } + + Text { + id: instruction + width: 425 + height: 22 + font.family: "Graphik" + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "#C4C4C4" + text: "This is the name that your teammates will see." + anchors { + left: root.left + right: root.right + top: title.bottom + topMargin: 18 + } + } + + HFTextField { + id: password + width: 306 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: "Display Name" + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: instruction.bottom + horizontalCenter: instruction.horizontalCenter + topMargin: 79 + } + } + + HFButton { + id: button + width: 122 + height: 36 + + font.family: "Graphik" + font.pixelSize: 18 + text: "NEXT" + + anchors { + top: password.bottom + horizontalCenter: instruction.horizontalCenter + topMargin: 59 + } + } +} diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml new file mode 100644 index 0000000000..011dbbe6cf --- /dev/null +++ b/launchers/qt/resources/qml/Download.qml @@ -0,0 +1,101 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 + +Item { + id: root + anchors.fill: parent + + Image { + id: logo + width: 150 + height: 150 + source: "../images/HiFi_Voxel.png" + + anchors { + top: root.top + topMargin: 48 + horizontalCenter: root.horizontalCenter + } + + RotationAnimator { + target: logo; + loops: Animation.Infinite + from: 0; + to: 360; + duration: 5000 + running: true + } + } + + Text { + id: firstText + width: 300 + height: 41 + font.family: "Graphik" + font.pixelSize: 28 + text: "Setup will take a moment" + color: "#FFFFFF" + + anchors { + top: logo.bottom + topMargin: 46 + horizontalCenter: logo.horizontalCenter + } + } + + Text { + id: secondText + font.family: "Graphik" + font.pixelSize: 14 + text: "We're getting your headquaters ready" + color: "#FFFFFF" + + anchors { + top: firstText.bottom + topMargin: 14 + horizontalCenter: logo.horizontalCenter + } + } + + ProgressBar { + id: progressBar + width: 394 + height: 12 + + anchors { + top: secondText.bottom + topMargin: 30 + horizontalCenter: logo.horizontalCenter + } + + background: Rectangle { + implicitWidth: progressBar.width + implicitHeight: progressBar.height + radius: 4 + color: "#252525" + } + + contentItem: Item { + implicitWidth: progressBar.width + implicitHeight: progressBar.height * 0.90 + + Rectangle { + width: progressBar.visualPosition * parent.width + height: parent.height + radius: 3 + color: "#01B2ED" + } + } + + + PropertyAnimation { + target: progressBar; + loops: Animation.Infinite + property: "value" + from: 0; + to: 1; + duration: 5000 + running: true + } + } +} diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index b9317451d5..77a6601fdf 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -9,7 +9,6 @@ TextField { verticalAlignment: TextInput.AlignVCenter horizontalAlignment: TextInput.AlignLeft placeholderText: "PlaceHolder" - echoMode: TextInput.Password property string seperatorColor: "#FFFFFF" background: Item { anchors.fill: parent diff --git a/launchers/qt/resources/qml/Login.qml b/launchers/qt/resources/qml/Login.qml new file mode 100644 index 0000000000..fb4dd2367b --- /dev/null +++ b/launchers/qt/resources/qml/Login.qml @@ -0,0 +1,109 @@ +// login + +import QtQuick 2.3 +import QtQuick 2.1 +import "HFControls" + +Item { + id: root + anchors.fill: parent + Text { + id: title + width: 325 + height: 26 + font.family: "Graphik" + font.pixelSize: 28 + color: "#FFFFFF" + text: "Please Log in" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors { + top: root.top + topMargin: 29 + horizontalCenter: root.horizontalCenter + } + } + + Text { + id: instruction + width: 425 + height: 22 + font.family: "Graphik" + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "#C4C4C4" + text: "Be sure you've uploaded your Avatar before signing in." + anchors { + left: root.left + right: root.right + top: title.bottom + topMargin: 18 + } + } + + HFTextField { + id: organization + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: "Organization name" + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: instruction.bottom + horizontalCenter: instruction.horizontalCenter + topMargin: 24 + } + } + + HFTextField { + id: username + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: "Username" + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: organization.bottom + horizontalCenter: instruction.horizontalCenter + topMargin: 28 + } + } + + HFTextField { + id: password + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: "Password" + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + echoMode: TextInput.Password + anchors { + top: username.bottom + horizontalCenter: instruction.horizontalCenter + topMargin: 28 + } + } + + HFButton { + id: button + width: 122 + height: 36 + + font.family: "Graphik" + font.pixelSize: 18 + text: "NEXT" + + anchors { + top: password.bottom + horizontalCenter: instruction.horizontalCenter + topMargin: 48 + } + } +} diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 5a0e9fc5a4..0ceda5b189 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -11,7 +11,6 @@ Image { source: "../images/hifi_window@2x.png" Loader { - anchors.centerIn: parent anchors.fill: parent id: loader } diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 64a3005b28..10d30a7285 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -11,6 +11,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); + _launcherState->setUIState(LauncherState::DOWNLOAD_SCREEN); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->setFlags(Qt::FramelessWindowHint); From 9c062e32282f92ded585deba6dd29606ea3ddac4 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 29 Aug 2019 15:48:53 -0700 Subject: [PATCH 011/203] progrss bar touch up --- launchers/qt/resources/qml/Download.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index 011dbbe6cf..8020889e8a 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -60,7 +60,7 @@ Item { ProgressBar { id: progressBar width: 394 - height: 12 + height: 8 anchors { top: secondText.bottom @@ -71,18 +71,18 @@ Item { background: Rectangle { implicitWidth: progressBar.width implicitHeight: progressBar.height - radius: 4 + radius: 8 color: "#252525" } contentItem: Item { implicitWidth: progressBar.width - implicitHeight: progressBar.height * 0.90 + implicitHeight: progressBar.height * 0.85 Rectangle { width: progressBar.visualPosition * parent.width height: parent.height - radius: 3 + radius: 6 color: "#01B2ED" } } From 1f4e15314c73516a03a7767dfc9a51f244446ac0 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 30 Aug 2019 08:48:25 -0700 Subject: [PATCH 012/203] enable dragging the window --- launchers/qt/src/LauncherWindow.cpp | 19 +++++++++++++------ launchers/qt/src/LauncherWindow.h | 6 +++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/launchers/qt/src/LauncherWindow.cpp b/launchers/qt/src/LauncherWindow.cpp index c647ac6e4e..b86c2ca84c 100644 --- a/launchers/qt/src/LauncherWindow.cpp +++ b/launchers/qt/src/LauncherWindow.cpp @@ -2,30 +2,37 @@ #include +#include + void LauncherWindow::keyPressEvent(QKeyEvent* event) { QQuickView::keyPressEvent(event); if (!event->isAccepted()) { - // std::cout << "Key press event\n"; } } void LauncherWindow::mousePressEvent(QMouseEvent* event) { QQuickView::mousePressEvent(event); if (!event->isAccepted()) { - //std::cout << "mouse press event\n"; + if (event->button() == Qt::LeftButton) { + _drag = true; + _previousMousePos = QCursor::pos(); + } } } void LauncherWindow::mouseReleaseEvent(QMouseEvent* event) { QQuickView::mouseReleaseEvent(event); - if (!event->isAccepted()) { - //std::cout << "mouse release event\n"; - } + _drag = false; } void LauncherWindow::mouseMoveEvent(QMouseEvent* event) { QQuickView::mouseMoveEvent(event); if (!event->isAccepted()) { - // std::cout << "mouse move event\n"; + if (_drag) { + QPoint cursorPos = QCursor::pos(); + QPoint offset = _previousMousePos - cursorPos; + _previousMousePos = cursorPos; + setPosition(position() - offset); + } } } diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h index 903b0397dc..394d9aed41 100644 --- a/launchers/qt/src/LauncherWindow.h +++ b/launchers/qt/src/LauncherWindow.h @@ -1,5 +1,5 @@ #include - +#include class LauncherWindow : public QQuickView { public: @@ -7,4 +7,8 @@ public: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; + +private: + bool _drag { false }; + QPoint _previousMousePos; }; From 44bbe1ea36e08681c222fa9a8e88f13a099c92b2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 29 Aug 2019 16:12:47 -0700 Subject: [PATCH 013/203] Add qt+ssl to qt launcher --- cmake/init.cmake | 6 ++- launchers/qt/CMakeLists.txt | 50 +++++++++++++++++-- launchers/qt/resources/qml/Login.qml | 2 + launchers/qt/src/Launcher.cpp | 2 +- launchers/qt/src/LauncherState.cpp | 75 ++++++++++++++++++++++++++++ launchers/qt/src/LauncherState.h | 23 ++++++++- launchers/qt/src/main.cpp | 4 ++ 7 files changed, 154 insertions(+), 8 deletions(-) diff --git a/cmake/init.cmake b/cmake/init.cmake index 3f632b30f8..fb72e828ea 100644 --- a/cmake/init.cmake +++ b/cmake/init.cmake @@ -30,12 +30,14 @@ endif () # CMAKE_CURRENT_SOURCE_DIR is the parent folder here set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") -set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(HF_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}") set(MACRO_DIR "${HF_CMAKE_DIR}/macros") set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals") -file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake") +file(GLOB HIFI_CUSTOM_MACROS "${HF_CMAKE_DIR}/macros/*.cmake") +message("CMAKE FILEs: ${CMAKE_CURRENT_LIST_DIR} ${MACRO_DIR} ||") foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) + message("CMAKE FILE: ${CUSTOM_MACRO}") include(${CUSTOM_MACRO}) endforeach() unset(HIFI_CUSTOM_MACROS) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index bd19e10341..ad31023cb7 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -1,14 +1,50 @@ cmake_minimum_required(VERSION 3.11) + project(HQLauncher) + set(CMAKE_CXX_STANDARD_REQUIRED_ON) -include("cmake/init.cmake") -find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED) -find_package(OpenGL) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) #set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) + +include("cmake/init.cmake") + +include(ExternalProject) + +if (WIN32) + + ExternalProject_Add( + qtlite + URL "https://hifi-public.s3.amazonaws.com/huffman/launcher/qt-lite-ssl.zip" + URL_HASH MD5=83eeba1565e5727aef11655acf893c15 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + ) + + + ExternalProject_Get_Property(qtlite SOURCE_DIR) + ExternalProject_Get_Property(qtlite STAMP_DIR) + + include("${STAMP_DIR}/download-qtlite.cmake") + include("${STAMP_DIR}/extract-qtlite.cmake") + include("${STAMP_DIR}/verify-qtlite.cmake") + + message("${SOURCE_DIR}/lib/cmake") + + list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake) + + set(SSL_DIR ${SOURCE_DIR}/ssl) + message("SSL dir is ${SSL_DIR}") + +endif () + +find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED) +find_package(OpenGL) + set(CUSTOM_LAUNCHER_QRC_PATHS "") set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc) @@ -38,9 +74,12 @@ set(src_files src/LauncherWindow.h src/LauncherWindow.cpp) +set(TARGET_NAME ${PROJECT_NAME}) add_executable(${PROJECT_NAME} ${src_files}) -target_link_libraries(${PROJECT_NAME} PUBLIC +#target_openssl() + +target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::QuickControls2 @@ -51,6 +90,9 @@ target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENGL_LIBRARIES} ${plugin_libs} wsock32 ws2_32 Winmm version imm32 dwmapi + Crypt32 Iphlpapi + "${SSL_DIR}/lib/libeay32.lib" + "${SSL_DIR}/lib/ssleay32.lib" "${_qt5Core_install_prefix}/lib/qtpcre2.lib" "${_qt5Core_install_prefix}/lib/qtlibpng.lib" "${_qt5Core_install_prefix}/lib/qtfreetype.lib" diff --git a/launchers/qt/resources/qml/Login.qml b/launchers/qt/resources/qml/Login.qml index fb4dd2367b..c6cb949cca 100644 --- a/launchers/qt/resources/qml/Login.qml +++ b/launchers/qt/resources/qml/Login.qml @@ -105,5 +105,7 @@ Item { horizontalCenter: instruction.horizontalCenter topMargin: 48 } + + onClicked: LauncherState.login(username.text, password.text) } } diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 10d30a7285..4c430e41ec 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -11,7 +11,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); - _launcherState->setUIState(LauncherState::DOWNLOAD_SCREEN); + _launcherState->setUIState(LauncherState::LOGIN_SCREEN); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->setFlags(Qt::FramelessWindowHint); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 73197de9c1..214f587d2c 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -2,12 +2,68 @@ #include +#include +#include + +#include +#include +#include +#include + +#include #include static const std::array QML_FILE_FOR_UI_STATE = { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml", "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } }; +LauncherState::LauncherState() { + // TODO Show splash screen until this request is complete + auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json")); + auto reply = _networkAccessManager.get(*request); + QObject::connect(reply, &QNetworkReply::finished, [reply, this]() { + if (reply->error()) { + qDebug() << "Error getting builds from thunder: " << reply->errorString(); + } else { + qDebug() << "Builds reply has been received"; + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + if (parseError.error) { + qDebug() << "Error parsing response from thunder: " << data; + } else { + auto root = doc.object(); + if (!root.contains("default_tag")) { + return; + } + + _latestBuilds.defaultTag = root["default_tag"].toString(); + + auto results = root["results"]; + if (!results.isArray()) { + return; + } + + for (auto result : results.toArray()) { + auto entry = result.toObject(); + Build build; + build.tag = entry["name"].toString(); + build.latestVersion = entry["latest_version"].toInt(); + build.buildNumber = entry["build_number"].toInt(); +#ifdef Q_OS_WIN + build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString(); +#elif defined(Q_OS_MACOS) + build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString(); +#else + #error "Launcher is only supported on Windows and Mac OS" +#endif + _latestBuilds.builds.push_back(build); + } + } + } + }); +} + QString LauncherState::getCurrentUISource() const { return QML_FILE_FOR_UI_STATE[getUIState()]; } @@ -31,3 +87,22 @@ void LauncherState::setLastLoginError(LastLoginError lastLoginError) { LauncherState::LastLoginError LauncherState::getLastLoginError() const { return _lastLoginError; } + +void LauncherState::login(QString username, QString password) { + qDebug() << "Got login: " << username << password; + + auto request = new QNetworkRequest(QUrl("https://metaverse.highfidelity.com/oauth/token")); + + request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QUrlQuery query; + query.addQueryItem("grant_type", "password"); + query.addQueryItem("username", username); + query.addQueryItem("password", password); + query.addQueryItem("scope", "owner"); + + auto reply = _networkAccessManager.post(*request, query.toString().toUtf8()); + + QObject::connect(reply, &QNetworkReply::finished, [reply, this]() { + qDebug() << "Got response for login: " << reply->readAll(); + }); +} diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 081d6acfca..73e512be63 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -1,11 +1,24 @@ #include #include +#include + +struct Build { + QString tag; + int latestVersion; + int buildNumber; + QString installerZipURL; +}; + +struct LatestBuilds { + QString defaultTag; + std::vector builds; +}; class LauncherState : public QObject { Q_OBJECT public: - LauncherState() = default; + LauncherState(); ~LauncherState() = default; enum UIState { @@ -36,10 +49,18 @@ public: void setLastLoginError(LastLoginError lastLoginError); LastLoginError getLastLoginError() const; + + + // LOGIN + Q_INVOKABLE void login(QString username, QString password); + signals: void updateSourceUrl(QString sourceUrl); private: + QNetworkAccessManager _networkAccessManager; + LatestBuilds _latestBuilds; + UIState _uiState { SPLASH_SCREEN }; LastLoginError _lastLoginError { NONE }; }; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index bf4e303e94..50bfb5f59d 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -2,11 +2,13 @@ #include "LauncherWindow.h" #include "Launcher.h" + //Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); + int main(int argc, char *argv[]) { QString name { "HQLauncher" }; @@ -14,5 +16,7 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName(name); Launcher launcher(argc, argv); + return launcher.exec(); + } From 3f865a430b67a2ed29bc328ea932f1ab440b9a18 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 30 Aug 2019 09:10:47 -0700 Subject: [PATCH 014/203] splash screen to login page transititon --- launchers/qt/src/Launcher.cpp | 2 +- launchers/qt/src/LauncherState.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 4c430e41ec..20405d5a39 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -11,7 +11,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); - _launcherState->setUIState(LauncherState::LOGIN_SCREEN); + _launcherState->setUIState(LauncherState::SPLASH_SCREEN); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->setFlags(Qt::FramelessWindowHint); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 214f587d2c..52e13bc3b7 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -61,6 +61,7 @@ LauncherState::LauncherState() { } } } + setUIState(LauncherState::LOGIN_SCREEN); }); } @@ -74,6 +75,7 @@ void LauncherState::declareQML() { void LauncherState::setUIState(UIState state) { _uiState = state; + emit updateSourceUrl(getCurrentUISource()); } LauncherState::UIState LauncherState::getUIState() const { From 1f1115f98330dd0089ce515ec0bc8feafddb123d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 30 Aug 2019 13:41:46 -0700 Subject: [PATCH 015/203] Add ApplicationState to qt launcher --- launchers/qt/src/LauncherState.cpp | 150 +++++++++++++++++++--------- launchers/qt/src/LauncherState.h | 40 +++++++- launchers/qt/src/LauncherWindow.cpp | 3 + launchers/qt/src/main.cpp | 3 +- 4 files changed, 144 insertions(+), 52 deletions(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 52e13bc3b7..9c5f66f9cf 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -17,52 +17,15 @@ static const std::array QML_FILE_ { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml", "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } }; +void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) const { + if (_appState != state) { + __debugbreak(); + exit(0); + } +} + LauncherState::LauncherState() { - // TODO Show splash screen until this request is complete - auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json")); - auto reply = _networkAccessManager.get(*request); - QObject::connect(reply, &QNetworkReply::finished, [reply, this]() { - if (reply->error()) { - qDebug() << "Error getting builds from thunder: " << reply->errorString(); - } else { - qDebug() << "Builds reply has been received"; - auto data = reply->readAll(); - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(data, &parseError); - if (parseError.error) { - qDebug() << "Error parsing response from thunder: " << data; - } else { - auto root = doc.object(); - if (!root.contains("default_tag")) { - return; - } - - _latestBuilds.defaultTag = root["default_tag"].toString(); - - auto results = root["results"]; - if (!results.isArray()) { - return; - } - - for (auto result : results.toArray()) { - auto entry = result.toObject(); - Build build; - build.tag = entry["name"].toString(); - build.latestVersion = entry["latest_version"].toInt(); - build.buildNumber = entry["build_number"].toInt(); -#ifdef Q_OS_WIN - build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString(); -#elif defined(Q_OS_MACOS) - build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString(); -#else - #error "Launcher is only supported on Windows and Mac OS" -#endif - _latestBuilds.builds.push_back(build); - } - } - } - setUIState(LauncherState::LOGIN_SCREEN); - }); + requestBuilds(); } QString LauncherState::getCurrentUISource() const { @@ -90,7 +53,74 @@ LauncherState::LastLoginError LauncherState::getLastLoginError() const { return _lastLoginError; } +void LauncherState::requestBuilds() { + ASSERT_STATE(ApplicationState::INIT); + _appState = ApplicationState::REQUESTING_BUILDS; + + // TODO Show splash screen until this request is complete + auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json")); + auto reply = _networkAccessManager.get(*request); + + QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedBuildsReply); +} + +void LauncherState::receivedBuildsReply() { + auto reply = static_cast(sender()); + + ASSERT_STATE(ApplicationState::REQUESTING_BUILDS); + + if (reply->error()) { + qDebug() << "Error getting builds from thunder: " << reply->errorString(); + } else { + qDebug() << "Builds reply has been received"; + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + if (parseError.error) { + qDebug() << "Error parsing response from thunder: " << data; + } else { + auto root = doc.object(); + if (!root.contains("default_tag")) { + _appState = ApplicationState::REQUESTING_BUILDS_FAILED; + setUIState(LauncherState::ERROR_SCREEN); + return; + } + + _latestBuilds.defaultTag = root["default_tag"].toString(); + + auto results = root["results"]; + if (!results.isArray()) { + _appState = ApplicationState::REQUESTING_BUILDS_FAILED; + setUIState(LauncherState::ERROR_SCREEN); + return; + } + + for (auto result : results.toArray()) { + auto entry = result.toObject(); + Build build; + build.tag = entry["name"].toString(); + build.latestVersion = entry["latest_version"].toInt(); + build.buildNumber = entry["build_number"].toInt(); +#ifdef Q_OS_WIN + build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString(); +#elif defined(Q_OS_MACOS) + build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString(); +#else + #error "Launcher is only supported on Windows and Mac OS" +#endif + _latestBuilds.builds.push_back(build); + } + } + } + _appState = ApplicationState::WAITING_FOR_LOGIN; + setUIState(LauncherState::LOGIN_SCREEN); +} + void LauncherState::login(QString username, QString password) { + ASSERT_STATE(ApplicationState::WAITING_FOR_LOGIN); + + _appState = ApplicationState::REQUESTING_LOGIN; + qDebug() << "Got login: " << username << password; auto request = new QNetworkRequest(QUrl("https://metaverse.highfidelity.com/oauth/token")); @@ -104,7 +134,31 @@ void LauncherState::login(QString username, QString password) { auto reply = _networkAccessManager.post(*request, query.toString().toUtf8()); - QObject::connect(reply, &QNetworkReply::finished, [reply, this]() { - qDebug() << "Got response for login: " << reply->readAll(); - }); + QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply); +} + +Q_INVOKABLE void LauncherState::receivedLoginReply() { + auto reply = static_cast(sender()); + + ASSERT_STATE(ApplicationState::REQUESTING_LOGIN); + + qDebug() << "Got response for login: " << reply->readAll(); + + + download(); +} + +void LauncherState::download() { + _appState = ApplicationState::DOWNLOADING_CONTENT; + setUIState(LauncherState::DOWNLOAD_SCREEN); +} + +void LauncherState::contentDownloadComplete() { +} + +void LauncherState::clientDownloadComplete() { +} + +void LauncherState::launchClient() { + _appState = ApplicationState::LAUNCHING_HIGH_FIDELITY; } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 73e512be63..62a58c9778 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -32,6 +32,28 @@ public: }; Q_ENUMS(UIState); + enum class ApplicationState { + INIT, + + REQUESTING_BUILDS, + REQUESTING_BUILDS_FAILED, + + WAITING_FOR_LOGIN, + REQUESTING_LOGIN, + + WAITING_FOR_SIGNUP, + REQUESTING_SIGNUP, + + DOWNLOADING_CONTENT, + DOWNLOADING_HIGH_FIDELITY, + + EXTRACTING_DATA, + + LAUNCHING_HIGH_FIDELITY + }; + Q_ENUMS(ApplicationState); + + enum LastLoginError { NONE = 0, ORGINIZATION, @@ -41,6 +63,8 @@ public: Q_ENUMS(LastLoginError); Q_INVOKABLE QString getCurrentUISource() const; + void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) const; + static void declareQML(); void setUIState(UIState state); @@ -49,10 +73,21 @@ public: void setLastLoginError(LastLoginError lastLoginError); LastLoginError getLastLoginError() const; + // Request builds + void requestBuilds(); + Q_INVOKABLE void receivedBuildsReply(); - - // LOGIN + // Login Q_INVOKABLE void login(QString username, QString password); + Q_INVOKABLE void receivedLoginReply(); + + // Download + void download(); + Q_INVOKABLE void contentDownloadComplete(); + Q_INVOKABLE void clientDownloadComplete(); + + // Launching + void launchClient(); signals: void updateSourceUrl(QString sourceUrl); @@ -61,6 +96,7 @@ private: QNetworkAccessManager _networkAccessManager; LatestBuilds _latestBuilds; + ApplicationState _appState { ApplicationState::INIT }; UIState _uiState { SPLASH_SCREEN }; LastLoginError _lastLoginError { NONE }; }; diff --git a/launchers/qt/src/LauncherWindow.cpp b/launchers/qt/src/LauncherWindow.cpp index b86c2ca84c..a88df8efe7 100644 --- a/launchers/qt/src/LauncherWindow.cpp +++ b/launchers/qt/src/LauncherWindow.cpp @@ -7,6 +7,9 @@ void LauncherWindow::keyPressEvent(QKeyEvent* event) { QQuickView::keyPressEvent(event); if (!event->isAccepted()) { + if (event->key() == Qt::Key_Escape) { + exit(0); + } } } diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 50bfb5f59d..9ed6f42390 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -9,8 +9,7 @@ Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { QString name { "HQLauncher" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); From 37db15b5a3bcbcafe4e56389f6589abd27300376 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 3 Sep 2019 14:50:45 -0700 Subject: [PATCH 016/203] getting mac cmake to work --- launchers/qt/CMakeLists.txt | 149 +- launchers/qt/cmake/installer/Thumbs.db | Bin 0 -> 7168 bytes .../qt/cmake/installer/install-folder.rsrc | 1634 +++++++++++++++++ .../qt/cmake/installer/installer-header.bmp | Bin 0 -> 102656 bytes launchers/qt/cmake/installer/installer.ico | Bin 0 -> 306053 bytes .../qt/cmake/installer/uninstaller-header.bmp | Bin 0 -> 102656 bytes .../cmake/macros/SetPackagingParameters.cmake | 45 + .../qt/cmake/modules/FindQtStaticDeps.cmake | 31 + .../cmake/modules/MacOSXBundleInfo.plist.in | 37 + launchers/qt/src/LauncherState.h | 4 +- launchers/qt/src/main.cpp | 4 +- 11 files changed, 1874 insertions(+), 30 deletions(-) create mode 100644 launchers/qt/cmake/installer/Thumbs.db create mode 100644 launchers/qt/cmake/installer/install-folder.rsrc create mode 100644 launchers/qt/cmake/installer/installer-header.bmp create mode 100644 launchers/qt/cmake/installer/installer.ico create mode 100644 launchers/qt/cmake/installer/uninstaller-header.bmp create mode 100644 launchers/qt/cmake/macros/SetPackagingParameters.cmake create mode 100644 launchers/qt/cmake/modules/FindQtStaticDeps.cmake create mode 100644 launchers/qt/cmake/modules/MacOSXBundleInfo.plist.in diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index ad31023cb7..47ab8f75b9 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -1,18 +1,39 @@ cmake_minimum_required(VERSION 3.11) - project(HQLauncher) -set(CMAKE_CXX_STANDARD_REQUIRED_ON) +if (APPLE) + set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9) +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules") +include("cmake/macros/SetPackagingParameters.cmake") +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) #set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) - +#add_compile_options(-W -Wall -Wextra -Wpedantic -Werror -std=c++1z) include("cmake/init.cmake") +include("cmake/macros/SetPackagingParameters.cmake") + + +function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) + if (NOT DEFINED ${_RESULT_NAME}) + if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "") + set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE) + else() + set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE) + endif() + endif() +endfunction() include(ExternalProject) +if (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-framework Cocoa -framework CoreServices -framework Carbon -framework IOKit -framework Security -framework SystemConfiguration") +endif() if (WIN32) ExternalProject_Add( @@ -42,8 +63,43 @@ if (WIN32) endif () + +if (APPLE) + ExternalProject_Add( + qtlite + URL "https://dantescalves.com/launcher//qt-lite-ssl-mac.zip" + URL_HASH MD5=2b073937a7c5dbae6e6ed244d2063398 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + ) + + + ExternalProject_Get_Property(qtlite SOURCE_DIR) + ExternalProject_Get_Property(qtlite STAMP_DIR) + + include("${STAMP_DIR}/download-qtlite.cmake") + include("${STAMP_DIR}/extract-qtlite.cmake") + include("${STAMP_DIR}/verify-qtlite.cmake") + + message("${SOURCE_DIR}/lib/cmake") + + list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake) + + set(SSL_DIR ${SOURCE_DIR}/ssl) + set(OPENSSL_ROOT_DIR ${SSL_DIR}) + message("SSL dir is ${SSL_DIR}") +endif() + +if (APPLE) + set(OPENSSL_USE_STATIC_LIBS TRUE) + find_package(OpenSSL REQUIRED) +endif() + find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED) -find_package(OpenGL) +find_package(OpenGL REQUIRED) +find_package(QtStaticDeps REQUIRED) set(CUSTOM_LAUNCHER_QRC_PATHS "") set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) @@ -61,7 +117,6 @@ add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) foreach(plugin ${Qt5Gui_PLUGINS}) get_target_property(_loc ${plugin} LOCATION) - message("Plugin ${plugin} is at location ${_loc}") set(plugin_libs ${plugin_libs} ${_loc}) endforeach() @@ -75,9 +130,18 @@ set(src_files src/LauncherWindow.cpp) set(TARGET_NAME ${PROJECT_NAME}) -add_executable(${PROJECT_NAME} ${src_files}) -#target_openssl() + +set_packaging_parameters() +if (WIN32) + add_executable(${PROJECT_NAME} ${src_files}) +elseif (APPLE) + set_target_properties(${this_target} PROPERTIES + MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) + + set(MACOSX_BUNDLE_ICON_FILE "interface.icns") + add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files}) +endif() target_link_libraries(${PROJECT_NAME} Qt5::Core @@ -89,31 +153,38 @@ target_link_libraries(${PROJECT_NAME} ${Qt_LIBRARIES} ${OPENGL_LIBRARIES} ${plugin_libs} - wsock32 ws2_32 Winmm version imm32 dwmapi - Crypt32 Iphlpapi - "${SSL_DIR}/lib/libeay32.lib" - "${SSL_DIR}/lib/ssleay32.lib" - "${_qt5Core_install_prefix}/lib/qtpcre2.lib" - "${_qt5Core_install_prefix}/lib/qtlibpng.lib" - "${_qt5Core_install_prefix}/lib/qtfreetype.lib" - "${_qt5Core_install_prefix}/lib/Qt5AccessibilitySupport.lib" - "${_qt5Core_install_prefix}/lib/Qt5FbSupport.lib" - "${_qt5Core_install_prefix}/lib/Qt5OpenGLExtensions.lib" - "${_qt5Core_install_prefix}/lib/Qt5QuickTemplates2.lib" - "${_qt5Core_install_prefix}/lib/Qt5FontDatabaseSupport.lib" - "${_qt5Core_install_prefix}/lib/Qt5ThemeSupport.lib" -# "${_qt5Core_install_prefix}/lib/Qt5Bootstrap.lib" - "${_qt5Core_install_prefix}/lib/Qt5EventDispatcherSupport.lib" - "${_qt5Core_install_prefix}/qml/QtQuick.2/qtquick2plugin.lib" - "${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/qtquickcontrols2plugin.lib" - "${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib") + ${QT_STATIC_LIBS} + ) + +if (WIN32) + target_link_libraries(${PROJECT_NAME} + wsock32 ws2_32 Winmm version imm32 dwmapi + Crypt32 Iphlpapi + "${SSL_DIR}/lib/libeay32.lib" + "${SSL_DIR}/lib/ssleay32.lib" + "${_qt5Core_install_prefix}/qml/QtQuick.2/qtquick2plugin.lib" + "${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/qtquickcontrols2plugin.lib" + "${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib") +elseif (APPLE) + target_link_libraries(${PROJECT_NAME} + ${OPENSSL_LIBRARIES} + "${_qt5Core_install_prefix}/qml/QtQuick.2/libqtquick2plugin.a" + "${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/libqtquickcontrols2plugin.a" + "${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/libqtquicktemplates2plugin.a" + "${_qt5Core_install_prefix}/plugins/platforms/libqcocoa.a") +endif() target_include_directories(${PROJECT_NAME} PUBLIC ${Qt5Core_INCLUDE_DIRS} ${Qt5Quick_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} - ${Qt5Qml_INCLUDE_DIRS}) + ${Qt5Qml_INCLUDE_DIRS} + ) +if (APPLE) + target_include_directories(${PROJECT_NAME} PUBLIC + ${OPENSSL_INCLUDE_DIR}) +endif() set(INTERFACE_EXEC_DIR "$") set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") @@ -121,3 +192,29 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${RESOURCES_RCC}" "${INTERFACE_EXEC_DIR}") + + + +if (APPLE) + install( + TARGETS HQLauncher + BUNDLE DESTINATION "." + COMPONENT applications) + + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) + + include(CPackComponent) + + set(CPACK_PACKAGE_NAME "HQ Launcher") + set(CPACK_PACKAGE_VENDOR "High Fidelity") + set(CPACK_PACKAGE_FILE_NAME "HQ Launcher") + + set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) + + set(DMG_SUBFOLDER_NAME "High Fidelity") + set(ESCAPED_DMG_SUBFOLDER_NAME "") + set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc") + + set(CPACK_GENERATOR "DragNDrop") + include(CPack) +endif() diff --git a/launchers/qt/cmake/installer/Thumbs.db b/launchers/qt/cmake/installer/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..86bc943370371869ad9d9b95e647aa37f386685c GIT binary patch literal 7168 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 literal 0 HcmV?d00001 diff --git a/launchers/qt/cmake/installer/install-folder.rsrc b/launchers/qt/cmake/installer/install-folder.rsrc new file mode 100644 index 0000000000..8ce12afd3f --- /dev/null +++ b/launchers/qt/cmake/installer/install-folder.rsrc @@ -0,0 +1,1634 @@ +data 'icns' (-16455) { + $"6963 6E73 0000 65EB 544F 4320 0000 0018" /* icns..eëTOC .... */ + $"6974 3332 0000 25C3 7438 6D6B 0000 4008" /* it32..%Ãt8mk..@. */ + $"6974 3332 0000 25C3 0000 0000 FF00 FF00" /* it32..%Ã....ÿ.ÿ. */ + $"FF00 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ. */ + $"FF00 FF00 EE00 0005 A008 0007 D500 0309" /* ÿ.ÿ.î... ...Õ..Æ */ + $"549A BBA0 C102 C0A3 48D2 0003 2FB2 E4E3" /* Tš» Á.À£HÒ../²äã */ + $"A2DF 03E3 E176 01CF 0002 30C9 DDA5 D502" /* ¢ß.ãáv.Ï..0ÉÝ¥Õ. */ + $"D7DE 61CE 0002 0EB1 D6A7 D002 D2CC 2BCD" /* ×ÞaÎ...±Ö§Ð.ÒÌ+Í */ + $"0001 5BD4 A9CA 02D1 9A05 CB00 0202 9ACA" /* ..[Ô©Ê.Ñš.Ë...šÊ */ + $"AAC5 01CC 4FCB 0002 0EAF C1AA BF02 C2AF" /* ªÅ.ÌOË...¯Áª¿.¯ */ + $"16CA 0002 14AE BCAA BA03 B9C1 7F02 C900" /* .Ê...®¼ªº.¹Á..É. */ + $"0213 A9B6 ACB4 02BA 6503 C800 0212 A4B1" /* ..©¶¬´.ºe.È...¤± */ + $"ADAF 03B4 803B 26BA 2502 2415 0186 0002" /* ­¯.´€;&º%.$..†.. */ + $"129F ABAE A902 AEAE AAB9 A905 AAA9 A184" /* .Ÿ«®©.®®ª¹©.ª©¡„ */ + $"4003 8400 0211 9AA5 EFA4 04A5 A9AA 740E" /* @.„...š¥ï¤.¥©ªt. */ + $"8300 0211 949F 819E EC9D 049E 9EA6 7607" /* ƒ...”Ÿžì.žž¦v. */ + $"8200 0710 8F9A 9997 9698 9CEA 9F05 9E9A" /* ‚...š™—–˜œêŸ.žš */ + $"9797 A04A 8200 080F 8A94 919C B5CE DDE4" /* —— J‚...Š”‘œµÎÝä */ + $"E9E5 06E2 D5BA 9F94 840B 8100 050F 848F" /* éå.âÕºŸ”„....„ */ + $"B2E1 F9EE FF04 F6D3 A78F 2881 0003 0E80" /* ²áùîÿ.öÓ§(...€ */ + $"BEF6 F1FF 03FD DBA3 3881 0002 0BA1 F5F3" /* ¾öñÿ.ýÛ£8...¡õó */ + $"FF02 FECD 3C81 0001 16D1 F5FF 01ED 4781" /* ÿ.þÍ<...Ñõÿ.íG */ + $"0001 36F1 F5FF 01F9 5281 0001 43F7 F5FF" /* ..6ñõÿ.ùR..C÷õÿ */ + $"01FB 5A81 0002 43F6 FFF3 FE02 FFFB 5F81" /* .ûZ..Cöÿóþ.ÿû_ */ + $"0002 43F5 FFF3 FD02 FEFA 5F81 0002 43F5" /* ..Cõÿóý.þú_..Cõ */ + $"FEB5 FC06 FDFE FEFF FEFE FDB4 FC02 FDF9" /* þµü.ýþþÿþþý´ü.ýù */ + $"5F81 0002 43F5 FEB0 FC00 FD81 FF06 FAF2" /* _..Cõþ°ü.ýÿ.úò */ + $"EDEB EEF4 FD81 FF00 FDAF FC02 FDF9 5F81" /* íëîôýÿ.ý¯ü.ýù_ */ + $"0002 43F4 FDAD FB16 FCFF FFF6 D1A0 704B" /* ..Côý­û.üÿÿöÑ pK */ + $"3222 1A18 1B26 3854 7CAD DDFD FFFF FCAC" /* 2"...&8T|­Ýýÿÿü¬ */ + $"FB02 FCF8 5F81 0002 43F3 FCAB FA06 FCFF" /* û.üø_..Cóü«ú.üÿ */ + $"FAC4 732E 068A 0006 0E3E 89D7 FFFF FBAA" /* úÄs..Š...>‰×ÿÿûª */ + $"FA02 FBF8 5F81 0002 43F3 FBA9 F905 FAFF" /* ú.ûø_..Cóû©ù.úÿ */ + $"F3A1 3C02 8300 0608 131B 1D19 1005 8300" /* ó¡<.ƒ.........ƒ. */ + $"040C 55BC FDFF A9F9 02FA F75F 8100 0243" /* ..U¼ýÿ©ù.ú÷_..C */ + $"F3FB A7F9 04F8 FEFB A831 8200 0E26 5C91" /* óû§ù.øþû¨1‚..&\‘ */ + $"B9D4 E3EA ECE8 DFCE B083 4D19 8100 0501" /* ¹ÔãêìèßΰƒM.... */ + $"4DC7 FFFB F8A6 F902 FAF7 5F81 0002 43F2" /* MÇÿûø¦ù.ú÷_..Cò */ + $"FAA7 F802 FFD5 4E81 0003 1D6E BFEF 80FF" /* ú§ø.ÿÕN...n¿ï€ÿ */ + $"01FC FB80 FA08 FBFD FFFF FEE6 AC56 0D80" /* .üû€ú.ûýÿÿþæ¬V.€ */ + $"0004 0973 EDFF F7A5 F802 F9F6 5F81 0002" /* ..Æsíÿ÷¥ø.ùö_.. */ + $"43F1 F8A4 F704 F6F8 FE9F 1580 0005 238F" /* Cñø¤÷.öøþŸ.€..# */ + $"E6FF FDF8 8AF7 05F9 FFFE D570 0F80 0002" /* æÿýøŠ÷.ùÿþÕp.€.. */ + $"31C6 FFA5 F702 F8F5 5F81 0002 43F0 F7A4" /* 1Æÿ¥÷.øõ_..Cð÷¤ */ + $"F602 F9F7 7280 0004 0C7C E8FF F990 F603" /* ö.ù÷r€...|èÿùö. */ + $"FBFF D357 8000 030F A1FF F7A3 F602 F7F5" /* ûÿÓW€...¡ÿ÷£ö.÷õ */ + $"5F81 0002 43F0 F7A3 F602 F9F2 5880 0003" /* _..Cð÷£ö.ùòX€.. */ + $"37C8 FFF9 94F6 09FB FCA4 1900 0003 8AFE" /* 7Èÿù”öÆûü¤....Šþ */ + $"F7A2 F602 F7F5 5F81 0002 43F0 F6A2 F502" /* ÷¢ö.÷õ_..Cðö¢õ. */ + $"F8F2 5180 0002 63EE FC82 F501 F6F6 91F5" /* øòQ€..cîü‚õ.öö‘õ */ + $"02FF D539 8000 0186 FDA2 F502 F6F4 5F81" /* .ÿÕ9€..†ý¢õ.öô_ */ + $"0002 43EF F5A1 F408 F5F7 5D00 0001 7FFA" /* ..Cïõ¡ô.õ÷]....ú */ + $"F781 F405 F5FD F3F4 FDF5 90F4 07FB EA4F" /* ÷ô.õýóôýõô.ûêO */ + $"0000 0194 FEA1 F402 F5F3 5E81 0002 43EE" /* ...”þ¡ô.õó^..Cî */ + $"F4A1 F301 FC7D 8000 0286 FCF4 81F3 07F5" /* ô¡ó.ü}€..†üôó.õ */ + $"EF81 3335 88F2 F490 F307 F7F0 5200 0008" /* ï35ˆòôó.÷ðR... */ + $"B4FC A0F3 02F4 F25E 8100 0243 EEF3 A0F2" /* ´ü ó.ôò^..Cîó ò */ + $"07FB AE04 0000 77FB F382 F201 F86A 8100" /* .û®...wûó‚ò.øj. */ + $"0177 F991 F207 F6ED 4200 001D DBF7 9FF2" /* .wù‘ò.öíB...Û÷Ÿò */ + $"02F3 F25E 8100 0243 EDF2 9FF1 07F5 DF20" /* .óò^..CíòŸñ.õß */ + $"0000 53F5 F382 F102 F5D8 0D81 0002 15E0" /* ..Sõó‚ñ.õØ....à */ + $"F486 F101 F2F2 86F1 07F7 DD25 0000 50F5" /* ô†ñ.òò†ñ.÷Ý%..Põ */ + $"F29E F102 F2F1 5E81 0002 43EC F19F F006" /* òžñ.òñ^..CìñŸð. */ + $"F864 0000 25E1 F483 F002 F5CA 0481 0002" /* ød..%áôƒð.õÊ... */ + $"0AD4 F484 F005 F1F9 F4F5 F8F1 85F0 06F9" /* ÂÔô„ð.ñùôõøñ…ð.ù */ + $"B808 0000 A0F9 9EF0 02F1 F05E 8100 0243" /* ¸... ùžð.ñð^..C */ + $"EBF0 9EEF 06F6 BE06 0004 AEF8 84EF 02F1" /* ëðžï.ö¾...®ø„ï.ñ */ + $"E92C 8100 0237 EEF0 83EF 07F1 ED88 3E43" /* é,..7îðƒï.ñíˆ>C */ + $"99F2 F085 EF06 FA74 0000 25E5 F19D EF02" /* ™òð…ï.út..%åñï. */ + $"F0EF 5E81 0002 43EB F09E EF05 F54B 0000" /* ðï^..Cëðžï.õK.. */ + $"59F7 86EF 07F5 BA20 0000 26C3 F584 EF01" /* Y÷†ï.õº ..&Ãõ„ï. */ + $"F569 8100 0188 F785 EF06 F1E5 2500 0088" /* õi..ˆ÷…ï.ñå%..ˆ */ + $"F99D EF02 F0EF 5E81 0002 43EB F09D EF06" /* ùï.ðï^..Cëðï. */ + $"F5BE 0500 0DCC F486 EF07 EEFA B400 13D3" /* õ¾...Ìô†ï.îú´..Ó */ + $"F6EE 83EF 02F2 D20A 8100 021E E5F0 84EF" /* öîƒï.òÒÂ...åð„ï */ + $"07EE F898 0000 23E5 F09C EF02 F0EF 5E81" /* .îø˜..#åðœï.ðï^ */ + $"0002 43EB EF9D EE06 F760 0000 61F7 ED87" /* ..Cëïî.÷`..a÷í‡ */ + $"EE05 F4BD 0018 DFEF 84EE 01F3 BF82 0002" /* î.ô½..ßï„î.ó¿‚.. */ + $"0FD7 F085 EE07 EFE9 2A00 009D F6ED 9BEE" /* .×ð…î.ïé*..öí›î */ + $"02EF EF5E 8100 0243 EAEE 9CED 06EF DB16" /* .ïï^..Cêîœí.ïÛ. */ + $"0006 C0F3 88ED 05F3 BB00 17DD EF84 ED02" /* ..Àóˆí.ó»..Ýï„í. */ + $"EFE0 1E81 0000 3988 ED05 F788 0000 46F2" /* ïà...9ˆí.÷ˆ..Fò */ + $"9CED 02EE EE5E 8100 0243 E9ED 9CEC 05F4" /* œí.îî^..Céíœì.ô */ + $"9D00 003B EE89 EC05 F2BB 0017 DCEE 85EC" /* ..;î‰ì.ò»..Üî…ì */ + $"07F3 A715 0000 20BF F187 EC06 EFD3 1000" /* .ó§... ¿ñ‡ì.ïÓ.. */ + $"0DD0 EF9B EC02 EDED 5E81 0002 43E9 EC9C" /* .Ðï›ì.íí^..Céìœ */ + $"EB05 F459 0000 86F5 89EB 05F1 BA00 17DB" /* ë.ôY..†õ‰ë.ñº..Û */ + $"ED86 EB05 F7A4 0016 CFF2 89EB 05F1 4900" /* í†ë.÷¤..Ïò‰ë.ñI. */ + $"0096 F49B EB02 ECEC 5E81 0002 43E8 EB9B" /* .–ô›ë.ìì^..Cèë› */ + $"EA06 EBE4 2500 05C2 EF89 EA05 F0B9 0017" /* ê.ëä%..Âï‰ê.ð¹.. */ + $"DAEC 86EA 05F1 B300 1DE1 EC89 EA05 F48B" /* Úì†ê.ñ³..áì‰ê.ô‹ */ + $"0000 5AF3 9BEA 02EB EC5E 8100 0243 E7EA" /* ..Zó›ê.ëì^..Cçê */ + $"9BE9 06ED C607 0024 E3EA 89E9 05EF B800" /* ›é.íÆ..$ãê‰é.ï¸. */ + $"18E1 ED86 E905 F0B0 001C DFEB 89E9 06EE" /* .áí†é.ð°..ßë‰é.î */ + $"BE04 002D E7EA 9AE9 02EA EB5E 8100 0243" /* ¾..-çêšé.êë^..C */ + $"E7EA 9BE9 05F1 A400 004B F08A E907 EFB8" /* çê›é.ñ¤..KðŠé.ï¸ */ + $"000A 8AE0 F3EC 84E9 05F0 B000 1CDF EB89" /* .Šàóì„é.ð°..ßë‰ */ + $"E906 EBDC 1A00 11D3 EC9A E902 EAEB 5E81" /* é.ëÜ...Óìšé.êë^ */ + $"0002 43E7 E99B E805 F284 0000 6FF2 8AE8" /* ..Cçé›è.ò„..oòŠè */ + $"01EE B780 0004 2481 D6F2 EC82 E805 EFAF" /* .î·€..$Öòì‚è.ï¯ */ + $"001C DEEA 89E8 06E9 EA35 0002 BCEE 9AE8" /* ..Þê‰è.éê5..¼îšè */ + $"02E9 EA5E 8100 0243 E6E8 9BE7 05F1 6B00" /* .éê^..Cæè›ç.ñk. */ + $"008A F18A E70B EDB7 0003 0200 0019 70CB" /* .ŠñŠç.í·......pË */ + $"F0ED 80E7 05EE AF00 1CDD E98A E705 EF4E" /* ðí€ç.î¯..ÝéŠç.ïN */ + $"0000 A7EF 9AE7 02E8 E95E 8100 0243 E5E7" /* ..§ïšç.èé^..Cåç */ + $"9BE6 05EF 5B00 009B EF8A E614 ECB6 0017" /* ›æ.ï[..›ïŠæ.ì¶.. */ + $"A959 0B00 0010 60BF EDED E7ED AE00 1CDC" /* ©Y....`¿ííçí®..Ü */ + $"E88A E605 F05F 0000 97EF 9AE6 02E7 E95E" /* èŠæ.ð_..—ïšæ.çé^ */ + $"8100 0243 E4E6 9BE5 05ED 5300 00A3 ED8A" /* ..Cäæ›å.íS..£íŠ */ + $"E514 EBB5 0016 DCEF C367 1400 0008 50B1" /* å.ëµ..ÜïÃg....P± */ + $"E9F4 AE00 1CDB E78A E505 EF68 0000 8FEE" /* éô®..ÛçŠå.ïh..î */ + $"9AE5 02E6 E85E 8100 0243 E4E5 9BE4 05EC" /* šå.æè^..Cäå›ä.ì */ + $"5200 00A3 EC8A E414 EAB4 0016 D5E7 E9EE" /* R..£ìŠä.ê´..Õçéî */ + $"CD76 1D00 0002 42A9 A900 1CDA E68A E405" /* Ív....B©©..ÚæŠä. */ + $"EF68 0000 8EEE 9AE4 02E5 E75E 8100 0243" /* ïh..Žîšä.åç^..C */ + $"E3E4 9BE3 05EC 5900 009A EC8A E30C E9B3" /* ãä›ã.ìY..šìŠã.é³ */ + $"0016 D4E5 E3E3 E7ED D585 2880 0004 1E00" /* ..ÔåããçíÕ…(€.... */ + $"1CD9 E58A E305 ED5F 0000 95EC 9AE3 02E4" /* .ÙåŠã.í_..•ìšã.ä */ + $"E65E 8100 0243 E3E4 9BE3 05ED 6800 0089" /* æ^..Cãä›ã.íh..‰ */ + $"EC8A E305 E9B3 0016 D4E5 81E3 04E5 EDDC" /* ìŠã.é³..Ôåã.åíÜ */ + $"9435 8000 021C D9E5 8AE3 05EB 4E00 00A3" /* ”5€...ÙåŠã.ëN..£ */ + $"EA9A E302 E4E6 5E81 0002 43E3 E49B E305" /* êšã.äæ^..Cãä›ã. */ + $"EC80 0000 6FED 8AE3 05E9 B300 16D4 E583" /* ì€..oíŠã.é³..Ôåƒ */ + $"E308 E4EC E2A4 3B00 1CD9 E58A E305 E536" /* ã.äìâ¤;..ÙåŠã.å6 */ + $"0002 B6E8 9AE3 02E4 E65E 8100 0243 E2E3" /* ..¶èšã.äæ^..Câã */ + $"9BE2 05EA 9D00 004B E98A E205 E8B3 0016" /* ›â.ê..KéŠâ.è³.. */ + $"D3E4 85E2 06E3 EFAF 001B D8E4 89E2 06E4" /* Óä…â.ãï¯..Øä‰â.ä */ + $"D61A 000F CCE5 9AE2 02E3 E65D 8100 0243" /* Ö...Ìåšâ.ãæ]..C */ + $"E2E2 9BE1 06E5 BE06 0025 DCE2 89E1 05E7" /* ââ›á.å¾..%Üâ‰á.ç */ + $"B200 16D2 E386 E105 E7AA 001B D7E3 89E1" /* ²..Òã†á.çª..×ã‰á */ + $"06E6 B904 002A DEE2 9AE1 02E2 E55D 8100" /* .æ¹..*Þâšá.âå]. */ + $"0243 E1E1 9BE0 06E1 D921 0006 BCE4 89E0" /* .Cáá›à.áÙ!..¼ä‰à */ + $"05E6 B200 16D3 E286 E005 E6A9 001B D6E2" /* .æ²..Óâ†à.æ©..Öâ */ + $"89E0 05E9 8800 0054 E89B E002 E1E4 5D81" /* ‰à.éˆ..Tè›à.áä] */ + $"0002 43E0 E09C DF05 E751 0000 83E9 89DF" /* ..Cààœß.çQ..ƒé‰ß */ + $"05EA A900 12C6 E686 DF05 E5A8 001B D5E1" /* .ê©..Ææ†ß.å¨..Õá */ + $"89DF 05E6 4900 008B E89B DF02 E0E3 5D81" /* ‰ß.æI..‹è›ß.àã] */ + $"0002 43DF DF9C DE05 E691 0000 3BE2 88DE" /* ..CßßœÞ.æ‘..;âˆÞ */ + $"07E4 AF1F 0000 25B7 E385 DE05 E4A8 001B" /* .ä¯...%·ã…Þ.ä¨.. */ + $"D4E0 88DE 06E1 C911 000A C1E2 9BDE 02DF" /* ÔàˆÞ.áÉ..ÂÁâ›Þ.ß */ + $"E35D 8100 0243 DFDE 9CDD 06E0 CA12 0007" /* ã]..CßÞœÝ.àÊ... */ + $"B7E2 86DD 02DE D82A 8100 0234 DCDE 84DD" /* ·â†Ý.ÞØ*..4ÜÞ„Ý */ + $"05E3 A700 1BD3 DF88 DD05 E783 0000 3DE1" /* .ã§..Ó߈Ý.çƒ..=á */ + $"9DDD 01E2 5D81 0002 43DE DD9D DC05 E454" /* Ý.â]..CÞÝÜ.äT */ + $"0000 5FE5 86DC 02E1 B904 8100 0209 C3E0" /* .._å†Ü.á¹...ÆÃà */ + $"84DC 05E2 A700 1BD3 DE87 DC06 DDDA 2B00" /* „Ü.â§..ÓÞ‡Ü.ÝÚ+. */ + $"008D E59D DC01 E15D 8100 0243 DEDD 9DDC" /* .åÜ.á]..CÞÝÜ */ + $"06E2 AB03 000E C0E0 85DC 02DF C40C 8100" /* .â«...Àà…Ü.ßÄ.. */ + $"0213 CCDE 84DC 05E7 A300 18CE E287 DC06" /* ..ÌÞ„Ü.ç£..Îâ‡Ü. */ + $"E592 0000 1DD1 DE9D DC01 E15D 8100 0243" /* å’...ÑÞÜ.á]..C */ + $"DDDC 9DDB 07DC DF40 0000 57E4 DC84 DB02" /* ÝÜÛ.Üß@..WäÜ„Û. */ + $"DCE1 5E81 0001 6AE2 84DB 07E1 AF23 0001" /* Üá^..jâ„Û.á¯#.. */ + $"32C1 DF85 DB06 DDD5 2700 0077 E49E DB01" /* 2Áß…Û.ÝÕ'..wäžÛ. */ + $"E05D 8100 0243 DDDC 9DDB 08DA E1A9 0400" /* à]..CÝÜÛ.Úá©.. */ + $"05A5 E2DA 83DB 09DA DCD6 712C 2E78 D9DC" /* .¥âÚƒÛÆÚÜÖq,.xÙÜ */ + $"DA82 DB02 DCD4 2881 0001 42DE 84DB 07DA" /* Ú‚Û.ÜÔ(..BÞ„Û.Ú */ + $"E471 0000 1DCE DD9E DB01 E05D 8100 0243" /* äq...ÎÝžÛ.à]..C */ + $"DDDB 9FDA 07E0 5400 0027 CFDD D983 DA07" /* ÝÛŸÚ.àT..'ÏÝÙƒÚ. */ + $"D9DB E1D8 D9E1 DBD9 83DA 01DE B182 0002" /* ÙÛáØÙáÛÙƒÚ.Þ±‚.. */ + $"0FC7 DC82 DA08 D9E1 AD0A 0000 8AE2 D99E" /* .ÇÜ‚Ú.Ùá­Â..ŠâÙž */ + $"DA01 E05D 8100 0144 DCA0 D907 DCC4 1800" /* Ú.à]..DÜ Ù.ÜÄ.. */ + $"0052 DEDA 91D9 02DC B905 8100 0216 CCDA" /* .RÞÚ‘Ù.ܹ....ÌÚ */ + $"81D9 07D8 DDCB 2600 0041 DBA0 D901 DF5D" /* Ù.ØÝË&..AÛ Ù.ß] */ + $"8100 0144 DBA0 D807 D7E0 9402 0000 72E0" /* ..DÛ Ø.×à”...rà */ + $"92D8 01DA 4C81 0001 68DE 82D8 07DA D642" /* ’Ø.ÚL..hÞ‚Ø.ÚÖB */ + $"0000 16BE DCA0 D801 DE5D 8100 0144 DBA2" /* ...¾Ü Ø.Þ]..DÛ¢ */ + $"D707 DE66 0000 027F E0D8 90D7 07D9 CC5E" /* ×.Þf....àØ×.ÙÌ^ */ + $"2024 6ED4 D881 D707 DAD8 5100 0005 98DF" /* $nÔØ×.ÚØQ...˜ß */ + $"A1D7 01DD 5D81 0001 44DA A2D6 08D8 D74A" /* ¡×.Ý]..DÚ¢Ö.Ø×J */ + $"0000 0279 DDD8 90D6 05D8 DDCF D1DD D781" /* ...yÝØÖ.ØÝÏÑÝ× */ + $"D602 DBD2 4E80 0001 7ADE A2D6 01DC 5D81" /* Ö.ÛÒN€..zÞ¢Ö.Ü] */ + $"0001 44DA A3D6 02D9 D03F 8000 0260 D4DB" /* ..DÚ£Ö.ÙÐ?€..`ÔÛ */ + $"91D6 01D7 D782 D602 DEC1 3A80 0002 6CDC" /* ‘Ö.×ׂÖ.ÞÁ:€..lÜ */ + $"D7A2 D601 DC5D 8100 0144 D9A4 D502 D9CE" /* ×¢Ö.Ü]..DÙ¤Õ.ÙÎ */ + $"4480 0003 38B5 DFD7 94D5 03D9 DC98 1C80" /* D€..8µß×”Õ.Ùܘ.€ */ + $"0002 6FDB D6A3 D501 DC5D 8100 0144 D8A5" /* ..oÛÖ£Õ.Ü]..DØ¥ */ + $"D402 D8D2 5980 0004 0F75 CDDD D690 D40A" /* Ô.ØÒY€...uÍÝÖÔ */ + $"D7DD BD55 0200 0009 82DB D5A4 D401 DB5D" /* ×ݽU...Æ‚ÛÕ¤Ô.Û] */ + $"8100 0144 D8A6 D303 D5D8 7F0D 8000 0526" /* ..DئÓ.ÕØ..€..& */ + $"85CA DCD8 D48A D305 D5DA DBBD 6A13 8000" /* …ÊÜØÔŠÓ.ÕÚÛ½j.€. */ + $"0323 A2DC D4A5 D301 DA5D 8100 0144 D7A7" /* .#¢ÜÔ¥Ó.Ú]..Dק */ + $"D203 D3DB AE39 8100 141F 68AB D0DB DAD8" /* Ò.ÓÛ®9...h«ÐÛÚØ */ + $"D5D4 D4D3 D4D4 D6D8 DBD9 C99C 5411 8000" /* ÕÔÔÓÔÔÖØÛÙÉœT.€. */ + $"0303 57C5 D9A7 D201 D95D 8100 0144 D6A9" /* ..WÅÙ§Ò.Ù]..DÖ© */ + $"D103 D7D1 8422 8100 0F04 2859 85A5 B9C5" /* Ñ.×Ñ„"...(Y…¥¹Å */ + $"CACB C9C3 B59D 7A4B 1C82 0003 389F D8D4" /* ÊËÉõzK.‚..8ŸØÔ */ + $"A8D1 01D9 5D81 0001 44D6 AAD0 04D1 D9C7" /* ¨Ñ.Ù]..DÖªÐ.ÑÙÇ */ + $"7D2A 8400 060B 161E 201C 1408 8300 0506" /* }*„..... ...ƒ... */ + $"3D95 D1D7 D1A9 D001 D85C 8100 0144 D6AC" /* =•Ñ×Ñ©Ð.Ø\..DÖ¬ */ + $"D006 D2D9 CD9B 551F 018A 0006 072A 68AB" /* Ð.ÒÙÍ›U..Š...*h« */ + $"D4D7 D1AB D001 D85C 8100 0144 D6AD D016" /* Ô×Ñ«Ð.Ø\..DÖ­Ð. */ + $"CFD1 D6D8 C8A5 7A52 3521 150F 0D10 1825" /* ÏÑÖØÈ¥zR5!.....% */ + $"3B5C 85AF CED8 D4AE D001 D85C 8100 0144" /* ;\…¯ÎØÔ®Ð.Ø\..D */ + $"D5B1 CF0F D0D4 D7D7 D1C9 C1BC BABD C3CC" /* Õ±Ï.ÐÔ××ÑÉÁ¼º½ÃÌ */ + $"D3D7 D7D3 B1CF 01D7 5C81 0001 44D4 B6CE" /* Ó××Ó±Ï.×\..DԶΠ*/ + $"00CF 81D0 01CF CFB5 CE01 D65C 8100 0144" /* .ÏÐ.ÏϵÎ.Ö\..D */ + $"D3F5 CD01 D65C 8100 0144 D3F5 CC01 D55C" /* ÓõÍ.Ö\..DÓõÌ.Õ\ */ + $"8100 0144 D2F5 CB01 D45C 8100 0144 D1F5" /* ..DÒõË.Ô\..DÑõ */ + $"CA01 D35C 8100 0144 D1F5 CA01 D35C 8100" /* Ê.Ó\..DÑõÊ.Ó\. */ + $"0144 D1F5 C901 D35C 8100 0144 D0F5 C801" /* .DÑõÉ.Ó\..DÐõÈ. */ + $"D25C 8100 0144 CFF5 C801 D15C 8100 0244" /* Ò\..DÏõÈ.Ñ\..D */ + $"C7BA F3BB 02BA C85B 8100 0144 C6F5 BA01" /* Ǻó».ºÈ[..DÆõº. */ + $"C75B 8100 0144 CAF5 C001 CC5C 8100 0144" /* Ç[..DÊõÀ.Ì\..D */ + $"CEF5 C501 CF5C 8100 0144 D1F5 CA01 D35C" /* ÎõÅ.Ï\..DÑõÊ.Ó\ */ + $"8100 0144 D5F5 CF01 D75C 8100 0144 D9F5" /* ..DÕõÏ.×\..DÙõ */ + $"D401 DB5D 8100 0146 DEF5 DA01 E05E 8100" /* Ô.Û]..FÞõÚ.à^. */ + $"022A B5B8 F4B7 01BF 5382 0000 07F5 0801" /* .*µ¸ô·.¿S‚...õ.. */ + $"0904 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* Æ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ. */ + $"FF00 FF00 FF00 FF00 FF00 E700 FF00 FF00" /* ÿ.ÿ.ÿ.ÿ.ÿ.ç.ÿ.ÿ. */ + $"FF00 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ. */ + $"FF00 FF00 EE00 0004 A008 0007 D500 0409" /* ÿ.ÿ.î... ...Õ..Æ */ + $"5399 BAC1 A0C0 01A2 48D2 0004 2FB1 E3E2" /* S™ºÁ À.¢HÒ../±ãâ */ + $"DFA1 DE03 E2E0 7601 CF00 0230 C9DC A4D5" /* ß¡Þ.âàv.Ï..0ÉÜ¤Õ */ + $"03D4 D6DD 60CE 0002 0EB0 D5A7 CF02 D1CB" /* .ÔÖÝ`Î...°Õ§Ï.ÑË */ + $"2BCD 0001 5BD3 A9C9 02D0 9905 CB00 0202" /* +Í..[Ó©É.Й.Ë... */ + $"99C9 AAC4 01CB 4FCB 0002 0EAE C0AA BE02" /* ™ÉªÄ.ËOË...®Àª¾. */ + $"C1AE 16CA 0002 13AD BAAB B802 C07F 02C9" /* Á®.Ê...­º«¸.À..É */ + $"0002 13A8 B5AC B302 B964 03C8 0002 12A2" /* ...¨µ¬³.¹d.È...¢ */ + $"AFAC AD04 AEB2 7F3B 26BA 2502 2415 0186" /* ¯¬­.®².;&º%.$..† */ + $"0002 129D AAAE A801 ADAD BCA8 03A0 8240" /* ...ª®¨.­­¼¨. ‚@ */ + $"0384 0002 1198 A3EF A204 A3A7 A973 0D83" /* .„...˜£ï¢.£§©s.ƒ */ + $"0002 1192 9D81 9CEC 9B04 9C9C A474 0782" /* ...’œì›.œœ¤t.‚ */ + $"0007 108D 9897 9594 969A EA9D 059C 9895" /* ...˜—•”–šê.œ˜• */ + $"959E 4982 0008 0F88 928F 9AB4 CDDD E4E9" /* •žI‚...ˆ’š´ÍÝäé */ + $"E506 E2D5 B99E 9282 0B81 0005 0F82 8DB1" /* å.âÕ¹ž’‚....‚± */ + $"E1F9 EEFF 04F6 D2A6 8D28 8100 030D 7EBD" /* áùîÿ.öÒ¦(...~½ */ + $"F6F1 FF03 FDDB A137 8100 020A A0F6 F3FF" /* öñÿ.ýÛ¡7.. öóÿ */ + $"02FE CD3B 8100 0116 D1F5 FF01 ED46 8100" /* .þÍ;...Ñõÿ.íF. */ + $"0136 F1F5 FF01 F852 8100 0143 F7F5 FF01" /* .6ñõÿ.øR..C÷õÿ. */ + $"FB5A 8100 0243 F6FF F3FE 02FF FB5F 8100" /* ûZ..Cöÿóþ.ÿû_. */ + $"0243 F5FF F3FD 02FE FA5F 8100 0243 F5FE" /* .Cõÿóý.þú_..Cõþ */ + $"B6FC 82FD B5FC 02FD F95F 8100 0243 F4FD" /* ¶ü‚ýµü.ýù_..Côý */ + $"B0FB 0FFC FDFF FFFE FBF7 F5F5 F6F8 FBFE" /* °û.üýÿÿþû÷õõöøûþ */ + $"FFFF FDB0 FB02 FCF8 5F81 0002 43F4 FDAE" /* ÿÿý°û.üø_..Côý® */ + $"FB14 FDFF F9EB D8C6 B7AE A8A5 A4A5 A9B0" /* û.ýÿùëØÆ·®¨¥¤¥©° */ + $"BBCA DDEF FCFF FDAD FB02 FCF8 5F81 0002" /* »ÊÝïüÿý­û.üø_.. */ + $"43F3 FCAB FA08 FBFE FAE6 C6AC 9D97 9785" /* Cóü«ú.ûþúæƬ——… */ + $"9809 9797 98A0 B2CF EDFD FDFB AAFA 02FB" /* ˜Æ——˜ ²Ïíýýûªú.û */ + $"F85F 8100 0243 F3FB A9F9 1DFA FDF7 D8B1" /* ø_..Cóû©ù.úý÷ر */ + $"9B97 9999 9797 9A9E A2A5 A6A5 A19D 9997" /* ›—™™——šž¢¥¦¥¡™— */ + $"9899 9897 9FBA E3FB FCA9 F902 FAF7 5F81" /* ˜™˜—Ÿºãûü©ù.ú÷_ */ + $"0002 43F3 FBA7 F922 F8FB FADA AC98 9899" /* ..Cóû§ù"øûúÚ¬˜˜™ */ + $"979B A9BD D1E1 EBF0 F3F4 F2EF E8DD CCB7" /* —›©½ÑáëðóôòïèÝÌ· */ + $"A499 989A 979A B7E6 FCFA F8A6 F902 FAF7" /* ¤™˜š—š·æüúø¦ù.ú÷ */ + $"5F81 0002 43F2 FAA7 F802 FBEB B780 991C" /* _..Còú§ø.ûë·€™. */ + $"98A5 C4E2 F5FB FCFA F9F9 F8F8 F9F9 FAFB" /* ˜¥Äâõûüúùùøøùùúû */ + $"FCFA F1DB BB9F 979A 979D C6F4 FAA6 F802" /* üúñÛ»Ÿ—š—Æôú¦ø. */ + $"F9F6 5F81 0002 43F1 F8A6 F70A FAD6 A197" /* ùö_..Cñø¦÷ÂúÖ¡— */ + $"9A97 A7D0 F1FB F98B F70B F8FA FAEA C4A0" /* š—§Ðñûù‹÷.øúúêÄ  */ + $"989A 97AC E5FA A5F7 02F8 F55F 8100 0243" /* ˜š—¬åú¥÷.øõ_..C */ + $"F0F7 A4F6 0AF7 F6C5 999A 989E C8F1 FAF7" /* ð÷¤öÂ÷öÅ™š˜žÈñú÷ */ + $"90F6 09F8 FAE9 BB99 9998 9FD6 F9A4 F602" /* öÆøúé»™™˜ŸÖù¤ö. */ + $"F7F5 5F81 0002 43F0 F6A3 F509 F6F3 BB97" /* ÷õ_..Cðö£õÆöó»— */ + $"9A97 AEE4 F9F6 94F5 08F7 F7D7 A398 9A9A" /* š—®äùö”õ.÷÷×£˜šš */ + $"CEF8 A3F5 02F6 F45F 8100 0243 EFF5 A2F4" /* Îø£õ.öô_..Cïõ¢ô */ + $"08F5 F3B8 979A 98BF F2F7 98F4 07F8 E9AF" /* .õó¸—š˜¿ò÷˜ô.øé¯ */ + $"989A 99CC F7A2 F402 F5F3 5E81 0002 43EF" /* ˜š™Ì÷¢ô.õó^..Cï */ + $"F5A2 F407 F5BD 979A 99C9 F6F5 82F4 03F7" /* õ¢ô.õ½—š™Éöõ‚ô.÷ */ + $"F4F4 F791 F407 F6F1 B797 9A99 D1F7 A1F4" /* ôô÷‘ô.öñ·—š™Ñ÷¡ô */ + $"02F5 F35E 8100 0243 EEF4 A1F3 07F6 C897" /* .õó^..Cîô¡ó.öÈ— */ + $"9A99 CBF6 F481 F305 F4F2 CAAC ADCC 92F3" /* š™Ëöôó.ôòʬ­Ì’ó */ + $"07F5 F2B8 989A 9CDC F6A0 F302 F4F2 5E81" /* .õò¸˜šœÜö ó.ôò^ */ + $"0002 43EE F3A0 F206 F5DA 9B9A 98C6 F583" /* ..Cîó ò.õÚ›š˜Æõƒ */ + $"F207 F4C1 9699 9996 C6F5 91F2 07F3 F0B3" /* ò.ôÁ–™™–Æõ‘ò.óð³ */ + $"9899 A5EA F49F F202 F3F2 5E81 0002 43ED" /* ˜™¥êôŸò.óò^..Cí */ + $"F29F F107 F2EB A699 98B9 F3F2 82F1 09F2" /* òŸñ.ò릙˜¹óò‚ñÆò */ + $"E8A0 9A9B 9B9A A2EB F291 F106 F3EA A799" /* è š››š¢ëò‘ñ.ó꧙ */ + $"97B8 F39F F102 F2F1 5E81 0002 43EC F19F" /* —¸óŸñ.òñ^..CìñŸ */ + $"F006 F3BF 9799 A8EB F283 F002 F2E3 9C80" /* ð.ó¿—™¨ëòƒð.ò㜀 */ + $"9B03 9A9F E6F1 84F0 04F1 F3F1 F2F3 86F0" /* ›.šŸæñ„ð.ñóñòó†ð */ + $"06F3 DC9C 9A99 D4F4 9EF0 02F1 F05E 8100" /* .óÜœš™Ôôžð.ñð^. */ + $"0243 EBF0 9EEF 06F2 DE9C 9A9B D8F3 84EF" /* .Cëðžï.òÞœš›Øó„ï */ + $"09F0 EDAA 989B 9B97 AEEF F083 EF07 F0EF" /* Æðíª˜››—®ïðƒï.ðï */ + $"CBB0 B2D1 F0F0 85EF 06F3 C498 99A8 ECF0" /* Ë°²Ñðð…ï.óĘ™¨ìð */ + $"9DEF 02F0 EF5E 8100 0243 EBEF 9EEE 05F0" /* ï.ðï^..Cëïžî.ð */ + $"B598 98BA F186 EE07 F1DC A59A 9AA7 DFF0" /* µ˜˜ºñ†î.ñÜ¥šš§ßð */ + $"84EE 07F0 C096 9898 97CB F185 EE06 EFEB" /* „î.ðÀ–˜˜—Ëñ…î.ïë */ + $"A899 98CB F29D EE02 EFEF 5E81 0002 43EB" /* ¨™˜Ëòî.ïï^..Cë */ + $"EF9D EE07 F0DD 9C9A 9FE2 EFED 85EE 07ED" /* ïî.ðÝœšŸâïí…î.í */ + $"F1DA 9AA1 E4F0 ED83 EE08 EFE3 9E9A 9B9B" /* ñÚš¡äðíƒî.ïãžš›› */ + $"99A6 EA85 EE06 EDF1 D099 99A7 EA9D EE02" /* ™¦ê…î.íñЙ™§êî. */ + $"EFEF 5E81 0002 43EA EE9D ED05 F0BC 9798" /* ïï^..Cêîí.ð¼—˜ */ + $"BDF0 88ED 05EF DD9A A3E8 EE84 ED01 EFDD" /* ½ðˆí.ïÝš£èî„í.ïÝ */ + $"819B 039A A0E5 EE86 ED05 EBAA 9998 D1F0" /* ›.š åî†í.몙˜Ñð */ + $"9CED 02EE EE5E 8100 0243 EAEE 9CED 06EE" /* œí.îî^..Cêîœí.î */ + $"E6A3 9A9C DDEF 88ED 05EF DC9A A3E7 EE85" /* 棚œÝïˆí.ïÜš£çî… */ + $"ED06 E8A6 989B 9B97 AF88 ED05 F0CA 9898" /* í.覘››—¯ˆí.ðʘ˜ */ + $"B3EE 9CED 02EE EE5E 8100 0243 E9ED 9CEC" /* ³îœí.îî^..Céíœì */ + $"05EF D199 99AF ED89 EC05 EEDB 9AA3 E7ED" /* .ïÑ™™¯í‰ì.îÛš£çí */ + $"85EC 07EE D4A1 9A9A A5DC EE87 EC06 EDE3" /* …ì.îÔ¡šš¥Üî‡ì.íã */ + $"A09A 9FE2 ED9B EC02 EDED 5E81 0002 43E9" /*  šŸâí›ì.íí^..Cé */ + $"EC9C EB05 EEB9 9898 C9EE 89EB 05ED DA9A" /* ìœë.˜Éî‰ë.íÚš */ + $"A3E6 EC86 EB05 EFD2 99A2 E1EE 89EB 05ED" /* £æì†ë.ïÒ™¢áî‰ë.í */ + $"B498 98CE EE9B EB02 ECEC 5E81 0002 43E8" /* ´˜˜Îî›ë.ìì^..Cè */ + $"EB9C EA05 E8A7 999C DCEC 89EA 05EC D99A" /* ëœê.觙œÜì‰ê.ìÙš */ + $"A3E5 EB86 EA05 ECD8 99A5 E7EB 89EA 05ED" /* £åë†ê.ìØ™¥çë‰ê.í */ + $"CA98 98BA ED9B EA02 EBEC 5E81 0002 43E7" /* ʘ˜ºí›ê.ëì^..Cç */ + $"EA9B E905 EADD 9D99 A7E7 8AE9 05EB D99A" /* ê›é.êÝ™§çŠé.ëÙš */ + $"A3E7 EB86 E905 EBD6 99A5 E6EA 89E9 05EB" /* £çë†é.ëÖ™¥æê‰é.ë */ + $"DA9C 99AA E89B E902 EAEB 5E81 0002 43E7" /* Úœ™ªè›é.êë^..Cç */ + $"E99B E805 EBD1 9998 B4EA 8AE8 07EA D89B" /* é›è.ëÑ™˜´êŠè.êØ› */ + $"9DC8 E5EB E984 E805 EAD5 99A4 E5E9 89E8" /* Èåëé„è.êÕ™¤åé‰è */ + $"06E9 E4A4 99A1 E1E9 9AE8 02E9 EA5E 8100" /* .é䤙¡áéšè.éê^. */ + $"0243 E6E8 9BE7 05EA C698 98C0 EA8A E709" /* .Cæè›ç.êƘ˜ÀêŠçÆ */ + $"E9D7 9B9A 98A6 C5E1 EAE8 82E7 05E9 D499" /* é×›š˜¦Åáêè‚ç.éÔ™ */ + $"A4E4 E88A E705 E8AC 999C D9E9 9AE7 02E8" /* ¤äèŠç.謙œÙéšç.è */ + $"E95E 8100 0243 E5E7 9BE6 05E9 BE98 98C8" /* é^..Cåç›æ.龘˜È */ + $"E98A E60B E8D6 9B9C 9C97 97A2 BFDD E9E8" /* éŠæ.èÖ›œœ——¢¿Ýéè */ + $"80E6 05E8 D499 A4E3 E78A E605 E9B4 9899" /* €æ.èÔ™¤ãçŠæ.é´˜™ */ + $"D1E9 9AE6 02E7 E95E 8100 0243 E5E7 9BE6" /* Ñéšæ.çé^..Cåç›æ */ + $"05E9 B998 99CE E98A E614 E8D6 9AA2 D2B8" /* .鹘™ÎéŠæ.èÖš¢Ò¸ */ + $"9E97 979F BAD9 E8E8 E6E8 D499 A4E3 E78A" /* ž——ŸºÙèèæèÔ™¤ãçŠ */ + $"E605 E9BA 9898 CCE9 9AE6 02E7 E95E 8100" /* æ.麘˜Ìéšæ.çé^. */ + $"0243 E4E6 9BE5 05E8 B698 99D0 E88A E514" /* .Cäæ›å.趘™ÐèŠå. */ + $"E7D5 9AA2 E2E9 DABC A197 979D B5D4 E7EA" /* çÕš¢âéÚ¼¡——µÔçê */ + $"D499 A4E2 E68A E505 E8BD 9898 C9E8 9AE5" /* Ô™¤âæŠå.轘˜Éèšå */ + $"02E6 E85E 8100 0243 E4E5 9BE4 05E7 B698" /* .æè^..Cäå›ä.綘 */ + $"99CF E78A E414 E6D5 9AA2 DFE6 E6E8 DDC1" /* ™ÏçŠä.æÕš¢ßææèÝÁ */ + $"A398 969B AFD1 D299 A4E1 E58A E405 E7BC" /* £˜–›¯ÑÒ™¤áåŠä.ç¼ */ + $"9898 C9E7 9AE4 02E5 E75E 8100 0243 E4E4" /* ˜˜Éçšä.åç^..Cää */ + $"9BE3 05E6 B798 99CC E68A E314 E5D5 9AA2" /* ›ã.æ·˜™ÌæŠã.åÕš¢ */ + $"DEE4 E3E3 E5E7 DFC5 A799 979A A49A A4E0" /* Þäããåçßŧ™—š¤š¤à */ + $"E48A E305 E7B9 9899 CAE6 9AE3 02E4 E65E" /* äŠã.繘™Êæšã.äæ^ */ + $"8100 0243 E3E4 9BE3 05E6 BC98 98C6 E68A" /* ..Cãä›ã.漘˜ÆæŠ */ + $"E305 E5D4 9AA2 DEE4 82E3 09E6 E0C9 AB99" /* ã.åÔš¢Þä‚ãÆæàÉ«™ */ + $"989A A4E0 E48A E305 E5B4 9899 CEE5 9AE3" /* ˜š¤àäŠã.å´˜™Îåšã */ + $"02E4 E65E 8100 0243 E2E3 9BE2 05E5 C398" /* .äæ^..Câã›â.åØ */ + $"98BD E58A E205 E4D3 9AA2 DDE3 84E2 07E4" /* ˜½åŠâ.äÓš¢Ýã„â.ä */ + $"E2CE AD99 A4DF E38B E204 AC99 9BD4 E39A" /* âέ™¤ßã‹â.¬™›Ôãš */ + $"E202 E3E6 5D81 0002 43E2 E29B E105 E3CC" /* â.ãæ]..Cââ›á.ãÌ */ + $"9998 B2E3 8AE1 05E3 D29A A2DC E286 E105" /* ™˜²ãŠá.ãÒš¢Üâ†á. */ + $"E5D1 99A3 DEE2 8AE1 05DD A399 A0DA E29A" /* åÑ™£ÞâŠá.Ý£™ Úâš */ + $"E102 E2E5 5D81 0002 43E1 E19B E005 E1D5" /* á.âå]..Cáá›à.áÕ */ + $"9D99 A6DE 8AE0 05E2 D29A A2DB E186 E004" /* ™¦ÞŠà.âÒš¢Ûá†à. */ + $"E2CF 99A3 DD8A E005 E1D4 9C99 A8DF 9BE0" /* âÏ™£ÝŠà.áÔœ™¨ß›à */ + $"02E1 E45D 8100 0243 E0E0 9CDF 05DD A599" /* .áä]..Cààœß.Ý¥™ */ + $"9DD4 E089 DF05 E1D1 9AA2 DBE0 86DF 04E1" /* Ôà‰ß.áÑš¢Ûà†ß.á */ + $"CE99 A3DC 8ADF 05E2 C498 98B4 E19B DF02" /* Ι£ÜŠß.âĘ˜´á›ß. */ + $"E0E3 5D81 0002 43DF DF9C DE05 E0B3 9898" /* àã]..CßßœÞ.೘˜ */ + $"C2E1 89DE 05E1 CE9A A0D7 E086 DE04 E0CE" /* Âá‰Þ.áΚ ×à†Þ.àÎ */ + $"99A3 DB8A DE05 E0B1 9999 C5E1 9BDE 02DF" /* ™£ÛŠÞ.à±™™Åá›Þ.ß */ + $"E35D 8100 0243 DFDF 9CDE 05E1 C799 99AD" /* ã]..CßßœÞ.áÇ™™­ */ + $"DF88 DE07 E0D0 A39A 9AA5 D2DF 85DE 04E0" /* ߈Þ.àУšš¥Òß…Þ.à */ + $"CE99 A3DB 89DE 06DF D8A0 9A9E D5DF 9BDE" /* Ι£Û‰Þ.ßØ šžÕß›Þ */ + $"02DF E35D 8100 0243 DFDE 9CDD 06DE D7A0" /* .ßã]..CßÞœÝ.Þ×  */ + $"9A9C D2DF 86DD 07DE DCA7 989B 9B98 AA86" /* šœÒ߆Ý.Þܧ˜››˜ª† */ + $"DD04 DFCD 99A3 DA89 DD05 E0C2 9899 ADDE" /* Ý.ßÍ™£Ú‰Ý.à˜™­Þ */ + $"9DDD 01E2 5D81 0002 43DE DD9D DC05 DFB4" /* Ý.â]..CÞÝÜ.ß´ */ + $"9898 B7DF 86DC 02DD D29C 809B 039A 9ED5" /* ˜˜·ß†Ü.ÝÒœ€›.šžÕ */ + $"DD84 DC04 DECC 99A3 DA88 DC06 DDDB A899" /* Ý„Ü.ÞÌ™£ÚˆÜ.ÝÛ¨™ */ + $"99C5 DF9D DC01 E15D 8100 0243 DDDC 9DDB" /* ™ÅßÜ.á]..CÝÜÛ */ + $"06DD CD9B 9A9F D3DD 85DB 09DC D49E 9A9B" /* .ÝÍ›šŸÓÝ…ÛÆÜÔžš› */ + $"9B9A A1D7 DC84 DB05 DFCA 99A2 D7DD 87DB" /* ›š¡×Ü„Û.ßÊ™¢×Ý‡Û */ + $"06DE C699 9AA3 D8DC 9DDB 01E0 5D81 0002" /* .ÞÆ™š£ØÜÛ.à].. */ + $"43DD DB9E DA05 DCAE 9998 B4DD 86DA 07DC" /* CÝÛžÚ.Ü®™˜´Ý†Ú.Ü */ + $"B697 9999 97BA DC84 DA07 DCCE A49A 9BA9" /* ¶—™™—ºÜ„Ú.ÜΤš›© */ + $"D3DC 85DA 06DB D8A6 9A98 BEDD 9EDA 01E0" /* ÓÜ…Ú.Ûئš˜¾ÝžÚ.à */ + $"5D81 0002 43DD DB9E DA07 DBCB 9B9A 9BCA" /* ]..CÝÛžÚ.ÛË›š›Ê */ + $"DCD9 85DA 05D8 BBA7 A7BD D985 DA07 D8A6" /* ÜÙ…Ú.Ø»§§½Ù…Ú.ئ */ + $"989B 9B98 AEDB 84DA 06D9 DCBB 989A A3D6" /* ˜››˜®Û„Ú.ÙÜ»˜š£Ö */ + $"9FDA 01E0 5D81 0001 44DB A0D9 07DA B398" /* ŸÚ.à]..DÛ Ù.Ú³˜ */ + $"99A6 D6D9 D883 D907 D8D9 DBD8 D8DB D9D8" /* ™¦ÖÙ؃Ù.ØÙÛØØÛÙØ */ + $"83D9 01DA CD81 9B02 9A9F D383 D908 D8DB" /* ƒÙ.ÚÍ›.šŸÓƒÙ.ØÛ */ + $"CC9D 9A99 C2DB D89E D901 DF5D 8100 0144" /* Ìš™ÂÛØžÙ.ß]..D */ + $"DBA0 D806 D9D2 A29A 98B2 D992 D802 D9CF" /* Û Ø.ÙÒ¢š˜²Ù’Ø.ÙÏ */ + $"9C80 9B02 9AA1 D483 D805 D9D4 A599 99AD" /* œ€›.š¡ÔƒØ.ÙÔ¥™™­ */ + $"A1D8 01DE 5D81 0001 44DB A1D7 06D9 C49A" /* ¡Ø.Þ]..DÛ¡×.ÙÄš */ + $"9B99 BBD9 92D7 07D8 B097 9A9A 97B8 D982" /* ›™»Ù’×.Ø°—šš—¸Ù‚ */ + $"D707 D8D7 AD99 9AA1 D0D8 A0D7 01DD 5D81" /* ×.Ø×­™š¡ÐØ ×.Ý] */ + $"0001 44DB A2D7 06D9 B799 9B9A BED9 91D7" /* ..DÛ¢×.Ù·™›š¾Ù‘× */ + $"06D8 D4B5 A3A4 B9D6 82D7 07D8 D7B1 989A" /* .ØÔµ£¤¹Ö‚×.Ø×±˜š */ + $"9BC5 D9A1 D701 DD5D 8100 0144 DAA4 D605" /* ›ÅÙ¡×.Ý]..DÚ¤Ö. */ + $"AF98 9A9B BCD8 92D6 03D8 D4D4 D882 D607" /* ¯˜š›¼Ø’Ö.ØÔÔØ‚Ö. */ + $"D7D5 B099 9B99 BCD8 A2D6 01DC 5D81 0001" /* ×Õ°™›™¼Ø¢Ö.Ü].. */ + $"44D9 A3D5 08D6 D3AC 999A 9AB5 D4D6 98D5" /* DÙ£Õ.ÖÓ¬™ššµÔÖ˜Õ */ + $"07D7 CFAA 999B 99B8 D7A3 D501 DC5D 8100" /* .×Ϫ™›™¸×£Õ.Ü]. */ + $"0144 D8A4 D409 D5D2 AD99 9B99 AACB D6D5" /* .DؤÔÆÕÒ­™›™ªËÖÕ */ + $"94D4 08D5 D6C4 A299 9A9A B9D5 A4D4 01DB" /* ”Ô.ÕÖÄ¢™šš¹Õ¤Ô.Û */ + $"5D81 0001 44D8 A5D3 0AD4 D2B2 999A 999E" /* ]..DØ¥ÓÂÔÒ²™š™ž */ + $"BAD1 D5D4 90D3 09D4 D5CD B19B 9A9A 9CBD" /* ºÑÕÔÓÆÔÕͱ›ššœ½ */ + $"D5A5 D301 DA5D 8100 0144 D7A6 D20B D3D4" /* Õ¥Ó.Ú]..DצÒ.ÓÔ */ + $"BC9E 999A 99A4 BED0 D5D3 8BD2 0BD3 D4D4" /* ¼ž™š™¤¾ÐÕÓ‹Ò.ÓÔÔ */ + $"CCB6 9F99 9A99 A3C5 D4A6 D201 D95D 8100" /* ̶Ÿ™š™£ÅÔ¦Ò.Ù]. */ + $"0144 D6A8 D10A D3C8 A999 9A9A 99A3 B6C7" /* .DÖ¨ÑÂÓÈ©™šš™£¶Ç */ + $"D180 D384 D280 D30A CFC3 B09F 999A 999B" /* Ñ€Ó„Ò€ÓÂÏ🙚™› */ + $"B1CE D3A7 D101 D95D 8100 0144 D6A9 D020" /* ±ÎÓ§Ñ.Ù]..DÖ©Ð */ + $"D2D0 BCA3 999A 9A99 9CA5 B1BD C5CA CDCE" /* Òм£™šš™œ¥±½ÅÊÍÎ */ + $"CFCE CDC9 C3BA AEA2 9A99 9A99 9AA9 C3D2" /* ÏÎÍÉú®¢š™š™š©ÃÒ */ + $"D1A8 D001 D85C 8100 0144 D6AB D01C D2CE" /* ѨÐ.Ø\..DÖ«Ð.ÒÎ */ + $"BBA5 9A99 9A9A 9999 9B9E A1A2 A3A2 A09D" /* »¥š™šš™™›ž¡¢£¢  */ + $"9A99 999A 9A99 9CAA C1D0 D2AA D001 D85C" /* š™™šš™œªÁÐÒªÐ.Ø\ */ + $"8100 0144 D5AC CF06 D0D2 CFC2 B0A2 9B80" /* ..DÕ¬Ï.ÐÒÏ°¢›€ */ + $"9984 9A80 9906 9CA5 B5C6 D0D1 D0AB CF01" /* ™„š€™.œ¥µÆÐÑЫÏ. */ + $"D75C 8100 0144 D5AF CF14 D0D1 CDC4 B9AF" /* ×\..DÕ¯Ï.ÐÑÍĹ¯ */ + $"A8A3 A09F 9E9F A1A4 A9B1 BCC7 CED1 D0AE" /* ¨£ ŸžŸ¡¤©±¼ÇÎÑЮ */ + $"CF01 D65C 8100 0144 D4B2 CE05 CFD0 D0CE" /* Ï.Ö\..DÔ²Î.ÏÐÐÎ */ + $"CCCA 80C9 05CB CDCF D0D0 CFB1 CE01 D65C" /* ÌÊ€É.ËÍÏÐÐϱÎ.Ö\ */ + $"8100 0144 D3F5 CD01 D65C 8100 0144 D3F5" /* ..DÓõÍ.Ö\..DÓõ */ + $"CC01 D55C 8100 0144 D2F5 CB01 D45C 8100" /* Ì.Õ\..DÒõË.Ô\. */ + $"0144 D1F5 CA01 D35C 8100 0144 D1F5 C901" /* .DÑõÊ.Ó\..DÑõÉ. */ + $"D35C 8100 0144 D1F5 C901 D35C 8100 0144" /* Ó\..DÑõÉ.Ó\..D */ + $"D0F5 C801 D25C 8100 0144 CFF5 C701 D15C" /* ÐõÈ.Ò\..DÏõÇ.Ñ\ */ + $"8100 0144 CFF5 C701 D05C 8100 0244 C6B8" /* ..DÏõÇ.Ð\..DƸ */ + $"F3B9 02B8 C75B 8100 0244 C5B8 F3B9 02B8" /* ó¹.¸Ç[..DŸó¹.¸ */ + $"C65B 8100 0144 C9F5 BF01 CB5C 8100 0144" /* Æ[..DÉõ¿.Ë\..D */ + $"CDF5 C401 CF5C 8100 0144 D1F5 C901 D35C" /* ÍõÄ.Ï\..DÑõÉ.Ó\ */ + $"8100 0144 D4F5 CE01 D65C 8100 0144 D8F5" /* ..DÔõÎ.Ö\..DØõ */ + $"D301 DA5D 8100 0146 DEF5 D901 E05E 8100" /* Ó.Ú]..FÞõÙ.à^. */ + $"022A B5B7 F4B6 01BE 5382 0000 07F5 0801" /* .*µ·ô¶.¾S‚...õ.. */ + $"0904 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* Æ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ. */ + $"FF00 FF00 FF00 FF00 FF00 E700 FF00 FF00" /* ÿ.ÿ.ÿ.ÿ.ÿ.ç.ÿ.ÿ. */ + $"FF00 FF00 FF00 FF00 FF00 FF00 FF00 FF00" /* ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ. */ + $"FF00 FF00 EE00 0004 A008 0007 D500 0409" /* ÿ.ÿ.î... ...Õ..Æ */ + $"549A BAC1 9EC0 03C1 C0A3 48D2 0004 2FB2" /* TšºÁžÀ.ÁÀ£HÒ../² */ + $"E3E3 DFA1 DE03 E2E1 7601 CF00 0230 C9DC" /* ããß¡Þ.âáv.Ï..0ÉÜ */ + $"A5D5 02D6 DD60 CE00 020E B0D6 A7CF 02D1" /* ¥Õ.ÖÝ`Î...°Ö§Ï.Ñ */ + $"CB2B CD00 025B D3C9 A7CA 03C9 D09A 05CB" /* Ë+Í..[ÓɧÊ.ÉК.Ë */ + $"0002 0299 CAAA C401 CB4F CB00 020E AEC1" /* ...™ÊªÄ.ËOË...®Á */ + $"AABE 02C1 AE16 CA00 0213 ADBB ABB9 02C0" /* ª¾.Á®.Ê...­»«¹.À */ + $"7F02 C900 0213 A8B5 ACB3 02B9 6503 C800" /* ..É...¨µ¬³.¹e.È. */ + $"0212 A3B0 ADAE 03B3 803B 26BA 2502 2415" /* ..£°­®.³€;&º%.$. */ + $"0186 0002 129E AAAE A802 ADAD A9BB A803" /* .†...žª®¨.­­©»¨. */ + $"A083 4003 8400 0211 98A4 AFA2 BDA3 04A4" /*  ƒ@.„...˜¤¯¢½£.¤ */ + $"A7A9 730E 8300 0211 939E 819D ED9C 039D" /* §©s.ƒ...“žíœ. */ + $"A575 0782 0007 108E 9897 9694 969B EA9E" /* ¥u.‚...Ž˜—–”–›êž */ + $"059D 9995 959F 4A82 0008 0F89 928F 9AB4" /* .™••ŸJ‚...‰’š´ */ + $"CDDD E4E9 E506 E2D5 B99E 9283 0B81 0005" /* ÍÝäéå.âÕ¹ž’ƒ... */ + $"0F82 8EB1 E1F9 EEFF 04F6 D2A7 8E28 8100" /* .‚Ž±áùîÿ.öÒ§Ž(. */ + $"030D 7FBD F6F1 FF03 FDDB A237 8100 020A" /* ...½öñÿ.ýÛ¢7.. */ + $"A0F6 F3FF 02FE CD3B 8100 0116 D1F5 FF01" /*  öóÿ.þÍ;...Ñõÿ. */ + $"ED46 8100 0136 F1F5 FF01 F852 8100 0143" /* íF..6ñõÿ.øR..C */ + $"F7F5 FF01 FB5A 8100 0243 F6FF F3FE 02FF" /* ÷õÿ.ûZ..Cöÿóþ.ÿ */ + $"FB5F 8100 0243 F5FF F3FD 02FE FA5F 8100" /* û_..Cõÿóý.þú_. */ + $"0243 F5FE B6FC 81FD B6FC 02FD F95F 8100" /* .Cõþ¶üý¶ü.ýù_. */ + $"0243 F5FE B1FC 05FD FEFF FEFC FA80 F805" /* .Cõþ±ü.ýþÿþüú€ø. */ + $"FAFC FEFF FEFD B0FC 02FD F95F 8100 0243" /* úüþÿþý°ü.ýù_..C */ + $"F4FD ADFB 15FC FDFE FAF1 E5D9 D0CA C6C4" /* ôý­û.üýþúñåÙÐÊÆÄ */ + $"C4C5 C7CB D2DC E8F4 FCFE FCAD FB02 FCF8" /* ÄÅÇËÒÜèôüþü­û.üø */ + $"5F81 0002 43F3 FCAB FA08 FBFD FAED DAC9" /* _..Cóü«ú.ûýúíÚÉ */ + $"BFBC BB86 BC07 BBBC C1CD DFF2 FCFC ABFA" /* ¿¼»†¼.»¼ÁÍßòüü«ú */ + $"02FB F85F 8100 0243 F3FB A9F9 1DFA FCF8" /* .ûø_..Cóû©ù.úüø */ + $"E5CC BEBB BCBD BCBC BDC0 C2C4 C5C4 C2BF" /* å̾»¼½¼¼½ÀÂÄÅÄ¿ */ + $"BDBC BCBD BCBB C1D2 EBFA FBA9 F902 FAF7" /* ½¼¼½¼»ÁÒëúû©ù.ú÷ */ + $"5F81 0002 43F3 FBA7 F90F F8FA F9E5 C9BC" /* _..Cóû§ù.øúùåɼ */ + $"BCBD BCBE C7D3 E0EA F0F3 80F5 0FF3 EEE7" /* ¼½¼¾ÇÓàêðó€õ.óîç */ + $"DDD0 C4BD BCBD BCBE D0ED FBF9 F8A6 F902" /* ÝÐĽ¼½¼¾Ðíûùø¦ù. */ + $"FAF7 5F81 0002 43F2 FAA7 F80E FAF0 D0BC" /* ú÷_..Còú§ø.úðм */ + $"BCBD BCC4 D7EA F6FA FAF9 F982 F80E F9FA" /* ¼½¼Ä×êöúúùù‚ø.ùú */ + $"FAF9 F4E6 D2C1 BCBD BCBF D9F5 F9A6 F802" /* úùôæÒÁ¼½¼¿Ùõù¦ø. */ + $"F9F6 5F81 0002 43F1 F8A6 F70A F8E3 C2BC" /* ùö_..Cñø¦÷Âøã¼ */ + $"BDBC C5DE F3F9 F88C F70A F9F9 EFD7 C1BC" /* ½¼ÅÞóùøŒ÷Âùùï×Á¼ */ + $"BDBB C9EC F9A5 F702 F8F5 5F81 0002 43F0" /* ½»Éìù¥÷.øõ_..Cð */ + $"F7A6 F607 D8BD BDBC C0DA F3F8 91F6 09F7" /* ÷¦ö.ؽ½¼ÀÚóø‘öÆ÷ */ + $"F8EE D1BD BDBC C1E3 F8A4 F602 F7F5 5F81" /* øîѽ½¼Áãø¤ö.÷õ_ */ + $"0002 43F0 F6A3 F509 F6F4 D2BC BEBC CAEB" /* ..Cðö£õÆöôÒ¼¾¼Êë */ + $"F7F6 94F5 08F6 F7E3 C3BC BDBE DDF7 A3F5" /* ÷ö”õ.ö÷ãü½¾Ý÷£õ */ + $"02F6 F45F 8100 0243 F0F6 A3F5 07F4 D0BC" /* .öô_..Cðö£õ.ôм */ + $"BEBC D4F3 F698 F507 F7EE CABC BDBD DCF7" /* ¾¼Ôóö˜õ.÷îʼ½½Ü÷ */ + $"A2F5 02F6 F45F 8100 0243 EFF5 A2F4 07F5" /* ¢õ.öô_..Cïõ¢ô.õ */ + $"D2BC BEBD DAF5 F582 F403 F6F4 F4F6 91F4" /* Ò¼¾½Úõõ‚ô.öôôö‘ô */ + $"07F5 F2CF BCBE BDDF F6A1 F402 F5F3 5E81" /* .õòϼ¾½ßö¡ô.õó^ */ + $"0002 43EE F4A1 F306 F5D9 BCBE BDDB F583" /* ..Cîô¡ó.õÙ¼¾½Ûõƒ */ + $"F304 F2DA C9C9 DC92 F307 F4F2 CFBC BDBF" /* ó.òÚÉÉÜ’ó.ôòϼ½¿ */ + $"E5F5 A0F3 02F4 F25E 8100 0243 EEF3 A0F2" /* åõ ó.ôò^..Cîó ò */ + $"06F4 E4BE BEBC D8F4 83F2 07F3 D5BB BDBC" /* .ôä¾¾¼Øôƒò.óÕ»½¼ */ + $"BBD8 F491 F207 F3F1 CCBC BDC4 EDF3 9FF2" /* »Øô‘ò.óñ̼½ÄíóŸò */ + $"02F3 F25E 8100 0243 EDF2 9FF1 07F2 EDC5" /* .óò^..CíòŸñ.òíÅ */ + $"BDBC D0F2 F282 F109 F2EC C1BD BEBE BDC2" /* ½¼Ðòò‚ñÆòìÁ½¾¾½Â */ + $"EEF2 91F1 06F2 EDC5 BDBC CFF2 9FF1 02F2" /* îò‘ñ.ò펼ÏòŸñ.ò */ + $"F15E 8100 0243 ECF1 9FF0 06F2 D3BC BDC6" /* ñ^..CìñŸð.òÓ¼½Æ */ + $"EDF1 83F0 02F1 E8BF 81BE 02C0 EAF1 85F0" /* íñƒð.ñ迾.Àêñ…ð */ + $"03F2 F1F1 F286 F006 F2E4 BFBE BDDF F29E" /* .òññò†ð.ò俾½ßòž */ + $"F002 F1F0 5E81 0002 43EB F09E EF06 F1E5" /* ð.ñð^..Cëðžï.ñå */ + $"BFBE BEE2 F184 EF07 F0EE C7BC BEBE BCC9" /* ¿¾¾âñ„ï.ðîǼ¾¾¼É */ + $"85EF 06F0 EFDA CACB DEF0 86EF 06F2 D6BC" /* …ï.ðïÚÊËÞð†ï.òÖ¼ */ + $"BDC6 EDF0 9DEF 02F0 EF5E 8100 0243 EBEF" /* ½Æíðï.ðï^..Cëï */ + $"9EEE 05F0 CDBC BCD0 F086 EE07 F0E4 C4BE" /* žî.ðͼ¼Ðð†î.ðäľ */ + $"BDC5 E5EF 84EE 02F0 D3BB 80BC 01DA F085" /* ½Ååï„î.ðÓ»€¼.Úð… */ + $"EE06 EFEC C5BD BCDA F09D EE02 EFEF 5E81" /* î.ï쎼Úðî.ïï^ */ + $"0002 43EB EF9D EE06 EFE4 BEBD C0E7 EF87" /* ..Cëïî.ïä¾½Àçï‡ */ + $"EE05 F0E2 BDC2 E9EF 84EE 02EF E8C0 80BE" /* î.ðâ½Âéï„î.ïèÀ€¾ */ + $"02BD C4EC 86EE 05F0 DDBC BDC5 EC9D EE02" /* .½Äì†î.ðݼ½Åìî. */ + $"EFEF 5E81 0002 43EB EF9D EE05 EFD1 BCBC" /* ïï^..Cëïî.ïѼ¼ */ + $"D1F0 88EE 04EF E4BD C3EB 85EE 01EF E482" /* Ñðˆî.ïä½Ãë…î.ïä‚ */ + $"BE01 C1E9 87EE 05ED C6BD BCDD EF9C EE02" /* ¾.Áé‡î.íƽ¼Ýïœî. */ + $"EFEF 5E81 0002 43EA EE9D ED05 E9C2 BDBF" /* ïï^..Cêîí.齿 */ + $"E4EE 88ED 04EE E3BD C3EA 86ED 06EA C4BD" /* äîˆí.îã½Ãê†í.êĽ */ + $"BEBE BCC9 88ED 05EF D9BC BCCC EE9C ED02" /* ¾¾¼Éˆí.ïÙ¼¼Ìîœí. */ + $"EEEE 5E81 0002 43E9 ED9C EC04 EEDC BDBD" /* îî^..Céíœì.îܽ½ */ + $"C98A EC04 EDE2 BDC2 E986 EC07 EDDE C1BE" /* ÉŠì.íâ½Âé†ì.íÞÁ¾ */ + $"BEC4 E3ED 88EC 04E7 C1BD C1E6 9CEC 02ED" /* ¾Äãíˆì.çÁ½Áæœì.í */ + $"ED5E 8100 0243 E9EC 9CEB 05ED CFBC BCD8" /* í^..Céìœë.íϼ¼Ø */ + $"ED89 EB04 ECE2 BDC2 E887 EB05 EDDD BDC2" /* í‰ë.ìâ½Âè‡ë.íݽ */ + $"E6EC 89EB 05EC CCBC BCDB ED9B EB02 ECEC" /* æì‰ë.ì̼¼Ûí›ë.ìì */ + $"5E81 0002 43E8 EB9C EA05 E9C5 BDBF E2EB" /* ^..Cèëœê.鎿âë */ + $"89EA 04EB E1BD C2E7 87EA 04EB DFBD C3E8" /* ‰ê.ëá½Âç‡ê.ëß½Ãè */ + $"8AEA 05EC D8BC BCCF EC9B EA02 EBEC 5E81" /* Šê.ìؼ¼Ïì›ê.ëì^ */ + $"0002 43E7 EA9B E905 EAE3 BFBD C5E8 8AE9" /* ..Cçê›é.ê㿽ÅèŠé */ + $"04EA E0BD C2E7 87E9 04EA DEBD C3E7 8AE9" /* .êà½Âç‡é.êÞ½ÃçŠé */ + $"04EA E1BF BDC6 9CE9 02EA EB5E 8100 0243" /* .ê´Ɯé.êë^..C */ + $"E7E9 9BE8 05E9 DCBD BCCC E98A E807 E9DF" /* çé›è.éܽ¼ÌéŠè.éß */ + $"BEBF D7E6 EAE9 84E8 04E9 DEBD C3E6 8BE8" /* ¾¿×æêé„è.éÞ½Ãæ‹è */ + $"05E6 C3BD C1E4 E99A E802 E9EA 5E81 0002" /* .æýÁäéšè.éê^.. */ + $"43E6 E89B E705 E9D5 BCBC D2E9 8AE7 09E8" /* Cæè›ç.éÕ¼¼ÒéŠçÆè */ + $"DEBE BEBD C4D5 E4E9 E882 E704 E8DD BDC3" /* Þ¾¾½ÄÕäéè‚ç.èݽà */ + $"E58C E704 C8BD BEDF E89A E702 E8E9 5E81" /* åŒç.Ƚ¾ßèšç.èé^ */ + $"0002 43E6 E89B E705 E9D1 BCBC D7E9 8AE7" /* ..Cæè›ç.éѼ¼×éŠç */ + $"0BE8 DEBE BFBE BCBC C2D2 E2E9 E880 E704" /* .èÞ¾¿¾¼¼ÂÒâéè€ç. */ + $"E8DD BDC3 E58B E705 E8CC BCBD DCE8 9AE7" /* èݽÃå‹ç.è̼½Üèšç */ + $"02E8 E95E 8100 0243 E5E7 9BE6 05E8 CEBC" /* .èé^..Cåç›æ.èμ */ + $"BDD9 E88A E613 E7DE BDC2 DBCD C0BC BCC0" /* ½ÙèŠæ.çÞ½ÂÛÍÀ¼¼À */ + $"CFDF E7E7 E6E7 DCBD C3E4 8BE6 05E8 CFBC" /* ÏßççæçܽÃä‹æ.èϼ */ + $"BDD9 E89A E602 E7E9 5E81 0002 43E4 E69B" /* ½Ùèšæ.çé^..Cäæ› */ + $"E505 E6CC BCBD DAE6 8AE5 13E6 DDBD C2E3" /* å.æ̼½ÚæŠå.æݽÂã */ + $"E7DF CFC1 BCBC BFCC DCE6 E8DC BDC3 E38B" /* çßÏÁ¼¼¿ÌÜæèܽÃã‹ */ + $"E505 E7D0 BCBD D6E7 9AE5 02E6 E85E 8100" /* å.çм½Öçšå.æè^. */ + $"0243 E4E5 9BE4 05E5 CCBC BDD9 E58A E413" /* .Cäå›ä.å̼½ÙåŠä. */ + $"E5DC BDC2 E1E4 E5E6 E1D2 C3BC BCBE C9DB" /* åܽÂáäåæáÒü¼¾ÉÛ */ + $"DABD C3E2 8BE4 05E7 D0BC BDD6 E69A E402" /* Ú½Ãâ‹ä.çм½Öæšä. */ + $"E5E7 5E81 0002 43E4 E49B E305 E5CD BCBD" /* åç^..Cää›ã.åͼ½ */ + $"D7E4 8AE3 04E4 DBBD C2E1 80E3 0BE4 E5E1" /* ×äŠã.äÛ½Âá€ã.äåá */ + $"D4C4 BDBC BEC3 BDC3 E18B E305 E5CE BCBD" /* ÔĽ¼¾Ã½Ãá‹ã.åμ½ */ + $"D7E4 9AE3 02E4 E65E 8100 0243 E3E4 9BE3" /* ×äšã.äæ^..Cãä›ã */ + $"05E5 CFBC BDD4 E48A E304 E4DB BDC2 E183" /* .åϼ½ÔäŠã.äÛ½Âრ*/ + $"E308 E4E2 D6C6 BDBC BDC3 E18B E305 E4CB" /* ã.äâÖƽ¼½Ãá‹ã.äË */ + $"BDBD D8E4 9AE3 02E4 E65E 8100 0243 E2E3" /* ½½Øäšã.äæ^..Câã */ + $"9BE2 05E3 D2BC BCCF E48A E204 E3DA BDC1" /* ›â.ãÒ¼¼ÏäŠâ.ãÚ½Á */ + $"E085 E206 E3E2 D8C7 BDC2 E08C E204 C6BD" /* à…â.ãâØǽÂàŒâ.ƽ */ + $"BEDB E39A E202 E3E6 5D81 0002 43E2 E29B" /* ¾Ûãšâ.ãæ]..Cââ› */ + $"E105 E2D6 BDBD CAE2 8AE1 04E2 DABE C1DF" /* á.âÖ½½ÊâŠá.âÚ¾Áß */ + $"87E1 04E3 D9BD C2DF 8BE1 04DF C2BD C0DD" /* ‡á.ãÙ½Âß‹á.ß½ÀÝ */ + $"9BE1 02E2 E55D 8100 0243 E1E1 9BE0 05E1" /* ›á.âå]..Cáá›à.á */ + $"DBBF BDC4 DF8A E004 E1D9 BEC1 DE87 E004" /* Û¿½Äߊà.áÙ¾ÁÞ‡à. */ + $"E1D8 BDC2 DE8A E004 E1DA BEBD C49C E002" /* áؽÂÞŠà.áÚ¾½Äœà. */ + $"E1E4 5D81 0002 43E1 E19C E005 DFC3 BDBF" /* áä]..Cááœà.ßý¿ */ + $"DAE1 89E0 04E1 D9BE C1DE 87E0 04E1 D8BD" /* Úá‰à.áÙ¾ÁÞ‡à.áؽ */ + $"C2DE 8AE0 05E1 D3BD BDCB E19B E002 E1E4" /* ÂÞŠà.áÓ½½Ëá›à.áä */ + $"5D81 0002 43E0 E09C DF05 E0CA BDBD D1E0" /* ]..Cààœß.àʽ½Ñà */ + $"89DF 05E1 D7BE C0DC E086 DF04 E0D7 BDC2" /* ‰ß.á×¾ÀÜà†ß.à׽ */ + $"DE8A DF05 E0C9 BDBD D3E0 9BDF 02E0 E35D" /* ÞŠß.àɽ½Óà›ß.àã] */ + $"8100 0243 DFDF 9CDE 05DF D3BD BDC6 DF88" /* ..CßßœÞ.ßÓ½½Æ߈ */ + $"DE07 DFD7 C2BE BEC3 D8DF 85DE 04DF D6BD" /* Þ.ß×¾¾ÃØß…Þ.ßÖ½ */ + $"C2DD 8ADE 05DB C0BD BFDA DF9B DE02 DFE3" /* ÂÝŠÞ.ÛÀ½¿Úß›Þ.ßã */ + $"5D81 0002 43DF DE9D DD05 DAC0 BDBF D8DE" /* ]..CßÞÝ.ÚÀ½¿ØÞ */ + $"88DD 05C4 BDBE BEBD C586 DD04 DED5 BDC2" /* ˆÝ.Ľ¾¾½Å†Ý.Þս */ + $"DC89 DD05 DED1 BDBD C6DE 9DDD 01E2 5D81" /* ܉Ý.Þѽ½ÆÞÝ.â] */ + $"0002 43DE DD9D DC05 DDC9 BDBD CBDD 86DC" /* ..CÞÝÜ.Ýɽ½ËÝ†Ü */ + $"02DD D7BF 81BE 01BF D985 DC04 DDD5 BDC2" /* .Ý׿¾.¿Ù…Ü.Ýս */ + $"DB8A DC04 C4BD BDD1 DD9D DC01 E15D 8100" /* ÛŠÜ.Ľ½ÑÝÜ.á]. */ + $"0243 DDDC 9DDB 06DC D5BE BEC0 D8DC 85DB" /* .CÝÜÛ.ÜÕ¾¾ÀØÜ…Û */ + $"02DC D8C0 81BE 01C0 D985 DB05 DDD4 BDC1" /* .ÜØÀ¾.ÀÙ…Û.ÝÔ½Á */ + $"DADC 87DB 05DC D1BD BEC2 DA9E DB01 E05D" /* Ú܇Û.Üѽ¾ÂÚžÛ.à] */ + $"8100 0243 DDDB 9EDA 05DB C6BD BDC9 DB86" /* ..CÝÛžÚ.Ûƽ½ÉÛ† */ + $"DA07 DBCA BCBD BDBC CCDB 84DA 07DB D5C2" /* Ú.Ûʼ½½¼ÌÛ„Ú.ÛÕ */ + $"BEBE C4D7 DB85 DA06 DBDA C3BD BDCD DC9E" /* ¾¾Ä×Û…Ú.ÛÚý½ÍÜž */ + $"DA01 E05D 8100 0243 DDDA 9ED9 01DA D380" /* Ú.à]..CÝÚžÙ.ÚÓ€ */ + $"BE01 D3DA 85D9 07DA D9CC C3C3 CDD9 DA85" /* ¾.ÓÚ…Ù.ÚÙÌÃÃÍÙÚ… */ + $"D906 C3BD BEBE BCC6 DA85 D906 DBCC BDBD" /* Ù.ý¾¾¼ÆÚ…Ù.Û̽½ */ + $"C2D8 DA9E D901 E05D 8100 0244 DCDA 9FD9" /* ÂØÚžÙ.à]..DÜÚŸÙ */ + $"05DA C8BD BDC3 D887 D903 DAD9 D9DA 85D9" /* .ÚȽ½Ã؇Ù.ÚÙÙÚ…Ù */ + $"01DA D482 BE01 C0D7 84D9 06DA D3BF BEBD" /* .ÚÔ‚¾.ÀׄÙ.ÚÓ¿¾½ */ + $"CFDA 9FD9 01DF 5D81 0001 44DC A1D9 04D6" /* ÏÚŸÙ.ß]..DÜ¡Ù.Ö */ + $"C1BE BDC8 94D9 01D5 BF81 BE01 C1D7 84D9" /* Á¾½È”Ù.Õ¿¾.Áׄ٠*/ + $"04D7 C3BD BDC6 A1D9 01DF 5D81 0001 44DB" /* .×ý½Æ¡Ù.ß]..DÛ */ + $"A1D8 06D9 D0BE BEBD CCD9 93D8 06C7 BCBE" /* ¡Ø.Ùо¾½ÌÙ“Ø.Ǽ¾ */ + $"BDBC CAD9 83D8 05D7 C6BD BEC0 D5A1 D801" /* ½¼ÊÙƒØ.×ƽ¾ÀÕ¡Ø. */ + $"DE5D 8100 0144 DBA2 D706 D8CA BDBE BECD" /* Þ]..DÛ¢×.Øʽ¾¾Í */ + $"D892 D704 D6C9 C1C2 CB85 D705 C7BD BEBE" /* Ø’×.ÖÉÁÂË…×.ǽ¾¾ */ + $"D0D8 A1D7 01DD 5D81 0001 44DA A4D6 05C6" /* ÐØ¡×.Ý]..DÚ¤Ö.Æ */ + $"BDBE BECC D792 D603 D7D5 D5D7 84D6 05C7" /* ½¾¾Ì×’Ö.×ÕÕׄÖ.Ç */ + $"BDBE BDCC D7A2 D601 DC5D 8100 0144 D9A4" /* ½¾½Ì×¢Ö.Ü]..DÙ¤ */ + $"D505 D4C5 BDBE BDC8 9AD5 07D6 D3C4 BDBE" /* Õ.ÔŽ¾½ÈšÕ.ÖÓĽ¾ */ + $"BDCA D6A3 D501 DC5D 8100 0144 D8A5 D407" /* ½ÊÖ£Õ.Ü]..DØ¥Ô. */ + $"D3C5 BDBE BDC4 D1D5 96D4 07D5 CEC1 BDBE" /* ÓŽ¾½ÄÑÕ–Ô.ÕÎÁ½¾ */ + $"BDC9 D5A4 D401 DB5D 8100 0144 D8A7 D307" /* ½ÉÕ¤Ô.Û]..DاÓ. */ + $"C7BD BEBD BFCA D2D4 92D3 08D4 D1C6 BEBE" /* ǽ¾½¿ÊÒÔ’Ó.ÔÑƾ¾ */ + $"BDBE CBD4 A5D3 01DA 5D81 0001 44D7 A7D2" /* ½¾ËÔ¥Ó.Ú]..D×§Ò */ + $"0AD3 CABF BDBE BDC1 CBD1 D3D3 8CD2 0AD3" /* ÂÓÊ¿½¾½ÁËÑÓÓŒÒÂÓ */ + $"D3D0 C8C0 BDBE BDC1 CDD3 A6D2 01D9 5D81" /* ÓÐÈÀ½¾½ÁÍÓ¦Ò.Ù] */ + $"0001 44D7 A8D2 0CD3 CEC3 BDBE BEBD C1C8" /* ..DרÒ.ÓÎý¾¾½ÁÈ */ + $"CED2 D3D3 85D2 80D3 0AD1 CDC6 BFBD BEBD" /* ÎÒÓÓ…Ò€ÓÂÑÍÆ¿½¾½ */ + $"BEC6 D1D3 A7D2 01D9 5D81 0001 44D6 A9D1" /* ¾ÆÑÓ§Ò.Ù]..DÖ©Ñ */ + $"0ED2 D1CA C1BD BEBE BDBE C2C6 CACD CFD0" /* .ÒÑÊÁ½¾¾½¾ÂÆÊÍÏÐ */ + $"80D1 0ED0 CFCD C9C5 C1BE BDBE BDBE C3CD" /* €Ñ.ÐÏÍÉÅÁ¾½¾½¾ÃÍ */ + $"D2D2 A8D1 01D9 5D81 0001 44D6 AAD0 07D1" /* ÒÒ¨Ñ.Ù]..DÖªÐ.Ñ */ + $"D1D0 C9C2 BEBD BE80 BD02 BEBF C080 C10C" /* ÑÐɾ½¾€½.¾¿À€Á. */ + $"C0BF BEBD BDBE BEBD BEC3 CBD1 D1AA D001" /* À¿¾½½¾¾½¾ÃËÑѪÐ. */ + $"D85C 8100 0144 D5AC CF06 D0D0 CFCB C5C0" /* Ø\..DÕ¬Ï.ÐÐÏËÅÀ */ + $"BE80 BD80 BE00 BD80 BE80 BD05 BFC1 C7CC" /* ¾€½€¾.½€¾€½.¿ÁÇÌ */ + $"D0D0 ACCF 01D7 5C81 0001 44D5 B1CF 06CE" /* ÐЬÏ.×\..DÕ±Ï.Î */ + $"CBC7 C4C2 C1C0 80BF 06C0 C1C3 C5C8 CCCE" /* ËÇÄÂÁÀ€¿.ÀÁÃÅÈÌÎ */ + $"B0CF 01D6 5C81 0001 44D4 B6CE 01CD CD80" /* °Ï.Ö\..DÔ¶Î.ÍÍ€ */ + $"CC01 CDCD B5CE 01D6 5C81 0001 44D3 F5CD" /* Ì.Í͵Î.Ö\..DÓõÍ */ + $"01D6 5C81 0001 44D3 F5CC 01D5 5C81 0001" /* .Ö\..DÓõÌ.Õ\.. */ + $"44D2 F5CB 01D4 5C81 0001 44D2 F5CB 01D4" /* DÒõË.Ô\..DÒõË.Ô */ + $"5C81 0001 44D1 F5CA 01D3 5C81 0001 44D1" /* \..DÑõÊ.Ó\..DÑ */ + $"F5C9 01D3 5C81 0001 44D0 F5C8 01D2 5C81" /* õÉ.Ó\..DÐõÈ.Ò\ */ + $"0001 44CF F5C7 01D1 5C81 0001 44CF F5C7" /* ..DÏõÇ.Ñ\..DÏõÇ */ + $"01D0 5C81 0002 44C6 B9F3 BA02 B9C7 5B81" /* .Ð\..Dƹóº.¹Ç[ */ + $"0001 44C5 F5B9 01C7 5B81 0001 44C9 F5BF" /* ..DÅõ¹.Ç[..DÉõ¿ */ + $"01CB 5C81 0001 44CD F5C4 01CF 5C81 0001" /* .Ë\..DÍõÄ.Ï\.. */ + $"44D1 F5C9 01D3 5C81 0001 44D4 F5CE 01D7" /* DÑõÉ.Ó\..DÔõÎ.× */ + $"5C81 0001 44D8 F5D4 01DB 5D81 0002 46DE" /* \..DØõÔ.Û]..FÞ */ + $"DAF4 D901 E05E 8100 012B B5F5 B701 BF53" /* ÚôÙ.à^..+µõ·.¿S */ + $"8200 0007 F508 0109 04FF 00FF 00FF 00FF" /* ‚...õ..Æ.ÿ.ÿ.ÿ.ÿ */ + $"00FF 00FF 00FF 00FF 00FF 00FF 00FF 00FF" /* .ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ.ÿ */ + $"00E7 0074 386D 6B00 0040 0800 0000 0000" /* .ç.t8mk..@...... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0508 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0700 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"000B 5FAD D3DB DBDB DBDB DBDB DBDB DBDB" /* .._­ÓÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB D9B7 5300 0000 0000" /* ÛÛÛÛÛÛÛÛÙ·S..... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"37CC FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* 7Ìÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FF8A 0100 0000" /* ÿÿÿÿÿÿÿÿÿÿÿŠ.... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 003A" /* ...............: */ + $"EDFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF 7400 0000" /* ÿÿÿÿÿÿÿÿÿÿÿÿt... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 11D8" /* ...............Ø */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF F636 0000" /* ÿÿÿÿÿÿÿÿÿÿÿÿö6.. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 73FF" /* ..............sÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFC3 0600" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÃ.. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0003 C7FF" /* ..............Çÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF 6800" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿh. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0013 E9FF" /* ..............éÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF E71D" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿç. */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 001B EFFF" /* ..............ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFAF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯ */ + $"0300 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 001B EFFF" /* ..............ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"8F04 0000 0000 0000 0000 0000 0000 0000" /* ............... */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 001B EFFF" /* ..............ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFBB 5738 3737 3737 3737 3737 3737 3737" /* ÿ»W8777777777777 */ + $"3737 3737 3737 3737 3737 3737 3737 3737" /* 7777777777777777 */ + $"3737 3737 3737 3737 3737 3737 3737 3737" /* 7777777777777777 */ + $"3737 3737 3737 3737 3737 3737 3737 3737" /* 7777777777777777 */ + $"3734 1F01 0000 0000 0000 0000 001B EFFF" /* 74............ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFE F2C4 6104 0000 0000 0000 001B EFFF" /* ÿþòÄa.........ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFB3 1500 0000 0000 001B EFFF" /* ÿÿÿÿÿ³........ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF BE0B 0000 0000 001B EFFF" /* ÿÿÿÿÿÿ¾.......ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FF7D 0000 0000 001B EFFF" /* ÿÿÿÿÿÿÿ}......ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFE5 1300 0000 001B EFFF" /* ÿÿÿÿÿÿÿå......ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 4900 0000 001B EFFF" /* ÿÿÿÿÿÿÿÿI.....ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 6D00 0000 001A EFFF" /* ÿÿÿÿÿÿÿÿm.....ïÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7400 0000 0021 F4FF" /* ÿÿÿÿÿÿÿÿt....!ôÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0046 FFFF" /* ÿÿÿÿÿÿÿÿs....Fÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0056 FFFF" /* ÿÿÿÿÿÿÿÿs....Vÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0055 FFFF" /* ÿÿÿÿÿÿÿÿs....Uÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7300 0000 0058 FFFF" /* ÿÿÿÿÿÿÿÿs....Xÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF" /* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ */ + $"FFFF FFFF FFFF FFFF 7500 0000 0034 D6DB" /* ÿÿÿÿÿÿÿÿu....4ÖÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB" /* ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ */ + $"DBDB DBDB DBDB DBDB 6300 0000 0000 0708" /* ÛÛÛÛÛÛÛÛc....... */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0808 0808 0808 0808 0808" /* ................ */ + $"0808 0808 0808 0809 0400 0000 0000 0000" /* .......Æ........ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 0000 0000 0000" /* ................ */ + $"0000 0000 0000 0000 0000 00" /* ........... */ +}; + diff --git a/launchers/qt/cmake/installer/installer-header.bmp b/launchers/qt/cmake/installer/installer-header.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7588a338b1bb0685b9d1d2a6ddb828d1ad9715d1 GIT binary patch literal 102656 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 R0000000000z-=uk=s$YR{73)* literal 0 HcmV?d00001 diff --git a/launchers/qt/cmake/installer/installer.ico b/launchers/qt/cmake/installer/installer.ico new file mode 100644 index 0000000000000000000000000000000000000000..d42eae96adb83a395122ecfe25bb1eb1d2893fa2 GIT binary patch literal 306053 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

j*o&)~=MZ2p3J_5M6`m;Per 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 Z2m}IwKp+qZ1OkCTAP@)yqP_I$^?$FtU&;Ug literal 0 HcmV?d00001 diff --git a/launchers/qt/cmake/macros/SetPackagingParameters.cmake b/launchers/qt/cmake/macros/SetPackagingParameters.cmake new file mode 100644 index 0000000000..ed24b3bd6b --- /dev/null +++ b/launchers/qt/cmake/macros/SetPackagingParameters.cmake @@ -0,0 +1,45 @@ +# +# SetPackagingParameters.cmake +# cmake/macros +# +# Created by Leonardo Murillo on 07/14/2015. +# Copyright 2015 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +# This macro checks some Jenkins defined environment variables to determine the origin of this build +# and decides how targets should be packaged. + +macro(SET_PACKAGING_PARAMETERS) + set(PR_BUILD 0) + set(PRODUCTION_BUILD 0) + set(DEV_BUILD 0) + set(BUILD_NUMBER 0) + + set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV") + set_from_env(RELEASE_NUMBER RELEASE_NUMBER "") + set_from_env(STABLE_BUILD STABLE_BUILD 0) + + message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}") + set(BUILD_NUMBER ${RELEASE_NUMBER}) + + if (RELEASE_TYPE STREQUAL "PRODUCTION") + set(PRODUCTION_BUILD 1) + set(BUILD_VERSION ${RELEASE_NUMBER}) + + # add definition for this release type + add_definitions(-DPRODUCTION_BUILD) + + elseif (RELEASE_TYPE STREQUAL "PR") + set(PR_BUILD 1) + set(BUILD_VERSION "PR${RELEASE_NUMBER}") + + # add definition for this release type + add_definitions(-DPR_BUILD) + else () + set(DEV_BUILD 1) + set(BUILD_VERSION "dev") + endif () + +endmacro(SET_PACKAGING_PARAMETERS) diff --git a/launchers/qt/cmake/modules/FindQtStaticDeps.cmake b/launchers/qt/cmake/modules/FindQtStaticDeps.cmake new file mode 100644 index 0000000000..6924a7b9eb --- /dev/null +++ b/launchers/qt/cmake/modules/FindQtStaticDeps.cmake @@ -0,0 +1,31 @@ + +set(qt_static_lib_dependices + "qtpcre2" + "qtlibpng" + "qtfreetype" + "Qt5AccessibilitySupport" + "Qt5FbSupport" + "Qt5GraphicsSupport" + "Qt5CglSupport" + "Qt5ClipboardSupport" + "Qt5OpenGLExtensions" + "Qt5QuickTemplates2" + "Qt5FontDatabaseSupport" + "Qt5ThemeSupport" + "Qt5EventDispatcherSupport") + +if (WIN32) +elseif(APPLE) +endif() + +set(LIBS_PREFIX "${_qt5Core_install_prefix}/lib/") +foreach (_qt_static_dep ${qt_static_lib_dependices}) + if (WIN32) + set(lib_path "${LIBS_PREFIX}${_qt_static_dep}.lib") + else() + set(lib_path "${LIBS_PREFIX}lib${_qt_static_dep}.a") + endif() + set(QT_STATIC_LIBS ${QT_STATIC_LIBS} ${lib_path}) +endforeach() + +unset(qt_static_lib_dependices) diff --git a/launchers/qt/cmake/modules/MacOSXBundleInfo.plist.in b/launchers/qt/cmake/modules/MacOSXBundleInfo.plist.in new file mode 100644 index 0000000000..3fe8e80f7a --- /dev/null +++ b/launchers/qt/cmake/modules/MacOSXBundleInfo.plist.in @@ -0,0 +1,37 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${APP_NAME} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + com.highfidelity.launcher + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + CFBundleVersion + 1.0 + NSMainNibFile + Window + NSPrincipalClass + NSApplication + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleDisplayName + ${MACOSX_BUNDLE_BUNDLE_NAME} + + diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 73e512be63..e7d2055995 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -30,7 +30,7 @@ public: ERROR_SCREEN, UI_STATE_NUM }; - Q_ENUMS(UIState); + Q_ENUMS(UIState) enum LastLoginError { NONE = 0, @@ -38,7 +38,7 @@ public: CREDENTIALS, LAST_ERROR_NUM }; - Q_ENUMS(LastLoginError); + Q_ENUMS(LastLoginError) Q_INVOKABLE QString getCurrentUISource() const; static void declareQML(); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 50bfb5f59d..a026f3a852 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -3,8 +3,8 @@ #include "LauncherWindow.h" #include "Launcher.h" -//Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); -Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +//Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); From 8473acaceeb0ef6292780c59ea02f5f15746188e Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 3 Sep 2019 15:50:59 -0700 Subject: [PATCH 017/203] plugin fix --- launchers/qt/src/main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index a026f3a852..3fc8c73b58 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -3,8 +3,13 @@ #include "LauncherWindow.h" #include "Launcher.h" + +#ifdef Q_OS_WIN +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#elif defined(Q_OS_MACOS) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); -//Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#endif + Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); From 058343fecc9574b23c6b1d39424b657db063e726 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 3 Sep 2019 18:05:13 -0700 Subject: [PATCH 018/203] fix window cmake --- launchers/qt/cmake/modules/FindQtStaticDeps.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launchers/qt/cmake/modules/FindQtStaticDeps.cmake b/launchers/qt/cmake/modules/FindQtStaticDeps.cmake index 6924a7b9eb..b80080695a 100644 --- a/launchers/qt/cmake/modules/FindQtStaticDeps.cmake +++ b/launchers/qt/cmake/modules/FindQtStaticDeps.cmake @@ -5,9 +5,6 @@ set(qt_static_lib_dependices "qtfreetype" "Qt5AccessibilitySupport" "Qt5FbSupport" - "Qt5GraphicsSupport" - "Qt5CglSupport" - "Qt5ClipboardSupport" "Qt5OpenGLExtensions" "Qt5QuickTemplates2" "Qt5FontDatabaseSupport" @@ -16,6 +13,11 @@ set(qt_static_lib_dependices if (WIN32) elseif(APPLE) + set(qt_static_lib_dependices + ${qt_static_lib_dependices} + "Qt5GraphicsSupport" + "Qt5CglSupport" + "Qt5ClipboardSupport") endif() set(LIBS_PREFIX "${_qt5Core_install_prefix}/lib/") From 4c5de120b71ce7b27001f16dd7c02042bfe8ab3e Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 4 Sep 2019 09:13:22 -0700 Subject: [PATCH 019/203] enable post build script for qt launcher --- tools/ci-scripts/postbuild.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci-scripts/postbuild.py b/tools/ci-scripts/postbuild.py index b93ed5a664..f04217ce24 100644 --- a/tools/ci-scripts/postbuild.py +++ b/tools/ci-scripts/postbuild.py @@ -149,7 +149,7 @@ def signBuild(executablePath): def zipDarwinLauncher(): - launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', sys.platform) + launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', 'qt') launcherBuildPath = os.path.join(BUILD_PATH, 'launcher') archiveName = computeArchiveName('HQ Launcher') @@ -170,7 +170,7 @@ def zipDarwinLauncher(): def buildLightLauncher(): - launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', sys.platform) + launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', 'qt') launcherBuildPath = os.path.join(BUILD_PATH, 'launcher') if not os.path.exists(launcherBuildPath): os.makedirs(launcherBuildPath) From 954e5c986a922e3128f015dbaee9455b71069f62 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 4 Sep 2019 10:10:35 -0700 Subject: [PATCH 020/203] down grade cmake required version --- launchers/qt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 47ab8f75b9..3989ac0e90 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.10) project(HQLauncher) if (APPLE) From 1583db946343ee8f181ec2825dd5ea6a033aab8d Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 4 Sep 2019 15:09:32 -0700 Subject: [PATCH 021/203] disable bitcode --- launchers/qt/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 3989ac0e90..5dce490ded 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -141,6 +141,7 @@ elseif (APPLE) set(MACOSX_BUNDLE_ICON_FILE "interface.icns") add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files}) + set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") endif() target_link_libraries(${PROJECT_NAME} From 9d62e37a465b325b0b1151511269e192abcb3dd6 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 5 Sep 2019 14:08:36 -0700 Subject: [PATCH 022/203] fix jekins mac builds --- launchers/qt/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 5dce490ded..b91509872e 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -13,7 +13,6 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) #set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) -#add_compile_options(-W -Wall -Wextra -Wpedantic -Werror -std=c++1z) include("cmake/init.cmake") include("cmake/macros/SetPackagingParameters.cmake") @@ -67,8 +66,8 @@ endif () if (APPLE) ExternalProject_Add( qtlite - URL "https://dantescalves.com/launcher//qt-lite-ssl-mac.zip" - URL_HASH MD5=2b073937a7c5dbae6e6ed244d2063398 + URL "https://dantescalves.com/launcher/qt-lite-clang-8.1.0.zip" + URL_HASH MD5=f8462bfcd54e6b05ef888f8b197149c2 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 59f490c97e54ea07af0bc6ff9d5db7702938627e Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 5 Sep 2019 15:19:35 -0700 Subject: [PATCH 023/203] trying to build resources --- launchers/qt/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index b91509872e..eefbbe969d 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -178,8 +178,9 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${Qt5Core_INCLUDE_DIRS} ${Qt5Quick_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} - ${Qt5Qml_INCLUDE_DIRS} - ) + ${Qt5Qml_INCLUDE_DIRS}) + +add_dependencies(${PROJECT_NAME} resources) if (APPLE) target_include_directories(${PROJECT_NAME} PUBLIC From 97460c27cfdf77dd181191b5b5e944bffe07d3c5 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 9 Sep 2019 15:52:42 -0700 Subject: [PATCH 024/203] adding new UI screens --- .../qt/resources/qml/DownloadFinished.qml | 68 ++++++++ .../qml/HFBase/CreateAccountBase.qml | 159 ++++++++++++++++++ launchers/qt/resources/qml/HFBase/Error.qml | 105 ++++++++++++ .../resources/qml/HFControls/HFTextField.qml | 12 +- launchers/qt/resources/qml/root.qml | 6 +- launchers/qt/src/Launcher.cpp | 2 +- launchers/qt/src/Launcher.h | 1 + launchers/qt/src/LauncherState.cpp | 6 +- launchers/qt/src/PathUtils.h | 6 + 9 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 launchers/qt/resources/qml/DownloadFinished.qml create mode 100644 launchers/qt/resources/qml/HFBase/CreateAccountBase.qml create mode 100644 launchers/qt/resources/qml/HFBase/Error.qml create mode 100644 launchers/qt/src/PathUtils.h diff --git a/launchers/qt/resources/qml/DownloadFinished.qml b/launchers/qt/resources/qml/DownloadFinished.qml new file mode 100644 index 0000000000..f817137a8c --- /dev/null +++ b/launchers/qt/resources/qml/DownloadFinished.qml @@ -0,0 +1,68 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 +import "HFControls" + +Item { + id: root + anchors.centerIn: parent + + Image { + anchors.centerIn: parent + width: parent.width + height: parent.height + mirror: true + source: "qrc:/images/hifi_window@2x.png" + transformOrigin: Item.Center + rotation: 0 + } + + + Image { + id: logo + width: 132 + height: 134 + source: "qrc:/images/HiFi_Voxel.png" + + anchors { + top: root.top + topMargin: 84 + horizontalCenter: root.horizontalCenter + } + } + + Text { + id: header + width: 87 + height: 31 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Graphik" + font.bold: true + font.pixelSize: 28 + color: "#FFFFFF" + text: "You're all set!" + anchors { + top: logo.bottom + topMargin: 26 + horizontalCenter: logo.horizontalCenter + } + } + + Text { + id: description + width: 100 + height: 40 + font.family: "Graphik" + font.pixelSize: 14 + color: "#FFFFFF" + text: "We will see you in world." + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors { + top: header.bottom + topMargin: 8 + horizontalCenter: header.horizontalCenter + } + } + +} diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml new file mode 100644 index 0000000000..563e12f924 --- /dev/null +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -0,0 +1,159 @@ +import QtQuick 2.3 +import QtQuick 2.1 +import "../HFControls" + + +Item { + id: root + anchors.centerIn: parent + property string titleText: "Create your account" + property string usernamePlaceholder: "User name" + property string passwordPlaceholder: "Set a password" + property int marginLeft: root.width * 0.15 + + Image { + anchors.centerIn: parent + width: parent.width + height: parent.height + mirror: true + source: "qrc:/images/hifi_window@2x.png" + transformOrigin: Item.Center + rotation: 180 + } + + Text { + id: title + width: 325 + height: 26 + font.family: "Graphik" + font.bold: true + font.pixelSize: 24 + color: "#FFFFFF" + text: root.titleText + anchors { + top: root.top + topMargin: 29 + left: root.left + leftMargin: root.marginLeft + } + } + + Text { + id: instruction + width: 425 + height: 22 + font.family: "Graphik" + font.pixelSize: 10 + color: "#C4C4C4" + text: "Use the email address that you regisetered with." + anchors { + left: root.left + leftMargin: root.marginLeft + top: title.bottom + topMargin: 9 + } + } + + HFTextField { + id: organization + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: "Organization name" + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: instruction.bottom + left: root.left + leftMargin: root.marginLeft + topMargin: 4 + } + } + + HFTextField { + id: username + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: root.usernamePlaceholder + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: organization.bottom + left: root.left + leftMargin: root.marginLeft + topMargin: 10 + } + } + + HFTextField { + id: passwordField + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: root.passwordPlaceholder + color: "#808080" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + echoMode: TextInput.Password + anchors { + top: username.bottom + left: root.left + leftMargin: root.marginLeft + topMargin: 10 + } + } + + + Text { + id: displayNameText + text: "This is the display name other people see in world. It can be changed at \nanytime, from your profile" + font.family: "Graphik" + font.pixelSize: 10 + color: "#FFFFFF" + anchors { + top: passwordField.bottom + left: root.left + leftMargin: root.marginLeft + topMargin: 15 + } + } + + HFTextField { + id: displayName + width: 353 + height: 40 + font.family: "Graphik" + font.pixelSize: 18 + placeholderText: "Password" + color: "#C4C4C4" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: displayNameText.bottom + left: root.left + leftMargin: root.marginLeft + topMargin: 8 + } + } + + HFButton { + id: button + width: 122 + height: 36 + + font.family: "Graphik" + font.pixelSize: 18 + text: "NEXT" + + anchors { + top: displayName.bottom + left: root.left + leftMargin: root.marginLeft + topMargin: 15 + } + + onClicked: LauncherState.login(username.text, password.text) + } +} diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml new file mode 100644 index 0000000000..d05ef01413 --- /dev/null +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -0,0 +1,105 @@ +import QtQuick 2.3 +import QtQuick 2.1 +import "../HFControls" + + +Item { + id: root + anchors.centerIn: parent + + Image { + anchors.centerIn: parent + width: parent.width + height: parent.height + mirror: false + source: "qrc:/images/hifi_window@2x.png" + //fillMode: Image.PreserveAspectFit + transformOrigin: Item.Center + //rotation: 90 + } + + Image { + id: logo + width: 132 + height: 134 + source: "qrc:/images/HiFi_Voxel.png" + + anchors { + top: root.top + topMargin: 84 + horizontalCenter: root.horizontalCenter + } + } + + Text { + id: header + width: 87 + height: 31 + font.family: "Graphik" + font.bold: true + font.pixelSize: 28 + color: "#FFFFFF" + text: "Uh oh." + anchors { + top: logo.bottom + topMargin: 26 + horizontalCenter: logo.horizontalCenter + } + } + + Text { + id: description + width: 100 + height: 40 + font.family: "Graphik" + font.pixelSize: 14 + color: "#FFFFFF" + text: "We seem to have a problem.\n Please restart HQ Launcher" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors { + top: header.bottom + topMargin: 8 + horizontalCenter: header.horizontalCenter + } + } + + + HFButton { + id: button + width: 166 + height: 35 + font.family: "Graphik" + font.pixelSize: 18 + text: "Restart" + + anchors { + top: description.bottom + topMargin: 15 + horizontalCenter: description.horizontalCenter + } + } + + + Text { + id: hifilogo + width: 100 + height: 18 + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Graphik" + font.pixelSize: 18 + font.bold: true + color: "#FFFFFF" + + text: "High Fidelity" + anchors { + right: root.right + rightMargin: 17 + bottom: root.bottom + bottomMargin: 17 + } + } + +} diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index 77a6601fdf..7d25cc5193 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 2.1 TextField { id: control - color: "#FFFFFF" + //color: "#000000" font.family: "Graphik Medium" font.pixelSize: 22 verticalAlignment: TextInput.AlignVCenter @@ -13,14 +13,8 @@ TextField { background: Item { anchors.fill: parent Rectangle { - anchors { - bottom: parent.bottom - left: parent.left - leftMargin: 7 - right: parent.right - } - height: 1 - color: control.seperatorColor + color: "#FFFFFF" + anchors.fill: parent } } } diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 0ceda5b189..b2fdedb441 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -4,11 +4,11 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 import HQLauncher 1.0 import "HFControls" -Image { +Item { id: root width: 515 - height: 450 - source: "../images/hifi_window@2x.png" + height: 390 + //source: "../images/hifi_window@2x.png" Loader { anchors.fill: parent diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 20405d5a39..50c4a665e5 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -11,7 +11,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); - _launcherState->setUIState(LauncherState::SPLASH_SCREEN); + _launcherState->setUIState(LauncherState::DOWNLOAD_FINSISHED); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->setFlags(Qt::FramelessWindowHint); diff --git a/launchers/qt/src/Launcher.h b/launchers/qt/src/Launcher.h index 2310c6ba08..7b939485d6 100644 --- a/launchers/qt/src/Launcher.h +++ b/launchers/qt/src/Launcher.h @@ -1,3 +1,4 @@ +#pragma once #include #include diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 52e13bc3b7..090bf5e9c5 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -14,8 +14,8 @@ #include static const std::array QML_FILE_FOR_UI_STATE = - { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml", - "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } }; + { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/HFBase/CreateAccountBase.qml", "qrc:/qml/DisplayName.qml", + "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinished.qml", "qrc:/qml/HFBase/Error.qml" } }; LauncherState::LauncherState() { // TODO Show splash screen until this request is complete @@ -61,7 +61,7 @@ LauncherState::LauncherState() { } } } - setUIState(LauncherState::LOGIN_SCREEN); + //setUIState(LauncherState::LOGIN_SCREEN); }); } diff --git a/launchers/qt/src/PathUtils.h b/launchers/qt/src/PathUtils.h new file mode 100644 index 0000000000..4c6c6e9515 --- /dev/null +++ b/launchers/qt/src/PathUtils.h @@ -0,0 +1,6 @@ +#pragma once + +class PathUtils { +public: + static resourcePath(); +}; From 93367ce8a4d04c8cd9fabac1138465da5567e21e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Sep 2019 13:56:38 -0700 Subject: [PATCH 025/203] Add unzipper to qt launcher --- launchers/qt/CMakeLists.txt | 8 +- launchers/qt/deps/miniz/miniz.cpp | 7657 +++++++++++++++++++++++++++++ launchers/qt/deps/miniz/miniz.h | 1338 +++++ launchers/qt/deps/miniz/stdafx.h | 0 launchers/qt/src/Unzipper.cpp | 80 + launchers/qt/src/Unzipper.h | 21 + 6 files changed, 9103 insertions(+), 1 deletion(-) create mode 100644 launchers/qt/deps/miniz/miniz.cpp create mode 100644 launchers/qt/deps/miniz/miniz.h create mode 100644 launchers/qt/deps/miniz/stdafx.h create mode 100644 launchers/qt/src/Unzipper.cpp create mode 100644 launchers/qt/src/Unzipper.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index ad31023cb7..505bdcf1d3 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -72,7 +72,12 @@ set(src_files src/LauncherState.h src/LauncherState.cpp src/LauncherWindow.h - src/LauncherWindow.cpp) + src/LauncherWindow.cpp + src/Unzipper.h + src/Unzipper.cpp + deps/miniz/miniz.h + deps/miniz/miniz.cpp + ) set(TARGET_NAME ${PROJECT_NAME}) add_executable(${PROJECT_NAME} ${src_files}) @@ -109,6 +114,7 @@ target_link_libraries(${PROJECT_NAME} "${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib") target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/deps/ ${Qt5Core_INCLUDE_DIRS} ${Qt5Quick_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} diff --git a/launchers/qt/deps/miniz/miniz.cpp b/launchers/qt/deps/miniz/miniz.cpp new file mode 100644 index 0000000000..96d7cc106a --- /dev/null +++ b/launchers/qt/deps/miniz/miniz.cpp @@ -0,0 +1,7657 @@ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ +#include "stdafx.h" +#include "miniz.h" + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API's */ + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ +#if 0 + mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#else +/* Faster, but larger CPU cache footprint. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +#ifndef MINIZ_NO_ZLIB_APIS + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; /* Can't make forward progress without some input. + */ + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflateReset(mz_streamp pStream) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + + pDecomp = (inflate_state *)pStream->state; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + /* pDecomp->m_window_bits = window_bits */; + + return MZ_OK; +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (;;) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct + { + int m_err; + const char *m_pDesc; + } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif /*MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Compression (independent from all decompression API's) */ + +/* Purposely making these tables static for faster init and thread safety. */ +static const mz_uint16 s_tdefl_len_sym[256] = + { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, + 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, + 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 + }; + +static const mz_uint8 s_tdefl_len_extra[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 + }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = + { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 + }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = + { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 + }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = + { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = + { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 + }; + +/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ +typedef struct +{ + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) + { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) + { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) + { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) + { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) + { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) + { + used++; + root--; + } + while (avbl > used) + { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +/* Limits canonical Huffman code table's max code size. */ +enum +{ + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 +}; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) + { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) + { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do \ + { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) \ + { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) \ + { \ + if (rle_repeat_count < 3) \ + { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } \ + else \ + { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) \ + { \ + if (rle_z_count < 3) \ + { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } \ + else if (rle_z_count <= 10) \ + { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } \ + else \ + { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) + { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) + { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } + else + { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) + { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) +#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) +#endif +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) + continue; + p = s; + probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); + break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) +{ + mz_uint32 ret; + memcpy(&ret, p, sizeof(mz_uint32)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) +#endif +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); +#else + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; +#endif + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + /* Simple lazy/greedy parsing state machine. */ + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) + { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + /* Move the lookahead forward by len_to_move bytes. */ + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); + /* Check if it's time to flush the current LZ codes to the internal output buffer. */ + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) + { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) + { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_dict); + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do + { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ +#endif + +/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at + http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. + This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) + { + MZ_FREE(pComp); + return NULL; + } + /* write dummy header */ + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + /* compress image data */ + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) + { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) + { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + /* write real header */ + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x44, 0x41, + 0x54 }; + pnghdr[18] = (mz_uint8)(w >> 8); + pnghdr[19] = (mz_uint8)w; + pnghdr[22] = (mz_uint8)(h >> 8); + pnghdr[23] = (mz_uint8)h; + pnghdr[25] = chans[num_chans]; + pnghdr[33] = (mz_uint8)(*pLen_out >> 24); + pnghdr[34] = (mz_uint8)(*pLen_out >> 16); + pnghdr[35] = (mz_uint8)(*pLen_out >> 8); + pnghdr[36] = (mz_uint8)*pLen_out; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + /* write footer (IDAT CRC-32, followed by IEND chunk) */ + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) + { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + /* compute final size of file, grab compressed data buffer and return */ + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ +/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc() +{ + return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); +} + +void tdefl_compressor_free(tdefl_compressor *pComp) +{ + MZ_FREE(pComp); +} +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Decompression (completely independent from all compression API's) */ + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) \ + { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do \ + { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do \ + { \ + for (;;) \ + { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +#define TINFL_GET_BYTE(state_index, c) \ + do \ + { \ + while (pIn_buf_cur >= pIn_buf_end) \ + { \ + TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ + } \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do \ + { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ +/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do \ + { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1ULL << (8ULL + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1ULL << (8ULL + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) + { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); +#else + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; +#endif + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + while(counter>2) + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + counter -= 3; + } + if (counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + + /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ + TINFL_SKIP_BITS(32, num_bits & 7); + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ + + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + for (counter = 0; counter < 4; ++counter) + { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + + TINFL_CR_FINISH + +common_exit: + /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ + /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ + if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) + { + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + } + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Higher level helper functions. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +#ifndef MINIZ_NO_MALLOC +tinfl_decompressor *tinfl_decompressor_alloc() +{ + tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); + if (pDecomp) + tinfl_init(pDecomp); + return pDecomp; +} + +void tinfl_decompressor_free(tinfl_decompressor *pDecomp) +{ + MZ_FREE(pDecomp); +} +#endif + +#ifdef __cplusplus +} +#endif +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat64 +#define MZ_FILE_STAT _stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__APPLE__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen(p, m, s) +#define MZ_DELETE_FILE remove + +#else +#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#ifdef __STRICT_ANSI__ +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#else +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif /* #ifdef _MSC_VER */ +#endif /* #ifdef MINIZ_NO_STDIO */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) +{ + memset(pArray, 0, sizeof(mz_zip_array)); + pArray->m_element_size = element_size; +} + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + if (n > 0) + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) +{ + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) +{ + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data; + void* buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) + { + buf = MZ_MALLOC(ext_data_size); + if(buf==NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8*)buf; + } + else + { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +void mz_zip_zero_struct(mz_zip_archive *pZip) +{ + if (pZip) + MZ_CLEAR_OBJ(*pZip); +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; + status = MZ_FALSE; + } + } + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) +{ + if (!pMem) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pNeeds_keepalive = NULL; + +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + + pZip->m_pState->m_mem_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); +} + +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) +{ + mz_uint64 file_size; + MZ_FILE *pFile; + + if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + file_size = archive_size; + if (!file_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + } + + file_size = MZ_FTELL64(pFile); + } + + /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ + + if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) +{ + mz_uint64 cur_file_ofs; + + if ((!pZip) || (!pFile)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + cur_file_ofs = MZ_FTELL64(pFile); + + if (!archive_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + + archive_size = MZ_FTELL64(pFile) - cur_file_ofs; + + if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = archive_size; + pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const uint32_t size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + uint32_t file_index = pIndices[(uint32_t)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return NULL; + } + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pState->m_pMem) + { + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + } + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); +#endif + } + + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + } +#endif + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + status = TINFL_STATUS_FAILED; + } + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); +#endif + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (file_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_reader_extract_iter_state *pState; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + /* Argument sanity check */ + if ((!pZip) || (!pZip->m_pState)) + return NULL; + + /* Allocate an iterator status structure */ + pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); + if (!pState) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + /* Fetch file details */ + if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Encryption and patch files are not supported. */ + if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Init state - save args */ + pState->pZip = pZip; + pState->flags = flags; + + /* Init state - reset variables to defaults */ + pState->status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + pState->file_crc32 = MZ_CRC32_INIT; +#endif + pState->read_buf_ofs = 0; + pState->out_buf_ofs = 0; + pState->pRead_buf = NULL; + pState->pWrite_buf = NULL; + pState->out_blk_remain = 0; + + /* Read and parse the local directory entry. */ + pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; + pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + else + { + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, therefore intermediate read buffer required */ + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + else + { + /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ + pState->read_buf_size = 0; + } + pState->read_buf_avail = 0; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, init decompressor */ + tinfl_init( &pState->inflator ); + + /* Allocate write buffer */ + if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + if (pState->pRead_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + + return pState; +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_uint32 file_index; + + /* Locate file index by name */ + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return NULL; + + /* Construct iterator */ + return mz_zip_reader_extract_iter_new(pZip, file_index, flags); +} + +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) +{ + size_t copied_to_caller = 0; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) + return 0; + + if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data, calc amount to return. */ + copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); + + /* Zip is in memory....or requires reading from a file? */ + if (pState->pZip->m_pState->m_pMem) + { + /* Copy data to caller's buffer */ + memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); + pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; + } + else + { + /* Read directly into caller's buffer */ + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) + { + /* Failed to read all that was asked for, flag failure and alert user */ + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + copied_to_caller = 0; + } + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Compute CRC if not returning compressed data only */ + if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); +#endif + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += copied_to_caller; + pState->out_buf_ofs += copied_to_caller; + pState->comp_remaining -= copied_to_caller; + } + else + { + do + { + /* Calc ptr to write buffer - given current output pos and block size */ + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + /* Calc max output size - given current output pos and block size */ + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + if (!pState->out_blk_remain) + { + /* Read more data from file if none available (and reading from file) */ + if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) + { + /* Calc read size */ + pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += pState->read_buf_avail; + pState->comp_remaining -= pState->read_buf_avail; + pState->read_buf_ofs = 0; + } + + /* Perform decompression */ + in_buf_size = (size_t)pState->read_buf_avail; + pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + pState->read_buf_avail -= in_buf_size; + pState->read_buf_ofs += in_buf_size; + + /* Update current output block size remaining */ + pState->out_blk_remain = out_buf_size; + } + + if (pState->out_blk_remain) + { + /* Calc amount to return. */ + size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); + + /* Copy data to caller's buffer */ + memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Perform CRC */ + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); +#endif + + /* Decrement data consumed from block */ + pState->out_blk_remain -= to_copy; + + /* Inc output offset, while performing sanity check */ + if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Increment counter of data copied to caller */ + copied_to_caller += to_copy; + } + } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); + } + + /* Return how many bytes were copied into user buffer */ + return copied_to_caller; +} + +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) +{ + int status; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) + return MZ_FALSE; + + /* Was decompression completed and requested? */ + if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + pState->status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (pState->file_crc32 != pState->file_stat.m_crc32) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + } +#endif + } + + /* Free buffers */ + if (!pState->pZip->m_pState->m_pMem) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); + if (pState->pWrite_buf) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); + + /* Save status */ + status = pState->status; + + /* Free context */ + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); + + return status == TINFL_STATUS_DONE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; + + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + + if (MZ_FCLOSE(pFile) == EOF) + { + if (status) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + + status = MZ_FALSE; + } + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + + return status; +} + +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} + +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); +} + +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_uint32 *p = (mz_uint32 *)pOpaque; + (void)file_ofs; + *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); + return n; +} + +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_zip_internal_state *pState; + const mz_uint8 *pCentral_dir_header; + mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint64 local_header_ofs = 0; + mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; + mz_bool has_data_descriptor; + mz_uint32 local_header_bit_flags; + + mz_zip_array file_data_array; + mz_zip_array_init(&file_data_array, 1); + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (file_index > pZip->m_total_files) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); + + if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_is_encrypted) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports stored and deflate. */ + if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + if (!file_stat.m_is_supported) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + /* Read and parse the local directory entry. */ + local_header_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); + local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + has_data_descriptor = (local_header_bit_flags & 8) != 0; + + if (local_header_filename_len != strlen(file_stat.m_filename)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (local_header_filename_len) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ + if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_uint32 extra_size_remaining = local_header_extra_len; + const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ + /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ + if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) + { + mz_uint8 descriptor_buf[32]; + mz_bool has_id; + const mz_uint8 *pSrc; + mz_uint32 file_crc32; + mz_uint64 comp_size = 0, uncomp_size = 0; + + mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; + + file_crc32 = MZ_READ_LE32(pSrc); + + if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); + } + else + { + comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); + } + + if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + else + { + if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + mz_zip_array_clear(pZip, &file_data_array); + + if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) + { + if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) + return MZ_FALSE; + + /* 1 more check to be sure, although the extract checks too. */ + if (uncomp_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + return MZ_FALSE; + } + } + + return MZ_TRUE; + +handle_failure: + mz_zip_array_clear(pZip, &file_data_array); + return MZ_FALSE; +} + +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) +{ + mz_zip_internal_state *pState; + uint32_t i; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Basic sanity checks */ + if (!pState->m_zip64) + { + if (pZip->m_total_files > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pZip->m_archive_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + else + { + if (pZip->m_total_files >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + for (i = 0; i < pZip->m_total_files; i++) + { + if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) + { + mz_uint32 found_index; + mz_zip_archive_file_stat stat; + + if (!mz_zip_reader_file_stat(pZip, i, &stat)) + return MZ_FALSE; + + if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) + return MZ_FALSE; + + /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ + if (found_index != i) + return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + } + + if (!mz_zip_validate_file(pZip, i, flags)) + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if ((!pMem) || (!size)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if (!pFilename) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +/* ------------------- .ZIP archive writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) +{ + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if (!n) + return 0; + + /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ + if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + return 0; + } + + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + + while (new_capacity < new_size) + new_capacity *= 2; + + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return 0; + } + + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + status = MZ_FALSE; + } + } + + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) +{ + mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; + + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + { + if (!pZip->m_pRead) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (pZip->m_file_offset_alignment) + { + /* Ensure user specified file offset alignment is a power of 2. */ + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + + pZip->m_pState->m_zip64 = zip64; + pZip->m_pState->m_zip64_has_extended_info_fields = zip64; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + return mz_zip_writer_init_v2(pZip, existing_size, 0); +} + +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_mem_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; + + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + return 0; + } + + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); +} + +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) +{ + MZ_FILE *pFile; + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + + pZip->m_pState->m_pFile = pFile; + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; + char buf[4096]; + + MZ_CLEAR_OBJ(buf); + + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, 0, flags)) + return MZ_FALSE; + + pZip->m_pState->m_pFile = pFile; + pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_zip_internal_state *pState; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) + { + /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ + if (!pZip->m_pState->m_zip64) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* No sense in trying to write to an archive that's already at the support max size */ + if (pZip->m_pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + (void)pFilename; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); +#else + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (!pFilename) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + } + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; +#endif /* #ifdef MINIZ_NO_STDIO */ + } + else if (pState->m_pMem) + { + /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + } + /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ + else if (!pZip->m_pWrite) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Start writing new files at the archive's current central directory location. */ + /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_central_directory_file_ofs = 0; + + /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ + /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ + /* TODO: We could easily maintain the sorted central directory offsets. */ + mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); +} + +/* TODO: pArchive_name is a terrible name here! */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) +{ + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + MZ_WRITE_LE16(pDst + 2, 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, + mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes, + const char *user_extra_data, mz_uint user_extra_data_len) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + if (!pZip->m_pState->m_zip64) + { + if (local_header_ofs > 0xFFFFFFFF) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + /* Try to resize the central directory array back into its original state. */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ + if (*pArchive_name == '/') + return MZ_FALSE; + + /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ + + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); +} + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = 0; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + +#ifndef MINIZ_NO_TIME + if (last_modified != NULL) + { + mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); + } + else + { + MZ_TIME_T cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif /* #ifndef MINIZ_NO_TIME */ + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + /* Set DOS Subdirectory attribute bit. */ + ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + + /* Subdirectories cannot contain data. */ + if ((buf_size) || (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes; + + MZ_CLEAR_OBJ(local_dir_header); + + if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + method = MZ_DEFLATED; + } + + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + + if (pExtra_data != NULL) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + if (uncomp_size) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_zip_internal_state *pState; + mz_uint64 file_ofs = 0; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX)) + { + /* Source file is too large for non-zip64 */ + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + pState->m_zip64 = MZ_TRUE; + } + + /* We could support this, but why? */ + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + +#ifndef MINIZ_NO_TIME + if (pFile_time) + { + mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); + } +#endif + + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_archive_file_ofs; + + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + if (uncomp_size && level) + { + method = MZ_DEFLATED; + } + + MZ_CLEAR_OBJ(local_dir_header); + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((read_callback(callback_opaque, file_ofs, pRead_buf, n) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + } + + for (;;) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + tdefl_flush flush = TDEFL_NO_FLUSH; + + if (read_callback(callback_opaque, file_ofs, pRead_buf, in_buf_size)!= in_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + break; + } + + file_ofs += in_buf_size; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) + flush = TDEFL_FULL_FLUSH; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + break; + } + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO + +static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pSrc_file); +} + +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, size_to_add, pFile_time, pComment, comment_size, level_and_flags, + user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); +} + +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + MZ_FILE *pSrc_file = NULL; + mz_uint64 uncomp_size = 0; + MZ_TIME_T file_modified_time; + MZ_TIME_T *pFile_time = NULL; + mz_bool status; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + pFile_time = &file_modified_time; + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); + + MZ_FCLOSE(pSrc_file); + + return status; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) +{ + /* + 64 should be enough for any new zip64 data */ + if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); + + if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) + { + mz_uint8 new_ext_block[64]; + mz_uint8 *pDst = new_ext_block; + mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + mz_write_le16(pDst + sizeof(mz_uint16), 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + mz_write_le64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + } + + if (pComp_size) + { + mz_write_le64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + mz_write_le64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + } + + if (pDisk_start) + { + mz_write_le32(pDst, *pDisk_start); + pDst += sizeof(mz_uint32); + } + + mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); + + if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if ((pExt) && (ext_len)) + { + mz_uint32 extra_size_remaining = ext_len; + const mz_uint8 *pExtra_data = pExt; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + return MZ_TRUE; +} + +/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; + mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + mz_zip_archive_file_stat src_file_stat; + mz_uint32 src_filename_len, src_comment_len, src_ext_len; + mz_uint32 local_header_filename_size, local_header_extra_len; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ + if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Get pointer to the source central dir header and crack it */ + if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); + src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); + src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; + + /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ + if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + if (!pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) + return MZ_FALSE; + + cur_src_file_ofs = src_file_stat.m_local_header_ofs; + cur_dst_file_ofs = pZip->m_archive_size; + + /* Read the source archive's local dir header */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Compute the total size we need to copy (filename+extra data+compressed data) */ + local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; + + /* Try to find a zip64 extended information field */ + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_zip_array file_data_array; + const mz_uint8 *pExtra_data; + mz_uint32 extra_size_remaining = local_header_extra_len; + + mz_zip_array_init(&file_data_array, 1); + if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + + mz_zip_array_clear(pZip, &file_data_array); + } + + if (!pState->m_zip64) + { + /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ + /* We also check when the archive is finalized so this doesn't need to be perfect. */ + mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; + + if (approx_new_archive_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + /* Write dest archive padding */ + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + + cur_dst_file_ofs += num_alignment_padding_bytes; + + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + while (src_archive_bytes_remaining) + { + n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_dst_file_ofs += n; + + src_archive_bytes_remaining -= n; + } + + /* Now deal with the optional data descriptor */ + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + /* Copy data descriptor */ + if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + /* src is zip64, dest must be zip64 */ + + /* name uint32_t's */ + /* id 1 (optional in zip64?) */ + /* crc 1 */ + /* comp_size 2 */ + /* uncomp_size 2 */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); + } + else + { + /* src is NOT zip64 */ + mz_bool has_id; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + + if (pZip->m_pState->m_zip64) + { + /* dest is zip64, so upgrade the data descriptor */ + const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); + const mz_uint32 src_crc32 = pSrc_descriptor[0]; + const mz_uint64 src_comp_size = pSrc_descriptor[1]; + const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; + + mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); + mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); + + n = sizeof(mz_uint32) * 6; + } + else + { + /* dest is NOT zip64, just copy it as-is */ + n = sizeof(mz_uint32) * (has_id ? 4 : 3); + } + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + /* Finally, add the new central dir header */ + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + + if (pState->m_zip64) + { + /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ + const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; + mz_zip_array new_ext_block; + + mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); + + if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return MZ_FALSE; + } + + MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + mz_zip_array_clear(pZip, &new_ext_block); + } + else + { + /* sanity checks */ + if (cur_dst_file_ofs > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (local_dir_header_ofs >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + } + + /* This shouldn't trigger unless we screwed up during the initial sanity checks */ + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + { + /* TODO: Support central dirs >= 32-bits in size */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + } + + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[256]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + /* Write central directory */ + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += central_dir_size; + } + + if (pState->m_zip64) + { + /* Write zip64 end of central directory header */ + mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; + + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Write end of central directory record */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) +{ + if ((!ppBuf) || (!pSize)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + *ppBuf = NULL; + *pSize = 0; + + if ((!pZip) || (!pZip->m_pState)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_pWrite != mz_zip_heap_write_func) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *ppBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + return mz_zip_writer_end_internal(pZip, MZ_TRUE); +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); +} + +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + mz_zip_zero_struct(&zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_FILENAME; + return MZ_FALSE; + } + + /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ + /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + /* Create a new archive. */ + if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + created_new_archive = MZ_TRUE; + } + else + { + /* Append to an existing archive. */ + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); + + return MZ_FALSE; + } + } + + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + actual_err = zip_archive.m_last_error; + + /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ + if (!mz_zip_writer_finalize_archive(&zip_archive)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if (!mz_zip_writer_end_internal(&zip_archive, status)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if ((!status) && (created_new_archive)) + { + /* It's a new archive and something went wrong, so just delete it. */ + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + + if (pErr) + *pErr = actual_err; + + return status; +} + +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) +{ + mz_uint32 file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + + return NULL; + } + + mz_zip_zero_struct(&zip_archive); + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + return NULL; + } + + if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) + { + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + } + + mz_zip_reader_end_internal(&zip_archive, p != NULL); + + if (pErr) + *pErr = zip_archive.m_last_error; + + return p; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* ------------------- Misc utils */ + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write calledback failed"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_file_archive_start_ofs; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) + return mz_zip_writer_end(pZip); +#endif + + return MZ_FALSE; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/launchers/qt/deps/miniz/miniz.h b/launchers/qt/deps/miniz/miniz.h new file mode 100644 index 0000000000..7db62811e5 --- /dev/null +++ b/launchers/qt/deps/miniz/miniz.h @@ -0,0 +1,1338 @@ +/* miniz.c 2.1.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateReset/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ +#pragma once + + + + + +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ + +/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ +/*#define MINIZ_NO_STDIO */ + +/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ +/* get/set file times, and the C run-time funcs that get/set times won't be called. */ +/* The current downside is the times written to your archives will be from 1979. */ +/*#define MINIZ_NO_TIME */ + +/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_APIS */ + +/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ +/*#define MINIZ_NO_ZLIB_APIS */ + +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ +/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. + Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc + callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user + functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ +/*#define MINIZ_NO_MALLOC */ + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ +#define MINIZ_NO_TIME +#endif + +#include + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ +#define MINIZ_X86_OR_X64_CPU 1 +#else +#define MINIZ_X86_OR_X64_CPU 0 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#else +#define MINIZ_HAS_64BIT_REGISTERS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API Definitions. */ + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ +typedef unsigned long mz_ulong; + +/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +/* Compression strategies. */ +enum +{ + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* Method */ +#define MZ_DEFLATED 8 + +/* Heap allocation callbacks. +Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ +enum +{ + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +#define MZ_VERSION "10.1.0" +#define MZ_VERNUM 0xA100 +#define MZ_VER_MAJOR 10 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 0 +#define MZ_VER_SUBREVISION 0 + +#ifndef MINIZ_NO_ZLIB_APIS + +/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ +enum +{ + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +/* Window bits */ +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +/* Compression/decompression stream struct. */ +typedef struct mz_stream_s +{ + const unsigned char *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + mz_ulong total_in; /* total number of bytes consumed so far */ + + unsigned char *next_out; /* pointer to next byte to write */ + unsigned int avail_out; /* number of bytes that can be written to next_out */ + mz_ulong total_out; /* total number of bytes produced so far */ + + char *msg; /* error msg (unused) */ + struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ + + mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ + mz_free_func zfree; /* optional heap free function (defaults to free) */ + void *opaque; /* heap alloc function user pointer */ + + int data_type; /* data_type (unused) */ + mz_ulong adler; /* adler32 of the source or uncompressed data */ + mz_ulong reserved; /* not used */ +} mz_stream; + +typedef mz_stream *mz_streamp; + +/* Returns the version string of miniz.c. */ +const char *mz_version(void); + +/* mz_deflateInit() initializes a compressor with default options: */ +/* Parameters: */ +/* pStream must point to an initialized mz_stream struct. */ +/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ +/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ +/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if the input parameters are bogus. */ +/* MZ_MEM_ERROR on out of memory. */ +int mz_deflateInit(mz_streamp pStream, int level); + +/* mz_deflateInit2() is like mz_deflate(), except with more control: */ +/* Additional parameters: */ +/* method must be MZ_DEFLATED */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ +/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ +int mz_deflateReset(mz_streamp pStream); + +/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ +/* Return values: */ +/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ +/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ +int mz_deflate(mz_streamp pStream, int flush); + +/* mz_deflateEnd() deinitializes a compressor: */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +int mz_deflateEnd(mz_streamp pStream); + +/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +/* Single-call compression functions mz_compress() and mz_compress2(): */ +/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ +mz_ulong mz_compressBound(mz_ulong source_len); + +/* Initializes a decompressor. */ +int mz_inflateInit(mz_streamp pStream); + +/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ +int mz_inflateReset(mz_streamp pStream); + +/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ +/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ +/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ +/* Return values: */ +/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ +/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_DATA_ERROR if the deflate stream is invalid. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ +/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ +int mz_inflate(mz_streamp pStream, int flush); + +/* Deinitializes a decompressor. */ +int mz_inflateEnd(mz_streamp pStream); + +/* Single-call decompression. */ +/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +/* Returns a string description of the specified error code, or NULL if the error code is invalid. */ +const char *mz_error(int err); + +/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflateReset mz_inflateReset +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +#endif /* MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif +#pragma once +#include +#include +#include +#include + +/* ------------------- Types and macros */ +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef int64_t mz_int64; +typedef uint64_t mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#define MZ_FILE FILE +#endif /* #ifdef MINIZ_NO_STDIO */ + +#ifdef MINIZ_NO_TIME +typedef struct mz_dummy_time_t_tag +{ + int m_dummy; +} mz_dummy_time_t; +#define MZ_TIME_T mz_dummy_time_t +#else +#define MZ_TIME_T time_t +#endif + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern void miniz_def_free_func(void *opaque, void *address); +extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif +#pragma once + + +#ifdef __cplusplus +extern "C" { +#endif +/* ------------------- Low-level Compression API Definitions */ + +/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ +#define TDEFL_LESS_MEMORY 0 + +/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ +/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ +enum +{ + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ +/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ +/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ +/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ +/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ +/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ +/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ +/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ +/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +/* High level compression functions: */ +/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ +/* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must free() the returned block when it's no longer needed. */ +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ +/* Returns 0 on failure. */ +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* Compresses an image to a compressed PNG file in memory. */ +/* On entry: */ +/* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ +/* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ +/* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ +/* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pLen_out will be set to the size of the PNG image file. */ +/* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); + +/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum +{ + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ +#if TDEFL_LESS_MEMORY +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1 +} tdefl_status; + +/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +/* tdefl's compression state structure. */ +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +/* Initializes the compressor. */ +/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ +/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ +/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ +/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ +/* tdefl_compress_buffer() always consumes the entire input buffer. */ +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +/* Create tdefl_compress() flags given zlib-style compression parameters. */ +/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ +/* window_bits may be -15 (raw deflate) or 15 (zlib) */ +/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor structure in C so that */ +/* non-C language bindings to tdefl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc(void); +void tdefl_compressor_free(tdefl_compressor *pComp); +#endif + +#ifdef __cplusplus +} +#endif +#pragma once + +/* ------------------- Low-level Decompression API Definitions */ + +#ifdef __cplusplus +extern "C" { +#endif +/* Decompression flags used by tinfl_decompress(). */ +/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ +/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ +/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ +/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +/* High level decompression functions: */ +/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ +/* On return: */ +/* Function returns a pointer to the decompressed data, or NULL on failure. */ +/* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must call mz_free() on the returned block when it's no longer needed. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ +/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ +/* Returns 1 on success or 0 on failure. */ +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tinfl_decompressor structure in C so that */ +/* non-C language bindings to tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tinfl_decompressor *tinfl_decompressor_alloc(void); +void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +#endif + +/* Max size of LZ dictionary. */ +#define TINFL_LZ_DICT_SIZE 32768 + +/* Return status. */ +typedef enum { + /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ + /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ + /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, + + /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ + TINFL_STATUS_BAD_PARAM = -3, + + /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ + TINFL_STATUS_ADLER32_MISMATCH = -2, + + /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ + TINFL_STATUS_FAILED = -1, + + /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ + + /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ + /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ + TINFL_STATUS_DONE = 0, + + /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ + /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ + /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + + /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ + /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ + /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ + /* so I may need to add some code to address this. */ + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) \ + do \ + { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +/* Internal/private bits follow. */ +enum +{ + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#else +#define TINFL_USE_64BIT_BITBUF 0 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif + +#pragma once + + +/* ------------------- ZIP archive reading/writing */ + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 +}; + +typedef struct +{ + /* Central directory file index. */ + mz_uint32 m_file_index; + + /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ + mz_uint64 m_central_dir_ofs; + + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; + +#ifndef MINIZ_NO_TIME + MZ_TIME_T m_time; +#endif + + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; + + /* File's compressed size. */ + mz_uint64 m_comp_size; + + /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ + mz_uint64 m_uncomp_size; + + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; + + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; + + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; + + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; + + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; + + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000 +} mz_zip_flags; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef struct +{ + mz_zip_archive *pZip; + mz_uint flags; + + int status; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32; +#endif + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + void *pWrite_buf; + + size_t out_blk_remain; + + tinfl_decompressor inflator; + +} mz_zip_reader_extract_iter_state; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +/* Read a archive from a disk file. */ +/* file_start_ofs is the file offset where the archive actually begins, or 0. */ +/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); + +/* Read an archive from an already opened FILE, beginning at the current file position. */ +/* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */ +/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); +#endif + +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +/* -------- ZIP reading or writing */ + +/* Clears a mz_zip_archive struct to all zeros. */ +/* Important: This must be done before passing the struct to any mz_zip functions. */ +void mz_zip_zero_struct(mz_zip_archive *pZip); + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); + +/* Returns the total number of files in the archive. */ +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); + +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); + +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +const char *mz_zip_get_error_string(mz_zip_error mz_err); + +/* MZ_TRUE if the archive file entry is a directory entry. */ +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); + +/* Returns detailed information about an archive file entry. */ +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a memory buffer using no memory allocation. */ +/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +/* Extracts a archive file to a memory buffer. */ +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Extracts a archive file using a callback function to output the file's data. */ +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +/* Extract a file iteratively */ +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); + +#ifndef MINIZ_NO_STDIO +/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ +/* This function only extracts files, not archive directory records. */ +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); + +/* Extracts a archive file starting at the current position in the destination FILE stream. */ +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); +#endif + +#if 0 +/* TODO */ + typedef void *mz_zip_streaming_extract_state_ptr; + mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); + size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); + mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); +#endif + +/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ +/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + +/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); + +/* Misc utils/helpers, valid for ZIP reading or writing */ +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +mz_bool mz_zip_end(mz_zip_archive *pZip); + +/* -------- ZIP writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Inits a ZIP archive writer. */ +/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ +/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +#endif + +/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ +/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ +/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ +/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ +/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ +/* the archive is finalized the file's central directory will be hosed. */ +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); + +/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ +/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ +/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ +/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +#ifndef MINIZ_NO_STDIO +/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); +#endif + +/* Adds a file to an archive by fully cloning the data from another archive. */ +/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); + +/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ +/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ +/* An archive must be manually finalized by calling this function for it to be valid. */ +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); + +/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ +/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); + +/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ +/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +/* -------- Misc. high-level helper functions: */ + +/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ +/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); + +/* Reads a single file from an archive into a heap block. */ +/* If pComment is not NULL, only the file with the specified comment will be extracted. */ +/* Returns NULL on failure. */ +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifdef __cplusplus +} +#endif + +#endif /* MINIZ_NO_ARCHIVE_APIS */ diff --git a/launchers/qt/deps/miniz/stdafx.h b/launchers/qt/deps/miniz/stdafx.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/launchers/qt/src/Unzipper.cpp b/launchers/qt/src/Unzipper.cpp new file mode 100644 index 0000000000..3d6672b340 --- /dev/null +++ b/launchers/qt/src/Unzipper.cpp @@ -0,0 +1,80 @@ +#include "Unzipper.h" + +#include +#include +#include +#include + +Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) : + _zipFilePath(zipFilePath), _outputDirectory(outputDirectory) { +} + +//uint64_t extractZip(const QString& zipFile, const std::string& path, + //std::vector& files) { + //std::function progressCallback) { + +void Unzipper::run() { + qDebug() << "Reading zip file" << _zipFilePath << ", extracting to" << _outputDirectory.absolutePath(); + + mz_zip_archive zip_archive; + memset(&zip_archive, 0, sizeof(zip_archive)); + + auto status = mz_zip_reader_init_file(&zip_archive, _zipFilePath.toUtf8().data(), 0); + + if (!status) { + auto zip_error = mz_zip_get_last_error(&zip_archive); + auto zip_error_msg = mz_zip_get_error_string(zip_error); + emit finished(true, "Failed to initialize miniz: " + QString::number(zip_error) + " " + zip_error_msg); + return; + } + + int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); + + qDebug() << "Zip archive has a file count of " << fileCount; + + if (fileCount == 0) { + mz_zip_reader_end(&zip_archive); + emit finished(false, ""); + return; + } + + mz_zip_archive_file_stat file_stat; + if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) { + mz_zip_reader_end(&zip_archive); + emit finished(true, "Zip archive cannot be stat'd"); + return; + } + + // Get root folder + uint64_t totalSize = 0; + uint64_t totalCompressedSize = 0; + bool _shouldFail = false; + for (int i = 0; i < fileCount; i++) { + if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue; + + QString filename = file_stat.m_filename; + QString fullFilename = _outputDirectory.filePath(filename); + if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) { + if (!_outputDirectory.mkpath(fullFilename)) { + mz_zip_reader_end(&zip_archive); + emit finished(true, "Unzipping error while creating folder: " + fullFilename); + return; + } + continue; + } + if (mz_zip_reader_extract_to_file(&zip_archive, i, fullFilename.toUtf8().data(), 0)) { + totalCompressedSize += (uint64_t)file_stat.m_comp_size; + totalSize += (uint64_t)file_stat.m_uncomp_size; + emit progress((float)totalCompressedSize / (float)zip_archive.m_archive_size); + } else { + emit finished(true, "Unzipping error unzipping file: " + fullFilename); + return; + } + } + + qDebug() << "Done unzipping archive, total size:" << totalSize; + + mz_zip_reader_end(&zip_archive); + + emit finished(false, ""); +} diff --git a/launchers/qt/src/Unzipper.h b/launchers/qt/src/Unzipper.h new file mode 100644 index 0000000000..9fb0b087d4 --- /dev/null +++ b/launchers/qt/src/Unzipper.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include + +class Unzipper : public QObject, public QRunnable { + Q_OBJECT +public: + Unzipper(const QString& zipFilePath, const QDir& outputDirectory); + void run() override; + +signals: + void progress(float progress); + void finished(bool error, QString errorMessage); + +private: + const QString _zipFilePath; + const QDir _outputDirectory; +}; From 93c443abc87dc2e25a8b42da95579aab05575413 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Sep 2019 13:57:26 -0700 Subject: [PATCH 026/203] Add qt launcher states, download, and install --- launchers/qt/resources/qml/Download.qml | 20 +- .../resources/qml/HFControls/HFTextField.qml | 1 + launchers/qt/resources/qml/root.qml | 11 + launchers/qt/src/Launcher.cpp | 2 +- launchers/qt/src/LauncherState.cpp | 368 ++++++++++++++++-- launchers/qt/src/LauncherState.h | 87 +++-- launchers/qt/src/main.cpp | 2 +- 7 files changed, 428 insertions(+), 63 deletions(-) diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index 8020889e8a..c323d3b505 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -62,6 +62,8 @@ Item { width: 394 height: 8 + value: LauncherState.downloadProgress; + anchors { top: secondText.bottom topMargin: 30 @@ -88,14 +90,14 @@ Item { } - PropertyAnimation { - target: progressBar; - loops: Animation.Infinite - property: "value" - from: 0; - to: 1; - duration: 5000 - running: true - } + //PropertyAnimation { + //target: progressBar; + //loops: Animation.Infinite + //property: "value" + //from: 0; + //to: 1; + //duration: 5000 + //running: true + //} } } diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index 77a6601fdf..547cd94843 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -10,6 +10,7 @@ TextField { horizontalAlignment: TextInput.AlignLeft placeholderText: "PlaceHolder" property string seperatorColor: "#FFFFFF" + selectByMouse: true background: Item { anchors.fill: parent Rectangle { diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 0ceda5b189..e80466a6eb 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -4,6 +4,7 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 import HQLauncher 1.0 import "HFControls" + Image { id: root width: 515 @@ -21,4 +22,14 @@ Image { loader.source = url; }); } + + Text { + font.pixelSize: 12 + + anchors.right: root.right + anchors.bottom: root.bottom + + color: "#FFFFFF" + text: LauncherState.uiState.toString() + " - " + LauncherState.applicationState + } } diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 20405d5a39..23467624f8 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -11,7 +11,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); - _launcherState->setUIState(LauncherState::SPLASH_SCREEN); + //_launcherState->setUIState(LauncherState::SPLASH_SCREEN); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->setFlags(Qt::FramelessWindowHint); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 9c5f66f9cf..d1635c277c 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -1,5 +1,9 @@ #include "LauncherState.h" +#include "Unzipper.h" + +#include + #include #include @@ -13,18 +17,56 @@ #include #include +#include + +#include + + +bool LatestBuilds::getBuild(QString tag, Build* outBuild) { + if (tag.isNull()) { + tag = defaultTag; + } + + for (auto& build : builds) { + if (build.tag == tag) { + *outBuild = build; + return true; + } + } + + return false; +} + static const std::array QML_FILE_FOR_UI_STATE = { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml", "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } }; -void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) const { - if (_appState != state) { +void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { + if (_applicationState != state) { +#ifdef Q_OS_WIN __debugbreak(); - exit(0); +#endif + setApplicationState(ApplicationState::UnexpectedError); } } +void LauncherState::ASSERT_STATE(std::vector states) { + for (auto state : states) { + if (_applicationState == state) { + return; + } + } +#ifdef Q_OS_WIN + __debugbreak(); +#endif + setApplicationState(ApplicationState::UnexpectedError); +} + LauncherState::LauncherState() { + _launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + // TODO Fix launcher directory + qDebug() << "Launcher directory: " << _launcherDirectory.absolutePath(); + _launcherDirectory.mkpath(_launcherDirectory.absolutePath()); requestBuilds(); } @@ -36,13 +78,29 @@ void LauncherState::declareQML() { qmlRegisterType("HQLauncher", 1, 0, "LauncherStateEnums"); } -void LauncherState::setUIState(UIState state) { - _uiState = state; - emit updateSourceUrl(getCurrentUISource()); -} - LauncherState::UIState LauncherState::getUIState() const { - return _uiState; + switch (_applicationState) { + case ApplicationState::Init: + case ApplicationState::RequestingBuilds: + return SPLASH_SCREEN; + case ApplicationState::WaitingForLogin: + case ApplicationState::RequestingLogin: + return LOGIN_SCREEN; + case ApplicationState::DownloadingClient: + case ApplicationState::InstallingClient: + case ApplicationState::DownloadingContentCache: + case ApplicationState::InstallingContentCache: + return DOWNLOAD_SCREEN; + case ApplicationState::LaunchingHighFidelity: + return DOWNLOAD_FINSISHED; + case ApplicationState::UnexpectedError: + __debugbreak(); + return ERROR_SCREEN; + default: + qDebug() << "FATAL: No UI for" << _applicationState; + __debugbreak(); + return ERROR_SCREEN; + } } void LauncherState::setLastLoginError(LastLoginError lastLoginError) { @@ -54,8 +112,8 @@ LauncherState::LastLoginError LauncherState::getLastLoginError() const { } void LauncherState::requestBuilds() { - ASSERT_STATE(ApplicationState::INIT); - _appState = ApplicationState::REQUESTING_BUILDS; + ASSERT_STATE(ApplicationState::Init); + setApplicationState(ApplicationState::RequestingBuilds); // TODO Show splash screen until this request is complete auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json")); @@ -67,7 +125,7 @@ void LauncherState::requestBuilds() { void LauncherState::receivedBuildsReply() { auto reply = static_cast(sender()); - ASSERT_STATE(ApplicationState::REQUESTING_BUILDS); + ASSERT_STATE(ApplicationState::RequestingBuilds); if (reply->error()) { qDebug() << "Error getting builds from thunder: " << reply->errorString(); @@ -81,8 +139,7 @@ void LauncherState::receivedBuildsReply() { } else { auto root = doc.object(); if (!root.contains("default_tag")) { - _appState = ApplicationState::REQUESTING_BUILDS_FAILED; - setUIState(LauncherState::ERROR_SCREEN); + setApplicationState(ApplicationState::UnexpectedError); return; } @@ -90,8 +147,7 @@ void LauncherState::receivedBuildsReply() { auto results = root["results"]; if (!results.isArray()) { - _appState = ApplicationState::REQUESTING_BUILDS_FAILED; - setUIState(LauncherState::ERROR_SCREEN); + setApplicationState(ApplicationState::UnexpectedError); return; } @@ -112,14 +168,13 @@ void LauncherState::receivedBuildsReply() { } } } - _appState = ApplicationState::WAITING_FOR_LOGIN; - setUIState(LauncherState::LOGIN_SCREEN); + setApplicationState(ApplicationState::WaitingForLogin); } void LauncherState::login(QString username, QString password) { - ASSERT_STATE(ApplicationState::WAITING_FOR_LOGIN); + ASSERT_STATE(ApplicationState::WaitingForLogin); - _appState = ApplicationState::REQUESTING_LOGIN; + setApplicationState(ApplicationState::RequestingLogin); qDebug() << "Got login: " << username << password; @@ -133,32 +188,287 @@ void LauncherState::login(QString username, QString password) { query.addQueryItem("scope", "owner"); auto reply = _networkAccessManager.post(*request, query.toString().toUtf8()); - QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply); } Q_INVOKABLE void LauncherState::receivedLoginReply() { + ASSERT_STATE(ApplicationState::RequestingLogin); + + // TODO Check for errors auto reply = static_cast(sender()); - ASSERT_STATE(ApplicationState::REQUESTING_LOGIN); + if (reply->error()) { + setApplicationState(ApplicationState::UnexpectedError); + return; + } - qDebug() << "Got response for login: " << reply->readAll(); + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + auto root = doc.object(); + if (!root.contains("access_token") + || !root.contains("token_type") + || !root.contains("expires_in") + || !root.contains("refresh_token") + || !root.contains("scope") + || !root.contains("created_at")) { - download(); + setApplicationState(ApplicationState::UnexpectedError); + return; + } + + _loginResponse.accessToken = root["access_token"].toString(); + _loginResponse.refreshToken = root["refresh_token"].toString(); + _loginResponse.tokenType = root["token_type"].toString(); + + qDebug() << "Got response for login: " << data; + + downloadClient(); } -void LauncherState::download() { - _appState = ApplicationState::DOWNLOADING_CONTENT; - setUIState(LauncherState::DOWNLOAD_SCREEN); +QString LauncherState::getContentCachePath() const { + return _launcherDirectory.filePath("cache"); } -void LauncherState::contentDownloadComplete() { +bool LauncherState::shouldDownloadContentCache() const { + return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); +} + +void LauncherState::downloadClient() { + ASSERT_STATE(ApplicationState::RequestingLogin); + + Build build; + if (!_latestBuilds.getBuild(_buildTag, &build)) { + qDebug() << "Cannot determine latest build"; + setApplicationState(ApplicationState::UnexpectedError); + return; + } + + _downloadProgress = 0; + setApplicationState(ApplicationState::DownloadingClient); + + // Start client download + { + qDebug() << "Latest build: " << build.tag << build.buildNumber << build.latestVersion << build.installerZipURL; + auto request = new QNetworkRequest(QUrl(build.installerZipURL)); + auto reply = _networkAccessManager.get(*request); + + _clientZipFile.setFileName(_launcherDirectory.absoluteFilePath("client.zip")); + + qDebug() << "Opening " << _clientZipFile.fileName(); + if (!_clientZipFile.open(QIODevice::WriteOnly)) { + setApplicationState(ApplicationState::UnexpectedError); + return; + } + + connect(reply, &QNetworkReply::finished, this, &LauncherState::clientDownloadComplete); + connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { + char buf[4096]; + while (reply->bytesAvailable() > 0) { + qint64 size; + size = reply->read(buf, (qint64)sizeof(buf)); + if (size == 0) { + break; + } + _clientZipFile.write(buf, size); + } + }); + connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) { + _downloadProgress = (float)received / (float)total; + emit downloadProgressChanged(); + }); + } } void LauncherState::clientDownloadComplete() { + ASSERT_STATE(ApplicationState::DownloadingClient); + + _clientZipFile.close(); + + installClient(); +} + +void LauncherState::installClient() { + ASSERT_STATE(ApplicationState::DownloadingClient); + setApplicationState(ApplicationState::InstallingClient); + + auto installDir = _launcherDirectory.absoluteFilePath("interface_install"); + _launcherDirectory.mkpath("interface_install"); + + _downloadProgress = 0; + + qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << installDir; + + auto unzipper = new Unzipper(_clientZipFile.fileName(), QDir(installDir)); + unzipper->setAutoDelete(true); + connect(unzipper, &Unzipper::progress, this, [this](float progress) { + qDebug() << "Unzipper progress: " << progress; + _downloadProgress = progress; + emit downloadProgressChanged(); + }); + connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) { + if (error) { + qDebug() << "Unzipper finished with error: " << errorMessage; + setApplicationState(ApplicationState::UnexpectedError); + } else { + qDebug() << "Unzipper finished without error"; + downloadContentCache(); + } + }); + QThreadPool::globalInstance()->start(unzipper); + + //launchClient(); +} + +void LauncherState::downloadContentCache() { + ASSERT_STATE(ApplicationState::InstallingClient); + + // Start content set cache download + if (shouldDownloadContentCache()) { + setApplicationState(ApplicationState::DownloadingContentCache); + + _downloadProgress = 0; + + auto request = new QNetworkRequest(QUrl(_contentCacheURL)); + auto reply = _networkAccessManager.get(*request); + + _contentZipFile.setFileName(_launcherDirectory.absoluteFilePath("content_cache.zip")); + + qDebug() << "Opening " << _contentZipFile.fileName(); + if (!_contentZipFile.open(QIODevice::WriteOnly)) { + setApplicationState(ApplicationState::UnexpectedError); + return; + } + + connect(reply, &QNetworkReply::finished, this, &LauncherState::contentCacheDownloadComplete); + connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { + char buf[4096]; + while (reply->bytesAvailable() > 0) { + qint64 size; + size = reply->read(buf, (qint64)sizeof(buf)); + if (size == 0) { + break; + } + _contentZipFile.write(buf, size); + } + }); + connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) { + _downloadProgress = (float)received / (float)total; + emit downloadProgressChanged(); + }); + } else { + launchClient(); + } +} + +void LauncherState::contentCacheDownloadComplete() { + ASSERT_STATE(ApplicationState::DownloadingContentCache); + + _contentZipFile.close(); + + installContentCache(); +} + + +void LauncherState::installContentCache() { + ASSERT_STATE(ApplicationState::DownloadingContentCache); + setApplicationState(ApplicationState::InstallingContentCache); + + auto installDir = getContentCachePath(); + + qDebug() << "Unzipping " << _contentZipFile.fileName() << " to " << installDir; + + _downloadProgress = 0; + + auto unzipper = new Unzipper(_contentZipFile.fileName(), QDir(installDir)); + unzipper->setAutoDelete(true); + connect(unzipper, &Unzipper::progress, this, [this](float progress) { + qDebug() << "Unzipper progress (content cache): " << progress; + _downloadProgress = progress; + emit downloadProgressChanged(); + }); + connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) { + if (error) { + qDebug() << "Unzipper finished with error: " << errorMessage; + setApplicationState(ApplicationState::UnexpectedError); + } else { + qDebug() << "Unzipper finished without error"; + launchClient(); + } + }); + QThreadPool::globalInstance()->start(unzipper); + } void LauncherState::launchClient() { - _appState = ApplicationState::LAUNCHING_HIGH_FIDELITY; + ASSERT_STATE({ ApplicationState::InstallingClient, ApplicationState::InstallingContentCache }); + + setApplicationState(ApplicationState::LaunchingHighFidelity); + + QDir installDirectory = _launcherDirectory.filePath("interface_install"); + auto clientPath = installDirectory.absoluteFilePath("interface.exe"); + + QString homePath = "hifi://hq"; + QString defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper"); + QString displayName = "fixMe"; + QString contentCachePath = _launcherDirectory.filePath("cache"); + + // TODO Fix parameters + QString params = "--url " + homePath + + " --setBookmark hqhome=\"" + homePath + "\"" + + " --defaultScriptsOverride " + QDir::toNativeSeparators(defaultScriptsPath) + + " --displayName " + displayName + + " --cache " + contentCachePath; + +#if defined(Q_OS_WIN) + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // set the size of the structures + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + // start the program up + BOOL success = CreateProcess( + clientPath.toUtf8().data(), + params.toUtf8().data(), + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + exit(0); +#elif defined(Q_OS_MACOS) + // TODO Implement launching of client +#else +#error UNSUPPORTED PLATFORM +#endif +} + +void LauncherState::setApplicationState(ApplicationState state) { + qDebug() << "Changing application state: " << _applicationState << " -> " << state; + + if (state == ApplicationState::UnexpectedError) { + __debugbreak(); + } + + _applicationState = state; + + emit uiStateChanged(); + emit updateSourceUrl(getCurrentUISource()); + + emit applicationStateChanged(); +} + +LauncherState::ApplicationState LauncherState::getApplicationState() const { + return _applicationState; } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 62a58c9778..156daaa165 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -1,6 +1,10 @@ +#pragma once + +#include #include #include #include +#include struct Build { QString tag; @@ -10,12 +14,24 @@ struct Build { }; struct LatestBuilds { + bool getBuild(QString tag, Build* outBuild); + QString defaultTag; std::vector builds; }; +struct LoginResponse { + QString accessToken; + QString tokenType; + QString refreshToken; +}; + class LauncherState : public QObject { Q_OBJECT + Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged); + Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged); + + Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged); public: LauncherState(); @@ -30,29 +46,27 @@ public: ERROR_SCREEN, UI_STATE_NUM }; - Q_ENUMS(UIState); + Q_ENUM(UIState); enum class ApplicationState { - INIT, + Init, - REQUESTING_BUILDS, - REQUESTING_BUILDS_FAILED, + UnexpectedError, - WAITING_FOR_LOGIN, - REQUESTING_LOGIN, + RequestingBuilds, - WAITING_FOR_SIGNUP, - REQUESTING_SIGNUP, + WaitingForLogin, + RequestingLogin, - DOWNLOADING_CONTENT, - DOWNLOADING_HIGH_FIDELITY, + DownloadingClient, + DownloadingContentCache, - EXTRACTING_DATA, + InstallingClient, + InstallingContentCache, - LAUNCHING_HIGH_FIDELITY + LaunchingHighFidelity }; - Q_ENUMS(ApplicationState); - + Q_ENUM(ApplicationState); enum LastLoginError { NONE = 0, @@ -60,19 +74,23 @@ public: CREDENTIALS, LAST_ERROR_NUM }; - Q_ENUMS(LastLoginError); + Q_ENUM(LastLoginError); + Q_INVOKABLE QString getCurrentUISource() const; - void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) const; + void ASSERT_STATE(LauncherState::ApplicationState state); + void ASSERT_STATE(std::vector states); static void declareQML(); - void setUIState(UIState state); UIState getUIState() const; void setLastLoginError(LastLoginError lastLoginError); LastLoginError getLastLoginError() const; + void setApplicationState(ApplicationState state); + ApplicationState getApplicationState() const; + // Request builds void requestBuilds(); Q_INVOKABLE void receivedBuildsReply(); @@ -81,22 +99,45 @@ public: Q_INVOKABLE void login(QString username, QString password); Q_INVOKABLE void receivedLoginReply(); - // Download - void download(); - Q_INVOKABLE void contentDownloadComplete(); - Q_INVOKABLE void clientDownloadComplete(); + // Client + void downloadClient(); + void installClient(); + + // Content Cache + void downloadContentCache(); + void installContentCache(); // Launching void launchClient(); + Q_INVOKABLE float getDownloadProgress() const { return _downloadProgress; } + signals: void updateSourceUrl(QString sourceUrl); + void uiStateChanged(); + void applicationStateChanged(); + void downloadProgressChanged(); + +private slots: + void clientDownloadComplete(); + void contentCacheDownloadComplete(); private: + bool shouldDownloadContentCache() const; + QString getContentCachePath() const; + QNetworkAccessManager _networkAccessManager; LatestBuilds _latestBuilds; + QDir _launcherDirectory; - ApplicationState _appState { ApplicationState::INIT }; - UIState _uiState { SPLASH_SCREEN }; + // Application State + ApplicationState _applicationState { ApplicationState::Init }; + LoginResponse _loginResponse; LastLoginError _lastLoginError { NONE }; + QString _buildTag { QString::null }; + QString _contentCacheURL{ "https://orgs.highfidelity.com/content-cache/content_cache_small-only_data8.zip" }; // QString::null }; // If null, there is no content cache to download + QFile _clientZipFile; + QFile _contentZipFile; + + float _downloadProgress { 0 }; }; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 9ed6f42390..93a0c0e7dc 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -10,7 +10,7 @@ Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); int main(int argc, char *argv[]) { - QString name { "HQLauncher" }; + QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); From a78be7a451258547d4b826d6072e49ae5daf4742 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Sep 2019 14:57:22 -0700 Subject: [PATCH 027/203] Add passing of tokens to hifi in qt launcher, and remove dead code --- launchers/qt/src/LauncherState.cpp | 5 +++++ launchers/qt/src/LauncherState.h | 1 + launchers/qt/src/Unzipper.cpp | 5 ----- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index d1635c277c..12f9bfa360 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -223,6 +223,7 @@ Q_INVOKABLE void LauncherState::receivedLoginReply() { _loginResponse.tokenType = root["token_type"].toString(); qDebug() << "Got response for login: " << data; + _loginTokenResponse = data; downloadClient(); } @@ -421,6 +422,10 @@ void LauncherState::launchClient() { + " --displayName " + displayName + " --cache " + contentCachePath; + if (!_loginTokenResponse.isEmpty()) { + params += " --tokens \"" + _loginTokenResponse.replace("\"", "\\\"") + "\""; + } + #if defined(Q_OS_WIN) STARTUPINFO si; PROCESS_INFORMATION pi; diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 353c5947e1..691ea23ebf 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -137,6 +137,7 @@ private: LastLoginError _lastLoginError { NONE }; QString _buildTag { QString::null }; QString _contentCacheURL{ "https://orgs.highfidelity.com/content-cache/content_cache_small-only_data8.zip" }; // QString::null }; // If null, there is no content cache to download + QString _loginTokenResponse; QFile _clientZipFile; QFile _contentZipFile; diff --git a/launchers/qt/src/Unzipper.cpp b/launchers/qt/src/Unzipper.cpp index 3d6672b340..c7cca0b5e3 100644 --- a/launchers/qt/src/Unzipper.cpp +++ b/launchers/qt/src/Unzipper.cpp @@ -9,10 +9,6 @@ Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) : _zipFilePath(zipFilePath), _outputDirectory(outputDirectory) { } -//uint64_t extractZip(const QString& zipFile, const std::string& path, - //std::vector& files) { - //std::function progressCallback) { - void Unzipper::run() { qDebug() << "Reading zip file" << _zipFilePath << ", extracting to" << _outputDirectory.absolutePath(); @@ -45,7 +41,6 @@ void Unzipper::run() { return; } - // Get root folder uint64_t totalSize = 0; uint64_t totalCompressedSize = 0; bool _shouldFail = false; From 7538e100f8e393a4ced1a2710277abf50cebfa10 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Fri, 13 Sep 2019 15:04:28 -0700 Subject: [PATCH 028/203] do not recompile qml --- launchers/qt/CMakeLists.txt | 12 ++++++++++-- launchers/qt/resources/qml/DownloadFinished.qml | 4 ++-- launchers/qt/resources/qml/SplashScreen.qml | 13 ++++++++++++- launchers/qt/src/Launcher.cpp | 6 ++++-- launchers/qt/src/LauncherState.cpp | 7 ++++--- launchers/qt/src/LauncherState.h | 1 + launchers/qt/src/LauncherWindow.h | 5 ++++- launchers/qt/src/PathUtils.cpp | 7 +++++++ launchers/qt/src/PathUtils.h | 9 +++++++-- 9 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 launchers/qt/src/PathUtils.cpp diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index eefbbe969d..8a4f6ea623 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -62,7 +62,6 @@ if (WIN32) endif () - if (APPLE) ExternalProject_Add( qtlite @@ -126,7 +125,9 @@ set(src_files src/LauncherState.h src/LauncherState.cpp src/LauncherWindow.h - src/LauncherWindow.cpp) + src/LauncherWindow.cpp + src/PathUtils.cpp + src/PathUtils.h) set(TARGET_NAME ${PROJECT_NAME}) @@ -195,6 +196,13 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD "${INTERFACE_EXEC_DIR}") +if (LAUNCHER_SOURCE_TREE_RESOURCES) + target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/") + message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/") +else() + target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/") + message("Use resource.rcc path: qrc:/") +endif() if (APPLE) install( diff --git a/launchers/qt/resources/qml/DownloadFinished.qml b/launchers/qt/resources/qml/DownloadFinished.qml index f817137a8c..a16800fa49 100644 --- a/launchers/qt/resources/qml/DownloadFinished.qml +++ b/launchers/qt/resources/qml/DownloadFinished.qml @@ -11,7 +11,7 @@ Item { width: parent.width height: parent.height mirror: true - source: "qrc:/images/hifi_window@2x.png" + source: PathUtils.resourcePath("images/hifi_window@2x.png"); transformOrigin: Item.Center rotation: 0 } @@ -21,7 +21,7 @@ Item { id: logo width: 132 height: 134 - source: "qrc:/images/HiFi_Voxel.png" + source: PathUtils.resourcePath("images/HiFi_Voxel.png"); anchors { top: root.top diff --git a/launchers/qt/resources/qml/SplashScreen.qml b/launchers/qt/resources/qml/SplashScreen.qml index 793536205a..a710a7245c 100644 --- a/launchers/qt/resources/qml/SplashScreen.qml +++ b/launchers/qt/resources/qml/SplashScreen.qml @@ -4,10 +4,21 @@ import QtQuick.Controls 2.1 Item { anchors.fill: parent + + Image { + anchors.centerIn: parent + width: parent.width + height: parent.height + mirror: true + source: PathUtils.resourcePath("images/hifi_window@2x.png"); + transformOrigin: Item.Center + rotation: 0 + } + Image { anchors.centerIn: parent width: 225 height: 205 - source: "../images/hifi_logo_large@2x.png" + source: PathUtils.resourcePath("images/hifi_logo_large@2x.png"); } } diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 50c4a665e5..a2a993c7dd 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -6,17 +6,19 @@ #include "LauncherWindow.h" #include "LauncherState.h" +#include "PathUtils.h" Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); - _launcherState->setUIState(LauncherState::DOWNLOAD_FINSISHED); + _launcherState->setUIState(LauncherState::SPLASH_SCREEN); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); + _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); _launcherWindow->setFlags(Qt::FramelessWindowHint); LauncherState::declareQML(); - _launcherWindow->setSource(QUrl("qrc:/qml/root.qml")); + _launcherWindow->setSource(QUrl(PathUtils::resourcePath("qml/root.qml"))); _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); _launcherWindow->show(); } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 090bf5e9c5..2312478cc1 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -1,5 +1,6 @@ #include "LauncherState.h" +#include "PathUtils.h" #include #include @@ -14,8 +15,8 @@ #include static const std::array QML_FILE_FOR_UI_STATE = - { { "qrc:/qml/SplashScreen.qml", "qrc:/qml/HFBase/CreateAccountBase.qml", "qrc:/qml/DisplayName.qml", - "qrc:/qml/Download.qml", "qrc:/qml/DownloadFinished.qml", "qrc:/qml/HFBase/Error.qml" } }; + { { "SplashScreen.qml", "HFBase/CreateAccountBase.qml", "DisplayName.qml", + "Download.qml", "DownloadFinished.qml", "HFBase/Error.qml" } }; LauncherState::LauncherState() { // TODO Show splash screen until this request is complete @@ -75,7 +76,7 @@ void LauncherState::declareQML() { void LauncherState::setUIState(UIState state) { _uiState = state; - emit updateSourceUrl(getCurrentUISource()); + emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); } LauncherState::UIState LauncherState::getUIState() const { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index e7d2055995..fab7697d90 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -1,5 +1,6 @@ #include #include +#include #include struct Build { diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h index 394d9aed41..376ab9ae7b 100644 --- a/launchers/qt/src/LauncherWindow.h +++ b/launchers/qt/src/LauncherWindow.h @@ -1,5 +1,6 @@ #include #include +#include class LauncherWindow : public QQuickView { public: @@ -7,8 +8,10 @@ public: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; - + //void setLauncherState(std::shared_ptr launcherState) { _launcherState = launcherState; } private: bool _drag { false }; QPoint _previousMousePos; + + ///std::shared_ptr _launcherState { nullptr }; }; diff --git a/launchers/qt/src/PathUtils.cpp b/launchers/qt/src/PathUtils.cpp new file mode 100644 index 0000000000..ae02585788 --- /dev/null +++ b/launchers/qt/src/PathUtils.cpp @@ -0,0 +1,7 @@ +#include "PathUtils.h" + +#include + +QString PathUtils::resourcePath(const QString& source) { + return QString(RESOURCE_PREFIX_URL + source); +} diff --git a/launchers/qt/src/PathUtils.h b/launchers/qt/src/PathUtils.h index 4c6c6e9515..29d9b609d6 100644 --- a/launchers/qt/src/PathUtils.h +++ b/launchers/qt/src/PathUtils.h @@ -1,6 +1,11 @@ #pragma once -class PathUtils { +#include +#include +class PathUtils : public QObject { + Q_OBJECT public: - static resourcePath(); + PathUtils() = default; + ~PathUtils() = default; + Q_INVOKABLE static QString resourcePath(const QString& source); }; From b5df1a9f4d3a85443fce6c2181cc2ed56cea74c1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Sep 2019 15:21:31 -0700 Subject: [PATCH 029/203] Fix defaults scripts and add version checking to qt launcher --- launchers/qt/src/LauncherState.cpp | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 12f9bfa360..02adbdbdb3 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -21,6 +21,11 @@ #include +QString getCurrentClientVersion() { + // TODO Implement client version checking + return ""; +} + bool LatestBuilds::getBuild(QString tag, Build* outBuild) { if (tag.isNull()) { @@ -246,6 +251,13 @@ void LauncherState::downloadClient() { return; } + auto currentVersion = getCurrentClientVersion(); + if (QString::number(build.latestVersion) == currentVersion) { + qDebug() << "Existing client install is up-to-date."; + downloadContentCache(); + return; + } + _downloadProgress = 0; setApplicationState(ApplicationState::DownloadingClient); @@ -294,8 +306,9 @@ void LauncherState::installClient() { ASSERT_STATE(ApplicationState::DownloadingClient); setApplicationState(ApplicationState::InstallingClient); - auto installDir = _launcherDirectory.absoluteFilePath("interface_install"); + _launcherDirectory.rmpath("interface_install"); _launcherDirectory.mkpath("interface_install"); + auto installDir = _launcherDirectory.absoluteFilePath("interface_install"); _downloadProgress = 0; @@ -323,7 +336,7 @@ void LauncherState::installClient() { } void LauncherState::downloadContentCache() { - ASSERT_STATE(ApplicationState::InstallingClient); + ASSERT_STATE({ ApplicationState::RequestingLogin, ApplicationState::InstallingClient }); // Start content set cache download if (shouldDownloadContentCache()) { @@ -410,12 +423,13 @@ void LauncherState::launchClient() { QDir installDirectory = _launcherDirectory.filePath("interface_install"); auto clientPath = installDirectory.absoluteFilePath("interface.exe"); + // TODO Get correct home path QString homePath = "hifi://hq"; - QString defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper"); + QString defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js"); QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); - // TODO Fix parameters + // TODO Confirm that these params are correct across Windows and OSX QString params = "--url " + homePath + " --setBookmark hqhome=\"" + homePath + "\"" + " --defaultScriptsOverride " + QDir::toNativeSeparators(defaultScriptsPath) @@ -441,12 +455,12 @@ void LauncherState::launchClient() { params.toUtf8().data(), nullptr, // Process handle not inheritable nullptr, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - CREATE_NEW_CONSOLE, // Opens file in a separate console - nullptr, // Use parent's environment block - nullptr, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure ); // Close process and thread handles. CloseHandle(pi.hProcess); From da807e9b29345a00effd4cc5d4d99571e2efa562 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Fri, 13 Sep 2019 15:41:14 -0700 Subject: [PATCH 030/203] fixing some ui elements --- launchers/qt/resources/qml/Download.qml | 11 +++++++++++ .../qt/resources/qml/HFBase/CreateAccountBase.qml | 2 +- launchers/qt/src/LauncherState.cpp | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index c323d3b505..7393e0129e 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -5,6 +5,17 @@ Item { id: root anchors.fill: parent + + Image { + anchors.centerIn: parent + width: parent.width + height: parent.height + mirror: true + source: "qrc:/images/hifi_window@2x.png" + transformOrigin: Item.Center + rotation: 180 + } + Image { id: logo width: 150 diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 563e12f924..d330a3d8e7 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -154,6 +154,6 @@ Item { topMargin: 15 } - onClicked: LauncherState.login(username.text, password.text) + onClicked: LauncherState.login(username.text, passwordField.text) } } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 58ff31e588..9e1587e755 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -41,7 +41,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) { static const std::array QML_FILE_FOR_UI_STATE = { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", - "Download.qml", "DownloadFinished.qml", "qml/HFBase/Error.qml" } }; + "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { if (_applicationState != state) { From e03e21c0b40d4d189026b5b1b73b9cb0d0cf0a06 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 Sep 2019 11:07:57 -0700 Subject: [PATCH 031/203] Add support for symlinks and correct file permissions in qt launcher --- launchers/qt/src/Unzipper.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/launchers/qt/src/Unzipper.cpp b/launchers/qt/src/Unzipper.cpp index a650a4540a..a8bd1d8616 100644 --- a/launchers/qt/src/Unzipper.cpp +++ b/launchers/qt/src/Unzipper.cpp @@ -4,6 +4,11 @@ #include #include +#ifdef Q_OS_MACOS +#include +#include +#endif + Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) : _zipFilePath(zipFilePath), _outputDirectory(outputDirectory) { } @@ -56,7 +61,27 @@ void Unzipper::run() { } continue; } - if (mz_zip_reader_extract_to_file(&zip_archive, i, fullFilename.toUtf8().data(), 0)) { + + constexpr uint16_t FILE_PERMISSIONS_MASK { 0777 }; + + uint16_t mod_attr = (file_stat.m_external_attr >> 16) & FILE_PERMISSIONS_MASK; + uint16_t filetype_attr = (file_stat.m_external_attr >> 16) & S_IFMT; + bool is_symlink = filetype_attr == S_IFLNK; + + if (is_symlink) { +#ifdef Q_OS_MACOS + size_t size; + auto data = mz_zip_reader_extract_to_heap(&zip_archive, i, &size, 0); + auto target = QString::fromUtf8((char*)data, size); + + qDebug() << "Extracted symlink: " << size << target; + + symlink(target.toUtf8().data(), fullFilename.toUtf8().data()); +#else + emit finished(true, "Error unzipping symlink"); + return; +#endif + } else if (mz_zip_reader_extract_to_file(&zip_archive, i, fullFilename.toUtf8().data(), 0)) { totalCompressedSize += (uint64_t)file_stat.m_comp_size; totalSize += (uint64_t)file_stat.m_uncomp_size; emit progress((float)totalCompressedSize / (float)zip_archive.m_archive_size); @@ -64,6 +89,9 @@ void Unzipper::run() { emit finished(true, "Unzipping error unzipping file: " + fullFilename); return; } +#ifdef Q_OS_MACOS + chmod(fullFilename.toUtf8().data(), mod_attr); +#endif } qDebug() << "Done unzipping archive, total size:" << totalSize; From 975fc47572dcf441dcb431092a6c6a5c1e2b76aa Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 17 Sep 2019 12:29:48 -0700 Subject: [PATCH 032/203] fix qml urls and text fields --- launchers/qt/CMakeLists.txt | 9 + launchers/qt/resources/images/hidePass.png | Bin 0 -> 2106 bytes launchers/qt/resources/images/showPass.png | Bin 0 -> 2240 bytes launchers/qt/resources/qml/Download.qml | 4 +- .../qml/HFBase/CreateAccountBase.qml | 114 ++++++++--- .../qt/resources/qml/HFBase/LoginBase.qml | 183 ++++++++++++++++++ .../resources/qml/HFControls/HFTextField.qml | 30 +++ launchers/qt/resources/qml/Login.qml | 108 +---------- launchers/qt/resources/qml/root.qml | 12 +- launchers/qt/src/Helper.cpp | 5 + launchers/qt/src/Helper.h | 4 + launchers/qt/src/LauncherState.cpp | 8 +- launchers/qt/src/LauncherWindow.h | 5 +- launchers/qt/src/darwin/Helper.mm | 42 ++++ .../src/darwin/NSTask+NSTaskExecveAdditions.h | 9 + .../src/darwin/NSTask+NSTaskExecveAdditions.m | 75 +++++++ 16 files changed, 459 insertions(+), 149 deletions(-) create mode 100644 launchers/qt/resources/images/hidePass.png create mode 100644 launchers/qt/resources/images/showPass.png create mode 100644 launchers/qt/resources/qml/HFBase/LoginBase.qml create mode 100644 launchers/qt/src/Helper.cpp create mode 100644 launchers/qt/src/Helper.h create mode 100644 launchers/qt/src/darwin/Helper.mm create mode 100644 launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.h create mode 100644 launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.m diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 9bd4436073..9896e3f5d9 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -130,10 +130,19 @@ set(src_files src/PathUtils.h src/Unzipper.h src/Unzipper.cpp + src/Helper.h deps/miniz/miniz.h deps/miniz/miniz.cpp ) + +if (APPLE) + set(src_files ${src_files} + src/darwin/Helper.mm + src/darwin/NSTask+NSTaskExecveAdditions.h + src/darwin/NSTask+NSTaskExecveAdditions.m + ) +endif() set(TARGET_NAME ${PROJECT_NAME}) diff --git a/launchers/qt/resources/images/hidePass.png b/launchers/qt/resources/images/hidePass.png new file mode 100644 index 0000000000000000000000000000000000000000..92500ae3f1a42cc1e366b4b5ced24eb9040439c4 GIT binary patch literal 2106 zcmV-A2*vk_P) z2vwEtId5v_i~xZy`Tzk?xJ2%bu{*}$7+7z7fg?|v{$)oWLg#uyneC?Jg+&Ds*JVl_wB6&(j`ooNXEBEI2zkz;&1#`oRt-O2AA@p$b*rTYCC6=4W* z@`3O_w z$(&PZa+0OUb3%@tXhYjP8J5wHlvG6VNUv#Mj`5}Y?bkuKj_;1~!!iEDTW9?L$JjF; zp>&v|(oc{IZJu;FEs)1!+|w3}uJh-K<#d_Pgcal`!VpTc6%9jJ6V(gzne#Q3;4UOx zwj%CO-WOrHJQ@`@j$ytyTJUwZ>{#J{!d;U+vW+;35J#TVTIhir!!S>rQ)zs-=e#@z zmiMWaAjFktxNkeIyh$_66Gzi(^E9WnaMe6p);fx?o;>G7x+;7+vZu9;7PetN!g+Zr z=Fwl#)_l~oA~%Ge-9nrW^M1y9G~EMNfEI+$sA_4aRycofE6?Q(tuU^x_j7rQgtk&*Pe{cDux6ziFX z9T}iCNzHWC`4jOG<~3cK{@Uf~(Q>I0ZZ8O7`hY#L-u0Iq9}mWoylyRzM(h!j(3Zg# zC-^9a@g__E&$2GV0(%%qyC;8nG#*(8>ybwtR3y3B`gtNF4eM8n_k65M40-w! zZ>>TUrca~;E0^7FA_Em^*blZ_kEER{U`rUna(T|p-8QU?oyhlLtvu9>pE-tf3=e>D zq_gBLkJdJiq-ndrbb!^()Q#(@ljqE4SCC>7(O1)R8*`ik?!Q!#leR3ce(8# z55G9l3Hi%I_xE9%l*iOH{iTnZ1&qm)qN+W|u*$C-=`8un(=%Le>rgaH7(%?Ex^-;B zw^mI-#X^a8C)EG=X^0a59mKpOIRuW`TFe2jB7kFIm+nQyv0`7~3f;utc<_x*^&GjZw7 zT4BBCwwC1S+_5+ie&QI$UkG}!;tVm&tC2LeoQ4`(9^=ThcS3A93q{@4D@M zPus-wF-1SWS%_E2&&T+cwto|H{B9Z{{T1e|qi>!zt!b_S;dbO{c)L)UwKQ8cVRI1j ze9R~Y+6+C@HlO7JcU4nB2OU@p~zEMK#`{qjHSp^SiqhlPhkNKMV_nS zThjA|`3&!>7cyZBfnEaA7kLW*pv{o)^G~JODxAeqCP_vj6}9 literal 0 HcmV?d00001 diff --git a/launchers/qt/resources/images/showPass.png b/launchers/qt/resources/images/showPass.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1c0b14f4f89b716ede8f5b2068dc677cf36772 GIT binary patch literal 2240 zcmV;x2tW6UP)6F&h5}$|fFbw9A{j-J zWk>##?0o0k<4i_YWa+1?YgxA8g%{4?71z(r&SYn|JNs;BuiLHt{^ls%O(dVD-F`f> z-`Z`{ntuMkC&V=gNE=^8@EedYZTG zc4`_9L9%|9_Io$z9uxg-^-ABj+ZPfrJG5(pCS$o&ecn7 z6Y_paOq1f7gcrOR_Ki5Sa;;dz$@g30LMzGRO$>W6#mXogylTbpPNMs+c;VmJMk}|k zhpF0qZz6eDk^S!&XBZOVLb`L(8}=fu7}jy#@(Fq+ZMZf4dZ4D=kT%2!VNu>QP5dH} zGOQ!}6buv7hEGID1^qlu)$Wtp=n}|ZzcWlvNk0);gkfU3S|J(IE5pVnLO)bwKJmOC zn3r&vbFwzXd+D&{^R{=1`wQv8`msQTD?msWj#f}CuIXay$#dHyKf`{Adr!B(^(=2i zI=A>mWavjhx{zuvJlNJ=kP_!qoIkv*J^8*%+_#`xG(IcnhfhS>$U{1ep&MWBC6$3C(7 zp%fE_Q+o0{_tJ%sCM2z!oOPcKq1&bAoVc?Tp5KX8P;7oE)pA@`aLZ7WZ$h)C`xx~1 zg@vHKrz_SOtH5q$Z6ay4fri97uBDzm#Sl*GtRx==-9pMe5)7hiD6q4>V^Gj78!~&qKMLPMDaKp`c>iS>Kv;@3SV4cybCz>&Ep$EAk_Bd!w2OMI^)v zG^W?F7}C9ukWV~nIghR6w9}16N%yg>85u)7-Wf9MJA|$CyAM6mo@+T9`Nos~m|k1x z_D0__6043Mp6WuS;#NXvqRfz2Jk|{Ps2I<~g(!(1Uv@{ZW;(at#jWS}Lu?sGKo$A8 zBi)k3BA$~HXWx3@H)=Z$# zp4>a-C(VDs$khi<&-59|F}XM%W6&+ zyV;up-1XfODC_&gV;YmtvJ-2)!ju(p@k|vg`*15ed52sL^>R6xO1ZdDgryJcp8>XD6aUx@~8vf zf8qSyupF!oeDx_iD^x(fM$qq8ED!fULZ|V+u{?ebe4lz^Fc(7nF5u`)WYW3+gt!xg z_knbVWu3y;gmI@PmUOljTj%}!@kGGicGEq5Gobnt7;5Zzwm;n98*7Bv2;t zq+c-|>UF-MyK`y0bJn6VA?>!1DGy%pEf|wX`l$ozuGM^7PU~~K{PFTW01>Y1`l zDU_tSYhgNS`V9eFrV~&RDTgq<_fg^K*;Pe%+12Y9M!=A^&St|jLNcD;?i#Dd_C)G= zy3SPHIvmscsBm7B)_Jmf9Uq!`Y9nkIUq}_F4JaM^l#C;uI^nn1Oyhl0hbk+aADH+C z2_b#e--hvoBn*3SlaO!KV(ODee3u0__n zJkN2(FprgjistjSEU9hr2l*ejGZ>*?fT{9_tfN z%23;d^)~NFpX*wba9o7`XFA##hCEc{>*ZR(w;Z0(Dh#Xi(ADgM{=DKKnv#AKq>C1g zi_l+4wr3lL6MBf0^>|aFWeLsjmg(6%6ODS=n)Eu=;P|41<01^3d$D5D)8W1c9ukrD zcEb1=!VO(?>}v(1F^$d?Ynl6Aim_)^=R7ufQ3(B?Qh#g}``%|ruaMAGgO*{`nW8UUF=6NdrWEjq$5!MRUie3@FTu?^xupBm6K^Mf&|gHI5MPjQgKJ#~|EB$X z(^0N1``O_Ca(EH&IXt!H{jtrvVMgW>&@n<@F&*=feFgXRG!4%iM(BPm_*gu(+ea26 znq!tApksu?OLcX76+vK6tYB3959~$rz(VgUTbh7e)N_2vd2h^FI#w_ZhqpM};dJpV zT|h22hA`~okggbHNjx2WZrI01zqH#&F7)>COB0X}Dvk(lyEf^`^PP>qR3Lxx zJixz5vG}+K0mTrW2f+68XPll|r+BiD92&oFx4*NW7EqBiJb!KY)$ + +void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, + const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken = QString()); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 9e1587e755..2111568780 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -2,6 +2,7 @@ #include "PathUtils.h" #include "Unzipper.h" +#include "Helper.h" #ifdef Q_OS_WIN #include @@ -415,14 +416,15 @@ void LauncherState::launchClient() { setApplicationState(ApplicationState::LaunchingHighFidelity); QDir installDirectory = _launcherDirectory.filePath("interface_install"); - auto clientPath = installDirectory.absoluteFilePath("interface.exe"); + auto clientPath = installDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); QString homePath = "hifi://hq"; QString defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper"); QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); - // TODO Fix parameters + //::launchClient(clientPath, homePath, defaultScriptsPath, displayName, contentCachePath, _loginTokenResponse); + /* // TODO Fix parameters QString params = "--url " + homePath + " --setBookmark hqhome=\"" + homePath + "\"" + " --defaultScriptsOverride " + QDir::toNativeSeparators(defaultScriptsPath) @@ -463,7 +465,7 @@ void LauncherState::launchClient() { // TODO Implement launching of client #else #error UNSUPPORTED PLATFORM -#endif +#endif*/ } void LauncherState::setApplicationState(ApplicationState state) { diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h index 376ab9ae7b..02012dc342 100644 --- a/launchers/qt/src/LauncherWindow.h +++ b/launchers/qt/src/LauncherWindow.h @@ -1,3 +1,4 @@ +#include "LauncherState.h" #include #include #include @@ -8,10 +9,10 @@ public: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; - //void setLauncherState(std::shared_ptr launcherState) { _launcherState = launcherState; } + void setLauncherStatePtr(std::shared_ptr launcherState) { _launcherState = launcherState; } private: bool _drag { false }; QPoint _previousMousePos; - ///std::shared_ptr _launcherState { nullptr }; + std::shared_ptr _launcherState { nullptr }; }; diff --git a/launchers/qt/src/darwin/Helper.mm b/launchers/qt/src/darwin/Helper.mm new file mode 100644 index 0000000000..6bc0059fc8 --- /dev/null +++ b/launchers/qt/src/darwin/Helper.mm @@ -0,0 +1,42 @@ +#include "../Helper.h" + +#import "NSTask+NSTaskExecveAdditions.h" + +#import +#include +#include + +void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, + const QString& displayName, const QString& contentCachePath, const QString& loginTokenResponse) { + + NSString* homeBookmark = [[NSString stringWithFormat:@"hqhome="] stringByAppendingString:homePath.toNSString()]; + NSArray* arguments; + if (!loginTokenResponse.isEmpty()) { + arguments = [NSArray arrayWithObjects: + @"--url" , homePath.toNSString(), + @"--tokens", loginTokenResponse.toNSString(), + @"--cache", contentCachePath.toNSString(), + @"--displayName", displayName.toNSString(), + @"--defaultScriptsOverride", defaultScriptOverride.toNSString(), + @"--setBookmark", homeBookmark, + @"--no-updater", + @"--no-launcher", nil]; + } else { + arguments = [NSArray arrayWithObjects: + @"--url" , homePath.toNSString(), + @"--cache", contentCachePath.toNSString(), + @"--defaultScriptsOverride", defaultScriptOverride.toNSString(), + @"--setBookmark", homeBookmark, + @"--no-updater", + @"--no-launcher", nil]; + } + + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + NSURL *url = [NSURL fileURLWithPath:[workspace fullPathForApplication:clientPath.toNSString()]]; + NSLog(@"------> path %@: ", [url path]); + NSTask *task = [[NSTask alloc] init]; + task.launchPath = [url path]; + task.arguments = arguments; + [task replaceThisProcess]; + +} diff --git a/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.h b/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.h new file mode 100644 index 0000000000..f26a4021de --- /dev/null +++ b/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.h @@ -0,0 +1,9 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSTask (NSTaskExecveAdditions) +- (void) replaceThisProcess; +@end + +NS_ASSUME_NONNULL_END diff --git a/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.m b/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.m new file mode 100644 index 0000000000..6cdf669247 --- /dev/null +++ b/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.m @@ -0,0 +1,75 @@ +#import "NSTask+NSTaskExecveAdditions.h" + +#import + +char ** +toCArray(NSArray *array) +{ + // Add one to count to accommodate the NULL that terminates the array. + char **cArray = (char **) calloc([array count] + 1, sizeof(char *)); + if (cArray == NULL) { + NSException *exception = [NSException + exceptionWithName:@"MemoryException" + reason:@"malloc failed" + userInfo:nil]; + @throw exception; + } + char *str; + for (int i = 0; i < [array count]; i++) { + str = (char *) [array[i] UTF8String]; + if (str == NULL) { + NSException *exception = [NSException + exceptionWithName:@"NULLStringException" + reason:@"UTF8String was NULL" + userInfo:nil]; + @throw exception; + } + if (asprintf(&cArray[i], "%s", str) == -1) { + for (int j = 0; j < i; j++) { + free(cArray[j]); + } + free(cArray); + NSException *exception = [NSException + exceptionWithName:@"MemoryException" + reason:@"malloc failed" + userInfo:nil]; + @throw exception; + } + } + return cArray; +} + +@implementation NSTask (NSTaskExecveAdditions) + +- (void) replaceThisProcess { + char **args = toCArray([@[[self launchPath]] arrayByAddingObjectsFromArray:[self arguments]]); + + NSMutableArray *env = [[NSMutableArray alloc] init]; + NSDictionary* environvment = [[NSProcessInfo processInfo] environment]; + for (NSString* key in environvment) { + NSString* environmentVariable = [[key stringByAppendingString:@"="] stringByAppendingString:environvment[key]]; + [env addObject:environmentVariable]; + } + + char** envp = toCArray(env); + // `execve` replaces the current process with `path`. + // It will only return if it fails to replace the current process. + NSLog(@"------------>"); + chdir(dirname(args[0])); + execve(args[0], (char * const *)args, envp); + + NSLog(@"----------- FAILED "); + // If we're here `execve` failed. :( + for (int i = 0; i < [[self arguments] count]; i++) { + free((void *) args[i]); + } + free((void *) args); + + NSException *exception = [NSException + exceptionWithName:@"ExecveException" + reason:[NSString stringWithFormat:@"couldn't execve: %s", strerror(errno)] + userInfo:nil]; + @throw exception; +} + +@end From 7d6404b40bb9b892e0cad331a4bffd6bc4dfa1eb Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 17 Sep 2019 14:13:48 -0700 Subject: [PATCH 033/203] launching interface for mac and windows --- launchers/qt/CMakeLists.txt | 1 + launchers/qt/src/Helper.cpp | 44 ++++++++++++++++++++++ launchers/qt/src/LauncherState.cpp | 60 ++++++++---------------------- 3 files changed, 60 insertions(+), 45 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 9896e3f5d9..e633d7f0e4 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -131,6 +131,7 @@ set(src_files src/Unzipper.h src/Unzipper.cpp src/Helper.h + src/Helper.cpp deps/miniz/miniz.h deps/miniz/miniz.cpp ) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 8bbab41698..2c7f89f0df 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -1,5 +1,49 @@ #include "Helper.h" +#ifdef Q_OS_WIN +#include +#endif + +#if defined(Q_OS_WIN) void launchClient(const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken) { + + // TODO Fix parameters + QString params = "--url " + homePath + + " --setBookmark hqhome=\"" + homePath + "\"" + + " --defaultScriptsOverride " + defaultScriptsPath + + " --displayName " + displayName + + " --cache " + contentCachePath; + + if (!_loginTokenResponse.isEmpty()) { + params += " --tokens \"" + _loginTokenResponse.replace("\"", "\\\"") + "\""; + } + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // set the size of the structures + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + // start the program up + BOOL success = CreateProcess( + clientPath.toUtf8().data(), + params.toUtf8().data(), + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + exit(0); } + +#endif diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 2111568780..0f32aeb6b7 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -416,56 +416,26 @@ void LauncherState::launchClient() { setApplicationState(ApplicationState::LaunchingHighFidelity); QDir installDirectory = _launcherDirectory.filePath("interface_install"); - auto clientPath = installDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); + QString clientPath; +#if defined(Q_OS_WIN) + clientPath = installDirectory.absoluteFilePath("interface.exe"); +#elif defined(Q_OS_MACOS) + clientPath = installDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); +#endif QString homePath = "hifi://hq"; - QString defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper"); + QString defaultScriptsPath; +#if defined(Q_OS_WIN) + defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js"); +#elif defined(Q_OS_MACOS) + defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js"); +#endif + + qDebug() << "------> " << defaultScriptsPath; QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); - //::launchClient(clientPath, homePath, defaultScriptsPath, displayName, contentCachePath, _loginTokenResponse); - /* // TODO Fix parameters - QString params = "--url " + homePath - + " --setBookmark hqhome=\"" + homePath + "\"" - + " --defaultScriptsOverride " + QDir::toNativeSeparators(defaultScriptsPath) - + " --displayName " + displayName - + " --cache " + contentCachePath; - - if (!_loginTokenResponse.isEmpty()) { - params += " --tokens \"" + _loginTokenResponse.replace("\"", "\\\"") + "\""; - } - -#if defined(Q_OS_WIN) - STARTUPINFO si; - PROCESS_INFORMATION pi; - - // set the size of the structures - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - - // start the program up - BOOL success = CreateProcess( - clientPath.toUtf8().data(), - params.toUtf8().data(), - nullptr, // Process handle not inheritable - nullptr, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - CREATE_NEW_CONSOLE, // Opens file in a separate console - nullptr, // Use parent's environment block - nullptr, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure - ); - // Close process and thread handles. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - exit(0); -#elif defined(Q_OS_MACOS) - // TODO Implement launching of client -#else -#error UNSUPPORTED PLATFORM -#endif*/ + ::launchClient(clientPath, homePath, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse); } void LauncherState::setApplicationState(ApplicationState state) { From 09b9b1e10cae3dfc901d48c5af8f0b6f486cbbda Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 19 Sep 2019 09:41:01 -0700 Subject: [PATCH 034/203] mac auto-updater --- launchers/qt/src/Helper.cpp | 26 +++++++++- launchers/qt/src/Helper.h | 9 ++++ launchers/qt/src/Launcher.cpp | 1 + launchers/qt/src/LauncherState.cpp | 77 +++++++++++++++++++++++++++++- launchers/qt/src/LauncherState.h | 9 ++++ launchers/qt/src/darwin/Helper.mm | 30 ++++++++++++ launchers/qt/src/main.cpp | 26 +++++++++- 7 files changed, 175 insertions(+), 3 deletions(-) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 2c7f89f0df..999241111a 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -3,6 +3,11 @@ #include #endif +#include +#include +#include +#include + #if defined(Q_OS_WIN) void launchClient(const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, @@ -45,5 +50,24 @@ void launchClient(const QString& homePath, const QString& defaultScriptOverride, CloseHandle(pi.hThread); exit(0); } - #endif + + + +void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { + if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) { + qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist"; + } + + bool success = false; +#ifdef Q_OS_MAC + success = replaceDirectory(oldLauncherPath, newLauncherPath); +#endif + + if (success) { + qDebug() << "succeessfully replaced"; + } else { + qDebug() << "failed"; + exit(0); + } +} diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index ee145e56f3..de47b45f70 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -1,4 +1,13 @@ #include +#include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken = QString()); + + +void launchAutoUpdater(const QString& autoUpdaterPath); +void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString()); + +#ifdef Q_OS_MAC +bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); +#endif diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 2daaa415de..2d5fd8dc21 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -10,6 +10,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; + qDebug() << "resources.rcc path: " << resourceBinaryLocation; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); //_launcherState->setUIState(LauncherState::SPLASH_SCREEN); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 0f32aeb6b7..3ac05a0bf2 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -174,8 +174,24 @@ void LauncherState::receivedBuildsReply() { #endif _latestBuilds.builds.push_back(build); } + + auto launcherResults = root["launcher"].toObject(); + + Build launcherBuild; + launcherBuild.latestVersion = launcherResults["version"].toInt(); + +#ifdef Q_OS_WIN + launcherBuild.installerZipURL = launcherResults["windows"].toObject()["url"].toString(); +#elif defined(Q_OS_MACOS) + launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString(); +#else + #error "Launcher is only supported on Windows and Mac OS" +#endif + _latestBuilds.launcherBuild = launcherBuild; } } + + //downloadLauncher(); setApplicationState(ApplicationState::WaitingForLogin); } @@ -290,6 +306,11 @@ void LauncherState::downloadClient() { } } +void LauncherState::launcherDownloadComplete() { + _launcherZipFile.close(); + installLauncher(); +} + void LauncherState::clientDownloadComplete() { ASSERT_STATE(ApplicationState::DownloadingClient); @@ -330,6 +351,61 @@ void LauncherState::installClient() { //launchClient(); } +void LauncherState::downloadLauncher() { + auto request = new QNetworkRequest(QUrl(_latestBuilds.launcherBuild.installerZipURL)); + auto reply = _networkAccessManager.get(*request); + + _launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.zip")); + + qDebug() << "opening " << _launcherZipFile.fileName(); + + if (!_launcherZipFile.open(QIODevice::WriteOnly)) { + setApplicationState(ApplicationState::UnexpectedError); + return; + } + + connect(reply, &QNetworkReply::finished, this, &LauncherState::launcherDownloadComplete); + connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { + char buf[4096]; + while (reply->bytesAvailable() > 0) { + qint64 size; + size = reply->read(buf, (qint64)sizeof(buf)); + if (size == 0) { + break; + } + _launcherZipFile.write(buf, size); + } + }); +} + +void LauncherState::installLauncher() { + auto installDir = _launcherDirectory.absoluteFilePath("launcher_install"); + _launcherDirectory.mkpath("launcher_install"); + + qDebug() << "Uzipping " << _launcherZipFile.fileName() << " to " << installDir; + + auto unzipper = new Unzipper(_launcherZipFile.fileName(), QDir(installDir)); + unzipper->setAutoDelete(true); + connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) { + if (error) { + qDebug() << "Unzipper finished with error: " << errorMessage; + } else { + qDebug() << "Unzipper finished without error"; + + QDir installDirectory = _launcherDirectory.filePath("launcher_install"); + QString launcherPath; +#if defined(Q_OS_WIN) + launcherPath = installDirectory.absoluteFilePath("HQ Launcher.exe"); +#elif defined(Q_OS_MACOS) + launcherPath = installDirectory.absoluteFilePath("HQ Launcher.app"); +#endif + //::launchAutoUpdater(launcherPath); + } + }); + + QThreadPool::globalInstance()->start(unzipper); +} + void LauncherState::downloadContentCache() { ASSERT_STATE(ApplicationState::InstallingClient); @@ -431,7 +507,6 @@ void LauncherState::launchClient() { defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js"); #endif - qDebug() << "------> " << defaultScriptsPath; QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index d4590519a1..1f0ea2ffdf 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -19,6 +19,7 @@ struct LatestBuilds { QString defaultTag; std::vector builds; + Build launcherBuild; }; struct LoginResponse { @@ -59,9 +60,11 @@ public: RequestingLogin, DownloadingClient, + DownloadingLauncher, DownloadingContentCache, InstallingClient, + InstallingLauncher, InstallingContentCache, LaunchingHighFidelity @@ -101,6 +104,10 @@ public: Q_INVOKABLE void login(QString username, QString password); Q_INVOKABLE void receivedLoginReply(); + // Launcher + void downloadLauncher(); + void installLauncher(); + // Client void downloadClient(); void installClient(); @@ -123,6 +130,7 @@ signals: private slots: void clientDownloadComplete(); void contentCacheDownloadComplete(); + void launcherDownloadComplete(); private: bool shouldDownloadContentCache() const; @@ -140,6 +148,7 @@ private: QString _contentCacheURL{ "https://orgs.highfidelity.com/content-cache/content_cache_small-only_data8.zip" }; // QString::null }; // If null, there is no content cache to download QString _loginTokenResponse; QFile _clientZipFile; + QFile _launcherZipFile; QFile _contentZipFile; float _downloadProgress { 0 }; diff --git a/launchers/qt/src/darwin/Helper.mm b/launchers/qt/src/darwin/Helper.mm index 6bc0059fc8..5f4d875c82 100644 --- a/launchers/qt/src/darwin/Helper.mm +++ b/launchers/qt/src/darwin/Helper.mm @@ -40,3 +40,33 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr [task replaceThisProcess]; } + + +void launchAutoUpdater(const QString& autoUpdaterPath) { + NSTask* task = [[NSTask alloc] init]; + task.launchPath = [autoUpdaterPath.toNSString() stringByAppendingString:@"/Contents/Resources/updater"]; + task.arguments = @[[[NSBundle mainBundle] bundlePath], autoUpdaterPath.toNSString()]; + [task launch]; + + exit(0); +} + + +@interface UpdaterHelper : NSObject ++(NSURL*) NSStringToNSURL: (NSString*) path; +@end + +@implementation UpdaterHelper ++(NSURL*) NSStringToNSURL: (NSString*) path +{ + return [NSURL URLWithString: [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL: [NSURL URLWithString:@"file://"]]; +} +@end + + +bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory) { + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newDirectory.toNSString()]; + return (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()] + backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:nil]; +} diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 72df44c03c..c848f13b9f 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -2,7 +2,9 @@ #include "LauncherWindow.h" #include "Launcher.h" - +#include +#include +#include "Helper.h" #ifdef Q_OS_WIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); @@ -14,7 +16,29 @@ Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); + + +bool hasSuffix(const std::string path, const std::string suffix) { + if (path.substr(path.find_last_of(".") + 1) == suffix) { + return true; + } + + return false; +} + int main(int argc, char *argv[]) { + +#ifdef Q_OS_MAC + // auto updater + if (argc == 3) { + if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { + std::cout << "swapping launcher \n"; + swapLaunchers(argv[1], argv[2]); + } else { + std::cout << "not swapping launcher \n"; + } + } +#endif QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); From f97a8a06c882b67d67d4c8cc93bc48702ee0d541 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 19 Sep 2019 10:30:08 -0700 Subject: [PATCH 035/203] Add fix for S_IFLNK on Windows, and fix createClient --- launchers/qt/src/Helper.cpp | 35 +++++++++++++++++++---------------- launchers/qt/src/Helper.h | 2 +- launchers/qt/src/Unzipper.cpp | 4 ++++ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 2c7f89f0df..fe09f8de00 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -1,12 +1,13 @@ #include "Helper.h" -#ifdef Q_OS_WIN -#include -#endif +#include #if defined(Q_OS_WIN) -void launchClient(const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, - const QString& contentCachePath, const QString& loginResponseToken) { + +#include + +void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath, + const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { // TODO Fix parameters QString params = "--url " + homePath @@ -15,8 +16,8 @@ void launchClient(const QString& homePath, const QString& defaultScriptOverride, + " --displayName " + displayName + " --cache " + contentCachePath; - if (!_loginTokenResponse.isEmpty()) { - params += " --tokens \"" + _loginTokenResponse.replace("\"", "\\\"") + "\""; + if (!loginResponseToken.isEmpty()) { + params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\""; } STARTUPINFO si; @@ -31,19 +32,21 @@ void launchClient(const QString& homePath, const QString& defaultScriptOverride, BOOL success = CreateProcess( clientPath.toUtf8().data(), params.toUtf8().data(), - nullptr, // Process handle not inheritable - nullptr, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - CREATE_NEW_CONSOLE, // Opens file in a separate console - nullptr, // Use parent's environment block - nullptr, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure ); + // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - exit(0); + + QCoreApplication::instance()->quit(); } #endif diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index ee145e56f3..c64011c4a4 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -1,4 +1,4 @@ #include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, - const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken = QString()); + const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); diff --git a/launchers/qt/src/Unzipper.cpp b/launchers/qt/src/Unzipper.cpp index a8bd1d8616..5683156303 100644 --- a/launchers/qt/src/Unzipper.cpp +++ b/launchers/qt/src/Unzipper.cpp @@ -66,7 +66,11 @@ void Unzipper::run() { uint16_t mod_attr = (file_stat.m_external_attr >> 16) & FILE_PERMISSIONS_MASK; uint16_t filetype_attr = (file_stat.m_external_attr >> 16) & S_IFMT; +#ifdef Q_OS_MACOS bool is_symlink = filetype_attr == S_IFLNK; +#else + bool is_symlink = false; +#endif if (is_symlink) { #ifdef Q_OS_MACOS From 82faeb70c8e4ca1aedb2faa7fd02e068bb0b5d69 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 19 Sep 2019 10:40:16 -0700 Subject: [PATCH 036/203] remove merge conflict comments --- launchers/qt/src/Helper.h | 4 ---- launchers/qt/src/darwin/Helper.mm | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 347f4888e5..7d1f54a949 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -2,10 +2,7 @@ #include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, -<<<<<<< HEAD const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); -======= - const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken = QString()); void launchAutoUpdater(const QString& autoUpdaterPath); @@ -14,4 +11,3 @@ void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& ne #ifdef Q_OS_MAC bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); #endif ->>>>>>> 09b9b1e10cae3dfc901d48c5af8f0b6f486cbbda diff --git a/launchers/qt/src/darwin/Helper.mm b/launchers/qt/src/darwin/Helper.mm index 5f4d875c82..47bdcb2814 100644 --- a/launchers/qt/src/darwin/Helper.mm +++ b/launchers/qt/src/darwin/Helper.mm @@ -7,7 +7,7 @@ #include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, - const QString& displayName, const QString& contentCachePath, const QString& loginTokenResponse) { + const QString& displayName, const QString& contentCachePath, QString& loginTokenResponse) { NSString* homeBookmark = [[NSString stringWithFormat:@"hqhome="] stringByAppendingString:homePath.toNSString()]; NSArray* arguments; From 05e932963596e1e9273628b488b4a4a96a8d189e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 23 Sep 2019 08:27:22 -0700 Subject: [PATCH 037/203] Add proper version checking to qt launcher --- launchers/qt/CMakeLists.txt | 4 +-- launchers/qt/src/Helper.cpp | 1 + launchers/qt/src/Helper.h | 3 -- launchers/qt/src/LauncherState.cpp | 47 +++++++++++++++++++++++++++--- launchers/qt/src/LauncherState.h | 4 +++ 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index e633d7f0e4..86654930c5 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -37,8 +37,8 @@ if (WIN32) ExternalProject_Add( qtlite - URL "https://hifi-public.s3.amazonaws.com/huffman/launcher/qt-lite-ssl.zip" - URL_HASH MD5=83eeba1565e5727aef11655acf893c15 + URL "https://hifi-public.s3.amazonaws.com/huffman/launcher/qt-lite-ssl_2019-9-19.zip" + URL_HASH MD5=8b7a0b8fb772a014a3276274f40a9d14 #83eeba1565e5727aef11655acf893c15 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 75519492cb..f3ed87e9ab 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #if defined(Q_OS_WIN) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 347f4888e5..f35d890433 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -2,10 +2,7 @@ #include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, -<<<<<<< HEAD const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); -======= - const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken = QString()); void launchAutoUpdater(const QString& autoUpdaterPath); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 3799d691f7..cddcb43a4a 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -9,6 +9,8 @@ #endif #include +#include + #include #include @@ -23,10 +25,30 @@ #include #include +#include -QString getCurrentClientVersion() { - // TODO Implement client version checking - return ""; +#include + +QString LauncherState::getCurrentClientVersion() { + QProcess client; + + client.start(getClientExecutablePath(), { "--version" }); + + QEventLoop loop; + connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit); + loop.exec(); + + auto output = client.readAllStandardOutput(); + + QRegularExpression regex { "Interface (?\\d+)(-.*)?" }; + + auto match = regex.match(output); + + if (match.hasMatch()) { + return match.captured("version"); + } + + return QString::null; } @@ -261,6 +283,19 @@ QString LauncherState::getContentCachePath() const { return _launcherDirectory.filePath("cache"); } +QString LauncherState::getClientDirectory() const { + return _launcherDirectory.filePath("interface_install"); +} + +QString LauncherState::getClientExecutablePath() const { + QDir clientDirectory = getClientDirectory(); +#if defined(Q_OS_WIN) + return clientDirectory.absoluteFilePath("interface.exe"); +#elif defined(Q_OS_MACOS) + return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); +#endif +} + bool LauncherState::shouldDownloadContentCache() const { return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); } @@ -500,7 +535,11 @@ void LauncherState::installContentCache() { } void LauncherState::launchClient() { - ASSERT_STATE({ ApplicationState::InstallingClient, ApplicationState::InstallingContentCache }); + ASSERT_STATE({ + ApplicationState::RequestingLogin, + ApplicationState::InstallingClient, + ApplicationState::InstallingContentCache + }); setApplicationState(ApplicationState::LaunchingHighFidelity); diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 1f0ea2ffdf..0b87775e25 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -134,7 +134,11 @@ private slots: private: bool shouldDownloadContentCache() const; + QString getCurrentClientVersion(); + QString getContentCachePath() const; + QString getClientDirectory() const; + QString getClientExecutablePath() const; QNetworkAccessManager _networkAccessManager; LatestBuilds _latestBuilds; From e6128003eccade39766db773fd1be55ef6a4349c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 23 Sep 2019 16:58:06 -0700 Subject: [PATCH 038/203] Add content cache and settings support to qt launcher --- launchers/qt/src/Helper.cpp | 10 +- launchers/qt/src/LauncherState.cpp | 233 +++++++++++++++++++++-------- launchers/qt/src/LauncherState.h | 11 +- launchers/qt/src/Unzipper.cpp | 2 + 4 files changed, 187 insertions(+), 69 deletions(-) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index f3ed87e9ab..f8e8b03505 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -15,11 +15,11 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { // TODO Fix parameters - QString params = "--url " + homePath - + " --setBookmark hqhome=\"" + homePath + "\"" - + " --defaultScriptsOverride " + defaultScriptsPath - + " --displayName " + displayName - + " --cache " + contentCachePath; + QString params = "--url \"" + homePath + "\"" + + " --setBookmark \"hqhome=" + homePath + "\"" + + " --defaultScriptsOverride \"" + defaultScriptsPath + "\"" + + " --displayName \"" + displayName + "\"" + + " --cache \"" + contentCachePath + "\""; if (!loginResponseToken.isEmpty()) { params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\""; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index cddcb43a4a..91b91b785c 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -29,28 +29,29 @@ #include -QString LauncherState::getCurrentClientVersion() { - QProcess client; +const QString METAVERSE_API_URL{ "https://metaverse.highfidelity.com" }; +const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; - client.start(getClientExecutablePath(), { "--version" }); - - QEventLoop loop; - connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit); - loop.exec(); - - auto output = client.readAllStandardOutput(); - - QRegularExpression regex { "Interface (?\\d+)(-.*)?" }; - - auto match = regex.match(output); - - if (match.hasMatch()) { - return match.captured("version"); - } - - return QString::null; +QString LauncherState::getContentCachePath() const { + return _launcherDirectory.filePath("cache"); } +QString LauncherState::getClientDirectory() const { + return _launcherDirectory.filePath("interface_install"); +} + +QString LauncherState::getClientExecutablePath() const { + QDir clientDirectory = getClientDirectory(); +#if defined(Q_OS_WIN) + return clientDirectory.absoluteFilePath("interface.exe"); +#elif defined(Q_OS_MACOS) + return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); +#endif +} + +bool LauncherState::shouldDownloadContentCache() const { + return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); +} bool LatestBuilds::getBuild(QString tag, Build* outBuild) { if (tag.isNull()) { @@ -68,7 +69,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) { } static const std::array QML_FILE_FOR_UI_STATE = - { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", + { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { @@ -113,6 +114,7 @@ LauncherState::UIState LauncherState::getUIState() const { switch (_applicationState) { case ApplicationState::Init: case ApplicationState::RequestingBuilds: + case ApplicationState::GettingCurrentClientVersion: return SPLASH_SCREEN; case ApplicationState::WaitingForLogin: case ApplicationState::RequestingLogin: @@ -197,7 +199,7 @@ void LauncherState::receivedBuildsReply() { #elif defined(Q_OS_MACOS) build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString(); #else - #error "Launcher is only supported on Windows and Mac OS" +#error "Launcher is only supported on Windows and Mac OS" #endif _latestBuilds.builds.push_back(build); } @@ -212,16 +214,78 @@ void LauncherState::receivedBuildsReply() { #elif defined(Q_OS_MACOS) launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString(); #else - #error "Launcher is only supported on Windows and Mac OS" +#error "Launcher is only supported on Windows and Mac OS" #endif _latestBuilds.launcherBuild = launcherBuild; } } - //downloadLauncher(); + getCurrentClientVersion(); +} + +void LauncherState::getCurrentClientVersion() { + ASSERT_STATE(ApplicationState::RequestingBuilds); + + setApplicationState(ApplicationState::GettingCurrentClientVersion); + + QProcess client; + QEventLoop loop; + + //connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit); + connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit); + /* + connect(&client, QOverload::of(&QProcess::finished), [&]() { + qDebug() << "Finished"; + }); + connect(&client, &QProcess::errorOccurred, [&](QProcess::ProcessError err) { + qDebug() << "Error occurred" << err << client.error(); + }); + connect(&client, &QProcess::started, [&]() { + qDebug() << "Started"; + }); + connect(&client, &QProcess::stateChanged, [&]() { + qDebug() << "State changed " << client.state(); + }); + */ + + //qDebug() << "Starting client"; + client.start(getClientExecutablePath(), { "--version" }); + //qDebug() << "Started" << client.error(); + + if (client.state() != QProcess::NotRunning) { + //qDebug() << "Starting loop"; + loop.exec(); + } else { + qDebug() << "Not waiting for client, there was an error starting it: " << client.error(); + } + + // TODO Handle errors + auto output = client.readAllStandardOutput(); + + QRegularExpression regex { "Interface (?\\d+)(-.*)?" }; + + auto match = regex.match(output); + + if (match.hasMatch()) { + _currentClientVersion = match.captured("version"); + } else { + _currentClientVersion = QString::null; + } + qDebug() << "Current client version is: " << _currentClientVersion; + setApplicationState(ApplicationState::WaitingForLogin); } +QString getUserAgent() { +#if defined(Q_OS_WIN) + return "HQLauncher/fixme (Windows)"; +#elif defined(Q_OS_MACOS) + return "HQLauncher/fixme (MacOS)"; +#else +#error Unsupported platform +#endif +} + void LauncherState::login(QString username, QString password) { ASSERT_STATE(ApplicationState::WaitingForLogin); @@ -229,8 +293,9 @@ void LauncherState::login(QString username, QString password) { qDebug() << "Got login: " << username << password; - auto request = new QNetworkRequest(QUrl("https://metaverse.highfidelity.com/oauth/token")); + auto request = new QNetworkRequest(QUrl(METAVERSE_API_URL + "/oauth/token")); + request->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QUrlQuery query; query.addQueryItem("grant_type", "password"); @@ -276,30 +341,64 @@ Q_INVOKABLE void LauncherState::receivedLoginReply() { qDebug() << "Got response for login: " << data; _loginTokenResponse = data; + requestSettings(); +} + +void LauncherState::requestSettings() { + QUrl lockerURL = METAVERSE_API_URL; + lockerURL.setPath("/api/v1/user/locker"); + + auto lockerRequest = new QNetworkRequest(lockerURL); + lockerRequest->setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + lockerRequest->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); + lockerRequest->setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(_loginResponse.accessToken).toUtf8()); + + QNetworkReply* lockerReply = _networkAccessManager.get(*lockerRequest); + connect(lockerReply, &QNetworkReply::finished, this, &LauncherState::receivedSettingsReply); +} + +void LauncherState::receivedSettingsReply() { + auto reply = static_cast(sender()); + qDebug() << "Got reply: " << reply->error(); + if (reply->error()) { + setApplicationState(ApplicationState::UnexpectedError); + return; + } + auto data = reply->readAll(); + qDebug() << "Settings: " << data; + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing settings"; + setApplicationStateError("Error retreiving settings"); + return; + } + + auto root = doc.object(); + if (root["status"] != "success") { + qDebug() << "Status is not \"success\""; + setApplicationStateError("Error retreiving settings"); + return; + } + + _homeLocation = "hifi://hq"; + if (root["data"].toObject().contains("home_location")) { + auto homeLocation = root["data"].toObject()["home_location"]; + if (homeLocation.isString()) { + _homeLocation = homeLocation.toString(); + auto host = QUrl(_homeLocation).host(); + _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; + qDebug() << "Home location is: " << _homeLocation; + qDebug() << "Content cache url is: " << _contentCacheURL; + } + } + + //qDebug() << "Home:" << _homeLocation << QUrl(_homeLocation).host(); + downloadClient(); } -QString LauncherState::getContentCachePath() const { - return _launcherDirectory.filePath("cache"); -} - -QString LauncherState::getClientDirectory() const { - return _launcherDirectory.filePath("interface_install"); -} - -QString LauncherState::getClientExecutablePath() const { - QDir clientDirectory = getClientDirectory(); -#if defined(Q_OS_WIN) - return clientDirectory.absoluteFilePath("interface.exe"); -#elif defined(Q_OS_MACOS) - return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); -#endif -} - -bool LauncherState::shouldDownloadContentCache() const { - return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); -} - void LauncherState::downloadClient() { ASSERT_STATE(ApplicationState::RequestingLogin); @@ -310,8 +409,7 @@ void LauncherState::downloadClient() { return; } - auto currentVersion = getCurrentClientVersion(); - if (QString::number(build.latestVersion) == currentVersion) { + if (QString::number(build.latestVersion) == _currentClientVersion) { qDebug() << "Existing client install is up-to-date."; downloadContentCache(); return; @@ -381,7 +479,7 @@ void LauncherState::installClient() { auto unzipper = new Unzipper(_clientZipFile.fileName(), QDir(installDir)); unzipper->setAutoDelete(true); connect(unzipper, &Unzipper::progress, this, [this](float progress) { - qDebug() << "Unzipper progress: " << progress; + //qDebug() << "Unzipper progress: " << progress; _downloadProgress = progress; emit downloadProgressChanged(); }); @@ -463,8 +561,9 @@ void LauncherState::downloadContentCache() { _downloadProgress = 0; - auto request = new QNetworkRequest(QUrl(_contentCacheURL)); - auto reply = _networkAccessManager.get(*request); + QNetworkRequest request{ QUrl(_contentCacheURL) }; + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + auto reply = _networkAccessManager.get(request); _contentZipFile.setFileName(_launcherDirectory.absoluteFilePath("content_cache.zip")); @@ -475,17 +574,6 @@ void LauncherState::downloadContentCache() { } connect(reply, &QNetworkReply::finished, this, &LauncherState::contentCacheDownloadComplete); - connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { - char buf[4096]; - while (reply->bytesAvailable() > 0) { - qint64 size; - size = reply->read(buf, (qint64)sizeof(buf)); - if (size == 0) { - break; - } - _contentZipFile.write(buf, size); - } - }); connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) { _downloadProgress = (float)received / (float)total; emit downloadProgressChanged(); @@ -498,6 +586,21 @@ void LauncherState::downloadContentCache() { void LauncherState::contentCacheDownloadComplete() { ASSERT_STATE(ApplicationState::DownloadingContentCache); + auto reply = static_cast(sender()); + + if (reply->error()) { + qDebug() << "Error: " << reply->error() << reply->readAll(); + setApplicationStateError("Failed to retrieve content cache"); + return; + } + + char buf[4096]; + while (reply->bytesAvailable() > 0) { + qint64 size; + size = reply->read(buf, (qint64)sizeof(buf)); + _contentZipFile.write(buf, size); + } + _contentZipFile.close(); installContentCache(); @@ -552,7 +655,6 @@ void LauncherState::launchClient() { #endif // TODO Get correct home path - QString homePath = "hifi://hq"; QString defaultScriptsPath; #if defined(Q_OS_WIN) defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js"); @@ -563,7 +665,12 @@ void LauncherState::launchClient() { QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); - ::launchClient(clientPath, homePath, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse); + ::launchClient(clientPath, _homeLocation, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse); +} + +void LauncherState::setApplicationStateError(QString errorMessage) { + _applicationErrorMessage = errorMessage; + setApplicationState(ApplicationState::UnexpectedError); } void LauncherState::setApplicationState(ApplicationState state) { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 0b87775e25..c20dd99518 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -55,6 +55,7 @@ public: UnexpectedError, RequestingBuilds, + GettingCurrentClientVersion, WaitingForLogin, RequestingLogin, @@ -93,6 +94,7 @@ public: void setLastLoginError(LastLoginError lastLoginError); LastLoginError getLastLoginError() const; + void setApplicationStateError(QString errorMessage); void setApplicationState(ApplicationState state); ApplicationState getApplicationState() const; @@ -100,10 +102,14 @@ public: void requestBuilds(); Q_INVOKABLE void receivedBuildsReply(); + // Login Q_INVOKABLE void login(QString username, QString password); Q_INVOKABLE void receivedLoginReply(); + void requestSettings(); + Q_INVOKABLE void receivedSettingsReply(); + // Launcher void downloadLauncher(); void installLauncher(); @@ -134,7 +140,7 @@ private slots: private: bool shouldDownloadContentCache() const; - QString getCurrentClientVersion(); + void getCurrentClientVersion(); QString getContentCachePath() const; QString getClientDirectory() const; @@ -148,9 +154,12 @@ private: ApplicationState _applicationState { ApplicationState::Init }; LoginResponse _loginResponse; LastLoginError _lastLoginError { NONE }; + QString _applicationErrorMessage; + QString _currentClientVersion; QString _buildTag { QString::null }; QString _contentCacheURL{ "https://orgs.highfidelity.com/content-cache/content_cache_small-only_data8.zip" }; // QString::null }; // If null, there is no content cache to download QString _loginTokenResponse; + QString _homeLocation; QFile _clientZipFile; QFile _launcherZipFile; QFile _contentZipFile; diff --git a/launchers/qt/src/Unzipper.cpp b/launchers/qt/src/Unzipper.cpp index 5683156303..5d08d3b25a 100644 --- a/launchers/qt/src/Unzipper.cpp +++ b/launchers/qt/src/Unzipper.cpp @@ -16,6 +16,8 @@ Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) : void Unzipper::run() { qDebug() << "Reading zip file" << _zipFilePath << ", extracting to" << _outputDirectory.absolutePath(); + _outputDirectory.mkpath(_outputDirectory.absolutePath()); + mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); From f1fa2f8cd2632ac409ccb5f6d9e77cefc2149211 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 24 Sep 2019 18:45:42 -0700 Subject: [PATCH 039/203] adding windows auto-updater --- launchers/qt/CMakeLists.txt | 22 ++++-- launchers/qt/src/Helper.cpp | 49 ------------- .../{darwin/Helper.mm => Helper_darwin.mm} | 0 launchers/qt/src/Helper_windows.cpp | 70 +++++++++++++++++++ launchers/qt/src/Launcher.cpp | 1 + .../qt/src/LauncherInstaller_windows.cpp | 43 ++++++++++++ launchers/qt/src/LauncherInstaller_windows.h | 16 +++++ launchers/qt/src/LauncherState.cpp | 25 +++++-- launchers/qt/src/LauncherState.h | 2 + .../NSTask+NSTaskExecveAdditions.h | 0 .../NSTask+NSTaskExecveAdditions.m | 0 launchers/qt/src/main.cpp | 18 +++++ 12 files changed, 188 insertions(+), 58 deletions(-) rename launchers/qt/src/{darwin/Helper.mm => Helper_darwin.mm} (100%) create mode 100644 launchers/qt/src/Helper_windows.cpp create mode 100644 launchers/qt/src/LauncherInstaller_windows.cpp create mode 100644 launchers/qt/src/LauncherInstaller_windows.h rename launchers/qt/src/{darwin => }/NSTask+NSTaskExecveAdditions.h (100%) rename launchers/qt/src/{darwin => }/NSTask+NSTaskExecveAdditions.m (100%) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 86654930c5..169240c647 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -11,7 +11,6 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -#set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) include("cmake/init.cmake") @@ -110,6 +109,8 @@ add_custom_command( COMMAND "${_qt5Core_install_prefix}/bin/rcc" ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}) +QT5_ADD_RESOURCES(RES_SOURCES ${RESOURCES_QRC}) + list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC}) add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) @@ -134,14 +135,23 @@ set(src_files src/Helper.cpp deps/miniz/miniz.h deps/miniz/miniz.cpp + ${RES_SOURCES} ) if (APPLE) set(src_files ${src_files} - src/darwin/Helper.mm - src/darwin/NSTask+NSTaskExecveAdditions.h - src/darwin/NSTask+NSTaskExecveAdditions.m + src/Helper_darwin.mm + src/NSTask+NSTaskExecveAdditions.h + src/NSTask+NSTaskExecveAdditions.m + ) +endif() + +if (WIN32) + set(src_files ${src_files} + src/Helper_windows.cpp + src/LauncherInstaller_windows.h + src/LauncherInstaller_windows.cpp ) endif() set(TARGET_NAME ${PROJECT_NAME}) @@ -149,7 +159,7 @@ set(TARGET_NAME ${PROJECT_NAME}) set_packaging_parameters() if (WIN32) - add_executable(${PROJECT_NAME} ${src_files}) + add_executable(${PROJECT_NAME} ${src_files} build/resources.qrc) elseif (APPLE) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) @@ -220,6 +230,8 @@ else() message("Use resource.rcc path: qrc:/") endif() +target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") + if (APPLE) install( TARGETS HQLauncher diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index f8e8b03505..f056676d43 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -7,55 +7,6 @@ #include #include -#if defined(Q_OS_WIN) - -#include - -void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath, - const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { - - // TODO Fix parameters - QString params = "--url \"" + homePath + "\"" - + " --setBookmark \"hqhome=" + homePath + "\"" - + " --defaultScriptsOverride \"" + defaultScriptsPath + "\"" - + " --displayName \"" + displayName + "\"" - + " --cache \"" + contentCachePath + "\""; - - if (!loginResponseToken.isEmpty()) { - params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\""; - } - - STARTUPINFO si; - PROCESS_INFORMATION pi; - - // set the size of the structures - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - - // start the program up - BOOL success = CreateProcess( - clientPath.toUtf8().data(), - params.toUtf8().data(), - nullptr, // Process handle not inheritable - nullptr, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - CREATE_NEW_CONSOLE, // Opens file in a separate console - nullptr, // Use parent's environment block - nullptr, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure - ); - - // Close process and thread handles. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - QCoreApplication::instance()->quit(); -} -#endif - - void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) { diff --git a/launchers/qt/src/darwin/Helper.mm b/launchers/qt/src/Helper_darwin.mm similarity index 100% rename from launchers/qt/src/darwin/Helper.mm rename to launchers/qt/src/Helper_darwin.mm diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp new file mode 100644 index 0000000000..cb3671f274 --- /dev/null +++ b/launchers/qt/src/Helper_windows.cpp @@ -0,0 +1,70 @@ +#include "Helper.h" + +#include +#include + +void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath, + const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { + + // TODO Fix parameters + QString params = "--url \"" + homePath + "\"" + + " --setBookmark \"hqhome=" + homePath + "\"" + + " --defaultScriptsOverride \"" + defaultScriptsPath + "\"" + + " --displayName \"" + displayName + "\"" + + " --cache \"" + contentCachePath + "\""; + + if (!loginResponseToken.isEmpty()) { + params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\""; + } + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // set the size of the structures + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + // start the program up + BOOL success = CreateProcess( + clientPath.toUtf8().data(), + params.toUtf8().data(), + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + QCoreApplication::instance()->quit(); +} + +void launchAutoUpdater(const QString& autoUpdaterPath) { + QString params = "--restart --noUpdate"; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + BOOL success = CreateProcess( + autoUpdaterPath.toUtf8().data(), + params.toUtf8().data(), + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); +} diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 2d5fd8dc21..8ad9085cc8 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -9,6 +9,7 @@ #include "PathUtils.h" Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { + Q_INIT_RESOURCE(resources); QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; qDebug() << "resources.rcc path: " << resourceBinaryLocation; QResource::registerResource(resourceBinaryLocation); diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp new file mode 100644 index 0000000000..4a3f819c23 --- /dev/null +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -0,0 +1,43 @@ +#include "LauncherInstaller_windows.h" + +#include + +#include +#include +#include +#include + +LauncherInstaller::LauncherInstaller(const QString& applicationFilePath) { + _launcherInstallDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/HQ"; + qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath(); + _launcherInstallDir.mkpath(_launcherInstallDir.absolutePath()); + QFileInfo fileInfo(applicationFilePath); + _launcherRunningFilePath = fileInfo.absoluteFilePath(); + _launcherRunningDirPath = fileInfo.absoluteDir().absolutePath(); + qDebug() << "Launcher running file path: " << _launcherRunningFilePath; + qDebug() << "Launcher running dir path: " << _launcherRunningDirPath; +} + +bool LauncherInstaller::runningOutsideOfInstallDir() { + return (QString::compare(_launcherInstallDir.absolutePath(), _launcherRunningDirPath) != 0); +} + +void LauncherInstaller::install() { + if (runningOutsideOfInstallDir()) { + qDebug() << "Installing HQ Launcher...."; + QString oldLauncherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; + + if (QFile::exists(oldLauncherPath)) { + bool didRemove = QFile::remove(oldLauncherPath); + qDebug() << "did remove file: " << didRemove; + } + bool success = QFile::copy(_launcherRunningFilePath, oldLauncherPath); + if (success) { + qDebug() << "successful"; + } else { + qDebug() << "not successful"; + } + } +} + +void LauncherInstaller::uninstall() {} diff --git a/launchers/qt/src/LauncherInstaller_windows.h b/launchers/qt/src/LauncherInstaller_windows.h new file mode 100644 index 0000000000..1aa10ce876 --- /dev/null +++ b/launchers/qt/src/LauncherInstaller_windows.h @@ -0,0 +1,16 @@ +#pragma once + +#include +class LauncherInstaller { +public: + LauncherInstaller(const QString& applicationFilePath); + ~LauncherInstaller() = default; + + void install(); + void uninstall(); +private: + bool runningOutsideOfInstallDir(); + QDir _launcherInstallDir; + QString _launcherRunningFilePath; + QString _launcherRunningDirPath; +}; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 91b91b785c..1a8023e236 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -8,6 +8,7 @@ #include #endif #include +#include #include @@ -69,7 +70,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) { } static const std::array QML_FILE_FOR_UI_STATE = - { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", + { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { @@ -220,9 +221,17 @@ void LauncherState::receivedBuildsReply() { } } + if (shouldDownloadLauncher()) { + downloadLauncher(); + } getCurrentClientVersion(); } + +bool LauncherState::shouldDownloadLauncher() { + return _latestBuilds.launcherBuild.latestVersion != atoi(LAUNCHER_BUILD_VERSION); +} + void LauncherState::getCurrentClientVersion() { ASSERT_STATE(ApplicationState::RequestingBuilds); @@ -453,14 +462,17 @@ void LauncherState::downloadClient() { void LauncherState::launcherDownloadComplete() { _launcherZipFile.close(); + +#ifdef Q_OS_MAC installLauncher(); +#elif defined(Q_OS_WIN) + //launchAutoUpdater(_launcherZipFile.fileName()); +#endif } void LauncherState::clientDownloadComplete() { ASSERT_STATE(ApplicationState::DownloadingClient); - _clientZipFile.close(); - installClient(); } @@ -501,7 +513,11 @@ void LauncherState::downloadLauncher() { auto request = new QNetworkRequest(QUrl(_latestBuilds.launcherBuild.installerZipURL)); auto reply = _networkAccessManager.get(*request); +#ifdef Q_OS_MAC _launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.zip")); +#elif defined(Q_OS_WIN) + _launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.exe")); +#endif qDebug() << "opening " << _launcherZipFile.fileName(); @@ -525,8 +541,9 @@ void LauncherState::downloadLauncher() { } void LauncherState::installLauncher() { - auto installDir = _launcherDirectory.absoluteFilePath("launcher_install"); + _launcherDirectory.rmpath("launcher_install"); _launcherDirectory.mkpath("launcher_install"); + auto installDir = _launcherDirectory.absoluteFilePath("launcher_install"); qDebug() << "Uzipping " << _launcherZipFile.fileName() << " to " << installDir; diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index c20dd99518..f3a3c4a313 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -146,6 +146,8 @@ private: QString getClientDirectory() const; QString getClientExecutablePath() const; + bool shouldDownloadLauncher(); + QNetworkAccessManager _networkAccessManager; LatestBuilds _latestBuilds; QDir _launcherDirectory; diff --git a/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.h b/launchers/qt/src/NSTask+NSTaskExecveAdditions.h similarity index 100% rename from launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.h rename to launchers/qt/src/NSTask+NSTaskExecveAdditions.h diff --git a/launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.m b/launchers/qt/src/NSTask+NSTaskExecveAdditions.m similarity index 100% rename from launchers/qt/src/darwin/NSTask+NSTaskExecveAdditions.m rename to launchers/qt/src/NSTask+NSTaskExecveAdditions.m diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index c848f13b9f..df5987dd62 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -7,6 +7,7 @@ #include "Helper.h" #ifdef Q_OS_WIN +#include "LauncherInstaller_windows.h" Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(Q_OS_MACOS) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); @@ -26,8 +27,18 @@ bool hasSuffix(const std::string path, const std::string suffix) { return false; } +bool containsOption(int argc, char* argv[], const std::string& option) { + for (int index = 0; index < argc; index++) { + if (option.compare(argv[index]) == 0) { + return true; + } + } + return false; +} + int main(int argc, char *argv[]) { + //std::cout << "Launcher version: " << LAUNCHER_BUILD_VERSION; #ifdef Q_OS_MAC // auto updater if (argc == 3) { @@ -38,6 +49,13 @@ int main(int argc, char *argv[]) { std::cout << "not swapping launcher \n"; } } +#elif defined(Q_OS_WIN) + // try-install + + if (containsOption(argc, argv, "--restart")) { + LauncherInstaller launcherInstaller(argv[0]); + launcherInstaller.install(); + } #endif QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); From c01b23b78dce5a25f3ed25a650e09635c3a0638f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 25 Sep 2019 08:49:20 -0700 Subject: [PATCH 040/203] Update qt launcher to launch client if content cache dl fails --- launchers/qt/src/LauncherState.cpp | 8 ++++++-- launchers/qt/src/LauncherState.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 91b91b785c..5796893ef4 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -345,6 +345,8 @@ Q_INVOKABLE void LauncherState::receivedLoginReply() { } void LauncherState::requestSettings() { + // TODO Request settings if already logged in + QUrl lockerURL = METAVERSE_API_URL; lockerURL.setPath("/api/v1/user/locker"); @@ -561,6 +563,7 @@ void LauncherState::downloadContentCache() { _downloadProgress = 0; + qDebug() << "Downloading content cache from: " << _contentCacheURL; QNetworkRequest request{ QUrl(_contentCacheURL) }; request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); auto reply = _networkAccessManager.get(request); @@ -589,8 +592,9 @@ void LauncherState::contentCacheDownloadComplete() { auto reply = static_cast(sender()); if (reply->error()) { - qDebug() << "Error: " << reply->error() << reply->readAll(); - setApplicationStateError("Failed to retrieve content cache"); + qDebug() << "Error downloading content cache: " << reply->error() << reply->readAll(); + qDebug() << "Continuing to launch client"; + launchClient(); return; } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index c20dd99518..c4eb12cfde 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -157,7 +157,7 @@ private: QString _applicationErrorMessage; QString _currentClientVersion; QString _buildTag { QString::null }; - QString _contentCacheURL{ "https://orgs.highfidelity.com/content-cache/content_cache_small-only_data8.zip" }; // QString::null }; // If null, there is no content cache to download + QString _contentCacheURL; QString _loginTokenResponse; QString _homeLocation; QFile _clientZipFile; From 308483f4e71eb30e9955cede166e6d427418b9c2 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 25 Sep 2019 18:15:15 -0700 Subject: [PATCH 041/203] working on file system links --- launchers/qt/CMakeLists.txt | 10 +++- launchers/qt/src/Helper.h | 8 +++ launchers/qt/src/Helper_windows.cpp | 51 +++++++++++++++++-- launchers/qt/src/Launcher.cpp | 1 - .../qt/src/LauncherInstaller_windows.cpp | 12 ++++- launchers/qt/src/LauncherState.cpp | 5 +- launchers/qt/src/main.cpp | 2 - 7 files changed, 76 insertions(+), 13 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 169240c647..c10d78de47 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -16,6 +16,10 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) include("cmake/init.cmake") include("cmake/macros/SetPackagingParameters.cmake") +if (WIN32) + set(CMAKE_MFC_FLAG 1) +endif() + function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) if (NOT DEFINED ${_RESULT_NAME}) @@ -119,6 +123,8 @@ foreach(plugin ${Qt5Gui_PLUGINS}) set(plugin_libs ${plugin_libs} ${_loc}) endforeach() +qt5_add_resources(EXAMPLE_RCC_SRC build/resources.qrc) + set(src_files src/main.cpp src/Launcher.h @@ -135,7 +141,7 @@ set(src_files src/Helper.cpp deps/miniz/miniz.h deps/miniz/miniz.cpp - ${RES_SOURCES} + #${RES_SOURCES} ) @@ -159,7 +165,7 @@ set(TARGET_NAME ${PROJECT_NAME}) set_packaging_parameters() if (WIN32) - add_executable(${PROJECT_NAME} ${src_files} build/resources.qrc) + add_executable(${PROJECT_NAME} ${src_files})#build/resources.qrc ${EXAMPLE_RCC_SRC}) elseif (APPLE) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 7d1f54a949..975eb383d7 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -1,6 +1,10 @@ #include #include +#ifdef Q_OS_WIN +#include "Windows.h" +#endif + void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); @@ -11,3 +15,7 @@ void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& ne #ifdef Q_OS_MAC bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); #endif + +#ifdef Q_OS_WIN +HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs); +#endif diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index cb3671f274..5295943a4e 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -1,7 +1,12 @@ #include "Helper.h" +#include "windows.h" +#include "winnls.h" +#include "shobjidl.h" +#include "objbase.h" +#include "objidl.h" +#include "shlguid.h" #include -#include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath, const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { @@ -34,12 +39,12 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr FALSE, // Set handle inheritance to FALSE CREATE_NEW_CONSOLE, // Opens file in a separate console nullptr, // Use parent's environment block - nullptr, // Use parent's starting directory + nullptr, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure ); - // Close process and thread handles. + // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); @@ -68,3 +73,43 @@ void launchAutoUpdater(const QString& autoUpdaterPath) { &pi // Pointer to PROCESS_INFORMATION structure ); } + + +HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs) { + IShellLink* psl; + + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + CoInitialize(NULL); + HRESULT hres = E_INVALIDARG; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) { + IPersistFile* ppf; + + // Set the path to the shortcut target and add the description. + psl->SetPath(lpszPathObj); + psl->SetDescription(lpszDesc); + psl->SetArguments(lpszArgs); + + // Query IShellLink for the IPersistFile interface, used for saving the + // shortcut in persistent storage. + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); + + if (SUCCEEDED(hres)) { + WCHAR wsz[MAX_PATH]; + + // Ensure that the string is Unicode. + MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH); + + // Add code here to check return value from MultiByteWideChar + // for success. + + // Save the link by calling IPersistFile::Save. + hres = ppf->Save(wsz, TRUE); + ppf->Release(); + } + psl->Release(); + } + CoUninitialize(); + return SUCCEEDED(hres); +} diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 8ad9085cc8..2d5fd8dc21 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -9,7 +9,6 @@ #include "PathUtils.h" Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { - Q_INIT_RESOURCE(resources); QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; qDebug() << "resources.rcc path: " << resourceBinaryLocation; QResource::registerResource(resourceBinaryLocation); diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index 4a3f819c23..dc49c7d738 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -1,6 +1,7 @@ #include "LauncherInstaller_windows.h" +#include "Helper.h" -#include +#include #include #include @@ -37,6 +38,15 @@ void LauncherInstaller::install() { } else { qDebug() << "not successful"; } + + qDebug() << "LauncherInstaller: create uninstall link"; + QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.link"; + if (QFile::exists(uninstallLinkPath)) { + QFile::remove(uninstallLinkPath); + } + + createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); } } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 1a8023e236..f9ada2bc41 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -4,9 +4,6 @@ #include "Unzipper.h" #include "Helper.h" -#ifdef Q_OS_WIN -#include -#endif #include #include @@ -70,7 +67,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) { } static const std::array QML_FILE_FOR_UI_STATE = - { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", + { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index df5987dd62..3c810434e7 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -37,7 +37,6 @@ bool containsOption(int argc, char* argv[], const std::string& option) { } int main(int argc, char *argv[]) { - //std::cout << "Launcher version: " << LAUNCHER_BUILD_VERSION; #ifdef Q_OS_MAC // auto updater @@ -60,7 +59,6 @@ int main(int argc, char *argv[]) { QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); - Launcher launcher(argc, argv); return launcher.exec(); From 1d50856931823ac568dd95b3f51dfc47ee2f3e91 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 26 Sep 2019 10:50:32 -0700 Subject: [PATCH 042/203] update qt build --- launchers/qt/CMakeLists.txt | 4 ++-- launchers/qt/src/Helper_darwin.mm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index c10d78de47..831895c3ab 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -68,8 +68,8 @@ endif () if (APPLE) ExternalProject_Add( qtlite - URL "https://dantescalves.com/launcher/qt-lite-clang-8.1.0.zip" - URL_HASH MD5=f8462bfcd54e6b05ef888f8b197149c2 + URL "https://dantescalves.com/launcher/qt-lite-5.9.9-mac.zip" + URL_HASH MD5=0cd78d40e5f539a7e314cf99b6cae0d0 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 47bdcb2814..417abbb647 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -1,4 +1,4 @@ -#include "../Helper.h" +#include "Helper.h" #import "NSTask+NSTaskExecveAdditions.h" From 836e23f5cf60391aa7dccbfc266c597debbd81bc Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 26 Sep 2019 16:32:25 -0700 Subject: [PATCH 043/203] override defualt latest build url and add commandlineOption --- launchers/qt/CMakeLists.txt | 2 ++ launchers/qt/resources/qml/HFBase/Error.qml | 4 +++ launchers/qt/resources/qml/root.qml | 2 +- launchers/qt/src/CommandlineOptions.cpp | 30 ++++++++++++++++ launchers/qt/src/CommandlineOptions.h | 16 +++++++++ launchers/qt/src/Helper_darwin.mm | 2 +- launchers/qt/src/Launcher.cpp | 1 - launchers/qt/src/LauncherState.cpp | 39 ++++++++++----------- launchers/qt/src/LauncherState.h | 2 ++ launchers/qt/src/main.cpp | 23 ++++-------- 10 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 launchers/qt/src/CommandlineOptions.cpp create mode 100644 launchers/qt/src/CommandlineOptions.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 831895c3ab..53b1f54796 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -139,6 +139,8 @@ set(src_files src/Unzipper.cpp src/Helper.h src/Helper.cpp + src/CommandlineOptions.h + src/CommandlineOptions.cpp deps/miniz/miniz.h deps/miniz/miniz.cpp #${RES_SOURCES} diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index d05ef01413..f488a3e9bf 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -78,6 +78,10 @@ Item { topMargin: 15 horizontalCenter: description.horizontalCenter } + + onClicked: { + LauncherState.restart(); + } } diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index bfd704fa50..3c755c2a6b 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -15,7 +15,7 @@ Item { } Component.onCompleted: { - loader.source = LauncherState.getCurrentUISource(); + loader.source = "./SplashScreen.qml"; LauncherState.updateSourceUrl.connect(function(url) { loader.source = url; }); diff --git a/launchers/qt/src/CommandlineOptions.cpp b/launchers/qt/src/CommandlineOptions.cpp new file mode 100644 index 0000000000..162d3414f3 --- /dev/null +++ b/launchers/qt/src/CommandlineOptions.cpp @@ -0,0 +1,30 @@ +#include "CommandlineOptions.h" + +#include +#include + +bool isCommandlineOption(const std::string& option) { + if (option.rfind("--", 0) == 0 && option.at(2) != '-') { + return true; + } + return false; +} +bool CommandlineOptions::contains(const std::string& option) { + auto iter = std::find(_commandlineOptions.begin(), _commandlineOptions.end(), option); + return (iter != _commandlineOptions.end()); +} + +void CommandlineOptions::parse(const int argc, char** argv) { + for (int index = 1; index < argc; index++) { + std::string option = argv[index]; + if (isCommandlineOption(option)) { + std::cout << "adding commandline option: " << option << "\n"; + _commandlineOptions.push_back(option); + } + } +} + +CommandlineOptions* CommandlineOptions::getInstance() { + static CommandlineOptions commandlineOptions; + return &commandlineOptions; +} diff --git a/launchers/qt/src/CommandlineOptions.h b/launchers/qt/src/CommandlineOptions.h new file mode 100644 index 0000000000..4a34bd1337 --- /dev/null +++ b/launchers/qt/src/CommandlineOptions.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +class CommandlineOptions { +public: + CommandlineOptions() = default; + ~CommandlineOptions() = default; + + void parse(const int argc, char** argv); + bool contains(const std::string& option); + static CommandlineOptions* getInstance(); +private: + std::vector _commandlineOptions; +}; diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 417abbb647..5ffde89caf 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -7,7 +7,7 @@ #include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, - const QString& displayName, const QString& contentCachePath, QString& loginTokenResponse) { + const QString& displayName, const QString& contentCachePath, QString loginTokenResponse) { NSString* homeBookmark = [[NSString stringWithFormat:@"hqhome="] stringByAppendingString:homePath.toNSString()]; NSArray* arguments; diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 2d5fd8dc21..96da608f7d 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -13,7 +13,6 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { qDebug() << "resources.rcc path: " << resourceBinaryLocation; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); - //_launcherState->setUIState(LauncherState::SPLASH_SCREEN); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index f9ada2bc41..c6345522b2 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -151,12 +151,25 @@ void LauncherState::requestBuilds() { setApplicationState(ApplicationState::RequestingBuilds); // TODO Show splash screen until this request is complete - auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json")); + + QString latestBuildRequestUrl { "https://thunder.highfidelity.com/builds/api/tags/latest/?format=json" }; + QProcessEnvironment processEnvironment =QProcessEnvironment::systemEnvironment(); + + if (processEnvironment.contains("HQ_LAUNCHER_BUILDS_URL")) { + latestBuildRequestUrl = processEnvironment.value("HQ_LAUNCHER_BUILDS_URL"); + } + + auto request = new QNetworkRequest(QUrl(latestBuildRequestUrl)); auto reply = _networkAccessManager.get(*request); QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedBuildsReply); } +void LauncherState::restart() { + setApplicationState(ApplicationState::Init); + requestBuilds(); +} + void LauncherState::receivedBuildsReply() { auto reply = static_cast(sender()); @@ -219,7 +232,7 @@ void LauncherState::receivedBuildsReply() { } if (shouldDownloadLauncher()) { - downloadLauncher(); + //downloadLauncher(); } getCurrentClientVersion(); } @@ -239,27 +252,12 @@ void LauncherState::getCurrentClientVersion() { //connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit); connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit); - /* - connect(&client, QOverload::of(&QProcess::finished), [&]() { - qDebug() << "Finished"; - }); - connect(&client, &QProcess::errorOccurred, [&](QProcess::ProcessError err) { - qDebug() << "Error occurred" << err << client.error(); - }); - connect(&client, &QProcess::started, [&]() { - qDebug() << "Started"; - }); - connect(&client, &QProcess::stateChanged, [&]() { - qDebug() << "State changed " << client.state(); - }); - */ + connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit, Qt::QueuedConnection); - //qDebug() << "Starting client"; client.start(getClientExecutablePath(), { "--version" }); - //qDebug() << "Started" << client.error(); if (client.state() != QProcess::NotRunning) { - //qDebug() << "Starting loop"; + loop.exec(); } else { qDebug() << "Not waiting for client, there was an error starting it: " << client.error(); @@ -389,12 +387,13 @@ void LauncherState::receivedSettingsReply() { } _homeLocation = "hifi://hq"; + _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + QUrl(_homeLocation).host() + ".zip"; if (root["data"].toObject().contains("home_location")) { auto homeLocation = root["data"].toObject()["home_location"]; if (homeLocation.isString()) { _homeLocation = homeLocation.toString(); auto host = QUrl(_homeLocation).host(); - _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; + //_contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; qDebug() << "Home location is: " << _homeLocation; qDebug() << "Content cache url is: " << _contentCacheURL; } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index f3a3c4a313..b66d9e90e3 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -110,6 +110,8 @@ public: void requestSettings(); Q_INVOKABLE void receivedSettingsReply(); + Q_INVOKABLE void restart(); + // Launcher void downloadLauncher(); void installLauncher(); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 3c810434e7..ed5cbe8138 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -2,6 +2,7 @@ #include "LauncherWindow.h" #include "Launcher.h" +#include "CommandlineOptions.h" #include #include #include "Helper.h" @@ -27,31 +28,19 @@ bool hasSuffix(const std::string path, const std::string suffix) { return false; } -bool containsOption(int argc, char* argv[], const std::string& option) { - for (int index = 0; index < argc; index++) { - if (option.compare(argv[index]) == 0) { - return true; - } - } - return false; -} - int main(int argc, char *argv[]) { - //std::cout << "Launcher version: " << LAUNCHER_BUILD_VERSION; #ifdef Q_OS_MAC // auto updater if (argc == 3) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { - std::cout << "swapping launcher \n"; swapLaunchers(argv[1], argv[2]); - } else { - std::cout << "not swapping launcher \n"; } } -#elif defined(Q_OS_WIN) - // try-install - - if (containsOption(argc, argv, "--restart")) { +#endif + CommandlineOptions* options = CommandlineOptions::getInstance(); + options->parse(argc, argv); +#ifdef Q_OS_WIN + if (options->contains("--restart")) { LauncherInstaller launcherInstaller(argv[0]); launcherInstaller.install(); } From e8205728ce85c9d06c2c69cc2209dd101fe3f10b Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 27 Sep 2019 15:31:44 -0700 Subject: [PATCH 044/203] install and uninstall features --- launchers/qt/src/Helper.h | 2 +- .../qt/src/LauncherInstaller_windows.cpp | 53 ++++++++++++++++++- launchers/qt/src/LauncherInstaller_windows.h | 6 ++- launchers/qt/src/main.cpp | 7 ++- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 975eb383d7..d26cacfa5c 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -17,5 +17,5 @@ bool replaceDirectory(const QString& orginalDirectory, const QString& newDirecto #endif #ifdef Q_OS_WIN -HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs); +HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs = (LPCSTR)""); #endif diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index dc49c7d738..ea35e86c64 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -10,8 +10,11 @@ LauncherInstaller::LauncherInstaller(const QString& applicationFilePath) { _launcherInstallDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/HQ"; + _launcherApplicationsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/HQ"; qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath(); + qDebug() << "Launcher Application dir: " << _launcherApplicationsDir.absolutePath(); _launcherInstallDir.mkpath(_launcherInstallDir.absolutePath()); + _launcherApplicationsDir.mkpath(_launcherApplicationsDir.absolutePath()); QFileInfo fileInfo(applicationFilePath); _launcherRunningFilePath = fileInfo.absoluteFilePath(); _launcherRunningDirPath = fileInfo.absoluteDir().absolutePath(); @@ -24,6 +27,7 @@ bool LauncherInstaller::runningOutsideOfInstallDir() { } void LauncherInstaller::install() { + //qDebug() << "Is install dir empty: " << _launcherInstallDir.isEmpty(); if (runningOutsideOfInstallDir()) { qDebug() << "Installing HQ Launcher...."; QString oldLauncherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; @@ -40,14 +44,59 @@ void LauncherInstaller::install() { } qDebug() << "LauncherInstaller: create uninstall link"; - QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.link"; + QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.lnk"; if (QFile::exists(uninstallLinkPath)) { QFile::remove(uninstallLinkPath); } + + + QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QString applicationPath = _launcherApplicationsDir.absolutePath(); + + QString appStartLinkPath = applicationPath + "/HQ.lnk"; + QString uninstallAppStartLinkPath = applicationPath + "/Uninstall HQ.lnk"; + QString desktopAppLinkPath = desktopPath + "/HQ.lnk"; + + createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(), (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); + + createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)uninstallAppStartLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); + + createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)desktopAppLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Setup and Launch HQ")); + + createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)appStartLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Setup and Launch HQ")); + } else { + qDebug() << "FAILED!!!!!!!"; } } -void LauncherInstaller::uninstall() {} +void LauncherInstaller::uninstall() { + qDebug() << "Uninstall Launcher"; + QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QString applicationPath = _launcherApplicationsDir.absolutePath(); + + QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.lnk"; + if (QFile::exists(uninstallLinkPath)) { + QFile::remove(uninstallLinkPath); + } + + QString appStartLinkPath = applicationPath + "/HQ.lnk"; + if (QFile::exists(appStartLinkPath)) { + QFile::remove(appStartLinkPath); + } + + QString uninstallAppStartLinkPath = applicationPath + "/Uninstall HQ.lnk"; + if (QFile::exists(uninstallAppStartLinkPath)) { + QFile::remove(uninstallAppStartLinkPath); + } + + QString desktopAppLinkPath = desktopPath + "/HQ.lnk"; + if (QFile::exists(desktopAppLinkPath)) { + QFile::remove(desktopAppLinkPath); + } +} diff --git a/launchers/qt/src/LauncherInstaller_windows.h b/launchers/qt/src/LauncherInstaller_windows.h index 1aa10ce876..8791f9395f 100644 --- a/launchers/qt/src/LauncherInstaller_windows.h +++ b/launchers/qt/src/LauncherInstaller_windows.h @@ -8,9 +8,13 @@ public: void install(); void uninstall(); -private: bool runningOutsideOfInstallDir(); +private: + void createShortcuts(); + void deleteShortcuts(); + QDir _launcherInstallDir; + QDir _launcherApplicationsDir; QString _launcherRunningFilePath; QString _launcherRunningDirPath; }; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index ed5cbe8138..6b2bd39fde 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -40,9 +40,12 @@ int main(int argc, char *argv[]) { CommandlineOptions* options = CommandlineOptions::getInstance(); options->parse(argc, argv); #ifdef Q_OS_WIN - if (options->contains("--restart")) { - LauncherInstaller launcherInstaller(argv[0]); + LauncherInstaller launcherInstaller(argv[0]); + if (options->contains("--restart") || launcherInstaller.runningOutsideOfInstallDir()) { launcherInstaller.install(); + } else if (options->contains("--uninstall")) { + launcherInstaller.uninstall(); + return 0; } #endif QString name { "High Fidelity" }; From d5f042c3619674095221459cbacbdefa1cadd56a Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Fri, 27 Sep 2019 16:59:04 -0700 Subject: [PATCH 045/203] static link resources --- launchers/qt/CMakeLists.txt | 28 +++++++++------------------- launchers/qt/src/Launcher.cpp | 3 --- launchers/qt/src/LauncherState.h | 2 +- launchers/qt/src/PathUtils.cpp | 4 ++-- launchers/qt/src/PathUtils.h | 4 +++- launchers/qt/src/main.cpp | 1 + 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 53b1f54796..d54189137c 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -123,8 +123,6 @@ foreach(plugin ${Qt5Gui_PLUGINS}) set(plugin_libs ${plugin_libs} ${_loc}) endforeach() -qt5_add_resources(EXAMPLE_RCC_SRC build/resources.qrc) - set(src_files src/main.cpp src/Launcher.h @@ -143,7 +141,6 @@ set(src_files src/CommandlineOptions.cpp deps/miniz/miniz.h deps/miniz/miniz.cpp - #${RES_SOURCES} ) @@ -167,13 +164,13 @@ set(TARGET_NAME ${PROJECT_NAME}) set_packaging_parameters() if (WIN32) - add_executable(${PROJECT_NAME} ${src_files})#build/resources.qrc ${EXAMPLE_RCC_SRC}) + add_executable(${PROJECT_NAME} ${src_files} ${RES_SOURCES}) elseif (APPLE) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) set(MACOSX_BUNDLE_ICON_FILE "interface.icns") - add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files}) + add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files} ${RES_SOURCES}) set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") endif() @@ -222,21 +219,14 @@ if (APPLE) ${OPENSSL_INCLUDE_DIR}) endif() -set(INTERFACE_EXEC_DIR "$") -set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") -add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${RESOURCES_RCC}" - "${INTERFACE_EXEC_DIR}") - -if (LAUNCHER_SOURCE_TREE_RESOURCES) - target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/") - message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/") -else() - target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/") - message("Use resource.rcc path: qrc:/") -endif() + if (LAUNCHER_SOURCE_TREE_RESOURCES) + target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/") + message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/") + else() + target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/") + message("Use resource.rcc path: qrc:/") + endif() target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 96da608f7d..0b51328aef 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -9,9 +9,6 @@ #include "PathUtils.h" Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { - QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; - qDebug() << "resources.rcc path: " << resourceBinaryLocation; - QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index b66d9e90e3..b96440c827 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -130,7 +130,7 @@ public: Q_INVOKABLE float getDownloadProgress() const { return _downloadProgress; } signals: - void updateSourceUrl(QString sourceUrl); + void updateSourceUrl(QUrl sourceUrl); void uiStateChanged(); void applicationStateChanged(); void downloadProgressChanged(); diff --git a/launchers/qt/src/PathUtils.cpp b/launchers/qt/src/PathUtils.cpp index ae02585788..542e268308 100644 --- a/launchers/qt/src/PathUtils.cpp +++ b/launchers/qt/src/PathUtils.cpp @@ -2,6 +2,6 @@ #include -QString PathUtils::resourcePath(const QString& source) { - return QString(RESOURCE_PREFIX_URL + source); +QUrl PathUtils::resourcePath(const QString& source) { + return QUrl(RESOURCE_PREFIX_URL + source); } diff --git a/launchers/qt/src/PathUtils.h b/launchers/qt/src/PathUtils.h index 29d9b609d6..3bfddd624c 100644 --- a/launchers/qt/src/PathUtils.h +++ b/launchers/qt/src/PathUtils.h @@ -2,10 +2,12 @@ #include #include +#include +#include class PathUtils : public QObject { Q_OBJECT public: PathUtils() = default; ~PathUtils() = default; - Q_INVOKABLE static QString resourcePath(const QString& source); + Q_INVOKABLE static QUrl resourcePath(const QString& source); }; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 6b2bd39fde..a1c7ab61ca 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -29,6 +29,7 @@ bool hasSuffix(const std::string path, const std::string suffix) { } int main(int argc, char *argv[]) { + Q_INIT_RESOURCE(resources); #ifdef Q_OS_MAC // auto updater if (argc == 3) { From 13e8dc036c2898cf5adfb088764f2c0f9996ded9 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 30 Sep 2019 14:24:14 -0700 Subject: [PATCH 046/203] mac close interface and launcher and fix compile warnings --- launchers/qt/CMakeLists.txt | 1 + launchers/qt/src/Helper.h | 3 ++ launchers/qt/src/Helper_darwin.mm | 40 +++++++++++++++++++ launchers/qt/src/LauncherState.h | 6 +-- .../qt/src/NSTask+NSTaskExecveAdditions.m | 8 ++-- launchers/qt/src/Unzipper.cpp | 2 +- launchers/qt/src/main.cpp | 5 +++ 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index d54189137c..57f900140c 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -35,6 +35,7 @@ include(ExternalProject) if (APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework Cocoa -framework CoreServices -framework Carbon -framework IOKit -framework Security -framework SystemConfiguration") + add_compile_options(-W -Wall -Wextra -Wpedantic) endif() if (WIN32) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index d26cacfa5c..65f68ec41d 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -14,6 +14,9 @@ void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& ne #ifdef Q_OS_MAC bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); +void closeInterfaceIfRunning(); +void waitForInterfaceToClose(); +bool isLauncherAlreadyRunning(); #endif #ifdef Q_OS_WIN diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 5ffde89caf..28b35193ba 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -70,3 +70,43 @@ bool replaceDirectory(const QString& orginalDirectory, const QString& newDirecto return (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()] backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:nil]; } + + +void waitForInterfaceToClose() { + bool interfaceRunning = true; + + while (interfaceRunning) { + interfaceRunning = false; + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + NSArray* apps = [workspace runningApplications]; + for (NSRunningApplication* app in apps) { + if ([[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface"] || + [[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface-pr"]) { + interfaceRunning = true; + break; + } + } + } +} + +bool isLauncherAlreadyRunning() { + NSArray* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.highfidelity.launcher"]; + NSLog(@"Count: %lu", [apps count]); + if ([apps count] > 1) { + NSLog(@"launcher is already running"); + return true; + } + + return false; +} + +void closeInterfaceIfRunning() { + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + NSArray* apps = [workspace runningApplications]; + for (NSRunningApplication* app in apps) { + if ([[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface"] || + [[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface-pr"]) { + [app terminate]; + } + } +} diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index b96440c827..51e0399c1e 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -31,9 +31,9 @@ struct LoginResponse { class LauncherState : public QObject { Q_OBJECT - Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged); - Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged); - Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged); + Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged) + Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged) + Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) public: LauncherState(); diff --git a/launchers/qt/src/NSTask+NSTaskExecveAdditions.m b/launchers/qt/src/NSTask+NSTaskExecveAdditions.m index 6cdf669247..a216b24e95 100644 --- a/launchers/qt/src/NSTask+NSTaskExecveAdditions.m +++ b/launchers/qt/src/NSTask+NSTaskExecveAdditions.m @@ -15,7 +15,7 @@ toCArray(NSArray *array) @throw exception; } char *str; - for (int i = 0; i < [array count]; i++) { + for (NSUInteger i = 0; i < [array count]; i++) { str = (char *) [array[i] UTF8String]; if (str == NULL) { NSException *exception = [NSException @@ -25,7 +25,7 @@ toCArray(NSArray *array) @throw exception; } if (asprintf(&cArray[i], "%s", str) == -1) { - for (int j = 0; j < i; j++) { + for (NSUInteger j = 0; j < i; j++) { free(cArray[j]); } free(cArray); @@ -54,13 +54,11 @@ toCArray(NSArray *array) char** envp = toCArray(env); // `execve` replaces the current process with `path`. // It will only return if it fails to replace the current process. - NSLog(@"------------>"); chdir(dirname(args[0])); execve(args[0], (char * const *)args, envp); - NSLog(@"----------- FAILED "); // If we're here `execve` failed. :( - for (int i = 0; i < [[self arguments] count]; i++) { + for (NSUInteger i = 0; i < [[self arguments] count]; i++) { free((void *) args[i]); } free((void *) args); diff --git a/launchers/qt/src/Unzipper.cpp b/launchers/qt/src/Unzipper.cpp index 5d08d3b25a..54a81f272e 100644 --- a/launchers/qt/src/Unzipper.cpp +++ b/launchers/qt/src/Unzipper.cpp @@ -49,7 +49,7 @@ void Unzipper::run() { uint64_t totalSize = 0; uint64_t totalCompressedSize = 0; - bool _shouldFail = false; + //bool _shouldFail = false; for (int i = 0; i < fileCount; i++) { if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index a1c7ab61ca..db69bfafe3 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -31,6 +31,11 @@ bool hasSuffix(const std::string path, const std::string suffix) { int main(int argc, char *argv[]) { Q_INIT_RESOURCE(resources); #ifdef Q_OS_MAC + if (isLauncherAlreadyRunning()) { + return 0; + } + closeInterfaceIfRunning(); + // waitForInterfaceToClose(); // auto updater if (argc == 3) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { From e81deff4e94aa7246e3458e743da8dc7c485fbf0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 30 Sep 2019 10:42:00 -0700 Subject: [PATCH 047/203] Add signup to qt launcher, and cleanup --- launchers/qt/CMakeLists.txt | 10 +- .../qml/HFBase/CreateAccountBase.qml | 10 +- .../qt/resources/qml/HFBase/LoginBase.qml | 5 +- launchers/qt/src/Helper.cpp | 44 ++++ launchers/qt/src/Helper.h | 16 ++ launchers/qt/src/Helper_windows.cpp | 2 +- launchers/qt/src/LauncherState.cpp | 246 +++++++++--------- launchers/qt/src/LauncherState.h | 36 ++- launchers/qt/src/LoginRequest.cpp | 73 ++++++ launchers/qt/src/LoginRequest.h | 49 ++++ launchers/qt/src/SignupRequest.cpp | 79 ++++++ launchers/qt/src/SignupRequest.h | 38 +++ launchers/qt/src/UserSettingsRequest.cpp | 67 +++++ launchers/qt/src/UserSettingsRequest.h | 43 +++ launchers/qt/src/main.cpp | 10 +- 15 files changed, 575 insertions(+), 153 deletions(-) create mode 100644 launchers/qt/src/LoginRequest.cpp create mode 100644 launchers/qt/src/LoginRequest.h create mode 100644 launchers/qt/src/SignupRequest.cpp create mode 100644 launchers/qt/src/SignupRequest.h create mode 100644 launchers/qt/src/UserSettingsRequest.cpp create mode 100644 launchers/qt/src/UserSettingsRequest.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 169240c647..2cd6ff5456 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -127,8 +127,14 @@ set(src_files src/LauncherState.cpp src/LauncherWindow.h src/LauncherWindow.cpp - src/PathUtils.cpp + src/LoginRequest.h + src/LoginRequest.cpp + src/SignupRequest.h + src/SignupRequest.cpp + src/UserSettingsRequest.h + src/UserSettingsRequest.cpp src/PathUtils.h + src/PathUtils.cpp src/Unzipper.h src/Unzipper.cpp src/Helper.h @@ -159,7 +165,7 @@ set(TARGET_NAME ${PROJECT_NAME}) set_packaging_parameters() if (WIN32) - add_executable(${PROJECT_NAME} ${src_files} build/resources.qrc) + add_executable(${PROJECT_NAME} ${src_files} ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) elseif (APPLE) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index c18d14b314..3c98cebd33 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -47,7 +47,7 @@ Item { font.family: "Graphik" font.pixelSize: 14 color: "#C4C4C4" - text: "Use the email address that you regisetered with." + text: "Use the email address that you regisetered with. " + LauncherState.lastSignupError anchors { left: root.left leftMargin: root.marginLeft @@ -57,7 +57,7 @@ Item { } HFTextField { - id: organization + id: email width: 430 height: 50 font.family: "Graphik" @@ -83,7 +83,7 @@ Item { color: "#7e8c81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { - top: organization.bottom + top: email.bottom left: root.left leftMargin: root.marginLeft topMargin: 18 @@ -157,7 +157,7 @@ Item { topMargin: 21 } - onClicked: LauncherState.login(username.text, passwordField.text) + onClicked: LauncherState.signup(email.text, username.text, passwordField.text, displayName.text) } @@ -181,7 +181,7 @@ Item { onClicked: { console.log("clicked"); - root.parent.source = PathUtils.resourcePath("qml/Login.qml"); + LauncherState.gotoLogin(); } } } diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 9d468fc5c8..deb21ad085 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -109,7 +109,6 @@ Item { placeholderText: "Display name" color: "#7E8C81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) - echoMode: TextInput.Password anchors { top: displayText.bottom horizontalCenter: instruction.horizontalCenter @@ -132,7 +131,7 @@ Item { topMargin: 25 } - onClicked: LauncherState.login(username.text, password.text) + onClicked: LauncherState.login(username.text, password.text, displayName.text) } Text { @@ -155,7 +154,7 @@ Item { onClicked: { console.log("clicked"); - root.parent.source = PathUtils.resourcePath("qml/Login.qml"); + LauncherState.gotoSignup(); } } } diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index f056676d43..729824da3f 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -25,3 +25,47 @@ void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPat exit(0); } } + +QString getHTTPUserAgent() { +#if defined(Q_OS_WIN) + return "HQLauncher/fixme (Windows)"; +#elif defined(Q_OS_MACOS) + return "HQLauncher/fixme (MacOS)"; +#else +#error Unsupported platform +#endif +} + +const QString& getInterfaceSharedMemoryName() { + static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME"); + return applicationName; +} + +//#ifdef Q_OS_WIN +//#include +//#include +//#include +//#include +//#include +//#include +// +//bool isProcessRunning(const wchar_t *processName, int& processID) { +// bool exists = false; +// PROCESSENTRY32 entry; +// entry.dwSize = sizeof(PROCESSENTRY32); +// +// HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); +// +// if (Process32First(snapshot, &entry)) { +// while (Process32Next(snapshot, &entry)) { +// if (!_wcsicmp(entry.szExeFile, processName)) { +// exists = true; +// processID = entry.th32ProcessID; +// break; +// } +// } +// } +// CloseHandle(snapshot); +// return exists; +//} +//#endif diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 7d1f54a949..a0b4eb55fe 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -1,6 +1,14 @@ #include #include +//#define USE_STAGING + +#ifdef USE_STAGING +const QString METAVERSE_API_DOMAIN{ "https://staging.highfidelity.com" }; +#else +const QString METAVERSE_API_DOMAIN{ "https://metaverse.highfidelity.com" }; +#endif + void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); @@ -11,3 +19,11 @@ void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& ne #ifdef Q_OS_MAC bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); #endif + +QString getHTTPUserAgent(); + +const QString& getInterfaceSharedMemoryName(); + +//#ifdef Q_OS_WIN +//bool isProcessRunning(const wchar_t *processName, int& processID) { +//#endif diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index cb3671f274..bc5185d9f6 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -9,7 +9,7 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr // TODO Fix parameters QString params = "--url \"" + homePath + "\"" + " --setBookmark \"hqhome=" + homePath + "\"" - + " --defaultScriptsOverride \"" + defaultScriptsPath + "\"" + + " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\"" + " --displayName \"" + displayName + "\"" + " --cache \"" + contentCachePath + "\""; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 6b5f8bd35c..3901a5c4ab 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -30,8 +30,6 @@ #include -const QString METAVERSE_API_URL{ "https://metaverse.highfidelity.com" }; -const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; QString LauncherState::getContentCachePath() const { return _launcherDirectory.filePath("cache"); @@ -70,7 +68,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) { } static const std::array QML_FILE_FOR_UI_STATE = - { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", + { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml", "DisplayName.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { @@ -82,7 +80,7 @@ void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { } } -void LauncherState::ASSERT_STATE(std::vector states) { +void LauncherState::ASSERT_STATE(const std::vector& states) { for (auto state : states) { if (_applicationState == state) { return; @@ -120,6 +118,9 @@ LauncherState::UIState LauncherState::getUIState() const { case ApplicationState::WaitingForLogin: case ApplicationState::RequestingLogin: return LOGIN_SCREEN; + case ApplicationState::WaitingForSignup: + case ApplicationState::RequestingSignup: + return SIGNUP_SCREEN; case ApplicationState::DownloadingClient: case ApplicationState::InstallingClient: case ApplicationState::DownloadingContentCache: @@ -240,33 +241,11 @@ void LauncherState::getCurrentClientVersion() { QProcess client; QEventLoop loop; - //connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit); - connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit); - /* - connect(&client, QOverload::of(&QProcess::finished), [&]() { - qDebug() << "Finished"; - }); - connect(&client, &QProcess::errorOccurred, [&](QProcess::ProcessError err) { - qDebug() << "Error occurred" << err << client.error(); - }); - connect(&client, &QProcess::started, [&]() { - qDebug() << "Started"; - }); - connect(&client, &QProcess::stateChanged, [&]() { - qDebug() << "State changed " << client.state(); - }); - */ + connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit, Qt::QueuedConnection); + connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit, Qt::QueuedConnection); - //qDebug() << "Starting client"; client.start(getClientExecutablePath(), { "--version" }); - //qDebug() << "Started" << client.error(); - - if (client.state() != QProcess::NotRunning) { - //qDebug() << "Starting loop"; - loop.exec(); - } else { - qDebug() << "Not waiting for client, there was an error starting it: " << client.error(); - } + loop.exec(); // TODO Handle errors auto output = client.readAllStandardOutput(); @@ -282,132 +261,140 @@ void LauncherState::getCurrentClientVersion() { } qDebug() << "Current client version is: " << _currentClientVersion; - setApplicationState(ApplicationState::WaitingForLogin); + setApplicationState(ApplicationState::WaitingForSignup); } -QString getUserAgent() { -#if defined(Q_OS_WIN) - return "HQLauncher/fixme (Windows)"; -#elif defined(Q_OS_MACOS) - return "HQLauncher/fixme (MacOS)"; -#else -#error Unsupported platform -#endif + +void LauncherState::gotoSignup() { + if (_applicationState == ApplicationState::WaitingForLogin) { + setApplicationState(ApplicationState::WaitingForSignup); + } else { + qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState; + } } -void LauncherState::login(QString username, QString password) { +void LauncherState::gotoLogin() { + if (_applicationState == ApplicationState::WaitingForSignup) { + setApplicationState(ApplicationState::WaitingForLogin); + } else { + qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState; + } +} + +void LauncherState::signup(QString email, QString username, QString password, QString displayName) { + ASSERT_STATE(ApplicationState::WaitingForSignup); + + _username = username; + _password = password; + + setApplicationState(ApplicationState::RequestingSignup); + + auto signupRequest = new SignupRequest(); + + _displayName = displayName; + + { + _lastSignupError = SignupRequest::Error::None; + emit lastSignupErrorChanged(); + } + + QObject::connect(signupRequest, &SignupRequest::finished, this, [this, signupRequest] { + signupRequest->deleteLater(); + + + _lastSignupError = signupRequest->getError(); + emit lastSignupErrorChanged(); + + if (_lastSignupError != SignupRequest::Error::None) { + setApplicationStateError("Failed to sign up"); + return; + } + + setApplicationState(ApplicationState::RequestingLoginAfterSignup); + + // After successfully signing up, attempt to login + auto loginRequest = new LoginRequest(); + + connect(loginRequest, &LoginRequest::finished, this, [this, loginRequest]() { + ASSERT_STATE(ApplicationState::RequestingLoginAfterSignup); + + loginRequest->deleteLater(); + + auto err = loginRequest->getError(); + if (err != LoginRequest::Error::None) { + setApplicationStateError("Failed to login"); + return; + } + + + _loginResponse = loginRequest->getToken(); + _loginTokenResponse = loginRequest->getRawToken(); + + requestSettings(); + }); + + setApplicationState(ApplicationState::RequestingLoginAfterSignup); + loginRequest->send(_networkAccessManager, _username, _password); + }); + signupRequest->send(_networkAccessManager, email, username, password); +} + + +void LauncherState::login(QString username, QString password, QString displayName) { ASSERT_STATE(ApplicationState::WaitingForLogin); setApplicationState(ApplicationState::RequestingLogin); + _displayName = displayName; + qDebug() << "Got login: " << username << password; - auto request = new QNetworkRequest(QUrl(METAVERSE_API_URL + "/oauth/token")); + auto request = new LoginRequest(); - request->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); - request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QUrlQuery query; - query.addQueryItem("grant_type", "password"); - query.addQueryItem("username", username); - query.addQueryItem("password", password); - query.addQueryItem("scope", "owner"); + connect(request, &LoginRequest::finished, this, [this, request]() { + ASSERT_STATE(ApplicationState::RequestingLogin); - auto reply = _networkAccessManager.post(*request, query.toString().toUtf8()); - QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply); -} + request->deleteLater(); -Q_INVOKABLE void LauncherState::receivedLoginReply() { - ASSERT_STATE(ApplicationState::RequestingLogin); + auto err = request->getError(); + if (err != LoginRequest::Error::None) { + setApplicationStateError("Failed to login"); + return; + } - // TODO Check for errors - auto reply = static_cast(sender()); + _loginResponse = request->getToken(); + _loginTokenResponse = request->getRawToken(); - if (reply->error()) { - setApplicationState(ApplicationState::UnexpectedError); - return; - } + requestSettings(); + }); - auto data = reply->readAll(); - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(data, &parseError); - auto root = doc.object(); - - if (!root.contains("access_token") - || !root.contains("token_type") - || !root.contains("expires_in") - || !root.contains("refresh_token") - || !root.contains("scope") - || !root.contains("created_at")) { - - setApplicationState(ApplicationState::UnexpectedError); - return; - } - - _loginResponse.accessToken = root["access_token"].toString(); - _loginResponse.refreshToken = root["refresh_token"].toString(); - _loginResponse.tokenType = root["token_type"].toString(); - - qDebug() << "Got response for login: " << data; - _loginTokenResponse = data; - - requestSettings(); + request->send(_networkAccessManager, username, password); } void LauncherState::requestSettings() { // TODO Request settings if already logged in + qDebug() << "Requesting settings"; - QUrl lockerURL = METAVERSE_API_URL; - lockerURL.setPath("/api/v1/user/locker"); + auto request = new UserSettingsRequest(); - auto lockerRequest = new QNetworkRequest(lockerURL); - lockerRequest->setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - lockerRequest->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); - lockerRequest->setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(_loginResponse.accessToken).toUtf8()); - - QNetworkReply* lockerReply = _networkAccessManager.get(*lockerRequest); - connect(lockerReply, &QNetworkReply::finished, this, &LauncherState::receivedSettingsReply); -} - -void LauncherState::receivedSettingsReply() { - auto reply = static_cast(sender()); - qDebug() << "Got reply: " << reply->error(); - if (reply->error()) { - setApplicationState(ApplicationState::UnexpectedError); - return; - } - auto data = reply->readAll(); - qDebug() << "Settings: " << data; - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(data, &parseError); - - if (parseError.error != QJsonParseError::NoError) { - qDebug() << "Error parsing settings"; - setApplicationStateError("Error retreiving settings"); - return; - } - - auto root = doc.object(); - if (root["status"] != "success") { - qDebug() << "Status is not \"success\""; - setApplicationStateError("Error retreiving settings"); - return; - } - - _homeLocation = "hifi://hq"; - if (root["data"].toObject().contains("home_location")) { - auto homeLocation = root["data"].toObject()["home_location"]; - if (homeLocation.isString()) { - _homeLocation = homeLocation.toString(); + connect(request, &UserSettingsRequest::finished, this, [this, request]() { + auto userSettings = request->getUserSettings(); + if (userSettings.homeLocation.isEmpty()) { + _homeLocation = "hifi://hq"; + _contentCacheURL = ""; + } else { + _homeLocation = userSettings.homeLocation; auto host = QUrl(_homeLocation).host(); _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; - qDebug() << "Home location is: " << _homeLocation; - qDebug() << "Content cache url is: " << _contentCacheURL; } - } - //qDebug() << "Home:" << _homeLocation << QUrl(_homeLocation).host(); + qDebug() << "Home location is: " << _homeLocation; + qDebug() << "Content cache url is: " << _contentCacheURL; - downloadClient(); + downloadClient(); + }); + + request->send(_networkAccessManager, _loginResponse); } void LauncherState::downloadClient() { @@ -683,10 +670,9 @@ void LauncherState::launchClient() { defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js"); #endif - QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); - ::launchClient(clientPath, _homeLocation, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse); + ::launchClient(clientPath, _homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse); } void LauncherState::setApplicationStateError(QString errorMessage) { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 36dab7d98d..16137e7b87 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -7,6 +7,10 @@ #include #include +#include "LoginRequest.h" +#include "SignupRequest.h" +#include "UserSettingsRequest.h" + struct Build { QString tag; int latestVersion; @@ -22,18 +26,13 @@ struct LatestBuilds { Build launcherBuild; }; -struct LoginResponse { - QString accessToken; - QString tokenType; - QString refreshToken; -}; - class LauncherState : public QObject { Q_OBJECT Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged); Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged); Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged); + Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged); public: LauncherState(); @@ -41,6 +40,7 @@ public: enum UIState { SPLASH_SCREEN = 0, + SIGNUP_SCREEN, LOGIN_SCREEN, DISPLAY_NAME_SCREEN, DOWNLOAD_SCREEN, @@ -60,6 +60,10 @@ public: WaitingForLogin, RequestingLogin, + WaitingForSignup, + RequestingSignup, + RequestingLoginAfterSignup, + DownloadingClient, DownloadingLauncher, DownloadingContentCache, @@ -85,7 +89,7 @@ public: Q_INVOKABLE QString getCurrentUISource() const; void ASSERT_STATE(LauncherState::ApplicationState state); - void ASSERT_STATE(std::vector states); + void ASSERT_STATE(const std::vector& states); static void declareQML(); @@ -98,17 +102,21 @@ public: void setApplicationState(ApplicationState state); ApplicationState getApplicationState() const; + Q_INVOKABLE void gotoSignup(); + Q_INVOKABLE void gotoLogin(); + // Request builds void requestBuilds(); Q_INVOKABLE void receivedBuildsReply(); + // Signup + Q_INVOKABLE void signup(QString email, QString username, QString password, QString displayName); // Login - Q_INVOKABLE void login(QString username, QString password); - Q_INVOKABLE void receivedLoginReply(); + Q_INVOKABLE void login(QString username, QString password, QString displayName); + // Request Settings void requestSettings(); - Q_INVOKABLE void receivedSettingsReply(); // Launcher void downloadLauncher(); @@ -132,6 +140,7 @@ signals: void uiStateChanged(); void applicationStateChanged(); void downloadProgressChanged(); + void lastSignupErrorChanged(); private slots: void clientDownloadComplete(); @@ -154,8 +163,10 @@ private: // Application State ApplicationState _applicationState { ApplicationState::Init }; - LoginResponse _loginResponse; + LoginToken _loginResponse; LastLoginError _lastLoginError { NONE }; + SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; + QString _displayName; QString _applicationErrorMessage; QString _currentClientVersion; QString _buildTag { QString::null }; @@ -166,5 +177,8 @@ private: QFile _launcherZipFile; QFile _contentZipFile; + QString _username; + QString _password; + float _downloadProgress { 0 }; }; diff --git a/launchers/qt/src/LoginRequest.cpp b/launchers/qt/src/LoginRequest.cpp new file mode 100644 index 0000000000..488b5bbfa5 --- /dev/null +++ b/launchers/qt/src/LoginRequest.cpp @@ -0,0 +1,73 @@ +#include "LoginRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include + +void LoginRequest::send(QNetworkAccessManager& nam, QString username, QString password) { + QNetworkRequest request(QUrl(METAVERSE_API_DOMAIN + "/oauth/token")); + + request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QUrlQuery query; + query.addQueryItem("grant_type", "password"); + query.addQueryItem("username", username); + query.addQueryItem("password", password); + query.addQueryItem("scope", "owner"); + + auto reply = nam.post(request, query.toString().toUtf8()); + QObject::connect(reply, &QNetworkReply::finished, this, &LoginRequest::receivedResponse); +} + +void LoginRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + if (reply->error()) { + qDebug() << "Error logging in: " << reply->readAll(); + _error = Error::Unknown; + emit finished(); + //setApplicationState(ApplicationState::UnexpectedError); + return; + } + + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing response for login" << data; + _error = Error::BadResponse; + emit finished(); + //setApplicationStateError("Failed to login"); + return; + } + + auto root = doc.object(); + + if (!root.contains("access_token") + || !root.contains("token_type") + || !root.contains("expires_in") + || !root.contains("refresh_token") + || !root.contains("scope") + || !root.contains("created_at")) { + + _error = Error::BadUsernameOrPassword; + emit finished(); + return; + } + + _token.accessToken = root["access_token"].toString(); + _token.refreshToken = root["refresh_token"].toString(); + _token.tokenType = root["token_type"].toString(); + + qDebug() << "Got response for login: " << data; + _rawLoginToken = data; + + emit finished(); +} diff --git a/launchers/qt/src/LoginRequest.h b/launchers/qt/src/LoginRequest.h new file mode 100644 index 0000000000..07b28ff794 --- /dev/null +++ b/launchers/qt/src/LoginRequest.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +struct LoginToken { + QString accessToken; + QString tokenType; + QString refreshToken; +}; + +class LoginRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + BadResponse, + BadUsernameOrPassword + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam, QString username, QString password); + Error getError() const { return _error; } + + // The token is only valid if the request has finished without error + QString getRawToken() const { return _rawLoginToken; } + LoginToken getToken() const { return _token; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; + + QString _rawLoginToken; + LoginToken _token; +}; + diff --git a/launchers/qt/src/SignupRequest.cpp b/launchers/qt/src/SignupRequest.cpp new file mode 100644 index 0000000000..a6961c0094 --- /dev/null +++ b/launchers/qt/src/SignupRequest.cpp @@ -0,0 +1,79 @@ +#include "SignupRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include + +void SignupRequest::send(QNetworkAccessManager& nam, QString email, QString username, QString password) { + if (_state != State::Unsent) { + qDebug() << "Error: Trying to send signuprequest, but not unsent"; + return; + } + + _state = State::Sending; + + QUrl signupURL { METAVERSE_API_DOMAIN }; + signupURL.setPath("/api/v1/user/channel_user"); + QNetworkRequest request(signupURL); + + request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QUrlQuery query; + query.addQueryItem("email", email); + query.addQueryItem("username", username); + query.addQueryItem("password", password); + + auto reply = nam.post(request, query.toString().toUtf8()); + QObject::connect(reply, &QNetworkReply::finished, this, &SignupRequest::receivedResponse); +} + +void SignupRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + if (reply->error() && reply->size() == 0) { + qDebug() << "Error signing up: " << reply->error() << reply->readAll(); + _error = Error::Unknown; + emit finished(); + return; + } + + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing response for signup " << data; + _error = Error::Unknown; + emit finished(); + return; + } + + auto root = doc.object(); + + auto status = root["status"]; + if (status.isString() && status.toString() != "success") { + auto error = root["data"].toString(); + + _error = Error::Unknown; + + if (error == "no_such_email") { + _error = Error::NoSuchEmail; + } else if (error == "user_profile_already_completed") { + _error = Error::UserProfileAlreadyCompleted; + } else if (error == "bad_username") { + _error = Error::BadUsername; + } else if (error == "existing_username") { + _error = Error::ExistingUsername; + } else if (error == "bad_password") { + _error = Error::BadPassword; + } + } + + emit finished(); +} diff --git a/launchers/qt/src/SignupRequest.h b/launchers/qt/src/SignupRequest.h new file mode 100644 index 0000000000..255c0c9034 --- /dev/null +++ b/launchers/qt/src/SignupRequest.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +class SignupRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + NoSuchEmail, + UserProfileAlreadyCompleted, + BadUsername, + ExistingUsername, + BadPassword, + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam, QString email, QString username, QString password); + Error getError() const { return _error; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; +}; diff --git a/launchers/qt/src/UserSettingsRequest.cpp b/launchers/qt/src/UserSettingsRequest.cpp new file mode 100644 index 0000000000..62f7d5b4fb --- /dev/null +++ b/launchers/qt/src/UserSettingsRequest.cpp @@ -0,0 +1,67 @@ +#include "UserSettingsRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include + +const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; + +void UserSettingsRequest::send(QNetworkAccessManager& nam, const LoginToken& token) { + _state = State::Sending; + + QUrl lockerURL = METAVERSE_API_DOMAIN; + lockerURL.setPath("/api/v1/user/locker"); + + QNetworkRequest lockerRequest(lockerURL); + lockerRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + lockerRequest.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); + lockerRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(token.accessToken).toUtf8()); + + QNetworkReply* lockerReply = nam.get(lockerRequest); + connect(lockerReply, &QNetworkReply::finished, this, &UserSettingsRequest::receivedResponse); +} + +void UserSettingsRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + qDebug() << "Got reply: " << reply->error(); + if (reply->error()) { + _error = Error::Unknown; + emit finished(); + return; + } + + auto data = reply->readAll(); + qDebug() << "Settings: " << data; + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing settings"; + _error = Error::Unknown; + emit finished(); + return; + } + + auto root = doc.object(); + if (root["status"] != "success") { + qDebug() << "Status is not \"success\""; + _error = Error::Unknown; + emit finished(); + return; + } + + if (root["data"].toObject().contains("home_location")) { + auto homeLocation = root["data"].toObject()["home_location"]; + if (homeLocation.isString()) { + _userSettings.homeLocation = homeLocation.toString(); + } + } + + emit finished(); +} diff --git a/launchers/qt/src/UserSettingsRequest.h b/launchers/qt/src/UserSettingsRequest.h new file mode 100644 index 0000000000..5827364377 --- /dev/null +++ b/launchers/qt/src/UserSettingsRequest.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "LoginRequest.h" + +struct UserSettings { + QString homeLocation{ QString::null }; +}; + +class UserSettingsRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam, const LoginToken& token); + Error getError() const { return _error; } + + UserSettings getUserSettings() const { return _userSettings; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; + + UserSettings _userSettings; +}; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index df5987dd62..528e984306 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -1,5 +1,7 @@ #include +#include + #include "LauncherWindow.h" #include "Launcher.h" #include @@ -19,7 +21,7 @@ Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); -bool hasSuffix(const std::string path, const std::string suffix) { +bool hasSuffix(const std::string& path, const std::string& suffix) { if (path.substr(path.find_last_of(".") + 1) == suffix) { return true; } @@ -61,6 +63,12 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); + +#ifdef Q_OS_WIN + //QSharedMemory sharedMemory{ applicationName }; + //instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly); +#endif + Launcher launcher(argc, argv); return launcher.exec(); From 18fde053cb48f7f5abb6bd20d3636f9c6a59bbdb Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 1 Oct 2019 14:41:46 -0700 Subject: [PATCH 048/203] fixing last mac warning and taskbar icon for windows --- launchers/qt/CMakeLists.txt | 6 +++- launchers/qt/cmake/templates/Icon.rc.in | 1 + launchers/qt/deps/miniz/miniz.cpp | 1 + launchers/qt/resources/images/Launcher.rc2 | 13 +++++++ launchers/qt/resources/images/interface.ico | Bin 0 -> 26687 bytes launchers/qt/src/Helper.h | 3 ++ launchers/qt/src/Helper_windows.cpp | 34 ++++++++++++++++++ launchers/qt/src/Launcher.cpp | 2 +- .../qt/src/LauncherInstaller_windows.cpp | 25 +++++++++++++ launchers/qt/src/LauncherInstaller_windows.h | 2 ++ launchers/qt/src/LauncherWindow.h | 2 ++ launchers/qt/src/main.cpp | 2 -- 12 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 launchers/qt/cmake/templates/Icon.rc.in create mode 100644 launchers/qt/resources/images/Launcher.rc2 create mode 100644 launchers/qt/resources/images/interface.ico diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 57f900140c..50ea3362e1 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -165,7 +165,11 @@ set(TARGET_NAME ${PROJECT_NAME}) set_packaging_parameters() if (WIN32) - add_executable(${PROJECT_NAME} ${src_files} ${RES_SOURCES}) + set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/resources/images/interface.ico") + message(${CONFIGURE_ICON_PATH}) + set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT}) + add_executable(${PROJECT_NAME} WIN32 ${src_files} ${RES_SOURCES} ${CONFIGURE_ICON_RC_OUTPUT}) elseif (APPLE) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) diff --git a/launchers/qt/cmake/templates/Icon.rc.in b/launchers/qt/cmake/templates/Icon.rc.in new file mode 100644 index 0000000000..8cd04a60de --- /dev/null +++ b/launchers/qt/cmake/templates/Icon.rc.in @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "@CONFIGURE_ICON_PATH@" \ No newline at end of file diff --git a/launchers/qt/deps/miniz/miniz.cpp b/launchers/qt/deps/miniz/miniz.cpp index 96d7cc106a..a7e3e3d520 100644 --- a/launchers/qt/deps/miniz/miniz.cpp +++ b/launchers/qt/deps/miniz/miniz.cpp @@ -3228,6 +3228,7 @@ struct mz_zip_internal_state_tag static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) { MZ_ASSERT(index < pArray->m_size); + (void)(pArray); return index; } #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] diff --git a/launchers/qt/resources/images/Launcher.rc2 b/launchers/qt/resources/images/Launcher.rc2 new file mode 100644 index 0000000000..098b026372 --- /dev/null +++ b/launchers/qt/resources/images/Launcher.rc2 @@ -0,0 +1,13 @@ +// +// Launcher.rc2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/launchers/qt/resources/images/interface.ico b/launchers/qt/resources/images/interface.ico new file mode 100644 index 0000000000000000000000000000000000000000..09a97956a72607b8d9883a531eed9d04414e0b53 GIT binary patch literal 26687 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 literal 0 HcmV?d00001 diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 65f68ec41d..a72b9a2d7a 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -21,4 +21,7 @@ bool isLauncherAlreadyRunning(); #ifdef Q_OS_WIN HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs = (LPCSTR)""); +bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value); +bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value); +bool deleteRegistryKey(const std::string& regPath); #endif diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index 5295943a4e..a61db77f85 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -6,6 +6,8 @@ #include "objbase.h" #include "objidl.h" #include "shlguid.h" +#include +#include #include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath, @@ -113,3 +115,35 @@ HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszD CoUninitialize(); return SUCCEEDED(hres); } + +bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value) { + HKEY key; + auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); + if (status == ERROR_SUCCESS) { + status = RegSetValueExA(key, name.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)(value.size() + 1)); + return (bool) (status == ERROR_SUCCESS); + } + RegCloseKey(key); + return false; +} + +bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value) { + HKEY key; + auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); + if (status == ERROR_SUCCESS) { + status = RegSetValueExA(key, name.c_str(), 0, REG_DWORD, (const BYTE*)&value, sizeof(value)); + return (bool) TRUE; + } + RegCloseKey(key); + return false; +} + +bool deleteRegistryKey(const std::string& regPath) { + TCHAR szDelKey[MAX_PATH * 2]; + StringCchCopy(szDelKey, MAX_PATH * 2, regPath.c_str()); + auto status = RegDeleteKey(HKEY_CURRENT_USER, szDelKey); + if (status == ERROR_SUCCESS) { + return (bool) TRUE; + } + return false; +} diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 0b51328aef..af90507db6 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -13,7 +13,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); - _launcherWindow->setFlags(Qt::FramelessWindowHint); + _launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window); LauncherState::declareQML(); _launcherWindow->setSource(QUrl(PathUtils::resourcePath("qml/root.qml"))); _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index ea35e86c64..1bbceb61e7 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -70,6 +70,8 @@ void LauncherInstaller::install() { createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)appStartLinkPath.toStdString().c_str(), (LPCSTR)("Click to Setup and Launch HQ")); + + createApplicationRegistryKeys(); } else { qDebug() << "FAILED!!!!!!!"; } @@ -100,3 +102,26 @@ void LauncherInstaller::uninstall() { QFile::remove(desktopAppLinkPath); } } + + +void LauncherInstaller::createApplicationRegistryKeys() { + const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; + bool success = insertRegistryKey(REGISTRY_PATH, "DisplayName", "HQ"); + std::string installPath = _launcherInstallDir.absolutePath().toStdString(); + success = insertRegistryKey(REGISTRY_PATH, "InstallLocation", installPath); + std::string applicationExe = installPath + "/HQ Launcher.exe"; + std::string uninstallPath = '"' + applicationExe + '"' + " --uninstall"; + success = insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath); + success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", "DEV"); + success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); + success = insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); + //success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallDate", LauncherUtils::cStringToStd(CTime::GetCurrentTime().Format("%Y%m%d"))); + //success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size); + success = insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1); + success = insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1); + + qDebug() << "--------: " << success; +} + +void LauncherInstaller::deleteApplicationRegistryKeys() { +} diff --git a/launchers/qt/src/LauncherInstaller_windows.h b/launchers/qt/src/LauncherInstaller_windows.h index 8791f9395f..6ddd74f53f 100644 --- a/launchers/qt/src/LauncherInstaller_windows.h +++ b/launchers/qt/src/LauncherInstaller_windows.h @@ -11,7 +11,9 @@ public: bool runningOutsideOfInstallDir(); private: void createShortcuts(); + void createApplicationRegistryKeys(); void deleteShortcuts(); + void deleteApplicationRegistryKeys(); QDir _launcherInstallDir; QDir _launcherApplicationsDir; diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h index 02012dc342..79651e9e5e 100644 --- a/launchers/qt/src/LauncherWindow.h +++ b/launchers/qt/src/LauncherWindow.h @@ -5,6 +5,8 @@ class LauncherWindow : public QQuickView { public: + LauncherWindow() = default; + ~LauncherWindow() = default; void keyPressEvent(QKeyEvent* event) override; void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index db69bfafe3..2f8e26d500 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -35,8 +35,6 @@ int main(int argc, char *argv[]) { return 0; } closeInterfaceIfRunning(); - // waitForInterfaceToClose(); - // auto updater if (argc == 3) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { swapLaunchers(argv[1], argv[2]); From f236b64b06eab4475d5ee833a7c5a62515df0481 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 2 Oct 2019 14:22:50 -0700 Subject: [PATCH 049/203] Add BuildsRequest and config.json support to qt launcher --- launchers/qt/CMakeLists.txt | 17 +-- launchers/qt/src/BuildsRequest.cpp | 115 ++++++++++++++++++ launchers/qt/src/BuildsRequest.h | 54 +++++++++ launchers/qt/src/LauncherState.cpp | 185 ++++++++++++----------------- launchers/qt/src/LauncherState.h | 24 ++-- launchers/qt/src/main.cpp | 2 - 6 files changed, 267 insertions(+), 130 deletions(-) create mode 100644 launchers/qt/src/BuildsRequest.cpp create mode 100644 launchers/qt/src/BuildsRequest.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index e1d3281c4e..d616b90e69 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -136,6 +136,8 @@ set(src_files src/LoginRequest.cpp src/SignupRequest.h src/SignupRequest.cpp + src/BuildsRequest.h + src/BuildsRequest.cpp src/UserSettingsRequest.h src/UserSettingsRequest.cpp src/PathUtils.h @@ -231,13 +233,14 @@ if (APPLE) endif() - if (LAUNCHER_SOURCE_TREE_RESOURCES) - target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/") - message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/") - else() - target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/") - message("Use resource.rcc path: qrc:/") - endif() + +if (LAUNCHER_SOURCE_TREE_RESOURCES) + target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/") + message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/") +else() + target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/") + message("Use resource.rcc path: qrc:/") +endif() target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") diff --git a/launchers/qt/src/BuildsRequest.cpp b/launchers/qt/src/BuildsRequest.cpp new file mode 100644 index 0000000000..e693377dc7 --- /dev/null +++ b/launchers/qt/src/BuildsRequest.cpp @@ -0,0 +1,115 @@ +#include "BuildsRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include +#include +#include + +bool Builds::getBuild(QString tag, Build* outBuild) { + if (tag.isNull()) { + tag = defaultTag; + } + + for (auto& build : builds) { + if (build.tag == tag) { + *outBuild = build; + return true; + } + } + + return false; +} + +void BuildsRequest::send(QNetworkAccessManager& nam) { + QString latestBuildRequestUrl { "https://thunder.highfidelity.com/builds/api/tags/latest/?format=json" }; + QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); + + if (processEnvironment.contains("HQ_LAUNCHER_BUILDS_URL")) { + latestBuildRequestUrl = processEnvironment.value("HQ_LAUNCHER_BUILDS_URL"); + } + + QNetworkRequest request{ QUrl(latestBuildRequestUrl) }; + auto reply = nam.get(request); + + QObject::connect(reply, &QNetworkReply::finished, this, &BuildsRequest::receivedResponse); +} + +void BuildsRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + if (reply->error()) { + qDebug() << "Error getting builds from thunder: " << reply->errorString(); + _error = Error::Unknown; + emit finished(); + return; + } else { + qDebug() << "Builds reply has been received"; + + auto data = reply->readAll(); + + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error) { + qDebug() << "Error parsing response from thunder: " << data; + _error = Error::Unknown; + } else { + auto root = doc.object(); + if (!root.contains("default_tag")) { + //setApplicationState(ApplicationState::UnexpectedError); + _error = Error::MissingDefaultTag; + emit finished(); + return; + } + + _latestBuilds.defaultTag = root["default_tag"].toString(); + + auto results = root["results"]; + if (!results.isArray()) { + //setApplicationState(ApplicationState::UnexpectedError); + _error = Error::MalformedResponse; + emit finished(); + return; + } + + for (auto result : results.toArray()) { + auto entry = result.toObject(); + Build build; + build.tag = entry["name"].toString(); + build.latestVersion = entry["latest_version"].toInt(); + build.buildNumber = entry["build_number"].toInt(); +#ifdef Q_OS_WIN + build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString(); +#elif defined(Q_OS_MACOS) + build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString(); +#else +#error "Launcher is only supported on Windows and Mac OS" +#endif + _latestBuilds.builds.push_back(build); + } + + auto launcherResults = root["launcher"].toObject(); + + Build launcherBuild; + launcherBuild.latestVersion = launcherResults["version"].toInt(); + +#ifdef Q_OS_WIN + launcherBuild.installerZipURL = launcherResults["windows"].toObject()["url"].toString(); +#elif defined(Q_OS_MACOS) + launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString(); +#else +#error "Launcher is only supported on Windows and Mac OS" +#endif + _latestBuilds.launcherBuild = launcherBuild; + } + } + + + emit finished(); +} diff --git a/launchers/qt/src/BuildsRequest.h b/launchers/qt/src/BuildsRequest.h new file mode 100644 index 0000000000..865d375d2a --- /dev/null +++ b/launchers/qt/src/BuildsRequest.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +struct Build { + QString tag{ QString::null }; + int latestVersion{ 0 }; + int buildNumber{ 0 }; + QString installerZipURL{ QString::null }; +}; + +struct Builds { + bool getBuild(QString tag, Build* outBuild); + + QString defaultTag; + std::vector builds; + Build launcherBuild; +}; + +class BuildsRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + MalformedResponse, + MissingDefaultTag, + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam); + Error getError() const { return _error; } + + const Builds& getLatestBuilds() const { return _latestBuilds; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; + + Builds _latestBuilds; +}; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 976dbc22fb..7d00acd453 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -27,6 +27,10 @@ #include +//#define BREAK_ON_ERROR + +const QString configLoggedInKey{ "loggedIn" }; +const QString configLauncherPathKey{ "launcherPath" }; QString LauncherState::getContentCachePath() const { return _launcherDirectory.filePath("cache"); @@ -45,23 +49,21 @@ QString LauncherState::getClientExecutablePath() const { #endif } -bool LauncherState::shouldDownloadContentCache() const { - return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); +QString LauncherState::getConfigFilePath() const { + QDir clientDirectory = getClientDirectory(); + return clientDirectory.absoluteFilePath("config.json"); } -bool LatestBuilds::getBuild(QString tag, Build* outBuild) { - if (tag.isNull()) { - tag = defaultTag; - } +QString LauncherState::getLauncherFilePath() const { +#if defined(Q_OS_WIN) + return _launcherDirectory.absoluteFilePath("launcher.exe"); +#elif defined(Q_OS_MACOS) + return _launcherDirectory.absoluteFilePath("launcher.app"); +#endif +} - for (auto& build : builds) { - if (build.tag == tag) { - *outBuild = build; - return true; - } - } - - return false; +bool LauncherState::shouldDownloadContentCache() const { + return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); } static const std::array QML_FILE_FOR_UI_STATE = @@ -70,7 +72,7 @@ static const std::array QML_FILE_ void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { if (_applicationState != state) { -#ifdef Q_OS_WIN +#ifdef BREAK_ON_ERROR __debugbreak(); #endif setApplicationState(ApplicationState::UnexpectedError); @@ -84,8 +86,8 @@ void LauncherState::ASSERT_STATE(const std::vector(sender()); +void LauncherState::requestBuilds() { + ASSERT_STATE(ApplicationState::Init); + setApplicationState(ApplicationState::RequestingBuilds); - ASSERT_STATE(ApplicationState::RequestingBuilds); + auto request = new BuildsRequest(); - if (reply->error()) { - qDebug() << "Error getting builds from thunder: " << reply->errorString(); - } else { - qDebug() << "Builds reply has been received"; - auto data = reply->readAll(); - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(data, &parseError); - if (parseError.error) { - qDebug() << "Error parsing response from thunder: " << data; - } else { - auto root = doc.object(); - if (!root.contains("default_tag")) { - setApplicationState(ApplicationState::UnexpectedError); - return; - } - - _latestBuilds.defaultTag = root["default_tag"].toString(); - - auto results = root["results"]; - if (!results.isArray()) { - setApplicationState(ApplicationState::UnexpectedError); - return; - } - - for (auto result : results.toArray()) { - auto entry = result.toObject(); - Build build; - build.tag = entry["name"].toString(); - build.latestVersion = entry["latest_version"].toInt(); - build.buildNumber = entry["build_number"].toInt(); -#ifdef Q_OS_WIN - build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString(); -#elif defined(Q_OS_MACOS) - build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString(); -#else -#error "Launcher is only supported on Windows and Mac OS" -#endif - _latestBuilds.builds.push_back(build); - } - - auto launcherResults = root["launcher"].toObject(); - - Build launcherBuild; - launcherBuild.latestVersion = launcherResults["version"].toInt(); - -#ifdef Q_OS_WIN - launcherBuild.installerZipURL = launcherResults["windows"].toObject()["url"].toString(); -#elif defined(Q_OS_MACOS) - launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString(); -#else -#error "Launcher is only supported on Windows and Mac OS" -#endif - _latestBuilds.launcherBuild = launcherBuild; + QObject::connect(request, &BuildsRequest::finished, this, [=] { + ASSERT_STATE(ApplicationState::RequestingBuilds); + if (request->getError() != BuildsRequest::Error::None) { + setApplicationStateError("Could not retrieve latest builds"); + return; } - } - if (shouldDownloadLauncher()) { - //downloadLauncher(); - } - getCurrentClientVersion(); + _latestBuilds = request->getLatestBuilds(); + + if (shouldDownloadLauncher()) { + //downloadLauncher(); + } + getCurrentClientVersion(); + }); + + request->send(_networkAccessManager); } - bool LauncherState::shouldDownloadLauncher() { return _latestBuilds.launcherBuild.latestVersion != atoi(LAUNCHER_BUILD_VERSION); } @@ -271,7 +210,29 @@ void LauncherState::getCurrentClientVersion() { } qDebug() << "Current client version is: " << _currentClientVersion; - setApplicationState(ApplicationState::WaitingForSignup); + { + auto path = getConfigFilePath(); + QFile configFile{ path }; + + if (configFile.open(QIODevice::ReadOnly)) { + QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); + auto root = doc.object(); + + _config.launcherPath = getLauncherFilePath(); + _config.loggedIn = false; + if (root.contains(configLoggedInKey)) { + _config.loggedIn = root["loggedIn"].toBool(); + } + } else { + qDebug() << "Failed to open config.json"; + } + } + + if (_config.loggedIn) { + downloadClient(); + } else { + setApplicationState(ApplicationState::WaitingForSignup); + } } @@ -337,6 +298,7 @@ void LauncherState::signup(QString email, QString username, QString password, QS } + _config.loggedIn = true; _loginResponse = loginRequest->getToken(); _loginTokenResponse = loginRequest->getRawToken(); @@ -372,6 +334,7 @@ void LauncherState::login(QString username, QString password, QString displayNam return; } + _config.loggedIn = true; _loginResponse = request->getToken(); _loginTokenResponse = request->getRawToken(); @@ -672,7 +635,17 @@ void LauncherState::launchClient() { clientPath = installDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); #endif - // TODO Get correct home path + auto path = getConfigFilePath(); + QFile configFile{ path }; + if (configFile.open(QIODevice::WriteOnly)) { + QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); + doc.setObject({ + { configLoggedInKey, _config.loggedIn }, + { configLauncherPathKey, _config.launcherPath }, + }); + configFile.write(doc.toJson()); + } + QString defaultScriptsPath; #if defined(Q_OS_WIN) defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js"); @@ -694,9 +667,9 @@ void LauncherState::setApplicationState(ApplicationState state) { qDebug() << "Changing application state: " << _applicationState << " -> " << state; if (state == ApplicationState::UnexpectedError) { - #ifdef Q_OS_WIN +#ifdef BREAK_ON_ERROR __debugbreak(); - #endif +#endif } _applicationState = state; diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index eb87d15343..4f59eb101b 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -10,20 +10,11 @@ #include "LoginRequest.h" #include "SignupRequest.h" #include "UserSettingsRequest.h" +#include "BuildsRequest.h" -struct Build { - QString tag; - int latestVersion; - int buildNumber; - QString installerZipURL; -}; - -struct LatestBuilds { - bool getBuild(QString tag, Build* outBuild); - - QString defaultTag; - std::vector builds; - Build launcherBuild; +struct LauncherConfig { + QString launcherPath{ QString::null }; + bool loggedIn{ false }; }; class LauncherState : public QObject { @@ -107,7 +98,6 @@ public: // Request builds void requestBuilds(); - Q_INVOKABLE void receivedBuildsReply(); // Signup Q_INVOKABLE void signup(QString email, QString username, QString password, QString displayName); @@ -156,13 +146,17 @@ private: QString getContentCachePath() const; QString getClientDirectory() const; QString getClientExecutablePath() const; + QString getConfigFilePath() const; + QString getLauncherFilePath() const; bool shouldDownloadLauncher(); QNetworkAccessManager _networkAccessManager; - LatestBuilds _latestBuilds; + Builds _latestBuilds; QDir _launcherDirectory; + LauncherConfig _config; + // Application State ApplicationState _applicationState { ApplicationState::Init }; LoginToken _loginResponse; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index c21e56cc24..d1de92b45a 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -20,8 +20,6 @@ Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); - - bool hasSuffix(const std::string& path, const std::string& suffix) { if (path.substr(path.find_last_of(".") + 1) == suffix) { return true; From fccc7d3c2cb0f7b589162b41ec8b7f37b69e13df Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 2 Oct 2019 14:56:33 -0700 Subject: [PATCH 050/203] Update qt launcher with interface killing, home location in config, and extra launch params --- launchers/qt/src/Helper.h | 3 +++ launchers/qt/src/Helper_windows.cpp | 36 ++++++++++++++++++++++++++++- launchers/qt/src/LauncherState.cpp | 15 ++++++++---- launchers/qt/src/LauncherState.h | 4 ++-- launchers/qt/src/main.cpp | 6 +++++ 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 8c685acc7c..a41b55b233 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -32,6 +32,9 @@ HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszD bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value); bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value); bool deleteRegistryKey(const std::string& regPath); + +BOOL isProcessRunning(const char* processName, int& processID); +BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode); #endif QString getHTTPUserAgent(); diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index 84673e85b2..fda6a455f0 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -18,7 +18,8 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr + " --setBookmark \"hqhome=" + homePath + "\"" + " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\"" + " --displayName \"" + displayName + "\"" - + " --cache \"" + contentCachePath + "\""; + + " --cache \"" + contentCachePath + "\"" + + " --suppress-settings-reset --no-launcher --no-updater"; if (!loginResponseToken.isEmpty()) { params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\""; @@ -147,3 +148,36 @@ bool deleteRegistryKey(const std::string& regPath) { } return false; } + + +BOOL isProcessRunning(const char* processName, int& processID) { + bool exists = false; + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (Process32First(snapshot, &entry)) { + while (Process32Next(snapshot, &entry)) { + if (!_stricmp(entry.szExeFile, processName)) { + exists = true; + processID = entry.th32ProcessID; + break; + } + } + } + CloseHandle(snapshot); + return exists; +} + +BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode) { + DWORD dwDesiredAccess = PROCESS_TERMINATE; + BOOL bInheritHandle = FALSE; + HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); + if (hProcess == NULL) { + return FALSE; + } + BOOL result = TerminateProcess(hProcess, uExitCode); + CloseHandle(hProcess); + return result; +} diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 7d00acd453..582b416b85 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -29,6 +29,7 @@ //#define BREAK_ON_ERROR +const QString configHomeLocationKey { "homeLocation" }; const QString configLoggedInKey{ "loggedIn" }; const QString configLauncherPathKey{ "launcherPath" }; @@ -223,6 +224,9 @@ void LauncherState::getCurrentClientVersion() { if (root.contains(configLoggedInKey)) { _config.loggedIn = root["loggedIn"].toBool(); } + if (root.contains(configHomeLocationKey)) { + _config.homeLocation = root["homeLocation"].toString(); + } } else { qDebug() << "Failed to open config.json"; } @@ -353,15 +357,15 @@ void LauncherState::requestSettings() { connect(request, &UserSettingsRequest::finished, this, [this, request]() { auto userSettings = request->getUserSettings(); if (userSettings.homeLocation.isEmpty()) { - _homeLocation = "hifi://hq"; + _config.homeLocation = "hifi://hq"; _contentCacheURL = ""; } else { - _homeLocation = userSettings.homeLocation; - auto host = QUrl(_homeLocation).host(); + _config.homeLocation = userSettings.homeLocation; + auto host = QUrl(_config.homeLocation).host(); _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; } - qDebug() << "Home location is: " << _homeLocation; + qDebug() << "Home location is: " << _config.homeLocation; qDebug() << "Content cache url is: " << _contentCacheURL; downloadClient(); @@ -640,6 +644,7 @@ void LauncherState::launchClient() { if (configFile.open(QIODevice::WriteOnly)) { QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); doc.setObject({ + { configHomeLocationKey, _config.homeLocation }, { configLoggedInKey, _config.loggedIn }, { configLauncherPathKey, _config.launcherPath }, }); @@ -655,7 +660,7 @@ void LauncherState::launchClient() { QString contentCachePath = _launcherDirectory.filePath("cache"); - ::launchClient(clientPath, _homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse); + ::launchClient(clientPath, _config.homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse); } void LauncherState::setApplicationStateError(QString errorMessage) { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 4f59eb101b..324a653854 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -13,8 +13,9 @@ #include "BuildsRequest.h" struct LauncherConfig { - QString launcherPath{ QString::null }; + QString launcherPath{ "" }; bool loggedIn{ false }; + QString homeLocation{ "" }; }; class LauncherState : public QObject { @@ -168,7 +169,6 @@ private: QString _buildTag { QString::null }; QString _contentCacheURL; QString _loginTokenResponse; - QString _homeLocation; QFile _clientZipFile; QFile _launcherZipFile; QFile _contentZipFile; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index d1de92b45a..0965a5742f 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -51,6 +51,12 @@ int main(int argc, char *argv[]) { launcherInstaller.uninstall(); return 0; } + + int interfacePID = -1; + if (isProcessRunning("interface.exe", interfacePID)) { + shutdownProcess(interfacePID, 0); + } + #endif QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); From 509545601e12329725134d90e704d6d2a29d6040 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 2 Oct 2019 15:43:32 -0700 Subject: [PATCH 051/203] finish touch up of UI --- launchers/qt/CMakeLists.txt | 7 ++- launchers/qt/resources/qml/Download.qml | 34 +++++++--- .../qt/resources/qml/DownloadFinished.qml | 4 ++ .../qml/HFBase/CreateAccountBase.qml | 5 ++ launchers/qt/resources/qml/HFBase/Error.qml | 5 ++ .../qt/resources/qml/HFBase/LoginBase.qml | 5 ++ launchers/qt/resources/qml/SplashScreen.qml | 8 ++- launchers/qt/resources/qml/root.qml | 63 ++++++++++++++++++- .../qt/src/LauncherInstaller_windows.cpp | 19 +++++- launchers/qt/src/LauncherState.cpp | 44 ++++++++----- launchers/qt/src/LauncherState.h | 14 ++++- launchers/qt/src/PathUtils.cpp | 7 ++- 12 files changed, 184 insertions(+), 31 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index e1d3281c4e..e2a8831bc0 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -16,6 +16,10 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) include("cmake/init.cmake") include("cmake/macros/SetPackagingParameters.cmake") + if(MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + endif() + if (WIN32) set(CMAKE_MFC_FLAG 1) endif() @@ -233,13 +237,14 @@ endif() if (LAUNCHER_SOURCE_TREE_RESOURCES) target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/") + target_compile_definitions(${PROJECT_NAME} PRIVATE HIFI_USE_LOCAL_FILE) message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/") else() target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/") message("Use resource.rcc path: qrc:/") endif() -target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") + target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") if (APPLE) install( diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index a6548ac3b3..8303121479 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -99,16 +99,32 @@ Item { color: "#01B2ED" } } + } - //PropertyAnimation { - //target: progressBar; - //loops: Animation.Infinite - //property: "value" - //from: 0; - //to: 1; - //duration: 5000 - //running: true - //} + Text { + width: 100 + height: 17 + + text: "High Fidelity" + font.bold: true + font.family: "Graphik" + font.pixelSize: 24 + font.letterSpacing: -1 + color: "#FFFFFF" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + anchors { + bottom: root.bottom + bottomMargin: 15 + left: root.left + leftMargin: 30 + } + } + + Component.onCompleted: { + root.parent.setStateInfoState("right"); + root.parent.setBuildInfoState("left"); } } diff --git a/launchers/qt/resources/qml/DownloadFinished.qml b/launchers/qt/resources/qml/DownloadFinished.qml index a16800fa49..ecd2e6d0c2 100644 --- a/launchers/qt/resources/qml/DownloadFinished.qml +++ b/launchers/qt/resources/qml/DownloadFinished.qml @@ -65,4 +65,8 @@ Item { } } + Component.onCompleted: { + root.parent.setStateInfoState("right"); + root.parent.setBuildInfoState("left"); + } } diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 3c98cebd33..04b76d5e36 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -206,4 +206,9 @@ Item { rightMargin: 30 } } + + Component.onCompleted: { + root.parent.setStateInfoState("right"); + root.parent.setBuildInfoState("left"); + } } diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index f488a3e9bf..09df9e39a7 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -106,4 +106,9 @@ Item { } } + Component.onCompleted: { + root.parent.setStateInfoState("left"); + root.parent.setBuildInfoState("right"); + } + } diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index deb21ad085..3a295d5fa8 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -179,4 +179,9 @@ Item { rightMargin: 136 } } + + Component.onCompleted: { + root.parent.setStateInfoState("left"); + root.parent.setBuildInfoState("right"); + } } diff --git a/launchers/qt/resources/qml/SplashScreen.qml b/launchers/qt/resources/qml/SplashScreen.qml index a710a7245c..bd4fc5e762 100644 --- a/launchers/qt/resources/qml/SplashScreen.qml +++ b/launchers/qt/resources/qml/SplashScreen.qml @@ -2,6 +2,7 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 Item { + id: root anchors.fill: parent @@ -9,7 +10,7 @@ Item { anchors.centerIn: parent width: parent.width height: parent.height - mirror: true + mirror: false source: PathUtils.resourcePath("images/hifi_window@2x.png"); transformOrigin: Item.Center rotation: 0 @@ -21,4 +22,9 @@ Item { height: 205 source: PathUtils.resourcePath("images/hifi_logo_large@2x.png"); } + + Component.onCompleted: { + root.parent.setStateInfoState("left"); + root.parent.setBuildInfoState("right"); + } } diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 3c755c2a6b..8b22763908 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -12,6 +12,15 @@ Item { Loader { anchors.fill: parent id: loader + + + function setBuildInfoState(state) { + buildInfo.state = state; + } + + function setStateInfoState(state) { + stateInfo.state = state; + } } Component.onCompleted: { @@ -31,9 +40,61 @@ Item { font.pixelSize: 12 anchors.right: root.right - anchors.bottom: root.bottom + anchors.top: root.top color: "#FFFFFF" text: LauncherState.uiState.toString() + " - " + LauncherState.applicationState + + states: [ + State { + name: "left" + AnchorChanges { + target: stateInfo + anchors.left: root.left + anchors.right: undefined + } + }, + + State { + name: "right" + AnchorChanges { + target: stateInfo + anchors.right: root.right + anchors.left: undefined + } + } + ] + } + + Text { + id: buildInfo + + font.pixelSize: 12 + + anchors.right: root.right + anchors.bottom: root.bottom + + color: "#FFFFFF" + text: "v." + LauncherState.buildVersion; + + states: [ + State { + name: "left" + AnchorChanges { + target: buildInfo + anchors.left: root.left + anchors.right: undefined + } + }, + + State { + name: "right" + AnchorChanges { + target: buildInfo + anchors.right: root.right + anchors.left: undefined + } + } + ] } } diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index 1bbceb61e7..ffd6218fab 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -2,6 +2,10 @@ #include "Helper.h" #include +#include +#include +#include +#include #include #include @@ -101,6 +105,8 @@ void LauncherInstaller::uninstall() { if (QFile::exists(desktopAppLinkPath)) { QFile::remove(desktopAppLinkPath); } + + deleteApplicationRegistryKeys(); } @@ -115,13 +121,22 @@ void LauncherInstaller::createApplicationRegistryKeys() { success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", "DEV"); success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); success = insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); - //success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallDate", LauncherUtils::cStringToStd(CTime::GetCurrentTime().Format("%Y%m%d"))); + + auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + std::stringstream date; + date << std::put_time(std::localtime(&now), "%Y-%m-%d") ; + success = insertRegistryKey(REGISTRY_PATH, "InstallDate", date.str()); //success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size); success = insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1); success = insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1); - qDebug() << "--------: " << success; + qDebug() << "Did succcessfully insertRegistyKeys: " << success; } void LauncherInstaller::deleteApplicationRegistryKeys() { + const std::string regPath= "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; + if (!deleteRegistryKey(regPath.c_str())) { + qDebug() << "Failed to delete registryKeys"; + } } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 976dbc22fb..12ad27f4db 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -46,7 +46,7 @@ QString LauncherState::getClientExecutablePath() const { } bool LauncherState::shouldDownloadContentCache() const { - return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); + return !_contentCacheURL.isEmpty() && !QFile::exists(getContentCachePath()); } bool LatestBuilds::getBuild(QString tag, Build* outBuild) { @@ -233,7 +233,7 @@ void LauncherState::receivedBuildsReply() { } if (shouldDownloadLauncher()) { - //downloadLauncher(); + downloadLauncher(); } getCurrentClientVersion(); } @@ -390,12 +390,15 @@ void LauncherState::requestSettings() { connect(request, &UserSettingsRequest::finished, this, [this, request]() { auto userSettings = request->getUserSettings(); if (userSettings.homeLocation.isEmpty()) { + qDebug() << "UserSettings is empty"; _homeLocation = "hifi://hq"; _contentCacheURL = ""; } else { _homeLocation = userSettings.homeLocation; auto host = QUrl(_homeLocation).host(); _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; + + qDebug() << "Content cache url: " << _contentCacheURL; } qDebug() << "Home location is: " << _homeLocation; @@ -423,7 +426,7 @@ void LauncherState::downloadClient() { return; } - _downloadProgress = 0; + _interfaceDownloadProgress = 0; setApplicationState(ApplicationState::DownloadingClient); // Start client download @@ -453,7 +456,7 @@ void LauncherState::downloadClient() { } }); connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) { - _downloadProgress = (float)received / (float)total; + _interfaceDownloadProgress = (float)received / (float)total; emit downloadProgressChanged(); }); } @@ -465,7 +468,7 @@ void LauncherState::launcherDownloadComplete() { #ifdef Q_OS_MAC installLauncher(); #elif defined(Q_OS_WIN) - //launchAutoUpdater(_launcherZipFile.fileName()); + launchAutoUpdater(_launcherZipFile.fileName()); #endif } @@ -475,6 +478,16 @@ void LauncherState::clientDownloadComplete() { installClient(); } + +float LauncherState::calculateDownloadProgress() const{ + if (shouldDownloadContentCache()) { + return (_interfaceDownloadProgress * 0.40f) + (_interfaceInstallProgress * 0.10f) + + (_contentInstallProgress * 0.40f) + (_contentDownloadProgress * 0.10f); + } + + return (_interfaceDownloadProgress * 0.80f) + (_interfaceInstallProgress * 0.20f); +} + void LauncherState::installClient() { ASSERT_STATE(ApplicationState::DownloadingClient); setApplicationState(ApplicationState::InstallingClient); @@ -483,7 +496,7 @@ void LauncherState::installClient() { _launcherDirectory.mkpath("interface_install"); auto installDir = _launcherDirectory.absoluteFilePath("interface_install"); - _downloadProgress = 0; + _interfaceInstallProgress = 0; qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << installDir; @@ -491,7 +504,7 @@ void LauncherState::installClient() { unzipper->setAutoDelete(true); connect(unzipper, &Unzipper::progress, this, [this](float progress) { //qDebug() << "Unzipper progress: " << progress; - _downloadProgress = progress; + _interfaceInstallProgress = progress; emit downloadProgressChanged(); }); connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) { @@ -575,7 +588,7 @@ void LauncherState::downloadContentCache() { if (shouldDownloadContentCache()) { setApplicationState(ApplicationState::DownloadingContentCache); - _downloadProgress = 0; + _contentDownloadProgress = 0; qDebug() << "Downloading content cache from: " << _contentCacheURL; QNetworkRequest request{ QUrl(_contentCacheURL) }; @@ -592,7 +605,7 @@ void LauncherState::downloadContentCache() { connect(reply, &QNetworkReply::finished, this, &LauncherState::contentCacheDownloadComplete); connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) { - _downloadProgress = (float)received / (float)total; + _contentDownloadProgress = (float)received / (float)total; emit downloadProgressChanged(); }); } else { @@ -633,13 +646,13 @@ void LauncherState::installContentCache() { qDebug() << "Unzipping " << _contentZipFile.fileName() << " to " << installDir; - _downloadProgress = 0; + _contentInstallProgress = 0; auto unzipper = new Unzipper(_contentZipFile.fileName(), QDir(installDir)); unzipper->setAutoDelete(true); connect(unzipper, &Unzipper::progress, this, [this](float progress) { qDebug() << "Unzipper progress (content cache): " << progress; - _downloadProgress = progress; + _contentInstallProgress = progress; emit downloadProgressChanged(); }); connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) { @@ -700,9 +713,12 @@ void LauncherState::setApplicationState(ApplicationState state) { } _applicationState = state; - - emit uiStateChanged(); - emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); + UIState updatedUIState = getUIState(); + if (_uiState != updatedUIState) { + emit uiStateChanged(); + emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); + _uiState = getUIState(); + } emit applicationStateChanged(); } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index eb87d15343..42bfb45d19 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -32,7 +32,8 @@ class LauncherState : public QObject { Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged) Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged) Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) - Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged); + Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged) + Q_PROPERTY(QString buildVersion READ getBuildVersion) public: LauncherState(); @@ -95,6 +96,8 @@ public: UIState getUIState() const; + QString getBuildVersion() { return QString(LAUNCHER_BUILD_VERSION); } + void setLastLoginError(LastLoginError lastLoginError); LastLoginError getLastLoginError() const; @@ -135,7 +138,7 @@ public: // Launching void launchClient(); - Q_INVOKABLE float getDownloadProgress() const { return _downloadProgress; } + Q_INVOKABLE float getDownloadProgress() const { return calculateDownloadProgress(); } signals: void updateSourceUrl(QUrl sourceUrl); @@ -157,6 +160,8 @@ private: QString getClientDirectory() const; QString getClientExecutablePath() const; + float calculateDownloadProgress() const; + bool shouldDownloadLauncher(); QNetworkAccessManager _networkAccessManager; @@ -165,6 +170,7 @@ private: // Application State ApplicationState _applicationState { ApplicationState::Init }; + UIState _uiState { UIState::SPLASH_SCREEN }; LoginToken _loginResponse; LastLoginError _lastLoginError { NONE }; SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; @@ -183,4 +189,8 @@ private: QString _password; float _downloadProgress { 0 }; + float _contentDownloadProgress { 0 }; + float _contentInstallProgress { 0 }; + float _interfaceDownloadProgress { 0 }; + float _interfaceInstallProgress { 0 }; }; diff --git a/launchers/qt/src/PathUtils.cpp b/launchers/qt/src/PathUtils.cpp index 542e268308..3fe970d1f4 100644 --- a/launchers/qt/src/PathUtils.cpp +++ b/launchers/qt/src/PathUtils.cpp @@ -3,5 +3,10 @@ #include QUrl PathUtils::resourcePath(const QString& source) { - return QUrl(RESOURCE_PREFIX_URL + source); + QString filePath = RESOURCE_PREFIX_URL + source; +#ifdef HIFI_USE_LOCAL_FILE + return QUrl::fromLocalFile(filePath); +#else + return QUrl(filePath); +#endif } From 42c479668237855c7c58355c0f9b8fd05648d901 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 2 Oct 2019 16:01:20 -0700 Subject: [PATCH 052/203] add some debug --- launchers/qt/src/BuildsRequest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launchers/qt/src/BuildsRequest.cpp b/launchers/qt/src/BuildsRequest.cpp index e693377dc7..499a226da2 100644 --- a/launchers/qt/src/BuildsRequest.cpp +++ b/launchers/qt/src/BuildsRequest.cpp @@ -32,6 +32,8 @@ void BuildsRequest::send(QNetworkAccessManager& nam) { latestBuildRequestUrl = processEnvironment.value("HQ_LAUNCHER_BUILDS_URL"); } + qDeubug() << latestBuildRequestUrl; + QNetworkRequest request{ QUrl(latestBuildRequestUrl) }; auto reply = nam.get(request); From a2729065d051665a60d4077500a01846c6a6c0a1 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 2 Oct 2019 16:02:09 -0700 Subject: [PATCH 053/203] fix error --- launchers/qt/src/BuildsRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/BuildsRequest.cpp b/launchers/qt/src/BuildsRequest.cpp index 499a226da2..5986bf085f 100644 --- a/launchers/qt/src/BuildsRequest.cpp +++ b/launchers/qt/src/BuildsRequest.cpp @@ -32,7 +32,7 @@ void BuildsRequest::send(QNetworkAccessManager& nam) { latestBuildRequestUrl = processEnvironment.value("HQ_LAUNCHER_BUILDS_URL"); } - qDeubug() << latestBuildRequestUrl; + qDebug() << latestBuildRequestUrl; QNetworkRequest request{ QUrl(latestBuildRequestUrl) }; auto reply = nam.get(request); From 3dbd4f220feb5c0fad80c77b2fdc9d6c56d1b2f8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 2 Oct 2019 16:51:37 -0700 Subject: [PATCH 054/203] Add metaverse env var and login error message to qt launcher --- .../qt/resources/qml/HFBase/LoginBase.qml | 24 ++++++++++++++++-- launchers/qt/src/Helper.cpp | 9 +++++++ launchers/qt/src/Helper.h | 8 +----- launchers/qt/src/LauncherState.cpp | 25 +++++++++++-------- launchers/qt/src/LauncherState.h | 16 ++++-------- launchers/qt/src/LoginRequest.cpp | 15 ++++++++--- launchers/qt/src/LoginRequest.h | 1 + launchers/qt/src/SignupRequest.cpp | 4 +-- launchers/qt/src/UserSettingsRequest.cpp | 2 +- 9 files changed, 67 insertions(+), 37 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index deb21ad085..fa0737be0e 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -42,6 +42,7 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "#C4C4C4" + visible: LauncherState.lastLoginErrorMessage.length == 0 text: "Use the account credentials you created at sign-up" anchors { left: root.left @@ -51,6 +52,25 @@ Item { } } + Text { + id: error + width: 425 + height: 22 + font.family: "Graphik" + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "#FF9999" + visible: LauncherState.lastLoginErrorMessage.length > 0 + text: LauncherState.lastLoginErrorMessage + anchors { + left: root.left + right: root.right + top: title.bottom + topMargin: 18 + } + } + HFTextField { id: username width: 353 @@ -61,8 +81,8 @@ Item { color: "#7E8C81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { - top: instruction.bottom - horizontalCenter: instruction.horizontalCenter + top: error.bottom + horizontalCenter: error.horizontalCenter topMargin: 24 } } diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 729824da3f..38e6040a16 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -6,8 +6,17 @@ #include #include #include +#include +QString getMetaverseAPIDomain() { + QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); + if (processEnvironment.contains("HIFI_METAVERSE_URL")) { + return processEnvironment.value("HIFI_METAVERSE_URL"); + } + return "https://metaverse.highfidelity.com"; +} + void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) { qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist"; diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index a41b55b233..e5a3c4f502 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -5,13 +5,7 @@ #include "Windows.h" #endif -//#define USE_STAGING - -#ifdef USE_STAGING -const QString METAVERSE_API_DOMAIN{ "https://staging.highfidelity.com" }; -#else -const QString METAVERSE_API_DOMAIN{ "https://metaverse.highfidelity.com" }; -#endif +QString getMetaverseAPIDomain(); void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 582b416b85..0d094d8419 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -67,6 +67,11 @@ bool LauncherState::shouldDownloadContentCache() const { return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath()); } +void LauncherState::setLastLoginErrorMessage(const QString& msg) { + _lastLoginErrorMessage = msg; + emit lastLoginErrorMessageChanged(); +} + static const std::array QML_FILE_FOR_UI_STATE = { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml", "DisplayName.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; @@ -142,14 +147,6 @@ LauncherState::UIState LauncherState::getUIState() const { } } -void LauncherState::setLastLoginError(LastLoginError lastLoginError) { - _lastLoginError = lastLoginError; -} - -LauncherState::LastLoginError LauncherState::getLastLoginError() const { - return _lastLoginError; -} - void LauncherState::restart() { setApplicationState(ApplicationState::Init); requestBuilds(); @@ -296,7 +293,11 @@ void LauncherState::signup(QString email, QString username, QString password, QS loginRequest->deleteLater(); auto err = loginRequest->getError(); - if (err != LoginRequest::Error::None) { + if (err == LoginRequest::Error::BadUsernameOrPassword) { + setLastLoginErrorMessage("Bad username or password"); + setApplicationState(ApplicationState::WaitingForLogin); + return; + } else if (err != LoginRequest::Error::None) { setApplicationStateError("Failed to login"); return; } @@ -333,7 +334,11 @@ void LauncherState::login(QString username, QString password, QString displayNam request->deleteLater(); auto err = request->getError(); - if (err != LoginRequest::Error::None) { + if (err == LoginRequest::Error::BadUsernameOrPassword) { + setLastLoginErrorMessage("Bad username or password"); + setApplicationState(ApplicationState::WaitingForLogin); + return; + } else if (err != LoginRequest::Error::None) { setApplicationStateError("Failed to login"); return; } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 324a653854..37899486f1 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -25,6 +25,7 @@ class LauncherState : public QObject { Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged) Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged); + Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged); public: LauncherState(); @@ -67,16 +68,8 @@ public: LaunchingHighFidelity }; - enum LastLoginError { - NONE = 0, - ORGINIZATION, - CREDENTIALS, - LAST_ERROR_NUM - }; - Q_ENUM(UIState); Q_ENUM(ApplicationState) - Q_ENUM(LastLoginError) Q_INVOKABLE QString getCurrentUISource() const; @@ -87,8 +80,8 @@ public: UIState getUIState() const; - void setLastLoginError(LastLoginError lastLoginError); - LastLoginError getLastLoginError() const; + void setLastLoginErrorMessage(const QString& msg); + QString getLastLoginErrorMessage() const { return _lastLoginErrorMessage; } void setApplicationStateError(QString errorMessage); void setApplicationState(ApplicationState state); @@ -134,6 +127,7 @@ signals: void applicationStateChanged(); void downloadProgressChanged(); void lastSignupErrorChanged(); + void lastLoginErrorMessageChanged(); private slots: void clientDownloadComplete(); @@ -161,8 +155,8 @@ private: // Application State ApplicationState _applicationState { ApplicationState::Init }; LoginToken _loginResponse; - LastLoginError _lastLoginError { NONE }; SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; + QString _lastLoginErrorMessage{ "" }; QString _displayName; QString _applicationErrorMessage; QString _currentClientVersion; diff --git a/launchers/qt/src/LoginRequest.cpp b/launchers/qt/src/LoginRequest.cpp index 488b5bbfa5..1c08847c6a 100644 --- a/launchers/qt/src/LoginRequest.cpp +++ b/launchers/qt/src/LoginRequest.cpp @@ -8,7 +8,7 @@ #include void LoginRequest::send(QNetworkAccessManager& nam, QString username, QString password) { - QNetworkRequest request(QUrl(METAVERSE_API_DOMAIN + "/oauth/token")); + QNetworkRequest request(QUrl(getMetaverseAPIDomain() + "/oauth/token")); request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -28,11 +28,19 @@ void LoginRequest::receivedResponse() { auto reply = static_cast(sender()); - if (reply->error()) { + auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (statusCode < 100) { qDebug() << "Error logging in: " << reply->readAll(); _error = Error::Unknown; emit finished(); - //setApplicationState(ApplicationState::UnexpectedError); + return; + } + + if (statusCode >= 500 && statusCode < 600) { + qDebug() << "Error logging in: " << reply->readAll(); + _error = Error::ServerError; + emit finished(); return; } @@ -44,7 +52,6 @@ void LoginRequest::receivedResponse() { qDebug() << "Error parsing response for login" << data; _error = Error::BadResponse; emit finished(); - //setApplicationStateError("Failed to login"); return; } diff --git a/launchers/qt/src/LoginRequest.h b/launchers/qt/src/LoginRequest.h index 07b28ff794..465963b62e 100644 --- a/launchers/qt/src/LoginRequest.h +++ b/launchers/qt/src/LoginRequest.h @@ -21,6 +21,7 @@ public: enum class Error { None = 0, Unknown, + ServerError, BadResponse, BadUsernameOrPassword }; diff --git a/launchers/qt/src/SignupRequest.cpp b/launchers/qt/src/SignupRequest.cpp index a6961c0094..0a2c554f8d 100644 --- a/launchers/qt/src/SignupRequest.cpp +++ b/launchers/qt/src/SignupRequest.cpp @@ -15,7 +15,7 @@ void SignupRequest::send(QNetworkAccessManager& nam, QString email, QString user _state = State::Sending; - QUrl signupURL { METAVERSE_API_DOMAIN }; + QUrl signupURL { getMetaverseAPIDomain() }; signupURL.setPath("/api/v1/user/channel_user"); QNetworkRequest request(signupURL); @@ -27,7 +27,7 @@ void SignupRequest::send(QNetworkAccessManager& nam, QString email, QString user query.addQueryItem("username", username); query.addQueryItem("password", password); - auto reply = nam.post(request, query.toString().toUtf8()); + auto reply = nam.put(request, query.toString().toUtf8()); QObject::connect(reply, &QNetworkReply::finished, this, &SignupRequest::receivedResponse); } diff --git a/launchers/qt/src/UserSettingsRequest.cpp b/launchers/qt/src/UserSettingsRequest.cpp index 62f7d5b4fb..737d103784 100644 --- a/launchers/qt/src/UserSettingsRequest.cpp +++ b/launchers/qt/src/UserSettingsRequest.cpp @@ -12,7 +12,7 @@ const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; void UserSettingsRequest::send(QNetworkAccessManager& nam, const LoginToken& token) { _state = State::Sending; - QUrl lockerURL = METAVERSE_API_DOMAIN; + QUrl lockerURL{ getMetaverseAPIDomain() }; lockerURL.setPath("/api/v1/user/locker"); QNetworkRequest lockerRequest(lockerURL); From 7bdf6f675066f79845458512874a5e63d3ede18e Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 2 Oct 2019 17:30:27 -0700 Subject: [PATCH 055/203] mac improvements --- launchers/qt/CMakeLists.txt | 11 +++++++++++ launchers/qt/src/Helper_darwin.mm | 1 - launchers/qt/src/LauncherState.cpp | 7 +++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 3e23d2ee6a..8ecf42942a 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -183,12 +183,23 @@ if (WIN32) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT}) add_executable(${PROJECT_NAME} WIN32 ${src_files} ${RES_SOURCES} ${CONFIGURE_ICON_RC_OUTPUT}) elseif (APPLE) + set(APP_NAME "HQ Launcher") set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) set(MACOSX_BUNDLE_ICON_FILE "interface.icns") add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files} ${RES_SOURCES}) + set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME} + MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}) set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") + + + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources" + COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/MacOS/" ln -sf ./HQ\ Launcher updater + # Older versions of Launcher put updater in `/Contents/Resources/updater`. + COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources" ln -sf ../MacOS/HQ\ Launcher updater + ) endif() target_link_libraries(${PROJECT_NAME} diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 28b35193ba..b4fb9f8a6e 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -33,7 +33,6 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; NSURL *url = [NSURL fileURLWithPath:[workspace fullPathForApplication:clientPath.toNSString()]]; - NSLog(@"------> path %@: ", [url path]); NSTask *task = [[NSTask alloc] init]; task.launchPath = [url path]; task.arguments = arguments; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 52f4c9f5ac..15c7ec8163 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -1,5 +1,6 @@ #include "LauncherState.h" +#include "CommandlineOptions.h" #include "PathUtils.h" #include "Unzipper.h" #include "Helper.h" @@ -171,8 +172,10 @@ void LauncherState::requestBuilds() { _latestBuilds = request->getLatestBuilds(); - if (shouldDownloadLauncher()) { + CommandlineOptions* options = CommandlineOptions::getInstance(); + if (shouldDownloadLauncher() && !options->contains("--noUpdate")) { downloadLauncher(); + return; } getCurrentClientVersion(); }); @@ -541,7 +544,7 @@ void LauncherState::installLauncher() { #elif defined(Q_OS_MACOS) launcherPath = installDirectory.absoluteFilePath("HQ Launcher.app"); #endif - //::launchAutoUpdater(launcherPath); + ::launchAutoUpdater(launcherPath); } }); From 239ffcf46e4869a36ec07880d5a85e13613bac84 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 2 Oct 2019 17:39:41 -0700 Subject: [PATCH 056/203] fix windows auto-updater --- launchers/qt/src/Helper_windows.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index fda6a455f0..aca0941c4a 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -75,6 +75,8 @@ void launchAutoUpdater(const QString& autoUpdaterPath) { &si, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure ); + + QCoreApplication::instance()->quit(); } From fd92f41baefa367c3d768a9f33dbbe9d27f462ee Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 08:35:37 -0700 Subject: [PATCH 057/203] Add Graphik font and debuggable screens to qt launcher --- .../qt/resources/fonts/Graphik-Medium.ttf | Bin 0 -> 147368 bytes .../qt/resources/fonts/Graphik-Regular.ttf | Bin 0 -> 144968 bytes .../qt/resources/fonts/Graphik-Semibold.ttf | Bin 0 -> 52264 bytes .../qml/HFBase/CreateAccountBase.qml | 40 +++++---------- .../qt/resources/qml/HFBase/LoginBase.qml | 38 +++++---------- .../qt/resources/qml/HFControls/HFButton.qml | 4 ++ .../resources/qml/HFControls/HFTextField.qml | 14 ++++-- launchers/qt/resources/qml/SplashScreen.qml | 4 +- launchers/qt/resources/qml/root.qml | 12 +++-- launchers/qt/src/Helper_windows.cpp | 2 +- launchers/qt/src/Launcher.cpp | 9 ++++ launchers/qt/src/LauncherState.cpp | 46 +++++++++++++++--- launchers/qt/src/LauncherState.h | 24 +++++---- launchers/qt/src/LauncherWindow.cpp | 6 +++ launchers/qt/src/LauncherWindow.h | 1 + launchers/qt/src/PathUtils.cpp | 8 +++ launchers/qt/src/PathUtils.h | 1 + 17 files changed, 127 insertions(+), 82 deletions(-) create mode 100644 launchers/qt/resources/fonts/Graphik-Medium.ttf create mode 100644 launchers/qt/resources/fonts/Graphik-Regular.ttf create mode 100644 launchers/qt/resources/fonts/Graphik-Semibold.ttf diff --git a/launchers/qt/resources/fonts/Graphik-Medium.ttf b/launchers/qt/resources/fonts/Graphik-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9d766a1d761532beceac5968ca4d0186dd1d48ce GIT binary patch literal 147368 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 literal 0 HcmV?d00001 diff --git a/launchers/qt/resources/fonts/Graphik-Semibold.ttf b/launchers/qt/resources/fonts/Graphik-Semibold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b0ee02248d4f672b646884c768921992b72563ab GIT binary patch literal 52264 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 literal 0 HcmV?d00001 diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 04b76d5e36..0c1e9f4421 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -21,16 +21,12 @@ Item { rotation: 180 } - Text { + HFTextHeader { id: title width: 481 height: 27 - font.family: "Graphik" - font.bold: true - font.pixelSize: 29 lineHeight: 35 lineHeightMode: Text.FixedHeight - color: "#FFFFFF" text: root.titleText anchors { top: root.top @@ -40,14 +36,12 @@ Item { } } - Text { + HFTextRegular { id: instruction width: 425 height: 22 - font.family: "Graphik" - font.pixelSize: 14 - color: "#C4C4C4" - text: "Use the email address that you regisetered with. " + LauncherState.lastSignupError + + text: "Use the email address that you registered with." anchors { left: root.left leftMargin: root.marginLeft @@ -60,10 +54,7 @@ Item { id: email width: 430 height: 50 - font.family: "Graphik" - font.pixelSize: 18 placeholderText: "Email Address" - color: "#7e8c81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { top: instruction.bottom @@ -77,10 +68,7 @@ Item { id: username width: 430 height: 50 - font.family: "Graphik" - font.pixelSize: 18 placeholderText: root.usernamePlaceholder - color: "#7e8c81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { top: email.bottom @@ -94,10 +82,7 @@ Item { id: passwordField width: 430 height: 50 - font.family: "Graphik" - font.pixelSize: 18 placeholderText: root.passwordPlaceholder - color: "#7e8c81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) togglePasswordField: true echoMode: TextInput.Password @@ -110,12 +95,14 @@ Item { } - Text { + HFTextRegular { id: displayNameText - text: "You can change this at anytime from you Profile" - font.family: "Graphik" - font.pixelSize: 10 - color: "#C4C4C4" + + text: "This is the display name other people see in world, it can be changed at anytime, from your profile." + wrapMode: Text.Wrap + + width: 430 + anchors { top: passwordField.bottom left: root.left @@ -128,10 +115,7 @@ Item { id: displayName width: 430 height: 50 - font.family: "Graphik" - font.pixelSize: 18 placeholderText: "Display Name" - color: "#7e8c81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { top: displayNameText.bottom @@ -146,8 +130,6 @@ Item { width: 134 height: 50 - font.family: "Graphik" - font.pixelSize: 14 text: "NEXT" anchors { diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 417fdc19ca..109e6e50b1 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -15,14 +15,11 @@ Item { transformOrigin: Item.Center rotation: 0 } - Text { + HFTextHeader { id: title width: 325 height: 26 - font.family: "Graphik" - font.pixelSize: 28 font.bold: true - color: "#FFFFFF" text: "Please Log in" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -33,15 +30,14 @@ Item { } } - Text { + HFTextRegular { id: instruction width: 425 height: 22 - font.family: "Graphik" - font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - color: "#C4C4C4" + visible: LauncherState.lastLoginErrorMessage.length == 0 text: "Use the account credentials you created at sign-up" anchors { @@ -52,15 +48,16 @@ Item { } } - Text { + HFTextRegular { id: error width: 425 height: 22 - font.family: "Graphik" - font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + color: "#FF9999" + visible: LauncherState.lastLoginErrorMessage.length > 0 text: LauncherState.lastLoginErrorMessage anchors { @@ -75,10 +72,9 @@ Item { id: username width: 353 height: 50 - font.family: "Graphik" - font.pixelSize: 18 + placeholderText: "Username" - color: "#7E8C81" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { top: error.bottom @@ -91,10 +87,7 @@ Item { id: password width: 353 height: 50 - font.family: "Graphik" - font.pixelSize: 18 placeholderText: "Password" - color: "#7E8C81" togglePasswordField: true echoMode: TextInput.Password seperatorColor: Qt.rgba(1, 1, 1, 0.3) @@ -106,12 +99,10 @@ Item { } - Text { + HFTextRegular { id: displayText - text: "You can change this at anytime from your profile" - color: "#C4C4C4" - font.pixelSize: 14 + text: "You can change this at anytime from your profile." anchors { top: password.bottom @@ -124,10 +115,7 @@ Item { id: displayName width: 353 height: 50 - font.family: "Graphik" - font.pixelSize: 18 placeholderText: "Display name" - color: "#7E8C81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { top: displayText.bottom @@ -141,8 +129,6 @@ Item { width: 110 height: 50 - font.family: "Graphik" - font.pixelSize: 18 text: "NEXT" anchors { diff --git a/launchers/qt/resources/qml/HFControls/HFButton.qml b/launchers/qt/resources/qml/HFControls/HFButton.qml index 3e44d74c7e..a1ebb50ee8 100644 --- a/launchers/qt/resources/qml/HFControls/HFButton.qml +++ b/launchers/qt/resources/qml/HFControls/HFButton.qml @@ -9,6 +9,10 @@ Button { property int backgroundOpacity: 1 property int backgroundRadius: 1 property int backgroundWidth: 2 + + font.family: "Graphik Semibold" + font.pointSize: 12 + background: Rectangle { implicitWidth: 100 implicitHeight: 40 diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index 77777a37d3..e5abab7b52 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -3,15 +3,18 @@ import QtQuick.Controls 2.1 TextField { id: control - //color: "#000000" - font.family: "Graphik Medium" - font.pixelSize: 22 + + font.family: "Graphik Regular" + font.pointSize: 10.5 + color: "#7e8c81" + property bool togglePasswordField: false verticalAlignment: TextInput.AlignVCenter horizontalAlignment: TextInput.AlignLeft placeholderText: "PlaceHolder" property string seperatorColor: "#FFFFFF" selectByMouse: true + background: Item { anchors.fill: parent Rectangle { @@ -24,8 +27,9 @@ TextField { visible: control.togglePasswordField source: (control.echoMode == TextInput.Password) ? PathUtils.resourcePath("images/showPass.png") : PathUtils.resourcePath("images/hidePass.png"); - width: 21 - height: 14 + fillMode: Image.PreserveAspectFit + width: 24 + smooth: true anchors { top: parent.top topMargin: 18 diff --git a/launchers/qt/resources/qml/SplashScreen.qml b/launchers/qt/resources/qml/SplashScreen.qml index bd4fc5e762..47dff2aa9d 100644 --- a/launchers/qt/resources/qml/SplashScreen.qml +++ b/launchers/qt/resources/qml/SplashScreen.qml @@ -18,8 +18,8 @@ Item { Image { anchors.centerIn: parent - width: 225 - height: 205 + width: 240 + height: 180 source: PathUtils.resourcePath("images/hifi_logo_large@2x.png"); } diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 8b22763908..2aa2a34af8 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -71,10 +71,16 @@ Item { font.pixelSize: 12 - anchors.right: root.right - anchors.bottom: root.bottom + anchors { + leftMargin: 10 + rightMargin: 10 + bottomMargin: 10 - color: "#FFFFFF" + right: root.right + bottom: root.bottom + } + + color: "#777" text: "v." + LauncherState.buildVersion; states: [ diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index fda6a455f0..fc540a368a 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -156,7 +156,7 @@ BOOL isProcessRunning(const char* processName, int& processID) { entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - + if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (!_stricmp(entry.szExeFile, processName)) { diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index af90507db6..b0566eee0a 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "LauncherWindow.h" #include "LauncherState.h" @@ -10,11 +11,19 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherState = std::make_shared(); + _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); _launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window); + _launcherWindow->setLauncherStatePtr(_launcherState); + LauncherState::declareQML(); + + QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Regular.ttf")); + QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Medium.ttf")); + QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Semibold.ttf")); + _launcherWindow->setSource(QUrl(PathUtils::resourcePath("qml/root.qml"))); _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); _launcherWindow->show(); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 0ef3bba8f8..70b9e52cc1 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -33,6 +33,34 @@ const QString configHomeLocationKey { "homeLocation" }; const QString configLoggedInKey{ "loggedIn" }; const QString configLauncherPathKey{ "launcherPath" }; +void LauncherState::toggleDebugState() { + _isDebuggingScreens = !_isDebuggingScreens; + + UIState updatedUIState = getUIState(); + if (_uiState != updatedUIState) { + emit uiStateChanged(); + emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); + _uiState = getUIState(); + } +} +void LauncherState::gotoNextDebugScreen() { + if (_currentDebugScreen < (UIState::UI_STATE_NUM - 1)) { + _currentDebugScreen = (UIState)(_currentDebugScreen + 1); + UIState updatedUIState = getUIState(); + emit uiStateChanged(); + emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); + _uiState = getUIState(); + } +} +void LauncherState::gotoPreviousDebugScreen() { + if (_currentDebugScreen > 0) { + _currentDebugScreen = (UIState)(_currentDebugScreen - 1); + emit uiStateChanged(); + emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); + _uiState = getUIState(); + } +} + QString LauncherState::getContentCachePath() const { return _launcherDirectory.filePath("cache"); } @@ -116,35 +144,39 @@ void LauncherState::declareQML() { } LauncherState::UIState LauncherState::getUIState() const { + if (_isDebuggingScreens) { + return _currentDebugScreen; + } + switch (_applicationState) { case ApplicationState::Init: case ApplicationState::RequestingBuilds: case ApplicationState::GettingCurrentClientVersion: - return SPLASH_SCREEN; + return UIState::SplashScreen; case ApplicationState::WaitingForLogin: case ApplicationState::RequestingLogin: - return LOGIN_SCREEN; + return UIState::LoginScreen; case ApplicationState::WaitingForSignup: case ApplicationState::RequestingSignup: - return SIGNUP_SCREEN; + return UIState::SignupScreen; case ApplicationState::DownloadingClient: case ApplicationState::InstallingClient: case ApplicationState::DownloadingContentCache: case ApplicationState::InstallingContentCache: - return DOWNLOAD_SCREEN; + return UIState::DownloadScreen; case ApplicationState::LaunchingHighFidelity: - return DOWNLOAD_FINSISHED; + return UIState::DownloadFinishedScreen; case ApplicationState::UnexpectedError: #ifdef BREAK_ON_ERROR __debugbreak(); #endif - return ERROR_SCREEN; + return UIState::ErrorScreen; default: qDebug() << "FATAL: No UI for" << _applicationState; #ifdef BREAK_ON_ERROR __debugbreak(); #endif - return ERROR_SCREEN; + return UIState::ErrorScreen; } } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index fc04f52c11..0842eafa52 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -33,13 +33,13 @@ public: ~LauncherState() = default; enum UIState { - SPLASH_SCREEN = 0, - SIGNUP_SCREEN, - LOGIN_SCREEN, - DISPLAY_NAME_SCREEN, - DOWNLOAD_SCREEN, - DOWNLOAD_FINSISHED, - ERROR_SCREEN, + SplashScreen = 0, + SignupScreen, + LoginScreen, + DownloadScreen, + DownloadFinishedScreen, + ErrorScreen, + UI_STATE_NUM }; @@ -66,9 +66,15 @@ public: InstallingLauncher, InstallingContentCache, - LaunchingHighFidelity + LaunchingHighFidelity, }; + bool _isDebuggingScreens{ false }; + UIState _currentDebugScreen{ UIState::SplashScreen }; + void toggleDebugState(); + void gotoNextDebugScreen(); + void gotoPreviousDebugScreen(); + Q_ENUM(UIState); Q_ENUM(ApplicationState) @@ -159,7 +165,7 @@ private: // Application State ApplicationState _applicationState { ApplicationState::Init }; - UIState _uiState { UIState::SPLASH_SCREEN }; + UIState _uiState { UIState::SplashScreen }; LoginToken _loginResponse; SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; QString _lastLoginErrorMessage{ "" }; diff --git a/launchers/qt/src/LauncherWindow.cpp b/launchers/qt/src/LauncherWindow.cpp index a88df8efe7..3c13e5908a 100644 --- a/launchers/qt/src/LauncherWindow.cpp +++ b/launchers/qt/src/LauncherWindow.cpp @@ -9,6 +9,12 @@ void LauncherWindow::keyPressEvent(QKeyEvent* event) { if (!event->isAccepted()) { if (event->key() == Qt::Key_Escape) { exit(0); + } else if (event->key() == Qt::Key_Up) { + _launcherState->toggleDebugState(); + } else if (event->key() == Qt::Key_Left) { + _launcherState->gotoPreviousDebugScreen(); + } else if (event->key() == Qt::Key_Right) { + _launcherState->gotoNextDebugScreen(); } } } diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h index 79651e9e5e..08e8cf4825 100644 --- a/launchers/qt/src/LauncherWindow.h +++ b/launchers/qt/src/LauncherWindow.h @@ -12,6 +12,7 @@ public: void mouseReleaseEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void setLauncherStatePtr(std::shared_ptr launcherState) { _launcherState = launcherState; } + private: bool _drag { false }; QPoint _previousMousePos; diff --git a/launchers/qt/src/PathUtils.cpp b/launchers/qt/src/PathUtils.cpp index 3fe970d1f4..eba74721cb 100644 --- a/launchers/qt/src/PathUtils.cpp +++ b/launchers/qt/src/PathUtils.cpp @@ -10,3 +10,11 @@ QUrl PathUtils::resourcePath(const QString& source) { return QUrl(filePath); #endif } + +QString PathUtils::fontPath(const QString& fontName) { +#ifdef HIFI_USE_LOCAL_FILE + return resourcePath("/fonts/" + fontName).toString(QUrl::PreferLocalFile); +#else + return ":/fonts/" + fontName; +#endif +} diff --git a/launchers/qt/src/PathUtils.h b/launchers/qt/src/PathUtils.h index 3bfddd624c..c33041736b 100644 --- a/launchers/qt/src/PathUtils.h +++ b/launchers/qt/src/PathUtils.h @@ -10,4 +10,5 @@ public: PathUtils() = default; ~PathUtils() = default; Q_INVOKABLE static QUrl resourcePath(const QString& source); + QString static fontPath(const QString& fontName); }; From f3a7e8623f0d064512295fb9580e1d2327fe26e7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 08:39:29 -0700 Subject: [PATCH 058/203] Remove unused UI screen --- launchers/qt/src/LauncherState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 874f532e7d..8eb807b26b 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -103,7 +103,7 @@ void LauncherState::setLastLoginErrorMessage(const QString& msg) { } static const std::array QML_FILE_FOR_UI_STATE = - { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml", "DisplayName.qml", + { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { From a7ea16f68510355de6fe8f0d58b0e9640cb324f1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 09:19:05 -0700 Subject: [PATCH 059/203] Add HFTextHeader and HFTextRegular --- launchers/qt/resources/qml/HFControls/HFTextHeader.qml | 8 ++++++++ launchers/qt/resources/qml/HFControls/HFTextRegular.qml | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 launchers/qt/resources/qml/HFControls/HFTextHeader.qml create mode 100644 launchers/qt/resources/qml/HFControls/HFTextRegular.qml diff --git a/launchers/qt/resources/qml/HFControls/HFTextHeader.qml b/launchers/qt/resources/qml/HFControls/HFTextHeader.qml new file mode 100644 index 0000000000..f0349d7f5c --- /dev/null +++ b/launchers/qt/resources/qml/HFControls/HFTextHeader.qml @@ -0,0 +1,8 @@ +import QtQuick 2.3 +import QtQuick 2.1 + +Text { + font.family: "Graphik Semibold" + font.pointSize: 24 + color: "#ffffff" +} diff --git a/launchers/qt/resources/qml/HFControls/HFTextRegular.qml b/launchers/qt/resources/qml/HFControls/HFTextRegular.qml new file mode 100644 index 0000000000..ae600c12a5 --- /dev/null +++ b/launchers/qt/resources/qml/HFControls/HFTextRegular.qml @@ -0,0 +1,8 @@ +import QtQuick 2.3 +import QtQuick 2.1 + +Text { + font.family: "Graphik Regular" + font.pointSize: 10.5 + color: "#C4C4C4" +} From 4fca8ae31a80d7b4014ba06c46d9323e64af91e0 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 3 Oct 2019 09:26:58 -0700 Subject: [PATCH 060/203] remove warnings --- launchers/qt/src/LauncherState.cpp | 2 +- launchers/qt/src/LauncherState.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 8eb807b26b..ebfbc22ba4 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -47,7 +47,7 @@ void LauncherState::toggleDebugState() { void LauncherState::gotoNextDebugScreen() { if (_currentDebugScreen < (UIState::UI_STATE_NUM - 1)) { _currentDebugScreen = (UIState)(_currentDebugScreen + 1); - UIState updatedUIState = getUIState(); + //UIState updatedUIState = getUIState(); emit uiStateChanged(); emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); _uiState = getUIState(); diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 0842eafa52..8ed099bda3 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -25,7 +25,7 @@ class LauncherState : public QObject { Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged) Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged) - Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged); + Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged) Q_PROPERTY(QString buildVersion READ getBuildVersion) public: From a71d8ec9ce559ec7076812787df222162c74fe9d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 11:16:15 -0700 Subject: [PATCH 061/203] Update qt lite urls in qt launcher --- launchers/qt/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 8ecf42942a..5c2da72fb4 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -45,8 +45,8 @@ if (WIN32) ExternalProject_Add( qtlite - URL "https://hifi-public.s3.amazonaws.com/huffman/launcher/qt-lite-ssl_2019-9-19.zip" - URL_HASH MD5=8b7a0b8fb772a014a3276274f40a9d14 #83eeba1565e5727aef11655acf893c15 + URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win.zip" + URL_HASH MD5=8b7a0b8fb772a014a3276274f40a9d14 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" @@ -73,7 +73,7 @@ endif () if (APPLE) ExternalProject_Add( qtlite - URL "https://dantescalves.com/launcher/qt-lite-5.9.9-mac.zip" + URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-mac.zip" URL_HASH MD5=0cd78d40e5f539a7e314cf99b6cae0d0 CONFIGURE_COMMAND "" BUILD_COMMAND "" From 8e310f3ae6d6ae8a11513a83ce7113361c5d0614 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 11:16:51 -0700 Subject: [PATCH 062/203] Add signup error messages to qt launcher --- .../qml/HFBase/CreateAccountBase.qml | 23 +++++++++++++ launchers/qt/src/Helper_windows.cpp | 5 ++- launchers/qt/src/LauncherState.cpp | 34 ++++++++++++++++--- launchers/qt/src/LauncherState.h | 6 ++++ launchers/qt/src/SignupRequest.cpp | 1 + 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 0c1e9f4421..8b429bae26 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -28,6 +28,7 @@ Item { lineHeight: 35 lineHeightMode: Text.FixedHeight text: root.titleText + visible: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh." anchors { top: root.top topMargin: 29 @@ -42,6 +43,8 @@ Item { height: 22 text: "Use the email address that you registered with." + visible: LauncherState.lastSignupErrorMessage.length == 0 + anchors { left: root.left leftMargin: root.marginLeft @@ -50,6 +53,26 @@ Item { } } + HFTextRegular { + id: error + width: 425 + height: 22 + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + color: "#FF9999" + + visible: LauncherState.lastSignupErrorMessage.length > 0 + text: LauncherState.lastSignupErrorMessage + anchors { + left: root.left + right: root.right + top: title.bottom + topMargin: 18 + } + } + HFTextField { id: email width: 430 diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index 849f34d08c..743e0681e7 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -1,5 +1,7 @@ #include "Helper.h" +#include + #include "windows.h" #include "winnls.h" #include "shobjidl.h" @@ -8,7 +10,6 @@ #include "shlguid.h" #include #include -#include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath, const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { @@ -50,8 +51,6 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - - QCoreApplication::instance()->quit(); } void launchAutoUpdater(const QString& autoUpdaterPath) { diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 8eb807b26b..1049972437 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -97,6 +97,11 @@ bool LauncherState::shouldDownloadContentCache() const { return !_contentCacheURL.isEmpty() && !QFile::exists(getContentCachePath()); } +void LauncherState::setLastSignupErrorMessage(const QString& msg) { + _lastSignupErrorMessage = msg; + emit lastSignupErrorMessageChanged(); +} + void LauncherState::setLastLoginErrorMessage(const QString& msg) { _lastLoginErrorMessage = msg; emit lastLoginErrorMessageChanged(); @@ -108,10 +113,10 @@ static const std::array QML_FILE_ void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { if (_applicationState != state) { + qDebug() << "Unexpected state, current: " << _applicationState << ", expected: " << state; #ifdef BREAK_ON_ERROR __debugbreak(); #endif - setApplicationState(ApplicationState::UnexpectedError); } } @@ -122,10 +127,10 @@ void LauncherState::ASSERT_STATE(const std::vectorgetError(); emit lastSignupErrorChanged(); - if (_lastSignupError != SignupRequest::Error::None) { + auto err = signupRequest->getError(); + if (err == SignupRequest::Error::ExistingUsername) { + setLastSignupErrorMessage(_username + " is already taken - please try a different username."); + setApplicationState(ApplicationState::WaitingForSignup); + return; + } else if (err == SignupRequest::Error::BadPassword) { + setLastSignupErrorMessage("That's an invalid password - please try another password."); + setApplicationState(ApplicationState::WaitingForSignup); + return; + } else if (err == SignupRequest::Error::BadUsername) { + setLastSignupErrorMessage("That's an invalid username - please try another username."); + setApplicationState(ApplicationState::WaitingForSignup); + return; + } else if (err == SignupRequest::Error::UserProfileAlreadyCompleted || err == SignupRequest::Error::NoSuchEmail) { + setLastSignupErrorMessage("That email does not have an account setup for it, or it was previously completed."); + setApplicationState(ApplicationState::WaitingForSignup); + return; + } else if (err != SignupRequest::Error::None) { setApplicationStateError("Failed to sign up"); return; } @@ -418,7 +441,7 @@ void LauncherState::requestSettings() { } void LauncherState::downloadClient() { - ASSERT_STATE(ApplicationState::RequestingLogin); + ASSERT_STATE({ ApplicationState::RequestingLogin, ApplicationState::RequestingLoginAfterSignup }); Build build; if (!_latestBuilds.getBuild(_buildTag, &build)) { @@ -675,6 +698,8 @@ void LauncherState::installContentCache() { } +#include +#include void LauncherState::launchClient() { ASSERT_STATE({ ApplicationState::RequestingLogin, @@ -714,6 +739,7 @@ void LauncherState::launchClient() { QString contentCachePath = _launcherDirectory.filePath("cache"); ::launchClient(clientPath, _config.homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse); + QTimer::singleShot(3000, QCoreApplication::instance(), &QCoreApplication::quit); } void LauncherState::setApplicationStateError(QString errorMessage) { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 0842eafa52..3c3b7a31c2 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -26,6 +26,7 @@ class LauncherState : public QObject { Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged) Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged); + Q_PROPERTY(QString lastSignupErrorMessage READ getLastSignupErrorMessage NOTIFY lastSignupErrorMessageChanged); Q_PROPERTY(QString buildVersion READ getBuildVersion) public: @@ -90,6 +91,9 @@ public: void setLastLoginErrorMessage(const QString& msg); QString getLastLoginErrorMessage() const { return _lastLoginErrorMessage; } + void setLastSignupErrorMessage(const QString& msg); + QString getLastSignupErrorMessage() const { return _lastSignupErrorMessage; } + QString getBuildVersion() { return QString(LAUNCHER_BUILD_VERSION); } void setApplicationStateError(QString errorMessage); @@ -136,6 +140,7 @@ signals: void applicationStateChanged(); void downloadProgressChanged(); void lastSignupErrorChanged(); + void lastSignupErrorMessageChanged(); void lastLoginErrorMessageChanged(); private slots: @@ -169,6 +174,7 @@ private: LoginToken _loginResponse; SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; QString _lastLoginErrorMessage{ "" }; + QString _lastSignupErrorMessage{ "" }; QString _displayName; QString _applicationErrorMessage; QString _currentClientVersion; diff --git a/launchers/qt/src/SignupRequest.cpp b/launchers/qt/src/SignupRequest.cpp index 0a2c554f8d..91f495e73a 100644 --- a/launchers/qt/src/SignupRequest.cpp +++ b/launchers/qt/src/SignupRequest.cpp @@ -44,6 +44,7 @@ void SignupRequest::receivedResponse() { } auto data = reply->readAll(); + qDebug() << "Signup response: " << data; QJsonParseError parseError; auto doc = QJsonDocument::fromJson(data, &parseError); From 9abc9f5f70d254cf7944e20f05f9cca0c9efc133 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 12:16:18 -0700 Subject: [PATCH 063/203] Update qt launcher style and fix display name being set to anon --- launchers/qt/resources/qml/Download.qml | 43 ++++--------------- .../qt/resources/qml/DownloadFinished.qml | 20 +++------ .../qml/HFBase/CreateAccountBase.qml | 14 +----- launchers/qt/resources/qml/HFBase/Error.qml | 35 ++++++--------- .../qt/resources/qml/HFBase/LoginBase.qml | 14 +----- .../resources/qml/HFControls/HFTextLogo.qml | 14 ++++++ launchers/qt/resources/qml/root.qml | 8 ++-- launchers/qt/src/Helper_windows.cpp | 5 ++- 8 files changed, 49 insertions(+), 104 deletions(-) create mode 100644 launchers/qt/resources/qml/HFControls/HFTextLogo.qml diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index 8303121479..04c2adacd2 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -1,5 +1,6 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 +import "HFControls" Item { id: root @@ -24,7 +25,7 @@ Item { anchors { top: root.top - topMargin: 48 + topMargin: 98 horizontalCenter: root.horizontalCenter } @@ -38,14 +39,10 @@ Item { } } - Text { + HFTextHeader { id: firstText - width: 300 - height: 41 - font.family: "Graphik" - font.pixelSize: 28 + text: "Setup will take a moment" - color: "#FFFFFF" anchors { top: logo.bottom @@ -54,16 +51,14 @@ Item { } } - Text { + HFTextRegular { id: secondText - font.family: "Graphik" - font.pixelSize: 14 - text: "We're getting your headquaters ready" - color: "#FFFFFF" + + text: "We're getting everything setup for you." anchors { top: firstText.bottom - topMargin: 14 + topMargin: 8 horizontalCenter: logo.horizontalCenter } } @@ -101,28 +96,6 @@ Item { } } - - Text { - width: 100 - height: 17 - - text: "High Fidelity" - font.bold: true - font.family: "Graphik" - font.pixelSize: 24 - font.letterSpacing: -1 - color: "#FFFFFF" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - anchors { - bottom: root.bottom - bottomMargin: 15 - left: root.left - leftMargin: 30 - } - } - Component.onCompleted: { root.parent.setStateInfoState("right"); root.parent.setBuildInfoState("left"); diff --git a/launchers/qt/resources/qml/DownloadFinished.qml b/launchers/qt/resources/qml/DownloadFinished.qml index ecd2e6d0c2..efe5b26925 100644 --- a/launchers/qt/resources/qml/DownloadFinished.qml +++ b/launchers/qt/resources/qml/DownloadFinished.qml @@ -4,6 +4,7 @@ import "HFControls" Item { id: root + anchors.centerIn: parent Image { @@ -17,7 +18,7 @@ Item { } - Image { + Image { id: logo width: 132 height: 134 @@ -25,21 +26,15 @@ Item { anchors { top: root.top - topMargin: 84 + topMargin: 144 horizontalCenter: root.horizontalCenter } } - Text { + HFTextHeader { id: header - width: 87 - height: 31 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - font.family: "Graphik" - font.bold: true - font.pixelSize: 28 - color: "#FFFFFF" text: "You're all set!" anchors { top: logo.bottom @@ -48,13 +43,8 @@ Item { } } - Text { + HFTextRegular { id: description - width: 100 - height: 40 - font.family: "Graphik" - font.pixelSize: 14 - color: "#FFFFFF" text: "We will see you in world." horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 8b429bae26..65f983074a 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -191,19 +191,7 @@ Item { } } - Text { - width: 100 - height: 17 - - text: "High Fidelity" - font.bold: true - font.family: "Graphik" - font.pixelSize: 24 - font.letterSpacing: -1 - color: "#FFFFFF" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - + HFTextLogo { anchors { bottom: root.bottom bottomMargin: 46 diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index 09df9e39a7..030963f64e 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -26,20 +26,19 @@ Item { anchors { top: root.top - topMargin: 84 + topMargin: 98 horizontalCenter: root.horizontalCenter } } - Text { + HFTextHeader { id: header + width: 87 height: 31 - font.family: "Graphik" - font.bold: true - font.pixelSize: 28 - color: "#FFFFFF" + text: "Uh oh." + anchors { top: logo.bottom topMargin: 26 @@ -47,16 +46,17 @@ Item { } } - Text { + HFTextRegular { id: description + width: 100 height: 40 - font.family: "Graphik" - font.pixelSize: 14 - color: "#FFFFFF" + text: "We seem to have a problem.\n Please restart HQ Launcher" + horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + anchors { top: header.bottom topMargin: 8 @@ -69,8 +69,7 @@ Item { id: button width: 166 height: 35 - font.family: "Graphik" - font.pixelSize: 18 + text: "Restart" anchors { @@ -85,19 +84,9 @@ Item { } - Text { + HFTextLogo { id: hifilogo - width: 100 - height: 18 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Graphik" - font.pixelSize: 18 - font.bold: true - color: "#FFFFFF" - - text: "High Fidelity" anchors { right: root.right rightMargin: 17 diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 109e6e50b1..69c712b887 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -165,19 +165,7 @@ Item { } } - Text { - width: 100 - height: 17 - - text: "High Fidelity" - font.bold: true - font.family: "Graphik" - font.pixelSize: 18 - font.letterSpacing: -1 - color: "#FFFFFF" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - + HFTextLogo { anchors { bottom: root.bottom bottomMargin: 58 diff --git a/launchers/qt/resources/qml/HFControls/HFTextLogo.qml b/launchers/qt/resources/qml/HFControls/HFTextLogo.qml new file mode 100644 index 0000000000..f3c7468373 --- /dev/null +++ b/launchers/qt/resources/qml/HFControls/HFTextLogo.qml @@ -0,0 +1,14 @@ +import QtQuick 2.3 +import QtQuick 2.1 + +Text { + width: 100 + height: 17 + + text: "High Fidelity" + font.bold: true + font.family: "Graphik Semibold" + font.pointSize: 14 + font.letterSpacing: -1 + color: "#FFFFFF" +} diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index 2aa2a34af8..ba39ac3cda 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -35,7 +35,7 @@ Item { loader.source = url; } - Text { + HFTextRegular { id: stateInfo font.pixelSize: 12 @@ -66,7 +66,7 @@ Item { ] } - Text { + HFTextRegular { id: buildInfo font.pixelSize: 12 @@ -80,8 +80,8 @@ Item { bottom: root.bottom } - color: "#777" - text: "v." + LauncherState.buildVersion; + color: "#666" + text: "V." + LauncherState.buildVersion; states: [ State { diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index 743e0681e7..f31f5c7c7c 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -18,10 +18,13 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr QString params = "--url \"" + homePath + "\"" + " --setBookmark \"hqhome=" + homePath + "\"" + " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\"" - + " --displayName \"" + displayName + "\"" + " --cache \"" + contentCachePath + "\"" + " --suppress-settings-reset --no-launcher --no-updater"; + if (!displayName.isEmpty()) { + params += " --displayName \"" + displayName + "\""; + } + if (!loginResponseToken.isEmpty()) { params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\""; } From 22c96e782219f6ebfdba3c2bf94b2c875dd7e741 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 12:34:45 -0700 Subject: [PATCH 064/203] Update styling of qt launcher --- .../qt/resources/qml/HFBase/CreateAccountBase.qml | 12 +++++------- launchers/qt/resources/qml/HFBase/LoginBase.qml | 11 ++++------- launchers/qt/resources/qml/HFControls/HFButton.qml | 7 +++++++ .../qt/resources/qml/HFControls/HFTextError.qml | 7 +++++++ .../qt/resources/qml/HFControls/HFTextField.qml | 1 + 5 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 launchers/qt/resources/qml/HFControls/HFTextError.qml diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 65f983074a..84633da3ac 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -53,16 +53,15 @@ Item { } } - HFTextRegular { + HFTextError { id: error + width: 425 height: 22 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - color: "#FF9999" - visible: LauncherState.lastSignupErrorMessage.length > 0 text: LauncherState.lastSignupErrorMessage anchors { @@ -167,9 +166,6 @@ Item { Text { - width: 214 - height: 12 - text: "Already have an account?" font.family: "Graphik" font.pixelSize: 14 @@ -177,13 +173,15 @@ Item { anchors { top: button.bottom - topMargin: 19 + topMargin: 16 left: button.left } MouseArea { anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { console.log("clicked"); LauncherState.gotoLogin(); diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 69c712b887..8238989eb6 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -48,7 +48,7 @@ Item { } } - HFTextRegular { + HFTextError { id: error width: 425 height: 22 @@ -56,8 +56,6 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - color: "#FF9999" - visible: LauncherState.lastLoginErrorMessage.length > 0 text: LauncherState.lastLoginErrorMessage anchors { @@ -141,9 +139,6 @@ Item { } Text { - width: 214 - height: 12 - text: "Create New Account" font.family: "Graphik" font.pixelSize: 14 @@ -151,13 +146,15 @@ Item { anchors { top: button.bottom - topMargin: 19 + topMargin: 16 left: button.left } MouseArea { anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { console.log("clicked"); LauncherState.gotoSignup(); diff --git a/launchers/qt/resources/qml/HFControls/HFButton.qml b/launchers/qt/resources/qml/HFControls/HFButton.qml index a1ebb50ee8..0c2bdb0617 100644 --- a/launchers/qt/resources/qml/HFControls/HFButton.qml +++ b/launchers/qt/resources/qml/HFControls/HFButton.qml @@ -31,4 +31,11 @@ Button { verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } + + MouseArea { + id: mouseArea + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onPressed: mouse.accepted = false + } } diff --git a/launchers/qt/resources/qml/HFControls/HFTextError.qml b/launchers/qt/resources/qml/HFControls/HFTextError.qml new file mode 100644 index 0000000000..f1862c764c --- /dev/null +++ b/launchers/qt/resources/qml/HFControls/HFTextError.qml @@ -0,0 +1,7 @@ +import QtQuick 2.3 +import QtQuick 2.1 + +HFTextRegular { + color: "#FF9999" +} + diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index e5abab7b52..d758fcc2dc 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -41,6 +41,7 @@ TextField { MouseArea { anchors.fill: parent + cursorShape: Qt.PointingHandCursor onClicked: { if (control.echoMode === TextInput.Password) { control.echoMode = TextInput.Normal; From 5c93f1bb6ee593a67f565fa6c244d34e701faa64 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 3 Oct 2019 13:02:45 -0700 Subject: [PATCH 065/203] Update qt launcher button style and error color --- launchers/qt/resources/qml/HFBase/Error.qml | 17 ++-------- .../qt/resources/qml/HFBase/LoginBase.qml | 1 - .../qt/resources/qml/HFControls/HFButton.qml | 3 ++ .../resources/qml/HFControls/HFTextError.qml | 2 +- .../resources/qml/HFControls/HFTextField.qml | 2 +- launchers/qt/resources/qml/root.qml | 31 ------------------- 6 files changed, 7 insertions(+), 49 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index 030963f64e..9c176d5349 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -68,13 +68,12 @@ Item { HFButton { id: button width: 166 - height: 35 - text: "Restart" + text: "RESTART" anchors { top: description.bottom - topMargin: 15 + topMargin: 60 horizontalCenter: description.horizontalCenter } @@ -83,18 +82,6 @@ Item { } } - - HFTextLogo { - id: hifilogo - - anchors { - right: root.right - rightMargin: 17 - bottom: root.bottom - bottomMargin: 17 - } - } - Component.onCompleted: { root.parent.setStateInfoState("left"); root.parent.setBuildInfoState("right"); diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 8238989eb6..fd9a52bc94 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -125,7 +125,6 @@ Item { HFButton { id: button width: 110 - height: 50 text: "NEXT" diff --git a/launchers/qt/resources/qml/HFControls/HFButton.qml b/launchers/qt/resources/qml/HFControls/HFButton.qml index 0c2bdb0617..ad04286114 100644 --- a/launchers/qt/resources/qml/HFControls/HFButton.qml +++ b/launchers/qt/resources/qml/HFControls/HFButton.qml @@ -3,6 +3,9 @@ import QtQuick.Controls 2.1 Button { id: control + + height: 50 + property string backgroundColor: "#00000000" property string borderColor: "#FFFFFF" property string textColor: "#FFFFFF" diff --git a/launchers/qt/resources/qml/HFControls/HFTextError.qml b/launchers/qt/resources/qml/HFControls/HFTextError.qml index f1862c764c..2bc7d94ecf 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextError.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextError.qml @@ -2,6 +2,6 @@ import QtQuick 2.3 import QtQuick 2.1 HFTextRegular { - color: "#FF9999" + color: "#FF0014" } diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index d758fcc2dc..361e81b951 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -6,7 +6,7 @@ TextField { font.family: "Graphik Regular" font.pointSize: 10.5 - color: "#7e8c81" + color: text.length == 0 ? "#7e8c81" : "#000000" property bool togglePasswordField: false verticalAlignment: TextInput.AlignVCenter diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index ba39ac3cda..f26bb1ec4b 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -35,37 +35,6 @@ Item { loader.source = url; } - HFTextRegular { - id: stateInfo - font.pixelSize: 12 - - anchors.right: root.right - anchors.top: root.top - - color: "#FFFFFF" - text: LauncherState.uiState.toString() + " - " + LauncherState.applicationState - - states: [ - State { - name: "left" - AnchorChanges { - target: stateInfo - anchors.left: root.left - anchors.right: undefined - } - }, - - State { - name: "right" - AnchorChanges { - target: stateInfo - anchors.right: root.right - anchors.left: undefined - } - } - ] - } - HFTextRegular { id: buildInfo From 130c727bee92d47346c4ebf85e6ac65e7429a261 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 3 Oct 2019 13:45:09 -0700 Subject: [PATCH 066/203] fix qml error --- launchers/qt/CMakeLists.txt | 1 - launchers/qt/resources/qml/root.qml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 5c2da72fb4..7056e953ba 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -191,7 +191,6 @@ elseif (APPLE) add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files} ${RES_SOURCES}) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME} MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}) - set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index f26bb1ec4b..c7281919fe 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -19,7 +19,7 @@ Item { } function setStateInfoState(state) { - stateInfo.state = state; + //stateInfo.state = state; } } From caa99c1d22ae68e73f9bd85913834cdcec3dda41 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 3 Oct 2019 18:49:34 -0700 Subject: [PATCH 067/203] windows uninstall --- .../qt/resources/qml/HFBase/LoginBase.qml | 2 + launchers/qt/src/.#main.cpp | 1 + launchers/qt/src/Helper_windows.cpp | 1 + .../qt/src/LauncherInstaller_windows.cpp | 67 ++++++++++--------- launchers/qt/src/main.cpp | 2 +- 5 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 launchers/qt/src/.#main.cpp diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index fd9a52bc94..13c00dff19 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -120,6 +120,8 @@ Item { horizontalCenter: instruction.horizontalCenter topMargin: 4 } + + onAccepted: LauncherState.login(username.text, password.text, displayName.text) } HFButton { diff --git a/launchers/qt/src/.#main.cpp b/launchers/qt/src/.#main.cpp new file mode 100644 index 0000000000..fee0637218 --- /dev/null +++ b/launchers/qt/src/.#main.cpp @@ -0,0 +1 @@ +Dante@DESKTOP-9JK5VFR.10856:1570152789 \ No newline at end of file diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index f31f5c7c7c..a6bb1dabd5 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -8,6 +8,7 @@ #include "objbase.h" #include "objidl.h" #include "shlguid.h" +#include #include #include diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index ffd6218fab..d9ecde395b 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -47,42 +47,47 @@ void LauncherInstaller::install() { qDebug() << "not successful"; } - qDebug() << "LauncherInstaller: create uninstall link"; - QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.lnk"; - if (QFile::exists(uninstallLinkPath)) { - QFile::remove(uninstallLinkPath); - } - - - - QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); - QString applicationPath = _launcherApplicationsDir.absolutePath(); - - QString appStartLinkPath = applicationPath + "/HQ.lnk"; - QString uninstallAppStartLinkPath = applicationPath + "/Uninstall HQ.lnk"; - QString desktopAppLinkPath = desktopPath + "/HQ.lnk"; - - - createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(), - (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); - - createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)uninstallAppStartLinkPath.toStdString().c_str(), - (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); - - createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)desktopAppLinkPath.toStdString().c_str(), - (LPCSTR)("Click to Setup and Launch HQ")); - - createSymbolicLink((LPCSTR)oldLauncherPath.toStdString().c_str(), (LPCSTR)appStartLinkPath.toStdString().c_str(), - (LPCSTR)("Click to Setup and Launch HQ")); + deleteShortcuts(); + createShortcuts(); createApplicationRegistryKeys(); } else { - qDebug() << "FAILED!!!!!!!"; + qDebug() << "Failed to install HQ Launcher"; } } +void LauncherInstaller::createShortcuts() { + QString launcherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; + + QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.lnk"; + QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QString applicationPath = _launcherApplicationsDir.absolutePath(); + + QString appStartLinkPath = applicationPath + "/HQ Launcher.lnk"; + QString uninstallAppStartLinkPath = applicationPath + "/Uninstall HQ.lnk"; + QString desktopAppLinkPath = desktopPath + "/HQ Launcher.lnk"; + + + createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); + + createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallAppStartLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall")); + + createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)desktopAppLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Setup and Launch HQ")); + + createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)appStartLinkPath.toStdString().c_str(), + (LPCSTR)("Click to Setup and Launch HQ")); +} + void LauncherInstaller::uninstall() { qDebug() << "Uninstall Launcher"; + deleteShortcuts(); + deleteApplicationRegistryKeys(); +} + +void LauncherInstaller::deleteShortcuts() { QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString applicationPath = _launcherApplicationsDir.absolutePath(); @@ -91,7 +96,7 @@ void LauncherInstaller::uninstall() { QFile::remove(uninstallLinkPath); } - QString appStartLinkPath = applicationPath + "/HQ.lnk"; + QString appStartLinkPath = applicationPath + "/HQ Launcher.lnk"; if (QFile::exists(appStartLinkPath)) { QFile::remove(appStartLinkPath); } @@ -101,12 +106,10 @@ void LauncherInstaller::uninstall() { QFile::remove(uninstallAppStartLinkPath); } - QString desktopAppLinkPath = desktopPath + "/HQ.lnk"; + QString desktopAppLinkPath = desktopPath + "/HQ Launcher.lnk"; if (QFile::exists(desktopAppLinkPath)) { QFile::remove(desktopAppLinkPath); } - - deleteApplicationRegistryKeys(); } diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 0965a5742f..29d5251cc1 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -46,7 +46,7 @@ int main(int argc, char *argv[]) { #ifdef Q_OS_WIN LauncherInstaller launcherInstaller(argv[0]); if (options->contains("--restart") || launcherInstaller.runningOutsideOfInstallDir()) { - launcherInstaller.install(); + launcherInstaller.install(); } else if (options->contains("--uninstall")) { launcherInstaller.uninstall(); return 0; From 903fe71ed522bc4680566bd4f7eefcfa375081d0 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 3 Oct 2019 18:51:19 -0700 Subject: [PATCH 068/203] remove grabage file --- launchers/qt/src/.#main.cpp | 1 - launchers/qt/src/main.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 launchers/qt/src/.#main.cpp diff --git a/launchers/qt/src/.#main.cpp b/launchers/qt/src/.#main.cpp deleted file mode 100644 index fee0637218..0000000000 --- a/launchers/qt/src/.#main.cpp +++ /dev/null @@ -1 +0,0 @@ -Dante@DESKTOP-9JK5VFR.10856:1570152789 \ No newline at end of file diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 29d5251cc1..0965a5742f 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -46,7 +46,7 @@ int main(int argc, char *argv[]) { #ifdef Q_OS_WIN LauncherInstaller launcherInstaller(argv[0]); if (options->contains("--restart") || launcherInstaller.runningOutsideOfInstallDir()) { - launcherInstaller.install(); + launcherInstaller.install(); } else if (options->contains("--uninstall")) { launcherInstaller.uninstall(); return 0; From fe64f6e9ecb61a224ff7a2da1eeb4148690350fc Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 4 Oct 2019 10:50:03 -0700 Subject: [PATCH 069/203] Update qt launcher styling, make unpinnable, and update name --- launchers/qt/resources/qml/Download.qml | 1 - .../qt/resources/qml/DownloadFinished.qml | 1 - .../qml/HFBase/CreateAccountBase.qml | 18 +- launchers/qt/resources/qml/HFBase/Error.qml | 6 +- .../qt/resources/qml/HFBase/LoginBase.qml | 279 +++++++++--------- .../resources/qml/HFControls/HFTextField.qml | 2 + .../resources/qml/HFControls/HFTextLogo.qml | 3 - launchers/qt/resources/qml/SplashScreen.qml | 1 - launchers/qt/resources/qml/root.qml | 4 - launchers/qt/src/Helper_windows.cpp | 4 +- launchers/qt/src/LauncherState.cpp | 12 +- launchers/qt/src/LauncherWindow.cpp | 26 ++ launchers/qt/src/LauncherWindow.h | 2 +- launchers/qt/src/main.cpp | 1 + 14 files changed, 190 insertions(+), 170 deletions(-) diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index 04c2adacd2..00542d02d0 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -97,7 +97,6 @@ Item { } Component.onCompleted: { - root.parent.setStateInfoState("right"); root.parent.setBuildInfoState("left"); } } diff --git a/launchers/qt/resources/qml/DownloadFinished.qml b/launchers/qt/resources/qml/DownloadFinished.qml index efe5b26925..9a4b3e2f3d 100644 --- a/launchers/qt/resources/qml/DownloadFinished.qml +++ b/launchers/qt/resources/qml/DownloadFinished.qml @@ -56,7 +56,6 @@ Item { } Component.onCompleted: { - root.parent.setStateInfoState("right"); root.parent.setBuildInfoState("left"); } } diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 84633da3ac..7b122b12db 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -24,7 +24,6 @@ Item { HFTextHeader { id: title width: 481 - height: 27 lineHeight: 35 lineHeightMode: Text.FixedHeight text: root.titleText @@ -40,7 +39,6 @@ Item { HFTextRegular { id: instruction width: 425 - height: 22 text: "Use the email address that you registered with." visible: LauncherState.lastSignupErrorMessage.length == 0 @@ -57,16 +55,15 @@ Item { id: error width: 425 - height: 22 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap visible: LauncherState.lastSignupErrorMessage.length > 0 text: LauncherState.lastSignupErrorMessage + anchors { left: root.left - right: root.right + leftMargin: root.marginLeft top: title.bottom topMargin: 18 } @@ -75,7 +72,7 @@ Item { HFTextField { id: email width: 430 - height: 50 + placeholderText: "Email Address" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -89,7 +86,7 @@ Item { HFTextField { id: username width: 430 - height: 50 + placeholderText: root.usernamePlaceholder seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -103,7 +100,7 @@ Item { HFTextField { id: passwordField width: 430 - height: 50 + placeholderText: root.passwordPlaceholder seperatorColor: Qt.rgba(1, 1, 1, 0.3) togglePasswordField: true @@ -136,7 +133,6 @@ Item { HFTextField { id: displayName width: 430 - height: 50 placeholderText: "Display Name" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -150,7 +146,6 @@ Item { HFButton { id: button width: 134 - height: 50 text: "NEXT" @@ -199,7 +194,6 @@ Item { } Component.onCompleted: { - root.parent.setStateInfoState("right"); root.parent.setBuildInfoState("left"); } } diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index 9c176d5349..6f30a8a464 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -49,10 +49,7 @@ Item { HFTextRegular { id: description - width: 100 - height: 40 - - text: "We seem to have a problem.\n Please restart HQ Launcher" + text: "We seem to have a problem.\nPlease restart HQ Launcher" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -83,7 +80,6 @@ Item { } Component.onCompleted: { - root.parent.setStateInfoState("left"); root.parent.setBuildInfoState("right"); } diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index fd9a52bc94..cedad8cc32 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -15,163 +15,172 @@ Item { transformOrigin: Item.Center rotation: 0 } - HFTextHeader { - id: title - width: 325 - height: 26 - font.bold: true - text: "Please Log in" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + + Item { + width: 353 + height: root.height + + anchors { top: root.top - topMargin: 40 horizontalCenter: root.horizontalCenter } - } - HFTextRegular { - id: instruction - width: 425 - height: 22 + HFTextHeader { + id: title - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + font.bold: true - visible: LauncherState.lastLoginErrorMessage.length == 0 - text: "Use the account credentials you created at sign-up" - anchors { - left: root.left - right: root.right - top: title.bottom - topMargin: 18 - } - } + text: "Please Log in" - HFTextError { - id: error - width: 425 - height: 22 + anchors { + top: parent.top + topMargin: 40 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - visible: LauncherState.lastLoginErrorMessage.length > 0 - text: LauncherState.lastLoginErrorMessage - anchors { - left: root.left - right: root.right - top: title.bottom - topMargin: 18 - } - } - - HFTextField { - id: username - width: 353 - height: 50 - - placeholderText: "Username" - - seperatorColor: Qt.rgba(1, 1, 1, 0.3) - anchors { - top: error.bottom - horizontalCenter: error.horizontalCenter - topMargin: 24 - } - } - - HFTextField { - id: password - width: 353 - height: 50 - placeholderText: "Password" - togglePasswordField: true - echoMode: TextInput.Password - seperatorColor: Qt.rgba(1, 1, 1, 0.3) - anchors { - top: username.bottom - horizontalCenter: instruction.horizontalCenter - topMargin: 25 - } - } - - - HFTextRegular { - id: displayText - - text: "You can change this at anytime from your profile." - - anchors { - top: password.bottom - topMargin: 50 - left: password.left - } - } - - HFTextField { - id: displayName - width: 353 - height: 50 - placeholderText: "Display name" - seperatorColor: Qt.rgba(1, 1, 1, 0.3) - anchors { - top: displayText.bottom - horizontalCenter: instruction.horizontalCenter - topMargin: 4 - } - } - - HFButton { - id: button - width: 110 - - text: "NEXT" - - anchors { - top: displayName.bottom - left: displayName.left - topMargin: 25 + horizontalCenter: parent.horizontalCenter + } } - onClicked: LauncherState.login(username.text, password.text, displayName.text) - } + HFTextRegular { + id: instruction - Text { - text: "Create New Account" - font.family: "Graphik" - font.pixelSize: 14 - color: "#009EE0" + visible: LauncherState.lastLoginErrorMessage.length == 0 + text: "Use the account credentials you created at sign-up" + anchors { + left: parent.left + right: parent.right; - anchors { - top: button.bottom - topMargin: 16 - left: button.left + top: title.bottom + topMargin: 18 + } } - MouseArea { - anchors.fill: parent + HFTextError { + id: error - cursorShape: Qt.PointingHandCursor + visible: LauncherState.lastLoginErrorMessage.length > 0 + text: LauncherState.lastLoginErrorMessage + anchors { + top: title.bottom + topMargin: 18 - onClicked: { - console.log("clicked"); - LauncherState.gotoSignup(); + left: parent.left + right: parent.right; + } + } + + HFTextField { + id: username + + placeholderText: "Username" + + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: error.bottom + topMargin: 24 + + left: parent.left + right: parent.right; + } + } + + HFTextField { + id: password + + placeholderText: "Password" + togglePasswordField: true + echoMode: TextInput.Password + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: username.bottom + topMargin: 25 + + left: parent.left + right: parent.right; + } + } + + + HFTextRegular { + id: displayText + + text: "You can change this at anytime from your profile." + + anchors { + top: password.bottom + topMargin: 50 + + left: parent.left + right: parent.right; + } + } + + HFTextField { + id: displayName + + placeholderText: "Display name" + seperatorColor: Qt.rgba(1, 1, 1, 0.3) + anchors { + top: displayText.bottom + topMargin: 4 + + left: parent.left + right: parent.right; + } + } + + HFButton { + id: button + width: 110 + + text: "NEXT" + + anchors { + top: displayName.bottom + topMargin: 25 + + left: parent.left + } + + onClicked: LauncherState.login(username.text, password.text, displayName.text) + } + + Text { + id: createAccountLink + + text: "Create New Account" + font.family: "Graphik" + font.pixelSize: 14 + color: "#009EE0" + + anchors { + top: button.bottom + topMargin: 16 + left: parent.left + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + console.log("clicked"); + LauncherState.gotoSignup(); + } + } + } + + HFTextLogo { + anchors { + bottom: createAccountLink.bottom + + right: parent.right } } } - HFTextLogo { - anchors { - bottom: root.bottom - bottomMargin: 58 - right: root.right - rightMargin: 136 - } - } - Component.onCompleted: { - root.parent.setStateInfoState("left"); root.parent.setBuildInfoState("right"); } } diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index 361e81b951..cce0a0fc1e 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -4,6 +4,8 @@ import QtQuick.Controls 2.1 TextField { id: control + height: 50 + font.family: "Graphik Regular" font.pointSize: 10.5 color: text.length == 0 ? "#7e8c81" : "#000000" diff --git a/launchers/qt/resources/qml/HFControls/HFTextLogo.qml b/launchers/qt/resources/qml/HFControls/HFTextLogo.qml index f3c7468373..08025ef715 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextLogo.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextLogo.qml @@ -2,9 +2,6 @@ import QtQuick 2.3 import QtQuick 2.1 Text { - width: 100 - height: 17 - text: "High Fidelity" font.bold: true font.family: "Graphik Semibold" diff --git a/launchers/qt/resources/qml/SplashScreen.qml b/launchers/qt/resources/qml/SplashScreen.qml index 47dff2aa9d..0e2e522232 100644 --- a/launchers/qt/resources/qml/SplashScreen.qml +++ b/launchers/qt/resources/qml/SplashScreen.qml @@ -24,7 +24,6 @@ Item { } Component.onCompleted: { - root.parent.setStateInfoState("left"); root.parent.setBuildInfoState("right"); } } diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index f26bb1ec4b..e3f82450f2 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -17,10 +17,6 @@ Item { function setBuildInfoState(state) { buildInfo.state = state; } - - function setStateInfoState(state) { - stateInfo.state = state; - } } Component.onCompleted: { diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index f31f5c7c7c..fdaf5c3a4a 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -39,8 +39,8 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr // start the program up BOOL success = CreateProcess( - clientPath.toUtf8().data(), - params.toUtf8().data(), + clientPath.toLatin1().data(), + params.toLatin1().data(), nullptr, // Process handle not inheritable nullptr, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index a2f5892055..90fa6e66f1 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -325,15 +325,19 @@ void LauncherState::signup(QString email, QString username, QString password, QS setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::BadPassword) { - setLastSignupErrorMessage("That's an invalid password - please try another password."); + setLastSignupErrorMessage("That's an invalid password - passwords must be at least 6 characters."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::BadUsername) { setLastSignupErrorMessage("That's an invalid username - please try another username."); setApplicationState(ApplicationState::WaitingForSignup); return; - } else if (err == SignupRequest::Error::UserProfileAlreadyCompleted || err == SignupRequest::Error::NoSuchEmail) { - setLastSignupErrorMessage("That email does not have an account setup for it, or it was previously completed."); + } else if (err == SignupRequest::Error::UserProfileAlreadyCompleted) { + setLastSignupErrorMessage("That email has already been completed."); + setApplicationState(ApplicationState::WaitingForSignup); + return; + } else if (err == SignupRequest::Error::NoSuchEmail) { + setLastSignupErrorMessage("That email does not have an account setup for it."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err != SignupRequest::Error::None) { @@ -383,8 +387,6 @@ void LauncherState::login(QString username, QString password, QString displayNam _displayName = displayName; - qDebug() << "Got login: " << username << password; - auto request = new LoginRequest(); connect(request, &LoginRequest::finished, this, [this, request]() { diff --git a/launchers/qt/src/LauncherWindow.cpp b/launchers/qt/src/LauncherWindow.cpp index 3c13e5908a..53a781f1e1 100644 --- a/launchers/qt/src/LauncherWindow.cpp +++ b/launchers/qt/src/LauncherWindow.cpp @@ -4,6 +4,32 @@ #include +#ifdef Q_OS_WIN +#include +#include +#include +#endif + +LauncherWindow::LauncherWindow() { +#ifdef Q_OS_WIN + // On Windows, disable pinning of the launcher. + IPropertyStore* pps; + HWND id = (HWND)this->winId(); + if (id == NULL) { + qDebug() << "Failed to disable pinning, window id is null"; + } else { + HRESULT hr = SHGetPropertyStoreForWindow(id, IID_PPV_ARGS(&pps)); + if (SUCCEEDED(hr)) { + PROPVARIANT var; + var.vt = VT_BOOL; + var.boolVal = VARIANT_TRUE; + hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var); + pps->Release(); + } + } +#endif +} + void LauncherWindow::keyPressEvent(QKeyEvent* event) { QQuickView::keyPressEvent(event); if (!event->isAccepted()) { diff --git a/launchers/qt/src/LauncherWindow.h b/launchers/qt/src/LauncherWindow.h index 08e8cf4825..52b83b88c0 100644 --- a/launchers/qt/src/LauncherWindow.h +++ b/launchers/qt/src/LauncherWindow.h @@ -5,7 +5,7 @@ class LauncherWindow : public QQuickView { public: - LauncherWindow() = default; + LauncherWindow(); ~LauncherWindow() = default; void keyPressEvent(QKeyEvent* event) override; void mousePressEvent(QMouseEvent* event) override; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 0965a5742f..95d2c16185 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -61,6 +61,7 @@ int main(int argc, char *argv[]) { QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); + QCoreApplication::setApplicationName(name); Launcher launcher(argc, argv); From 0afbad6557fa7a689640993686597c53c82325b0 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Fri, 4 Oct 2019 14:30:34 -0700 Subject: [PATCH 070/203] cmake improvments --- launchers/qt/CMakeLists.txt | 9 ++--- .../qml/HFBase/CreateAccountBase.qml | 2 ++ launchers/qt/src/Helper_darwin.mm | 33 ++++++++++++++++--- .../qt/src/LauncherInstaller_windows.cpp | 5 +++ 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 7056e953ba..c087e87bf2 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -1,9 +1,8 @@ -cmake_minimum_required(VERSION 3.10) -project(HQLauncher) - +cmake_minimum_required(VERSION 3.0) if (APPLE) - set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9) + set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.10) endif() +project(HQLauncher) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules") include("cmake/macros/SetPackagingParameters.cmake") @@ -195,6 +194,8 @@ elseif (APPLE) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources" + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/resources/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/" COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/MacOS/" ln -sf ./HQ\ Launcher updater # Older versions of Launcher put updater in `/Contents/Resources/updater`. COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources" ln -sf ../MacOS/HQ\ Launcher updater diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 84633da3ac..283c7d7841 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -145,6 +145,8 @@ Item { leftMargin: root.marginLeft topMargin: 4 } + + onAccepted: LauncherState.signup(email.text, username.text, passwordField.text, displayName.text) } HFButton { diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index b4fb9f8a6e..422859fa67 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -42,10 +42,35 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr void launchAutoUpdater(const QString& autoUpdaterPath) { - NSTask* task = [[NSTask alloc] init]; - task.launchPath = [autoUpdaterPath.toNSString() stringByAppendingString:@"/Contents/Resources/updater"]; - task.arguments = @[[[NSBundle mainBundle] bundlePath], autoUpdaterPath.toNSString()]; - [task launch]; + NSException *exception; + bool launched = false; + // Older versions of Launcher put updater in `/Contents/Resources/updater`. + NSString* newLauncher = autoUpdaterPath.toNSString(); + for (NSString *bundlePath in @[@"/Contents/MacOS/updater", + @"/Contents/Resources/updater", + ]) { + NSTask* task = [[NSTask alloc] init]; + task.launchPath = [newLauncher stringByAppendingString: bundlePath]; + task.arguments = @[[[NSBundle mainBundle] bundlePath], newLauncher]; + + NSLog(@"launching updater: %@ %@", task.launchPath, task.arguments); + + @try { + [task launch]; + } + @catch (NSException *e) { + NSLog(@"couldn't launch updater: %@, %@", e.name, e.reason); + exception = e; + continue; + } + + launched = true; + break; + } + + if (!launched) { + @throw exception; + } exit(0); } diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index d9ecde395b..c91a001754 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -84,6 +84,11 @@ void LauncherInstaller::createShortcuts() { void LauncherInstaller::uninstall() { qDebug() << "Uninstall Launcher"; deleteShortcuts(); + + QString launcherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; + if (QFile::exists(launcherPath)) { + QFile::remove(launcherPath); + } deleteApplicationRegistryKeys(); } From f912268129a1479c2f8b975c02d643578c599ebe Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 4 Oct 2019 16:43:58 -0700 Subject: [PATCH 071/203] Update qt launcher to disable inputs when making requests --- .../resources/qml/HFBase/CreateAccountBase.qml | 17 ++++++++++++++++- launchers/qt/resources/qml/HFBase/LoginBase.qml | 12 ++++++++++++ .../qt/resources/qml/HFControls/HFButton.qml | 4 ++-- .../qt/resources/qml/HFControls/HFTextField.qml | 2 +- launchers/qt/resources/qml/SplashScreen.qml | 1 - launchers/qt/src/BuildsRequest.cpp | 2 -- launchers/qt/src/Launcher.cpp | 1 + launchers/qt/src/LauncherState.cpp | 10 ++++------ launchers/qt/src/LauncherState.h | 9 ++++----- launchers/qt/src/main.cpp | 3 ++- 10 files changed, 42 insertions(+), 19 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index b39479fbaf..cf03b282bf 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -1,6 +1,8 @@ import QtQuick 2.3 import QtQuick 2.1 + import "../HFControls" +import HQLauncher 1.0 Item { @@ -11,6 +13,8 @@ Item { property string passwordPlaceholder: "Set a password" property int marginLeft: root.width * 0.15 + property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForSignup + Image { anchors.centerIn: parent width: parent.width @@ -26,7 +30,7 @@ Item { width: 481 lineHeight: 35 lineHeightMode: Text.FixedHeight - text: root.titleText + text: root.titleText + " " + LauncherState.applicationState visible: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh." anchors { top: root.top @@ -73,6 +77,8 @@ Item { id: email width: 430 + enabled: root.enabled + placeholderText: "Email Address" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -87,6 +93,8 @@ Item { id: username width: 430 + enabled: root.enabled + placeholderText: root.usernamePlaceholder seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -101,6 +109,8 @@ Item { id: passwordField width: 430 + enabled: root.enabled + placeholderText: root.passwordPlaceholder seperatorColor: Qt.rgba(1, 1, 1, 0.3) togglePasswordField: true @@ -133,6 +143,9 @@ Item { HFTextField { id: displayName width: 430 + + enabled: root.enabled + placeholderText: "Display Name" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -149,6 +162,8 @@ Item { id: button width: 134 + enabled: root.enabled + text: "NEXT" anchors { diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 41d011635e..f30ce7bcd5 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -1,11 +1,15 @@ import QtQuick 2.3 import QtQuick 2.1 + import "../HFControls" +import HQLauncher 1.0 Item { id: root anchors.fill: parent + property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForLogin + Image { anchors.centerIn: parent width: parent.width @@ -72,6 +76,8 @@ Item { HFTextField { id: username + enabled: root.enabled + placeholderText: "Username" seperatorColor: Qt.rgba(1, 1, 1, 0.3) @@ -87,6 +93,8 @@ Item { HFTextField { id: password + enabled: root.enabled + placeholderText: "Password" togglePasswordField: true echoMode: TextInput.Password @@ -118,6 +126,8 @@ Item { HFTextField { id: displayName + enabled: root.enabled + placeholderText: "Display name" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { @@ -134,6 +144,8 @@ Item { id: button width: 110 + enabled: root.enabled + text: "NEXT" anchors { diff --git a/launchers/qt/resources/qml/HFControls/HFButton.qml b/launchers/qt/resources/qml/HFControls/HFButton.qml index ad04286114..a4d077a1e7 100644 --- a/launchers/qt/resources/qml/HFControls/HFButton.qml +++ b/launchers/qt/resources/qml/HFControls/HFButton.qml @@ -7,8 +7,8 @@ Button { height: 50 property string backgroundColor: "#00000000" - property string borderColor: "#FFFFFF" - property string textColor: "#FFFFFF" + property string borderColor: enabled ? "#FFFFFF" : "#7e8c81" + property string textColor: borderColor property int backgroundOpacity: 1 property int backgroundRadius: 1 property int backgroundWidth: 2 diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index cce0a0fc1e..a98e73c659 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -8,7 +8,7 @@ TextField { font.family: "Graphik Regular" font.pointSize: 10.5 - color: text.length == 0 ? "#7e8c81" : "#000000" + color: (text.length == 0 || !enabled) ? "#7e8c81" : "#000000" property bool togglePasswordField: false verticalAlignment: TextInput.AlignVCenter diff --git a/launchers/qt/resources/qml/SplashScreen.qml b/launchers/qt/resources/qml/SplashScreen.qml index 0e2e522232..acbb7b900f 100644 --- a/launchers/qt/resources/qml/SplashScreen.qml +++ b/launchers/qt/resources/qml/SplashScreen.qml @@ -5,7 +5,6 @@ Item { id: root anchors.fill: parent - Image { anchors.centerIn: parent width: parent.width diff --git a/launchers/qt/src/BuildsRequest.cpp b/launchers/qt/src/BuildsRequest.cpp index 5986bf085f..e7ec02c380 100644 --- a/launchers/qt/src/BuildsRequest.cpp +++ b/launchers/qt/src/BuildsRequest.cpp @@ -64,7 +64,6 @@ void BuildsRequest::receivedResponse() { } else { auto root = doc.object(); if (!root.contains("default_tag")) { - //setApplicationState(ApplicationState::UnexpectedError); _error = Error::MissingDefaultTag; emit finished(); return; @@ -74,7 +73,6 @@ void BuildsRequest::receivedResponse() { auto results = root["results"]; if (!results.isArray()) { - //setApplicationState(ApplicationState::UnexpectedError); _error = Error::MalformedResponse; emit finished(); return; diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index b0566eee0a..e2d660e782 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -15,6 +15,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); + _launcherWindow->setTitle("High Fidelity"); _launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window); _launcherWindow->setLauncherStatePtr(_launcherState); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 90fa6e66f1..ddb5ab2dd8 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -111,7 +111,7 @@ static const std::array QML_FILE_ { { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; -void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { +void LauncherState::ASSERT_STATE(ApplicationState state) { if (_applicationState != state) { qDebug() << "Unexpected state, current: " << _applicationState << ", expected: " << state; #ifdef BREAK_ON_ERROR @@ -120,7 +120,7 @@ void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { } } -void LauncherState::ASSERT_STATE(const std::vector& states) { +void LauncherState::ASSERT_STATE(const std::vector& states) { for (auto state : states) { if (_applicationState == state) { return; @@ -146,7 +146,7 @@ QString LauncherState::getCurrentUISource() const { } void LauncherState::declareQML() { - qmlRegisterType("HQLauncher", 1, 0, "LauncherStateEnums"); + qmlRegisterType("HQLauncher", 1, 0, "ApplicationState"); } LauncherState::UIState LauncherState::getUIState() const { @@ -535,7 +535,6 @@ void LauncherState::installClient() { auto unzipper = new Unzipper(_clientZipFile.fileName(), QDir(installDir)); unzipper->setAutoDelete(true); connect(unzipper, &Unzipper::progress, this, [this](float progress) { - //qDebug() << "Unzipper progress: " << progress; _interfaceInstallProgress = progress; emit downloadProgressChanged(); }); @@ -549,8 +548,6 @@ void LauncherState::installClient() { } }); QThreadPool::globalInstance()->start(unzipper); - - //launchClient(); } void LauncherState::downloadLauncher() { @@ -772,3 +769,4 @@ void LauncherState::setApplicationState(ApplicationState state) { LauncherState::ApplicationState LauncherState::getApplicationState() const { return _applicationState; } + diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 6c5015fe77..7d902417ac 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -70,19 +70,18 @@ public: LaunchingHighFidelity, }; + Q_ENUM(ApplicationState) + bool _isDebuggingScreens{ false }; UIState _currentDebugScreen{ UIState::SplashScreen }; void toggleDebugState(); void gotoNextDebugScreen(); void gotoPreviousDebugScreen(); - Q_ENUM(UIState); - Q_ENUM(ApplicationState) - Q_INVOKABLE QString getCurrentUISource() const; - void ASSERT_STATE(LauncherState::ApplicationState state); - void ASSERT_STATE(const std::vector& states); + void ASSERT_STATE(ApplicationState state); + void ASSERT_STATE(const std::vector& states); static void declareQML(); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 95d2c16185..a6d512b593 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -58,10 +58,11 @@ int main(int argc, char *argv[]) { } #endif + QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); - QCoreApplication::setApplicationName(name); + QCoreApplication::setApplicationName("HQ Launcher"); Launcher launcher(argc, argv); From c3c283c3ed776db15a2a6f5159a937e68a78fd72 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 7 Oct 2019 09:14:39 -0700 Subject: [PATCH 072/203] fixing auto updater fpor mac --- launchers/darwin/images/HiFi_Voxel.png | Bin 0 -> 2988 bytes launchers/darwin/images/HiFi_Window.png | Bin 0 -> 4637 bytes launchers/darwin/images/hifi_logo_large@2x.png | Bin 0 -> 16487 bytes launchers/darwin/images/hifi_logo_small@2x.png | Bin 0 -> 4678 bytes launchers/darwin/images/hifi_window@2x.png | Bin 0 -> 7787 bytes launchers/darwin/images/interface.icns | Bin 0 -> 212135 bytes launchers/darwin/src/updater/main.m | 17 +++++++++++++++++ launchers/qt/src/Helper_darwin.mm | 4 +++- 8 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 launchers/darwin/images/HiFi_Voxel.png create mode 100644 launchers/darwin/images/HiFi_Window.png create mode 100644 launchers/darwin/images/hifi_logo_large@2x.png create mode 100644 launchers/darwin/images/hifi_logo_small@2x.png create mode 100644 launchers/darwin/images/hifi_window@2x.png create mode 100644 launchers/darwin/images/interface.icns diff --git a/launchers/darwin/images/HiFi_Voxel.png b/launchers/darwin/images/HiFi_Voxel.png new file mode 100644 index 0000000000000000000000000000000000000000..cd46b96c28c35dee45cf7b11d4d0a74352ab46f1 GIT binary patch literal 2988 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 literal 0 HcmV?d00001 diff --git a/launchers/darwin/images/hifi_logo_large@2x.png b/launchers/darwin/images/hifi_logo_large@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d480da86ddc13c61dee02443563bfed4022ecdbc GIT binary patch literal 16487 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 literal 0 HcmV?d00001 diff --git a/launchers/darwin/images/hifi_logo_small@2x.png b/launchers/darwin/images/hifi_logo_small@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b8782dd226488ca46eda5755d54507177608592f GIT binary patch literal 4678 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 b7G-T4&!2i=qu;#rr^nXHp7Q6eBgg*-w6q($ literal 0 HcmV?d00001 diff --git a/launchers/darwin/images/hifi_window@2x.png b/launchers/darwin/images/hifi_window@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c7638fb61c0ea753d04943535c678688bbf092ea GIT binary patch literal 7787 zcmeHLdpMM9yT37|>7+(2t(<0!SrjqG*GmeSF?TXQvgj zrZ1ch0JB*3wk`mKsQ_{_r^x~Un_%Dv07QxXx)6XFis(-U4reR?n4-d8y;i!`(SgAg z3-vg@VjrGfgir$Lv9OAeaJT}Vl_gBj)WWFzx;%@Ay?)k{ zvW+;HM>f_o(dE*OjLD|vdUO**BXjd5WCMM=8BO1eMmN-@n=p*@83qRA$%kS|wg~q1 zW4PG_8uOE{VAeBlOG+KCgxL&xSo;cW_MmIM%r|BEe3=DLGef@MpB1KY8gsvz= z<0}VSUI;gsFOl-aA~NF0@ezkgnH0)b6@-#+Y(*iHFmc5~S_DTzqwDD-l`vg>x!>p{ zp}~RJ;l5lNFOVnXiKHPkx*q)-ZG=$b==hENe`G5ZexnYN+J#B~K6S|INC}VT!V3|H z26K6KVLXvk?O&bZrCYZ;Oii1~+#erW9<@}Wk*~SJL#+a>jCIzkQ>Kp4Cn6IXrFbs_thDN&jrVM?3oYavg<@j=>oPUm3 z?92Cy{D-8zT!x=GSjdqwDSRQvpGT93{ISy<9T_Z9h?FDZ@>sS^N`z3N$LISpe0>dh zM*7C)x*UCDuC6}M$XM6R*vCiL+}y~_#Dv2&G%)o0y5CmJ4Mj%$y5INzxSt)&_vec^ zf&V(6Z$@I+hy%sJj$&UPlVV8uZk}ah;~XsZ;|KDXln|E{%gHP|8xy*@iHWX(9v%5Q z=5q{teuz{Y961?1uDqbhQ6Qg;xfz4QMUlXyaFGk}d?}OD{C^ojoNagjPxN>F0w)U* z`$@w&!MtVuJdyODaXp$P*@A|`7FUJ#w_0Lr--Y;Bau?fQ(C}NrvLyd!%HxTAnUr9P zm8J5O%T#~`gDl%+t0OXcpE*i0!ec7>vfK{zG_iWF*A(dLc-r3fjUXmg#}_*sn-ZAr zs?d9JyVK=;haRfU=yfm;Yd&GPz+*F`= z!zr)f?NDmm{1vpTXI?)k9~zCdOdsJ5KX~6I9;e;21^|I-j8{@tV%q5wHJ-Iwk5!DVrEI>>KiyYFD?p zuw9c%qR14g48X()aGnIeP)%3jN|z%aOhXR*J(hnnS&8 zrlY@Q^w5Ke$WJwvi&y~A;;ft$ZTZ7+*3Puk9lJGhPd{l^A8VJN7_VD8WXX~RSahwz zI_a6xaMsQ~!ytF&pwZjtaD^>YfVht2QY)v--F@{z!}6{v!BtoABUefTjB4&(uXXJc zbkb8lwzVn`THDJ4bZu=>sIGMl`Y7GhUUJ-|sZ)LYbX)9*SEh1nm-uHHfPwqECDk!V zkj|>C9nU?YKFF1SxL@TJ9aN<@4av%%`94u0hEj9ydRe(^tMMt1A&jwopv3)Ye*w}l zR&z*Q??Ywp0JhMyc{Mh3K2=LiDB%qt2DGLC*eut83I6R!P)me;UpW{ zY_yS3(^Y6wAug}%%0SKA+mEk~97i_Y*(Ew=jriJ0!!z&!vfa(Y$un5>jY=@$}UEntQRAn$=Q8*bT@m!Dr~4XG#j7OdxuM)x}h}R0i6+A zXN2tTTi53UaI-mNMtZu3j%th@ww8C%ElcS6u1g1Lq|Ga?>LmQls9GCs7M`kdS41-A z>^n3%Lp8<|?LJv&eDr64xTFogm4144Yx(D_bOevF>QVgz_~021!p1yA-srdV^7hqe z)F2($D2tF^!|j{{;ClxbOlN zOv7C*r0(kbE`pb%TW|MnazQ5=-!W-na4=L4`@N!F?5CsS8;l;8KDv7Yxz*~lHOK<% zkBD{BknE0yy=6Fl#$^{JJWeS8b_xdYDYuKwlWOqp+!dFF+avzGJD35VWMCFF#GIih zY|<3#b}Pw&b>rD3UgZ}y#q{69aJ-AMC(5uIU4&JXR30i3D*Qe_f-TPz_+)$`tc9$ z={5+AOgE2w|HJ7hPg@uJ9ah37i(45uKi5JG7k@+ipUG?)6uw8!VHGJ5C9{T?k?6$H z!$Qw}NXc`Du=gL22>zlViM$N^mE>Jf!KLA#8Jq@S&4}J;le+@DHU0bf z_w(=P|M}ysyj#VIDFcw|B+Scb>?i@BFFQ)`bOeCN z3s|NPIe^O4P#Edy720f7howY-RO?>%@(bb901TuWIFp$;lYCY})Rn~makhRK5#IeY1>)*T zHHf8$QQ59Oxob27$dIo0N|2-RV%G&En`TS4Rk+^oIB(syIi z5o9~?VuV$k$wGf5t-WI0?7USi6~J27RBM@S(vzhGfG2CUh;N2aPB+0DvqqL|I(oHi zTqUc#9e}L-R;cpl@TTd=EQ2XZ3Nr^blK{x^p?kfDB`RfShSSgovb;0`=RNA*xMS3u zl%a^30OVP?Wd_qKn12AICeGSh(Q*rbte_mIvKN_d`(T>)i-4K41|u+zgK61ryA*Fk zTOc(nKC$NPRs^tCGjYNtnu-f(;())-#$UfqCj1^=lm9u&9NW8%HD@k&(9;pQ!-aLE zOWWrGXp8Hqk_-U&^Dc%ej;*l1vl><4KShPqoEB6j*OaUy^|UB&R~QV$q`I@0jQQW6 zEfgX)*ob!oE!wyzti)e&w<)$GCGEc6?k(1o0U$L|h+(}uAM9y$Kt6EZjZo*`EeeAHAVPy zygNc?O;I`lgUPO_Bnaz@?27i|{=DWTR_XDEdc6lr@>v-4T9wB}vfOtgZP|)i_?+Uj zijy_~&UU?;@zM4&ki8d2Gz06Qipqj=JYQia_G(rpcK#|DE<|^rfZ?pss;ZzK`&`WW z(hsrYswha`bW@4HF$e(r>z9!tj8QYqmS;Japw2`4blRGPD84HIB$|271X%Z$O#Eec z%?C*&&Lou}C-R7EPmC`|vvZzTDJ z)DIhxHV5zSZNIy;W2j~2VFG}Cb|G~d=FCOXj7dx$j%qI}+k!vNd~f4GT5>R z@9gE@dlbVd!DHz9o+=(@yZb?;EvvR8nk%e?PXW)l=yHs=nW1TT9=X+}jjO(fgJrcw zN8}1;H*CcLcO=c6UGq%i+uIh*1LkAhU!#y@#+eDQ-y7e_BPC(?3j!WMdTH$+jx|NE zD!^enM3_r=qlvJ9d^c%5nHGE58%T! z$x6WPcw@N-Cxd*f7S93eu$sbQ>FYb+Oaq9^)Yc@1DcpE)b$TQT;J#}$by*WilViD; zsFLnE00YOa?e)&{e1wQw7cU{cIbp{n0(7Z#ed8)j@;0C0Z> zelfArtUUI6(A&Rcv*ZExoxMq&hPiLsyrTpxfqN}B!!y#j59UVSmk>PJ<+oFtN=#>~ zYY*)Kuuh!rl&~YG1yk?D2z5arDsZ1x5LwQ&FKB(;PBxxt)ffLn#BNyQ=-UceP_0$@ zOoMK=cEnW%;#}rs5w3<+ciBH@PXSo@>n4(|ZOp}_)3N~7IzLZZ@Ay8dL}4+&z^>p) zn`qs7Vn#(S!rm8B+JsVHm*Pok<%6p}_ud((%>N*1f4R`|UUzuDyHQJZ3;pGK}=-J1$9-PH}xy7v`sIbiYbeFIO}_^%Efcgmg! zp60T$Qhzys>P2*-lS)c_{6{qffO*;1sSDJ0?>X_3sRppfI*~O8Ya3M0y}&&(%mlm4 z;+HEo5ngPbsslcZrn+IIsIsv$DcE!_4yy8vu$=KE>6n zxa$^$hka8e9&xwWvgb4b;#Bj#B-;#LK%4KEJxpuQJ*jY`ww_*kx<{P=AmwC-=3YNN z@j7jI>^XZ}7&@F^(3GSeOFO02`6b+Ee0`!7382e1ktI1H-?)B5qLzAMcdnV~{Bz|l zjR>J;S2t2#o1CC%eVxQb!=bqov3!rdzL>YCmzg9 zQrhhH#R#CHW+yvAY_hq(Kcp>iR%mGE$hF^$G&`QIuFFVqJJVhZ5Es7Kj9A3sIlMvC6d!bR#wB%zgfOR~}d8I;Lk%Xx> z;pxD>-q{wCycl5K$%E99HyT;v9b02}Zm61KAy{>LRbTyz!O? zmEaZgqTZ`?WN^XLHPQxRp&J67_BT5sQ9XBh3?i)};fx zuBQQ5dp&U@_3Tqy`}XCb00&zxB{TAx@hO1wC*P&D>!fUYwMwbHxG>5)-`#6xcW!{i z9IJ^v0tXrJ*4Us~nXdk6EcVh+!-AaoOg8as*}~W5s>!txAGa5j9*kND(6sIfD`E4S z_@cw&n$EW58e5C;XKOEIVoP%wP6?ZP z;R9sP^Uzv$ig~`V;dYQn`;=Da>6Ds}F4&5_2Q^+TV*99RM}c?+S37g$FQk6XwmW;< zLng-jCm0`Is?DyqI`BhB;`S5txhc#S3tdqy*v9&BV`6aY#DXtxTW8mAcXRK^Eqrir zM~Fhr{u!7uyYsUMQQ6#lx4Mj!8e0c;O|`!ZTMnRiy%uqxB8)i^`{3Y??!<_9xsOf& zRQw=vl4Uyo;hvdX<7=ep^Qshy(R8SF4s+w}*ztmEg^Xe7*u2&T_IWT7dd@&#nSfA6qdn2V>y*@2y zd?`IvchCsng&;eWr9dsZpkftcbV`dGrF~X3sLcmh)LfB!t~>tdv4)+emFdj+$?_c! d`;kgyr3nR_`!*yYc%T>umYtLB+2!5|{{fcM-*o^0 literal 0 HcmV?d00001 diff --git a/launchers/darwin/images/interface.icns b/launchers/darwin/images/interface.icns new file mode 100644 index 0000000000000000000000000000000000000000..8dadfd50376cb3f1fda6114dc2c4b4f8b218758e GIT binary patch literal 212135 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̓CD3`2WI*m)}f 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 z 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�N#U({jvxfB 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 #include #include +#include +#include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, QString loginTokenResponse) { @@ -72,7 +74,7 @@ void launchAutoUpdater(const QString& autoUpdaterPath) { @throw exception; } - exit(0); + QCoreApplication::instance()->quit(); } From 7c15500b77bf031f274a12ae87c09bce7c55eaa2 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 7 Oct 2019 16:46:23 -0700 Subject: [PATCH 073/203] adding log file and fixing config file for mac --- launchers/qt/src/CommandlineOptions.cpp | 4 +- launchers/qt/src/Helper.cpp | 57 +++++++++++++++++++ launchers/qt/src/Helper.h | 2 + launchers/qt/src/Helper_darwin.mm | 6 +- .../qt/src/LauncherInstaller_windows.cpp | 1 - launchers/qt/src/LauncherState.cpp | 9 ++- launchers/qt/src/main.cpp | 11 ++-- 7 files changed, 78 insertions(+), 12 deletions(-) diff --git a/launchers/qt/src/CommandlineOptions.cpp b/launchers/qt/src/CommandlineOptions.cpp index 162d3414f3..9254644f8f 100644 --- a/launchers/qt/src/CommandlineOptions.cpp +++ b/launchers/qt/src/CommandlineOptions.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include bool isCommandlineOption(const std::string& option) { if (option.rfind("--", 0) == 0 && option.at(2) != '-') { @@ -18,7 +20,7 @@ void CommandlineOptions::parse(const int argc, char** argv) { for (int index = 1; index < argc; index++) { std::string option = argv[index]; if (isCommandlineOption(option)) { - std::cout << "adding commandline option: " << option << "\n"; + qDebug() << "adding commandline option: " << QString::fromStdString(option); _commandlineOptions.push_back(option); } } diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 38e6040a16..30de16cdcc 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -7,6 +7,11 @@ #include #include #include +#include +#include +#include +#include +//#incluce QString getMetaverseAPIDomain() { @@ -17,6 +22,43 @@ QString getMetaverseAPIDomain() { return "https://metaverse.highfidelity.com"; } + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + Q_UNUSED(context); + + QString date = QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss"); + QString txt = QString("[%1] ").arg(date); + + switch (type) { + case QtDebugMsg: + txt += QString("{Debug} \t\t %1").arg(message); + break; + case QtWarningMsg: + txt += QString("{Warning} \t %1").arg(message); + break; + case QtCriticalMsg: + txt += QString("{Critical} \t %1").arg(message); + break; + case QtFatalMsg: + txt += QString("{Fatal} \t\t %1").arg(message); + break; + case QtInfoMsg: + txt += QString("{Info} \t %1").arg(message); + break; + } + + QDir launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + launcherDirectory.mkpath(launcherDirectory.absolutePath()); + QString filename = launcherDirectory.absoluteFilePath("Log.txt"); + + QFile outFile(filename); + outFile.open(QIODevice::WriteOnly | QIODevice::Append); + + QTextStream textStream(&outFile); + std::cout << txt.toStdString() << "\n"; + textStream << txt << "\n"; +} + void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) { qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist"; @@ -35,6 +77,21 @@ void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPat } } + +void cleanLogFile() { + QDir launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + launcherDirectory.mkpath(launcherDirectory.absolutePath()); + QString filename = launcherDirectory.absoluteFilePath("Log.txt"); + QString tmpFilename = launcherDirectory.absoluteFilePath("Log-last.txt"); + if (QFile::exists(filename)) { + if (QFile::exists(tmpFilename)) { + QFile::remove(tmpFilename); + } + QFile::rename(filename, tmpFilename); + QFile::remove(filename); + } +} + QString getHTTPUserAgent() { #if defined(Q_OS_WIN) return "HQLauncher/fixme (Windows)"; diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index e5a3c4f502..669dd26f3b 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -13,6 +13,8 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr void launchAutoUpdater(const QString& autoUpdaterPath); void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString()); +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message); +void cleanLogFile(); #ifdef Q_OS_MAC bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 6e306d795b..47866a8ffb 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -55,13 +55,13 @@ void launchAutoUpdater(const QString& autoUpdaterPath) { task.launchPath = [newLauncher stringByAppendingString: bundlePath]; task.arguments = @[[[NSBundle mainBundle] bundlePath], newLauncher]; - NSLog(@"launching updater: %@ %@", task.launchPath, task.arguments); + qDebug() << "launching updater: " << task.launchPath << task.arguments; @try { [task launch]; } @catch (NSException *e) { - NSLog(@"couldn't launch updater: %@, %@", e.name, e.reason); + qDebug() << "couldn't launch updater: " << QString::fromNSString(e.name) << QString::fromNSString(e.reason); exception = e; continue; } @@ -117,9 +117,9 @@ void waitForInterfaceToClose() { bool isLauncherAlreadyRunning() { NSArray* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.highfidelity.launcher"]; - NSLog(@"Count: %lu", [apps count]); if ([apps count] > 1) { NSLog(@"launcher is already running"); + qDebug() << "launcher is already running"; return true; } diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index c91a001754..e8f1d785a7 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -31,7 +31,6 @@ bool LauncherInstaller::runningOutsideOfInstallDir() { } void LauncherInstaller::install() { - //qDebug() << "Is install dir empty: " << _launcherInstallDir.isEmpty(); if (runningOutsideOfInstallDir()) { qDebug() << "Installing HQ Launcher...."; QString oldLauncherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index ddb5ab2dd8..97911c8a4d 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -271,6 +271,7 @@ void LauncherState::getCurrentClientVersion() { } } + qDebug() << "Is logged-in: " << _config.loggedIn; if (_config.loggedIn) { downloadClient(); } else { @@ -718,14 +719,18 @@ void LauncherState::launchClient() { auto path = getConfigFilePath(); QFile configFile{ path }; - if (configFile.open(QIODevice::WriteOnly)) { + if (configFile.open(QIODevice::ReadWrite)) { QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); doc.setObject({ { configHomeLocationKey, _config.homeLocation }, { configLoggedInKey, _config.loggedIn }, { configLauncherPathKey, _config.launcherPath }, }); - configFile.write(doc.toJson()); + qint64 result = configFile.write(doc.toJson()); + configFile.close(); + qDebug() << "Wrote data to config data: " << result; + } else { + qDebug() << "Failed to open config file"; } QString defaultScriptsPath; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index a6d512b593..7526ccc8be 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -29,7 +29,13 @@ bool hasSuffix(const std::string& path, const std::string& suffix) { } int main(int argc, char *argv[]) { + QString name { "High Fidelity" }; + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setOrganizationName(name); + QCoreApplication::setApplicationName("HQ Launcher"); Q_INIT_RESOURCE(resources); + cleanLogFile(); + qInstallMessageHandler(messageHandler); #ifdef Q_OS_MAC if (isLauncherAlreadyRunning()) { return 0; @@ -59,11 +65,6 @@ int main(int argc, char *argv[]) { #endif - QString name { "High Fidelity" }; - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QCoreApplication::setOrganizationName(name); - QCoreApplication::setApplicationName("HQ Launcher"); - Launcher launcher(argc, argv); return launcher.exec(); From 4179ef3391b1fce7d1c8091adab5e746a0656b09 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 8 Oct 2019 10:23:13 -0700 Subject: [PATCH 074/203] fix mac application path --- launchers/qt/src/Helper.h | 1 + launchers/qt/src/Helper_darwin.mm | 4 ++++ launchers/qt/src/LauncherState.cpp | 3 +-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 669dd26f3b..2d1f06052f 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -21,6 +21,7 @@ bool replaceDirectory(const QString& orginalDirectory, const QString& newDirecto void closeInterfaceIfRunning(); void waitForInterfaceToClose(); bool isLauncherAlreadyRunning(); +QString getBundlePath(); #endif #ifdef Q_OS_WIN diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 47866a8ffb..684143e11b 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -42,6 +42,10 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr } +QString getBundlePath() { + return QString::fromNSString([[NSBundle mainBundle] bundlePath]); +} + void launchAutoUpdater(const QString& autoUpdaterPath) { NSException *exception; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 97911c8a4d..ec5a2c048d 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -79,7 +79,6 @@ QString LauncherState::getClientExecutablePath() const { #endif } - QString LauncherState::getConfigFilePath() const { QDir clientDirectory = getClientDirectory(); return clientDirectory.absoluteFilePath("config.json"); @@ -89,7 +88,7 @@ QString LauncherState::getLauncherFilePath() const { #if defined(Q_OS_WIN) return _launcherDirectory.absoluteFilePath("launcher.exe"); #elif defined(Q_OS_MACOS) - return _launcherDirectory.absoluteFilePath("launcher.app"); + return getBundlePath() + "/Contents/MacOS/HQ Launcher"; #endif } From 86f1286face75be63f3c1c78b634a2e9ec8fe429 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 8 Oct 2019 10:43:12 -0700 Subject: [PATCH 075/203] do not add double entries to config.json --- launchers/qt/src/LauncherState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index ec5a2c048d..5ad437ec84 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -718,7 +718,7 @@ void LauncherState::launchClient() { auto path = getConfigFilePath(); QFile configFile{ path }; - if (configFile.open(QIODevice::ReadWrite)) { + if (configFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) { QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); doc.setObject({ { configHomeLocationKey, _config.homeLocation }, From fba18c2719c8a3f6a47de62ee15ce03f16087783 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 8 Oct 2019 12:55:02 -0700 Subject: [PATCH 076/203] Fixup qt launcher style and add last login --- .../qml/HFBase/CreateAccountBase.qml | 5 ++-- .../qt/resources/qml/HFBase/LoginBase.qml | 1 + launchers/qt/src/LauncherState.cpp | 30 +++++++++++++++---- launchers/qt/src/LauncherState.h | 4 +++ 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index cf03b282bf..dd23862303 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -9,7 +9,7 @@ Item { id: root anchors.centerIn: parent property string titleText: "Sign-in and pick a password" - property string usernamePlaceholder: "User name" + property string usernamePlaceholder: "Username" property string passwordPlaceholder: "Set a password" property int marginLeft: root.width * 0.15 @@ -30,8 +30,7 @@ Item { width: 481 lineHeight: 35 lineHeightMode: Text.FixedHeight - text: root.titleText + " " + LauncherState.applicationState - visible: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh." + text: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh." anchors { top: root.top topMargin: 29 diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index f30ce7bcd5..98d7500890 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -78,6 +78,7 @@ Item { enabled: root.enabled + text: LauncherState.lastUsedUsername placeholderText: "Username" seperatorColor: Qt.rgba(1, 1, 1, 0.3) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index ddb5ab2dd8..71e15b577b 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -31,6 +31,7 @@ //#define BREAK_ON_ERROR const QString configHomeLocationKey { "homeLocation" }; +const QString configLastLoginKey { "lastLogin" }; const QString configLoggedInKey{ "loggedIn" }; const QString configLauncherPathKey{ "launcherPath" }; @@ -261,10 +262,16 @@ void LauncherState::getCurrentClientVersion() { _config.launcherPath = getLauncherFilePath(); _config.loggedIn = false; if (root.contains(configLoggedInKey)) { - _config.loggedIn = root["loggedIn"].toBool(); + _config.loggedIn = root[configLoggedInKey].toBool(); + } + if (root.contains(configLastLoginKey)) { + _config.lastLogin = root[configLastLoginKey].toString(); } if (root.contains(configHomeLocationKey)) { - _config.homeLocation = root["homeLocation"].toString(); + _config.homeLocation = root[configHomeLocationKey].toString(); + } + if (root.contains(configLauncherPathKey)) { + _config.launcherPath = root[configLauncherPathKey].toString(); } } else { qDebug() << "Failed to open config.json"; @@ -274,7 +281,12 @@ void LauncherState::getCurrentClientVersion() { if (_config.loggedIn) { downloadClient(); } else { - setApplicationState(ApplicationState::WaitingForSignup); + if (_config.lastLogin.isEmpty()) { + setApplicationState(ApplicationState::WaitingForSignup); + } else { + _lastUsedUsername = _config.lastLogin; + setApplicationState(ApplicationState::WaitingForLogin); + } } } @@ -312,7 +324,7 @@ void LauncherState::signup(QString email, QString username, QString password, QS emit lastSignupErrorChanged(); } - QObject::connect(signupRequest, &SignupRequest::finished, this, [this, signupRequest] { + QObject::connect(signupRequest, &SignupRequest::finished, this, [this, signupRequest, username] { signupRequest->deleteLater(); @@ -350,6 +362,9 @@ void LauncherState::signup(QString email, QString username, QString password, QS // After successfully signing up, attempt to login auto loginRequest = new LoginRequest(); + _lastUsedUsername = username; + _config.lastLogin = username; + connect(loginRequest, &LoginRequest::finished, this, [this, loginRequest]() { ASSERT_STATE(ApplicationState::RequestingLoginAfterSignup); @@ -389,7 +404,7 @@ void LauncherState::login(QString username, QString password, QString displayNam auto request = new LoginRequest(); - connect(request, &LoginRequest::finished, this, [this, request]() { + connect(request, &LoginRequest::finished, this, [this, request, username]() { ASSERT_STATE(ApplicationState::RequestingLogin); request->deleteLater(); @@ -404,6 +419,8 @@ void LauncherState::login(QString username, QString password, QString displayNam return; } + _lastUsedUsername = username; + _config.lastLogin = username; _config.loggedIn = true; _loginResponse = request->getToken(); _loginTokenResponse = request->getRawToken(); @@ -722,8 +739,9 @@ void LauncherState::launchClient() { QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); doc.setObject({ { configHomeLocationKey, _config.homeLocation }, + { configLastLoginKey, _config.lastLogin }, { configLoggedInKey, _config.loggedIn }, - { configLauncherPathKey, _config.launcherPath }, + { configLauncherPathKey, getLauncherFilePath() }, }); configFile.write(doc.toJson()); } diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 7d902417ac..7cbb09d237 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -13,6 +13,7 @@ #include "BuildsRequest.h" struct LauncherConfig { + QString lastLogin{ "" }; QString launcherPath{ "" }; bool loggedIn{ false }; QString homeLocation{ "" }; @@ -28,6 +29,7 @@ class LauncherState : public QObject { Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged) Q_PROPERTY(QString lastSignupErrorMessage READ getLastSignupErrorMessage NOTIFY lastSignupErrorMessageChanged) Q_PROPERTY(QString buildVersion READ getBuildVersion) + Q_PROPERTY(QString lastUsedUsername READ getLastUsedUsername) public: LauncherState(); @@ -94,6 +96,7 @@ public: QString getLastSignupErrorMessage() const { return _lastSignupErrorMessage; } QString getBuildVersion() { return QString(LAUNCHER_BUILD_VERSION); } + QString getLastUsedUsername() const { return _lastUsedUsername; } void setApplicationStateError(QString errorMessage); void setApplicationState(ApplicationState state); @@ -174,6 +177,7 @@ private: SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; QString _lastLoginErrorMessage{ "" }; QString _lastSignupErrorMessage{ "" }; + QString _lastUsedUsername; QString _displayName; QString _applicationErrorMessage; QString _currentClientVersion; From 80f02938810c97d001295bebc3994c6a1b9297a9 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 8 Oct 2019 16:57:14 -0700 Subject: [PATCH 077/203] update install/uninstall and fix createProcess args --- launchers/qt/src/Helper.cpp | 1 + launchers/qt/src/Helper_windows.cpp | 4 +- .../qt/src/LauncherInstaller_windows.cpp | 81 ++++++++++++++++++- launchers/qt/src/LauncherInstaller_windows.h | 1 + launchers/qt/src/main.cpp | 11 +-- 5 files changed, 85 insertions(+), 13 deletions(-) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 30de16cdcc..42f76fe0fa 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -57,6 +57,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt QTextStream textStream(&outFile); std::cout << txt.toStdString() << "\n"; textStream << txt << "\n"; + outFile.close(); } void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index ebbf1fab72..662e64daae 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -16,7 +16,7 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr const QString& displayName, const QString& contentCachePath, QString loginResponseToken) { // TODO Fix parameters - QString params = "--url \"" + homePath + "\"" + QString params = "\"" + clientPath + "\"" + " --url \"" + homePath + "\"" + " --setBookmark \"hqhome=" + homePath + "\"" + " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\"" + " --cache \"" + contentCachePath + "\"" @@ -58,7 +58,7 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr } void launchAutoUpdater(const QString& autoUpdaterPath) { - QString params = "--restart --noUpdate"; + QString params = "\"" + QCoreApplication::applicationFilePath() + "\"" + " --restart --noUpdate"; STARTUPINFO si; PROCESS_INFORMATION pi; diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index e8f1d785a7..3423815d7c 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -1,4 +1,6 @@ #include "LauncherInstaller_windows.h" + +#include "CommandlineOptions.h" #include "Helper.h" #include @@ -9,12 +11,12 @@ #include #include -#include +#include #include LauncherInstaller::LauncherInstaller(const QString& applicationFilePath) { - _launcherInstallDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/HQ"; - _launcherApplicationsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/HQ"; + _launcherInstallDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + _launcherApplicationsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/Launcher"; qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath(); qDebug() << "Launcher Application dir: " << _launcherApplicationsDir.absolutePath(); _launcherInstallDir.mkpath(_launcherInstallDir.absolutePath()); @@ -33,6 +35,7 @@ bool LauncherInstaller::runningOutsideOfInstallDir() { void LauncherInstaller::install() { if (runningOutsideOfInstallDir()) { qDebug() << "Installing HQ Launcher...."; + uninstallOldLauncher(); QString oldLauncherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; if (QFile::exists(oldLauncherPath)) { @@ -83,10 +86,46 @@ void LauncherInstaller::createShortcuts() { void LauncherInstaller::uninstall() { qDebug() << "Uninstall Launcher"; deleteShortcuts(); + CommandlineOptions* options = CommandlineOptions::getInstance(); + if (!options->contains("--resumeUninstall")) { + QDir tmpDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + QString destination = tmpDirectory.absolutePath() + "/HQ Launcher.exe"; + qDebug() << destination; + if (QFile::exists(destination)) { + QFile::remove(destination); + } + bool copied = QFile::copy(_launcherRunningFilePath, destination); + if (copied) { + QString params = "\"" + _launcherRunningFilePath + "\"" + " --resumeUninstall"; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + BOOL success = CreateProcess( + destination.toUtf8().data(), + params.toUtf8().data(), + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + } else { + qDebug() << "Failed to complete uninstall launcher"; + } + return; + } QString launcherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; if (QFile::exists(launcherPath)) { - QFile::remove(launcherPath); + bool removed = QFile::remove(launcherPath); + qDebug() << "Successfully removed " << launcherPath << ": " << removed; } deleteApplicationRegistryKeys(); } @@ -116,6 +155,40 @@ void LauncherInstaller::deleteShortcuts() { } } +void LauncherInstaller::uninstallOldLauncher() { + QDir localAppPath = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation).value(0) + "/../../HQ"; + QDir startAppPath = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0) + "/HQ"; + QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + + qDebug() << localAppPath.absolutePath(); + qDebug() << startAppPath.absolutePath(); + QString desktopAppLinkPath = desktopPath + "/HQ Launcher.lnk"; + if (QFile::exists(desktopAppLinkPath)) { + QFile::remove(desktopAppLinkPath); + } + + QString uninstallLinkPath = localAppPath.absolutePath() + "/Uninstall HQ.lnk"; + if (QFile::exists(uninstallLinkPath)) { + QFile::remove(uninstallLinkPath); + } + + QString applicationPath = localAppPath.absolutePath() + "/HQ Launcher.exe"; + if (QFile::exists(applicationPath)) { + QFile::remove(applicationPath); + } + + QString appStartLinkPath = startAppPath.absolutePath() + "/HQ Launcher.lnk"; + if (QFile::exists(appStartLinkPath)) { + QFile::remove(appStartLinkPath); + } + + QString uninstallAppStartLinkPath = startAppPath.absolutePath() + "/Uninstall HQ.lnk"; + if (QFile::exists(uninstallAppStartLinkPath)) { + QFile::remove(uninstallAppStartLinkPath); + } +} + + void LauncherInstaller::createApplicationRegistryKeys() { const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; diff --git a/launchers/qt/src/LauncherInstaller_windows.h b/launchers/qt/src/LauncherInstaller_windows.h index 6ddd74f53f..3bd9d0242a 100644 --- a/launchers/qt/src/LauncherInstaller_windows.h +++ b/launchers/qt/src/LauncherInstaller_windows.h @@ -11,6 +11,7 @@ public: bool runningOutsideOfInstallDir(); private: void createShortcuts(); + void uninstallOldLauncher(); void createApplicationRegistryKeys(); void deleteShortcuts(); void deleteApplicationRegistryKeys(); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 7526ccc8be..ee3256dc66 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) { QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); - QCoreApplication::setApplicationName("HQ Launcher"); + QCoreApplication::setApplicationName("Launcher"); Q_INIT_RESOURCE(resources); cleanLogFile(); qInstallMessageHandler(messageHandler); @@ -51,11 +51,11 @@ int main(int argc, char *argv[]) { options->parse(argc, argv); #ifdef Q_OS_WIN LauncherInstaller launcherInstaller(argv[0]); - if (options->contains("--restart") || launcherInstaller.runningOutsideOfInstallDir()) { - launcherInstaller.install(); - } else if (options->contains("--uninstall")) { + if (options->contains("--uninstall") || options->contains("--resumeUninstall")) { launcherInstaller.uninstall(); return 0; + } else if (options->contains("--restart") || launcherInstaller.runningOutsideOfInstallDir()) { + launcherInstaller.install(); } int interfacePID = -1; @@ -64,9 +64,6 @@ int main(int argc, char *argv[]) { } #endif - Launcher launcher(argc, argv); - return launcher.exec(); - } From 3196770152c8ea604fde9c59322d8afa67b1e333 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 9 Oct 2019 09:00:55 -0700 Subject: [PATCH 078/203] Update qtlite on Windows --- launchers/qt/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index c087e87bf2..0ff6a4e084 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -44,8 +44,8 @@ if (WIN32) ExternalProject_Add( qtlite - URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win.zip" - URL_HASH MD5=8b7a0b8fb772a014a3276274f40a9d14 + URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win-oct-9-2019.zip" + URL_HASH MD5=4c33eeca16563855be50dab8dd4f0f53 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 4d990d36342af253f0a2ff7a0cb8438041792e23 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 9 Oct 2019 09:03:46 -0700 Subject: [PATCH 079/203] Disable submit button in qt launcher if fields empty --- .../qt/resources/qml/HFBase/CreateAccountBase.qml | 10 +++++----- launchers/qt/resources/qml/HFBase/LoginBase.qml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index dd23862303..06f8aaf5f3 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -105,7 +105,7 @@ Item { } HFTextField { - id: passwordField + id: password width: 430 enabled: root.enabled @@ -132,7 +132,7 @@ Item { width: 430 anchors { - top: passwordField.bottom + top: password.bottom left: root.left leftMargin: root.marginLeft topMargin: 22 @@ -154,14 +154,14 @@ Item { topMargin: 4 } - onAccepted: LauncherState.signup(email.text, username.text, passwordField.text, displayName.text) + onAccepted: LauncherState.signup(email.text, username.text, password.text, displayName.text) } HFButton { id: button width: 134 - enabled: root.enabled + enabled: root.enabled && email.text.length > 0 && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0 text: "NEXT" @@ -172,7 +172,7 @@ Item { topMargin: 21 } - onClicked: LauncherState.signup(email.text, username.text, passwordField.text, displayName.text) + onClicked: LauncherState.signup(email.text, username.text, password.text, displayName.text) } diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 98d7500890..bd8b5a4a1b 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -145,7 +145,7 @@ Item { id: button width: 110 - enabled: root.enabled + enabled: root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0 text: "NEXT" From fc018257e1c5cec851da80c0b5366db03c6db04d Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 9 Oct 2019 12:20:35 -0700 Subject: [PATCH 080/203] Select avatar to look using API. Blink when look at change --- interface/src/avatar/MyAvatar.cpp | 28 +++++++++++++------ interface/src/avatar/MyAvatar.h | 2 ++ .../src/avatars-renderer/Head.cpp | 7 +++-- libraries/avatars/src/HeadData.cpp | 16 +++++++++++ libraries/avatars/src/HeadData.h | 15 ++++++---- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ea0efe29cb..f529f168f4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2188,15 +2188,20 @@ void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) { foreach (const AvatarSharedPointer& avatarData, hash) { std::shared_ptr avatar = std::static_pointer_cast(avatarData); if (!avatar->isMyAvatar() && avatar->isInitialized()) { - glm::vec3 otherForward = avatar->getHead()->getForwardDirection(); - glm::vec3 otherPosition = avatar->getHead()->getEyePosition(); - const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f; - bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD; - bool lookingAtOtherAlready = _lookAtTargetAvatar.lock().get() == avatar.get(); - float cost = lookAtCostFunction(myForward, myPosition, otherForward, otherPosition, otherIsTalking, lookingAtOtherAlready); - if (cost < bestCost) { - bestCost = cost; + if (_forceTargetAvatarID.isNull() || avatar->getID() != _forceTargetAvatarID) { + glm::vec3 otherForward = avatar->getHead()->getForwardDirection(); + glm::vec3 otherPosition = avatar->getHead()->getEyePosition(); + const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f; + bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD; + bool lookingAtOtherAlready = _lookAtTargetAvatar.lock().get() == avatar.get(); + float cost = lookAtCostFunction(myForward, myPosition, otherForward, otherPosition, otherIsTalking, lookingAtOtherAlready); + if (cost < bestCost) { + bestCost = cost; + bestAvatar = avatar; + } + } else { bestAvatar = avatar; + break; } } } @@ -6838,3 +6843,10 @@ void MyAvatar::resetPointAt() { } } +void MyAvatar::setLookAtAvatarID(const QUuid& avatarID) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "setLookAtAvatarID", + Q_ARG(const QUuid&, avatarID)); + } + _forceTargetAvatarID = avatarID; +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a7ba639461..d93ee776be 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -971,6 +971,7 @@ public: * @param {Uuid} entityID - The entity that the hand touch effect will be enabled for. */ Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID); + Q_INVOKABLE void setLookAtAvatarID(const QUuid& avatarID); bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); } void setUseAdvancedMovementControls(bool useAdvancedMovementControls) @@ -2656,6 +2657,7 @@ private: AvatarWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; + QUuid _forceTargetAvatarID; bool _shouldRender { true }; float _oculusYawOffset; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index 63d8e2981c..a9dde201af 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -96,10 +96,13 @@ void Head::simulate(float deltaTime) { // no blinking when brows are raised; blink less with increasing loudness const float BASE_BLINK_RATE = 15.0f / 60.0f; const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; - if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * + if (_forceBlink || forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { float randSpeedVariability = randFloat(); float eyeBlinkVelocity = BLINK_SPEED + randSpeedVariability * BLINK_SPEED_VARIABILITY; + if (_forceBlink) { + eyeBlinkVelocity = 0.5f * eyeBlinkVelocity; + } _leftEyeBlinkVelocity = eyeBlinkVelocity; _rightEyeBlinkVelocity = eyeBlinkVelocity; if (randFloat() < 0.5f) { @@ -114,7 +117,7 @@ void Head::simulate(float deltaTime) { if (_leftEyeBlink == FULLY_CLOSED) { _leftEyeBlinkVelocity = -BLINK_SPEED; - + updateEyeLookAt(); } else if (_leftEyeBlink == FULLY_OPEN) { _leftEyeBlinkVelocity = 0.0f; } diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index c86e534929..f8a09905ef 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -233,3 +233,19 @@ void HeadData::setHasProceduralEyeMovement(bool hasProceduralEyeMovement) { void HeadData::setFaceTrackerConnected(bool value) { _isFaceTrackerConnected = value; } + +void HeadData::setLookAtPosition(const glm::vec3& lookAtPosition) { + if (_requestLookAtPosition != lookAtPosition) { + _lookAtPositionChanged = usecTimestampNow(); + glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); + glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); + const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees + _forceBlink = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; + _lookAtUpdated = false; + } + _requestLookAtPosition = lookAtPosition; + if (_lookAtUpdated) { + _forceBlink = false; + _lookAtPosition = lookAtPosition; + } +} \ No newline at end of file diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index dc5aaf2595..a8005b5659 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -64,12 +64,7 @@ public: void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } - void setLookAtPosition(const glm::vec3& lookAtPosition) { - if (_lookAtPosition != lookAtPosition) { - _lookAtPositionChanged = usecTimestampNow(); - } - _lookAtPosition = lookAtPosition; - } + void setLookAtPosition(const glm::vec3& lookAtPosition); bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; } bool getHasProceduralEyeFaceMovement() const; @@ -84,6 +79,11 @@ public: void setFaceTrackerConnected(bool value); bool getFaceTrackerConnected() const { return _isFaceTrackerConnected; } + void updateEyeLookAt() { + _lookAtPosition = _requestLookAtPosition; + _lookAtUpdated = true; + } + friend class AvatarData; QJsonObject toJson() const; @@ -95,6 +95,7 @@ protected: float _basePitch; float _baseRoll; + glm::vec3 _requestLookAtPosition; glm::vec3 _lookAtPosition; quint64 _lookAtPositionChanged { 0 }; @@ -115,6 +116,8 @@ protected: QVector _summedBlendshapeCoefficients; QMap _blendshapeLookupMap; AvatarData* _owningAvatar; + bool _forceBlink { false }; + bool _lookAtUpdated { false }; private: // privatize copy ctor and assignment operator so copies of this object cannot be made From ac4346125d3cb1ca6d752a0d1c5bf7c0dc57c80a Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 9 Oct 2019 16:39:31 -0700 Subject: [PATCH 081/203] fix home location problem --- launchers/qt/src/UserSettingsRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/UserSettingsRequest.cpp b/launchers/qt/src/UserSettingsRequest.cpp index 737d103784..10e01f82ab 100644 --- a/launchers/qt/src/UserSettingsRequest.cpp +++ b/launchers/qt/src/UserSettingsRequest.cpp @@ -57,7 +57,7 @@ void UserSettingsRequest::receivedResponse() { } if (root["data"].toObject().contains("home_location")) { - auto homeLocation = root["data"].toObject()["home_location"]; + QJsonValue homeLocation = root["data"].toObject()["home_location"]; if (homeLocation.isString()) { _userSettings.homeLocation = homeLocation.toString(); } From 859e8b4ea9e8f48a5e68d3e865792992543e915d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 9 Oct 2019 17:00:34 -0700 Subject: [PATCH 082/203] Update qt launcher messaging to reflect feedback --- .../qml/HFBase/CreateAccountBase.qml | 15 ++++++++++--- launchers/qt/resources/qml/HFBase/Error.qml | 2 +- .../qt/resources/qml/HFBase/LoginBase.qml | 8 +++---- .../qml/HFControls/HFTextRegular.qml | 10 +++++++++ launchers/qt/src/LauncherState.cpp | 21 ++++++++++++++++--- launchers/qt/src/LauncherState.h | 3 ++- 6 files changed, 46 insertions(+), 13 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 06f8aaf5f3..a3e7ee5557 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -10,7 +10,7 @@ Item { anchors.centerIn: parent property string titleText: "Sign-in and pick a password" property string usernamePlaceholder: "Username" - property string passwordPlaceholder: "Set a password" + property string passwordPlaceholder: "Set a password (must be at least 6 characters)" property int marginLeft: root.width * 0.15 property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForSignup @@ -43,7 +43,7 @@ Item { id: instruction width: 425 - text: "Use the email address that you registered with." + text: "Use the email address you applied for access with." visible: LauncherState.lastSignupErrorMessage.length == 0 anchors { @@ -64,6 +64,16 @@ Item { visible: LauncherState.lastSignupErrorMessage.length > 0 text: LauncherState.lastSignupErrorMessage + textFormat: Text.StyledText + + onLinkActivated: { + if (link == "login") { + LauncherState.gotoLogin(); + } else { + LauncherState.openURLInBrowser(link) + } + } + anchors { left: root.left leftMargin: root.marginLeft @@ -194,7 +204,6 @@ Item { cursorShape: Qt.PointingHandCursor onClicked: { - console.log("clicked"); LauncherState.gotoLogin(); } } diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index 6f30a8a464..ad57f242f4 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -49,7 +49,7 @@ Item { HFTextRegular { id: description - text: "We seem to have a problem.\nPlease restart HQ Launcher" + text: "We seem to have a problem.\nPlease restart Launcher" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index bd8b5a4a1b..cb468f9bc1 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -51,11 +51,10 @@ Item { visible: LauncherState.lastLoginErrorMessage.length == 0 text: "Use the account credentials you created at sign-up" anchors { - left: parent.left - right: parent.right; - top: title.bottom topMargin: 18 + + horizontalCenter: parent.horizontalCenter } } @@ -68,8 +67,7 @@ Item { top: title.bottom topMargin: 18 - left: parent.left - right: parent.right; + horizontalCenter: parent.horizontalCenter } } diff --git a/launchers/qt/resources/qml/HFControls/HFTextRegular.qml b/launchers/qt/resources/qml/HFControls/HFTextRegular.qml index ae600c12a5..58103d6118 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextRegular.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextRegular.qml @@ -2,7 +2,17 @@ import QtQuick 2.3 import QtQuick 2.1 Text { + id: root + font.family: "Graphik Regular" font.pointSize: 10.5 + color: "#C4C4C4" + linkColor: color + + MouseArea { + anchors.fill: root + cursorShape: root.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.NoButton + } } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 10c8afcdb0..f22cf6dcf0 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -28,6 +28,10 @@ #include +#ifdef Q_OS_WIN +#include +#endif + //#define BREAK_ON_ERROR const QString configHomeLocationKey { "homeLocation" }; @@ -35,6 +39,14 @@ const QString configLastLoginKey { "lastLogin" }; const QString configLoggedInKey{ "loggedIn" }; const QString configLauncherPathKey{ "launcherPath" }; +Q_INVOKABLE void LauncherState::openURLInBrowser(QString url) { +#ifdef Q_OS_WIN + ShellExecute(0, 0, url.toLatin1(), 0, 0 , SW_SHOW); +#elif defined(Q_OS_DARWIN) + system("open \"" + url.toLatin1() + "\"); +#endif +} + void LauncherState::toggleDebugState() { _isDebuggingScreens = !_isDebuggingScreens; @@ -293,6 +305,8 @@ void LauncherState::getCurrentClientVersion() { void LauncherState::gotoSignup() { if (_applicationState == ApplicationState::WaitingForLogin) { + setLastSignupErrorMessage(""); + _lastLoginErrorMessage = ""; setApplicationState(ApplicationState::WaitingForSignup); } else { qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState; @@ -301,6 +315,7 @@ void LauncherState::gotoSignup() { void LauncherState::gotoLogin() { if (_applicationState == ApplicationState::WaitingForSignup) { + setLastLoginErrorMessage(""); setApplicationState(ApplicationState::WaitingForLogin); } else { qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState; @@ -337,7 +352,7 @@ void LauncherState::signup(QString email, QString username, QString password, QS setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::BadPassword) { - setLastSignupErrorMessage("That's an invalid password - passwords must be at least 6 characters."); + setLastSignupErrorMessage("That's an invalid password - must be at least 6 characters."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::BadUsername) { @@ -345,11 +360,11 @@ void LauncherState::signup(QString email, QString username, QString password, QS setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::UserProfileAlreadyCompleted) { - setLastSignupErrorMessage("That email has already been completed."); + setLastSignupErrorMessage("This email exists, if it's yours please login."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::NoSuchEmail) { - setLastSignupErrorMessage("That email does not have an account setup for it."); + setLastSignupErrorMessage("That email isn't setup yet. Request Access."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err != SignupRequest::Error::None) { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 7cbb09d237..32ab9bb9e9 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -25,7 +25,6 @@ class LauncherState : public QObject { Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged) Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged) Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) - Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged) Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged) Q_PROPERTY(QString lastSignupErrorMessage READ getLastSignupErrorMessage NOTIFY lastSignupErrorMessageChanged) Q_PROPERTY(QString buildVersion READ getBuildVersion) @@ -136,6 +135,8 @@ public: Q_INVOKABLE float getDownloadProgress() const { return calculateDownloadProgress(); } + Q_INVOKABLE void openURLInBrowser(QString url); + signals: void updateSourceUrl(QUrl sourceUrl); void uiStateChanged(); From e505174041c4ac230c5229ccb971f77534115ecc Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 10 Oct 2019 09:52:07 -0700 Subject: [PATCH 083/203] fix mac compile problem --- launchers/qt/src/LauncherState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index f22cf6dcf0..18774dc481 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -43,7 +43,7 @@ Q_INVOKABLE void LauncherState::openURLInBrowser(QString url) { #ifdef Q_OS_WIN ShellExecute(0, 0, url.toLatin1(), 0, 0 , SW_SHOW); #elif defined(Q_OS_DARWIN) - system("open \"" + url.toLatin1() + "\"); + system("open \"" + url.toLatin1() + "\""); #endif } From e52d2ff3426d767ba0909388c36dac7951a78f93 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Thu, 10 Oct 2019 20:57:30 -0700 Subject: [PATCH 084/203] fix mac TextFields --- .../resources/qml/HFControls/HFTextField.qml | 34 ++++++++++++++++++- launchers/qt/src/Launcher.cpp | 7 ++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index a98e73c659..133107ddd6 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -1,4 +1,4 @@ -import QtQuick 2.3 +import QtQuick 2.5 import QtQuick.Controls 2.1 TextField { @@ -55,4 +55,36 @@ TextField { } } } + + Keys.onPressed: { + event.accepted = false; + + if (Platform === "MacOS") { + if (event.key == Qt.Key_Left) { + if (control.cursorPosition > 0) { + var index = control.cursorPosition - 1; + control.select(index, index); + } + event.accepted = true; + } + + if (event.key == Qt.Key_Right) { + if (control.cursorPosition < control.text.length) { + var index = control.cursorPosition + 1; + control.select(index, index); + } + event.accepted = true; + } + + if (event.modifiers & Qt.ControlModifier) { + if (event.key == Qt.Key_C) { + control.copy(); + event.accepted = true; + } else if (event.key == Qt.Key_V) { + control.paste(); + event.accepted = true; + } + } + } + } } diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index e2d660e782..edc52f6427 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -12,9 +12,16 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherState = std::make_shared(); + QString platform; +#ifdef Q_OS_WIN + platform = "Windows"; +#elif defined(Q_OS_MACOS) + platform = "MacOS"; +#endif _launcherWindow = std::make_unique(); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); + _launcherWindow->rootContext()->setContextProperty("Platform", platform); _launcherWindow->setTitle("High Fidelity"); _launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window); _launcherWindow->setLauncherStatePtr(_launcherState); From e4bc3a7d9d59a3050685ef86c6f4bc123d253187 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Oct 2019 07:58:13 +1300 Subject: [PATCH 085/203] Recording JSDoc --- .../src/RecordingScriptingInterface.h | 178 +++++++++++++++--- 1 file changed, 147 insertions(+), 31 deletions(-) diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 604ec6bc2e..19db6dc50d 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -24,6 +24,9 @@ class QScriptEngine; class QScriptValue; /**jsdoc + * The Recording API makes and plays back recordings of voice and avatar movements. Playback may be done on a + * user's avatar or an assignment client agent (see the {@link Agent} API). + * * @namespace Recording * * @hifi-interface @@ -40,56 +43,79 @@ public: public slots: /**jsdoc - * @function Recording.loadRecording - * @param {string} url - * @param {Recording~loadRecordingCallback} [callback=null] + * Called when a {@link Recording.loadRecording} call is complete. + * @callback Recording~loadRecordingCallback + * @param {boolean} success - true if the recording has successfully been loaded, false if it + * hasn't. + * @param {string} url - The URL of the recording that was requested to be loaded. */ /**jsdoc - * Called when {@link Recording.loadRecording} is complete. - * @callback Recording~loadRecordingCallback - * @param {boolean} success - * @param {string} url + * Loads a recording so that it is ready for playing. + * @function Recording.loadRecording + * @param {string} url - The ATP, HTTP, or file system URL of the recording to load. + * @param {Recording~loadRecordingCallback} [callback=null] - The function to call upon completion. + * @example Load and play back a recording from the asset server. + * var assetPath = Window.browseAssets(); + * print("Asset path: " + assetPath); + * + * if (assetPath.slice(-4) === ".hfr") { + * Recording.loadRecording("atp:" + assetPath, function (success, url) { + * if (!success) { + * print("Error loading recording."); + * return; + * } + * Recording.startPlaying(); + * }); + * } */ void loadRecording(const QString& url, QScriptValue callback = QScriptValue()); /**jsdoc + * Starts playing the recording currently loaded or paused. * @function Recording.startPlaying */ void startPlaying(); /**jsdoc + * Pauses playback of the recording currently playing. Use {@link Recording.startPlaying|startPlaying} to resume playback + * or {@link Recording.stopPlaying|stopPlaying} to stop playback. * @function Recording.pausePlayer */ void pausePlayer(); /**jsdoc + * Stops playing the recording currently playing or paused. * @function Recording.stopPlaying */ void stopPlaying(); /**jsdoc + * Gets whether a recording is currently playing. * @function Recording.isPlaying - * @returns {boolean} + * @returns {boolean} true if a recording is being played, false if one isn't. */ bool isPlaying() const; /**jsdoc + * Gets whether recording playback is currently paused. * @function Recording.isPaused - * @returns {boolean} + * @returns {boolean} true if recording playback is currently paused, false if it isn't. */ bool isPaused() const; /**jsdoc + * Gets the current playback time in the loaded recording, in seconds. * @function Recording.playerElapsed - * @returns {number} + * @returns {number} The current playback time in the loaded recording, in seconds. */ float playerElapsed() const; /**jsdoc + * Gets the length of the loaded recording, in seconds. * @function Recording.playerLength - * @returns {number} + * @returns {number} The length of the recording currently loaded, in seconds */ float playerLength() const; @@ -102,132 +128,222 @@ public slots: void setPlayerVolume(float volume); /**jsdoc + *

Not implemented: This method is not implemented yet.

* @function Recording.setPlayerAudioOffset - * @param {number} audioOffset + * @param {number} audioOffset - Audio offset. */ void setPlayerAudioOffset(float audioOffset); /**jsdoc + * Sets the current playback time in the loaded recording. * @function Recording.setPlayerTime - * @param {number} time + * @param {number} time - The current playback time, in seconds. */ void setPlayerTime(float time); /**jsdoc + * Sets whether playback should repeat in a loop. * @function Recording.setPlayerLoop - * @param {boolean} loop + * @param {boolean} loop - true if playback should repeat, false if it shouldn't. */ void setPlayerLoop(bool loop); /**jsdoc + * Sets whether recording playback will use the display name that the recording was made with. * @function Recording.setPlayerUseDisplayName - * @param {boolean} useDisplayName + * @param {boolean} useDisplayName - true to have recording playback use the display name that the recording + * was made with, false to have recording playback keep the current display name. */ void setPlayerUseDisplayName(bool useDisplayName); /**jsdoc + *

Not used.

* @function Recording.setPlayerUseAttachments - * @param {boolean} useAttachments + * @param {boolean} useAttachments - Use attachments. + * @deprecated This method is deprecated and will be removed. */ void setPlayerUseAttachments(bool useAttachments); /**jsdoc + *

Not used.

* @function Recording.setPlayerUseHeadModel - * @param {boolean} useHeadModel - * @todo Note: This function currently has no effect. + * @param {boolean} useHeadModel - Use head model. + * @deprecated This method is deprecated and will be removed. */ void setPlayerUseHeadModel(bool useHeadModel); /**jsdoc + * Sets whether recording playback will use the avatar model that the recording was made with. * @function Recording.setPlayerUseSkeletonModel - * @param {boolean} useSkeletonModel - * @todo Note: This function currently doesn't work. + * @param {boolean} useSkeletonModel - true to have recording playback use the avatar model that the recording + * was made with, false to have playback use the current avatar model. */ void setPlayerUseSkeletonModel(bool useSkeletonModel); /**jsdoc + * Sets whether recordings are played at the current avatar location or the recorded location. * @function Recording.setPlayFromCurrentLocation - * @param {boolean} playFromCurrentLocation + * @param {boolean} playFromCurrentLocation - true to play recordings at the current avatar location, + * false to play recordings at the recorded location. */ void setPlayFromCurrentLocation(bool playFromCurrentLocation); /**jsdoc + * Gets whether recording playback will use the display name that the recording was made with. * @function Recording.getPlayerUseDisplayName - * @returns {boolean} + * @returns {boolean} true if recording playback will use the display name that the recording was made with, + * false if playback will keep the current display name. */ bool getPlayerUseDisplayName() { return _useDisplayName; } /**jsdoc + *

Not used.

* @function Recording.getPlayerUseAttachments - * @returns {boolean} + * @returns {boolean} Use attachments. + * @deprecated This method is deprecated and will be removed. */ bool getPlayerUseAttachments() { return _useAttachments; } /**jsdoc + *

Not used.

* @function Recording.getPlayerUseHeadModel - * @returns {boolean} + * @returns {boolean} Use head model. + * @deprecated This method is deprecated and will be removed. */ bool getPlayerUseHeadModel() { return _useHeadModel; } /**jsdoc + * Gets whether recording playback will use the avatar model that the recording was made with. * @function Recording.getPlayerUseSkeletonModel - * @returns {boolean} + * @returns {boolean} true if recording playback will use the avatar model that the recording was made with, + * false if playback will use the current avatar model. */ bool getPlayerUseSkeletonModel() { return _useSkeletonModel; } /**jsdoc + * Gets whether recordings are played at the current avatar location or the recorded location. * @function Recording.getPlayFromCurrentLocation - * @returns {boolean} + * @returns {boolean} true if recordings are played at the current avatar location, false if + * played at the recorded location. */ bool getPlayFromCurrentLocation() { return _playFromCurrentLocation; } /**jsdoc + * Starts making a recording. * @function Recording.startRecording */ void startRecording(); /**jsdoc + * Stops making a recording. The recording may be saved using {@link Recording.saveRecording|saveRecording} or + * {@link Recording.saveRecordingToAsset|saveRecordingToAsset}, or immediately played back with + * {@link Recording.loadLastRecording|loadLastRecording}. * @function Recording.stopRecording */ void stopRecording(); /**jsdoc + * Gets whether a recording is currently being made. * @function Recording.isRecording - * @returns {boolean} + * @returns {boolean} true if a recording is currently being made, false if one isn't. */ bool isRecording() const; /**jsdoc + * Gets the duration of the recording currently being made or recently made, in seconds. * @function Recording.recorderElapsed - * @returns {number} + * @returns {number} The duration of the recording currently being made or recently made, in seconds. */ float recorderElapsed() const; /**jsdoc + * Gets the default directory that recordings are saved in. * @function Recording.getDefaultRecordingSaveDirectory - * @returns {string} + * @returns {string} The default recording save directory. + * @exampl Report the default save directory. + * print("Default save directory: " + Recording.getDefaultRecordingSaveDirectory()); */ QString getDefaultRecordingSaveDirectory(); /**jsdoc + * Saves the most recently made recording to a file. * @function Recording.saveRecording - * @param {string} filename + * @param {string} filename - The path and name of the file to save the recording to. + * @example Save a 5 second recording to a file. + * Recording.startRecording(); + * + * Script.setTimeout(function () { + * Recording.stopRecording(); + * var filename = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ + * filename = filename.slice(0, -5).replace(/:/g, "").replace("T", "-") + * + ".hfr"; // yyyymmmdd-hhmmss.hfr + * filename = Recording.getDefaultRecordingSaveDirectory() + filename; + * Recording.saveRecording(filename); + * print("Saved recording: " + filename); + * }, 5000); */ void saveRecording(const QString& filename); /**jsdoc + * Called when a {@link Recording.saveRecordingToAsset} call is complete. + * @callback Recording~saveRecordingToAssetCallback + * @param {string} url - The URL of the recording stored in the asset server if successful, "" if + * unsuccessful. The URL has atp: as the scheme and the SHA256 hash as the filename (with no extension). + */ + /**jsdoc + * Saves the most recently made recording to the domain's asset server. * @function Recording.saveRecordingToAsset - * @param {function} getClipAtpUrl + * @param {Recording~saveRecordingToAssetCallback} callback - The function to call upon completion. + * @returns {boolean} true if the recording is successfully being saved, false if not. + * @example Save a 5 second recording to the asset server. + * function onSavedRecordingToAsset(url) { + * if (url === "") { + * print("Couldn't save recording."); + * return; + * } + * + * print("Saved recording: " + url); // atp:SHA256 + * + * var filename = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ + * filename = filename.slice(0, -5).replace(/:/g, "").replace("T", "-") + * + ".hfr"; // yyyymmmdd-hhmmss.hfr + * var hash = url.slice(4); // Remove leading "atp:" from url. + * mappingPath = "/recordings/" + filename; + * Assets.setMapping(mappingPath, hash, function (error) { + * if (error) { + * print("Mapping error: " + error); + * } + * }); + * print("Mapped recording: " + mappingPath); // /recordings/filename + * } + * + * Recording.startRecording(); + * + * Script.setTimeout(function () { + * Recording.stopRecording(); + * var success = Recording.saveRecordingToAsset(onSavedRecordingToAsset); + * if (!success) { + * print("Couldn't save recording."); + * } + * }, 5000); */ bool saveRecordingToAsset(QScriptValue getClipAtpUrl); /**jsdoc + * Loads the most recently made recording and plays it back on your avatar. * @function Recording.loadLastRecording + * @example Make a 5 second recording and immediately play it back on your avatar. + * Recording.startRecording(); + * + * Script.setTimeout(function () { + * Recording.stopRecording(); + * Recording.loadLastRecording(); + * }, 5000); */ void loadLastRecording(); From 1715a86dbaf629fa1853312de3d844e76650e45f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Oct 2019 07:58:26 +1300 Subject: [PATCH 086/203] Fix typo in Assets JSDoc --- libraries/script-engine/src/AssetScriptingInterface.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 955adaa86c..3ba7632cd0 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -30,8 +30,8 @@ * format: atp:/path/filename. The assets may optionally be baked, in which case a request for the original * unbaked version of the asset is automatically redirected to the baked version. The asset data may optionally be stored as * compressed.

- *

The client cache can be access directly, using "atp:" or "cache:" URLs. Interface, avatar, and - * assignment client scripts can write to the cache. All script types can read from the cache.

+ *

The client cache can be accessed directly, using "atp:" or "cache:" URLs. Interface, avatar, + * and assignment client scripts can write to the cache. All script types can read from the cache.

* * @namespace Assets * From 9f35a3b803e648104b0967c4059bda2b56aa7c45 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Fri, 11 Oct 2019 15:28:35 -0700 Subject: [PATCH 087/203] setting some more testing env --- launchers/qt/src/CommandlineOptions.cpp | 5 +++++ launchers/qt/src/CommandlineOptions.h | 1 + launchers/qt/src/LauncherState.cpp | 12 +++++++++++- launchers/qt/src/LauncherState.h | 2 +- launchers/qt/src/main.cpp | 10 ++++++++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/launchers/qt/src/CommandlineOptions.cpp b/launchers/qt/src/CommandlineOptions.cpp index 9254644f8f..fa60e0d236 100644 --- a/launchers/qt/src/CommandlineOptions.cpp +++ b/launchers/qt/src/CommandlineOptions.cpp @@ -26,6 +26,11 @@ void CommandlineOptions::parse(const int argc, char** argv) { } } +void CommandlineOptions::append(const std::string& command) { + qDebug() << "appending option: " << QString::fromStdString(command); + _commandlineOptions.push_back(command); +} + CommandlineOptions* CommandlineOptions::getInstance() { static CommandlineOptions commandlineOptions; return &commandlineOptions; diff --git a/launchers/qt/src/CommandlineOptions.h b/launchers/qt/src/CommandlineOptions.h index 4a34bd1337..2b0937b3ae 100644 --- a/launchers/qt/src/CommandlineOptions.h +++ b/launchers/qt/src/CommandlineOptions.h @@ -10,6 +10,7 @@ public: void parse(const int argc, char** argv); bool contains(const std::string& option); + void append(const std::string& command); static CommandlineOptions* getInstance(); private: std::vector _commandlineOptions; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 18774dc481..763718a08e 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -230,8 +230,18 @@ void LauncherState::requestBuilds() { request->send(_networkAccessManager); } +QString LauncherState::getBuildVersion() { + QString buildVersion { LAUNCHER_BUILD_VERSION }; + QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); + if (processEnvironment.contains("HQ_LAUNCHER_BUILD_VERSION")) { + buildVersion = processEnvironment.value("HQ_LAUNCHER_BUILD_VERSION"); + } + + return buildVersion; +} + bool LauncherState::shouldDownloadLauncher() { - return _latestBuilds.launcherBuild.latestVersion != atoi(LAUNCHER_BUILD_VERSION); + return _latestBuilds.launcherBuild.latestVersion != getBuildVersion().toInt(); } void LauncherState::getCurrentClientVersion() { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 32ab9bb9e9..11a8fbaaaf 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -94,7 +94,7 @@ public: void setLastSignupErrorMessage(const QString& msg); QString getLastSignupErrorMessage() const { return _lastSignupErrorMessage; } - QString getBuildVersion() { return QString(LAUNCHER_BUILD_VERSION); } + QString getBuildVersion(); QString getLastUsedUsername() const { return _lastUsedUsername; } void setApplicationStateError(QString errorMessage); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index ee3256dc66..4e0e1b7437 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -7,6 +7,7 @@ #include "CommandlineOptions.h" #include #include +#include #include "Helper.h" #ifdef Q_OS_WIN @@ -36,6 +37,7 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE(resources); cleanLogFile(); qInstallMessageHandler(messageHandler); + bool didUpdate = false; #ifdef Q_OS_MAC if (isLauncherAlreadyRunning()) { return 0; @@ -44,6 +46,7 @@ int main(int argc, char *argv[]) { if (argc == 3) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { swapLaunchers(argv[1], argv[2]); + didUpdate = true; } } #endif @@ -64,6 +67,13 @@ int main(int argc, char *argv[]) { } #endif + + QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); + if (processEnvironment.contains("HQ_LAUNCHER_BUILD_VERSION")) { + if (didUpdate || options->contains("--restart")) { + options->append("--noUpdate"); + } + } Launcher launcher(argc, argv); return launcher.exec(); } From 94f9e35704637832b5d493ecf8b522ccce8ba448 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Oct 2019 11:41:53 +1300 Subject: [PATCH 088/203] Toolbars JSDoc --- .../ui/src/ui/ToolbarScriptingInterface.h | 110 +++++++++--------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.h b/libraries/ui/src/ui/ToolbarScriptingInterface.h index 952d3cce95..a4746e4566 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.h +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.h @@ -19,57 +19,16 @@ class QQuickItem; -/**jsdoc - * @class ToolbarButtonProxy - * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - */ +// No JSDoc for ToolbarButtonProxy because oolbarProxy#addButton() doesn't work. class ToolbarButtonProxy : public QmlWrapper { Q_OBJECT public: ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr); - /**jsdoc - * @function ToolbarButtonProxy#editProperties - * @param {object} properties - */ Q_INVOKABLE void editProperties(const QVariantMap& properties); - - // QmlWrapper methods. - - /**jsdoc - * @function ToolbarButtonProxy#writeProperty - * @parm {string} propertyName - * @param {object} propertyValue - */ - - /**jsdoc - * @function ToolbarButtonProxy#writeProperties - * @param {object} properties - */ - - /**jsdoc - * @function ToolbarButtonProxy#readProperty - * @param {string} propertyName - * @returns {object} - */ - - /**jsdoc - * @function ToolbarButtonProxy#readProperties - * @param {string[]} propertyList - * @returns {object} - */ - signals: - - /**jsdoc - * @function ToolbarButtonProxy#clicked - * @returns {Signal} - */ void clicked(); protected: @@ -80,7 +39,12 @@ protected: Q_DECLARE_METATYPE(ToolbarButtonProxy*); /**jsdoc + * An instance of a toolbar. + * + *

Retrieve an existing toolbar or create a new toolbar using {@link Toolbars.getToolbar}.

+ * * @class ToolbarProxy + * @hideconstructor * * @hifi-interface * @hifi-client-entity @@ -92,15 +56,19 @@ public: ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr); /**jsdoc + * Currently doesn't work. * @function ToolbarProxy#addButton - * @param {object} properties - * @returns {ToolbarButtonProxy} + * @param {object} properties - Button properties + * @returns {object} The button added. + * @deprecated This method is deprecated and will be removed. */ Q_INVOKABLE ToolbarButtonProxy* addButton(const QVariant& properties); /**jsdoc + * Currently doesn't work. * @function ToolbarProxy#removeButton - * @param {string} name + * @param {string} name - Button name. + * @deprecated This method is deprecated and will be removed. */ Q_INVOKABLE void removeButton(const QVariant& name); @@ -108,32 +76,46 @@ public: // QmlWrapper methods. /**jsdoc + * Sets the value of a toolbar property. A property is added to the toolbar if the named property doesn't already + * exist. * @function ToolbarProxy#writeProperty - * @parm {string} propertyName - * @param {object} propertyValue + * @parm {string} propertyName - The name of the property. Toolbar properties are those in the QML implementation of the + * toolbar. + * @param {object} propertyValue - The value of the property. */ /**jsdoc + * Sets the values of toolbar properties. A property is added to the toolbar if a named property doesn't already + * exist. * @function ToolbarProxy#writeProperties - * @param {object} properties + * @param {object} properties - The names and values of the properties to set. Toolbar properties are those in the QML + * implementation of the toolbar. */ /**jsdoc + * Gets the value of a toolbar property. * @function ToolbarProxy#readProperty - * @param {string} propertyName - * @returns {object} + * @param {string} propertyName - The property name. Toolbar properties are those in the QML implementation of the toolbar. + * @returns {object} The value of the property if the property name is valid, otherwise undefined. */ /**jsdoc + * Gets the values of toolbar properties. * @function ToolbarProxy#readProperties - * @param {string[]} propertyList - * @returns {object} + * @param {string[]} propertyList - The names of the properties to get the values of. Toolbar properties are those in the + * QML implementation of the toolbar. + * @returns {object} The names and values of the specified properties. If the toolbar doesn't have a particular property + * then the result doesn't include that property. */ }; Q_DECLARE_METATYPE(ToolbarProxy*); /**jsdoc + * The Toolbars API provides facilities to work with the system or other toolbar. + * + *

See also the {@link Tablet} API for use of the system tablet and toolbar in desktop and HMD modes.

+ * * @namespace Toolbars * * @hifi-interface @@ -145,13 +127,33 @@ class ToolbarScriptingInterface : public QObject, public Dependency { public: /**jsdoc + * Gets an instance of a toolbar. A new toolbar is created if one with the specified name doesn't already exist. * @function Toolbars.getToolbar - * @param {string} toolbarID - * @returns {ToolbarProxy} + * @param {string} name - A unique name that identifies the toolbar. + * @returns {ToolbarProxy} The toolbar instance. */ Q_INVOKABLE ToolbarProxy* getToolbar(const QString& toolbarId); signals: + /**jsdoc + * Triggered when the visibility of a toolbar changes. + * @function Toolbars.toolbarVisibleChanged + * @param {boolean} isVisible - true if the toolbar is visible, false if it is hidden. + * @param {string} toolbarName - The name of the toolbar. + * @returns {Signal} + * @example Briefly hide the system toolbar. + * Toolbars.toolbarVisibleChanged.connect(function(visible, name) { + * print("Toolbar " + name + " visible changed to " + visible); + * }); + * + * var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + * if (toolbar) { + * toolbar.writeProperty("visible", false); + * Script.setTimeout(function () { + * toolbar.writeProperty("visible", true); + * }, 2000); + * } + */ void toolbarVisibleChanged(bool isVisible, QString toolbarName); }; From d4c5a20c4fe5cfffc41abe39f3211596efaeffe4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Oct 2019 11:42:49 +1300 Subject: [PATCH 089/203] Tablet JSDoc fixes --- libraries/ui/src/ui/TabletScriptingInterface.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 88d4ebe267..04222b3ea1 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -39,9 +39,10 @@ class QmlWindowClass; class OffscreenQmlSurface; /**jsdoc - * The Tablet API provides the facilities to work with the system or other tablet. In toolbar mode (Developer > - * UI > Tablet Becomes Toolbar), the tablet's menu buttons are displayed in a toolbar and other tablet content is displayed - * in a dialog. + * The Tablet API provides the facilities to work with the system or other tablet. In toolbar mode (see Developer + * > UI options), the tablet's menu buttons are displayed in a toolbar and other tablet content is displayed in a dialog. + * + *

See also the {@link Toolbars} API for working with toolbars.

* * @namespace Tablet * @@ -98,7 +99,7 @@ public: void setToolbarScriptingInterface(ToolbarScriptingInterface* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; } /**jsdoc - * Gets an instance of a tablet. A new tablet is created if one with the specified ID doesn't already exist. + * Gets an instance of a tablet. A new tablet is created if one with the specified name doesn't already exist. * @function Tablet.getTablet * @param {string} name - A unique name that identifies the tablet. * @returns {TabletProxy} The tablet instance. @@ -210,11 +211,10 @@ private: Q_DECLARE_METATYPE(TabletButtonsProxyModel*); /**jsdoc - * An instance of a tablet. In toolbar mode (Developer > - * UI > Tablet Becomes Toolbar), the tablet's menu buttons are displayed in a toolbar and other tablet content is displayed - * in a dialog. + * An instance of a tablet. In toolbar mode (see Developer > UI options), the tablet's menu buttons are displayed in a + * toolbar and other tablet content is displayed in a dialog. * - *

Create a new tablet or retrieve an existing tablet using {@link Tablet.getTablet}.

+ *

Retrieve an existing tablet or create a new tablet using {@link Tablet.getTablet}.

* * @class TabletProxy * @@ -317,7 +317,7 @@ public: Q_INVOKABLE void returnToPreviousAppImpl(bool localSafeContext); /**jsdoc - *@function TabletProxy#loadQMLOnTopImpl + * @function TabletProxy#loadQMLOnTopImpl * @deprecated This function is deprecated and will be removed. */ // Internal function, do not call from scripts. From f43f1915583edf8c0d7eac923d3d93c1846b3227 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 11 Oct 2019 17:45:08 -0700 Subject: [PATCH 090/203] Fix login and signup screens not working with + in input --- launchers/qt/src/LoginRequest.cpp | 6 +++--- launchers/qt/src/SignupRequest.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launchers/qt/src/LoginRequest.cpp b/launchers/qt/src/LoginRequest.cpp index 1c08847c6a..ecbbd8d743 100644 --- a/launchers/qt/src/LoginRequest.cpp +++ b/launchers/qt/src/LoginRequest.cpp @@ -15,11 +15,11 @@ void LoginRequest::send(QNetworkAccessManager& nam, QString username, QString pa QUrlQuery query; query.addQueryItem("grant_type", "password"); - query.addQueryItem("username", username); - query.addQueryItem("password", password); + query.addQueryItem("username", QUrl::toPercentEncoding(username)); + query.addQueryItem("password", QUrl::toPercentEncoding(password)); query.addQueryItem("scope", "owner"); - auto reply = nam.post(request, query.toString().toUtf8()); + auto reply = nam.post(request, query.query(QUrl::FullyEncoded).toLatin1()); QObject::connect(reply, &QNetworkReply::finished, this, &LoginRequest::receivedResponse); } diff --git a/launchers/qt/src/SignupRequest.cpp b/launchers/qt/src/SignupRequest.cpp index 91f495e73a..84fbc66db5 100644 --- a/launchers/qt/src/SignupRequest.cpp +++ b/launchers/qt/src/SignupRequest.cpp @@ -23,11 +23,11 @@ void SignupRequest::send(QNetworkAccessManager& nam, QString email, QString user request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QUrlQuery query; - query.addQueryItem("email", email); - query.addQueryItem("username", username); - query.addQueryItem("password", password); + query.addQueryItem("email", QUrl::toPercentEncoding(email)); + query.addQueryItem("username", QUrl::toPercentEncoding(username)); + query.addQueryItem("password", QUrl::toPercentEncoding(password)); - auto reply = nam.put(request, query.toString().toUtf8()); + auto reply = nam.put(request, query.query(QUrl::FullyEncoded).toLatin1()); QObject::connect(reply, &QNetworkReply::finished, this, &SignupRequest::receivedResponse); } From 505813b3c356bfee7835a96c89f8e5d02a3db9bf Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 14 Oct 2019 10:44:55 -0700 Subject: [PATCH 091/203] Fix blink async and other is talking bug --- interface/src/avatar/MyAvatar.cpp | 15 +++++---------- interface/src/avatar/MyAvatar.h | 1 - .../src/avatars-renderer/Head.cpp | 8 +++++--- libraries/avatars/src/AvatarData.cpp | 9 +++++++++ libraries/avatars/src/AvatarData.h | 1 + libraries/avatars/src/HeadData.cpp | 16 ++++++++++------ libraries/avatars/src/HeadData.h | 7 ++----- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f529f168f4..0abbd4e324 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2150,7 +2150,7 @@ static float lookAtCostFunction(const glm::vec3& myForward, const glm::vec3& myP const float DISTANCE_FACTOR = 3.14f; const float MY_ANGLE_FACTOR = 1.0f; const float OTHER_ANGLE_FACTOR = 1.0f; - const float OTHER_IS_TALKING_TERM = otherIsTalking ? 1.0f : 0.0f; + const float OTHER_IS_TALKING_TERM = otherIsTalking ? -1.0f : 0.0f; const float LOOKING_AT_OTHER_ALREADY_TERM = lookingAtOtherAlready ? -0.2f : 0.0f; const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; // meters @@ -2176,6 +2176,9 @@ static float lookAtCostFunction(const glm::vec3& myForward, const glm::vec3& myP void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) { glm::vec3 myForward = _lookAtYaw * IDENTITY_FORWARD; + if (_skeletonModel->isLoaded()) { + myForward = getHeadJointFrontVector(); + } glm::vec3 myPosition = getHead()->getEyePosition(); CameraMode mode = qApp->getCamera().getMode(); if (mode == CAMERA_MODE_FIRST_PERSON) { @@ -2189,7 +2192,7 @@ void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) { std::shared_ptr avatar = std::static_pointer_cast(avatarData); if (!avatar->isMyAvatar() && avatar->isInitialized()) { if (_forceTargetAvatarID.isNull() || avatar->getID() != _forceTargetAvatarID) { - glm::vec3 otherForward = avatar->getHead()->getForwardDirection(); + glm::vec3 otherForward = avatar->getHeadJointFrontVector(); glm::vec3 otherPosition = avatar->getHead()->getEyePosition(); const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f; bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD; @@ -6842,11 +6845,3 @@ void MyAvatar::resetPointAt() { POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING); } } - -void MyAvatar::setLookAtAvatarID(const QUuid& avatarID) { - if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(this, "setLookAtAvatarID", - Q_ARG(const QUuid&, avatarID)); - } - _forceTargetAvatarID = avatarID; -} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d93ee776be..3f91222392 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -971,7 +971,6 @@ public: * @param {Uuid} entityID - The entity that the hand touch effect will be enabled for. */ Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID); - Q_INVOKABLE void setLookAtAvatarID(const QUuid& avatarID); bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); } void setUseAdvancedMovementControls(bool useAdvancedMovementControls) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index a9dde201af..8184804e1f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -96,12 +96,15 @@ void Head::simulate(float deltaTime) { // no blinking when brows are raised; blink less with increasing loudness const float BASE_BLINK_RATE = 15.0f / 60.0f; const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; - if (_forceBlink || forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * + if (_blinkToRetarget || forceBlink || + (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { float randSpeedVariability = randFloat(); float eyeBlinkVelocity = BLINK_SPEED + randSpeedVariability * BLINK_SPEED_VARIABILITY; - if (_forceBlink) { + if (_blinkToRetarget) { + // Slow down by half the blink if reseting eye target eyeBlinkVelocity = 0.5f * eyeBlinkVelocity; + _blinkToRetarget = false; } _leftEyeBlinkVelocity = eyeBlinkVelocity; _rightEyeBlinkVelocity = eyeBlinkVelocity; @@ -123,7 +126,6 @@ void Head::simulate(float deltaTime) { } if (_rightEyeBlink == FULLY_CLOSED) { _rightEyeBlinkVelocity = -BLINK_SPEED; - } else if (_rightEyeBlink == FULLY_OPEN) { _rightEyeBlinkVelocity = 0.0f; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a91154ff15..69694a00c3 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -3214,3 +3214,12 @@ void AvatarData::clearAvatarGrabData(const QUuid& grabID) { } }); } + +glm::vec3 AvatarData::getHeadJointFrontVector() const { + int headJointIndex = getJointIndex("Head"); + glm::quat headJointRotation = Quaternions::Y_180 * getAbsoluteJointRotationInObjectFrame(headJointIndex);// getAbsoluteJointRotationInRigFrame(headJointIndex, headJointRotation); + headJointRotation = getWorldOrientation() * headJointRotation; + float headYaw = safeEulerAngles(headJointRotation).y; + glm::quat headYawRotation = glm::angleAxis(headYaw, Vectors::UP); + return headYawRotation * IDENTITY_FORWARD; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 59a2e2a53e..bfe775e972 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1484,6 +1484,7 @@ public: std::vector getSkeletonData() const; void sendSkeletonData() const; QVector getJointData() const; + glm::vec3 getHeadJointFrontVector() const; signals: diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index f8a09905ef..7d05a50143 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -240,12 +240,16 @@ void HeadData::setLookAtPosition(const glm::vec3& lookAtPosition) { glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees - _forceBlink = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; + _blinkToRetarget = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; _lookAtUpdated = false; + } + if (_lookAtUpdated) { + _lookAtPosition = lookAtPosition; } _requestLookAtPosition = lookAtPosition; - if (_lookAtUpdated) { - _forceBlink = false; - _lookAtPosition = lookAtPosition; - } -} \ No newline at end of file +} + +void HeadData::updateEyeLookAt() { + _lookAtPosition = _requestLookAtPosition; + _lookAtUpdated = true; +} diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index a8005b5659..7b73f57113 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -79,10 +79,7 @@ public: void setFaceTrackerConnected(bool value); bool getFaceTrackerConnected() const { return _isFaceTrackerConnected; } - void updateEyeLookAt() { - _lookAtPosition = _requestLookAtPosition; - _lookAtUpdated = true; - } + void updateEyeLookAt(); friend class AvatarData; @@ -116,7 +113,7 @@ protected: QVector _summedBlendshapeCoefficients; QMap _blendshapeLookupMap; AvatarData* _owningAvatar; - bool _forceBlink { false }; + bool _blinkToRetarget { false }; bool _lookAtUpdated { false }; private: From c2fb97159f5af0e8762aab0d38d52e4b995a623e Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 14 Oct 2019 13:12:05 -0700 Subject: [PATCH 092/203] mac suppress reset settings --- launchers/qt/src/Helper_darwin.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 684143e11b..2015c830cf 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -22,7 +22,8 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr @"--defaultScriptsOverride", defaultScriptOverride.toNSString(), @"--setBookmark", homeBookmark, @"--no-updater", - @"--no-launcher", nil]; + @"--no-launcher", + @"--suppress-settings-reset", nil]; } else { arguments = [NSArray arrayWithObjects: @"--url" , homePath.toNSString(), @@ -30,7 +31,8 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr @"--defaultScriptsOverride", defaultScriptOverride.toNSString(), @"--setBookmark", homeBookmark, @"--no-updater", - @"--no-launcher", nil]; + @"--no-launcher", + @"--suppress-settings-reset", nil]; } NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; From 7de5a6852a20d4531b7903fdc763580ac001a71a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 15 Oct 2019 14:42:12 -0700 Subject: [PATCH 093/203] Update qt launcher to use static run-time libraries on Windows --- launchers/qt/CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 0ff6a4e084..3b3374f152 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -15,12 +15,13 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) include("cmake/init.cmake") include("cmake/macros/SetPackagingParameters.cmake") - if(MSVC) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) - endif() +if(MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) -if (WIN32) - set(CMAKE_MFC_FLAG 1) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MT") + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /MT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") endif() @@ -44,8 +45,8 @@ if (WIN32) ExternalProject_Add( qtlite - URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win-oct-9-2019.zip" - URL_HASH MD5=4c33eeca16563855be50dab8dd4f0f53 + URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win-oct-15-2019.zip" + URL_HASH MD5=0176277bca37d219e83738caf3a698eb CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 53b4ff724d0493e0df5042d99bc304373e69d35f Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 15 Oct 2019 16:20:42 -0700 Subject: [PATCH 094/203] Continue to run new HQ Launcher if auto-updater fails --- launchers/qt/src/Helper.cpp | 11 +++-------- launchers/qt/src/Helper.h | 2 +- launchers/qt/src/Helper_darwin.mm | 12 +++++++++--- launchers/qt/src/LoginRequest.cpp | 2 +- launchers/qt/src/main.cpp | 10 +++++++--- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 42f76fe0fa..2fe3ecc46a 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -60,22 +60,17 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt outFile.close(); } -void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { +bool swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) { qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist"; } bool success = false; #ifdef Q_OS_MAC + qDebug() << "replacing launchers -> old launcher: " << oldLauncherPath << " new launcher: " << newLauncherPath; success = replaceDirectory(oldLauncherPath, newLauncherPath); #endif - - if (success) { - qDebug() << "succeessfully replaced"; - } else { - qDebug() << "failed"; - exit(0); - } + return success; } diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 2d1f06052f..619b754c5a 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -12,7 +12,7 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr void launchAutoUpdater(const QString& autoUpdaterPath); -void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString()); +bool swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString()); void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message); void cleanLogFile(); diff --git a/launchers/qt/src/Helper_darwin.mm b/launchers/qt/src/Helper_darwin.mm index 2015c830cf..8b7ad16a87 100644 --- a/launchers/qt/src/Helper_darwin.mm +++ b/launchers/qt/src/Helper_darwin.mm @@ -97,10 +97,17 @@ void launchAutoUpdater(const QString& autoUpdaterPath) { bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory) { + NSError *error = nil; NSFileManager* fileManager = [NSFileManager defaultManager]; NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newDirectory.toNSString()]; - return (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()] - backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:nil]; + bool success = (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()] + backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:&error]; + + if (error != nil) { + qDebug() << "NSFileManager::replaceItemAtURL -> error: " << error; + } + + return success; } @@ -124,7 +131,6 @@ void waitForInterfaceToClose() { bool isLauncherAlreadyRunning() { NSArray* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.highfidelity.launcher"]; if ([apps count] > 1) { - NSLog(@"launcher is already running"); qDebug() << "launcher is already running"; return true; } diff --git a/launchers/qt/src/LoginRequest.cpp b/launchers/qt/src/LoginRequest.cpp index ecbbd8d743..5aed5d2700 100644 --- a/launchers/qt/src/LoginRequest.cpp +++ b/launchers/qt/src/LoginRequest.cpp @@ -73,7 +73,7 @@ void LoginRequest::receivedResponse() { _token.refreshToken = root["refresh_token"].toString(); _token.tokenType = root["token_type"].toString(); - qDebug() << "Got response for login: " << data; + qDebug() << "Got response for login"; _rawLoginToken = data; emit finished(); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 4e0e1b7437..c3971d03d9 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -37,6 +37,8 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE(resources); cleanLogFile(); qInstallMessageHandler(messageHandler); + CommandlineOptions* options = CommandlineOptions::getInstance(); + options->parse(argc, argv); bool didUpdate = false; #ifdef Q_OS_MAC if (isLauncherAlreadyRunning()) { @@ -45,13 +47,15 @@ int main(int argc, char *argv[]) { closeInterfaceIfRunning(); if (argc == 3) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { - swapLaunchers(argv[1], argv[2]); + bool success = swapLaunchers(argv[1], argv[2]); + qDebug() << "Launcher install success: " << success; + if (!success) { + options->append("--noUpdate"); + } didUpdate = true; } } #endif - CommandlineOptions* options = CommandlineOptions::getInstance(); - options->parse(argc, argv); #ifdef Q_OS_WIN LauncherInstaller launcherInstaller(argv[0]); if (options->contains("--uninstall") || options->contains("--resumeUninstall")) { From 7474f3f721c5535ba0aa1b057a9b980c71112647 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 15 Oct 2019 16:27:14 -0700 Subject: [PATCH 095/203] add qt-lite-mac-config.opt --- tools/qt-builder/qt-lite-osx-config.opt | 184 ++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 tools/qt-builder/qt-lite-osx-config.opt diff --git a/tools/qt-builder/qt-lite-osx-config.opt b/tools/qt-builder/qt-lite-osx-config.opt new file mode 100644 index 0000000000..28bb1b88e2 --- /dev/null +++ b/tools/qt-builder/qt-lite-osx-config.opt @@ -0,0 +1,184 @@ +-static +-optimize-size +-qt-libpng +-no-libjpeg +-qt-sqlite +-qt-zlib +-qt-freetype +-qt-pcre +-strip +-opensource +-release +-nomake +tests +-nomake +examples +-nomake +tests +-no-compile-examples +-no-pch +-confirm-license +-skip +qtmultimedia +-prefix +./qt-install +-openssl-linked +OPENSSL_LIBS=-lssl -lcrypto +-L/usr/local/Cellar/openssl/1.0.2s/lib +-I/usr/local/Cellar/openssl/1.0.2s/include +-no-feature-widgets +-no-feature-dbus +-no-feature-xml +-no-feature-sql +-no-feature-concurrent +-no-feature-quicktemplates2-hover +-no-feature-quicktemplates2-multitouch +-no-feature-quickcontrols2-material +-no-feature-quickcontrols2-universal +-no-feature-qml-network +-no-feature-qml-profiler +-no-feature-quick-listview +-no-feature-quick-particles +-no-feature-abstractbutton +-no-feature-abstractslider +-no-feature-buttongroup +-no-feature-calendarwidget +-no-feature-checkbox +-no-feature-combobox +-no-feature-commandlinkbutton +-no-feature-contextmenu +-no-feature-datetimeedit +-no-feature-dial +-no-feature-dockwidget +-no-feature-fontcombobox +-no-feature-formlayout +-no-feature-graphicseffect +-no-feature-graphicsview +-no-feature-groupbox +-no-feature-keysequenceedit +-no-feature-label +-no-feature-lcdnumber +-no-feature-lineedit +-no-feature-listwidget +-no-feature-mainwindow +-no-feature-mdiarea +-no-feature-menu +-no-feature-menubar +-no-feature-printpreviewwidget +-no-feature-progressbar +-no-feature-pushbutton +-no-feature-radiobutton +-no-feature-resizehandler +-no-feature-rubberband +-no-feature-scrollarea +-no-feature-scrollbar +-no-feature-scroller +-no-feature-sizegrip +-no-feature-slider +-no-feature-spinbox +-no-feature-splashscreen +-no-feature-splitter +-no-feature-stackedwidget +-no-feature-statusbar +-no-feature-statustip +-no-feature-syntaxhighlighter +-no-feature-tabbar +-no-feature-tablewidget +-no-feature-tabwidget +-no-feature-textbrowser +-no-feature-textedit +-no-feature-toolbar +-no-feature-toolbox +-no-feature-toolbutton +-no-feature-tooltip +-no-feature-treewidget +-no-feature-validator +-no-feature-widgettextcontrol +-no-feature-quick-designer +-no-feature-quick-flipable +-no-feature-quick-pathview +-no-feature-qml-profiler +-no-feature-gif +-no-feature-ico +-no-feature-harfbuzz +-no-feature-qml-debug +-no-feature-quick-listview +-no-feature-quick-sprite +-no-feature-quick-path +-no-feature-quick-canvas +-no-feature-quick-animatedimage +-no-feature-qml-interpreter +-no-feature-action +-no-feature-cssparser +-no-feature-sharedmemory +-no-feature-tabletevent +-no-feature-texthtmlparser +-no-feature-textodfwriter +-no-feature-sessionmanager +-no-feature-systemsemaphore +-no-feature-im +-no-feature-effects +-no-feature-appstore-compliant +-no-feature-big_codecs +-no-feature-codecs +-no-feature-colordialog +-no-feature-colornames +-no-feature-columnview +-no-feature-commandlineparser +-no-feature-cups +-no-feature-d3d12 +-no-feature-datawidgetmapper +-no-feature-datetimeparser +-no-feature-desktopservices +-no-feature-dialog +-no-feature-dialogbuttonbox +-no-feature-dirmodel +-no-feature-dom +-no-feature-errormessage +-no-feature-filedialog +-no-feature-filesystemiterator +-no-feature-filesystemwatcher +-no-feature-fontdialog +-no-feature-fscompleter +-no-feature-gestures +-no-feature-iconv +-no-feature-wizard +-no-feature-xmlstreamwriter +-no-feature-whatsthis +-no-feature-undoview +-no-feature-undostack +-no-feature-undogroup +-no-feature-undocommand +-no-feature-treeview +-no-feature-translation +-no-feature-topleveldomain +-no-feature-tableview +-no-feature-style-stylesheet +-no-feature-stringlistmodel +-no-feature-sortfilterproxymodel +-no-feature-wheelevent +-no-feature-statemachine +-no-feature-standarditemmodel +-no-feature-proxymodel +-no-feature-printer +-no-feature-printpreviewdialog +-no-feature-printdialog +-no-feature-picture +-no-feature-pdf +-no-feature-movie +-no-feature-messagebox +-no-feature-listview +-no-feature-itemmodel +-no-feature-inputdialog +-no-feature-filesystemmodel +-no-feature-identityproxymodel +-no-feature-mimetype +-no-feature-paint_debug +-no-feature-progressdialog +-no-feature-quick-positioners +-no-feature-sha3-fast +-no-feature-shortcut +-no-feature-completer +-no-feature-image_heuristic_mask +-no-feature-image_text +-no-feature-imageformat_bmp From 8411e6b03351347e51f4af8176511f42e3823ff7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 8 Oct 2019 10:42:19 -0700 Subject: [PATCH 096/203] Blendshape script API work * Moved hasScriptedBlendshapes, hasProceduralBlinkFaceMovement, hasProceduralEyeFaceMovement, hasAudioEnabledFaceMovement to AvatarData so they are accessable via agent scripts. * Marked setForceFaceTrackerConnected as depricated. * Updated jsdoc comments --- assignment-client/src/Agent.cpp | 1 - interface/src/avatar/MyAvatar.cpp | 25 ---------- interface/src/avatar/MyAvatar.h | 23 ++------- interface/src/avatar/MyHead.cpp | 4 +- libraries/avatars/src/AvatarData.cpp | 44 +++++++++++++---- libraries/avatars/src/AvatarData.h | 53 +++++++++++++++------ libraries/avatars/src/HeadData.cpp | 7 ++- libraries/avatars/src/HeadData.h | 6 +-- libraries/shared/src/FaceshiftConstants.cpp | 16 +++++-- 9 files changed, 98 insertions(+), 81 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ee1e21c837..2cad2ca722 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -376,7 +376,6 @@ void Agent::executeScript() { // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); scriptedAvatar->setID(getSessionUUID()); - scriptedAvatar->setForceFaceTrackerConnected(true); // call model URL setters with empty URLs so our avatar, if user, will have the default models scriptedAvatar->setSkeletonModelURL(QUrl()); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a9ebd110d1..3c27ef52c2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3405,31 +3405,6 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { return !defaultMode || (!firstPerson && !insideHead) || (overrideAnim && !insideHead); } -void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { - if (hasScriptedBlendshapes == _hasScriptedBlendShapes) { - return; - } - if (!hasScriptedBlendshapes) { - // send a forced avatarData update to make sure the script can send neutal blendshapes on unload - // without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true - // before sending the update, or else it won't send the neutal blendshapes to the receiving clients - sendAvatarDataPacket(true); - } - _hasScriptedBlendShapes = hasScriptedBlendshapes; -} - -void MyAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { - _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); -} - -void MyAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { - _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); -} - -void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { - _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); -} - void MyAvatar::setRotationRecenterFilterLength(float length) { const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f; _rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0f139ddbff..acbd0f27a5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -184,12 +184,6 @@ class MyAvatar : public Avatar { * property value is audioListenerModeCustom. * @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the * audioListenerMode property value is audioListenerModeCustom. - * @property {boolean} hasScriptedBlendshapes=false - true to transmit blendshapes over the network. - *

Note: Currently doesn't work. Use {@link MyAvatar.setForceFaceTrackerConnected} instead.

- * @property {boolean} hasProceduralBlinkFaceMovement=true - true if procedural blinking is turned on. - * @property {boolean} hasProceduralEyeFaceMovement=true - true if procedural eye movement is turned on. - * @property {boolean} hasAudioEnabledFaceMovement=true - true to move the mouth blendshapes with voice audio - * when MyAvatar.hasScriptedBlendshapes is enabled. * @property {number} rotationRecenterFilterLength - Configures how quickly the avatar root rotates to recenter its facing * direction to match that of the user's torso based on head and hands orientation. A smaller value makes the * recentering happen more quickly. The minimum value is 0.01. @@ -312,7 +306,10 @@ class MyAvatar : public Avatar { * @borrows Avatar.setAttachmentsVariant as setAttachmentsVariant * @borrows Avatar.updateAvatarEntity as updateAvatarEntity * @borrows Avatar.clearAvatarEntity as clearAvatarEntity - * @borrows Avatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected + * @borrows Avatar.hasScriptedBlendshapes as hasScriptedBlendshapes + * @borrows Avatar.hasProceduralBlinkFaceMovement as hasProceduralBlinkFaceMovement + * @borrows Avatar.hasEyeFaceMovement as hasEyeFaceMovement + * @borrows Avatar.hasAudioEnabledFaceMovement as hasAudioEnabledFaceMovement * @borrows Avatar.setSkeletonModelURL as setSkeletonModelURL * @borrows Avatar.getAttachmentData as getAttachmentData * @borrows Avatar.setAttachmentData as setAttachmentData @@ -359,10 +356,6 @@ class MyAvatar : public Avatar { Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom) Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition) Q_PROPERTY(glm::quat customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation) - Q_PROPERTY(bool hasScriptedBlendshapes READ getHasScriptedBlendshapes WRITE setHasScriptedBlendshapes) - Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement) - Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement) - Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength) Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold) Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation) @@ -2555,14 +2548,6 @@ private: virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); } bool getShouldRenderLocally() const { return _shouldRender; } - void setHasScriptedBlendshapes(bool hasScriptedBlendshapes); - bool getHasScriptedBlendshapes() const override { return _hasScriptedBlendShapes; } - void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); - bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } - void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); - bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } - void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); - bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } void setRotationRecenterFilterLength(float length); float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; } void setRotationThreshold(float angleRadians); diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index e5c8b71ea2..4705d2d765 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -64,7 +64,9 @@ void MyHead::simulate(float deltaTime) { bool eyeLidsTracked = userInputMapper->getActionStateValid(controller::Action::LEFT_EYE_BLINK) && userInputMapper->getActionStateValid(controller::Action::RIGHT_EYE_BLINK); - setFaceTrackerConnected(eyeLidsTracked); + + setHasScriptedBlendshapes(eyeLidsTracked); + if (eyeLidsTracked) { float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK); float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 710bfb8d2a..520241d020 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -110,7 +110,7 @@ AvatarData::AvatarData() : _targetScale(1.0f), _handState(0), _keyState(NO_KEY_DOWN), - _forceFaceTrackerConnected(false), + _hasScriptedBlendshapes(false), _headData(NULL), _errorLogExpiry(0), _owningAvatarMixer(), @@ -154,6 +154,32 @@ float AvatarData::getDomainLimitedScale() const { } } + +void AvatarData::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { + if (hasScriptedBlendshapes == _hasScriptedBlendshapes) { + return; + } + if (!hasScriptedBlendshapes) { + // send a forced avatarData update to make sure the script can send neutal blendshapes on unload + // without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true + // before sending the update, or else it won't send the neutal blendshapes to the receiving clients + sendAvatarDataPacket(true); + } + _hasScriptedBlendshapes = hasScriptedBlendshapes; +} + +void AvatarData::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +} + +void AvatarData::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +} + +void AvatarData::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +} + void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) { _domainMinimumHeight = glm::clamp(domainMinimumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); } @@ -206,8 +232,8 @@ void AvatarData::lazyInitHeadData() const { if (!_headData) { _headData = new HeadData(const_cast(this)); } - if (_forceFaceTrackerConnected) { - _headData->_isFaceTrackerConnected = true; + if (_hasScriptedBlendshapes) { + _headData->_hasScriptedBlendshapes = true; } } @@ -338,7 +364,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent tranlationChangedSince(lastSentTime) || parentInfoChangedSince(lastSentTime)); hasHandControllers = _controllerLeftHandMatrixCache.isValid() || _controllerRightHandMatrixCache.isValid(); - hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && + hasFaceTrackerInfo = !dropFaceTracking && getHasScriptedBlendshapes() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = !sendMinimum; hasJointDefaultPoseFlags = hasJointData; @@ -529,8 +555,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent setAtBit16(flags, HAND_STATE_FINGER_POINTING_BIT); } // face tracker state - if (_headData->_isFaceTrackerConnected) { - setAtBit16(flags, IS_FACE_TRACKER_CONNECTED); + if (_headData->_hasScriptedBlendshapes) { + setAtBit16(flags, HAS_SCRIPTED_BLENDSHAPES); } // eye tracker state if (!_headData->_hasProceduralEyeMovement) { @@ -1150,7 +1176,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT) + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); - auto newFaceTrackerConnected = oneAtBit16(bitItems, IS_FACE_TRACKER_CONNECTED); + auto newHasScriptedBlendshapes = oneAtBit16(bitItems, HAS_SCRIPTED_BLENDSHAPES); auto newHasntProceduralEyeMovement = oneAtBit16(bitItems, IS_EYE_TRACKER_CONNECTED); auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); @@ -1161,7 +1187,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); - bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); + bool faceStateChanged = (_headData->_hasScriptedBlendshapes != newHasScriptedBlendshapes); bool eyeStateChanged = (_headData->_hasProceduralEyeMovement == newHasntProceduralEyeMovement); bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); @@ -1174,7 +1200,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _keyState = newKeyState; _handState = newHandState; - _headData->_isFaceTrackerConnected = newFaceTrackerConnected; + _headData->_hasScriptedBlendshapes = newHasScriptedBlendshapes; _headData->setHasProceduralEyeMovement(!newHasntProceduralEyeMovement); _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index df0783ef4b..6bed5fa9db 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -104,12 +104,12 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = // Procedural Collide with other avatars is enabled 12th bit // Procedural Has Hero Priority is enabled 13th bit -const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits -const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits -const int IS_FACE_TRACKER_CONNECTED = 4; // 5th bit +const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits (UNUSED) +const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits (UNUSED) +const int HAS_SCRIPTED_BLENDSHAPES = 4; // 5th bit const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING) const int HAS_REFERENTIAL = 6; // 7th bit -const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit +const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit (UNUSED) const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit @@ -325,7 +325,7 @@ namespace AvatarDataPacket { // variable length structure follows - // only present if IS_FACE_TRACKER_CONNECTED flag is set in AvatarInfo.flags + // only present if HAS_SCRIPTED_BLENDSHAPES flag is set in AvatarInfo.flags PACKED_BEGIN struct FaceTrackerInfo { float leftEyeBlink; float rightEyeBlink; @@ -534,6 +534,19 @@ class AvatarData : public QObject, public SpatiallyNestable { * size in the virtual world. Read-only. * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. * Read-only. + * @property {boolean} hasScriptedBlendshapes=false - Set this to true before using the {@link MyAvatar.setBlendshape} method, + * after you no longer want scripted control over the blendshapes set to back to false.
NOTE: this property will + * automatically become true if the Controller system has valid facial blendshape actions. + * @property {boolean} hasProceduralBlinkFaceMovement=true - By default avatars will blink automatically by animating facial + * blendshapes. Set this property to false to disable this automatic blinking. This can be useful if you + * wish to fully control the blink facial blendshapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasProceduralEyeFaceMovement=true - By default the avatar eye facial blendshapes will be adjusted + * automatically as the eyes move. This will prevent the iris is never obscured by the upper or lower lids. Set this + * property to false to disable this automatic movement. This can be useful if you wish to fully control + * the eye blendshapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasAudioEnabledFaceMovement=true - By default the avatar mouth blendshapes will animate based on + * the microphone audio. Set this property to false to disable that animaiton. This can be useful if you + * wish to fully control the blink facial blendshapes via the {@link MyAvatar.setBlendshape} method. */ Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale) @@ -575,6 +588,11 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(bool hasPriority READ getHasPriority) + Q_PROPERTY(bool hasScriptedBlendshapes READ getHasScriptedBlendshapes WRITE setHasScriptedBlendshapes) + Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement) + Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement) + Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) + public: virtual QString getName() const override { return QString("Avatar:") + _displayName; } @@ -684,10 +702,14 @@ public: float getDomainLimitedScale() const; - virtual bool getHasScriptedBlendshapes() const { return false; } - virtual bool getHasProceduralBlinkFaceMovement() const { return true; } - virtual bool getHasProceduralEyeFaceMovement() const { return true; } - virtual bool getHasAudioEnabledFaceMovement() const { return false; } + void setHasScriptedBlendshapes(bool hasScriptedBlendshapes); + bool getHasScriptedBlendshapes() const { return _hasScriptedBlendshapes; } + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasProceduralBlinkFaceMovement() const { return _headData->getHasProceduralBlinkFaceMovement(); } + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralEyeFaceMovement() const { return _headData->getHasProceduralEyeFaceMovement(); } + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasAudioEnabledFaceMovement() const { return _headData->getHasAudioEnabledFaceMovement(); } /**jsdoc * Gets the minimum scale allowed for this avatar in the current domain. @@ -1111,13 +1133,14 @@ public: /**jsdoc * Sets the value of a blendshape to animate your avatar's face. To enable other users to see the resulting animation of - * your avatar's face, use {@link Avatar.setForceFaceTrackerConnected} or {@link MyAvatar.setForceFaceTrackerConnected}. + * your avatar's face, set {@link Avatar.hasScriptedBlendshapes} to true while using this API and back to false when your + * animation is complete. * @function Avatar.setBlendshape * @param {string} name - The name of the blendshape, per the * {@link https://docs.highfidelity.com/create/avatars/avatar-standards.html#blendshapes Avatar Standards}. * @param {number} value - A value between 0.0 and 1.0. * @example Open your avatar's mouth wide. - * MyAvatar.setForceFaceTrackerConnected(true); + * MyAvatar.hasScriptedBlendshapes = true; * MyAvatar.setBlendshape("JawOpen", 1.0); * * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar". @@ -1163,15 +1186,16 @@ public: */ Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); - /**jsdoc + *

Deprecated: This method is deprecated and will be removed.

+ * Use Avatar.hasScriptedBlendshapes property instead. * Enables blendshapes set using {@link Avatar.setBlendshape} or {@link MyAvatar.setBlendshape} to be transmitted to other * users so that they can see the animation of your avatar's face. * @function Avatar.setForceFaceTrackerConnected * @param {boolean} connected - true to enable blendshape changes to be transmitted to other users, * false to disable. */ - Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } + Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { setHasScriptedBlendshapes(connected); } // key state void setKeyState(KeyState s) { _keyState = s; } @@ -1660,7 +1684,6 @@ protected: bool faceTrackerInfoChangedSince(quint64 time) const { return true; } // FIXME bool hasParent() const { return !getParentID().isNull(); } - bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } QByteArray packSkeletonData() const; QByteArray packSkeletonModelURL() const; @@ -1693,7 +1716,7 @@ protected: // key state KeyState _keyState; - bool _forceFaceTrackerConnected; + bool _hasScriptedBlendshapes; bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar mutable HeadData* _headData { nullptr }; diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index c86e534929..0e0dd9887f 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -206,8 +206,7 @@ void HeadData::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement } bool HeadData::getHasProceduralBlinkFaceMovement() const { - // return _hasProceduralBlinkFaceMovement; - return _hasProceduralBlinkFaceMovement && !_isFaceTrackerConnected; + return _hasProceduralBlinkFaceMovement; } void HeadData::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { @@ -230,6 +229,6 @@ void HeadData::setHasProceduralEyeMovement(bool hasProceduralEyeMovement) { _hasProceduralEyeMovement = hasProceduralEyeMovement; } -void HeadData::setFaceTrackerConnected(bool value) { - _isFaceTrackerConnected = value; +void HeadData::setHasScriptedBlendshapes(bool value) { + _hasScriptedBlendshapes = value; } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index dc5aaf2595..e5dd158505 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -81,8 +81,8 @@ public: bool getHasProceduralEyeMovement() const; void setHasProceduralEyeMovement(bool hasProceduralEyeMovement); - void setFaceTrackerConnected(bool value); - bool getFaceTrackerConnected() const { return _isFaceTrackerConnected; } + void setHasScriptedBlendshapes(bool value); + bool getHasScriptedBlendshapes() const { return _hasScriptedBlendshapes; } friend class AvatarData; @@ -103,7 +103,7 @@ protected: bool _hasProceduralEyeFaceMovement { true }; bool _hasProceduralEyeMovement { true }; - bool _isFaceTrackerConnected { false }; + bool _hasScriptedBlendshapes { false }; float _leftEyeBlink { 0.0f }; float _rightEyeBlink { 0.0f }; diff --git a/libraries/shared/src/FaceshiftConstants.cpp b/libraries/shared/src/FaceshiftConstants.cpp index 0d6f718e49..b847837a7a 100644 --- a/libraries/shared/src/FaceshiftConstants.cpp +++ b/libraries/shared/src/FaceshiftConstants.cpp @@ -34,7 +34,7 @@ const char* FACESHIFT_BLENDSHAPES[] = { "JawFwd", "JawLeft", "JawOpen", - "JawChew", + "JawChew", // legacy not in ARKit "JawRight", "MouthLeft", "MouthRight", @@ -48,21 +48,29 @@ const char* FACESHIFT_BLENDSHAPES[] = { "LipsStretch_R", "LipsUpperClose", "LipsLowerClose", - "LipsUpperUp", - "LipsLowerDown", + "LipsUpperUp", // legacy, split in ARKit + "LipsLowerDown", // legacy, split in ARKit "LipsUpperOpen", "LipsLowerOpen", "LipsFunnel", "LipsPucker", "ChinLowerRaise", "ChinUpperRaise", - "Sneer", + "Sneer", // legacy, split in ARKit "Puff", "CheekSquint_L", "CheekSquint_R", "" }; +// new in ARKit +// LipsTogether +// MouthPressLeft +// MouthPressRight +// MouthShrugLower +// MouthShrugUpper +// TongueOut + const int NUM_FACESHIFT_BLENDSHAPES = sizeof(FACESHIFT_BLENDSHAPES) / sizeof(char*); const int EYE_BLINK_L_INDEX = 0; From da0911e01b8a88b630513788601cee15650c31be Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 8 Oct 2019 16:08:29 -0700 Subject: [PATCH 097/203] Delete legacy blendshape code Move FaceshiftConstants to BlendshapeConstants. Delete FaceTracker and DdeFaceTracker classes. Delete old facetracker menu and preferences --- interface/CMakeLists.txt | 4 +- interface/src/Application.cpp | 113 +-- interface/src/Application.h | 9 - interface/src/Menu.cpp | 42 -- interface/src/avatar/MyAvatar.cpp | 73 +- interface/src/avatar/MyAvatar.h | 4 +- interface/src/avatar/MyHead.cpp | 4 +- interface/src/devices/DdeFaceTracker.cpp | 686 ------------------ interface/src/devices/DdeFaceTracker.h | 181 ----- interface/src/ui/AvatarInputs.cpp | 10 - interface/src/ui/AvatarInputs.h | 33 - interface/src/ui/PreferencesDialog.cpp | 17 - libraries/avatars-renderer/CMakeLists.txt | 2 +- .../src/avatars-renderer/Head.cpp | 19 +- libraries/avatars/src/HeadData.cpp | 2 +- libraries/avatars/src/HeadData.h | 2 +- libraries/fbx/src/FBXSerializer.cpp | 2 +- libraries/fbx/src/GLTFSerializer.cpp | 2 +- ...tConstants.cpp => BlendshapeConstants.cpp} | 14 +- libraries/shared/src/BlendshapeConstants.h | 76 ++ libraries/shared/src/FaceshiftConstants.h | 25 - libraries/trackers/CMakeLists.txt | 7 - .../trackers/src/trackers/FaceTracker.cpp | 133 ---- libraries/trackers/src/trackers/FaceTracker.h | 131 ---- libraries/trackers/src/trackers/Logging.cpp | 11 - libraries/trackers/src/trackers/Logging.h | 16 - tools/jsdoc/plugins/hifi.js | 2 - 27 files changed, 110 insertions(+), 1510 deletions(-) delete mode 100644 interface/src/devices/DdeFaceTracker.cpp delete mode 100644 interface/src/devices/DdeFaceTracker.h rename libraries/shared/src/{FaceshiftConstants.cpp => BlendshapeConstants.cpp} (84%) create mode 100644 libraries/shared/src/BlendshapeConstants.h delete mode 100644 libraries/shared/src/FaceshiftConstants.h delete mode 100644 libraries/trackers/CMakeLists.txt delete mode 100644 libraries/trackers/src/trackers/FaceTracker.cpp delete mode 100644 libraries/trackers/src/trackers/FaceTracker.h delete mode 100644 libraries/trackers/src/trackers/Logging.cpp delete mode 100644 libraries/trackers/src/trackers/Logging.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index bcd3f269e8..9030666609 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -211,10 +211,10 @@ endif() link_hifi_libraries( shared workload task octree ktx gpu gl procedural graphics graphics-scripting render pointers recording hfm fbx networking material-networking - model-networking model-baker entities avatars trackers + model-networking model-baker entities avatars audio audio-client animation script-engine physics render-utils entities-renderer avatars-renderer ui qml auto-updater midi - controllers plugins image trackers platform + controllers plugins image platform ui-plugins display-plugins input-plugins # Platform specific GL libraries ${PLATFORM_GL_BACKEND} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 92bc54d43f..677137148c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,7 +170,6 @@ #include "avatar/MyCharacterController.h" #include "CrashRecoveryHandler.h" #include "CrashHandler.h" -#include "devices/DdeFaceTracker.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" #include "InterfaceDynamicFactory.h" @@ -887,11 +886,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - -#ifdef HAVE_DDE - DependencyManager::set(); -#endif - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1067,7 +1061,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _lastSendDownstreamAudioStats(usecTimestampNow()), _notifiedPacketVersionMismatchThisDomain(false), _maxOctreePPS(maxOctreePacketsPerSecond.get()), - _lastFaceTrackerUpdate(0), _snapshotSound(nullptr), _sampleSound(nullptr) { @@ -2014,13 +2007,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo this->installEventFilter(this); - -#ifdef HAVE_DDE - auto ddeTracker = DependencyManager::get(); - ddeTracker->init(); - connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); -#endif - // If launched from Steam, let it handle updates const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater"; bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1; @@ -2762,9 +2748,6 @@ void Application::cleanupBeforeQuit() { } // Stop third party processes so that they're not left running in the event of a subsequent shutdown crash. -#ifdef HAVE_DDE - DependencyManager::get()->setEnabled(false); -#endif AnimDebugDraw::getInstance().shutdown(); // FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete: @@ -2835,10 +2818,6 @@ void Application::cleanupBeforeQuit() { _window->saveGeometry(); // Destroy third party processes after scripts have finished using them. -#ifdef HAVE_DDE - DependencyManager::destroy(); -#endif - DependencyManager::destroy(); // Must be destroyed before TabletScriptingInterface // stop QML @@ -3457,9 +3436,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); -#ifdef HAVE_DDE - surfaceContext->setContextProperty("FaceTracker", DependencyManager::get().data()); -#endif surfaceContext->setContextProperty("AvatarManager", DependencyManager::get().data()); surfaceContext->setContextProperty("LODManager", DependencyManager::get().data()); surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); @@ -3723,16 +3699,6 @@ void Application::runTests() { runUnitTests(); } -void Application::faceTrackerMuteToggled() { - - QAction* muteAction = Menu::getInstance()->getActionForOption(MenuOption::MuteFaceTracking); - Q_CHECK_PTR(muteAction); - bool isMuted = getSelectedFaceTracker()->isMuted(); - muteAction->setChecked(isMuted); - getSelectedFaceTracker()->setEnabled(!isMuted); - Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setEnabled(!isMuted); -} - void Application::setFieldOfView(float fov) { if (fov != _fieldOfView.get()) { _fieldOfView.set(fov); @@ -5307,43 +5273,6 @@ ivec2 Application::getMouse() const { return getApplicationCompositor().getReticlePosition(); } -FaceTracker* Application::getActiveFaceTracker() { -#ifdef HAVE_DDE - auto dde = DependencyManager::get(); - - if (dde && dde->isActive()) { - return static_cast(dde.data()); - } -#endif - - return nullptr; -} - -FaceTracker* Application::getSelectedFaceTracker() { - FaceTracker* faceTracker = nullptr; -#ifdef HAVE_DDE - if (Menu::getInstance()->isOptionChecked(MenuOption::UseCamera)) { - faceTracker = DependencyManager::get().data(); - } -#endif - return faceTracker; -} - -void Application::setActiveFaceTracker() const { -#ifdef HAVE_DDE - bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); - bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera); - Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE); - Menu::getInstance()->getActionForOption(MenuOption::CoupleEyelids)->setVisible(isUsingDDE); - Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE); - Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE); - Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setVisible(isUsingDDE); - auto ddeTracker = DependencyManager::get(); - ddeTracker->setIsMuted(isMuted); - ddeTracker->setEnabled(isUsingDDE && !isMuted); -#endif -} - bool Application::exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset) { @@ -5827,8 +5756,7 @@ void Application::updateMyAvatarLookAtPosition() { PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); auto myAvatar = getMyAvatar(); - FaceTracker* faceTracker = getActiveFaceTracker(); - myAvatar->updateLookAtPosition(faceTracker, _myCamera); + myAvatar->updateLookAtPosition(_myCamera); } void Application::updateThreads(float deltaTime) { @@ -6254,37 +6182,6 @@ void Application::update(float deltaTime) { auto myAvatar = getMyAvatar(); { PerformanceTimer perfTimer("devices"); - - FaceTracker* tracker = getSelectedFaceTracker(); - if (tracker && Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking) != tracker->isMuted()) { - tracker->toggleMute(); - } - - tracker = getActiveFaceTracker(); - if (tracker && !tracker->isMuted()) { - tracker->update(deltaTime); - - // Auto-mute microphone after losing face tracking? - if (tracker->isTracking()) { - _lastFaceTrackerUpdate = usecTimestampNow(); - } else { - const quint64 MUTE_MICROPHONE_AFTER_USECS = 5000000; //5 secs - Menu* menu = Menu::getInstance(); - auto audioClient = DependencyManager::get(); - if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) { - if (_lastFaceTrackerUpdate > 0 - && ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) { - audioClient->setMuted(true); - _lastFaceTrackerUpdate = 0; - } - } else { - _lastFaceTrackerUpdate = 0; - } - } - } else { - _lastFaceTrackerUpdate = 0; - } - auto userInputMapper = DependencyManager::get(); controller::HmdAvatarAlignmentType hmdAvatarAlignmentType; @@ -7080,10 +6977,6 @@ void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const { // feature. However, we still use this to reset face trackers, eye trackers, audio and to optionally re-load the avatar // rig and animations from scratch. void Application::resetSensors(bool andReload) { -#ifdef HAVE_DDE - DependencyManager::get()->reset(); -#endif - _overlayConductor.centerUI(); getActiveDisplayPlugin()->resetSensors(); getMyAvatar()->reset(true, andReload); @@ -7485,10 +7378,6 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("AccountServices", AccountServicesScriptingInterface::getInstance()); qScriptRegisterMetaType(scriptEngine.data(), DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); -#ifdef HAVE_DDE - scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get().data()); -#endif - scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get().data()); diff --git a/interface/src/Application.h b/interface/src/Application.h index e3334d12d6..80b10697b8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -81,7 +81,6 @@ #include "VisionSqueeze.h" class GLCanvas; -class FaceTracker; class MainWindow; class AssetUpload; class CompositorHelper; @@ -191,9 +190,6 @@ public: ivec2 getMouse() const; - FaceTracker* getActiveFaceTracker(); - FaceTracker* getSelectedFaceTracker(); - ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; } const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; } CompositorHelper& getApplicationCompositor() const; @@ -423,7 +419,6 @@ public slots: static void packageModel(); void resetSensors(bool andReload = false); - void setActiveFaceTracker() const; void hmdVisibleChanged(bool visible); @@ -497,8 +492,6 @@ private slots: void resettingDomain(); - void faceTrackerMuteToggled(); - void activeChanged(Qt::ApplicationState state); void windowMinimizedChanged(bool minimized); @@ -736,8 +729,6 @@ private: PerformanceManager _performanceManager; RefreshRateManager _refreshRateManager; - quint64 _lastFaceTrackerUpdate; - GameWorkload _gameWorkload; GraphicsEngine _graphicsEngine; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5be4db46a0..c0df4ff159 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -37,7 +37,6 @@ #include "avatar/AvatarManager.h" #include "avatar/AvatarPackager.h" #include "AvatarBookmarks.h" -#include "devices/DdeFaceTracker.h" #include "MainWindow.h" #include "render/DrawStatus.h" #include "scripting/MenuScriptingInterface.h" @@ -493,47 +492,6 @@ Menu::Menu() { // Developer > Avatar >>> MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); - // Developer > Avatar > Face Tracking - MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); - { - QActionGroup* faceTrackerGroup = new QActionGroup(avatarDebugMenu); - - bool defaultNoFaceTracking = true; -#ifdef HAVE_DDE - defaultNoFaceTracking = false; -#endif - QAction* noFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::NoFaceTracking, - 0, defaultNoFaceTracking, - qApp, SLOT(setActiveFaceTracker())); - faceTrackerGroup->addAction(noFaceTracker); - -#ifdef HAVE_DDE - QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, - 0, true, - qApp, SLOT(setActiveFaceTracker())); - faceTrackerGroup->addAction(ddeFaceTracker); -#endif - } -#ifdef HAVE_DDE - faceTrackingMenu->addSeparator(); - QAction* binaryEyelidControl = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::BinaryEyelidControl, 0, true); - binaryEyelidControl->setVisible(true); // DDE face tracking is on by default - QAction* coupleEyelids = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CoupleEyelids, 0, true); - coupleEyelids->setVisible(true); // DDE face tracking is on by default - QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true); - useAudioForMouth->setVisible(true); // DDE face tracking is on by default - QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true); - ddeFiltering->setVisible(true); // DDE face tracking is on by default - QAction* ddeCalibrate = addActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CalibrateCamera, 0, - DependencyManager::get().data(), SLOT(calibrate())); - ddeCalibrate->setVisible(true); // DDE face tracking is on by default - faceTrackingMenu->addSeparator(); - addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, - [](bool mute) { FaceTracker::setIsMuted(mute); }, - Qt::CTRL | Qt::SHIFT | Qt::Key_F, FaceTracker::isMuted()); - addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false); -#endif - action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); }); action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3c27ef52c2..fb42f89048 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -48,7 +48,6 @@ #include #include #include -#include #include #include @@ -749,7 +748,6 @@ void MyAvatar::update(float deltaTime) { Head* head = getHead(); head->relax(deltaTime); - updateFromTrackers(deltaTime); if (getIsInWalkingState() && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { setIsInWalkingState(false); @@ -1140,60 +1138,6 @@ void MyAvatar::updateSensorToWorldMatrix() { } -// Update avatar head rotation with sensor data -void MyAvatar::updateFromTrackers(float deltaTime) { - glm::vec3 estimatedRotation; - - bool hasHead = getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid(); - bool playing = DependencyManager::get()->isPlaying(); - if (hasHead && playing) { - return; - } - - FaceTracker* tracker = qApp->getActiveFaceTracker(); - bool inFacetracker = tracker && !FaceTracker::isMuted(); - - if (inFacetracker) { - estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); - } - - // Rotate the body if the head is turned beyond the screen - if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) { - const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f; - const float TRACKER_MIN_YAW_TURN = 15.0f; - const float TRACKER_MAX_YAW_TURN = 50.0f; - if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) && - (fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) { - if (estimatedRotation.y > 0.0f) { - _bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY; - } else { - _bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY; - } - } - } - - // Set the rotation of the avatar's head (as seen by others, not affecting view frustum) - // to be scaled such that when the user's physical head is pointing at edge of screen, the - // avatar head is at the edge of the in-world view frustum. So while a real person may move - // their head only 30 degrees or so, this may correspond to a 90 degree field of view. - // Note that roll is magnified by a constant because it is not related to field of view. - - - Head* head = getHead(); - if (hasHead || playing) { - head->setDeltaPitch(estimatedRotation.x); - head->setDeltaYaw(estimatedRotation.y); - head->setDeltaRoll(estimatedRotation.z); - } else { - ViewFrustum viewFrustum; - qApp->copyViewFrustum(viewFrustum); - float magnifyFieldOfView = viewFrustum.getFieldOfView() / _realWorldFieldOfView.get(); - head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView); - head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); - head->setDeltaRoll(estimatedRotation.z); - } -} - glm::vec3 MyAvatar::getLeftHandPosition() const { auto pose = getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); return pose.isValid() ? pose.getTranslation() : glm::vec3(0.0f); @@ -6585,7 +6529,7 @@ bool MyAvatar::getIsJointOverridden(int jointIndex) const { return _skeletonModel->getIsJointOverridden(jointIndex); } -void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) { +void MyAvatar::updateLookAtPosition(Camera& myCamera) { updateLookAtTargetAvatar(); @@ -6681,21 +6625,6 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) (getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } } - - // Deflect the eyes a bit to match the detected gaze from the face tracker if active. - if (faceTracker && !faceTracker->isMuted()) { - float eyePitch = faceTracker->getEstimatedEyePitch(); - float eyeYaw = faceTracker->getEstimatedEyeYaw(); - const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; - glm::vec3 origin = myHead->getEyePosition(); - float deflection = faceTracker->getEyeDeflection(); - if (isLookingAtSomeone) { - deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; - } - lookAtSpot = origin + myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( - eyePitch * deflection, eyeYaw * deflection, 0.0f))) * - glm::inverse(myCamera.getOrientation()) * (lookAtSpot - origin); - } } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index acbd0f27a5..d67a2567f8 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -34,7 +34,6 @@ #include "AtRestDetector.h" #include "MyCharacterController.h" #include "RingBufferHistory.h" -#include "devices/DdeFaceTracker.h" class AvatarActionHold; class ModelItemID; @@ -1899,7 +1898,7 @@ public: bool getFlowActive() const; bool getNetworkGraphActive() const; - void updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera); + void updateLookAtPosition(Camera& myCamera); // sets the reaction enabled and triggered parameters of the passed in params // also clears internal reaction triggers @@ -2542,7 +2541,6 @@ private: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override; void simulate(float deltaTime, bool inView) override; - void updateFromTrackers(float deltaTime); void saveAvatarUrl(); virtual void render(RenderArgs* renderArgs) override; virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 4705d2d765..1c6e4690dd 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -14,10 +14,8 @@ #include #include #include -#include -#include +#include -#include "devices/DdeFaceTracker.h" #include "Application.h" #include "MyAvatar.h" diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp deleted file mode 100644 index b9dc8326e8..0000000000 --- a/interface/src/devices/DdeFaceTracker.cpp +++ /dev/null @@ -1,686 +0,0 @@ -// -// DdeFaceTracker.cpp -// -// -// Created by Clement on 8/2/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "DdeFaceTracker.h" - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "Application.h" -#include "InterfaceLogging.h" -#include "Menu.h" - - -static const QHostAddress DDE_SERVER_ADDR("127.0.0.1"); -static const quint16 DDE_SERVER_PORT = 64204; -static const quint16 DDE_CONTROL_PORT = 64205; -#if defined(Q_OS_WIN) -static const QString DDE_PROGRAM_PATH = "/dde/dde.exe"; -#elif defined(Q_OS_MAC) -static const QString DDE_PROGRAM_PATH = "/dde.app/Contents/MacOS/dde"; -#endif -static const QStringList DDE_ARGUMENTS = QStringList() - << "--udp=" + DDE_SERVER_ADDR.toString() + ":" + QString::number(DDE_SERVER_PORT) - << "--receiver=" + QString::number(DDE_CONTROL_PORT) - << "--facedet_interval=500" // ms - << "--headless"; - -static const int NUM_EXPRESSIONS = 46; -static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSIONS) * sizeof(float) + sizeof(int); -static const int MAX_NAME_SIZE = 31; - -// There's almost but not quite a 1-1 correspondence between DDE's 46 and Faceshift 1.3's 48 packets. -// The best guess at mapping is to: -// - Swap L and R values -// - Skip two Faceshift values: JawChew (22) and LipsLowerDown (37) -static const int DDE_TO_FACESHIFT_MAPPING[] = { - 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, - 16, - 18, 17, - 19, - 23, - 21, - // Skip JawChew - 20, - 25, 24, 27, 26, 29, 28, 31, 30, 33, 32, - 34, 35, 36, - // Skip LipsLowerDown - 38, 39, 40, 41, 42, 43, 44, 45, - 47, 46 -}; - -// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much -// less than this. -static const float DDE_COEFFICIENT_SCALES[] = { - 1.0f, // EyeBlink_L - 1.0f, // EyeBlink_R - 1.0f, // EyeSquint_L - 1.0f, // EyeSquint_R - 1.0f, // EyeDown_L - 1.0f, // EyeDown_R - 1.0f, // EyeIn_L - 1.0f, // EyeIn_R - 1.0f, // EyeOpen_L - 1.0f, // EyeOpen_R - 1.0f, // EyeOut_L - 1.0f, // EyeOut_R - 1.0f, // EyeUp_L - 1.0f, // EyeUp_R - 3.0f, // BrowsD_L - 3.0f, // BrowsD_R - 3.0f, // BrowsU_C - 3.0f, // BrowsU_L - 3.0f, // BrowsU_R - 1.0f, // JawFwd - 2.0f, // JawLeft - 1.8f, // JawOpen - 1.0f, // JawChew - 2.0f, // JawRight - 1.5f, // MouthLeft - 1.5f, // MouthRight - 1.5f, // MouthFrown_L - 1.5f, // MouthFrown_R - 2.5f, // MouthSmile_L - 2.5f, // MouthSmile_R - 1.0f, // MouthDimple_L - 1.0f, // MouthDimple_R - 1.0f, // LipsStretch_L - 1.0f, // LipsStretch_R - 1.0f, // LipsUpperClose - 1.0f, // LipsLowerClose - 1.0f, // LipsUpperUp - 1.0f, // LipsLowerDown - 1.0f, // LipsUpperOpen - 1.0f, // LipsLowerOpen - 1.5f, // LipsFunnel - 2.5f, // LipsPucker - 1.5f, // ChinLowerRaise - 1.5f, // ChinUpperRaise - 1.0f, // Sneer - 3.0f, // Puff - 1.0f, // CheekSquint_L - 1.0f // CheekSquint_R -}; - -struct DDEPacket { - //roughly in mm - float focal_length[1]; - float translation[3]; - - //quaternion - float rotation[4]; - - // The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much - // less than this. - float expressions[NUM_EXPRESSIONS]; - - //avatar id selected on the UI - int avatar_id; - - //client name, arbitrary length - char name[MAX_NAME_SIZE + 1]; -}; - -static const float STARTING_DDE_MESSAGE_TIME = 0.033f; -static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f; -static const int CALIBRATION_SAMPLES = 150; - -DdeFaceTracker::DdeFaceTracker() : - DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT) -{ - -} - -DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort) : - _ddeProcess(NULL), - _ddeStopping(false), - _host(host), - _serverPort(serverPort), - _controlPort(controlPort), - _lastReceiveTimestamp(0), - _reset(false), - _leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes - _rightBlinkIndex(1), - _leftEyeDownIndex(4), - _rightEyeDownIndex(5), - _leftEyeInIndex(6), - _rightEyeInIndex(7), - _leftEyeOpenIndex(8), - _rightEyeOpenIndex(9), - _browDownLeftIndex(14), - _browDownRightIndex(15), - _browUpCenterIndex(16), - _browUpLeftIndex(17), - _browUpRightIndex(18), - _mouthSmileLeftIndex(28), - _mouthSmileRightIndex(29), - _jawOpenIndex(21), - _lastMessageReceived(0), - _averageMessageTime(STARTING_DDE_MESSAGE_TIME), - _lastHeadTranslation(glm::vec3(0.0f)), - _filteredHeadTranslation(glm::vec3(0.0f)), - _lastBrowUp(0.0f), - _filteredBrowUp(0.0f), - _eyePitch(0.0f), - _eyeYaw(0.0f), - _lastEyePitch(0.0f), - _lastEyeYaw(0.0f), - _filteredEyePitch(0.0f), - _filteredEyeYaw(0.0f), - _longTermAverageEyePitch(0.0f), - _longTermAverageEyeYaw(0.0f), - _lastEyeBlinks(), - _filteredEyeBlinks(), - _lastEyeCoefficients(), - _eyeClosingThreshold("ddeEyeClosingThreshold", DEFAULT_DDE_EYE_CLOSING_THRESHOLD), - _isCalibrating(false), - _calibrationCount(0), - _calibrationValues(), - _calibrationBillboard(NULL), - _calibrationMessage(QString()), - _isCalibrated(false) -{ - _coefficients.resize(NUM_FACESHIFT_BLENDSHAPES); - _blendshapeCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES); - _coefficientAverages.resize(NUM_FACESHIFT_BLENDSHAPES); - _calibrationValues.resize(NUM_FACESHIFT_BLENDSHAPES); - - _eyeStates[0] = EYE_UNCONTROLLED; - _eyeStates[1] = EYE_UNCONTROLLED; - - connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); - connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), - SLOT(socketStateChanged(QAbstractSocket::SocketState))); -} - -DdeFaceTracker::~DdeFaceTracker() { - setEnabled(false); - - if (_isCalibrating) { - cancelCalibration(); - } -} - -void DdeFaceTracker::init() { - FaceTracker::init(); - setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted); - Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setEnabled(!_isMuted); -} - -void DdeFaceTracker::setEnabled(bool enabled) { - if (!_isInitialized) { - // Don't enable until have explicitly initialized - return; - } -#ifdef HAVE_DDE - - if (_isCalibrating) { - cancelCalibration(); - } - - // isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket. - _udpSocket.close(); - - // Terminate any existing DDE process, perhaps left running after an Interface crash. - // Do this even if !enabled in case user reset their settings after crash. - const char* DDE_EXIT_COMMAND = "exit"; - _udpSocket.bind(_host, _serverPort); - _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); - - if (enabled && !_ddeProcess) { - _ddeStopping = false; - qCDebug(interfaceapp) << "DDE Face Tracker: Starting"; - _ddeProcess = new QProcess(qApp); - connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); - _ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS); - } - - if (!enabled && _ddeProcess) { - _ddeStopping = true; - qCDebug(interfaceapp) << "DDE Face Tracker: Stopping"; - } -#endif -} - -void DdeFaceTracker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { - if (_ddeProcess) { - if (_ddeStopping) { - qCDebug(interfaceapp) << "DDE Face Tracker: Stopped"; - - } else { - qCWarning(interfaceapp) << "DDE Face Tracker: Stopped unexpectedly"; - Menu::getInstance()->setIsOptionChecked(MenuOption::NoFaceTracking, true); - } - _udpSocket.close(); - delete _ddeProcess; - _ddeProcess = NULL; - } -} - -void DdeFaceTracker::reset() { - if (_udpSocket.state() == QAbstractSocket::BoundState) { - _reset = true; - - qCDebug(interfaceapp) << "DDE Face Tracker: Reset"; - - const char* DDE_RESET_COMMAND = "reset"; - _udpSocket.writeDatagram(DDE_RESET_COMMAND, DDE_SERVER_ADDR, _controlPort); - - FaceTracker::reset(); - - _reset = true; - } -} - -void DdeFaceTracker::update(float deltaTime) { - if (!isActive()) { - return; - } - FaceTracker::update(deltaTime); - - glm::vec3 headEulers = glm::degrees(glm::eulerAngles(_headRotation)); - _estimatedEyePitch = _eyePitch - headEulers.x; - _estimatedEyeYaw = _eyeYaw - headEulers.y; -} - -bool DdeFaceTracker::isActive() const { - return (_ddeProcess != NULL); -} - -bool DdeFaceTracker::isTracking() const { - static const quint64 ACTIVE_TIMEOUT_USECS = 3000000; //3 secs - return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS); -} - -//private slots and methods -void DdeFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) { - qCWarning(interfaceapp) << "DDE Face Tracker: Socket error: " << _udpSocket.errorString(); -} - -void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) { - QString state; - switch(socketState) { - case QAbstractSocket::BoundState: - state = "Bound"; - break; - case QAbstractSocket::ClosingState: - state = "Closing"; - break; - case QAbstractSocket::ConnectedState: - state = "Connected"; - break; - case QAbstractSocket::ConnectingState: - state = "Connecting"; - break; - case QAbstractSocket::HostLookupState: - state = "Host Lookup"; - break; - case QAbstractSocket::ListeningState: - state = "Listening"; - break; - case QAbstractSocket::UnconnectedState: - state = "Unconnected"; - break; - } - qCDebug(interfaceapp) << "DDE Face Tracker: Socket: " << state; -} - -void DdeFaceTracker::readPendingDatagrams() { - QByteArray buffer; - while (_udpSocket.hasPendingDatagrams()) { - buffer.resize(_udpSocket.pendingDatagramSize()); - _udpSocket.readDatagram(buffer.data(), buffer.size()); - } - decodePacket(buffer); -} - -float DdeFaceTracker::getBlendshapeCoefficient(int index) const { - return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f; -} - -void DdeFaceTracker::decodePacket(const QByteArray& buffer) { - _lastReceiveTimestamp = usecTimestampNow(); - - if (buffer.size() > MIN_PACKET_SIZE) { - if (!_isCalibrated) { - calibrate(); - } - - bool isFiltering = Menu::getInstance()->isOptionChecked(MenuOption::VelocityFilter); - - DDEPacket packet; - int bytesToCopy = glm::min((int)sizeof(packet), buffer.size()); - memset(&packet.name, '\n', MAX_NAME_SIZE + 1); - memcpy(&packet, buffer.data(), bytesToCopy); - - glm::vec3 translation; - memcpy(&translation, packet.translation, sizeof(packet.translation)); - glm::quat rotation; - memcpy(&rotation, &packet.rotation, sizeof(packet.rotation)); - if (_reset || (_lastMessageReceived == 0)) { - memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3)); - memcpy(&_referenceRotation, &rotation, sizeof(glm::quat)); - _reset = false; - } - - // Compute relative translation - float LEAN_DAMPING_FACTOR = 75.0f; - translation -= _referenceTranslation; - translation /= LEAN_DAMPING_FACTOR; - translation.x *= -1; - if (isFiltering) { - glm::vec3 linearVelocity = (translation - _lastHeadTranslation) / _averageMessageTime; - const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f; - float velocityFilter = glm::clamp(1.0f - glm::length(linearVelocity) * - LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); - _filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * translation; - _lastHeadTranslation = translation; - _headTranslation = _filteredHeadTranslation; - } else { - _headTranslation = translation; - } - - // Compute relative rotation - rotation = glm::inverse(_referenceRotation) * rotation; - if (isFiltering) { - glm::quat r = glm::normalize(rotation * glm::inverse(_headRotation)); - float theta = 2 * acos(r.w); - glm::vec3 angularVelocity; - if (theta > EPSILON) { - float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); - angularVelocity = theta / _averageMessageTime * glm::vec3(r.x, r.y, r.z) / rMag; - } else { - angularVelocity = glm::vec3(0, 0, 0); - } - const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f; - _headRotation = safeMix(_headRotation, rotation, glm::clamp(glm::length(angularVelocity) * - ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f)); - } else { - _headRotation = rotation; - } - - // Translate DDE coefficients to Faceshift compatible coefficients - for (int i = 0; i < NUM_EXPRESSIONS; i++) { - _coefficients[DDE_TO_FACESHIFT_MAPPING[i]] = packet.expressions[i]; - } - - // Calibration - if (_isCalibrating) { - addCalibrationDatum(); - } - for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { - _coefficients[i] -= _coefficientAverages[i]; - } - - // Use BrowsU_C to control both brows' up and down - float browUp = _coefficients[_browUpCenterIndex]; - if (isFiltering) { - const float BROW_VELOCITY_FILTER_STRENGTH = 0.5f; - float velocity = fabsf(browUp - _lastBrowUp) / _averageMessageTime; - float velocityFilter = glm::clamp(velocity * BROW_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); - _filteredBrowUp = velocityFilter * browUp + (1.0f - velocityFilter) * _filteredBrowUp; - _lastBrowUp = browUp; - browUp = _filteredBrowUp; - _coefficients[_browUpCenterIndex] = browUp; - } - _coefficients[_browUpLeftIndex] = browUp; - _coefficients[_browUpRightIndex] = browUp; - _coefficients[_browDownLeftIndex] = -browUp; - _coefficients[_browDownRightIndex] = -browUp; - - // Offset jaw open coefficient - static const float JAW_OPEN_THRESHOLD = 0.1f; - _coefficients[_jawOpenIndex] = _coefficients[_jawOpenIndex] - JAW_OPEN_THRESHOLD; - - // Offset smile coefficients - static const float SMILE_THRESHOLD = 0.5f; - _coefficients[_mouthSmileLeftIndex] = _coefficients[_mouthSmileLeftIndex] - SMILE_THRESHOLD; - _coefficients[_mouthSmileRightIndex] = _coefficients[_mouthSmileRightIndex] - SMILE_THRESHOLD; - - // Eye pitch and yaw - // EyeDown coefficients work better over both +ve and -ve values than EyeUp values. - // EyeIn coefficients work better over both +ve and -ve values than EyeOut values. - // Pitch and yaw values are relative to the screen. - const float EYE_PITCH_SCALE = -1500.0f; // Sign, scale, and average to be similar to Faceshift values. - _eyePitch = EYE_PITCH_SCALE * (_coefficients[_leftEyeDownIndex] + _coefficients[_rightEyeDownIndex]); - const float EYE_YAW_SCALE = 2000.0f; // Scale and average to be similar to Faceshift values. - _eyeYaw = EYE_YAW_SCALE * (_coefficients[_leftEyeInIndex] + _coefficients[_rightEyeInIndex]); - if (isFiltering) { - const float EYE_VELOCITY_FILTER_STRENGTH = 0.005f; - float pitchVelocity = fabsf(_eyePitch - _lastEyePitch) / _averageMessageTime; - float pitchVelocityFilter = glm::clamp(pitchVelocity * EYE_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); - _filteredEyePitch = pitchVelocityFilter * _eyePitch + (1.0f - pitchVelocityFilter) * _filteredEyePitch; - _lastEyePitch = _eyePitch; - _eyePitch = _filteredEyePitch; - float yawVelocity = fabsf(_eyeYaw - _lastEyeYaw) / _averageMessageTime; - float yawVelocityFilter = glm::clamp(yawVelocity * EYE_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); - _filteredEyeYaw = yawVelocityFilter * _eyeYaw + (1.0f - yawVelocityFilter) * _filteredEyeYaw; - _lastEyeYaw = _eyeYaw; - _eyeYaw = _filteredEyeYaw; - } - - // Velocity filter EyeBlink values - const float DDE_EYEBLINK_SCALE = 3.0f; - float eyeBlinks[] = { DDE_EYEBLINK_SCALE * _coefficients[_leftBlinkIndex], - DDE_EYEBLINK_SCALE * _coefficients[_rightBlinkIndex] }; - if (isFiltering) { - const float BLINK_VELOCITY_FILTER_STRENGTH = 0.3f; - for (int i = 0; i < 2; i++) { - float velocity = fabsf(eyeBlinks[i] - _lastEyeBlinks[i]) / _averageMessageTime; - float velocityFilter = glm::clamp(velocity * BLINK_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); - _filteredEyeBlinks[i] = velocityFilter * eyeBlinks[i] + (1.0f - velocityFilter) * _filteredEyeBlinks[i]; - _lastEyeBlinks[i] = eyeBlinks[i]; - } - } - - // Finesse EyeBlink values - float eyeCoefficients[2]; - if (Menu::getInstance()->isOptionChecked(MenuOption::BinaryEyelidControl)) { - if (_eyeStates[0] == EYE_UNCONTROLLED) { - _eyeStates[0] = EYE_OPEN; - _eyeStates[1] = EYE_OPEN; - } - - for (int i = 0; i < 2; i++) { - // Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen - // -ve values control EyeOpen; +ve values control EyeBlink - static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value - eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD); - - // Change to closing or opening states - const float EYE_CONTROL_HYSTERISIS = 0.25f; - float eyeClosingThreshold = getEyeClosingThreshold(); - float eyeOpeningThreshold = eyeClosingThreshold - EYE_CONTROL_HYSTERISIS; - if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > eyeClosingThreshold) { - _eyeStates[i] = EYE_CLOSING; - } else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING) - && eyeCoefficients[i] < eyeOpeningThreshold) { - _eyeStates[i] = EYE_OPENING; - } - - const float EYELID_MOVEMENT_RATE = 10.0f; // units/second - const float EYE_OPEN_SCALE = 0.2f; - if (_eyeStates[i] == EYE_CLOSING) { - // Close eyelid until it's fully closed - float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime; - if (closingValue >= 1.0f) { - _eyeStates[i] = EYE_CLOSED; - eyeCoefficients[i] = 1.0f; - } else { - eyeCoefficients[i] = closingValue; - } - } else if (_eyeStates[i] == EYE_OPENING) { - // Open eyelid until it meets the current adjusted value - float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime; - if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) { - _eyeStates[i] = EYE_OPEN; - eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE; - } else { - eyeCoefficients[i] = openingValue; - } - } else if (_eyeStates[i] == EYE_OPEN) { - // Reduce eyelid movement - eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE; - } else if (_eyeStates[i] == EYE_CLOSED) { - // Keep eyelid fully closed - eyeCoefficients[i] = 1.0; - } - } - - if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) { - // Couple eyelids - eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f; - } - - _lastEyeCoefficients[0] = eyeCoefficients[0]; - _lastEyeCoefficients[1] = eyeCoefficients[1]; - } else { - _eyeStates[0] = EYE_UNCONTROLLED; - _eyeStates[1] = EYE_UNCONTROLLED; - - eyeCoefficients[0] = _filteredEyeBlinks[0]; - eyeCoefficients[1] = _filteredEyeBlinks[1]; - } - - // Couple eyelid values if configured - use the most "open" value for both - if (Menu::getInstance()->isOptionChecked(MenuOption::CoupleEyelids)) { - float eyeCoefficient = std::min(eyeCoefficients[0], eyeCoefficients[1]); - eyeCoefficients[0] = eyeCoefficient; - eyeCoefficients[1] = eyeCoefficient; - } - - // Use EyeBlink values to control both EyeBlink and EyeOpen - if (eyeCoefficients[0] > 0) { - _coefficients[_leftBlinkIndex] = eyeCoefficients[0]; - _coefficients[_leftEyeOpenIndex] = 0.0f; - } else { - _coefficients[_leftBlinkIndex] = 0.0f; - _coefficients[_leftEyeOpenIndex] = -eyeCoefficients[0]; - } - if (eyeCoefficients[1] > 0) { - _coefficients[_rightBlinkIndex] = eyeCoefficients[1]; - _coefficients[_rightEyeOpenIndex] = 0.0f; - } else { - _coefficients[_rightBlinkIndex] = 0.0f; - _coefficients[_rightEyeOpenIndex] = -eyeCoefficients[1]; - } - - // Scale all coefficients - for (int i = 0; i < NUM_EXPRESSIONS; i++) { - _blendshapeCoefficients[i] - = glm::clamp(DDE_COEFFICIENT_SCALES[i] * _coefficients[i], 0.0f, 1.0f); - } - - // Calculate average frame time - const float FRAME_AVERAGING_FACTOR = 0.99f; - quint64 usecsNow = usecTimestampNow(); - if (_lastMessageReceived != 0) { - _averageMessageTime = FRAME_AVERAGING_FACTOR * _averageMessageTime - + (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f; - } - _lastMessageReceived = usecsNow; - - FaceTracker::countFrame(); - - } else { - qCWarning(interfaceapp) << "DDE Face Tracker: Decode error"; - } - - if (_isCalibrating && _calibrationCount > CALIBRATION_SAMPLES) { - finishCalibration(); - } -} - -void DdeFaceTracker::setEyeClosingThreshold(float eyeClosingThreshold) { - _eyeClosingThreshold.set(eyeClosingThreshold); -} - -static const int CALIBRATION_BILLBOARD_WIDTH = 300; -static const int CALIBRATION_BILLBOARD_HEIGHT = 120; -static QString CALIBRATION_INSTRUCTION_MESSAGE = "Hold still to calibrate camera"; - -void DdeFaceTracker::calibrate() { - if (!Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) || _isMuted) { - return; - } - - if (!_isCalibrating) { - qCDebug(interfaceapp) << "DDE Face Tracker: Calibration started"; - - _isCalibrating = true; - _calibrationCount = 0; - _calibrationMessage = CALIBRATION_INSTRUCTION_MESSAGE + "\n\n"; - - // FIXME: this overlay probably doesn't work anymore - _calibrationBillboard = new TextOverlay(); - glm::vec2 viewport = qApp->getCanvasSize(); - _calibrationBillboard->setX((viewport.x - CALIBRATION_BILLBOARD_WIDTH) / 2); - _calibrationBillboard->setY((viewport.y - CALIBRATION_BILLBOARD_HEIGHT) / 2); - _calibrationBillboard->setWidth(CALIBRATION_BILLBOARD_WIDTH); - _calibrationBillboard->setHeight(CALIBRATION_BILLBOARD_HEIGHT); - _calibrationBillboardID = qApp->getOverlays().addOverlay(_calibrationBillboard); - - for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { - _calibrationValues[i] = 0.0f; - } - } -} - -void DdeFaceTracker::addCalibrationDatum() { - const int LARGE_TICK_INTERVAL = 30; - const int SMALL_TICK_INTERVAL = 6; - int samplesLeft = CALIBRATION_SAMPLES - _calibrationCount; - if (samplesLeft % LARGE_TICK_INTERVAL == 0) { - _calibrationMessage += QString::number(samplesLeft / LARGE_TICK_INTERVAL); - // FIXME: set overlay text - } else if (samplesLeft % SMALL_TICK_INTERVAL == 0) { - _calibrationMessage += "."; - // FIXME: set overlay text - } - - for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { - _calibrationValues[i] += _coefficients[i]; - } - - _calibrationCount += 1; -} - -void DdeFaceTracker::cancelCalibration() { - qApp->getOverlays().deleteOverlay(_calibrationBillboardID); - _calibrationBillboard = NULL; - _isCalibrating = false; - qCDebug(interfaceapp) << "DDE Face Tracker: Calibration cancelled"; -} - -void DdeFaceTracker::finishCalibration() { - qApp->getOverlays().deleteOverlay(_calibrationBillboardID); - _calibrationBillboard = NULL; - _isCalibrating = false; - _isCalibrated = true; - - for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { - _coefficientAverages[i] = _calibrationValues[i] / (float)CALIBRATION_SAMPLES; - } - - reset(); - - qCDebug(interfaceapp) << "DDE Face Tracker: Calibration finished"; -} diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h deleted file mode 100644 index dc451134f0..0000000000 --- a/interface/src/devices/DdeFaceTracker.h +++ /dev/null @@ -1,181 +0,0 @@ -// -// DdeFaceTracker.h -// -// -// Created by Clement on 8/2/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_DdeFaceTracker_h -#define hifi_DdeFaceTracker_h - -#include - -//Disabling dde due to random crashes with closing the socket on macos. all the accompanying code is wrapped with the ifdef HAVE_DDE. uncomment the define below to enable -#if defined(Q_OS_WIN) || defined(Q_OS_OSX) - //#define HAVE_DDE -#endif - -#include -#include - -#include -#include - -#include - -/**jsdoc - * The FaceTracker API helps manage facial tracking hardware. - * @namespace FaceTracker - * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - */ - -class DdeFaceTracker : public FaceTracker, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - virtual void init() override; - virtual void reset() override; - virtual void update(float deltaTime) override; - - virtual bool isActive() const override; - virtual bool isTracking() const override; - - float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); } - float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); } - float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); } - float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); } - - float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); } - float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); } - float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); } - float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); } - float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); } - - float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); } - float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); } - float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); } - - float getEyeClosingThreshold() { return _eyeClosingThreshold.get(); } - void setEyeClosingThreshold(float eyeClosingThreshold); - -public slots: - - /**jsdoc - * @function FaceTracker.setEnabled - * @param {boolean} enabled - */ - void setEnabled(bool enabled) override; - - /**jsdoc - * @function FaceTracker.calibrate - */ - void calibrate(); - -private slots: - void processFinished(int exitCode, QProcess::ExitStatus exitStatus); - - //sockets - void socketErrorOccurred(QAbstractSocket::SocketError socketError); - void readPendingDatagrams(); - void socketStateChanged(QAbstractSocket::SocketState socketState); - -private: - DdeFaceTracker(); - DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort); - virtual ~DdeFaceTracker(); - - QProcess* _ddeProcess; - bool _ddeStopping; - - QHostAddress _host; - quint16 _serverPort; - quint16 _controlPort; - - float getBlendshapeCoefficient(int index) const; - void decodePacket(const QByteArray& buffer); - - // sockets - QUdpSocket _udpSocket; - quint64 _lastReceiveTimestamp; - - bool _reset; - glm::vec3 _referenceTranslation; - glm::quat _referenceRotation; - - int _leftBlinkIndex; - int _rightBlinkIndex; - int _leftEyeDownIndex; - int _rightEyeDownIndex; - int _leftEyeInIndex; - int _rightEyeInIndex; - int _leftEyeOpenIndex; - int _rightEyeOpenIndex; - - int _browDownLeftIndex; - int _browDownRightIndex; - int _browUpCenterIndex; - int _browUpLeftIndex; - int _browUpRightIndex; - - int _mouthSmileLeftIndex; - int _mouthSmileRightIndex; - - int _jawOpenIndex; - - QVector _coefficients; - - quint64 _lastMessageReceived; - float _averageMessageTime; - - glm::vec3 _lastHeadTranslation; - glm::vec3 _filteredHeadTranslation; - - float _lastBrowUp; - float _filteredBrowUp; - - float _eyePitch; // Degrees, relative to screen - float _eyeYaw; - float _lastEyePitch; - float _lastEyeYaw; - float _filteredEyePitch; - float _filteredEyeYaw; - float _longTermAverageEyePitch = 0.0f; - float _longTermAverageEyeYaw = 0.0f; - bool _longTermAverageInitialized = false; - - enum EyeState { - EYE_UNCONTROLLED, - EYE_OPEN, - EYE_CLOSING, - EYE_CLOSED, - EYE_OPENING - }; - EyeState _eyeStates[2]; - float _lastEyeBlinks[2]; - float _filteredEyeBlinks[2]; - float _lastEyeCoefficients[2]; - Setting::Handle _eyeClosingThreshold; - - QVector _coefficientAverages; - - bool _isCalibrating; - int _calibrationCount; - QVector _calibrationValues; - TextOverlay* _calibrationBillboard; - QUuid _calibrationBillboardID; - QString _calibrationMessage; - bool _isCalibrated; - void addCalibrationDatum(); - void cancelCalibration(); - void finishCalibration(); -}; - -#endif // hifi_DdeFaceTracker_h diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index a6bc7cf84f..3986cb1533 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include "Application.h" @@ -76,8 +75,6 @@ void AvatarInputs::update() { return; } - AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)); - AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); AI_UPDATE(isHMD, qApp->isHMDMode()); } @@ -103,13 +100,6 @@ bool AvatarInputs::getIgnoreRadiusEnabled() const { return DependencyManager::get()->getIgnoreRadiusEnabled(); } -void AvatarInputs::toggleCameraMute() { - FaceTracker* faceTracker = qApp->getSelectedFaceTracker(); - if (faceTracker) { - faceTracker->toggleMute(); - } -} - void AvatarInputs::resetSensors() { qApp->resetSensors(); } diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index dca39fd433..40ee0ea7d4 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -32,14 +32,6 @@ class AvatarInputs : public QObject { * @hifi-client-entity * @hifi-avatar * - * @property {boolean} cameraEnabled - true if webcam face tracking is enabled, false if it is - * disabled. - * Read-only. - *

Deprecated: This property is deprecated and will be removed.

- * @property {boolean} cameraMuted - true if webcam face tracking is muted (temporarily disabled), - * false it if isn't. - * Read-only. - *

Deprecated: This property is deprecated and will be removed.

* @property {boolean} ignoreRadiusEnabled - true if the privacy shield is enabled, false if it * is disabled. * Read-only. @@ -51,8 +43,6 @@ class AvatarInputs : public QObject { * it is hidden. */ - AI_PROPERTY(bool, cameraEnabled, false) - AI_PROPERTY(bool, cameraMuted, false) AI_PROPERTY(bool, isHMD, false) Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) @@ -97,22 +87,6 @@ public slots: signals: - /**jsdoc - * Triggered when webcam face tracking is enabled or disabled. - * @deprecated This signal is deprecated and will be removed. - * @function AvatarInputs.cameraEnabledChanged - * @returns {Signal} - */ - void cameraEnabledChanged(); - - /**jsdoc - * Triggered when webcam face tracking is muted (temporarily disabled) or unmuted. - * @deprecated This signal is deprecated and will be removed. - * @function AvatarInputs.cameraMutedChanged - * @returns {Signal} - */ - void cameraMutedChanged(); - /**jsdoc * Triggered when the display mode changes between desktop and HMD. * @function AvatarInputs.isHMDChanged @@ -183,13 +157,6 @@ protected: */ Q_INVOKABLE void resetSensors(); - /**jsdoc - * Toggles the muting (temporary disablement) of webcam face tracking on/off. - *

Deprecated: This function is deprecated and will be removed.

- * @function AvatarInputs.toggleCameraMute - */ - Q_INVOKABLE void toggleCameraMute(); - private: void onAvatarEnteredIgnoreRadius(); void onAvatarLeftIgnoreRadius(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index ec15dd8111..5f033fe008 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -285,22 +284,6 @@ void setupPreferences() { preferences->addPreference(preference); } - static const QString FACE_TRACKING{ "Face Tracking" }; - { -#ifdef HAVE_DDE - auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; - auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; - preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Closing Threshold", getter, setter)); -#endif - } - - - { - auto getter = []()->float { return FaceTracker::getEyeDeflection(); }; - auto setter = [](float value) { FaceTracker::setEyeDeflection(value); }; - preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); - } - static const QString VR_MOVEMENT{ "VR Movement" }; { auto getter = [myAvatar]()->bool { return myAvatar->getAllowTeleporting(); }; diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index de1ac1a7c2..7929be8b04 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME avatars-renderer) setup_hifi_library(Network Script) -link_hifi_libraries(shared shaders gpu graphics animation material-networking model-networking script-engine render render-utils image trackers entities-renderer) +link_hifi_libraries(shared shaders gpu graphics animation material-networking model-networking script-engine render render-utils image entities-renderer) include_hifi_library_headers(avatars) include_hifi_library_headers(networking) include_hifi_library_headers(hfm) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index 63d8e2981c..ea6cdd7087 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include "Logging.h" @@ -26,6 +25,22 @@ using namespace std; static bool disableEyelidAdjustment { false }; +static void updateFakeCoefficients(float leftBlink, float rightBlink, float browUp, + float jawOpen, float mouth2, float mouth3, float mouth4, QVector& coefficients) { + + coefficients.resize(std::max((int)coefficients.size(), (int)Blendshapes::BlendshapeCount)); + qFill(coefficients.begin(), coefficients.end(), 0.0f); + coefficients[(int)Blendshapes::EyeBlink_L] = leftBlink; + coefficients[(int)Blendshapes::EyeBlink_R] = rightBlink; + coefficients[(int)Blendshapes::BrowsU_C] = browUp; + coefficients[(int)Blendshapes::BrowsU_L] = browUp; + coefficients[(int)Blendshapes::BrowsU_R] = browUp; + coefficients[(int)Blendshapes::JawOpen] = jawOpen; + coefficients[(int)Blendshapes::MouthSmile_L] = coefficients[(int)Blendshapes::MouthSmile_R] = mouth4; + coefficients[(int)Blendshapes::LipsUpperClose] = mouth2; + coefficients[(int)Blendshapes::LipsFunnel] = mouth3; +} + Head::Head(Avatar* owningAvatar) : HeadData(owningAvatar), _leftEyeLookAtID(DependencyManager::get()->allocateID()), @@ -153,7 +168,7 @@ void Head::simulate(float deltaTime) { _mouthTime = 0.0f; } - FaceTracker::updateFakeCoefficients( + updateFakeCoefficients( _leftEyeBlink, _rightEyeBlink, _browAudioLift, diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 0e0dd9887f..561f2c798a 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -71,7 +71,7 @@ void HeadData::setOrientation(const glm::quat& orientation) { } void HeadData::computeBlendshapesLookupMap(){ - for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { + for (int i = 0; i < (int)Blendshapes::BlendshapeCount; i++) { _blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i; } } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index e5dd158505..c6bcc2599b 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -20,7 +20,7 @@ #include #include -#include +#include // degrees const float MIN_HEAD_YAW = -180.0f; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index f8339ddd31..100f6ee98e 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index da21c7995e..5d4daf53f7 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include "FBXSerializer.h" diff --git a/libraries/shared/src/FaceshiftConstants.cpp b/libraries/shared/src/BlendshapeConstants.cpp similarity index 84% rename from libraries/shared/src/FaceshiftConstants.cpp rename to libraries/shared/src/BlendshapeConstants.cpp index b847837a7a..528b941b73 100644 --- a/libraries/shared/src/FaceshiftConstants.cpp +++ b/libraries/shared/src/BlendshapeConstants.cpp @@ -1,5 +1,5 @@ // -// FaceshiftConstants.cpp +// BlendshapeConstants.cpp // // // Created by Clement on 1/23/15. @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "FaceshiftConstants.h" +#include "BlendshapeConstants.h" const char* FACESHIFT_BLENDSHAPES[] = { "EyeBlink_L", @@ -34,7 +34,7 @@ const char* FACESHIFT_BLENDSHAPES[] = { "JawFwd", "JawLeft", "JawOpen", - "JawChew", // legacy not in ARKit + "JawChew", "JawRight", "MouthLeft", "MouthRight", @@ -48,15 +48,15 @@ const char* FACESHIFT_BLENDSHAPES[] = { "LipsStretch_R", "LipsUpperClose", "LipsLowerClose", - "LipsUpperUp", // legacy, split in ARKit - "LipsLowerDown", // legacy, split in ARKit + "LipsUpperUp", + "LipsLowerDown", "LipsUpperOpen", "LipsLowerOpen", "LipsFunnel", "LipsPucker", "ChinLowerRaise", "ChinUpperRaise", - "Sneer", // legacy, split in ARKit + "Sneer", "Puff", "CheekSquint_L", "CheekSquint_R", @@ -71,8 +71,6 @@ const char* FACESHIFT_BLENDSHAPES[] = { // MouthShrugUpper // TongueOut -const int NUM_FACESHIFT_BLENDSHAPES = sizeof(FACESHIFT_BLENDSHAPES) / sizeof(char*); - const int EYE_BLINK_L_INDEX = 0; const int EYE_BLINK_R_INDEX = 1; const int EYE_SQUINT_L_INDEX = 2; diff --git a/libraries/shared/src/BlendshapeConstants.h b/libraries/shared/src/BlendshapeConstants.h new file mode 100644 index 0000000000..0fca03e34b --- /dev/null +++ b/libraries/shared/src/BlendshapeConstants.h @@ -0,0 +1,76 @@ +// +// BlendshapeConstants.h +// +// +// Created by Clement on 1/23/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_BlendshapeConstants_h +#define hifi_BlendshapeConstants_h + +/// The names of the blendshapes expected by Faceshift, terminated with an empty string. +extern const char* FACESHIFT_BLENDSHAPES[]; + +// Eyes and Brows indices +extern const int EYE_BLINK_INDICES[]; +extern const int EYE_OPEN_INDICES[]; +extern const int BROWS_U_INDICES[]; +extern const int EYE_SQUINT_INDICES[]; + +enum class Blendshapes : int { + EyeBlink_L = 0, + EyeBlink_R, + EyeSquint_L, + EyeSquint_R, + EyeDown_L, + EyeDown_R, + EyeIn_L, + EyeIn_R, + EyeOpen_L, + EyeOpen_R, + EyeOut_L, // 10 + EyeOut_R, + EyeUp_L, + EyeUp_R, + BrowsD_L, + BrowsD_R, + BrowsU_C, + BrowsU_L, + BrowsU_R, + JawFwd, + JawLeft, // 20 + JawOpen, + JawChew, // legacy not in ARKit + JawRight, + MouthLeft, + MouthRight, + MouthFrown_L, + MouthFrown_R, + MouthSmile_L, + MouthSmile_R, + MouthDimple_L, // 30 + MouthDimple_R, + LipsStretch_L, + LipsStretch_R, + LipsUpperClose, + LipsLowerClose, + LipsUpperUp, // legacy, split in ARKit + LipsLowerDown, // legacy, split in ARKit + LipsUpperOpen, + LipsLowerOpen, + LipsFunnel, // 40 + LipsPucker, + ChinLowerRaise, + ChinUpperRaise, + Sneer, // legacy, split in ARKit + Puff, + CheekSquint_L, + CheekSquint_R, + BlendshapeCount +}; + +#endif // hifi_BlendshapeConstants_h diff --git a/libraries/shared/src/FaceshiftConstants.h b/libraries/shared/src/FaceshiftConstants.h deleted file mode 100644 index 4349a3a21e..0000000000 --- a/libraries/shared/src/FaceshiftConstants.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// FaceshiftConstants.h -// -// -// Created by Clement on 1/23/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_FaceshiftConstants_h -#define hifi_FaceshiftConstants_h - -/// The names of the blendshapes expected by Faceshift, terminated with an empty string. -extern const char* FACESHIFT_BLENDSHAPES[]; -/// The size of FACESHIFT_BLENDSHAPES -extern const int NUM_FACESHIFT_BLENDSHAPES; -// Eyes and Brows indices -extern const int EYE_BLINK_INDICES[]; -extern const int EYE_OPEN_INDICES[]; -extern const int BROWS_U_INDICES[]; -extern const int EYE_SQUINT_INDICES[]; - -#endif // hifi_FaceshiftConstants_h diff --git a/libraries/trackers/CMakeLists.txt b/libraries/trackers/CMakeLists.txt deleted file mode 100644 index 6943f1a197..0000000000 --- a/libraries/trackers/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(TARGET_NAME trackers) -setup_hifi_library() -GroupSources("src") -link_hifi_libraries(shared) -include_hifi_library_headers(octree) - -target_bullet() diff --git a/libraries/trackers/src/trackers/FaceTracker.cpp b/libraries/trackers/src/trackers/FaceTracker.cpp deleted file mode 100644 index 034787f19a..0000000000 --- a/libraries/trackers/src/trackers/FaceTracker.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// -// Created by Andrzej Kapolka on 4/9/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "FaceTracker.h" - -#include -#include -#include "Logging.h" -//#include "Menu.h" - -const int FPS_TIMER_DELAY = 2000; // ms -const int FPS_TIMER_DURATION = 2000; // ms - -const float DEFAULT_EYE_DEFLECTION = 0.25f; -Setting::Handle FaceTracker::_eyeDeflection("faceshiftEyeDeflection", DEFAULT_EYE_DEFLECTION); -bool FaceTracker::_isMuted { true }; - -void FaceTracker::init() { - _isInitialized = true; // FaceTracker can be used now -} - -inline float FaceTracker::getBlendshapeCoefficient(int index) const { - return isValidBlendshapeIndex(index) ? glm::mix(0.0f, _blendshapeCoefficients[index], getFadeCoefficient()) - : 0.0f; -} - -const QVector& FaceTracker::getBlendshapeCoefficients() const { - static QVector blendshapes; - float fadeCoefficient = getFadeCoefficient(); - if (fadeCoefficient == 1.0f) { - return _blendshapeCoefficients; - } else { - blendshapes.resize(_blendshapeCoefficients.size()); - for (int i = 0; i < _blendshapeCoefficients.size(); i++) { - blendshapes[i] = glm::mix(0.0f, _blendshapeCoefficients[i], fadeCoefficient); - } - return blendshapes; - } -} - -float FaceTracker::getFadeCoefficient() const { - return _fadeCoefficient; -} - -const glm::vec3 FaceTracker::getHeadTranslation() const { - return glm::mix(glm::vec3(0.0f), _headTranslation, getFadeCoefficient()); -} - -const glm::quat FaceTracker::getHeadRotation() const { - return safeMix(glm::quat(), _headRotation, getFadeCoefficient()); -} - -void FaceTracker::update(float deltaTime) { - // Based on exponential distributions: http://en.wikipedia.org/wiki/Exponential_distribution - static const float EPSILON = 0.02f; // MUST BE < 1.0f - static const float INVERSE_AT_EPSILON = -std::log(EPSILON); // So that f(1.0f) = EPSILON ~ 0.0f - static const float RELAXATION_TIME = 0.8f; // sec - - if (isTracking()) { - if (_relaxationStatus == 1.0f) { - _fadeCoefficient = 1.0f; - return; - } - _relaxationStatus = glm::clamp(_relaxationStatus + deltaTime / RELAXATION_TIME, 0.0f, 1.0f); - _fadeCoefficient = 1.0f - std::exp(-_relaxationStatus * INVERSE_AT_EPSILON); - } else { - if (_relaxationStatus == 0.0f) { - _fadeCoefficient = 0.0f; - return; - } - _relaxationStatus = glm::clamp(_relaxationStatus - deltaTime / RELAXATION_TIME, 0.0f, 1.0f); - _fadeCoefficient = std::exp(-(1.0f - _relaxationStatus) * INVERSE_AT_EPSILON); - } -} - -void FaceTracker::reset() { - if (isActive() && !_isCalculatingFPS) { - QTimer::singleShot(FPS_TIMER_DELAY, this, SLOT(startFPSTimer())); - _isCalculatingFPS = true; - } -} - -void FaceTracker::startFPSTimer() { - _frameCount = 0; - QTimer::singleShot(FPS_TIMER_DURATION, this, SLOT(finishFPSTimer())); -} - -void FaceTracker::countFrame() { - if (_isCalculatingFPS) { - _frameCount++; - } -} - -void FaceTracker::finishFPSTimer() { - qCDebug(trackers) << "Face tracker FPS =" << (float)_frameCount / ((float)FPS_TIMER_DURATION / 1000.0f); - _isCalculatingFPS = false; -} - -void FaceTracker::toggleMute() { - _isMuted = !_isMuted; - emit muteToggled(); -} - -void FaceTracker::setEyeDeflection(float eyeDeflection) { - _eyeDeflection.set(eyeDeflection); -} - -void FaceTracker::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp, - float jawOpen, float mouth2, float mouth3, float mouth4, QVector& coefficients) { - const int MMMM_BLENDSHAPE = 34; - const int FUNNEL_BLENDSHAPE = 40; - const int SMILE_LEFT_BLENDSHAPE = 28; - const int SMILE_RIGHT_BLENDSHAPE = 29; - const int MAX_FAKE_BLENDSHAPE = 40; // Largest modified blendshape from above and below - - coefficients.resize(std::max((int)coefficients.size(), MAX_FAKE_BLENDSHAPE + 1)); - qFill(coefficients.begin(), coefficients.end(), 0.0f); - coefficients[_leftBlinkIndex] = leftBlink; - coefficients[_rightBlinkIndex] = rightBlink; - coefficients[_browUpCenterIndex] = browUp; - coefficients[_browUpLeftIndex] = browUp; - coefficients[_browUpRightIndex] = browUp; - coefficients[_jawOpenIndex] = jawOpen; - coefficients[SMILE_LEFT_BLENDSHAPE] = coefficients[SMILE_RIGHT_BLENDSHAPE] = mouth4; - coefficients[MMMM_BLENDSHAPE] = mouth2; - coefficients[FUNNEL_BLENDSHAPE] = mouth3; -} - diff --git a/libraries/trackers/src/trackers/FaceTracker.h b/libraries/trackers/src/trackers/FaceTracker.h deleted file mode 100644 index 47fbf72616..0000000000 --- a/libraries/trackers/src/trackers/FaceTracker.h +++ /dev/null @@ -1,131 +0,0 @@ -// -// Created by Andrzej Kapolka on 4/9/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_FaceTracker_h -#define hifi_FaceTracker_h - -#include -#include - -#include -#include - -#include - -/// Base class for face trackers (DDE, BinaryVR). - -class FaceTracker : public QObject { - Q_OBJECT - -public: - virtual bool isActive() const { return false; } - virtual bool isTracking() const { return false; } - - virtual void init(); - virtual void update(float deltaTime); - virtual void reset(); - - float getFadeCoefficient() const; - - const glm::vec3 getHeadTranslation() const; - const glm::quat getHeadRotation() const; - - float getEstimatedEyePitch() const { return _estimatedEyePitch; } - float getEstimatedEyeYaw() const { return _estimatedEyeYaw; } - - int getNumBlendshapes() const { return _blendshapeCoefficients.size(); } - bool isValidBlendshapeIndex(int index) const { return index >= 0 && index < getNumBlendshapes(); } - const QVector& getBlendshapeCoefficients() const; - float getBlendshapeCoefficient(int index) const; - - static bool isMuted() { return _isMuted; } - static void setIsMuted(bool isMuted) { _isMuted = isMuted; } - - static float getEyeDeflection() { return _eyeDeflection.get(); } - static void setEyeDeflection(float eyeDeflection); - - static void updateFakeCoefficients(float leftBlink, - float rightBlink, - float browUp, - float jawOpen, - float mouth2, - float mouth3, - float mouth4, - QVector& coefficients); - -signals: - - /**jsdoc - * @function FaceTracker.muteToggled - * @returns {Signal} - */ - void muteToggled(); - -public slots: - - // No JSDoc here because it's overridden in DdeFaceTracker. - virtual void setEnabled(bool enabled) = 0; - - /**jsdoc - * @function FaceTracker.toggleMute - */ - void toggleMute(); - - /**jsdoc - * @function FaceTracker.getMuted - * @returns {boolean} - */ - bool getMuted() { return _isMuted; } - -protected: - virtual ~FaceTracker() {}; - - bool _isInitialized = false; - static bool _isMuted; - - glm::vec3 _headTranslation = glm::vec3(0.0f); - glm::quat _headRotation = glm::quat(); - float _estimatedEyePitch = 0.0f; - float _estimatedEyeYaw = 0.0f; - QVector _blendshapeCoefficients; - - float _relaxationStatus = 0.0f; // Between 0.0f and 1.0f - float _fadeCoefficient = 0.0f; // Between 0.0f and 1.0f - - void countFrame(); - -private slots: - void startFPSTimer(); - void finishFPSTimer(); - -private: - bool _isCalculatingFPS = false; - int _frameCount = 0; - - // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes - static const int _leftBlinkIndex = 0; - static const int _rightBlinkIndex = 1; - static const int _leftEyeOpenIndex = 8; - static const int _rightEyeOpenIndex = 9; - - // Brows - static const int _browDownLeftIndex = 14; - static const int _browDownRightIndex = 15; - static const int _browUpCenterIndex = 16; - static const int _browUpLeftIndex = 17; - static const int _browUpRightIndex = 18; - - static const int _mouthSmileLeftIndex = 28; - static const int _mouthSmileRightIndex = 29; - - static const int _jawOpenIndex = 21; - - static Setting::Handle _eyeDeflection; -}; - -#endif // hifi_FaceTracker_h diff --git a/libraries/trackers/src/trackers/Logging.cpp b/libraries/trackers/src/trackers/Logging.cpp deleted file mode 100644 index a4dcf1b711..0000000000 --- a/libraries/trackers/src/trackers/Logging.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// -// Created by Bradley Austin Davis on 2017/04/25 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "Logging.h" - -Q_LOGGING_CATEGORY(trackers, "hifi.trackers") diff --git a/libraries/trackers/src/trackers/Logging.h b/libraries/trackers/src/trackers/Logging.h deleted file mode 100644 index 554429b61d..0000000000 --- a/libraries/trackers/src/trackers/Logging.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// Created by Bradley Austin Davis on 2017/04/25 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_TrackersLogging_h -#define hifi_TrackersLogging_h - -#include - -Q_DECLARE_LOGGING_CATEGORY(trackers) - -#endif // hifi_TrackersLogging_h diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 07549530ce..8b18d02458 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -29,7 +29,6 @@ exports.handlers = { '../../interface/src/audio', '../../interface/src/avatar', '../../interface/src/commerce', - '../../interface/src/devices', '../../interface/src/java', '../../interface/src/networking', '../../interface/src/raypick', @@ -64,7 +63,6 @@ exports.handlers = { '../../libraries/shared/src', '../../libraries/shared/src/shared', '../../libraries/task/src/task', - '../../libraries/trackers/src/trackers', '../../libraries/ui/src', '../../libraries/ui/src/ui', '../../plugins/oculus/src', From 6efd74a339189433ea086807dfe05f41e3997a61 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 11 Oct 2019 09:37:01 -0700 Subject: [PATCH 098/203] WIP adding blendshapes to input system --- interface/resources/controllers/standard.json | 1 + .../controllers/standard_nomovement.json | 1 + interface/resources/controllers/vive.json | 2 + interface/src/avatar/MyHead.cpp | 10 ++- .../src/avatars-renderer/Head.cpp | 18 ++--- .../controllers/src/controllers/Actions.cpp | 68 ++++++++++++++++- .../controllers/src/controllers/Actions.h | 68 ++++++++++++++++- .../src/controllers/StandardController.cpp | 68 ++++++++++++++++- .../src/controllers/StandardControls.h | 3 + libraries/shared/src/BlendshapeConstants.cpp | 51 ++++++------- libraries/shared/src/BlendshapeConstants.h | 74 +++++++++++++++---- 11 files changed, 300 insertions(+), 64 deletions(-) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 195f909942..7f24924f7b 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -166,6 +166,7 @@ { "from": "Standard.LeftEye", "to": "Actions.LeftEye" }, { "from": "Standard.RightEye", "to": "Actions.RightEye" }, + // AJT: blendshapes { "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" }, { "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" }, diff --git a/interface/resources/controllers/standard_nomovement.json b/interface/resources/controllers/standard_nomovement.json index 602d3bb798..67eddfad31 100644 --- a/interface/resources/controllers/standard_nomovement.json +++ b/interface/resources/controllers/standard_nomovement.json @@ -61,6 +61,7 @@ { "from": "Standard.LeftEye", "to": "Actions.LeftEye" }, { "from": "Standard.RightEye", "to": "Actions.RightEye" }, + // AJT: blendshapes { "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" }, { "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" }, diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index b6fae1dd79..045f72739c 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -98,6 +98,8 @@ { "from": "Vive.Head", "to" : "Standard.Head" }, { "from": "Vive.LeftEye", "to" : "Standard.LeftEye" }, { "from": "Vive.RightEye", "to" : "Standard.RightEye" }, + + // AJT: blendshapes (only keep blink) { "from": "Vive.LeftEyeBlink", "to" : "Standard.LeftEyeBlink" }, { "from": "Vive.RightEyeBlink", "to" : "Standard.RightEyeBlink" }, diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 1c6e4690dd..73259fe073 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -58,6 +58,8 @@ void MyHead::simulate(float deltaTime) { // } // } + // AJT: blendshapes + auto userInputMapper = DependencyManager::get(); bool eyeLidsTracked = userInputMapper->getActionStateValid(controller::Action::LEFT_EYE_BLINK) && @@ -69,13 +71,13 @@ void MyHead::simulate(float deltaTime) { float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK); float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK); _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[EYE_BLINK_INDICES[0]] = leftEyeBlink; - _blendshapeCoefficients[EYE_BLINK_INDICES[1]] = rightEyeBlink; + _blendshapeCoefficients[(int)Blendshapes::EyeBlink_L] = leftEyeBlink; + _blendshapeCoefficients[(int)Blendshapes::EyeBlink_R] = rightEyeBlink; } else { const float FULLY_OPEN = 0.0f; _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[EYE_BLINK_INDICES[0]] = FULLY_OPEN; - _blendshapeCoefficients[EYE_BLINK_INDICES[1]] = FULLY_OPEN; + _blendshapeCoefficients[(int)Blendshapes::EyeBlink_L] = FULLY_OPEN; + _blendshapeCoefficients[(int)Blendshapes::EyeBlink_R] = FULLY_OPEN; } } Parent::simulate(deltaTime); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index ea6cdd7087..c7f807509c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -262,26 +262,26 @@ void Head::applyEyelidOffset(glm::quat headOrientation) { float blinkUpCoefficient = -eyelidOffset; float blinkDownCoefficient = BLINK_DOWN_MULTIPLIER * eyelidOffset; - + float openUpCoefficient = eyelidOffset; float openDownCoefficient = OPEN_DOWN_MULTIPLIER * eyelidOffset; - + float browsUpCoefficient = BROW_UP_MULTIPLIER * eyelidOffset; float browsDownCoefficient = 0.0f; bool isLookingUp = (eyePitch > 0); - + if (isLookingUp) { for (int i = 0; i < 2; i++) { - _transientBlendshapeCoefficients[EYE_BLINK_INDICES[i]] = blinkUpCoefficient; - _transientBlendshapeCoefficients[EYE_OPEN_INDICES[i]] = openUpCoefficient; - _transientBlendshapeCoefficients[BROWS_U_INDICES[i]] = browsUpCoefficient; + _transientBlendshapeCoefficients[(int)Blendshapes::EyeBlink_L + i] = blinkUpCoefficient; + _transientBlendshapeCoefficients[(int)Blendshapes::EyeOpen_L + i] = openUpCoefficient; + _transientBlendshapeCoefficients[(int)Blendshapes::BrowsU_L + i] = browsUpCoefficient; } } else { for (int i = 0; i < 2; i++) { - _transientBlendshapeCoefficients[EYE_BLINK_INDICES[i]] = blinkDownCoefficient; - _transientBlendshapeCoefficients[EYE_OPEN_INDICES[i]] = openDownCoefficient; - _transientBlendshapeCoefficients[BROWS_U_INDICES[i]] = browsDownCoefficient; + _transientBlendshapeCoefficients[(int)Blendshapes::EyeBlink_L + i] = blinkDownCoefficient; + _transientBlendshapeCoefficients[(int)Blendshapes::EyeOpen_L + i] = openDownCoefficient; + _transientBlendshapeCoefficients[(int)Blendshapes::BrowsU_L + i] = browsDownCoefficient; } } } diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index b6b96216b5..198c342b1d 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -349,8 +349,72 @@ namespace controller { makePosePair(Action::HEAD, "Head"), makePosePair(Action::LEFT_EYE, "LeftEye"), makePosePair(Action::RIGHT_EYE, "RightEye"), - makeAxisPair(Action::LEFT_EYE_BLINK, "LeftEyeBlink"), - makeAxisPair(Action::RIGHT_EYE_BLINK, "RightEyeBlink"), + + // AJT: blendshapes + makeAxisPair(Action::EYEBLINK_L, "EyeBlink_L"), + makeAxisPair(Action::EYEBLINK_R, "EyeBlink_R"), + makeAxisPair(Action::EYESQUINT_L, "EyeSquint_L"), + makeAxisPair(Action::EYESQUINT_R, "EyeSquint_R"), + makeAxisPair(Action::EYEDOWN_L, "EyeDown_L"), + makeAxisPair(Action::EYEDOWN_R, "EyeDown_R"), + makeAxisPair(Action::EYEIN_L, "EyeIn_L"), + makeAxisPair(Action::EYEIN_R, "EyeIn_R"), + makeAxisPair(Action::EYEOPEN_L, "EyeOpen_L"), + makeAxisPair(Action::EYEOPEN_R, "EyeOpen_R"), + makeAxisPair(Action::EYEOUT_L, "EyeOut_L"), + makeAxisPair(Action::EYEOUT_R, "EyeOut_R"), + makeAxisPair(Action::EYEUP_L, "EyeUp_L"), + makeAxisPair(Action::EYEUP_R, "EyeUp_R"), + makeAxisPair(Action::BROWSD_L, "BrowsD_L"), + makeAxisPair(Action::BROWSD_R, "BrowsD_R"), + makeAxisPair(Action::BROWSU_C, "BrowsU_C"), + makeAxisPair(Action::BROWSU_L, "BrowsU_L"), + makeAxisPair(Action::BROWSU_R, "BrowsU_R"), + makeAxisPair(Action::JAWFWD, "JawFwd"), + makeAxisPair(Action::JAWLEFT, "JawLeft"), + makeAxisPair(Action::JAWOPEN, "JawOpen"), + makeAxisPair(Action::JAWRIGHT, "JawRight"), + makeAxisPair(Action::MOUTHLEFT, "MouthLeft"), + makeAxisPair(Action::MOUTHRIGHT, "MouthRight"), + makeAxisPair(Action::MOUTHFROWN_L, "MouthFrown_L"), + makeAxisPair(Action::MOUTHFROWN_R, "MouthFrown_R"), + makeAxisPair(Action::MOUTHSMILE_L, "MouthSmile_L"), + makeAxisPair(Action::MOUTHSMILE_R, "MouthSmile_R"), + makeAxisPair(Action::MOUTHDIMPLE_L, "MouthDimple_L"), + makeAxisPair(Action::MOUTHDIMPLE_R, "MouthDimple_R"), + makeAxisPair(Action::LIPSSTRETCH_L, "LipsStretch_L"), + makeAxisPair(Action::LIPSSTRETCH_R, "LipsStretch_R"), + makeAxisPair(Action::LIPSUPPERCLOSE, "LipsUpperClose"), + makeAxisPair(Action::LIPSLOWERCLOSE, "LipsLowerClose"), + makeAxisPair(Action::LIPSUPPEROPEN, "LipsUpperOpen"), + makeAxisPair(Action::LIPSLOWEROPEN, "LipsLowerOpen"), + makeAxisPair(Action::LIPSFUNNEL, "LipsFunnel"), + makeAxisPair(Action::LIPSPUCKER, "LipsPucker"), + makeAxisPair(Action::PUFF, "Puff"), + makeAxisPair(Action::CHEEKSQUINT_L, "CheekSquint_L"), + makeAxisPair(Action::CHEEKSQUINT_R, "CheekSquint_R"), + makeAxisPair(Action::LIPSTOGETHER, "LipsTogether"), + makeAxisPair(Action::MOUTHUPPERUP_L, "MouthUpperUp_L"), + makeAxisPair(Action::MOUTHUPPERUP_R, "MouthUpperUp_R"), + makeAxisPair(Action::MOUTHLOWERDOWN_L, "MouthLowerDown_L"), + makeAxisPair(Action::MOUTHLOWERDOWN_R, "MouthLowerDown_R"), + makeAxisPair(Action::MOUTHPRESS_L, "MouthPress_L"), + makeAxisPair(Action::MOUTHPRESS_R, "MouthPress_R"), + makeAxisPair(Action::MOUTHSHRUGLOWER, "MouthShrugLower"), + makeAxisPair(Action::MOUTHSHRUGUPPER, "MouthShrugUpper"), + makeAxisPair(Action::NOSESNEER_L, "NoseSneer_L"), + makeAxisPair(Action::NOSESNEER_R, "NoseSneer_R"), + makeAxisPair(Action::TONGUEOUT, "TongueOut"), + makeAxisPair(Action::USERBLENDSHAPE0, "UserBlendshape0"), + makeAxisPair(Action::USERBLENDSHAPE1, "UserBlendshape1"), + makeAxisPair(Action::USERBLENDSHAPE2, "UserBlendshape2"), + makeAxisPair(Action::USERBLENDSHAPE3, "UserBlendshape3"), + makeAxisPair(Action::USERBLENDSHAPE4, "UserBlendshape4"), + makeAxisPair(Action::USERBLENDSHAPE5, "UserBlendshape5"), + makeAxisPair(Action::USERBLENDSHAPE6, "UserBlendshape6"), + makeAxisPair(Action::USERBLENDSHAPE7, "UserBlendshape7"), + makeAxisPair(Action::USERBLENDSHAPE8, "UserBlendshape8"), + makeAxisPair(Action::USERBLENDSHAPE9, "UserBlendshape9"), makePosePair(Action::LEFT_HAND_THUMB1, "LeftHandThumb1"), makePosePair(Action::LEFT_HAND_THUMB2, "LeftHandThumb2"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index f91d9f2522..e1ad17aafa 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -183,8 +183,72 @@ enum class Action { LEFT_EYE, RIGHT_EYE, - LEFT_EYE_BLINK, - RIGHT_EYE_BLINK, + + // AJT: blendshapes + EyeBlink_L, + EyeBlink_R, + EyeSquint_L, + EyeSquint_R, + EyeDown_L, + EyeDown_R, + EyeIn_L, + EyeIn_R, + EyeOpen_L, + EyeOpen_R, + EyeOut_L, + EyeOut_R, + EyeUp_L, + EyeUp_R, + BrowsD_L, + BrowsD_R, + BrowsU_C, + BrowsU_L, + BrowsU_R, + JawFwd, + JawLeft, + JawOpen, + JawRight, + MouthLeft, + MouthRight, + MouthFrown_L, + MouthFrown_R, + MouthSmile_L, + MouthSmile_R, + MouthDimple_L, + MouthDimple_R, + LipsStretch_L, + LipsStretch_R, + LipsUpperClose, + LipsLowerClose, + LipsUpperOpen, + LipsLowerOpen, + LipsFunnel, + LipsPucker, + Puff, + CheekSquint_L, + CheekSquint_R, + LipsTogether, + MouthUpperUp_L, + MouthUpperUp_R, + MouthLowerDown_L, + MouthLowerDown_R, + MouthPress_L, + MouthPress_R, + MouthShrugLower, + MouthShrugUpper, + NoseSneer_L, + NoseSneer_R, + TongueOut, + UserBlendshape0, + UserBlendshape1, + UserBlendshape2, + UserBlendshape3, + UserBlendshape4, + UserBlendshape5, + UserBlendshape6, + UserBlendshape7, + UserBlendshape8, + UserBlendshape9, NUM_ACTIONS }; diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index ae592485dc..14697b26d2 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -355,8 +355,72 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(HEAD, "Head"), makePair(LEFT_EYE, "LeftEye"), makePair(RIGHT_EYE, "RightEye"), - makePair(LEFT_EYE_BLINK, "LeftEyeBlink"), - makePair(RIGHT_EYE_BLINK, "RightEyeBlink"), + + // AJT: blendshapes + makePair(EYEBLINK_L, "EyeBlink_L"), + makePair(EYEBLINK_R, "EyeBlink_R"), + makePair(EYESQUINT_L, "EyeSquint_L"), + makePair(EYESQUINT_R, "EyeSquint_R"), + makePair(EYEDOWN_L, "EyeDown_L"), + makePair(EYEDOWN_R, "EyeDown_R"), + makePair(EYEIN_L, "EyeIn_L"), + makePair(EYEIN_R, "EyeIn_R"), + makePair(EYEOPEN_L, "EyeOpen_L"), + makePair(EYEOPEN_R, "EyeOpen_R"), + makePair(EYEOUT_L, "EyeOut_L"), + makePair(EYEOUT_R, "EyeOut_R"), + makePair(EYEUP_L, "EyeUp_L"), + makePair(EYEUP_R, "EyeUp_R"), + makePair(BROWSD_L, "BrowsD_L"), + makePair(BROWSD_R, "BrowsD_R"), + makePair(BROWSU_C, "BrowsU_C"), + makePair(BROWSU_L, "BrowsU_L"), + makePair(BROWSU_R, "BrowsU_R"), + makePair(JAWFWD, "JawFwd"), + makePair(JAWLEFT, "JawLeft"), + makePair(JAWOPEN, "JawOpen"), + makePair(JAWRIGHT, "JawRight"), + makePair(MOUTHLEFT, "MouthLeft"), + makePair(MOUTHRIGHT, "MouthRight"), + makePair(MOUTHFROWN_L, "MouthFrown_L"), + makePair(MOUTHFROWN_R, "MouthFrown_R"), + makePair(MOUTHSMILE_L, "MouthSmile_L"), + makePair(MOUTHSMILE_R, "MouthSmile_R"), + makePair(MOUTHDIMPLE_L, "MouthDimple_L"), + makePair(MOUTHDIMPLE_R, "MouthDimple_R"), + makePair(LIPSSTRETCH_L, "LipsStretch_L"), + makePair(LIPSSTRETCH_R, "LipsStretch_R"), + makePair(LIPSUPPERCLOSE, "LipsUpperClose"), + makePair(LIPSLOWERCLOSE, "LipsLowerClose"), + makePair(LIPSUPPEROPEN, "LipsUpperOpen"), + makePair(LIPSLOWEROPEN, "LipsLowerOpen"), + makePair(LIPSFUNNEL, "LipsFunnel"), + makePair(LIPSPUCKER, "LipsPucker"), + makePair(PUFF, "Puff"), + makePair(CHEEKSQUINT_L, "CheekSquint_L"), + makePair(CHEEKSQUINT_R, "CheekSquint_R"), + makePair(LIPSTOGETHER, "LipsTogether"), + makePair(MOUTHUPPERUP_L, "MouthUpperUp_L"), + makePair(MOUTHUPPERUP_R, "MouthUpperUp_R"), + makePair(MOUTHLOWERDOWN_L, "MouthLowerDown_L"), + makePair(MOUTHLOWERDOWN_R, "MouthLowerDown_R"), + makePair(MOUTHPRESS_L, "MouthPress_L"), + makePair(MOUTHPRESS_R, "MouthPress_R"), + makePair(MOUTHSHRUGLOWER, "MouthShrugLower"), + makePair(MOUTHSHRUGUPPER, "MouthShrugUpper"), + makePair(NOSESNEER_L, "NoseSneer_L"), + makePair(NOSESNEER_R, "NoseSneer_R"), + makePair(TONGUEOUT, "TongueOut"), + makePair(USERBLENDSHAPE0, "UserBlendshape0"), + makePair(USERBLENDSHAPE1, "UserBlendshape1"), + makePair(USERBLENDSHAPE2, "UserBlendshape2"), + makePair(USERBLENDSHAPE3, "UserBlendshape3"), + makePair(USERBLENDSHAPE4, "UserBlendshape4"), + makePair(USERBLENDSHAPE5, "UserBlendshape5"), + makePair(USERBLENDSHAPE6, "UserBlendshape6"), + makePair(USERBLENDSHAPE7, "UserBlendshape7"), + makePair(USERBLENDSHAPE8, "UserBlendshape8"), + makePair(USERBLENDSHAPE9, "UserBlendshape9"), // Aliases, PlayStation style names makePair(LB, "L1"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 99d9246264..2af822ffc2 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -90,8 +90,11 @@ namespace controller { // Grips LEFT_GRIP, RIGHT_GRIP, + + // AJT: blendshapes LEFT_EYE_BLINK, RIGHT_EYE_BLINK, + NUM_STANDARD_AXES, LZ = LT, RZ = RT diff --git a/libraries/shared/src/BlendshapeConstants.cpp b/libraries/shared/src/BlendshapeConstants.cpp index 528b941b73..7564c31944 100644 --- a/libraries/shared/src/BlendshapeConstants.cpp +++ b/libraries/shared/src/BlendshapeConstants.cpp @@ -34,7 +34,6 @@ const char* FACESHIFT_BLENDSHAPES[] = { "JawFwd", "JawLeft", "JawOpen", - "JawChew", "JawRight", "MouthLeft", "MouthRight", @@ -48,40 +47,34 @@ const char* FACESHIFT_BLENDSHAPES[] = { "LipsStretch_R", "LipsUpperClose", "LipsLowerClose", - "LipsUpperUp", - "LipsLowerDown", "LipsUpperOpen", "LipsLowerOpen", "LipsFunnel", "LipsPucker", - "ChinLowerRaise", - "ChinUpperRaise", - "Sneer", "Puff", "CheekSquint_L", "CheekSquint_R", + "LipsTogether", + "MouthUpperUp_L", + "MouthUpperUp_R", + "MouthLowerDown_L", + "MouthLowerDown_R", + "MouthPress_L", + "MouthPress_R", + "MouthShrugLower", + "MouthShrugUpper", + "NoseSneer_L", + "NoseSneer_R", + "TongueOut", + "UserBlendshape0", + "UserBlendshape1", + "UserBlendshape2", + "UserBlendshape3", + "UserBlendshape4", + "UserBlendshape5", + "UserBlendshape6", + "UserBlendshape7", + "UserBlendshape8", + "UserBlendshape9", "" }; - -// new in ARKit -// LipsTogether -// MouthPressLeft -// MouthPressRight -// MouthShrugLower -// MouthShrugUpper -// TongueOut - -const int EYE_BLINK_L_INDEX = 0; -const int EYE_BLINK_R_INDEX = 1; -const int EYE_SQUINT_L_INDEX = 2; -const int EYE_SQUINT_R_INDEX = 3; -const int EYE_OPEN_L_INDEX = 8; -const int EYE_OPEN_R_INDEX = 9; -const int BROWS_U_L_INDEX = 17; -const int BROWS_U_R_INDEX = 18; - - -const int EYE_BLINK_INDICES[] = { EYE_BLINK_L_INDEX, EYE_BLINK_R_INDEX }; -const int EYE_SQUINT_INDICES[] = { EYE_SQUINT_L_INDEX, EYE_SQUINT_R_INDEX }; -const int EYE_OPEN_INDICES[] = { EYE_OPEN_L_INDEX, EYE_OPEN_R_INDEX }; -const int BROWS_U_INDICES[] = { BROWS_U_L_INDEX, BROWS_U_R_INDEX }; diff --git a/libraries/shared/src/BlendshapeConstants.h b/libraries/shared/src/BlendshapeConstants.h index 0fca03e34b..97b459fa0e 100644 --- a/libraries/shared/src/BlendshapeConstants.h +++ b/libraries/shared/src/BlendshapeConstants.h @@ -15,12 +15,6 @@ /// The names of the blendshapes expected by Faceshift, terminated with an empty string. extern const char* FACESHIFT_BLENDSHAPES[]; -// Eyes and Brows indices -extern const int EYE_BLINK_INDICES[]; -extern const int EYE_OPEN_INDICES[]; -extern const int BROWS_U_INDICES[]; -extern const int EYE_SQUINT_INDICES[]; - enum class Blendshapes : int { EyeBlink_L = 0, EyeBlink_R, @@ -32,7 +26,7 @@ enum class Blendshapes : int { EyeIn_R, EyeOpen_L, EyeOpen_R, - EyeOut_L, // 10 + EyeOut_L, EyeOut_R, EyeUp_L, EyeUp_R, @@ -42,9 +36,8 @@ enum class Blendshapes : int { BrowsU_L, BrowsU_R, JawFwd, - JawLeft, // 20 + JawLeft, JawOpen, - JawChew, // legacy not in ARKit JawRight, MouthLeft, MouthRight, @@ -52,25 +45,74 @@ enum class Blendshapes : int { MouthFrown_R, MouthSmile_L, MouthSmile_R, - MouthDimple_L, // 30 + MouthDimple_L, MouthDimple_R, LipsStretch_L, LipsStretch_R, LipsUpperClose, LipsLowerClose, - LipsUpperUp, // legacy, split in ARKit - LipsLowerDown, // legacy, split in ARKit LipsUpperOpen, LipsLowerOpen, - LipsFunnel, // 40 + LipsFunnel, LipsPucker, - ChinLowerRaise, - ChinUpperRaise, - Sneer, // legacy, split in ARKit Puff, CheekSquint_L, CheekSquint_R, + LipsTogether, + MouthUpperUp_L, + MouthUpperUp_R, + MouthLowerDown_L, + MouthLowerDown_R, + MouthPress_L, + MouthPress_R, + MouthShrugLower, + MouthShrugUpper, + NoseSneer_L, + NoseSneer_R, + TongueOut, + UserBlendshape0, + UserBlendshape1, + UserBlendshape2, + UserBlendshape3, + UserBlendshape4, + UserBlendshape5, + UserBlendshape6, + UserBlendshape7, + UserBlendshape8, + UserBlendshape9, BlendshapeCount }; +enum class LegacyBlendshpaes : int { + JawChew, // not in ARKit + LipsUpperUp, // split in ARKit + LipsLowerDown, // split in ARKit + ChinLowerRaise, // not in ARKit + ChinUpperRaise, // not in ARKit + Sneer, // split in ARKit + LegacyBlendshapeCount +} + +// NEW in ARKit +// * LipsTogether +// * MouthUpperUp_L +// * MouthUpperUp_R +// * MouthLowerDown_L +// * MouthLowerDown_R +// * MouthPress_L +// * MouthPress_R +// * MouthShrugLower +// * MouthShrugUpper +// * NoseSneer_L +// * NoseSneer_R +// * TongueOut + +// Legacy shapes +// * JawChew (not in ARKit) +// * MouthUpperUp (split in ARKit) +// * MouthLowerDown (split in ARKit) +// * Sneer (split in ARKit) +// * ChinLowerRaise (not in ARKit) +// * ChinUpperRase (not in ARKit) + #endif // hifi_BlendshapeConstants_h From 389f5a1d335ad4c88f358fb8ff7356a74741cac6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 15 Oct 2019 11:37:10 -0700 Subject: [PATCH 099/203] ARKit Blendshape hookup work --- interface/resources/controllers/standard.json | 68 ++++++++- .../controllers/standard_nomovement.json | 67 ++++++++- interface/resources/controllers/vive.json | 5 +- interface/src/avatar/MyAvatar.cpp | 12 -- interface/src/avatar/MyHead.cpp | 142 ++++++++++++++---- interface/src/avatar/MySkeletonModel.cpp | 5 + .../src/avatars-renderer/Head.cpp | 12 +- .../src/avatars-renderer/SkeletonModel.cpp | 4 + libraries/avatars/src/AvatarData.cpp | 79 ++++++---- libraries/avatars/src/AvatarData.h | 11 +- libraries/avatars/src/HeadData.cpp | 60 ++++---- libraries/avatars/src/HeadData.h | 43 ++++-- .../controllers/src/controllers/Actions.h | 128 ++++++++-------- .../src/controllers/StandardControls.h | 66 +++++++- .../networking/src/udt/PacketHeaders.cpp | 4 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- libraries/shared/src/BlendshapeConstants.h | 2 +- 17 files changed, 507 insertions(+), 204 deletions(-) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 7f24924f7b..8c84039b86 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -166,10 +166,70 @@ { "from": "Standard.LeftEye", "to": "Actions.LeftEye" }, { "from": "Standard.RightEye", "to": "Actions.RightEye" }, - // AJT: blendshapes - { "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" }, - { "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" }, - + { "from": "Standard.EyeBlink_L", "to": "Actions.EyeBlink_L" }, + { "from": "Standard.EyeBlink_R", "to": "Actions.EyeBlink_R" }, + { "from": "Standard.EyeSquint_L", "to": "Actions.EyeSquint_L" }, + { "from": "Standard.EyeSquint_R", "to": "Actions.EyeSquint_R" }, + { "from": "Standard.EyeDown_L", "to": "Actions.EyeDown_L" }, + { "from": "Standard.EyeDown_R", "to": "Actions.EyeDown_R" }, + { "from": "Standard.EyeIn_L", "to": "Actions.EyeIn_L" }, + { "from": "Standard.EyeIn_R", "to": "Actions.EyeIn_R" }, + { "from": "Standard.EyeOpen_L", "to": "Actions.EyeOpen_L" }, + { "from": "Standard.EyeOpen_R", "to": "Actions.EyeOpen_R" }, + { "from": "Standard.EyeOut_L", "to": "Actions.EyeOut_L" }, + { "from": "Standard.EyeOut_R", "to": "Actions.EyeOut_R" }, + { "from": "Standard.EyeUp_L", "to": "Actions.EyeUp_L" }, + { "from": "Standard.EyeUp_R", "to": "Actions.EyeUp_R" }, + { "from": "Standard.BrowsD_L", "to": "Actions.BrowsD_L" }, + { "from": "Standard.BrowsD_R", "to": "Actions.BrowsD_R" }, + { "from": "Standard.BrowsU_C", "to": "Actions.BrowsU_C" }, + { "from": "Standard.BrowsU_L", "to": "Actions.BrowsU_L" }, + { "from": "Standard.BrowsU_R", "to": "Actions.BrowsU_R" }, + { "from": "Standard.JawFwd", "to": "Actions.JawFwd" }, + { "from": "Standard.JawLeft", "to": "Actions.JawLeft" }, + { "from": "Standard.JawOpen", "to": "Actions.JawOpen" }, + { "from": "Standard.JawRight", "to": "Actions.JawRight" }, + { "from": "Standard.MouthLeft", "to": "Actions.MouthLeft" }, + { "from": "Standard.MouthRight", "to": "Actions.MouthRight" }, + { "from": "Standard.MouthFrown_L", "to": "Actions.MouthFrown_L" }, + { "from": "Standard.MouthFrown_R", "to": "Actions.MouthFrown_R" }, + { "from": "Standard.MouthSmile_L", "to": "Actions.MouthSmile_L" }, + { "from": "Standard.MouthSmile_R", "to": "Actions.MouthSmile_R" }, + { "from": "Standard.MouthDimple_L", "to": "Actions.MouthDimple_L" }, + { "from": "Standard.MouthDimple_R", "to": "Actions.MouthDimple_R" }, + { "from": "Standard.LipsStretch_L", "to": "Actions.LipsStretch_L" }, + { "from": "Standard.LipsStretch_R", "to": "Actions.LipsStretch_R" }, + { "from": "Standard.LipsUpperClose", "to": "Actions.LipsUpperClose" }, + { "from": "Standard.LipsLowerClose", "to": "Actions.LipsLowerClose" }, + { "from": "Standard.LipsUpperOpen", "to": "Actions.LipsUpperOpen" }, + { "from": "Standard.LipsLowerOpen", "to": "Actions.LipsLowerOpen" }, + { "from": "Standard.LipsFunnel", "to": "Actions.LipsFunnel" }, + { "from": "Standard.LipsPucker", "to": "Actions.LipsPucker" }, + { "from": "Standard.Puff", "to": "Actions.Puff" }, + { "from": "Standard.CheekSquint_L", "to": "Actions.CheekSquint_L" }, + { "from": "Standard.CheekSquint_R", "to": "Actions.CheekSquint_R" }, + { "from": "Standard.LipsTogether", "to": "Actions.LipsTogether" }, + { "from": "Standard.MouthUpperUp_L", "to": "Actions.MouthUpperUp_L" }, + { "from": "Standard.MouthUpperUp_R", "to": "Actions.MouthUpperUp_R" }, + { "from": "Standard.MouthLowerDown_L", "to": "Actions.MouthLowerDown_L" }, + { "from": "Standard.MouthLowerDown_R", "to": "Actions.MouthLowerDown_R" }, + { "from": "Standard.MouthPress_L", "to": "Actions.MouthPress_L" }, + { "from": "Standard.MouthPress_R", "to": "Actions.MouthPress_R" }, + { "from": "Standard.MouthShrugLower", "to": "Actions.MouthShrugLower" }, + { "from": "Standard.MouthShrugUpper", "to": "Actions.MouthShrugUpper" }, + { "from": "Standard.NoseSneer_L", "to": "Actions.NoseSneer_L" }, + { "from": "Standard.NoseSneer_R", "to": "Actions.NoseSneer_R" }, + { "from": "Standard.TongueOut", "to": "Actions.TongueOut" }, + { "from": "Standard.UserBlendshape0", "to": "Actions.UserBlendshape0" }, + { "from": "Standard.UserBlendshape1", "to": "Actions.UserBlendshape1" }, + { "from": "Standard.UserBlendshape2", "to": "Actions.UserBlendshape2" }, + { "from": "Standard.UserBlendshape3", "to": "Actions.UserBlendshape3" }, + { "from": "Standard.UserBlendshape4", "to": "Actions.UserBlendshape4" }, + { "from": "Standard.UserBlendshape5", "to": "Actions.UserBlendshape5" }, + { "from": "Standard.UserBlendshape6", "to": "Actions.UserBlendshape6" }, + { "from": "Standard.UserBlendshape7", "to": "Actions.UserBlendshape7" }, + { "from": "Standard.UserBlendshape8", "to": "Actions.UserBlendshape8" }, + { "from": "Standard.UserBlendshape9", "to": "Actions.UserBlendshape9" }, { "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" }, { "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" }, diff --git a/interface/resources/controllers/standard_nomovement.json b/interface/resources/controllers/standard_nomovement.json index 67eddfad31..e311a5fa62 100644 --- a/interface/resources/controllers/standard_nomovement.json +++ b/interface/resources/controllers/standard_nomovement.json @@ -61,9 +61,70 @@ { "from": "Standard.LeftEye", "to": "Actions.LeftEye" }, { "from": "Standard.RightEye", "to": "Actions.RightEye" }, - // AJT: blendshapes - { "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" }, - { "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" }, + { "from": "Standard.EyeBlink_L", "to": "Actions.EyeBlink_L" }, + { "from": "Standard.EyeBlink_R", "to": "Actions.EyeBlink_R" }, + { "from": "Standard.EyeSquint_L", "to": "Actions.EyeSquint_L" }, + { "from": "Standard.EyeSquint_R", "to": "Actions.EyeSquint_R" }, + { "from": "Standard.EyeDown_L", "to": "Actions.EyeDown_L" }, + { "from": "Standard.EyeDown_R", "to": "Actions.EyeDown_R" }, + { "from": "Standard.EyeIn_L", "to": "Actions.EyeIn_L" }, + { "from": "Standard.EyeIn_R", "to": "Actions.EyeIn_R" }, + { "from": "Standard.EyeOpen_L", "to": "Actions.EyeOpen_L" }, + { "from": "Standard.EyeOpen_R", "to": "Actions.EyeOpen_R" }, + { "from": "Standard.EyeOut_L", "to": "Actions.EyeOut_L" }, + { "from": "Standard.EyeOut_R", "to": "Actions.EyeOut_R" }, + { "from": "Standard.EyeUp_L", "to": "Actions.EyeUp_L" }, + { "from": "Standard.EyeUp_R", "to": "Actions.EyeUp_R" }, + { "from": "Standard.BrowsD_L", "to": "Actions.BrowsD_L" }, + { "from": "Standard.BrowsD_R", "to": "Actions.BrowsD_R" }, + { "from": "Standard.BrowsU_C", "to": "Actions.BrowsU_C" }, + { "from": "Standard.BrowsU_L", "to": "Actions.BrowsU_L" }, + { "from": "Standard.BrowsU_R", "to": "Actions.BrowsU_R" }, + { "from": "Standard.JawFwd", "to": "Actions.JawFwd" }, + { "from": "Standard.JawLeft", "to": "Actions.JawLeft" }, + { "from": "Standard.JawOpen", "to": "Actions.JawOpen" }, + { "from": "Standard.JawRight", "to": "Actions.JawRight" }, + { "from": "Standard.MouthLeft", "to": "Actions.MouthLeft" }, + { "from": "Standard.MouthRight", "to": "Actions.MouthRight" }, + { "from": "Standard.MouthFrown_L", "to": "Actions.MouthFrown_L" }, + { "from": "Standard.MouthFrown_R", "to": "Actions.MouthFrown_R" }, + { "from": "Standard.MouthSmile_L", "to": "Actions.MouthSmile_L" }, + { "from": "Standard.MouthSmile_R", "to": "Actions.MouthSmile_R" }, + { "from": "Standard.MouthDimple_L", "to": "Actions.MouthDimple_L" }, + { "from": "Standard.MouthDimple_R", "to": "Actions.MouthDimple_R" }, + { "from": "Standard.LipsStretch_L", "to": "Actions.LipsStretch_L" }, + { "from": "Standard.LipsStretch_R", "to": "Actions.LipsStretch_R" }, + { "from": "Standard.LipsUpperClose", "to": "Actions.LipsUpperClose" }, + { "from": "Standard.LipsLowerClose", "to": "Actions.LipsLowerClose" }, + { "from": "Standard.LipsUpperOpen", "to": "Actions.LipsUpperOpen" }, + { "from": "Standard.LipsLowerOpen", "to": "Actions.LipsLowerOpen" }, + { "from": "Standard.LipsFunnel", "to": "Actions.LipsFunnel" }, + { "from": "Standard.LipsPucker", "to": "Actions.LipsPucker" }, + { "from": "Standard.Puff", "to": "Actions.Puff" }, + { "from": "Standard.CheekSquint_L", "to": "Actions.CheekSquint_L" }, + { "from": "Standard.CheekSquint_R", "to": "Actions.CheekSquint_R" }, + { "from": "Standard.LipsTogether", "to": "Actions.LipsTogether" }, + { "from": "Standard.MouthUpperUp_L", "to": "Actions.MouthUpperUp_L" }, + { "from": "Standard.MouthUpperUp_R", "to": "Actions.MouthUpperUp_R" }, + { "from": "Standard.MouthLowerDown_L", "to": "Actions.MouthLowerDown_L" }, + { "from": "Standard.MouthLowerDown_R", "to": "Actions.MouthLowerDown_R" }, + { "from": "Standard.MouthPress_L", "to": "Actions.MouthPress_L" }, + { "from": "Standard.MouthPress_R", "to": "Actions.MouthPress_R" }, + { "from": "Standard.MouthShrugLower", "to": "Actions.MouthShrugLower" }, + { "from": "Standard.MouthShrugUpper", "to": "Actions.MouthShrugUpper" }, + { "from": "Standard.NoseSneer_L", "to": "Actions.NoseSneer_L" }, + { "from": "Standard.NoseSneer_R", "to": "Actions.NoseSneer_R" }, + { "from": "Standard.TongueOut", "to": "Actions.TongueOut" }, + { "from": "Standard.UserBlendshape0", "to": "Actions.UserBlendshape0" }, + { "from": "Standard.UserBlendshape1", "to": "Actions.UserBlendshape1" }, + { "from": "Standard.UserBlendshape2", "to": "Actions.UserBlendshape2" }, + { "from": "Standard.UserBlendshape3", "to": "Actions.UserBlendshape3" }, + { "from": "Standard.UserBlendshape4", "to": "Actions.UserBlendshape4" }, + { "from": "Standard.UserBlendshape5", "to": "Actions.UserBlendshape5" }, + { "from": "Standard.UserBlendshape6", "to": "Actions.UserBlendshape6" }, + { "from": "Standard.UserBlendshape7", "to": "Actions.UserBlendshape7" }, + { "from": "Standard.UserBlendshape8", "to": "Actions.UserBlendshape8" }, + { "from": "Standard.UserBlendshape9", "to": "Actions.UserBlendshape9" }, { "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" }, { "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" }, diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 045f72739c..4090256418 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -99,9 +99,8 @@ { "from": "Vive.LeftEye", "to" : "Standard.LeftEye" }, { "from": "Vive.RightEye", "to" : "Standard.RightEye" }, - // AJT: blendshapes (only keep blink) - { "from": "Vive.LeftEyeBlink", "to" : "Standard.LeftEyeBlink" }, - { "from": "Vive.RightEyeBlink", "to" : "Standard.RightEyeBlink" }, + { "from": "Vive.EyeBlink_L", "to" : "Standard.EyeBlink_L" }, + { "from": "Vive.EyeBlink_R", "to" : "Standard.EyeBlink_R" }, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fb42f89048..d697e1869a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -780,18 +780,6 @@ void MyAvatar::update(float deltaTime) { emit energyChanged(currentEnergy); updateEyeContactTarget(deltaTime); - - // if we're getting eye rotations from a tracker, disable observer-side procedural eye motions - auto userInputMapper = DependencyManager::get(); - bool eyesTracked = - userInputMapper->getPoseState(controller::Action::LEFT_EYE).valid && - userInputMapper->getPoseState(controller::Action::RIGHT_EYE).valid; - - int leftEyeJointIndex = getJointIndex("LeftEye"); - int rightEyeJointIndex = getJointIndex("RightEye"); - bool eyesAreOverridden = getIsJointOverridden(leftEyeJointIndex) || getIsJointOverridden(rightEyeJointIndex); - - _headData->setHasProceduralEyeMovement(!(eyesTracked || eyesAreOverridden)); } void MyAvatar::updateEyeContactTarget(float deltaTime) { diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 73259fe073..f92f8b7218 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -21,6 +21,73 @@ using namespace std; +static controller::Action blendshapeActions[] = { + controller::Action::EYEBLINK_L, + controller::Action::EYEBLINK_R, + controller::Action::EYESQUINT_L, + controller::Action::EYESQUINT_R, + controller::Action::EYEDOWN_L, + controller::Action::EYEDOWN_R, + controller::Action::EYEIN_L, + controller::Action::EYEIN_R, + controller::Action::EYEOPEN_L, + controller::Action::EYEOPEN_R, + controller::Action::EYEOUT_L, + controller::Action::EYEOUT_R, + controller::Action::EYEUP_L, + controller::Action::EYEUP_R, + controller::Action::BROWSD_L, + controller::Action::BROWSD_R, + controller::Action::BROWSU_C, + controller::Action::BROWSU_L, + controller::Action::BROWSU_R, + controller::Action::JAWFWD, + controller::Action::JAWLEFT, + controller::Action::JAWOPEN, + controller::Action::JAWRIGHT, + controller::Action::MOUTHLEFT, + controller::Action::MOUTHRIGHT, + controller::Action::MOUTHFROWN_L, + controller::Action::MOUTHFROWN_R, + controller::Action::MOUTHSMILE_L, + controller::Action::MOUTHSMILE_R, + controller::Action::MOUTHDIMPLE_L, + controller::Action::MOUTHDIMPLE_R, + controller::Action::LIPSSTRETCH_L, + controller::Action::LIPSSTRETCH_R, + controller::Action::LIPSUPPERCLOSE, + controller::Action::LIPSLOWERCLOSE, + controller::Action::LIPSUPPEROPEN, + controller::Action::LIPSLOWEROPEN, + controller::Action::LIPSFUNNEL, + controller::Action::LIPSPUCKER, + controller::Action::PUFF, + controller::Action::CHEEKSQUINT_L, + controller::Action::CHEEKSQUINT_R, + controller::Action::LIPSTOGETHER, + controller::Action::MOUTHUPPERUP_L, + controller::Action::MOUTHUPPERUP_R, + controller::Action::MOUTHLOWERDOWN_L, + controller::Action::MOUTHLOWERDOWN_R, + controller::Action::MOUTHPRESS_L, + controller::Action::MOUTHPRESS_R, + controller::Action::MOUTHSHRUGLOWER, + controller::Action::MOUTHSHRUGUPPER, + controller::Action::NOSESNEER_L, + controller::Action::NOSESNEER_R, + controller::Action::TONGUEOUT, + controller::Action::USERBLENDSHAPE0, + controller::Action::USERBLENDSHAPE1, + controller::Action::USERBLENDSHAPE2, + controller::Action::USERBLENDSHAPE3, + controller::Action::USERBLENDSHAPE4, + controller::Action::USERBLENDSHAPE5, + controller::Action::USERBLENDSHAPE6, + controller::Action::USERBLENDSHAPE7, + controller::Action::USERBLENDSHAPE8, + controller::Action::USERBLENDSHAPE9 +}; + MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) { } @@ -44,40 +111,57 @@ void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get(); // Only use face trackers when not playing back a recording. if (!player->isPlaying()) { - // TODO -- finish removing face-tracker specific code. To do this, add input channels for - // each blendshape-coefficient and update the various json files to relay them in a useful way. - // After that, input plugins can be used to drive the avatar's face, and the various "DDE" files - // can be ported into the plugin and removed. - // - // auto faceTracker = qApp->getActiveFaceTracker(); - // const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); - // _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes(); - // if (_isFaceTrackerConnected) { - // if (hasActualFaceTrackerConnected) { - // _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); - // } - // } - - // AJT: blendshapes auto userInputMapper = DependencyManager::get(); + + // if input system has control over blink blendshapes bool eyeLidsTracked = - userInputMapper->getActionStateValid(controller::Action::LEFT_EYE_BLINK) && - userInputMapper->getActionStateValid(controller::Action::RIGHT_EYE_BLINK); + userInputMapper->getActionStateValid(controller::Action::EYEBLINK_L) || + userInputMapper->getActionStateValid(controller::Action::EYEBLINK_R); - setHasScriptedBlendshapes(eyeLidsTracked); + // if input system has control over the brows. + bool browsTracked = + userInputMapper->getActionStateValid(controller::Action::BROWSD_L) || + userInputMapper->getActionStateValid(controller::Action::BROWSD_R) || + userInputMapper->getActionStateValid(controller::Action::BROWSU_L) || + userInputMapper->getActionStateValid(controller::Action::BROWSU_R) || + userInputMapper->getActionStateValid(controller::Action::BROWSU_C); - if (eyeLidsTracked) { - float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK); - float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK); - _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[(int)Blendshapes::EyeBlink_L] = leftEyeBlink; - _blendshapeCoefficients[(int)Blendshapes::EyeBlink_R] = rightEyeBlink; - } else { - const float FULLY_OPEN = 0.0f; - _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[(int)Blendshapes::EyeBlink_L] = FULLY_OPEN; - _blendshapeCoefficients[(int)Blendshapes::EyeBlink_R] = FULLY_OPEN; + // if input system has control of mouth + bool mouthTracked = + userInputMapper->getActionStateValid(controller::Action::JAWOPEN) || + userInputMapper->getActionStateValid(controller::Action::LIPSUPPERCLOSE) || + userInputMapper->getActionStateValid(controller::Action::LIPSLOWERCLOSE) || + userInputMapper->getActionStateValid(controller::Action::LIPSFUNNEL) || + userInputMapper->getActionStateValid(controller::Action::MOUTHSMILE_L) || + userInputMapper->getActionStateValid(controller::Action::MOUTHSMILE_R); + + bool eyesTracked = + userInputMapper->getPoseState(controller::Action::LEFT_EYE).valid && + userInputMapper->getPoseState(controller::Action::RIGHT_EYE).valid; + + MyAvatar* myAvatar = static_cast(_owningAvatar); + int leftEyeJointIndex = myAvatar->getJointIndex("LeftEye"); + int rightEyeJointIndex = myAvatar->getJointIndex("RightEye"); + bool eyeJointsOverridden = myAvatar->getIsJointOverridden(leftEyeJointIndex) || myAvatar->getIsJointOverridden(rightEyeJointIndex); + + bool anyInputTracked = false; + for (int i = 0; i < (int)Blendshapes::BlendshapeCount; i++) { + anyInputTracked = anyInputTracked || userInputMapper->getActionStateValid(blendshapeActions[i]); + } + + setHasInputDrivenBlendshapes(anyInputTracked); + + // suppress any procedural blendshape animation if they overlap with driven input. + setSuppressProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation, eyeLidsTracked); + setSuppressProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation, eyeLidsTracked || browsTracked); + setSuppressProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation, mouthTracked); + setSuppressProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation, eyesTracked || eyeJointsOverridden); + + if (anyInputTracked) { + for (int i = 0; i < (int)Blendshapes::BlendshapeCount; i++) { + _blendshapeCoefficients[i] = userInputMapper->getActionState(blendshapeActions[i]); + } } } Parent::simulate(deltaTime); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 8d92767321..0d7bd3f3b3 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -114,6 +114,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Head* head = _owningAvatar->getHead(); + // AJT: blendshapes TODO: RE-enable this and avoid duplication with + // SkeletonModel::updateRig() + /* bool eyePosesValid = !head->getHasProceduralEyeMovement(); glm::vec3 lookAt; if (eyePosesValid) { @@ -121,6 +124,8 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } else { lookAt = avoidCrossedEyes(head->getLookAtPosition()); } + */ + glm::vec3 lookAt = avoidCrossedEyes(head->getLookAtPosition()); MyAvatar* myAvatar = static_cast(_owningAvatar); assert(myAvatar); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index c7f807509c..2530fea4e7 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -72,7 +72,8 @@ void Head::simulate(float deltaTime) { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - if (getHasProceduralEyeMovement()) { + if (getProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation) && + !getSuppressProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation)) { // Update eye saccades const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; const float AVERAGE_SACCADE_INTERVAL = 6.0f; @@ -95,7 +96,8 @@ void Head::simulate(float deltaTime) { const float BLINK_START_VARIABILITY = 0.25f; const float FULLY_OPEN = 0.0f; const float FULLY_CLOSED = 1.0f; - if (getHasProceduralBlinkFaceMovement()) { + if (getProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation) && + !getSuppressProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation)) { // handle automatic blinks // Detect transition from talking to not; force blink after that and a delay bool forceBlink = false; @@ -146,7 +148,8 @@ void Head::simulate(float deltaTime) { } // use data to update fake Faceshift blendshape coefficients - if (getHasAudioEnabledFaceMovement()) { + if (getProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation) && + !getSuppressProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation)) { // Update audio attack data for facial animation (eyebrows and mouth) float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz _audioAttack = audioAttackAveragingRate * _audioAttack + @@ -178,7 +181,8 @@ void Head::simulate(float deltaTime) { _mouth4, _transientBlendshapeCoefficients); - if (getHasProceduralEyeFaceMovement()) { + if (getProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation) && + !getSuppressProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation)) { // This controls two things, the eye brow and the upper eye lid, it is driven by the vertical up/down angle of the // eyes relative to the head. This is to try to help prevent sleepy eyes/crazy eyes. applyEyelidOffset(getOrientation()); diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index e0fed08955..b52a68f066 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -111,6 +111,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Head* head = _owningAvatar->getHead(); + // AJT: blendshapes TODO: RE-enable this. but move into rig? + /* bool eyePosesValid = !head->getHasProceduralEyeMovement(); glm::vec3 lookAt; if (eyePosesValid) { @@ -118,6 +120,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } else { lookAt = avoidCrossedEyes(head->getCorrectedLookAtPosition()); } + */ + glm::vec3 lookAt = avoidCrossedEyes(head->getCorrectedLookAtPosition()); // no need to call Model::updateRig() because otherAvatars get their joint state // copied directly from AvtarData::_jointData (there are no Rig animations to blend) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 520241d020..c5dcfba1c0 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -110,7 +110,6 @@ AvatarData::AvatarData() : _targetScale(1.0f), _handState(0), _keyState(NO_KEY_DOWN), - _hasScriptedBlendshapes(false), _headData(NULL), _errorLogExpiry(0), _owningAvatarMixer(), @@ -156,7 +155,7 @@ float AvatarData::getDomainLimitedScale() const { void AvatarData::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { - if (hasScriptedBlendshapes == _hasScriptedBlendshapes) { + if (hasScriptedBlendshapes == _headData->getHasScriptedBlendshapes()) { return; } if (!hasScriptedBlendshapes) { @@ -165,19 +164,35 @@ void AvatarData::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { // before sending the update, or else it won't send the neutal blendshapes to the receiving clients sendAvatarDataPacket(true); } - _hasScriptedBlendshapes = hasScriptedBlendshapes; + _headData->setHasScriptedBlendshapes(hasScriptedBlendshapes); } -void AvatarData::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { - _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +bool AvatarData::getHasScriptedBlendshapes() const { + return _headData->getHasScriptedBlendshapes(); } -void AvatarData::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { - _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +void AvatarData::setHasProceduralBlinkFaceMovement(bool value) { + _headData->setProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation, value); } -void AvatarData::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { - _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +bool AvatarData::getHasProceduralBlinkFaceMovement() const { + return _headData->getProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation); +} + +void AvatarData::setHasProceduralEyeFaceMovement(bool value) { + _headData->setProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation, value); +} + +bool AvatarData::getHasProceduralEyeFaceMovement() const { + return _headData->getProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation); +} + +void AvatarData::setHasAudioEnabledFaceMovement(bool value) { + _headData->setProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation, value); +} + +bool AvatarData::getHasAudioEnabledFaceMovement() const { + return _headData->getProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation); } void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) { @@ -232,9 +247,6 @@ void AvatarData::lazyInitHeadData() const { if (!_headData) { _headData = new HeadData(const_cast(this)); } - if (_hasScriptedBlendshapes) { - _headData->_hasScriptedBlendshapes = true; - } } @@ -555,27 +567,31 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent setAtBit16(flags, HAND_STATE_FINGER_POINTING_BIT); } // face tracker state - if (_headData->_hasScriptedBlendshapes) { + if (_headData->_hasScriptedBlendshapes || _headData->_hasInputDrivenBlendshapes) { setAtBit16(flags, HAS_SCRIPTED_BLENDSHAPES); } // eye tracker state - if (!_headData->_hasProceduralEyeMovement) { - setAtBit16(flags, IS_EYE_TRACKER_CONNECTED); + if (_headData->getProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation) && + !_headData->getSuppressProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation)) { + setAtBit16(flags, HAS_PROCEDURAL_EYE_MOVEMENT); } // referential state if (!parentID.isNull()) { setAtBit16(flags, HAS_REFERENTIAL); } // audio face movement - if (_headData->getHasAudioEnabledFaceMovement()) { + if (_headData->getProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation) && + !_headData->getSuppressProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation)) { setAtBit16(flags, AUDIO_ENABLED_FACE_MOVEMENT); } // procedural eye face movement - if (_headData->getHasProceduralEyeFaceMovement()) { + if (_headData->getProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation) && + !_headData->getSuppressProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation)) { setAtBit16(flags, PROCEDURAL_EYE_FACE_MOVEMENT); } // procedural blink face movement - if (_headData->getHasProceduralBlinkFaceMovement()) { + if (_headData->getProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation) && + !_headData->getSuppressProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation)) { setAtBit16(flags, PROCEDURAL_BLINK_FACE_MOVEMENT); } // avatar collisions enabled @@ -1177,21 +1193,22 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); auto newHasScriptedBlendshapes = oneAtBit16(bitItems, HAS_SCRIPTED_BLENDSHAPES); - auto newHasntProceduralEyeMovement = oneAtBit16(bitItems, IS_EYE_TRACKER_CONNECTED); - + auto newHasProceduralEyeMovement = oneAtBit16(bitItems, HAS_PROCEDURAL_EYE_MOVEMENT); auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); + auto newCollideWithOtherAvatars = oneAtBit16(bitItems, COLLIDE_WITH_OTHER_AVATARS); auto newHasPriority = oneAtBit16(bitItems, HAS_HERO_PRIORITY); bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); - bool faceStateChanged = (_headData->_hasScriptedBlendshapes != newHasScriptedBlendshapes); - bool eyeStateChanged = (_headData->_hasProceduralEyeMovement == newHasntProceduralEyeMovement); - bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); - bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); - bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); + bool faceStateChanged = (_headData->getHasScriptedBlendshapes() != newHasScriptedBlendshapes); + + bool eyeStateChanged = (_headData->getProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation) != newHasProceduralEyeMovement); + bool audioEnableFaceMovementChanged = (_headData->getProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation) != newHasAudioEnabledFaceMovement); + bool proceduralEyeFaceMovementChanged = (_headData->getProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation) != newHasProceduralEyeFaceMovement); + bool proceduralBlinkFaceMovementChanged = (_headData->getProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation) != newHasProceduralBlinkFaceMovement); bool collideWithOtherAvatarsChanged = (_collideWithOtherAvatars != newCollideWithOtherAvatars); bool hasPriorityChanged = (getHasPriority() != newHasPriority); bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || @@ -1200,11 +1217,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _keyState = newKeyState; _handState = newHandState; - _headData->_hasScriptedBlendshapes = newHasScriptedBlendshapes; - _headData->setHasProceduralEyeMovement(!newHasntProceduralEyeMovement); - _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); - _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); - _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); + _headData->setHasScriptedBlendshapes(newHasScriptedBlendshapes); + _headData->setProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation, newHasProceduralEyeMovement); + _headData->setProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation, newHasAudioEnabledFaceMovement); + _headData->setProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation, newHasProceduralEyeFaceMovement); + _headData->setProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation, newHasProceduralBlinkFaceMovement); _collideWithOtherAvatars = newCollideWithOtherAvatars; setHasPriorityWithoutTimestampReset(newHasPriority); @@ -1289,7 +1306,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize); - _headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy! + _headData->_blendshapeCoefficients.resize(std::min(numCoefficients, (int)Blendshapes::BlendshapeCount)); // make sure there's room for the copy! //only copy the blendshapes to headData, not the procedural face info memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize); sourceBuffer += coefficientsSize; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 6bed5fa9db..53903412d0 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -107,7 +107,7 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits (UNUSED) const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits (UNUSED) const int HAS_SCRIPTED_BLENDSHAPES = 4; // 5th bit -const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING) +const int HAS_PROCEDURAL_EYE_MOVEMENT = 5; // 6th bit const int HAS_REFERENTIAL = 6; // 7th bit const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit (UNUSED) const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit @@ -703,13 +703,13 @@ public: float getDomainLimitedScale() const; void setHasScriptedBlendshapes(bool hasScriptedBlendshapes); - bool getHasScriptedBlendshapes() const { return _hasScriptedBlendshapes; } + bool getHasScriptedBlendshapes() const; void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); - bool getHasProceduralBlinkFaceMovement() const { return _headData->getHasProceduralBlinkFaceMovement(); } + bool getHasProceduralBlinkFaceMovement() const; void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); - bool getHasProceduralEyeFaceMovement() const { return _headData->getHasProceduralEyeFaceMovement(); } + bool getHasProceduralEyeFaceMovement() const; void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); - bool getHasAudioEnabledFaceMovement() const { return _headData->getHasAudioEnabledFaceMovement(); } + bool getHasAudioEnabledFaceMovement() const; /**jsdoc * Gets the minimum scale allowed for this avatar in the current domain. @@ -1716,7 +1716,6 @@ protected: // key state KeyState _keyState; - bool _hasScriptedBlendshapes; bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar mutable HeadData* _headData { nullptr }; diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 561f2c798a..f37b88c135 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -27,11 +27,10 @@ HeadData::HeadData(AvatarData* owningAvatar) : _basePitch(0.0f), _baseRoll(0.0f), _lookAtPosition(0.0f, 0.0f, 0.0f), - _blendshapeCoefficients(QVector(0, 0.0f)), - _transientBlendshapeCoefficients(QVector(0, 0.0f)), - _summedBlendshapeCoefficients(QVector(0, 0.0f)), _owningAvatar(owningAvatar) { + _userProceduralAnimationFlags.assign((size_t)ProceduralAnimaitonTypeCount, true); + _suppressProceduralAnimationFlags.assign((size_t)ProceduralAnimaitonTypeCount, false); computeBlendshapesLookupMap(); } @@ -102,7 +101,7 @@ const QVector& HeadData::getSummedBlendshapeCoefficients() { void HeadData::setBlendshape(QString name, float val) { - //Check to see if the named blendshape exists, and then set its value if it does + // Check to see if the named blendshape exists, and then set its value if it does auto it = _blendshapeLookupMap.find(name); if (it != _blendshapeLookupMap.end()) { if (_blendshapeCoefficients.size() <= it.value()) { @@ -112,6 +111,19 @@ void HeadData::setBlendshape(QString name, float val) { _transientBlendshapeCoefficients.resize(it.value() + 1); } _blendshapeCoefficients[it.value()] = val; + } else { + // check to see if this is a legacy blendshape that is present in + // ARKit blendshapes but is split. i.e. has left and right halfs. + if (name == "LipsUpperUp") { + _blendshapeCoefficients[(int)Blendshapes::MouthUpperUp_L] = val; + _blendshapeCoefficients[(int)Blendshapes::MouthUpperUp_R] = val; + } else if (name == "LipsLowerDown") { + _blendshapeCoefficients[(int)Blendshapes::MouthLowerDown_L] = val; + _blendshapeCoefficients[(int)Blendshapes::MouthLowerDown_R] = val; + } else if (name == "Sneer") { + _blendshapeCoefficients[(int)Blendshapes::NoseSneer_L] = val; + _blendshapeCoefficients[(int)Blendshapes::NoseSneer_R] = val; + } } } @@ -197,38 +209,34 @@ void HeadData::fromJson(const QJsonObject& json) { } } -bool HeadData::getHasProceduralEyeFaceMovement() const { - return _hasProceduralEyeFaceMovement; +bool HeadData::getProceduralAnimationFlag(ProceduralAnimationType type) const { + return _userProceduralAnimationFlags[(int)type]; } -void HeadData::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { - _hasProceduralEyeFaceMovement = hasProceduralEyeFaceMovement; +void HeadData::setProceduralAnimationFlag(ProceduralAnimationType type, bool value) { + _userProceduralAnimationFlags[(int)type] = value; } -bool HeadData::getHasProceduralBlinkFaceMovement() const { - return _hasProceduralBlinkFaceMovement; +bool HeadData::getSuppressProceduralAnimationFlag(ProceduralAnimationType type) const { + return _suppressProceduralAnimationFlags[(int)type]; } -void HeadData::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { - _hasProceduralBlinkFaceMovement = hasProceduralBlinkFaceMovement; +void HeadData::setSuppressProceduralAnimationFlag(ProceduralAnimationType type, bool value) { + _suppressProceduralAnimationFlags[(int)type] = value; } -bool HeadData::getHasAudioEnabledFaceMovement() const { - return _hasAudioEnabledFaceMovement; -} - -void HeadData::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { - _hasAudioEnabledFaceMovement = hasAudioEnabledFaceMovement; -} - -bool HeadData::getHasProceduralEyeMovement() const { - return _hasProceduralEyeMovement; -} - -void HeadData::setHasProceduralEyeMovement(bool hasProceduralEyeMovement) { - _hasProceduralEyeMovement = hasProceduralEyeMovement; +bool HeadData::getHasScriptedBlendshapes() const { + return _hasScriptedBlendshapes; } void HeadData::setHasScriptedBlendshapes(bool value) { _hasScriptedBlendshapes = value; } + +bool HeadData::getHasInputDrivenBlendshapes() const { + return _hasInputDrivenBlendshapes; +} + +void HeadData::setHasInputDrivenBlendshapes(bool value) { + _hasInputDrivenBlendshapes = value; +} diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index c6bcc2599b..9652792512 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -72,17 +72,29 @@ public: } bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; } - bool getHasProceduralEyeFaceMovement() const; - void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); - bool getHasProceduralBlinkFaceMovement() const; - void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); - bool getHasAudioEnabledFaceMovement() const; - void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); - bool getHasProceduralEyeMovement() const; - void setHasProceduralEyeMovement(bool hasProceduralEyeMovement); + enum ProceduralAnimationType { + AudioProceduralBlendshapeAnimation = 0, + BlinkProceduralBlendshapeAnimation, + LidAdjustmentProceduralBlendshapeAnimation, + SaccadeProceduralEyeJointAnimation, + ProceduralAnimaitonTypeCount, + }; + // called by scripts to enable or disable procedural blendshape or eye joint animations. + bool getProceduralAnimationFlag(ProceduralAnimationType type) const; + void setProceduralAnimationFlag(ProceduralAnimationType type, bool value); + + // called by c++ to suppress, i.e. temporarily disable a procedural animation. + bool getSuppressProceduralAnimationFlag(ProceduralAnimationType flag) const; + void setSuppressProceduralAnimationFlag(ProceduralAnimationType flag, bool value); + + // called by scripts to enable/disable manual adjustment of blendshapes void setHasScriptedBlendshapes(bool value); - bool getHasScriptedBlendshapes() const { return _hasScriptedBlendshapes; } + bool getHasScriptedBlendshapes() const; + + // called by C++ code to denote the presence of manually driven blendshapes. + void setHasInputDrivenBlendshapes(bool value); + bool getHasInputDrivenBlendshapes() const; friend class AvatarData; @@ -98,21 +110,20 @@ protected: glm::vec3 _lookAtPosition; quint64 _lookAtPositionChanged { 0 }; - bool _hasAudioEnabledFaceMovement { true }; - bool _hasProceduralBlinkFaceMovement { true }; - bool _hasProceduralEyeFaceMovement { true }; - bool _hasProceduralEyeMovement { true }; + std::vector _userProceduralAnimationFlags; + std::vector _suppressProceduralAnimationFlags; bool _hasScriptedBlendshapes { false }; + bool _hasInputDrivenBlendshapes { false }; float _leftEyeBlink { 0.0f }; float _rightEyeBlink { 0.0f }; float _averageLoudness { 0.0f }; float _browAudioLift { 0.0f }; - QVector _blendshapeCoefficients; - QVector _transientBlendshapeCoefficients; - QVector _summedBlendshapeCoefficients; + QVector _blendshapeCoefficients { (int)Blendshapes::BlendshapeCount, 0.0f }; + QVector _transientBlendshapeCoefficients { (int)Blendshapes::BlendshapeCount, 0.0f }; + QVector _summedBlendshapeCoefficients { (int)Blendshapes::BlendshapeCount, 0.0f }; QMap _blendshapeLookupMap; AvatarData* _owningAvatar; diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index e1ad17aafa..1a5c73a1d8 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -185,70 +185,70 @@ enum class Action { RIGHT_EYE, // AJT: blendshapes - EyeBlink_L, - EyeBlink_R, - EyeSquint_L, - EyeSquint_R, - EyeDown_L, - EyeDown_R, - EyeIn_L, - EyeIn_R, - EyeOpen_L, - EyeOpen_R, - EyeOut_L, - EyeOut_R, - EyeUp_L, - EyeUp_R, - BrowsD_L, - BrowsD_R, - BrowsU_C, - BrowsU_L, - BrowsU_R, - JawFwd, - JawLeft, - JawOpen, - JawRight, - MouthLeft, - MouthRight, - MouthFrown_L, - MouthFrown_R, - MouthSmile_L, - MouthSmile_R, - MouthDimple_L, - MouthDimple_R, - LipsStretch_L, - LipsStretch_R, - LipsUpperClose, - LipsLowerClose, - LipsUpperOpen, - LipsLowerOpen, - LipsFunnel, - LipsPucker, - Puff, - CheekSquint_L, - CheekSquint_R, - LipsTogether, - MouthUpperUp_L, - MouthUpperUp_R, - MouthLowerDown_L, - MouthLowerDown_R, - MouthPress_L, - MouthPress_R, - MouthShrugLower, - MouthShrugUpper, - NoseSneer_L, - NoseSneer_R, - TongueOut, - UserBlendshape0, - UserBlendshape1, - UserBlendshape2, - UserBlendshape3, - UserBlendshape4, - UserBlendshape5, - UserBlendshape6, - UserBlendshape7, - UserBlendshape8, - UserBlendshape9, + EYEBLINK_L, + EYEBLINK_R, + EYESQUINT_L, + EYESQUINT_R, + EYEDOWN_L, + EYEDOWN_R, + EYEIN_L, + EYEIN_R, + EYEOPEN_L, + EYEOPEN_R, + EYEOUT_L, + EYEOUT_R, + EYEUP_L, + EYEUP_R, + BROWSD_L, + BROWSD_R, + BROWSU_C, + BROWSU_L, + BROWSU_R, + JAWFWD, + JAWLEFT, + JAWOPEN, + JAWRIGHT, + MOUTHLEFT, + MOUTHRIGHT, + MOUTHFROWN_L, + MOUTHFROWN_R, + MOUTHSMILE_L, + MOUTHSMILE_R, + MOUTHDIMPLE_L, + MOUTHDIMPLE_R, + LIPSSTRETCH_L, + LIPSSTRETCH_R, + LIPSUPPERCLOSE, + LIPSLOWERCLOSE, + LIPSUPPEROPEN, + LIPSLOWEROPEN, + LIPSFUNNEL, + LIPSPUCKER, + PUFF, + CHEEKSQUINT_L, + CHEEKSQUINT_R, + LIPSTOGETHER, + MOUTHUPPERUP_L, + MOUTHUPPERUP_R, + MOUTHLOWERDOWN_L, + MOUTHLOWERDOWN_R, + MOUTHPRESS_L, + MOUTHPRESS_R, + MOUTHSHRUGLOWER, + MOUTHSHRUGUPPER, + NOSESNEER_L, + NOSESNEER_R, + TONGUEOUT, + USERBLENDSHAPE0, + USERBLENDSHAPE1, + USERBLENDSHAPE2, + USERBLENDSHAPE3, + USERBLENDSHAPE4, + USERBLENDSHAPE5, + USERBLENDSHAPE6, + USERBLENDSHAPE7, + USERBLENDSHAPE8, + USERBLENDSHAPE9, NUM_ACTIONS }; diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 2af822ffc2..55bcf0e36a 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -92,8 +92,70 @@ namespace controller { RIGHT_GRIP, // AJT: blendshapes - LEFT_EYE_BLINK, - RIGHT_EYE_BLINK, + EYEBLINK_L, + EYEBLINK_R, + EYESQUINT_L, + EYESQUINT_R, + EYEDOWN_L, + EYEDOWN_R, + EYEIN_L, + EYEIN_R, + EYEOPEN_L, + EYEOPEN_R, + EYEOUT_L, + EYEOUT_R, + EYEUP_L, + EYEUP_R, + BROWSD_L, + BROWSD_R, + BROWSU_C, + BROWSU_L, + BROWSU_R, + JAWFWD, + JAWLEFT, + JAWOPEN, + JAWRIGHT, + MOUTHLEFT, + MOUTHRIGHT, + MOUTHFROWN_L, + MOUTHFROWN_R, + MOUTHSMILE_L, + MOUTHSMILE_R, + MOUTHDIMPLE_L, + MOUTHDIMPLE_R, + LIPSSTRETCH_L, + LIPSSTRETCH_R, + LIPSUPPERCLOSE, + LIPSLOWERCLOSE, + LIPSUPPEROPEN, + LIPSLOWEROPEN, + LIPSFUNNEL, + LIPSPUCKER, + PUFF, + CHEEKSQUINT_L, + CHEEKSQUINT_R, + LIPSTOGETHER, + MOUTHUPPERUP_L, + MOUTHUPPERUP_R, + MOUTHLOWERDOWN_L, + MOUTHLOWERDOWN_R, + MOUTHPRESS_L, + MOUTHPRESS_R, + MOUTHSHRUGLOWER, + MOUTHSHRUGUPPER, + NOSESNEER_L, + NOSESNEER_R, + TONGUEOUT, + USERBLENDSHAPE0, + USERBLENDSHAPE1, + USERBLENDSHAPE2, + USERBLENDSHAPE3, + USERBLENDSHAPE4, + USERBLENDSHAPE5, + USERBLENDSHAPE6, + USERBLENDSHAPE7, + USERBLENDSHAPE8, + USERBLENDSHAPE9, NUM_STANDARD_AXES, LZ = LT, diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ed68fe89dc..87bd7941d3 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,10 +38,10 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: - return static_cast(AvatarMixerPacketVersion::SendVerificationFailed); + return static_cast(AvatarMixerPacketVersion::ARKitBlendshapes); case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::SendVerificationFailed); + return static_cast(AvatarMixerPacketVersion::ARKitBlendshapes); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 52a8c8db16..fbf575065e 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -339,7 +339,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { SendMaxTranslationDimension, FBXJointOrderChange, HandControllerSection, - SendVerificationFailed + SendVerificationFailed, + ARKitBlendshapes }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/shared/src/BlendshapeConstants.h b/libraries/shared/src/BlendshapeConstants.h index 97b459fa0e..63e99f465f 100644 --- a/libraries/shared/src/BlendshapeConstants.h +++ b/libraries/shared/src/BlendshapeConstants.h @@ -91,7 +91,7 @@ enum class LegacyBlendshpaes : int { ChinUpperRaise, // not in ARKit Sneer, // split in ARKit LegacyBlendshapeCount -} +}; // NEW in ARKit // * LipsTogether From baf651ccd5e4627b61293ee04f1041b68ced94ff Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 16 Oct 2019 14:56:05 -0700 Subject: [PATCH 100/203] fix uninstall keys --- launchers/qt/src/Helper_windows.cpp | 2 +- .../qt/src/LauncherInstaller_windows.cpp | 43 +++++++++++++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index 662e64daae..ec4f5c4063 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -161,7 +161,7 @@ BOOL isProcessRunning(const char* processName, int& processID) { entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - + if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (!_stricmp(entry.szExeFile, processName)) { diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index 3423815d7c..f157a3b927 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -51,7 +51,7 @@ void LauncherInstaller::install() { deleteShortcuts(); createShortcuts(); - + deleteApplicationRegistryKeys(); createApplicationRegistryKeys(); } else { qDebug() << "Failed to install HQ Launcher"; @@ -83,17 +83,31 @@ void LauncherInstaller::createShortcuts() { (LPCSTR)("Click to Setup and Launch HQ")); } +QString randomQtString() { + const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + const int randomStringLength = 5; + auto now = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(now.time_since_epoch()); + qsrand(duration.count()); + + QString randomString; + for(int i = 0; i < randomStringLength; i++) + { + int index = qrand() % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + return randomString; +} + void LauncherInstaller::uninstall() { qDebug() << "Uninstall Launcher"; deleteShortcuts(); CommandlineOptions* options = CommandlineOptions::getInstance(); if (!options->contains("--resumeUninstall")) { QDir tmpDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation); - QString destination = tmpDirectory.absolutePath() + "/HQ Launcher.exe"; - qDebug() << destination; - if (QFile::exists(destination)) { - QFile::remove(destination); - } + QString destination = tmpDirectory.absolutePath() + "/" + randomQtString() + ".exe"; + qDebug() << "temp file destination: " << destination; bool copied = QFile::copy(_launcherRunningFilePath, destination); if (copied) { @@ -193,12 +207,14 @@ void LauncherInstaller::uninstallOldLauncher() { void LauncherInstaller::createApplicationRegistryKeys() { const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; bool success = insertRegistryKey(REGISTRY_PATH, "DisplayName", "HQ"); - std::string installPath = _launcherInstallDir.absolutePath().toStdString(); + std::string installPath = _launcherInstallDir.absolutePath().replace("/", "\\").toStdString(); success = insertRegistryKey(REGISTRY_PATH, "InstallLocation", installPath); - std::string applicationExe = installPath + "/HQ Launcher.exe"; - std::string uninstallPath = '"' + applicationExe + '"' + " --uninstall"; + std::string applicationExe = installPath + "\\HQ Launcher.exe"; + std::string uninstallPath = applicationExe + " --uninstall"; + qDebug() << QString::fromStdString(applicationExe); + qDebug() << QString::fromStdString(uninstallPath); success = insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath); - success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", "DEV"); + success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", std::string(LAUNCHER_BUILD_VERSION)); success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); success = insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); @@ -207,7 +223,7 @@ void LauncherInstaller::createApplicationRegistryKeys() { std::stringstream date; date << std::put_time(std::localtime(&now), "%Y-%m-%d") ; success = insertRegistryKey(REGISTRY_PATH, "InstallDate", date.str()); - //success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size); + success = insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)14181); success = insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1); success = insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1); @@ -216,7 +232,6 @@ void LauncherInstaller::createApplicationRegistryKeys() { void LauncherInstaller::deleteApplicationRegistryKeys() { const std::string regPath= "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; - if (!deleteRegistryKey(regPath.c_str())) { - qDebug() << "Failed to delete registryKeys"; - } + bool success = deleteRegistryKey(regPath.c_str()); + qDebug() << "Did delete Application Registry Keys: " << success; } From 9f0e82e1e65deeaf372645bac81443deb402f70a Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 16 Oct 2019 15:29:28 -0700 Subject: [PATCH 101/203] Improve eye's look at and blinking. Fix look at update --- interface/src/avatar/MyAvatar.cpp | 5 ++-- .../src/avatars-renderer/Head.cpp | 6 ++-- libraries/avatars/src/AvatarData.h | 1 + libraries/avatars/src/HeadData.cpp | 30 ++++++++++++------- libraries/avatars/src/HeadData.h | 4 +-- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0abbd4e324..2bc0ae32d4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6709,8 +6709,9 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) if (headPose.isValid()) { lookAtSpot = transformPoint(headPose.getMatrix(), glm::vec3(0.0f, 0.0f, TREE_SCALE)); } else { - lookAtSpot = myHead->getEyePosition() + - (getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + lookAtSpot = _shouldTurnToFaceCamera ? + myHead->getLookAtPosition() : + myHead->getEyePosition() + getHeadJointFrontVector() * (float)TREE_SCALE; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index 8184804e1f..e2f5ef0c60 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -96,15 +96,15 @@ void Head::simulate(float deltaTime) { // no blinking when brows are raised; blink less with increasing loudness const float BASE_BLINK_RATE = 15.0f / 60.0f; const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; - if (_blinkToRetarget || forceBlink || + if (_forceBlinkToRetarget || forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { float randSpeedVariability = randFloat(); float eyeBlinkVelocity = BLINK_SPEED + randSpeedVariability * BLINK_SPEED_VARIABILITY; - if (_blinkToRetarget) { + if (_forceBlinkToRetarget) { // Slow down by half the blink if reseting eye target eyeBlinkVelocity = 0.5f * eyeBlinkVelocity; - _blinkToRetarget = false; + _forceBlinkToRetarget = false; } _leftEyeBlinkVelocity = eyeBlinkVelocity; _rightEyeBlinkVelocity = eyeBlinkVelocity; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index bfe775e972..61729f8db3 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1480,6 +1480,7 @@ public: void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; } bool getIsNewAvatar() { return _isNewAvatar; } void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; } + bool getIsClientAvatar() const { return _isClientAvatar; } void setSkeletonData(const std::vector& skeletonData); std::vector getSkeletonData() const; void sendSkeletonData() const; diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 7d05a50143..d9998883d7 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -235,21 +235,29 @@ void HeadData::setFaceTrackerConnected(bool value) { } void HeadData::setLookAtPosition(const glm::vec3& lookAtPosition) { - if (_requestLookAtPosition != lookAtPosition) { - _lookAtPositionChanged = usecTimestampNow(); - glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); - glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); - const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees - _blinkToRetarget = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; - _lookAtUpdated = false; - } - if (_lookAtUpdated) { + if (_owningAvatar->getIsClientAvatar() || _owningAvatar->isMyAvatar()) { + if (_isEyeLookAtUpdated && _requestLookAtPosition != lookAtPosition) { + _lookAtPositionChanged = usecTimestampNow(); + glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); + glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); + const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees + _forceBlinkToRetarget = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; + if (_forceBlinkToRetarget) { + _isEyeLookAtUpdated = false; + } else { + _lookAtPosition = lookAtPosition; + } + } + _requestLookAtPosition = lookAtPosition; + } else { + if (_lookAtPosition != lookAtPosition) { + _lookAtPositionChanged = usecTimestampNow(); + } _lookAtPosition = lookAtPosition; } - _requestLookAtPosition = lookAtPosition; } void HeadData::updateEyeLookAt() { _lookAtPosition = _requestLookAtPosition; - _lookAtUpdated = true; + _isEyeLookAtUpdated = true; } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 7b73f57113..190b179f25 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -113,8 +113,8 @@ protected: QVector _summedBlendshapeCoefficients; QMap _blendshapeLookupMap; AvatarData* _owningAvatar; - bool _blinkToRetarget { false }; - bool _lookAtUpdated { false }; + bool _forceBlinkToRetarget { false }; + bool _isEyeLookAtUpdated { false }; private: // privatize copy ctor and assignment operator so copies of this object cannot be made From 7ecd7abe2ba79eb036d9ff5a4860c7ce57098ccb Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 16 Oct 2019 22:29:55 -0700 Subject: [PATCH 102/203] Update text in qt launcher --- launchers/qt/resources/qml/HFBase/CreateAccountBase.qml | 2 +- launchers/qt/resources/qml/HFBase/LoginBase.qml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index a3e7ee5557..200f7ad0be 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -136,7 +136,7 @@ Item { HFTextRegular { id: displayNameText - text: "This is the display name other people see in world, it can be changed at anytime, from your profile." + text: "This is the display name other people see in High Fidelity. It can be changed at any time from your profile." wrapMode: Text.Wrap width: 430 diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index cb468f9bc1..60763308bb 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -21,7 +21,7 @@ Item { } Item { - width: 353 + width: 383 height: root.height @@ -111,7 +111,8 @@ Item { HFTextRegular { id: displayText - text: "You can change this at anytime from your profile." + text: "This is the display name other people see in High Fidelity. It can be changed at any time from your profile." + wrapMode: Text.Wrap anchors { top: password.bottom From 74526663a5c68019d1463835c65f2576372e82c6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 16 Oct 2019 22:31:10 -0700 Subject: [PATCH 103/203] Cleanup qt launcher paths, and add --version flag --- launchers/qt/src/CommandlineOptions.cpp | 2 - launchers/qt/src/Helper.cpp | 8 +- .../qt/src/LauncherInstaller_windows.cpp | 54 ++++++------- launchers/qt/src/LauncherState.cpp | 77 ++++++------------- launchers/qt/src/LauncherState.h | 6 -- launchers/qt/src/PathUtils.cpp | 55 +++++++++++++ launchers/qt/src/PathUtils.h | 16 +++- launchers/qt/src/main.cpp | 14 +++- 8 files changed, 137 insertions(+), 95 deletions(-) diff --git a/launchers/qt/src/CommandlineOptions.cpp b/launchers/qt/src/CommandlineOptions.cpp index fa60e0d236..e8b1ad0a12 100644 --- a/launchers/qt/src/CommandlineOptions.cpp +++ b/launchers/qt/src/CommandlineOptions.cpp @@ -20,14 +20,12 @@ void CommandlineOptions::parse(const int argc, char** argv) { for (int index = 1; index < argc; index++) { std::string option = argv[index]; if (isCommandlineOption(option)) { - qDebug() << "adding commandline option: " << QString::fromStdString(option); _commandlineOptions.push_back(option); } } } void CommandlineOptions::append(const std::string& command) { - qDebug() << "appending option: " << QString::fromStdString(command); _commandlineOptions.push_back(command); } diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 42f76fe0fa..43f633bb57 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -1,5 +1,7 @@ #include "Helper.h" +#include "PathUtils.h" + #include #include #include @@ -47,9 +49,9 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt break; } - QDir launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - launcherDirectory.mkpath(launcherDirectory.absolutePath()); - QString filename = launcherDirectory.absoluteFilePath("Log.txt"); + QDir logsDir = PathUtils::getLogsDirectory(); + logsDir.mkpath(logsDir.absolutePath()); + QString filename = logsDir.absoluteFilePath("Log.txt"); QFile outFile(filename); outFile.open(QIODevice::WriteOnly | QIODevice::Append); diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index 3423815d7c..9be8c6cc1b 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -2,6 +2,7 @@ #include "CommandlineOptions.h" #include "Helper.h" +#include "PathUtils.h" #include #include @@ -15,10 +16,11 @@ #include LauncherInstaller::LauncherInstaller(const QString& applicationFilePath) { - _launcherInstallDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - _launcherApplicationsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/Launcher"; + _launcherInstallDir = PathUtils::getLauncherDirectory(); + _launcherApplicationsDir = PathUtils::getApplicationsDirectory(); qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath(); qDebug() << "Launcher Application dir: " << _launcherApplicationsDir.absolutePath(); + _launcherInstallDir.mkpath(_launcherInstallDir.absolutePath()); _launcherApplicationsDir.mkpath(_launcherApplicationsDir.absolutePath()); QFileInfo fileInfo(applicationFilePath); @@ -36,7 +38,7 @@ void LauncherInstaller::install() { if (runningOutsideOfInstallDir()) { qDebug() << "Installing HQ Launcher...."; uninstallOldLauncher(); - QString oldLauncherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; + QString oldLauncherPath = PathUtils::getLauncherFilePath(); if (QFile::exists(oldLauncherPath)) { bool didRemove = QFile::remove(oldLauncherPath); @@ -59,15 +61,15 @@ void LauncherInstaller::install() { } void LauncherInstaller::createShortcuts() { - QString launcherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; + QString launcherPath = PathUtils::getLauncherFilePath(); - QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.lnk"; - QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); - QString applicationPath = _launcherApplicationsDir.absolutePath(); + QString uninstallLinkPath = _launcherInstallDir.absoluteFilePath("Uninstall HQ.lnk"); - QString appStartLinkPath = applicationPath + "/HQ Launcher.lnk"; - QString uninstallAppStartLinkPath = applicationPath + "/Uninstall HQ.lnk"; - QString desktopAppLinkPath = desktopPath + "/HQ Launcher.lnk"; + QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + + QString appStartLinkPath = _launcherApplicationsDir.absoluteFilePath("HQ Launcher.lnk"); + QString uninstallAppStartLinkPath = _launcherApplicationsDir.absoluteFilePath("Uninstall HQ.lnk"); + QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk"); createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(), @@ -122,7 +124,7 @@ void LauncherInstaller::uninstall() { } return; } - QString launcherPath = _launcherInstallDir.absolutePath() + "/HQ Launcher.exe"; + QString launcherPath = _launcherInstallDir.absoluteFilePath("HQ Launcher.exe"); if (QFile::exists(launcherPath)) { bool removed = QFile::remove(launcherPath); qDebug() << "Successfully removed " << launcherPath << ": " << removed; @@ -131,58 +133,58 @@ void LauncherInstaller::uninstall() { } void LauncherInstaller::deleteShortcuts() { - QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString applicationPath = _launcherApplicationsDir.absolutePath(); - QString uninstallLinkPath = _launcherInstallDir.absolutePath() + "/Uninstall HQ.lnk"; + QString uninstallLinkPath = _launcherInstallDir.absoluteFilePath("Uninstall HQ.lnk"); if (QFile::exists(uninstallLinkPath)) { QFile::remove(uninstallLinkPath); } - QString appStartLinkPath = applicationPath + "/HQ Launcher.lnk"; + QString appStartLinkPath = _launcherApplicationsDir.absoluteFilePath("HQ Launcher.lnk"); if (QFile::exists(appStartLinkPath)) { QFile::remove(appStartLinkPath); } - QString uninstallAppStartLinkPath = applicationPath + "/Uninstall HQ.lnk"; + QString uninstallAppStartLinkPath = _launcherApplicationsDir.absoluteFilePath("Uninstall HQ.lnk"); if (QFile::exists(uninstallAppStartLinkPath)) { QFile::remove(uninstallAppStartLinkPath); } - QString desktopAppLinkPath = desktopPath + "/HQ Launcher.lnk"; + QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk"); if (QFile::exists(desktopAppLinkPath)) { QFile::remove(desktopAppLinkPath); } } void LauncherInstaller::uninstallOldLauncher() { - QDir localAppPath = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation).value(0) + "/../../HQ"; - QDir startAppPath = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0) + "/HQ"; - QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QDir localAppDir = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation).value(0) + "/../../HQ"; + QDir startAppDir = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0) + "/HQ"; + QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); - qDebug() << localAppPath.absolutePath(); - qDebug() << startAppPath.absolutePath(); - QString desktopAppLinkPath = desktopPath + "/HQ Launcher.lnk"; + qDebug() << localAppDir.absolutePath(); + qDebug() << startAppDir.absolutePath(); + QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk"); if (QFile::exists(desktopAppLinkPath)) { QFile::remove(desktopAppLinkPath); } - QString uninstallLinkPath = localAppPath.absolutePath() + "/Uninstall HQ.lnk"; + QString uninstallLinkPath = localAppDir.absoluteFilePath("Uninstall HQ.lnk"); if (QFile::exists(uninstallLinkPath)) { QFile::remove(uninstallLinkPath); } - QString applicationPath = localAppPath.absolutePath() + "/HQ Launcher.exe"; + QString applicationPath = localAppDir.absoluteFilePath("HQ Launcher.exe"); if (QFile::exists(applicationPath)) { QFile::remove(applicationPath); } - QString appStartLinkPath = startAppPath.absolutePath() + "/HQ Launcher.lnk"; + QString appStartLinkPath = startAppDir.absoluteFilePath("HQ Launcher.lnk"); if (QFile::exists(appStartLinkPath)) { QFile::remove(appStartLinkPath); } - QString uninstallAppStartLinkPath = startAppPath.absolutePath() + "/Uninstall HQ.lnk"; + QString uninstallAppStartLinkPath = startAppDir.absoluteFilePath("Uninstall HQ.lnk"); if (QFile::exists(uninstallAppStartLinkPath)) { QFile::remove(uninstallAppStartLinkPath); } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 763718a08e..f8299c0ef4 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -23,7 +23,6 @@ #include -#include #include #include @@ -75,38 +74,8 @@ void LauncherState::gotoPreviousDebugScreen() { } } -QString LauncherState::getContentCachePath() const { - return _launcherDirectory.filePath("cache"); -} - -QString LauncherState::getClientDirectory() const { - return _launcherDirectory.filePath("interface_install"); -} - -QString LauncherState::getClientExecutablePath() const { - QDir clientDirectory = getClientDirectory(); -#if defined(Q_OS_WIN) - return clientDirectory.absoluteFilePath("interface.exe"); -#elif defined(Q_OS_MACOS) - return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); -#endif -} - -QString LauncherState::getConfigFilePath() const { - QDir clientDirectory = getClientDirectory(); - return clientDirectory.absoluteFilePath("config.json"); -} - -QString LauncherState::getLauncherFilePath() const { -#if defined(Q_OS_WIN) - return _launcherDirectory.absoluteFilePath("launcher.exe"); -#elif defined(Q_OS_MACOS) - return getBundlePath() + "/Contents/MacOS/HQ Launcher"; -#endif -} - bool LauncherState::shouldDownloadContentCache() const { - return !_contentCacheURL.isEmpty() && !QFile::exists(getContentCachePath()); + return !_contentCacheURL.isEmpty() && !QFile::exists(PathUtils::getContentCachePath()); } void LauncherState::setLastSignupErrorMessage(const QString& msg) { @@ -146,10 +115,10 @@ void LauncherState::ASSERT_STATE(const std::vector& states) { } LauncherState::LauncherState() { - _launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - // TODO Fix launcher directory + _launcherDirectory = PathUtils::getLauncherDirectory(); qDebug() << "Launcher directory: " << _launcherDirectory.absolutePath(); _launcherDirectory.mkpath(_launcherDirectory.absolutePath()); + _launcherDirectory.mkpath(PathUtils::getDownloadDirectory().absolutePath()); requestBuilds(); } @@ -255,7 +224,7 @@ void LauncherState::getCurrentClientVersion() { connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit, Qt::QueuedConnection); connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit, Qt::QueuedConnection); - client.start(getClientExecutablePath(), { "--version" }); + client.start(PathUtils::getClientExecutablePath(), { "--version" }); loop.exec(); // TODO Handle errors @@ -273,14 +242,14 @@ void LauncherState::getCurrentClientVersion() { qDebug() << "Current client version is: " << _currentClientVersion; { - auto path = getConfigFilePath(); + auto path = PathUtils::getConfigFilePath(); QFile configFile{ path }; if (configFile.open(QIODevice::ReadOnly)) { QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); auto root = doc.object(); - _config.launcherPath = getLauncherFilePath(); + _config.launcherPath = PathUtils::getLauncherFilePath(); _config.loggedIn = false; if (root.contains(configLoggedInKey)) { _config.loggedIn = root[configLoggedInKey].toBool(); @@ -509,7 +478,8 @@ void LauncherState::downloadClient() { auto request = new QNetworkRequest(QUrl(build.installerZipURL)); auto reply = _networkAccessManager.get(*request); - _clientZipFile.setFileName(_launcherDirectory.absoluteFilePath("client.zip")); + QDir downloadDir{ PathUtils::getDownloadDirectory() }; + _clientZipFile.setFileName(downloadDir.absoluteFilePath("client.zip")); qDebug() << "Opening " << _clientZipFile.fileName(); if (!_clientZipFile.open(QIODevice::WriteOnly)) { @@ -566,15 +536,18 @@ void LauncherState::installClient() { ASSERT_STATE(ApplicationState::DownloadingClient); setApplicationState(ApplicationState::InstallingClient); - _launcherDirectory.rmpath("interface_install"); - _launcherDirectory.mkpath("interface_install"); - auto installDir = _launcherDirectory.absoluteFilePath("interface_install"); + + auto clientDir = PathUtils::getClientDirectory(); + + auto clientPath = clientDir.absolutePath(); + _launcherDirectory.rmpath(clientPath); + _launcherDirectory.mkpath(clientPath); _interfaceInstallProgress = 0; - qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << installDir; + qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << clientDir.absolutePath(); - auto unzipper = new Unzipper(_clientZipFile.fileName(), QDir(installDir)); + auto unzipper = new Unzipper(_clientZipFile.fileName(), clientDir); unzipper->setAutoDelete(true); connect(unzipper, &Unzipper::progress, this, [this](float progress) { _interfaceInstallProgress = progress; @@ -666,7 +639,8 @@ void LauncherState::downloadContentCache() { request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); auto reply = _networkAccessManager.get(request); - _contentZipFile.setFileName(_launcherDirectory.absoluteFilePath("content_cache.zip")); + QDir downloadDir{ PathUtils::getDownloadDirectory() }; + _contentZipFile.setFileName(downloadDir.absoluteFilePath("content_cache.zip")); qDebug() << "Opening " << _contentZipFile.fileName(); if (!_contentZipFile.open(QIODevice::WriteOnly)) { @@ -713,7 +687,7 @@ void LauncherState::installContentCache() { ASSERT_STATE(ApplicationState::DownloadingContentCache); setApplicationState(ApplicationState::InstallingContentCache); - auto installDir = getContentCachePath(); + auto installDir = PathUtils::getContentCachePath(); qDebug() << "Unzipping " << _contentZipFile.fileName() << " to " << installDir; @@ -750,15 +724,10 @@ void LauncherState::launchClient() { setApplicationState(ApplicationState::LaunchingHighFidelity); - QDir installDirectory = _launcherDirectory.filePath("interface_install"); - QString clientPath; -#if defined(Q_OS_WIN) - clientPath = installDirectory.absoluteFilePath("interface.exe"); -#elif defined(Q_OS_MACOS) - clientPath = installDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); -#endif + QDir installDirectory = PathUtils::getClientDirectory(); + QString clientPath = PathUtils::getClientExecutablePath(); - auto path = getConfigFilePath(); + auto path = PathUtils::getConfigFilePath(); QFile configFile{ path }; if (configFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) { QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll()); @@ -766,7 +735,7 @@ void LauncherState::launchClient() { { configHomeLocationKey, _config.homeLocation }, { configLastLoginKey, _config.lastLogin }, { configLoggedInKey, _config.loggedIn }, - { configLauncherPathKey, getLauncherFilePath() }, + { configLauncherPathKey, PathUtils::getLauncherFilePath() }, }); qint64 result = configFile.write(doc.toJson()); configFile.close(); diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 11a8fbaaaf..39c4141b81 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -155,12 +155,6 @@ private: bool shouldDownloadContentCache() const; void getCurrentClientVersion(); - QString getContentCachePath() const; - QString getClientDirectory() const; - QString getClientExecutablePath() const; - QString getConfigFilePath() const; - QString getLauncherFilePath() const; - float calculateDownloadProgress() const; bool shouldDownloadLauncher(); diff --git a/launchers/qt/src/PathUtils.cpp b/launchers/qt/src/PathUtils.cpp index eba74721cb..0debda9d24 100644 --- a/launchers/qt/src/PathUtils.cpp +++ b/launchers/qt/src/PathUtils.cpp @@ -1,6 +1,8 @@ #include "PathUtils.h" +#include #include +#include QUrl PathUtils::resourcePath(const QString& source) { QString filePath = RESOURCE_PREFIX_URL + source; @@ -18,3 +20,56 @@ QString PathUtils::fontPath(const QString& fontName) { return ":/fonts/" + fontName; #endif } + +QDir PathUtils::getLauncherDirectory() { + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +} + +QDir PathUtils::getApplicationsDirectory() { + return QDir(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)).absoluteFilePath("Launcher"); +} + +// The client directory is where interface is installed to. +QDir PathUtils::getClientDirectory() { + return getLauncherDirectory().filePath("client"); +} + +QDir PathUtils::getLogsDirectory() { + return getLauncherDirectory().filePath("logs"); +} + +// The download directory is used to store files downloaded during installation. +QDir PathUtils::getDownloadDirectory() { + return getLauncherDirectory().filePath("downloads"); +} + +// The content cache path is the directory interface uses for caching data. +// It is pre-populated on startup with domain content. +QString PathUtils::getContentCachePath() { + return getLauncherDirectory().filePath("contentcache"); +} + +// The path to the interface binary. +QString PathUtils::getClientExecutablePath() { + QDir clientDirectory = getClientDirectory(); +#if defined(Q_OS_WIN) + return clientDirectory.absoluteFilePath("interface.exe"); +#elif defined(Q_OS_MACOS) + return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface"); +#endif +} + +// The path to the config.json file that the launcher uses to store information like +// the last user that logged in. +QString PathUtils::getConfigFilePath() { + return getClientDirectory().absoluteFilePath("config.json"); +} + +// The path to the launcher binary. +QString PathUtils::getLauncherFilePath() { +#if defined(Q_OS_WIN) + return getLauncherDirectory().absoluteFilePath("HQ Launcher.exe"); +#elif defined(Q_OS_MACOS) + return getBundlePath() + "/Contents/MacOS/HQ Launcher"; +#endif +} diff --git a/launchers/qt/src/PathUtils.h b/launchers/qt/src/PathUtils.h index c33041736b..d9f5279408 100644 --- a/launchers/qt/src/PathUtils.h +++ b/launchers/qt/src/PathUtils.h @@ -1,14 +1,28 @@ #pragma once +#include #include #include #include #include + class PathUtils : public QObject { Q_OBJECT public: PathUtils() = default; ~PathUtils() = default; Q_INVOKABLE static QUrl resourcePath(const QString& source); - QString static fontPath(const QString& fontName); + + static QString fontPath(const QString& fontName); + + static QDir getLauncherDirectory(); + static QDir getApplicationsDirectory(); + static QDir getDownloadDirectory(); + static QDir getClientDirectory(); + static QDir getLogsDirectory(); + + static QString getContentCachePath(); + static QString getClientExecutablePath(); + static QString getConfigFilePath(); + static QString getLauncherFilePath(); }; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 4e0e1b7437..33351dfbdf 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -30,14 +30,15 @@ bool hasSuffix(const std::string& path, const std::string& suffix) { } int main(int argc, char *argv[]) { - QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QCoreApplication::setOrganizationName(name); + QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setApplicationName("Launcher"); + Q_INIT_RESOURCE(resources); cleanLogFile(); qInstallMessageHandler(messageHandler); bool didUpdate = false; + #ifdef Q_OS_MAC if (isLauncherAlreadyRunning()) { return 0; @@ -50,8 +51,15 @@ int main(int argc, char *argv[]) { } } #endif + CommandlineOptions* options = CommandlineOptions::getInstance(); options->parse(argc, argv); + + if (options->contains("--version")) { + std::cout << LAUNCHER_BUILD_VERSION << std::endl; + return 0; + } + #ifdef Q_OS_WIN LauncherInstaller launcherInstaller(argv[0]); if (options->contains("--uninstall") || options->contains("--resumeUninstall")) { @@ -65,7 +73,6 @@ int main(int argc, char *argv[]) { if (isProcessRunning("interface.exe", interfacePID)) { shutdownProcess(interfacePID, 0); } - #endif QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); @@ -74,6 +81,7 @@ int main(int argc, char *argv[]) { options->append("--noUpdate"); } } + Launcher launcher(argc, argv); return launcher.exec(); } From 1a2080d1f1944875c3b9bc604480272c1ad3597c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 17 Oct 2019 09:03:20 -0700 Subject: [PATCH 104/203] fix mac compile option --- launchers/qt/src/PathUtils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launchers/qt/src/PathUtils.cpp b/launchers/qt/src/PathUtils.cpp index 0debda9d24..538303b564 100644 --- a/launchers/qt/src/PathUtils.cpp +++ b/launchers/qt/src/PathUtils.cpp @@ -1,5 +1,7 @@ #include "PathUtils.h" +#include "Helper.h" + #include #include #include From 3debfb95b0db5a264dfe78d556dc48e2f914ade9 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 17 Oct 2019 11:35:10 -0700 Subject: [PATCH 105/203] disable windows debug feature --- launchers/qt/src/LauncherState.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index f8299c0ef4..d4b75ca509 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -32,6 +32,7 @@ #endif //#define BREAK_ON_ERROR +//#define DEBUG_UI const QString configHomeLocationKey { "homeLocation" }; const QString configLastLoginKey { "lastLogin" }; @@ -47,6 +48,7 @@ Q_INVOKABLE void LauncherState::openURLInBrowser(QString url) { } void LauncherState::toggleDebugState() { +#ifdef DEBUG_UI _isDebuggingScreens = !_isDebuggingScreens; UIState updatedUIState = getUIState(); @@ -55,8 +57,10 @@ void LauncherState::toggleDebugState() { emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); _uiState = getUIState(); } +#endif } void LauncherState::gotoNextDebugScreen() { +#ifdef DEBUG_UI if (_currentDebugScreen < (UIState::UI_STATE_NUM - 1)) { _currentDebugScreen = (UIState)(_currentDebugScreen + 1); //UIState updatedUIState = getUIState(); @@ -64,14 +68,17 @@ void LauncherState::gotoNextDebugScreen() { emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); _uiState = getUIState(); } +#endif } void LauncherState::gotoPreviousDebugScreen() { +#ifdef DEBUG_UI if (_currentDebugScreen > 0) { _currentDebugScreen = (UIState)(_currentDebugScreen - 1); emit uiStateChanged(); emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); _uiState = getUIState(); } +#endif } bool LauncherState::shouldDownloadContentCache() const { From 24b43749671c9934bbd35f6b244f588f9a421c6c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 18 Oct 2019 09:15:56 +1300 Subject: [PATCH 106/203] AudioScope JSDoc --- interface/src/audio/AudioScope.h | 84 ++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index 912e337670..fcc9b21efe 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -26,19 +26,22 @@ class AudioScope : public QObject, public Dependency { SINGLETON_DEPENDENCY /**jsdoc - * The AudioScope API helps control the Audio Scope features in Interface + * The AudioScope API provides facilities for an audio scope. + * * @namespace AudioScope * + * @deprecated This API doesn't work properly. It is deprecated and will be removed. + * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {number} scopeInput Read-only. - * @property {number} scopeOutputLeft Read-only. - * @property {number} scopeOutputRight Read-only. - * @property {number} triggerInput Read-only. - * @property {number} triggerOutputLeft Read-only. - * @property {number} triggerOutputRight Read-only. + * @property {number[]} scopeInput - Scope input. Read-only. + * @property {number[]} scopeOutputLeft - Scope left output. Read-only. + * @property {number[]} scopeOutputRight - Scope right output. Read-only. + * @property {number[]} triggerInput - Trigger input. Read-only. + * @property {number[]} triggerOutputLeft - Trigger left output. Read-only. + * @property {number[]} triggerOutputRight - Trigger right output. Read-only. */ Q_PROPERTY(QVector scopeInput READ getScopeInput) @@ -58,159 +61,186 @@ public: public slots: /**jsdoc + * Toggle. * @function AudioScope.toggle */ void toggle() { setVisible(!_isEnabled); } /**jsdoc + * Set visible. * @function AudioScope.setVisible - * @param {boolean} visible + * @param {boolean} visible - Visible. */ void setVisible(bool visible); /**jsdoc + * Get visible. * @function AudioScope.getVisible - * @returns {boolean} + * @returns {boolean} Visible. */ bool getVisible() const { return _isEnabled; } /**jsdoc + * Toggle pause. * @function AudioScope.togglePause */ void togglePause() { setPause(!_isPaused); } /**jsdoc + * Set pause. * @function AudioScope.setPause - * @param {boolean} paused + * @param {boolean} pause - Pause. */ void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); } /**jsdoc + * Get pause. * @function AudioScope.getPause - * @returns {boolean} + * @returns {boolean} Pause. */ bool getPause() { return _isPaused; } /**jsdoc + * Toggle trigger. * @function AudioScope.toggleTrigger */ void toggleTrigger() { _autoTrigger = !_autoTrigger; } /**jsdoc + * Get auto trigger. * @function AudioScope.getAutoTrigger - * @returns {boolean} + * @returns {boolean} Auto trigger. */ bool getAutoTrigger() { return _autoTrigger; } /**jsdoc + * Set auto trigger. * @function AudioScope.setAutoTrigger - * @param {boolean} autoTrigger + * @param {boolean} autoTrigger - Auto trigger. */ void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; } /**jsdoc + * Set trigger values. * @function AudioScope.setTriggerValues - * @param {number} x - * @param {number} y + * @param {number} x - X. + * @param {number} y - Y. */ void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; } /**jsdoc + * Set triggered. * @function AudioScope.setTriggered - * @param {boolean} triggered + * @param {boolean} triggered - Triggered. */ void setTriggered(bool triggered) { _isTriggered = triggered; } /**jsdoc + * Get triggered. * @function AudioScope.getTriggered - * @returns {boolean} + * @returns {boolean} Triggered. */ bool getTriggered() { return _isTriggered; } /**jsdoc + * Get frames per second. * @function AudioScope.getFramesPerSecond - * @returns {number} + * @returns {number} Frames per second. */ float getFramesPerSecond(); /**jsdoc + * Get frames per scope. * @function AudioScope.getFramesPerScope - * @returns {number} + * @returns {number} Frames per scope. */ int getFramesPerScope() { return _framesPerScope; } /**jsdoc + * Select five frames audio scope. * @function AudioScope.selectAudioScopeFiveFrames */ void selectAudioScopeFiveFrames(); /**jsdoc + * Select twenty frames audio scope. * @function AudioScope.selectAudioScopeTwentyFrames */ void selectAudioScopeTwentyFrames(); /**jsdoc + * Select fifty frames audio scope. * @function AudioScope.selectAudioScopeFiftyFrames */ void selectAudioScopeFiftyFrames(); /**jsdoc + * Get scope input. * @function AudioScope.getScopeInput - * @returns {number[]} + * @returns {number[]} Scope input. */ QVector getScopeInput() { return _scopeInputData; }; /**jsdoc + * Get scope left output. * @function AudioScope.getScopeOutputLeft - * @returns {number[]} + * @returns {number[]} Scope left output. */ QVector getScopeOutputLeft() { return _scopeOutputLeftData; }; /**jsdoc + * Get scope right output. * @function AudioScope.getScopeOutputRight - * @returns {number[]} + * @returns {number[]} Scope right output. */ QVector getScopeOutputRight() { return _scopeOutputRightData; }; /**jsdoc + * Get trigger input. * @function AudioScope.getTriggerInput - * @returns {number[]} + * @returns {number[]} Trigger input. */ QVector getTriggerInput() { return _triggerInputData; }; /**jsdoc + * Get left trigger output. * @function AudioScope.getTriggerOutputLeft - * @returns {number[]} + * @returns {number[]} Left trigger output. */ QVector getTriggerOutputLeft() { return _triggerOutputLeftData; }; /**jsdoc + * Get right trigger output. * @function AudioScope.getTriggerOutputRight - * @returns {number[]} + * @returns {number[]} Right trigger output. */ QVector getTriggerOutputRight() { return _triggerOutputRightData; }; /**jsdoc + * Set local echo. * @function AudioScope.setLocalEcho - * @parm {boolean} localEcho + * @parm {boolean} localEcho - Local echo. */ void setLocalEcho(bool localEcho); /**jsdoc + * Set server echo. * @function AudioScope.setServerEcho - * @parm {boolean} serverEcho + * @parm {boolean} serverEcho - Server echo. */ void setServerEcho(bool serverEcho); signals: /**jsdoc + * Triggered when pause changes. * @function AudioScope.pauseChanged * @returns {Signal} */ void pauseChanged(); /**jsdoc + * Triggered when scoep is triggered. * @function AudioScope.triggered * @returns {Signal} */ From fa9bf4d51ca8996ef3f5f88fb45ac7b94ab301a7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 18 Oct 2019 09:17:03 +1300 Subject: [PATCH 107/203] JSDoc fixes noticed in passing --- interface/src/avatar/MyAvatar.h | 2 +- interface/src/raypick/LaserPointerScriptingInterface.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0f139ddbff..c93645331f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -275,7 +275,7 @@ class MyAvatar : public Avatar { * @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. * @property {number} isInSittingState - true if the user wearing the HMD is determined to be sitting - * (avatar leaning is disabled, recenntering is enabled), false if the user wearing the HMD is + * (avatar leaning is disabled, recentering is enabled), false if the user wearing the HMD is * determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). * If userRecenterModel == 2 (i.e., auto) the property value automatically updates as the user sits * or stands, unless isSitStandStateLocked == true. Setting the property value overrides the current diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 6c5ce0dbaf..5745e29e69 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -25,10 +25,10 @@ class LaserPointerScriptingInterface : public QObject, public Dependency { * represent objects for repeatedly calculating ray intersections with avatars, entities, and overlays. Ray pointers can also * be configured to generate events on entities and overlays intersected. * - *

Deprecated: This API is deprecated. Use {@link Pointers} instead. - * * @namespace LaserPointers * + * @deprecated This API is deprecated and will be removed. Use {@link Pointers} instead. + * * @hifi-interface * @hifi-client-entity * @hifi-avatar From f4f634f67bef093bca8b24286be9ad6143c89e80 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 17 Oct 2019 13:46:43 -0700 Subject: [PATCH 108/203] maybe a fix for windows scaling --- launchers/qt/src/Launcher.cpp | 2 ++ launchers/qt/src/LauncherState.cpp | 2 ++ launchers/qt/src/main.cpp | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index edc52f6427..cf80604c39 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -33,6 +33,8 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Semibold.ttf")); _launcherWindow->setSource(QUrl(PathUtils::resourcePath("qml/root.qml"))); + _launcherWindow->setHeight(540); + _launcherWindow->setWidth(627); _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); _launcherWindow->show(); } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index d4b75ca509..42d13bfa58 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -673,6 +673,8 @@ void LauncherState::contentCacheDownloadComplete() { if (reply->error()) { qDebug() << "Error downloading content cache: " << reply->error() << reply->readAll(); qDebug() << "Continuing to launch client"; + _contentDownloadProgress = 100.0f; + _contentInstallProgress = 100.0f; launchClient(); return; } diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index c7925a79dc..3918ec944e 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -49,7 +49,7 @@ int main(int argc, char *argv[]) { if (argc == 3) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { bool success = swapLaunchers(argv[1], argv[2]); - qDebug() << "Launcher install success: " << success; + qDebug() << "Successfully installed Launcher: " << success; if (!success) { options->append("--noUpdate"); } From 2504ab2c8c256cbc9befb743ee71c73bb5d84bdd Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 17 Oct 2019 15:06:43 -0700 Subject: [PATCH 109/203] Update qt launcher to work with Windows scaling --- launchers/qt/resources/qml/HFControls/HFButton.qml | 2 +- launchers/qt/resources/qml/HFControls/HFTextField.qml | 2 +- launchers/qt/resources/qml/HFControls/HFTextHeader.qml | 2 +- launchers/qt/resources/qml/HFControls/HFTextLogo.qml | 2 +- launchers/qt/resources/qml/HFControls/HFTextRegular.qml | 2 +- launchers/qt/resources/qml/root.qml | 4 ---- launchers/qt/src/Launcher.cpp | 6 ++++++ 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/launchers/qt/resources/qml/HFControls/HFButton.qml b/launchers/qt/resources/qml/HFControls/HFButton.qml index a4d077a1e7..727e139360 100644 --- a/launchers/qt/resources/qml/HFControls/HFButton.qml +++ b/launchers/qt/resources/qml/HFControls/HFButton.qml @@ -14,7 +14,7 @@ Button { property int backgroundWidth: 2 font.family: "Graphik Semibold" - font.pointSize: 12 + font.pixelSize: 15 background: Rectangle { implicitWidth: 100 diff --git a/launchers/qt/resources/qml/HFControls/HFTextField.qml b/launchers/qt/resources/qml/HFControls/HFTextField.qml index 133107ddd6..7b5dde0b23 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextField.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextField.qml @@ -7,7 +7,7 @@ TextField { height: 50 font.family: "Graphik Regular" - font.pointSize: 10.5 + font.pixelSize: 14 color: (text.length == 0 || !enabled) ? "#7e8c81" : "#000000" property bool togglePasswordField: false diff --git a/launchers/qt/resources/qml/HFControls/HFTextHeader.qml b/launchers/qt/resources/qml/HFControls/HFTextHeader.qml index f0349d7f5c..914ead46a1 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextHeader.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextHeader.qml @@ -3,6 +3,6 @@ import QtQuick 2.1 Text { font.family: "Graphik Semibold" - font.pointSize: 24 + font.pixelSize: 32 color: "#ffffff" } diff --git a/launchers/qt/resources/qml/HFControls/HFTextLogo.qml b/launchers/qt/resources/qml/HFControls/HFTextLogo.qml index 08025ef715..b8d06f16f1 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextLogo.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextLogo.qml @@ -5,7 +5,7 @@ Text { text: "High Fidelity" font.bold: true font.family: "Graphik Semibold" - font.pointSize: 14 + font.pixelSize: 17 font.letterSpacing: -1 color: "#FFFFFF" } diff --git a/launchers/qt/resources/qml/HFControls/HFTextRegular.qml b/launchers/qt/resources/qml/HFControls/HFTextRegular.qml index 58103d6118..fd43aafe55 100644 --- a/launchers/qt/resources/qml/HFControls/HFTextRegular.qml +++ b/launchers/qt/resources/qml/HFControls/HFTextRegular.qml @@ -5,7 +5,7 @@ Text { id: root font.family: "Graphik Regular" - font.pointSize: 10.5 + font.pixelSize: 14 color: "#C4C4C4" linkColor: color diff --git a/launchers/qt/resources/qml/root.qml b/launchers/qt/resources/qml/root.qml index e3f82450f2..b87f8e7450 100644 --- a/launchers/qt/resources/qml/root.qml +++ b/launchers/qt/resources/qml/root.qml @@ -7,8 +7,6 @@ import "HFControls" Item { id: root - width: 627 - height: 540 Loader { anchors.fill: parent id: loader @@ -34,8 +32,6 @@ Item { HFTextRegular { id: buildInfo - font.pixelSize: 12 - anchors { leftMargin: 10 rightMargin: 10 diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index cf80604c39..90b321ecb6 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -37,6 +37,12 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherWindow->setWidth(627); _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); _launcherWindow->show(); + + auto window = allWindows()[0]; + window->setMaximumHeight(540); + window->setMaximumWidth(627); + window->setMinimumHeight(540); + window->setMinimumWidth(627); } Launcher::~Launcher() { From 4afc4d691532136b07934c4afa9f766feff8a84a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 17 Oct 2019 15:41:46 -0700 Subject: [PATCH 110/203] Remove setMinimum/Maximum Width/Height in qt launcher --- launchers/qt/src/Launcher.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 90b321ecb6..cf80604c39 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -37,12 +37,6 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherWindow->setWidth(627); _launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView); _launcherWindow->show(); - - auto window = allWindows()[0]; - window->setMaximumHeight(540); - window->setMaximumWidth(627); - window->setMinimumHeight(540); - window->setMinimumWidth(627); } Launcher::~Launcher() { From 3bcb305f70b0ec3fd1ad4fa58da5ef56e96d32f3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 18 Oct 2019 11:51:51 +1300 Subject: [PATCH 111/203] Santize LODManager.setWorldQualityDetail() parameter value --- interface/src/LODManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 3e47d88f3c..9fd7b0851a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -397,7 +397,7 @@ void LODManager::setWorldDetailQuality(float quality) { static const float MIN_FPS = 10; static const float LOW = 0.25f; - bool isLowestValue = quality == LOW; + bool isLowestValue = quality <= LOW; bool isHMDMode = qApp->isHMDMode(); float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; From 6bcb9f64f7df15ce3b1b27ad103ea73b5f1243b3 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 17 Oct 2019 17:29:14 -0700 Subject: [PATCH 112/203] Persist in checking owner and sending challenges for failures --- assignment-client/src/avatars/MixerAvatar.cpp | 89 +++++++++++++------ assignment-client/src/avatars/MixerAvatar.h | 10 ++- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index ac633d9388..01f3454292 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -32,24 +32,41 @@ MixerAvatar::MixerAvatar() { _challengeTimer.setSingleShot(true); _challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS); - - _challengeTimer.callOnTimeout(this, [this]() { - if (_verifyState == challengeClient) { - _pendingEvent = false; - _verifyState = verificationFailed; - _needsIdentityUpdate = true; - qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID(); - } else { - qCDebug(avatars) << "Ignoring timeout of avatar challenge"; - } - }); - + _challengeTimer.callOnTimeout(this, &MixerAvatar::challengeTimeout); + // QTimer::start is a set of overloaded functions. + connect(this, &MixerAvatar::startChallengeTimer, &_challengeTimer, static_cast(&QTimer::start)); } const char* MixerAvatar::stateToName(VerifyState state) { return QMetaEnum::fromType().valueToKey(state); } +void MixerAvatar::challengeTimeout() { + switch (_verifyState) { + case challengeClient: + _verifyState = staticValidation; + _pendingEvent = true; + if (++_numberChallenges < NUM_CHALLENGES_BEFORE_FAIL) { + qCDebug(avatars) << "Retrying (" << _numberChallenges << ") timed-out challenge for" << getDisplayName() + << getSessionUUID(); + } else { + _certifyFailed = true; + _needsIdentityUpdate = true; + qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID(); + } + break; + + case verificationFailed: + qCDebug(avatars) << "Retrying failed challenge for" << getDisplayName() << getSessionUUID(); + _verifyState = staticValidation; + _pendingEvent = true; + break; + + default: + qCDebug(avatars) << "Ignoring timeout of avatar challenge"; + } +} + void MixerAvatar::fetchAvatarFST() { if (_verifyState >= requestingFST && _verifyState <= challengeClient) { qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState); @@ -210,6 +227,23 @@ void MixerAvatar::ownerRequestComplete() { networkReply->deleteLater(); } +void MixerAvatar::requestCurrentOwnership() { + // Get registered owner's public key from metaverse. + static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" }; + auto& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath(POP_MARKETPLACE_API); + networkRequest.setUrl(requestURL); + + QJsonObject request; + request["certificate_id"] = _certificateIdFromFST; + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete); +} + void MixerAvatar::processCertifyEvents() { if (!_pendingEvent) { return; @@ -221,26 +255,15 @@ void MixerAvatar::processCertifyEvents() { case receivedFST: { generateFSTHash(); + _numberChallenges = 0; if (_certificateIdFromFST.length() != 0) { QString& marketplacePublicKey = EntityItem::_marketplacePublicKey; bool staticVerification = validateFSTHash(marketplacePublicKey); _verifyState = staticVerification ? staticValidation : verificationFailed; if (_verifyState == staticValidation) { - static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" }; - auto& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath(POP_MARKETPLACE_API); - networkRequest.setUrl(requestURL); - - QJsonObject request; - request["certificate_id"] = _certificateIdFromFST; + requestCurrentOwnership(); _verifyState = requestingOwner; - QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete); } else { _needsIdentityUpdate = true; _pendingEvent = false; @@ -253,6 +276,13 @@ void MixerAvatar::processCertifyEvents() { break; } + case staticValidation: + { + requestCurrentOwnership(); + _verifyState = requestingOwner; + break; + } + case ownerResponse: { QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8()); @@ -325,8 +355,7 @@ void MixerAvatar::sendOwnerChallenge() { _challengeNonceHash = nonceHash.result(); _pendingEvent = false; - // QTimer::start is a set of overloaded functions. - QMetaObject::invokeMethod(&_challengeTimer, static_cast(&QTimer::start)); + emit startChallengeTimer(); } void MixerAvatar::processChallengeResponse(ReceivedMessage& response) { @@ -337,7 +366,7 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) { QByteArray responseData = response.readAll(); if (responseData.length() < 8) { _verifyState = error; - qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length(); + qCWarning(avatars) << "ALERT: Avatar challenge response packet too small, length:" << responseData.length(); return; } @@ -354,9 +383,11 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) { bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, QByteArray::fromBase64(signedNonce)); _verifyState = challengeResult ? verificationSucceeded : verificationFailed; + _certifyFailed = !challengeResult; _needsIdentityUpdate = true; - if (_verifyState == verificationFailed) { + if (_certifyFailed) { qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID(); + emit startChallengeTimer(); } else { qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID(); } diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 39095def50..bb6b859984 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -27,7 +27,7 @@ public: void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; } void fetchAvatarFST(); - virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; } + virtual bool isCertifyFailed() const override { return _certifyFailed; } bool needsIdentityUpdate() const { return _needsIdentityUpdate; } void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; } @@ -60,11 +60,15 @@ private: QString _ownerPublicKey; QByteArray _challengeNonceHash; QTimer _challengeTimer; + static constexpr int NUM_CHALLENGES_BEFORE_FAIL = 1; + int _numberChallenges { 0 }; + bool _certifyFailed { false }; bool _needsIdentityUpdate { false }; bool generateFSTHash(); bool validateFSTHash(const QString& publicKey) const; QByteArray canonicalJson(const QString fstFile); + void requestCurrentOwnership(); void sendOwnerChallenge(); static const QString VERIFY_FAIL_MODEL; @@ -72,6 +76,10 @@ private: private slots: void fstRequestComplete(); void ownerRequestComplete(); + void challengeTimeout(); + + signals: + void startChallengeTimer(); }; using MixerAvatarSharedPointer = std::shared_ptr; From 9c06fa08fd920df09b5419c38130cbd20e3feaa7 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 18 Oct 2019 14:09:18 -0700 Subject: [PATCH 113/203] trying to fix openssl lib issue --- launchers/qt/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 3b3374f152..38284b4c3a 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -66,7 +66,12 @@ if (WIN32) list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake) set(SSL_DIR ${SOURCE_DIR}/ssl) + et(OPENSSL_ROOT_DIR ${SSL_DIR}) message("SSL dir is ${SSL_DIR}") + set(OPENSSL_USE_STATIC_LIBS TRUE) + find_package(OpenSSL REQUIRED) + + message("-- Found OpenSSL Libs ${OPENSSL_LIBRARIES}") endif () @@ -220,8 +225,9 @@ if (WIN32) target_link_libraries(${PROJECT_NAME} wsock32 ws2_32 Winmm version imm32 dwmapi Crypt32 Iphlpapi - "${SSL_DIR}/lib/libeay32.lib" - "${SSL_DIR}/lib/ssleay32.lib" + #"${SSL_DIR}/lib/libeay32.lib" + #"${SSL_DIR}/lib/ssleay32.lib" + ${OPENSSL_LIBRARIES} "${_qt5Core_install_prefix}/qml/QtQuick.2/qtquick2plugin.lib" "${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/qtquickcontrols2plugin.lib" "${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib") From 62e5e152bae3961e764ffd005ba751df2ddaad44 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 18 Oct 2019 14:13:45 -0700 Subject: [PATCH 114/203] fixing last commit typo --- launchers/qt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 38284b4c3a..8155400f45 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -66,7 +66,7 @@ if (WIN32) list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake) set(SSL_DIR ${SOURCE_DIR}/ssl) - et(OPENSSL_ROOT_DIR ${SSL_DIR}) + set(OPENSSL_ROOT_DIR ${SSL_DIR}) message("SSL dir is ${SSL_DIR}") set(OPENSSL_USE_STATIC_LIBS TRUE) find_package(OpenSSL REQUIRED) From f573f8e03bc6bda51d47aff484a9ab8d12452e20 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 18 Oct 2019 14:37:04 -0700 Subject: [PATCH 115/203] Re-use challenge nonce until challenge succeeds --- assignment-client/src/avatars/MixerAvatar.cpp | 22 +++++++++++++------ assignment-client/src/avatars/MixerAvatar.h | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index 01f3454292..9ed1d6a9b6 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -52,7 +52,7 @@ void MixerAvatar::challengeTimeout() { } else { _certifyFailed = true; _needsIdentityUpdate = true; - qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID(); + qCWarning(avatars) << "ALERT: Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID(); } break; @@ -64,6 +64,7 @@ void MixerAvatar::challengeTimeout() { default: qCDebug(avatars) << "Ignoring timeout of avatar challenge"; + break; } } @@ -76,7 +77,7 @@ void MixerAvatar::fetchAvatarFST() { _pendingEvent = false; QUrl avatarURL = getSkeletonModelURL(); - if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") { + if ((avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") && !isCertifyFailed()) { // Not network FST. return; } @@ -340,18 +341,24 @@ void MixerAvatar::processCertifyEvents() { void MixerAvatar::sendOwnerChallenge() { auto nodeList = DependencyManager::get(); QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8(); - QByteArray nonce = QUuid::createUuid().toByteArray(); + if (_challengeNonce.isEmpty()) { + _challengeNonce = QUuid::createUuid().toByteArray(); + QCryptographicHash nonceHash(QCryptographicHash::Sha256); + nonceHash.addData(_challengeNonce); + _challengeNonceHash = nonceHash.result(); + + } auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - 2 * sizeof(int) + nonce.length() + avatarID.length(), true); + 2 * sizeof(int) + _challengeNonce.length() + avatarID.length(), true); challengeOwnershipPacket->writePrimitive(avatarID.length()); - challengeOwnershipPacket->writePrimitive(nonce.length()); + challengeOwnershipPacket->writePrimitive(_challengeNonce.length()); challengeOwnershipPacket->write(avatarID); - challengeOwnershipPacket->write(nonce); + challengeOwnershipPacket->write(_challengeNonce); nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) ); QCryptographicHash nonceHash(QCryptographicHash::Sha256); - nonceHash.addData(nonce); + nonceHash.addData(_challengeNonce); _challengeNonceHash = nonceHash.result(); _pendingEvent = false; @@ -390,6 +397,7 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) { emit startChallengeTimer(); } else { qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID(); + _challengeNonce.clear(); } } else { diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index bb6b859984..ec24d4e6bc 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -58,6 +58,7 @@ private: QString _certificateIdFromFST; QString _dynamicMarketResponse; QString _ownerPublicKey; + QByteArray _challengeNonce; QByteArray _challengeNonceHash; QTimer _challengeTimer; static constexpr int NUM_CHALLENGES_BEFORE_FAIL = 1; From c7e1fdaeacac2d5ea2f4099a7f150f8cd3577f1d Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 15 Oct 2019 08:55:53 -0700 Subject: [PATCH 116/203] adding hmd audio device info to deviceslist changing locations one more merge working on the threading issue changing offices, working on qml simple qml filtering fixed spacing problem when item is removed --- .../simplifiedUI/settingsApp/audio/Audio.qml | 12 +++--- .../hifi/simplifiedUI/settingsApp/vr/VR.qml | 13 +++--- interface/src/scripting/AudioDevices.cpp | 37 +++++++++++++--- interface/src/scripting/AudioDevices.h | 1 + libraries/audio-client/CMakeLists.txt | 2 +- libraries/audio-client/src/AudioClient.cpp | 42 +++++++++++++++---- .../audio-client/src/HifiAudioDeviceInfo.cpp | 1 + .../audio-client/src/HifiAudioDeviceInfo.h | 19 +++++++-- .../plugins/src/plugins/PluginManager.cpp | 16 ++++++- libraries/plugins/src/plugins/PluginManager.h | 2 +- 10 files changed, 111 insertions(+), 34 deletions(-) diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml index 108016ef8c..345c124725 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml @@ -258,13 +258,12 @@ Flickable { Layout.preferredHeight: contentItem.height Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false - spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.input delegate: Item { - width: parent.width - height: inputDeviceCheckbox.height - + width: parent.width + height: model.type != "hmd" ? inputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0 + visible: model.type != "hmd" SimplifiedControls.RadioButton { id: inputDeviceCheckbox anchors.left: parent.left @@ -354,13 +353,12 @@ Flickable { Layout.preferredHeight: contentItem.height Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false - spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.output delegate: Item { width: parent.width - height: outputDeviceCheckbox.height - + height: model.type != "hmd" ? outputDeviceCheckbox.height +simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0 + visible: model.type != "hmd" SimplifiedControls.RadioButton { id: outputDeviceCheckbox anchors.left: parent.left diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml index 420ee11a05..cc64f8bd9f 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml @@ -259,13 +259,13 @@ Flickable { Layout.preferredHeight: contentItem.height Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false - spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.input delegate: Item { - width: parent.width - height: inputDeviceCheckbox.height - + width: parent.width + height: model.type != "desktop" ? inputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0 + visible: model.type != "desktop" + SimplifiedControls.RadioButton { id: inputDeviceCheckbox anchors.left: parent.left @@ -355,13 +355,12 @@ Flickable { Layout.preferredHeight: contentItem.height Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false - spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.output delegate: Item { width: parent.width - height: outputDeviceCheckbox.height - + height: model.type != "desktop" ? outputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0 + visible: model.type != "desktop" SimplifiedControls.RadioButton { id: outputDeviceCheckbox anchors.left: parent.left diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 8cc45a3bf5..f4cae04a5e 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -44,7 +44,8 @@ enum AudioDeviceRole { SelectedDesktopRole, SelectedHMDRole, PeakRole, - InfoRole + InfoRole, + TypeRole }; QHash AudioDeviceList::_roles { @@ -52,7 +53,8 @@ QHash AudioDeviceList::_roles { { SelectedDesktopRole, "selectedDesktop" }, { SelectedHMDRole, "selectedHMD" }, { PeakRole, "peak" }, - { InfoRole, "info" } + { InfoRole, "info" }, + { TypeRole, "type"} }; static QString getTargetDevice(bool hmd, QAudio::Mode mode) { @@ -144,6 +146,8 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const { return _devices.at(index.row())->selectedHMD; } else if (role == InfoRole) { return QVariant::fromValue(_devices.at(index.row())->info); + } else if (role == TypeRole) { + return _devices.at(index.row())->type; } else { return QVariant(); } @@ -281,10 +285,18 @@ void AudioDeviceList::onDevicesChanged(const QList& devices device.info = deviceInfo; if (deviceInfo.isDefault()) { - if (deviceInfo.getMode() == QAudio::AudioInput) { - device.display = "Computer's default microphone (recommended)"; - } else { - device.display = "Computer's default audio (recommended)"; + if (deviceInfo.getDeviceType() == HifiAudioDeviceInfo::desktop) { + if (deviceInfo.getMode() == QAudio::AudioInput) { + device.display = "Computer's default microphone (recommended)"; + } else { + device.display = "Computer's default audio (recommended)"; + } + } else if (deviceInfo.getDeviceType() == HifiAudioDeviceInfo::hmd) { + if (deviceInfo.getMode() == QAudio::AudioInput) { + device.display = "Headset's default mic (recommended)"; + } else { + device.display = "Headset's default audio (recommended)"; + } } } else { device.display = device.info.deviceName() @@ -292,6 +304,19 @@ void AudioDeviceList::onDevicesChanged(const QList& devices .remove("Device") .replace(" )", ")"); } + + switch (deviceInfo.getDeviceType()) { + case HifiAudioDeviceInfo::hmd: + device.type = "hmd"; + break; + case HifiAudioDeviceInfo::desktop: + device.type = "desktop"; + break; + case HifiAudioDeviceInfo::both: + device.type = "both"; + break; + } + for (bool isHMD : {false, true}) { HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; diff --git a/interface/src/scripting/AudioDevices.h b/interface/src/scripting/AudioDevices.h index 13d97d53dd..3dc1949f80 100644 --- a/interface/src/scripting/AudioDevices.h +++ b/interface/src/scripting/AudioDevices.h @@ -29,6 +29,7 @@ public: QString display; bool selectedDesktop { false }; bool selectedHMD { false }; + QString type; }; class AudioDeviceList : public QAbstractListModel { diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 6b88292dd4..03279b08f6 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -6,7 +6,7 @@ setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) - +include_hifi_library_headers(gpu) if (ANDROID) else () target_webrtc() diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d29045c99b..344f52fc5a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -47,7 +47,7 @@ #include #include #include - +#include #include "AudioClientLogging.h" #include "AudioLogging.h" #include "AudioHelpers.h" @@ -81,6 +81,21 @@ Mutex _recordMutex; HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode); +#include +static QString getHmdAudioDeviceName(QAudio::Mode mode) { + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { + if (displayPlugin && displayPlugin->isHmd()) { + if (mode == QAudio::AudioInput) { + return displayPlugin->getPreferredAudioInDevice(); + } else { + return displayPlugin->getPreferredAudioOutDevice(); + } + break; + } + } + return QString(); +} + // thread-safe QList getAvailableDevices(QAudio::Mode mode) { // NOTE: availableDevices() clobbers the Qt internal device list @@ -88,13 +103,26 @@ QList getAvailableDevices(QAudio::Mode mode) { auto devices = QAudioDeviceInfo::availableDevices(mode); QList newDevices; - for (auto& device : devices) { newDevices.push_back(HifiAudioDeviceInfo(device, false, mode)); } newDevices.push_front(defaultAudioDeviceForMode(mode)); - + + QString hmdDeviceName = getHmdAudioDeviceName(mode); + if (!hmdDeviceName.isNull() && !hmdDeviceName.isEmpty()) { + HifiAudioDeviceInfo hmdDevice; + foreach(auto device, newDevices) { + if (device.getDevice().deviceName() == hmdDeviceName) { + hmdDevice = HifiAudioDeviceInfo(device.getDevice(), true, mode, HifiAudioDeviceInfo::hmd); + break; + } + } + + if (!hmdDevice.getDevice().isNull()) { + newDevices.push_front(hmdDevice); + } + } return newDevices; } @@ -477,7 +505,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { // find a device in the list that matches the name we have and return it foreach(QAudioDeviceInfo audioDevice, devices){ if (audioDevice.deviceName() == CFStringGetCStringPtr(deviceName, kCFStringEncodingMacRoman)) { - return HifiAudioDeviceInfo(audioDevice, true, mode); + return HifiAudioDeviceInfo(audioDevice, true, mode, HifiAudioDeviceInfo::desktop); } } } @@ -532,7 +560,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { HifiAudioDeviceInfo foundDevice; foreach(QAudioDeviceInfo audioDevice, devices) { if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { - foundDevice=HifiAudioDeviceInfo(audioDevice,true,mode); + foundDevice=HifiAudioDeviceInfo(audioDevice,true,mode, HifiAudioDeviceInfo::desktop); break; } } @@ -558,8 +586,8 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { } #endif // fallback for failed lookup is the default device - return (mode == QAudio::AudioInput) ? HifiAudioDeviceInfo(QAudioDeviceInfo::defaultInputDevice(), true,mode) : - HifiAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice(), true, mode); + return (mode == QAudio::AudioInput) ? HifiAudioDeviceInfo(QAudioDeviceInfo::defaultInputDevice(), true,mode, HifiAudioDeviceInfo::desktop) : + HifiAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice(), true, mode, HifiAudioDeviceInfo::desktop); } bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) { diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp index 6e2d5fbe92..df18a2376e 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp @@ -22,6 +22,7 @@ HifiAudioDeviceInfo& HifiAudioDeviceInfo::operator=(const HifiAudioDeviceInfo& o _audioDeviceInfo = other.getDevice(); _mode = other.getMode(); _isDefault = other.isDefault(); + _deviceType = other.getDeviceType(); return *this; } diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.h b/libraries/audio-client/src/HifiAudioDeviceInfo.h index 99520f1643..fadbbb6917 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.h +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.h @@ -23,19 +23,28 @@ class HifiAudioDeviceInfo : public QObject { Q_OBJECT public: + enum deviceType { + desktop, + hmd, + both + }; + HifiAudioDeviceInfo() : QObject() {} HifiAudioDeviceInfo(const HifiAudioDeviceInfo &deviceInfo) : QObject(){ _audioDeviceInfo = deviceInfo.getDevice(); _mode = deviceInfo.getMode(); _isDefault = deviceInfo.isDefault(); + _deviceType = deviceInfo.getDeviceType(); } - HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode) : + + HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode, deviceType devType=both) : _audioDeviceInfo(deviceInfo), _isDefault(isDefault), - _mode(mode){ + _mode(mode), + _deviceType(devType){ } - + void setMode(QAudio::Mode mode) { _mode = mode; } void setIsDefault() { _isDefault = true; } void setDevice(QAudioDeviceInfo devInfo); @@ -52,15 +61,17 @@ public: QAudioDeviceInfo getDevice() const { return _audioDeviceInfo; } bool isDefault() const { return _isDefault; } QAudio::Mode getMode() const { return _mode; } - + deviceType getDeviceType() const { return _deviceType; } HifiAudioDeviceInfo& operator=(const HifiAudioDeviceInfo& other); bool operator==(const HifiAudioDeviceInfo& rhs) const; bool operator!=(const HifiAudioDeviceInfo& rhs) const; + private: QAudioDeviceInfo _audioDeviceInfo; bool _isDefault { false }; QAudio::Mode _mode { QAudio::AudioInput }; + deviceType _deviceType{ both }; public: static const QString DEFAULT_DEVICE_NAME; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 8f1184904e..1ecc4f1d45 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -221,7 +221,21 @@ const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() { return oculusPlatformPlugin; } -const DisplayPluginList& PluginManager::getDisplayPlugins() { +#include +#include + + +DisplayPluginList PluginManager::getAllDisplayPlugins() { + if (thread() != QThread::currentThread()) { + DisplayPluginList list; + QMetaObject::invokeMethod(this, "getAllDisplayPlugins", Qt::BlockingQueuedConnection, Q_RETURN_ARG(DisplayPluginList, list)); + return list; + } else { + return _displayPlugins; + } +} + + const DisplayPluginList& PluginManager::getDisplayPlugins() { static std::once_flag once; static auto deviceAddedCallback = [](QString deviceName) { qCDebug(plugins) << "Added device: " << deviceName; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index eb377a2c8e..5ec82ea743 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -51,7 +51,7 @@ public: using PluginFilter = std::function; void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; } - + Q_INVOKABLE DisplayPluginList getAllDisplayPlugins(); signals: void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices); From 421cff1cb21a7f75fe79232780adbc8a6e5f9b7c Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 18 Oct 2019 15:16:37 -0700 Subject: [PATCH 117/203] fixed naming to match standards --- interface/src/scripting/AudioDevices.cpp | 18 +++++++++--------- .../audio-client/src/HifiAudioDeviceInfo.cpp | 1 - .../audio-client/src/HifiAudioDeviceInfo.h | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index f4cae04a5e..7cabd49e93 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -306,15 +306,15 @@ void AudioDeviceList::onDevicesChanged(const QList& devices } switch (deviceInfo.getDeviceType()) { - case HifiAudioDeviceInfo::hmd: - device.type = "hmd"; - break; - case HifiAudioDeviceInfo::desktop: - device.type = "desktop"; - break; - case HifiAudioDeviceInfo::both: - device.type = "both"; - break; + case HifiAudioDeviceInfo::hmd: + device.type = "hmd"; + break; + case HifiAudioDeviceInfo::desktop: + device.type = "desktop"; + break; + case HifiAudioDeviceInfo::both: + device.type = "both"; + break; } diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp index df18a2376e..5cbb15ff73 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp @@ -26,7 +26,6 @@ HifiAudioDeviceInfo& HifiAudioDeviceInfo::operator=(const HifiAudioDeviceInfo& o return *this; } - bool HifiAudioDeviceInfo::operator==(const HifiAudioDeviceInfo& rhs) const { //Does the QAudioDeviceinfo match as well as is this the default device or return getDevice() == rhs.getDevice() && isDefault() == rhs.isDefault(); diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.h b/libraries/audio-client/src/HifiAudioDeviceInfo.h index fadbbb6917..9b77f77c9e 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.h +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.h @@ -23,7 +23,7 @@ class HifiAudioDeviceInfo : public QObject { Q_OBJECT public: - enum deviceType { + enum DeviceType { desktop, hmd, both @@ -38,7 +38,7 @@ public: } - HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode, deviceType devType=both) : + HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode, DeviceType devType=both) : _audioDeviceInfo(deviceInfo), _isDefault(isDefault), _mode(mode), @@ -61,7 +61,7 @@ public: QAudioDeviceInfo getDevice() const { return _audioDeviceInfo; } bool isDefault() const { return _isDefault; } QAudio::Mode getMode() const { return _mode; } - deviceType getDeviceType() const { return _deviceType; } + DeviceType getDeviceType() const { return _deviceType; } HifiAudioDeviceInfo& operator=(const HifiAudioDeviceInfo& other); bool operator==(const HifiAudioDeviceInfo& rhs) const; bool operator!=(const HifiAudioDeviceInfo& rhs) const; @@ -71,7 +71,7 @@ private: QAudioDeviceInfo _audioDeviceInfo; bool _isDefault { false }; QAudio::Mode _mode { QAudio::AudioInput }; - deviceType _deviceType{ both }; + DeviceType _deviceType{ both }; public: static const QString DEFAULT_DEVICE_NAME; From 92f1b59cc8e555e0594cad64eb5151a3378f888e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Oct 2019 16:30:32 -0700 Subject: [PATCH 118/203] Full legacy API and FST support --- .../src/avatars/ScriptableAvatar.cpp | 12 ----- .../src/avatars/ScriptableAvatar.h | 7 --- libraries/avatars/src/HeadData.cpp | 3 ++ libraries/avatars/src/HeadData.h | 6 +-- .../controllers/src/controllers/Actions.cpp | 2 +- .../controllers/src/controllers/Actions.h | 2 +- .../src/controllers/StandardController.cpp | 2 +- .../src/controllers/StandardControls.h | 2 +- libraries/fbx/src/FSTReader.cpp | 50 +++++++++++++++++-- libraries/render-utils/src/Model.cpp | 2 + libraries/shared/src/BlendshapeConstants.h | 6 +-- 11 files changed, 60 insertions(+), 34 deletions(-) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 044ab86942..54eb499be1 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -279,18 +279,6 @@ void ScriptableAvatar::setJointMappingsFromNetworkReply() { networkReply->deleteLater(); } -void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { - _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); -} - -void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { - _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); -} - -void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { - _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); -} - AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const { // DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive! // Avoid calling this method if possible. diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index fc796b418f..f2f5a1e6f4 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -153,13 +153,6 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; - void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); - bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } - void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); - bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } - void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); - bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } - /**jsdoc * Gets details of all avatar entities. *

Warning: Potentially an expensive call. Do not use if possible.

diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index f37b88c135..ff73677bb8 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -27,6 +27,9 @@ HeadData::HeadData(AvatarData* owningAvatar) : _basePitch(0.0f), _baseRoll(0.0f), _lookAtPosition(0.0f, 0.0f, 0.0f), + _blendshapeCoefficients((int)Blendshapes::BlendshapeCount, 0.0f), + _transientBlendshapeCoefficients((int)Blendshapes::BlendshapeCount, 0.0f), + _summedBlendshapeCoefficients((int)Blendshapes::BlendshapeCount, 0.0f), _owningAvatar(owningAvatar) { _userProceduralAnimationFlags.assign((size_t)ProceduralAnimaitonTypeCount, true); diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 9652792512..218ffceaa1 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -121,9 +121,9 @@ protected: float _averageLoudness { 0.0f }; float _browAudioLift { 0.0f }; - QVector _blendshapeCoefficients { (int)Blendshapes::BlendshapeCount, 0.0f }; - QVector _transientBlendshapeCoefficients { (int)Blendshapes::BlendshapeCount, 0.0f }; - QVector _summedBlendshapeCoefficients { (int)Blendshapes::BlendshapeCount, 0.0f }; + QVector _blendshapeCoefficients; + QVector _transientBlendshapeCoefficients; + QVector _summedBlendshapeCoefficients; QMap _blendshapeLookupMap; AvatarData* _owningAvatar; diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 198c342b1d..6dd9a47395 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -350,7 +350,7 @@ namespace controller { makePosePair(Action::LEFT_EYE, "LeftEye"), makePosePair(Action::RIGHT_EYE, "RightEye"), - // AJT: blendshapes + // blendshapes makeAxisPair(Action::EYEBLINK_L, "EyeBlink_L"), makeAxisPair(Action::EYEBLINK_R, "EyeBlink_R"), makeAxisPair(Action::EYESQUINT_L, "EyeSquint_L"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 1a5c73a1d8..86821b4633 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -184,7 +184,7 @@ enum class Action { LEFT_EYE, RIGHT_EYE, - // AJT: blendshapes + // blendshapes EYEBLINK_L, EYEBLINK_R, EYESQUINT_L, diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index 14697b26d2..168604ee45 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -356,7 +356,7 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(LEFT_EYE, "LeftEye"), makePair(RIGHT_EYE, "RightEye"), - // AJT: blendshapes + // blendshapes makePair(EYEBLINK_L, "EyeBlink_L"), makePair(EYEBLINK_R, "EyeBlink_R"), makePair(EYESQUINT_L, "EyeSquint_L"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 55bcf0e36a..f99072af7c 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -91,7 +91,7 @@ namespace controller { LEFT_GRIP, RIGHT_GRIP, - // AJT: blendshapes + // blendshapes EYEBLINK_L, EYEBLINK_R, EYESQUINT_L, diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 41b660f722..2835151bfe 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -21,7 +21,7 @@ QVariantHash FSTReader::parseMapping(QIODevice* device) { QVariantHash properties; - + QByteArray line; while (!(line = device->readLine()).isEmpty()) { if ((line = line.trimmed()).startsWith('#')) { @@ -34,12 +34,10 @@ QVariantHash FSTReader::parseMapping(QIODevice* device) { QByteArray name = sections.at(0).trimmed(); if (sections.size() == 2) { properties.insertMulti(name, sections.at(1).trimmed()); - } else if (sections.size() == 3) { QVariantHash heading = properties.value(name).toHash(); heading.insertMulti(sections.at(1).trimmed(), sections.at(2).trimmed()); properties.insert(name, heading); - } else if (sections.size() >= 4) { QVariantHash heading = properties.value(name).toHash(); QVariantList contents; @@ -50,14 +48,56 @@ QVariantHash FSTReader::parseMapping(QIODevice* device) { properties.insert(name, heading); } } - + return properties; } +static void removeBlendshape(QVariantHash& bs, const QString& key) { + if (bs.contains(key)) { + bs.remove(key); + } +} + +static void splitBlendshapes(QVariantHash& bs, const QString& key, const QString& leftKey, const QString& rightKey) { + if (bs.contains(key) && !(bs.contains(leftKey) || bs.contains(rightKey))) { + // key has been split into leftKey and rightKey blendshapes + QVariantList origShapes = bs.values(key); + QVariantList halfShapes; + for (int i = 0; i < origShapes.size(); i++) { + QVariantList origShape = origShapes[i].toList(); + QVariantList halfShape; + halfShape.append(origShape[0]); + halfShape.append(QVariant(0.5f * origShape[1].toFloat())); + bs.insertMulti(leftKey, halfShape); + bs.insertMulti(rightKey, halfShape); + } + } +} + +// convert legacy blendshapes to arkit blendshapes +static void fixUpLegacyBlendshapes(QVariantHash& properties) { + QVariantHash bs = properties.value("bs").toHash(); + + // These blendshapes have no ARKit equivalent, so we remove them. + removeBlendshape(bs, "JawChew"); + removeBlendshape(bs, "ChinLowerRaise"); + removeBlendshape(bs, "ChinUpperRaise"); + + // These blendshapes are split in ARKit, we replace them with their left and right sides with a weight of 1/2. + splitBlendshapes(bs, "LipsUpperUp", "MouthUpperUp_L", "MouthUpperUp_R"); + splitBlendshapes(bs, "LipsLowerDown", "MouthLowerDown_L", "MouthLowerDown_R"); + splitBlendshapes(bs, "Sneer", "NoseSneer_L", "NoseSneer_R"); + + // re-insert new mutated bs hash into mapping properties. + properties.insert("bs", bs); +} + QVariantHash FSTReader::readMapping(const QByteArray& data) { QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); - return FSTReader::parseMapping(&buffer); + QVariantHash mapping = FSTReader::parseMapping(&buffer); + fixUpLegacyBlendshapes(mapping); + return mapping; } void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 74cf1ffa39..a7e098e1b7 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -39,6 +39,8 @@ #include "RenderUtilsLogging.h" #include +#include + using namespace std; int nakedModelPointerTypeId = qRegisterMetaType(); diff --git a/libraries/shared/src/BlendshapeConstants.h b/libraries/shared/src/BlendshapeConstants.h index 63e99f465f..0f934b2056 100644 --- a/libraries/shared/src/BlendshapeConstants.h +++ b/libraries/shared/src/BlendshapeConstants.h @@ -109,10 +109,10 @@ enum class LegacyBlendshpaes : int { // Legacy shapes // * JawChew (not in ARKit) -// * MouthUpperUp (split in ARKit) -// * MouthLowerDown (split in ARKit) +// * LipsUpperUp (split in ARKit) +// * LipsLowerDown (split in ARKit) // * Sneer (split in ARKit) // * ChinLowerRaise (not in ARKit) -// * ChinUpperRase (not in ARKit) +// * ChinUpperRaise (not in ARKit) #endif // hifi_BlendshapeConstants_h From ec7c8ce13c096705da241c97a281e622f397fa06 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 18 Oct 2019 16:50:36 -0700 Subject: [PATCH 119/203] updated default changes check to account for multiple devicetypes --- libraries/audio-client/src/AudioClient.cpp | 12 ++++++++++-- libraries/audio-client/src/AudioClient.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 344f52fc5a..418a45e5e9 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -138,8 +138,8 @@ void AudioClient::checkDevices() { auto inputDevices = getAvailableDevices(QAudio::AudioInput); auto outputDevices = getAvailableDevices(QAudio::AudioOutput); - QMetaObject::invokeMethod(this, "changeDefault", Q_ARG(HifiAudioDeviceInfo, inputDevices.first()), Q_ARG(QAudio::Mode, QAudio::AudioInput)); - QMetaObject::invokeMethod(this, "changeDefault", Q_ARG(HifiAudioDeviceInfo, outputDevices.first()), Q_ARG(QAudio::Mode, QAudio::AudioOutput)); + checkDefaultChanges(inputDevices); + checkDefaultChanges(outputDevices); Lock lock(_deviceMutex); if (inputDevices != _inputDevices) { @@ -153,6 +153,14 @@ void AudioClient::checkDevices() { } } +void AudioClient::checkDefaultChanges(QList& devices) { + foreach(auto device, devices) { + if (device.isDefault()) { + QMetaObject::invokeMethod(this, "changeDefault", Q_ARG(HifiAudioDeviceInfo, device), Q_ARG(QAudio::Mode, device.getMode())); + } + } +} + HifiAudioDeviceInfo AudioClient::getActiveAudioDevice(QAudio::Mode mode) const { Lock lock(_deviceMutex); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index b4ddb1018e..301b4d0fa5 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -238,6 +238,7 @@ public slots: bool shouldLoopbackInjectors() override { return _shouldEchoToServer; } Q_INVOKABLE void changeDefault(HifiAudioDeviceInfo newDefault, QAudio::Mode mode); + void checkDefaultChanges(QList& devices); // calling with a null QAudioDevice will use the system default bool switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo = HifiAudioDeviceInfo()); From 0acec03a3e3d631dfbe858b5bd38784f6a053e92 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 21 Oct 2019 13:58:08 -0700 Subject: [PATCH 120/203] making ui changes --- launchers/qt/resources/qml/Download.qml | 4 ++-- .../resources/qml/HFBase/CreateAccountBase.qml | 7 +++---- launchers/qt/resources/qml/HFBase/Error.qml | 9 +-------- .../qt/resources/qml/HFBase/LoginBase.qml | 18 +++++++++--------- launchers/qt/src/LauncherState.cpp | 5 +++-- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index 00542d02d0..094b9cc961 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -42,7 +42,7 @@ Item { HFTextHeader { id: firstText - text: "Setup will take a moment" + text: "Set up will take a moment" anchors { top: logo.bottom @@ -54,7 +54,7 @@ Item { HFTextRegular { id: secondText - text: "We're getting everything setup for you." + text: "We're getting everything set up for you." anchors { top: firstText.bottom diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 200f7ad0be..4a47ea4253 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -8,7 +8,7 @@ import HQLauncher 1.0 Item { id: root anchors.centerIn: parent - property string titleText: "Sign-in and pick a password" + property string titleText: "Sign in and pick a password" property string usernamePlaceholder: "Username" property string passwordPlaceholder: "Set a password (must be at least 6 characters)" property int marginLeft: root.width * 0.15 @@ -30,7 +30,7 @@ Item { width: 481 lineHeight: 35 lineHeightMode: Text.FixedHeight - text: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh." + text: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh" anchors { top: root.top topMargin: 29 @@ -43,7 +43,7 @@ Item { id: instruction width: 425 - text: "Use the email address you applied for access with." + text: "Use the email address you applied for access with" visible: LauncherState.lastSignupErrorMessage.length == 0 anchors { @@ -214,7 +214,6 @@ Item { bottom: root.bottom bottomMargin: 46 right: displayName.right - rightMargin: 30 } } diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index ad57f242f4..98eb1d17ac 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -33,10 +33,6 @@ Item { HFTextHeader { id: header - - width: 87 - height: 31 - text: "Uh oh." anchors { @@ -49,10 +45,7 @@ Item { HFTextRegular { id: description - text: "We seem to have a problem.\nPlease restart Launcher" - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + text: "We seem to have a problem.\n Please restart Launcher." anchors { top: header.bottom diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 60763308bb..fd24b63398 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -21,7 +21,7 @@ Item { } Item { - width: 383 + width: 430 height: root.height @@ -32,9 +32,8 @@ Item { HFTextHeader { id: title - - font.bold: true - + lineHeight: 35 + lineHeightMode: Text.FixedHeight text: "Please Log in" anchors { @@ -75,6 +74,7 @@ Item { id: username enabled: root.enabled + width: 430 text: LauncherState.lastUsedUsername placeholderText: "Username" @@ -91,7 +91,7 @@ Item { HFTextField { id: password - + width: 430 enabled: root.enabled placeholderText: "Password" @@ -125,7 +125,7 @@ Item { HFTextField { id: displayName - + width: 430 enabled: root.enabled placeholderText: "Display name" @@ -142,7 +142,7 @@ Item { HFButton { id: button - width: 110 + width: 134 enabled: root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0 @@ -161,7 +161,7 @@ Item { Text { id: createAccountLink - text: "Create New Account" + text: "Sign up" font.family: "Graphik" font.pixelSize: 14 color: "#009EE0" @@ -191,8 +191,8 @@ Item { right: parent.right } } - } + } Component.onCompleted: { root.parent.setBuildInfoState("right"); } diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 42d13bfa58..eccd02eb19 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -32,7 +32,7 @@ #endif //#define BREAK_ON_ERROR -//#define DEBUG_UI +#define DEBUG_UI const QString configHomeLocationKey { "homeLocation" }; const QString configLastLoginKey { "lastLogin" }; @@ -286,6 +286,7 @@ void LauncherState::getCurrentClientVersion() { setApplicationState(ApplicationState::WaitingForLogin); } } + //setApplicationState(ApplicationState::WaitingForLogin); } @@ -350,7 +351,7 @@ void LauncherState::signup(QString email, QString username, QString password, QS setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::NoSuchEmail) { - setLastSignupErrorMessage("That email isn't setup yet. Request Access."); + setLastSignupErrorMessage("That email isn't setup yet. Request access."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err != SignupRequest::Error::None) { From a4fcb2c39e1fcc93df6c0d3883929580704b1a10 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 21 Oct 2019 14:31:15 -0700 Subject: [PATCH 121/203] Add eyesLookAtTarget set/get API methods --- interface/src/Application.cpp | 6 +++--- interface/src/Application.h | 2 +- interface/src/avatar/MyAvatar.cpp | 26 +++++++++++++++++++++++--- interface/src/avatar/MyAvatar.h | 23 +++++++++++++++++++++-- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4766353eba..7c0e822bed 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5814,14 +5814,14 @@ void Application::pushPostUpdateLambda(void* key, const std::function& f // to everyone. // The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition(). // Note that it is called BEFORE we update position or joints based on sensors, etc. -void Application::updateMyAvatarLookAtPosition() { +void Application::updateMyAvatarLookAtPosition(float deltaTime) { PerformanceTimer perfTimer("lookAt"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); auto myAvatar = getMyAvatar(); FaceTracker* faceTracker = getActiveFaceTracker(); - myAvatar->updateLookAtPosition(faceTracker, _myCamera); + myAvatar->updateEyesLookAtPosition(faceTracker, _myCamera, deltaTime); } void Application::updateThreads(float deltaTime) { @@ -6605,7 +6605,7 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation, "MyAvatar"); PerformanceTimer perfTimer("MyAvatar"); - qApp->updateMyAvatarLookAtPosition(); + qApp->updateMyAvatarLookAtPosition(deltaTime); avatarManager->updateMyAvatar(deltaTime); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index af2348d1e9..2bb6c5ddb4 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -298,7 +298,7 @@ public: virtual void pushPostUpdateLambda(void* key, const std::function& func) override; - void updateMyAvatarLookAtPosition(); + void updateMyAvatarLookAtPosition(float deltaTime); float getGameLoopRate() const { return _gameLoopCounter.rate(); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2bc0ae32d4..e152902b47 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2284,7 +2284,9 @@ void MyAvatar::updateLookAtTargetAvatar() { AvatarHash hash = DependencyManager::get()->getHashCopy(); // determine what the best look at target for my avatar should be. - computeMyLookAtTarget(hash); + if (!_scriptControlsEyesLookAt) { + computeMyLookAtTarget(hash); + } // snap look at position for avatars that are looking at me. snapOtherAvatarLookAtTargetsToMe(hash); @@ -6617,7 +6619,7 @@ bool MyAvatar::getIsJointOverridden(int jointIndex) const { return _skeletonModel->getIsJointOverridden(jointIndex); } -void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) { +void MyAvatar::updateEyesLookAtPosition(FaceTracker* faceTracker, Camera& myCamera, float deltaTime) { updateLookAtTargetAvatar(); @@ -6647,6 +6649,13 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) } else { lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f; } + } else if (_scriptControlsEyesLookAt) { + if (_scriptEyesControlTimer < MAX_LOOK_AT_TIME_SCRIPT_CONTROL) { + _scriptEyesControlTimer += deltaTime; + lookAtSpot = _eyesLookAtTarget; + } else { + _scriptControlsEyesLookAt = false; + } } else { controller::Pose leftEyePose = getControllerPoseInAvatarFrame(controller::Action::LEFT_EYE); controller::Pose rightEyePose = getControllerPoseInAvatarFrame(controller::Action::RIGHT_EYE); @@ -6731,7 +6740,7 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) } } } - + _eyesLookAtTarget = lookAtSpot; getHead()->setLookAtPosition(lookAtSpot); } @@ -6814,6 +6823,17 @@ void MyAvatar::setHeadLookAt(const glm::vec3& lookAtTarget) { _lookAtScriptTarget = lookAtTarget; } +void MyAvatar::setEyesLookAt(const glm::vec3& lookAtTarget) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "setEyesLookAt", + Q_ARG(const glm::vec3&, lookAtTarget)); + return; + } + _eyesLookAtTarget = lookAtTarget; + _scriptEyesControlTimer = 0.0f; + _scriptControlsEyesLookAt = true; +} + glm::vec3 MyAvatar::getLookAtPivotPoint() { glm::vec3 avatarUp = getWorldOrientation() * Vectors::UP; glm::vec3 yAxisEyePosition = getWorldPosition() + avatarUp * glm::dot(avatarUp, _skeletonModel->getDefaultEyeModelPosition()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3f91222392..90c4157c3c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1762,10 +1762,26 @@ public: /**jsdoc * Returns the current head look at target point in world coordinates. * @function MyAvatar.getHeadLookAt - * @returns {Vec3} Default position between your avatar's eyes in world coordinates. + * @returns {Vec3} The head's look at target in world coordinates. */ Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; } + /**jsdoc + * Force the avatar's eyes to look to the specified location. + * Once this method is called, API calls will have full control of the eyes for a limited time. + * If this method is not called for two seconds, the engine will regain control of the eyes. + * @function MyAvatar.setEyesLookAt + * @param {Vec3} lookAtTarget - The target point in world coordinates. + */ + Q_INVOKABLE void setEyesLookAt(const glm::vec3& lookAtTarget); + + /**jsdoc + * Returns the current eyes look at target point in world coordinates. + * @function MyAvatar.getEyesLookAt + * @returns {Vec3} The eyes's look at target in world coordinates. + */ + Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget; } + /**jsdoc * Aims the pointing directional blending towards the provided target point. * The "point" reaction should be triggered before using this method. @@ -1903,7 +1919,7 @@ public: bool getFlowActive() const; bool getNetworkGraphActive() const; - void updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera); + void updateEyesLookAtPosition(FaceTracker* faceTracker, Camera& myCamera, float deltaTime); // sets the reaction enabled and triggered parameters of the passed in params // also clears internal reaction triggers @@ -2662,6 +2678,9 @@ private: eyeContactTarget _eyeContactTarget; float _eyeContactTargetTimer { 0.0f }; + glm::vec3 _eyesLookAtTarget; + bool _scriptControlsEyesLookAt{ false }; + float _scriptEyesControlTimer{ 0.0f }; glm::vec3 _trackedHeadPosition; From cb970a74995aa673358825f4cc142ab6f5e47c2a Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 21 Oct 2019 16:02:48 -0700 Subject: [PATCH 122/203] disable debug --- launchers/qt/src/LauncherState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index eccd02eb19..56491f8f88 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -32,7 +32,7 @@ #endif //#define BREAK_ON_ERROR -#define DEBUG_UI +//#define DEBUG_UI const QString configHomeLocationKey { "homeLocation" }; const QString configLastLoginKey { "lastLogin" }; From 03f88f696d1c1d8d31ec5649157b9f887f944850 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 21 Oct 2019 19:37:57 -0700 Subject: [PATCH 123/203] Renamed LipsTogether to MouthClose and bug fixes. --- interface/resources/controllers/standard.json | 2 +- interface/resources/controllers/standard_nomovement.json | 2 +- interface/src/avatar/MyHead.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 4 ++++ libraries/avatars/src/HeadData.cpp | 4 ++++ libraries/avatars/src/HeadData.h | 1 + libraries/controllers/src/controllers/Actions.cpp | 2 +- libraries/controllers/src/controllers/Actions.h | 2 +- libraries/controllers/src/controllers/StandardController.cpp | 2 +- libraries/controllers/src/controllers/StandardControls.h | 2 +- libraries/shared/src/BlendshapeConstants.cpp | 2 +- libraries/shared/src/BlendshapeConstants.h | 4 ++-- 12 files changed, 19 insertions(+), 10 deletions(-) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 8c84039b86..b6cb4e3e27 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -208,7 +208,7 @@ { "from": "Standard.Puff", "to": "Actions.Puff" }, { "from": "Standard.CheekSquint_L", "to": "Actions.CheekSquint_L" }, { "from": "Standard.CheekSquint_R", "to": "Actions.CheekSquint_R" }, - { "from": "Standard.LipsTogether", "to": "Actions.LipsTogether" }, + { "from": "Standard.MouthClose", "to": "Actions.MouthClose" }, { "from": "Standard.MouthUpperUp_L", "to": "Actions.MouthUpperUp_L" }, { "from": "Standard.MouthUpperUp_R", "to": "Actions.MouthUpperUp_R" }, { "from": "Standard.MouthLowerDown_L", "to": "Actions.MouthLowerDown_L" }, diff --git a/interface/resources/controllers/standard_nomovement.json b/interface/resources/controllers/standard_nomovement.json index e311a5fa62..04c0d2f329 100644 --- a/interface/resources/controllers/standard_nomovement.json +++ b/interface/resources/controllers/standard_nomovement.json @@ -103,7 +103,7 @@ { "from": "Standard.Puff", "to": "Actions.Puff" }, { "from": "Standard.CheekSquint_L", "to": "Actions.CheekSquint_L" }, { "from": "Standard.CheekSquint_R", "to": "Actions.CheekSquint_R" }, - { "from": "Standard.LipsTogether", "to": "Actions.LipsTogether" }, + { "from": "Standard.MouthClose", "to": "Actions.MouthClose" }, { "from": "Standard.MouthUpperUp_L", "to": "Actions.MouthUpperUp_L" }, { "from": "Standard.MouthUpperUp_R", "to": "Actions.MouthUpperUp_R" }, { "from": "Standard.MouthLowerDown_L", "to": "Actions.MouthLowerDown_L" }, diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index f92f8b7218..a0e70a3049 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -64,7 +64,7 @@ static controller::Action blendshapeActions[] = { controller::Action::PUFF, controller::Action::CHEEKSQUINT_L, controller::Action::CHEEKSQUINT_R, - controller::Action::LIPSTOGETHER, + controller::Action::MOUTHCLOSE, controller::Action::MOUTHUPPERUP_L, controller::Action::MOUTHUPPERUP_R, controller::Action::MOUTHLOWERDOWN_L, diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c5dcfba1c0..87d6c57d59 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1217,6 +1217,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _keyState = newKeyState; _handState = newHandState; + if (!newHasScriptedBlendshapes && getHasScriptedBlendshapes()) { + // if scripted blendshapes have just been turned off, slam blendshapes back to zero. + _headData->clearBlendshapeCoefficients(); + } _headData->setHasScriptedBlendshapes(newHasScriptedBlendshapes); _headData->setProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation, newHasProceduralEyeMovement); _headData->setProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation, newHasAudioEnabledFaceMovement); diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index ff73677bb8..93fb0190bb 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -83,6 +83,10 @@ int HeadData::getNumSummedBlendshapeCoefficients() const { return maxSize; } +void HeadData::clearBlendshapeCoefficients() { + _blendshapeCoefficients.fill(0.0f, (int)_blendshapeCoefficients.size()); +} + const QVector& HeadData::getSummedBlendshapeCoefficients() { int maxSize = std::max(_blendshapeCoefficients.size(), _transientBlendshapeCoefficients.size()); if (_summedBlendshapeCoefficients.size() != maxSize) { diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 218ffceaa1..083374b32d 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -62,6 +62,7 @@ public: const QVector& getSummedBlendshapeCoefficients(); int getNumSummedBlendshapeCoefficients() const; void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } + void clearBlendshapeCoefficients(); const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } void setLookAtPosition(const glm::vec3& lookAtPosition) { diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 6dd9a47395..36f454b5d0 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -393,7 +393,7 @@ namespace controller { makeAxisPair(Action::PUFF, "Puff"), makeAxisPair(Action::CHEEKSQUINT_L, "CheekSquint_L"), makeAxisPair(Action::CHEEKSQUINT_R, "CheekSquint_R"), - makeAxisPair(Action::LIPSTOGETHER, "LipsTogether"), + makeAxisPair(Action::MOUTHCLOSE, "MouthClose"), makeAxisPair(Action::MOUTHUPPERUP_L, "MouthUpperUp_L"), makeAxisPair(Action::MOUTHUPPERUP_R, "MouthUpperUp_R"), makeAxisPair(Action::MOUTHLOWERDOWN_L, "MouthLowerDown_L"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 86821b4633..5c96923dc3 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -227,7 +227,7 @@ enum class Action { PUFF, CHEEKSQUINT_L, CHEEKSQUINT_R, - LIPSTOGETHER, + MOUTHCLOSE, MOUTHUPPERUP_L, MOUTHUPPERUP_R, MOUTHLOWERDOWN_L, diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index 168604ee45..936f1c391f 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -399,7 +399,7 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(PUFF, "Puff"), makePair(CHEEKSQUINT_L, "CheekSquint_L"), makePair(CHEEKSQUINT_R, "CheekSquint_R"), - makePair(LIPSTOGETHER, "LipsTogether"), + makePair(MOUTHCLOSE, "MouthClose"), makePair(MOUTHUPPERUP_L, "MouthUpperUp_L"), makePair(MOUTHUPPERUP_R, "MouthUpperUp_R"), makePair(MOUTHLOWERDOWN_L, "MouthLowerDown_L"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index f99072af7c..965c095187 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -134,7 +134,7 @@ namespace controller { PUFF, CHEEKSQUINT_L, CHEEKSQUINT_R, - LIPSTOGETHER, + MOUTHCLOSE, MOUTHUPPERUP_L, MOUTHUPPERUP_R, MOUTHLOWERDOWN_L, diff --git a/libraries/shared/src/BlendshapeConstants.cpp b/libraries/shared/src/BlendshapeConstants.cpp index 7564c31944..91b68ed8a9 100644 --- a/libraries/shared/src/BlendshapeConstants.cpp +++ b/libraries/shared/src/BlendshapeConstants.cpp @@ -54,7 +54,7 @@ const char* FACESHIFT_BLENDSHAPES[] = { "Puff", "CheekSquint_L", "CheekSquint_R", - "LipsTogether", + "MouthClose", "MouthUpperUp_L", "MouthUpperUp_R", "MouthLowerDown_L", diff --git a/libraries/shared/src/BlendshapeConstants.h b/libraries/shared/src/BlendshapeConstants.h index 0f934b2056..8db29856c3 100644 --- a/libraries/shared/src/BlendshapeConstants.h +++ b/libraries/shared/src/BlendshapeConstants.h @@ -58,7 +58,7 @@ enum class Blendshapes : int { Puff, CheekSquint_L, CheekSquint_R, - LipsTogether, + MouthClose, MouthUpperUp_L, MouthUpperUp_R, MouthLowerDown_L, @@ -94,7 +94,7 @@ enum class LegacyBlendshpaes : int { }; // NEW in ARKit -// * LipsTogether +// * MouthClose // * MouthUpperUp_L // * MouthUpperUp_R // * MouthLowerDown_L From 378c47d16cb953d465a167ff7fcad910dd87cfdf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Oct 2019 19:18:11 +1300 Subject: [PATCH 124/203] Replace world detail quality float with enum --- .../dialogs/graphics/GraphicsSettings.qml | 14 +--- interface/src/Application.cpp | 1 + interface/src/LODManager.cpp | 78 +++++++------------ interface/src/LODManager.h | 57 ++++++++++---- interface/src/PerformanceManager.cpp | 6 +- interface/src/ui/PreferencesDialog.cpp | 17 ++-- 6 files changed, 88 insertions(+), 85 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml index 8634648d17..3b6502cc98 100644 --- a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml +++ b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml @@ -133,15 +133,12 @@ Item { ListElement { text: "Low World Detail" - worldDetailQualityValue: 0.25 } ListElement { text: "Medium World Detail" - worldDetailQualityValue: 0.5 } ListElement { text: "Full World Detail" - worldDetailQualityValue: 0.75 } } @@ -158,14 +155,7 @@ Item { currentIndex: -1 function refreshWorldDetailDropdown() { - var currentWorldDetailQuality = LODManager.worldDetailQuality; - if (currentWorldDetailQuality <= 0.25) { - worldDetailDropdown.currentIndex = 0; - } else if (currentWorldDetailQuality <= 0.5) { - worldDetailDropdown.currentIndex = 1; - } else { - worldDetailDropdown.currentIndex = 2; - } + worldDetailDropdown.currentIndex = LODManager.worldDetailQuality; } Component.onCompleted: { @@ -173,7 +163,7 @@ Item { } onCurrentIndexChanged: { - LODManager.worldDetailQuality = model.get(currentIndex).worldDetailQualityValue; + LODManager.worldDetailQuality = currentIndex; worldDetailDropdown.displayText = model.get(currentIndex).text; } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a10e17c010..257808c683 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7508,6 +7508,7 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get().data()); + qScriptRegisterMetaType(scriptEngine.data(), worldDetailQualityToScriptValue, worldDetailQualityFromScriptValue); scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Performance", new PerformanceScriptingInterface()); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 9fd7b0851a..1a72dffe20 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,11 +19,8 @@ #include "ui/DialogsManager.h" #include "InterfaceLogging.h" -const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS; -const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; - -Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS); -Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS); +Setting::Handle desktopWorldDetailQuality("desktopWorldDetailQuality", (int)DEFAULT_WORLD_DETAIL_QUALITY); +Setting::Handle hmdWorldDetailQuality("hmdWorldDetailQuality", (int)DEFAULT_WORLD_DETAIL_QUALITY); LODManager::LODManager() { } @@ -326,19 +323,21 @@ QString LODManager::getLODFeedbackText() { } void LODManager::loadSettings() { - setDesktopLODTargetFPS(desktopLODDecreaseFPS.get()); - Setting::Handle firstRun { Settings::firstRun, true }; + auto desktopQuality = static_cast(desktopWorldDetailQuality.get()); + auto hmdQuality = static_cast(hmdWorldDetailQuality.get()); + + Setting::Handle firstRun{ Settings::firstRun, true }; if (qApp->property(hifi::properties::OCULUS_STORE).toBool() && firstRun.get()) { - const float LOD_HIGH_QUALITY_LEVEL = 0.75f; - setHMDLODTargetFPS(LOD_HIGH_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS); - } else { - setHMDLODTargetFPS(hmdLODDecreaseFPS.get()); + hmdQuality = WORLD_DETAIL_HIGH; } + + setWorldDetailQuality(desktopQuality, false); + setWorldDetailQuality(hmdQuality, true); } void LODManager::saveSettings() { - desktopLODDecreaseFPS.set(getDesktopLODTargetFPS()); - hmdLODDecreaseFPS.set(getHMDLODTargetFPS()); + desktopWorldDetailQuality.set((int)_desktopWorldDetailQuality); + hmdWorldDetailQuality.set((int)_hmdWorldDetailQuality); } const float MIN_DECREASE_FPS = 0.5f; @@ -393,54 +392,35 @@ float LODManager::getLODTargetFPS() const { } } -void LODManager::setWorldDetailQuality(float quality) { +void LODManager::setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode) { static const float MIN_FPS = 10; - static const float LOW = 0.25f; - - bool isLowestValue = quality <= LOW; - bool isHMDMode = qApp->isHMDMode(); - - float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; - float desiredFPS = maxFPS; - - if (!isLowestValue) { - float calculatedFPS = (maxFPS - (maxFPS * quality)); - desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; - } + float desiredFPS = isHMDMode ? QUALITY_TO_FPS_HMD[quality] : QUALITY_TO_FPS_DESKTOP[quality]; if (isHMDMode) { + _hmdWorldDetailQuality = quality; setHMDLODTargetFPS(desiredFPS); } else { + _desktopWorldDetailQuality = quality; setDesktopLODTargetFPS(desiredFPS); } - +} + +void LODManager::setWorldDetailQuality(WorldDetailQuality quality) { + setWorldDetailQuality(quality, qApp->isHMDMode()); emit worldDetailQualityChanged(); } -float LODManager::getWorldDetailQuality() const { +WorldDetailQuality LODManager::getWorldDetailQuality() const { + return qApp->isHMDMode() ? _hmdWorldDetailQuality : _desktopWorldDetailQuality; +} - static const float LOW = 0.25f; - static const float MEDIUM = 0.5f; - static const float HIGH = 0.75f; +QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality) { + return worldDetailQuality; +} - bool inHMD = qApp->isHMDMode(); - - float targetFPS = 0.0f; - if (inHMD) { - targetFPS = getHMDLODTargetFPS(); - } else { - targetFPS = getDesktopLODTargetFPS(); - } - float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; - float percentage = 1.0f - targetFPS / maxFPS; - - if (percentage <= LOW) { - return LOW; - } else if (percentage <= MEDIUM) { - return MEDIUM; - } - - return HIGH; +void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality) { + worldDetailQuality = + static_cast(std::min(std::max(object.toInt32(), (int)WORLD_DETAIL_LOW), (int)WORLD_DETAIL_HIGH)); } void LODManager::setLODQualityLevel(float quality) { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 3dafa8c800..1a516e0410 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -23,26 +23,52 @@ #include +/**jsdoc + *

The world detail quality rendered.

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
0Low world detail quality.
1Medium world detail quality.
2High world detail quality.
+ * @typedef {number} LODManager.WorldDetailQuality + */ +enum WorldDetailQuality { + WORLD_DETAIL_LOW = 0, + WORLD_DETAIL_MEDIUM, + WORLD_DETAIL_HIGH +}; +Q_DECLARE_METATYPE(WorldDetailQuality); + #ifdef Q_OS_ANDROID const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate) #else const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid #endif -const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps + #ifdef Q_OS_ANDROID -const float LOD_MAX_LIKELY_HMD_FPS = 36.0f; // this is essentially, V-synch fps +const WorldDetailQuality DEFAULT_WORLD_DETAIL_QUALITY = WORLD_DETAIL_LOW; +const std::vector QUALITY_TO_FPS_DESKTOP = { 60.0f, 30.0f, 15.0f }; +const std::vector QUALITY_TO_FPS_HMD = { 25.0f, 16.0f, 10.0f }; #else -const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps +const WorldDetailQuality DEFAULT_WORLD_DETAIL_QUALITY = WORLD_DETAIL_MEDIUM; +const std::vector QUALITY_TO_FPS_DESKTOP = { 60.0f, 30.0f, 15.0f }; +const std::vector QUALITY_TO_FPS_HMD = { 90.0f, 45.0f, 22.5f }; #endif const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate class AABox; + /**jsdoc * The LOD class manages your Level of Detail functions within Interface. * @namespace LODManager - * + * * @hifi-interface * @hifi-client-entity * @hifi-avatar @@ -51,12 +77,12 @@ class AABox; * @property {number} engineRunTime Read-only. * @property {number} gpuTime Read-only. */ - class LODManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) + Q_PROPERTY(WorldDetailQuality worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality + NOTIFY worldDetailQualityChanged) Q_PROPERTY(float lodQualityLevel READ getLODQualityLevel WRITE setLODQualityLevel NOTIFY lodQualityLevelChanged) @@ -193,8 +219,8 @@ public: float getSmoothRenderTime() const { return _smoothRenderTime; }; float getSmoothRenderFPS() const { return (_smoothRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _smoothRenderTime : 0.0f); }; - void setWorldDetailQuality(float quality); - float getWorldDetailQuality() const; + void setWorldDetailQuality(WorldDetailQuality quality); + WorldDetailQuality getWorldDetailQuality() const; void setLODQualityLevel(float quality); float getLODQualityLevel() const; @@ -220,9 +246,6 @@ public: float getPidOd() const; float getPidO() const; - static const float DEFAULT_DESKTOP_LOD_DOWN_FPS; - static const float DEFAULT_HMD_LOD_DOWN_FPS; - signals: /**jsdoc @@ -244,6 +267,8 @@ signals: private: LODManager(); + void LODManager::setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode); + std::mutex _automaticLODLock; bool _automaticLODAdjust = true; @@ -258,8 +283,11 @@ private: float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL }; - float _desktopTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS }; - float _hmdTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS }; + WorldDetailQuality _desktopWorldDetailQuality { DEFAULT_WORLD_DETAIL_QUALITY }; + WorldDetailQuality _hmdWorldDetailQuality { DEFAULT_WORLD_DETAIL_QUALITY }; + + float _desktopTargetFPS { QUALITY_TO_FPS_DESKTOP[_desktopWorldDetailQuality] }; + float _hmdTargetFPS { QUALITY_TO_FPS_HMD[_hmdWorldDetailQuality] }; float _lodHalfAngle = getHalfAngleFromVisibilityDistance(DEFAULT_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT); int _boundaryLevelAdjust = 0; @@ -269,4 +297,7 @@ private: glm::vec4 _pidOutputs{ 0.0f }; }; +QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality); +void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality); + #endif // hifi_LODManager_h diff --git a/interface/src/PerformanceManager.cpp b/interface/src/PerformanceManager.cpp index 80c09e3fec..a7b9eff7cc 100644 --- a/interface/src/PerformanceManager.cpp +++ b/interface/src/PerformanceManager.cpp @@ -92,7 +92,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP RenderScriptingInterface::getInstance()->setShadowsEnabled(true); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME); - DependencyManager::get()->setWorldDetailQuality(0.75f); + DependencyManager::get()->setWorldDetailQuality(WORLD_DETAIL_HIGH); break; case PerformancePreset::MID: @@ -104,7 +104,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP RenderScriptingInterface::getInstance()->setShadowsEnabled(false); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::INTERACTIVE); - DependencyManager::get()->setWorldDetailQuality(0.5f); + DependencyManager::get()->setWorldDetailQuality(WORLD_DETAIL_MEDIUM); break; case PerformancePreset::LOW: @@ -114,7 +114,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale); - DependencyManager::get()->setWorldDetailQuality(0.25f); + DependencyManager::get()->setWorldDetailQuality(WORLD_DETAIL_LOW); break; case PerformancePreset::UNKNOWN: diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index ec15dd8111..b7b5342066 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -58,18 +58,19 @@ void setupPreferences() { static const QString GRAPHICS_QUALITY { "Graphics Quality" }; { auto getter = []()->float { - return DependencyManager::get()->getWorldDetailQuality(); + return (int)DependencyManager::get()->getWorldDetailQuality(); }; - auto setter = [](float value) { - DependencyManager::get()->setWorldDetailQuality(value); + auto setter = [](int value) { + DependencyManager::get()->setWorldDetailQuality(static_cast(value)); }; - auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter); - wodSlider->setMin(0.25f); - wodSlider->setMax(0.75f); - wodSlider->setStep(0.25f); - preferences->addPreference(wodSlider); + auto wodButtons = new RadioButtonsPreference(GRAPHICS_QUALITY, "World Detail", getter, setter); + QStringList items; + items << "Low World Detail" << "Medium World Detail" << "High World Detail"; + wodButtons->setHeading("World Detail"); + wodButtons->setItems(items); + preferences->addPreference(wodButtons); auto getterShadow = []()->bool { auto menu = Menu::getInstance(); From 6a2e67ca54f0763b78bdf78bdad8e9580fc792c9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Oct 2019 21:07:04 +1300 Subject: [PATCH 125/203] Typo --- interface/src/LODManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 1a516e0410..4708deb61b 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -267,7 +267,7 @@ signals: private: LODManager(); - void LODManager::setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode); + void setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode); std::mutex _automaticLODLock; bool _automaticLODAdjust = true; From a8602195d4cb5c602672dbb186415245202d6772 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 22 Oct 2019 07:17:57 -0700 Subject: [PATCH 126/203] addressing comments --- interface/src/scripting/AudioDevices.cpp | 22 ++++++++----------- libraries/audio-client/CMakeLists.txt | 1 + libraries/audio-client/src/AudioClient.cpp | 8 ++++--- .../audio-client/src/HifiAudioDeviceInfo.cpp | 3 +-- .../audio-client/src/HifiAudioDeviceInfo.h | 2 -- .../plugins/src/plugins/PluginManager.cpp | 6 ++--- libraries/plugins/src/plugins/PluginManager.h | 1 + 7 files changed, 19 insertions(+), 24 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 7cabd49e93..4aa1018c23 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -62,12 +62,6 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) { auto& setting = getSetting(hmd, mode); if (setting.isSet()) { deviceName = setting.get(); - } else if (hmd) { - if (mode == QAudio::AudioInput) { - deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice(); - } else { // if (_mode == QAudio::AudioOutput) - deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); - } } else { deviceName = HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME; } @@ -269,13 +263,15 @@ void AudioDeviceList::onDevicesChanged(const QList& devices bool hmdIsSelected = false; bool desktopIsSelected = false; - foreach(const HifiAudioDeviceInfo& deviceInfo, devices) { - for (bool isHMD : {false, true}) { - auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; - if (deviceInfo.deviceName() == backupSelectedDeviceName) { - HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; - selectedDevice = deviceInfo; - backupSelectedDeviceName.clear(); + if (!_backupSelectedDesktopDeviceName.isEmpty() && !_backupSelectedHMDDeviceName.isEmpty()) { + foreach(const HifiAudioDeviceInfo& deviceInfo, devices) { + for (bool isHMD : {false, true}) { + auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; + if (deviceInfo.deviceName() == backupSelectedDeviceName) { + HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; + selectedDevice = deviceInfo; + backupSelectedDeviceName.clear(); + } } } } diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 03279b08f6..e1d90334ff 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) + if (ANDROID) else () target_webrtc() diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 418a45e5e9..cb8128bbc9 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,7 @@ #include #include #include + #include "AudioClientLogging.h" #include "AudioLogging.h" #include "AudioHelpers.h" @@ -81,7 +83,7 @@ Mutex _recordMutex; HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode); -#include + static QString getHmdAudioDeviceName(QAudio::Mode mode) { foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { if (displayPlugin && displayPlugin->isHmd()) { @@ -568,7 +570,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { HifiAudioDeviceInfo foundDevice; foreach(QAudioDeviceInfo audioDevice, devices) { if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { - foundDevice=HifiAudioDeviceInfo(audioDevice,true,mode, HifiAudioDeviceInfo::desktop); + foundDevice=HifiAudioDeviceInfo(audioDevice, true, mode, HifiAudioDeviceInfo::desktop); break; } } @@ -594,7 +596,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { } #endif // fallback for failed lookup is the default device - return (mode == QAudio::AudioInput) ? HifiAudioDeviceInfo(QAudioDeviceInfo::defaultInputDevice(), true,mode, HifiAudioDeviceInfo::desktop) : + return (mode == QAudio::AudioInput) ? HifiAudioDeviceInfo(QAudioDeviceInfo::defaultInputDevice(), true, mode, HifiAudioDeviceInfo::desktop) : HifiAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice(), true, mode, HifiAudioDeviceInfo::desktop); } diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp index 5cbb15ff73..73d0670fa6 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp @@ -32,5 +32,4 @@ bool HifiAudioDeviceInfo::operator==(const HifiAudioDeviceInfo& rhs) const { } bool HifiAudioDeviceInfo::operator!=(const HifiAudioDeviceInfo& rhs) const { return getDevice() != rhs.getDevice() || isDefault() != rhs.isDefault(); -} - +} \ No newline at end of file diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.h b/libraries/audio-client/src/HifiAudioDeviceInfo.h index 9b77f77c9e..5bc7125574 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.h +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.h @@ -37,7 +37,6 @@ public: _deviceType = deviceInfo.getDeviceType(); } - HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode, DeviceType devType=both) : _audioDeviceInfo(deviceInfo), _isDefault(isDefault), @@ -66,7 +65,6 @@ public: bool operator==(const HifiAudioDeviceInfo& rhs) const; bool operator!=(const HifiAudioDeviceInfo& rhs) const; - private: QAudioDeviceInfo _audioDeviceInfo; bool _isDefault { false }; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 1ecc4f1d45..660159d9bd 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -13,6 +13,7 @@ #include #include #include +#include //#define HIFI_PLUGINMANAGER_DEBUG #if defined(HIFI_PLUGINMANAGER_DEBUG) @@ -21,6 +22,7 @@ #include #include +#include #include "RuntimePlugin.h" #include "CodecPlugin.h" @@ -221,10 +223,6 @@ const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() { return oculusPlatformPlugin; } -#include -#include - - DisplayPluginList PluginManager::getAllDisplayPlugins() { if (thread() != QThread::currentThread()) { DisplayPluginList list; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 5ec82ea743..f0aa662634 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -52,6 +52,7 @@ public: using PluginFilter = std::function; void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; } Q_INVOKABLE DisplayPluginList getAllDisplayPlugins(); + signals: void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices); From 0410a45da1430f88617704e25b019a6b8fff2a11 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 22 Oct 2019 11:11:13 -0700 Subject: [PATCH 127/203] fixed selection issue --- interface/src/scripting/AudioDevices.cpp | 21 ++++++++++++++++----- libraries/audio-client/src/AudioClient.cpp | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 4aa1018c23..7fc129de3c 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -268,9 +268,14 @@ void AudioDeviceList::onDevicesChanged(const QList& devices for (bool isHMD : {false, true}) { auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; if (deviceInfo.deviceName() == backupSelectedDeviceName) { - HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; - selectedDevice = deviceInfo; - backupSelectedDeviceName.clear(); + if (isHMD && deviceInfo.getDeviceType() != HifiAudioDeviceInfo::desktop) { + _selectedHMDDevice= deviceInfo; + backupSelectedDeviceName.clear(); + } else if (!isHMD && deviceInfo.getDeviceType() != HifiAudioDeviceInfo::hmd) { + _selectedDesktopDevice = deviceInfo; + backupSelectedDeviceName.clear(); + } + } } } @@ -323,8 +328,14 @@ void AudioDeviceList::onDevicesChanged(const QList& devices } else { //no selected device for context. fallback to saved - const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName; - isSelected = (device.info.deviceName() == savedDeviceName); + QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName; + + if (device.info.deviceName() == savedDeviceName) { + if ((isHMD && device.info.getDeviceType() != HifiAudioDeviceInfo::desktop) || + (!isHMD && device.info.getDeviceType() != HifiAudioDeviceInfo::hmd)) { + isSelected = true; + } + } } if (isSelected) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index cb8128bbc9..3e1557e09b 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -570,7 +570,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { HifiAudioDeviceInfo foundDevice; foreach(QAudioDeviceInfo audioDevice, devices) { if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { - foundDevice=HifiAudioDeviceInfo(audioDevice, true, mode, HifiAudioDeviceInfo::desktop); + foundDevice = HifiAudioDeviceInfo(audioDevice, true, mode, HifiAudioDeviceInfo::desktop); break; } } From ea864e4e9740b473989c8496f7b315c3d32fae31 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Oct 2019 08:13:46 +1300 Subject: [PATCH 128/203] Remove unused variable --- interface/src/LODManager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 1a72dffe20..4cd5025fc1 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -393,9 +393,7 @@ float LODManager::getLODTargetFPS() const { } void LODManager::setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode) { - static const float MIN_FPS = 10; float desiredFPS = isHMDMode ? QUALITY_TO_FPS_HMD[quality] : QUALITY_TO_FPS_DESKTOP[quality]; - if (isHMDMode) { _hmdWorldDetailQuality = quality; setHMDLODTargetFPS(desiredFPS); From bfa89452e091280706d9645f35f905329ba7c39e Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 22 Oct 2019 14:24:32 -0700 Subject: [PATCH 129/203] updating launcher text --- launchers/qt/resources/qml/Download.qml | 2 +- .../qml/HFBase/CreateAccountBase.qml | 6 +++++- .../qt/resources/qml/HFBase/LoginBase.qml | 6 +++++- launchers/qt/src/LauncherState.cpp | 19 +++++++++---------- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/launchers/qt/resources/qml/Download.qml b/launchers/qt/resources/qml/Download.qml index 094b9cc961..5c3bb3fd1c 100644 --- a/launchers/qt/resources/qml/Download.qml +++ b/launchers/qt/resources/qml/Download.qml @@ -42,7 +42,7 @@ Item { HFTextHeader { id: firstText - text: "Set up will take a moment" + text: "Setup will take a moment" anchors { top: logo.bottom diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index 4a47ea4253..c419878413 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -164,7 +164,11 @@ Item { topMargin: 4 } - onAccepted: LauncherState.signup(email.text, username.text, password.text, displayName.text) + onAccepted: { + if (root.enabled && email.text.length > 0 && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0) { + LauncherState.signup(email.text, username.text, password.text, displayName.text); + } + } } HFButton { diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index fd24b63398..1df9950dd4 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -137,7 +137,11 @@ Item { left: parent.left right: parent.right; } - onAccepted: LauncherState.login(username.text, password.text, displayName.text) + onAccepted: { + if (root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0) { + LauncherState.login(username.text, password.text, displayName.text); + } + } } HFButton { diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 56491f8f88..98e476bc99 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -286,7 +286,6 @@ void LauncherState::getCurrentClientVersion() { setApplicationState(ApplicationState::WaitingForLogin); } } - //setApplicationState(ApplicationState::WaitingForLogin); } @@ -335,19 +334,19 @@ void LauncherState::signup(QString email, QString username, QString password, QS auto err = signupRequest->getError(); if (err == SignupRequest::Error::ExistingUsername) { - setLastSignupErrorMessage(_username + " is already taken - please try a different username."); + setLastSignupErrorMessage(_username + " is already taken. Please try a different username."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::BadPassword) { - setLastSignupErrorMessage("That's an invalid password - must be at least 6 characters."); + setLastSignupErrorMessage("That's an invalid password. Must be at least 6 characters."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::BadUsername) { - setLastSignupErrorMessage("That's an invalid username - please try another username."); + setLastSignupErrorMessage("That's an invalid username. Please try another username."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::UserProfileAlreadyCompleted) { - setLastSignupErrorMessage("This email exists, if it's yours please login."); + setLastSignupErrorMessage("An account with this email already exists. Please log in."); setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err == SignupRequest::Error::NoSuchEmail) { @@ -355,7 +354,7 @@ void LauncherState::signup(QString email, QString username, QString password, QS setApplicationState(ApplicationState::WaitingForSignup); return; } else if (err != SignupRequest::Error::None) { - setApplicationStateError("Failed to sign up"); + setApplicationStateError("Failed to sign up. Please try again."); return; } @@ -374,11 +373,11 @@ void LauncherState::signup(QString email, QString username, QString password, QS auto err = loginRequest->getError(); if (err == LoginRequest::Error::BadUsernameOrPassword) { - setLastLoginErrorMessage("Bad username or password"); + setLastLoginErrorMessage("Invalid username or password."); setApplicationState(ApplicationState::WaitingForLogin); return; } else if (err != LoginRequest::Error::None) { - setApplicationStateError("Failed to login"); + setApplicationStateError("Failed to login. Please try again."); return; } @@ -413,11 +412,11 @@ void LauncherState::login(QString username, QString password, QString displayNam auto err = request->getError(); if (err == LoginRequest::Error::BadUsernameOrPassword) { - setLastLoginErrorMessage("Bad username or password"); + setLastLoginErrorMessage("Invalid username or password"); setApplicationState(ApplicationState::WaitingForLogin); return; } else if (err != LoginRequest::Error::None) { - setApplicationStateError("Failed to login"); + setApplicationStateError("Failed to login. Please try again."); return; } From 61b9f08fec914aa6a8233a398a545dfaa87e1da0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 22 Oct 2019 22:19:03 -0700 Subject: [PATCH 130/203] Blendshapes for OtherAvatars should update even if no joints have changes. --- interface/src/avatar/OtherAvatar.cpp | 5 ++++- .../avatars-renderer/src/avatars-renderer/SkeletonModel.cpp | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 5673c2443f..50f6369dbe 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -267,6 +267,7 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { _skeletonModel->getRig().computeExternalPoses(rootTransform); _jointDataSimulationRate.increment(); + head->simulate(deltaTime); _skeletonModel->simulate(deltaTime, true); locationChanged(); // joints changed, so if there are any children, update them. @@ -277,9 +278,11 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { headPosition = getWorldPosition(); } head->setPosition(headPosition); + } else { + head->simulate(deltaTime); + _skeletonModel->simulate(deltaTime, false); } head->setScale(getModelScale()); - head->simulate(deltaTime); relayJointDataToChildren(); } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index b52a68f066..fc16851942 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -165,8 +165,9 @@ void SkeletonModel::updateAttitude(const glm::quat& orientation) { // but just before head has been simulated. void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { updateAttitude(_owningAvatar->getWorldOrientation()); + setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients()); + if (fullUpdate) { - setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients()); Parent::simulate(deltaTime, fullUpdate); From 179f5e0873ff65c5960ef2d3b7e4846083bb8a47 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 23 Oct 2019 12:56:43 -0700 Subject: [PATCH 131/203] fixed context switching issue with hmd. included additional output info for other audio crash issues --- interface/src/scripting/AudioDevices.cpp | 4 ++-- libraries/audio-client/src/AudioClient.cpp | 27 ++++++++++++++-------- libraries/audio-client/src/AudioClient.h | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 7fc129de3c..76c871fa5c 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -164,8 +164,8 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) { QString deviceName = getTargetDevice(contextIsHMD, _mode); // FIXME can't use blocking connections here, so we can't determine whether the switch succeeded or not // We need to have the AudioClient emit signals on switch success / failure - QMetaObject::invokeMethod(client, "switchAudioDevice", - Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName)); + QMetaObject::invokeMethod(client, "switchAudioDevice", + Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName), Q_ARG(bool, contextIsHMD)); #if 0 bool switchResult = false; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 3e1557e09b..9ae751d6c3 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -424,12 +424,14 @@ void AudioClient::setAudioPaused(bool pause) { } } -HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { +HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName, bool isHmd=false) { HifiAudioDeviceInfo result; foreach (HifiAudioDeviceInfo audioDevice, getAvailableDevices(mode)) { if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { - result = audioDevice; - break; + if ((!isHmd && audioDevice.getDeviceType() != HifiAudioDeviceInfo::hmd) || (isHmd && audioDevice.getDeviceType() != HifiAudioDeviceInfo::desktop)) { + result = audioDevice; + break; + } } } return result; @@ -986,13 +988,18 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) { void AudioClient::changeDefault(HifiAudioDeviceInfo newDefault, QAudio::Mode mode) { HifiAudioDeviceInfo currentDevice = mode == QAudio::AudioInput ? _inputDeviceInfo : _outputDeviceInfo; - if (currentDevice.isDefault() && currentDevice.getDevice() != newDefault.getDevice()) { + if (currentDevice.isDefault() && currentDevice.getDeviceType() == newDefault.getDeviceType() && currentDevice.getDevice() != newDefault.getDevice()) { switchAudioDevice(mode, newDefault); } } bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo) { auto device = deviceInfo; + if (deviceInfo.getDevice().isNull()) { + qCDebug(audioclient) << __FUNCTION__ << " switching to null device :" + << deviceInfo.deviceName() << " : " << deviceInfo.getDevice().deviceName(); + } + if (mode == QAudio::AudioInput) { return switchInputToAudioDevice(device); } else { @@ -1000,8 +1007,8 @@ bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo } } -bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QString& deviceName) { - return switchAudioDevice(mode, getNamedAudioDeviceForMode(mode, deviceName)); +bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd) { + return switchAudioDevice(mode, getNamedAudioDeviceForMode(mode, deviceName, isHmd)); } void AudioClient::configureReverb() { @@ -1809,7 +1816,8 @@ void AudioClient::outputFormatChanged() { bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) { Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); - qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << _inputDeviceInfo.deviceName() <<"----"< Date: Thu, 24 Oct 2019 09:00:29 +1300 Subject: [PATCH 132/203] Typo --- libraries/script-engine/src/RecordingScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 19db6dc50d..fd9c2d64e6 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -265,7 +265,7 @@ public slots: * Gets the default directory that recordings are saved in. * @function Recording.getDefaultRecordingSaveDirectory * @returns {string} The default recording save directory. - * @exampl Report the default save directory. + * @example Report the default save directory. * print("Default save directory: " + Recording.getDefaultRecordingSaveDirectory()); */ QString getDefaultRecordingSaveDirectory(); From 116510225ad6f9590b7d7deaeefe77012be5bf66 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Oct 2019 09:10:26 +1300 Subject: [PATCH 133/203] Typo --- libraries/ui/src/ui/ToolbarScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.h b/libraries/ui/src/ui/ToolbarScriptingInterface.h index a4746e4566..746ba2894e 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.h +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.h @@ -19,7 +19,7 @@ class QQuickItem; -// No JSDoc for ToolbarButtonProxy because oolbarProxy#addButton() doesn't work. +// No JSDoc for ToolbarButtonProxy because ToolbarProxy#addButton() doesn't work. class ToolbarButtonProxy : public QmlWrapper { Q_OBJECT From 423074463dada43a8b70d851f53cf5423193a8bb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Oct 2019 09:17:13 +1300 Subject: [PATCH 134/203] Typo --- interface/src/audio/AudioScope.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index fcc9b21efe..26b228e900 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -240,7 +240,7 @@ signals: void pauseChanged(); /**jsdoc - * Triggered when scoep is triggered. + * Triggered when scope is triggered. * @function AudioScope.triggered * @returns {Signal} */ From 2591aed686acebf7c599199468a890f04a3bb1fd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Oct 2019 09:20:24 +1300 Subject: [PATCH 135/203] Exclude AudioScope API from JSDoc output --- tools/jsdoc/plugins/hifi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 07549530ce..56d2278b50 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -26,7 +26,7 @@ exports.handlers = { '../../assignment-client/src/octree', '../../interface/src', '../../interface/src/assets', - '../../interface/src/audio', + //'../../interface/src/audio', Exlude AudioScope API from output. '../../interface/src/avatar', '../../interface/src/commerce', '../../interface/src/devices', From bb0849d3a0295735159ff2b1cb9b0fdd7712bda1 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 23 Oct 2019 14:23:29 -0700 Subject: [PATCH 136/203] Move changes to Head and remove leftover code --- interface/src/avatar/MyAvatar.cpp | 27 ++++++++----------- interface/src/avatar/MyAvatar.h | 5 ++-- .../src/avatars-renderer/Head.cpp | 21 +++++++++++++++ .../src/avatars-renderer/Head.h | 7 +++++ libraries/avatars/src/AvatarData.h | 1 - libraries/avatars/src/HeadData.cpp | 27 ------------------- libraries/avatars/src/HeadData.h | 12 ++++----- 7 files changed, 47 insertions(+), 53 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e152902b47..9c9505323f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2191,20 +2191,15 @@ void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) { foreach (const AvatarSharedPointer& avatarData, hash) { std::shared_ptr avatar = std::static_pointer_cast(avatarData); if (!avatar->isMyAvatar() && avatar->isInitialized()) { - if (_forceTargetAvatarID.isNull() || avatar->getID() != _forceTargetAvatarID) { - glm::vec3 otherForward = avatar->getHeadJointFrontVector(); - glm::vec3 otherPosition = avatar->getHead()->getEyePosition(); - const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f; - bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD; - bool lookingAtOtherAlready = _lookAtTargetAvatar.lock().get() == avatar.get(); - float cost = lookAtCostFunction(myForward, myPosition, otherForward, otherPosition, otherIsTalking, lookingAtOtherAlready); - if (cost < bestCost) { - bestCost = cost; - bestAvatar = avatar; - } - } else { + glm::vec3 otherForward = avatar->getHeadJointFrontVector(); + glm::vec3 otherPosition = avatar->getHead()->getEyePosition(); + const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f; + bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD; + bool lookingAtOtherAlready = _lookAtTargetAvatar.lock().get() == avatar.get(); + float cost = lookAtCostFunction(myForward, myPosition, otherForward, otherPosition, otherIsTalking, lookingAtOtherAlready); + if (cost < bestCost) { + bestCost = cost; bestAvatar = avatar; - break; } } } @@ -6652,7 +6647,7 @@ void MyAvatar::updateEyesLookAtPosition(FaceTracker* faceTracker, Camera& myCame } else if (_scriptControlsEyesLookAt) { if (_scriptEyesControlTimer < MAX_LOOK_AT_TIME_SCRIPT_CONTROL) { _scriptEyesControlTimer += deltaTime; - lookAtSpot = _eyesLookAtTarget; + lookAtSpot = _eyesLookAtTarget.get(); } else { _scriptControlsEyesLookAt = false; } @@ -6740,7 +6735,7 @@ void MyAvatar::updateEyesLookAtPosition(FaceTracker* faceTracker, Camera& myCame } } } - _eyesLookAtTarget = lookAtSpot; + _eyesLookAtTarget.set(lookAtSpot); getHead()->setLookAtPosition(lookAtSpot); } @@ -6829,7 +6824,7 @@ void MyAvatar::setEyesLookAt(const glm::vec3& lookAtTarget) { Q_ARG(const glm::vec3&, lookAtTarget)); return; } - _eyesLookAtTarget = lookAtTarget; + _eyesLookAtTarget.set(lookAtTarget); _scriptEyesControlTimer = 0.0f; _scriptControlsEyesLookAt = true; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 90c4157c3c..c0fa15b624 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1780,7 +1780,7 @@ public: * @function MyAvatar.getEyesLookAt * @returns {Vec3} The eyes's look at target in world coordinates. */ - Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget; } + Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget.get(); } /**jsdoc * Aims the pointing directional blending towards the provided target point. @@ -2672,13 +2672,12 @@ private: AvatarWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; - QUuid _forceTargetAvatarID; bool _shouldRender { true }; float _oculusYawOffset; eyeContactTarget _eyeContactTarget; float _eyeContactTargetTimer { 0.0f }; - glm::vec3 _eyesLookAtTarget; + ThreadSafeValueCache _eyesLookAtTarget { glm::vec3() }; bool _scriptControlsEyesLookAt{ false }; float _scriptEyesControlTimer{ 0.0f }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index e2f5ef0c60..aa6dc779d5 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -355,3 +355,24 @@ float Head::getFinalPitch() const { float Head::getFinalRoll() const { return glm::clamp(_baseRoll + _deltaRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } + +void Head::setLookAtPosition(const glm::vec3& lookAtPosition) { + if (_isEyeLookAtUpdated && _requestLookAtPosition != lookAtPosition) { + _lookAtPositionChanged = usecTimestampNow(); + glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); + glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); + const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees + _forceBlinkToRetarget = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; + if (_forceBlinkToRetarget) { + _isEyeLookAtUpdated = false; + } else { + _lookAtPosition = lookAtPosition; + } + } + _requestLookAtPosition = lookAtPosition; +} + +void Head::updateEyeLookAt() { + _lookAtPosition = _requestLookAtPosition; + _isEyeLookAtUpdated = true; +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.h b/libraries/avatars-renderer/src/avatars-renderer/Head.h index e3c8d7d2b5..98b2fbb3a0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.h @@ -79,6 +79,9 @@ public: float getTimeWithoutTalking() const { return _timeWithoutTalking; } + virtual void setLookAtPosition(const glm::vec3& lookAtPosition) override; + void updateEyeLookAt(); + protected: // disallow copies of the Head, copy of owning Avatar is disallowed too Head(const Head&); @@ -123,6 +126,10 @@ protected: int _leftEyeLookAtID; int _rightEyeLookAtID; + glm::vec3 _requestLookAtPosition; + bool _forceBlinkToRetarget { false }; + bool _isEyeLookAtUpdated { false }; + // private methods void calculateMouthShapes(float timeRatio); void applyEyelidOffset(glm::quat headOrientation); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 61729f8db3..bfe775e972 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1480,7 +1480,6 @@ public: void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; } bool getIsNewAvatar() { return _isNewAvatar; } void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; } - bool getIsClientAvatar() const { return _isClientAvatar; } void setSkeletonData(const std::vector& skeletonData); std::vector getSkeletonData() const; void sendSkeletonData() const; diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index d9998883d7..7e1d13511e 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -234,30 +234,3 @@ void HeadData::setFaceTrackerConnected(bool value) { _isFaceTrackerConnected = value; } -void HeadData::setLookAtPosition(const glm::vec3& lookAtPosition) { - if (_owningAvatar->getIsClientAvatar() || _owningAvatar->isMyAvatar()) { - if (_isEyeLookAtUpdated && _requestLookAtPosition != lookAtPosition) { - _lookAtPositionChanged = usecTimestampNow(); - glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); - glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); - const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees - _forceBlinkToRetarget = angleBetween(oldAvatarLookAtVector, newAvatarLookAtVector) > MIN_BLINK_ANGLE; - if (_forceBlinkToRetarget) { - _isEyeLookAtUpdated = false; - } else { - _lookAtPosition = lookAtPosition; - } - } - _requestLookAtPosition = lookAtPosition; - } else { - if (_lookAtPosition != lookAtPosition) { - _lookAtPositionChanged = usecTimestampNow(); - } - _lookAtPosition = lookAtPosition; - } -} - -void HeadData::updateEyeLookAt() { - _lookAtPosition = _requestLookAtPosition; - _isEyeLookAtUpdated = true; -} diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 190b179f25..020827c4d9 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -64,7 +64,12 @@ public: void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } - void setLookAtPosition(const glm::vec3& lookAtPosition); + virtual void setLookAtPosition(const glm::vec3& lookAtPosition) { + if (_lookAtPosition != lookAtPosition) { + _lookAtPositionChanged = usecTimestampNow(); + } + _lookAtPosition = lookAtPosition; + } bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; } bool getHasProceduralEyeFaceMovement() const; @@ -79,8 +84,6 @@ public: void setFaceTrackerConnected(bool value); bool getFaceTrackerConnected() const { return _isFaceTrackerConnected; } - void updateEyeLookAt(); - friend class AvatarData; QJsonObject toJson() const; @@ -92,7 +95,6 @@ protected: float _basePitch; float _baseRoll; - glm::vec3 _requestLookAtPosition; glm::vec3 _lookAtPosition; quint64 _lookAtPositionChanged { 0 }; @@ -113,8 +115,6 @@ protected: QVector _summedBlendshapeCoefficients; QMap _blendshapeLookupMap; AvatarData* _owningAvatar; - bool _forceBlinkToRetarget { false }; - bool _isEyeLookAtUpdated { false }; private: // privatize copy ctor and assignment operator so copies of this object cannot be made From 9432a375675e6aa5b87ca26e87d22e80c0d837b3 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 23 Oct 2019 14:26:53 -0700 Subject: [PATCH 137/203] Undo changes in HeadData.cpp --- libraries/avatars/src/HeadData.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 7e1d13511e..c86e534929 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -233,4 +233,3 @@ void HeadData::setHasProceduralEyeMovement(bool hasProceduralEyeMovement) { void HeadData::setFaceTrackerConnected(bool value) { _isFaceTrackerConnected = value; } - From 850dcb839d100ed98733daf56a0d0af27c95937e Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 23 Oct 2019 14:39:34 -0700 Subject: [PATCH 138/203] adding build qt-lite build rules --- tools/qt-builder/qt-lite-build-steps-win.md | 90 ++++++++++ tools/qt-builder/qt-lite-osx-config.opt | 4 +- tools/qt-builder/qt-lite-windows-config.opt | 189 ++++++++++++++++++++ 3 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 tools/qt-builder/qt-lite-build-steps-win.md create mode 100644 tools/qt-builder/qt-lite-windows-config.opt diff --git a/tools/qt-builder/qt-lite-build-steps-win.md b/tools/qt-builder/qt-lite-build-steps-win.md new file mode 100644 index 0000000000..a693acbb9f --- /dev/null +++ b/tools/qt-builder/qt-lite-build-steps-win.md @@ -0,0 +1,90 @@ +# Windows +Command Prompt +``` +git clone --single-branch --branch 5.9 https://code.qt.io/qt/qt5.git +cd qt5 +perl ./init-repository -force --module-subset=qtbase,qtdeclarative,qtgraphicaleffects,qtquickcontrols,qtquickcontrols2,qtmultimedia +``` + +Modify the following lines in `qtbase/mkspecs/common/msvc-desktop.conf`, changing `-MD` to `-MT`, and `-MDd` to `-MTd` +``` +QMAKE_CFLAGS_RELEASE = $$QMAKE_CFLAGS_OPTIMIZE -MD +QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_OPTIMIZE -Zi -MD +QMAKE_CFLAGS_DEBUG = -Zi -MDd +``` + + +Command Prompt +``` +cp path-to-your-hifi-directory/tools/qt-builder/qt5vars.bat ./ +mkdir qt-build +cd qt-build +..\qt5vars.bat +..\configure.bat +``` + +Download ssl-static.zip and unzip to ssl-static folder next to qt5 folder +remove config.opt in the build folder +copy over the config file from qt-builder +``` +cp path-to-your-hifi-directory/tools/qt-builder/qt-lite-windows-config ./config.opt +``` +delete config.cache + +modify the config.opt file vaules `-L` and `-I` to point to the loaction of your openssl static lib + +``` +OPENSSL_LIBS=-lssleay32 -llibeay32 +-I\path\to\openssl\include +-L\path\to\openssl\lib +``` + +Command Prompt +``` +rem config.cache +config.status.bat +jom +jom install +``` + +# OSX +Must use Apple LLVM version 8.1.0 (clang-802.0.42) +Command Prompt +``` +git clone --single-branch --branch 5.9 https://code.qt.io/qt/qt5.git +cd qt5 +./init-repository -force --module-subset=qtbase,qtdeclarative,qtgraphicaleffects,qtquickcontrols,qtquickcontrols2,qtmultimedia,, +``` + +Command Prompt +``` +mkdir qt-build +cd qt-build +../configure +``` + +Download ssl-static.zip and unzip to ssl-static folder next to qt5 folder +copy over the config file from qt-builder +``` +cp path-to-your-hifi-directory/tools/qt-builder/qt-lite-osx-config ./config.opt +``` +delete config.cache + +modify the config.opt file vaules `-L` and `-I`to point to the loaction of your openssl static lib + +``` +OPENSSL_LIBS=-lssl -lcrypto +-L/path/to/openssl/lib +-I/path/to/openssl/include +``` + + +Comand Prompt +``` +rm config.cache +config.status +make +make install +``` + + diff --git a/tools/qt-builder/qt-lite-osx-config.opt b/tools/qt-builder/qt-lite-osx-config.opt index 28bb1b88e2..7497bf55ea 100644 --- a/tools/qt-builder/qt-lite-osx-config.opt +++ b/tools/qt-builder/qt-lite-osx-config.opt @@ -24,8 +24,8 @@ qtmultimedia ./qt-install -openssl-linked OPENSSL_LIBS=-lssl -lcrypto --L/usr/local/Cellar/openssl/1.0.2s/lib --I/usr/local/Cellar/openssl/1.0.2s/include +-L/path/to/openssl/lib +-I/path/to/openssl/include -no-feature-widgets -no-feature-dbus -no-feature-xml diff --git a/tools/qt-builder/qt-lite-windows-config.opt b/tools/qt-builder/qt-lite-windows-config.opt new file mode 100644 index 0000000000..dae6a6f39f --- /dev/null +++ b/tools/qt-builder/qt-lite-windows-config.opt @@ -0,0 +1,189 @@ +-static +-optimize-size +-qt-libpng +-no-libjpeg +-qt-sqlite +-qt-zlib +-qt-freetype +-qt-pcre +-strip +-opensource +-release +-nomake +tests +-nomake +examples +-nomake +tests +-no-compile-examples +-no-pch +-confirm-license +-opengl +desktop +-skip +qtmultimedia +-skip +qttranslations +-prefix +./qt-install + +-openssl-linked +OPENSSL_LIBS=-lssleay32 -llibeay32 +-I\path\to\openssl\include +-L\path\to\openssl\lib +-no-feature-widgets +-no-feature-dbus +-no-feature-xml +-no-feature-sql +-no-feature-concurrent +-no-feature-quicktemplates2-hover +-no-feature-quicktemplates2-multitouch +-no-feature-quickcontrols2-material +-no-feature-quickcontrols2-universal +-no-feature-qml-network +-no-feature-qml-profiler +-no-feature-quick-listview +-no-feature-quick-particles +-no-feature-abstractbutton +-no-feature-abstractslider +-no-feature-buttongroup +-no-feature-calendarwidget +-no-feature-checkbox +-no-feature-combobox +-no-feature-commandlinkbutton +-no-feature-contextmenu +-no-feature-datetimeedit +-no-feature-dial +-no-feature-dockwidget +-no-feature-fontcombobox +-no-feature-formlayout +-no-feature-graphicseffect +-no-feature-graphicsview +-no-feature-groupbox +-no-feature-keysequenceedit +-no-feature-label +-no-feature-lcdnumber +-no-feature-lineedit +-no-feature-listwidget +-no-feature-mainwindow +-no-feature-mdiarea +-no-feature-menu +-no-feature-menubar +-no-feature-printpreviewwidget +-no-feature-progressbar +-no-feature-pushbutton +-no-feature-radiobutton +-no-feature-resizehandler +-no-feature-rubberband +-no-feature-scrollarea +-no-feature-scrollbar +-no-feature-scroller +-no-feature-sizegrip +-no-feature-slider +-no-feature-spinbox +-no-feature-splashscreen +-no-feature-splitter +-no-feature-stackedwidget +-no-feature-statusbar +-no-feature-statustip +-no-feature-syntaxhighlighter +-no-feature-tabbar +-no-feature-tablewidget +-no-feature-tabwidget +-no-feature-textbrowser +-no-feature-textedit +-no-feature-toolbar +-no-feature-toolbox +-no-feature-toolbutton +-no-feature-tooltip +-no-feature-treewidget +-no-feature-validator +-no-feature-widgettextcontrol +-no-feature-quick-designer +-no-feature-quick-flipable +-no-feature-quick-pathview +-no-feature-qml-profiler +-no-feature-gif +-no-feature-ico +-no-feature-harfbuzz +-no-feature-qml-debug +-no-feature-quick-listview +-no-feature-quick-sprite +-no-feature-quick-path +-no-feature-quick-canvas +-no-feature-quick-animatedimage +-no-feature-qml-interpreter +-no-feature-action +-no-feature-cssparser +-no-feature-sharedmemory +-no-feature-tabletevent +-no-feature-texthtmlparser +-no-feature-textodfwriter +-no-feature-sessionmanager +-no-feature-systemsemaphore +-no-feature-im +-no-feature-effects +-no-feature-appstore-compliant +-no-feature-big_codecs +-no-feature-codecs +-no-feature-colordialog +-no-feature-colornames +-no-feature-columnview +-no-feature-commandlineparser +-no-feature-cups +-no-feature-d3d12 +-no-feature-datawidgetmapper +-no-feature-datetimeparser +-no-feature-desktopservices +-no-feature-dialog +-no-feature-dialogbuttonbox +-no-feature-dirmodel +-no-feature-dom +-no-feature-errormessage +-no-feature-filedialog +-no-feature-filesystemiterator +-no-feature-filesystemwatcher +-no-feature-fontdialog +-no-feature-fscompleter +-no-feature-gestures +-no-feature-iconv +-no-feature-wizard +-no-feature-xmlstreamwriter +-no-feature-whatsthis +-no-feature-undoview +-no-feature-undostack +-no-feature-undogroup +-no-feature-undocommand +-no-feature-treeview +-no-feature-translation +-no-feature-topleveldomain +-no-feature-tableview +-no-feature-style-stylesheet +-no-feature-stringlistmodel +-no-feature-sortfilterproxymodel +-no-feature-wheelevent +-no-feature-statemachine +-no-feature-standarditemmodel +-no-feature-proxymodel +-no-feature-printer +-no-feature-printpreviewdialog +-no-feature-printdialog +-no-feature-picture +-no-feature-pdf +-no-feature-movie +-no-feature-messagebox +-no-feature-listview +-no-feature-itemmodel +-no-feature-inputdialog +-no-feature-filesystemmodel +-no-feature-identityproxymodel +-no-feature-mimetype +-no-feature-paint_debug +-no-feature-progressdialog +-no-feature-quick-positioners +-no-feature-sha3-fast +-no-feature-shortcut +-no-feature-completer +-no-feature-image_heuristic_mask +-no-feature-image_text +-no-feature-imageformat_bmp \ No newline at end of file From d7996c77f44eef2b6d84033f077a238d1621db85 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 23 Oct 2019 14:48:25 -0700 Subject: [PATCH 139/203] android build fix --- libraries/audio-client/src/AudioClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9ae751d6c3..4a00dd9bf3 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -2005,9 +2005,9 @@ void AudioClient::setHeadsetPluggedIn(bool pluggedIn) { bool aecEnabled = enableAEC.get(); if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) { - switchAudioDevice(QAudio::AudioInput, VOICE_RECOGNITION); + switchAudioDevice(QAudio::AudioInput, VOICE_RECOGNITION, false); } else if (!pluggedIn && aecEnabled && _inputDeviceInfo.deviceName() != VOICE_COMMUNICATION) { - switchAudioDevice(QAudio::AudioInput, VOICE_COMMUNICATION); + switchAudioDevice(QAudio::AudioInput, VOICE_COMMUNICATION, false); } } _isHeadsetPluggedIn = pluggedIn; From ed4df09a261ce4cfd0069bf0608990b1dbe64194 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 23 Oct 2019 15:15:41 -0700 Subject: [PATCH 140/203] updated logging added clarifying comment for emit --- libraries/audio-client/src/AudioClient.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4a00dd9bf3..169a975f96 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1816,8 +1816,8 @@ void AudioClient::outputFormatChanged() { bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) { Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); - qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << _inputDeviceInfo.deviceName() << ":" << _inputDeviceInfo.getDevice().deviceName() - <<"-- new device: "<< inputDeviceInfo.deviceName() <<":"<< inputDeviceInfo.getDevice().deviceName() << "]"; + qCDebug(audioclient) << __FUNCTION__ << "_inputDeviceInfo: [" << _inputDeviceInfo.deviceName() << ":" << _inputDeviceInfo.getDevice().deviceName() + << "-- inputDeviceInfo: " << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << "]"; bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -1869,8 +1869,9 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice } if (!inputDeviceInfo.getDevice().isNull()) { - qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; + qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << " is available."; + //do not update UI that we're changing devices if default or same device bool doEmit = _inputDeviceInfo.deviceName() != inputDeviceInfo.deviceName(); _inputDeviceInfo = inputDeviceInfo; if (doEmit) { @@ -2053,8 +2054,8 @@ void AudioClient::noteAwakening() { bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) { Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); - qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << _outputDeviceInfo.deviceName() << ":" << _outputDeviceInfo.getDevice().deviceName() - << "-- new device: " << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "]"; + qCDebug(audioclient) << __FUNCTION__ << "_outputdeviceInfo: [" << _outputDeviceInfo.deviceName() << ":" << _outputDeviceInfo.getDevice().deviceName() + << "-- outputDeviceInfo: " << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "]"; bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -2110,7 +2111,9 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi } if (!outputDeviceInfo.getDevice().isNull()) { - qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; + qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << " is available."; + + //do not update UI that we're changing devices if default or same device bool doEmit = _outputDeviceInfo.deviceName() != outputDeviceInfo.deviceName(); _outputDeviceInfo = outputDeviceInfo; if (doEmit) { From ac8b664537ca25b6fbf2b2c3215985818c5594d6 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 23 Oct 2019 15:39:38 -0700 Subject: [PATCH 141/203] addresing pr comments --- libraries/audio-client/src/AudioClient.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 169a975f96..995fe20886 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1817,7 +1817,7 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); qCDebug(audioclient) << __FUNCTION__ << "_inputDeviceInfo: [" << _inputDeviceInfo.deviceName() << ":" << _inputDeviceInfo.getDevice().deviceName() - << "-- inputDeviceInfo: " << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << "]"; + << "-- inputDeviceInfo:" << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << "]"; bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -1869,7 +1869,7 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice } if (!inputDeviceInfo.getDevice().isNull()) { - qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << " is available."; + qCDebug(audioclient) << "The audio input device" << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << "is available."; //do not update UI that we're changing devices if default or same device bool doEmit = _inputDeviceInfo.deviceName() != inputDeviceInfo.deviceName(); @@ -2055,7 +2055,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); qCDebug(audioclient) << __FUNCTION__ << "_outputdeviceInfo: [" << _outputDeviceInfo.deviceName() << ":" << _outputDeviceInfo.getDevice().deviceName() - << "-- outputDeviceInfo: " << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "]"; + << "-- outputDeviceInfo:" << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "]"; bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -2111,7 +2111,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi } if (!outputDeviceInfo.getDevice().isNull()) { - qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << " is available."; + qCDebug(audioclient) << "The audio output device" << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "is available."; //do not update UI that we're changing devices if default or same device bool doEmit = _outputDeviceInfo.deviceName() != outputDeviceInfo.deviceName(); From 0222f188a740416b277293c90a637844d89ff5dd Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 23 Oct 2019 16:15:13 -0700 Subject: [PATCH 142/203] do not force people to hq --- launchers/qt/src/LauncherState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 98e476bc99..3689db5aa2 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -441,7 +441,7 @@ void LauncherState::requestSettings() { connect(request, &UserSettingsRequest::finished, this, [this, request]() { auto userSettings = request->getUserSettings(); if (userSettings.homeLocation.isEmpty()) { - _config.homeLocation = "hifi://hq"; + _config.homeLocation = ""; _contentCacheURL = ""; } else { _config.homeLocation = userSettings.homeLocation; From 31c78d7a7083e1f1d7bd9c6d5546a86a0b26fdcc Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 23 Oct 2019 16:42:37 -0700 Subject: [PATCH 143/203] edit home location --- launchers/qt/src/LauncherState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 3689db5aa2..01463b002d 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -441,7 +441,7 @@ void LauncherState::requestSettings() { connect(request, &UserSettingsRequest::finished, this, [this, request]() { auto userSettings = request->getUserSettings(); if (userSettings.homeLocation.isEmpty()) { - _config.homeLocation = ""; + _config.homeLocation = "file:///~/serverless/tutorial.json"; _contentCacheURL = ""; } else { _config.homeLocation = userSettings.homeLocation; From f28d317914448c90f0c2a1697de8832b5dc29975 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 23 Oct 2019 16:43:10 -0700 Subject: [PATCH 144/203] Clean up reamining issues --- interface/src/avatar/MySkeletonModel.cpp | 14 +++++--------- .../src/avatars-renderer/SkeletonModel.cpp | 11 ----------- libraries/avatars/src/AvatarData.cpp | 3 ++- libraries/avatars/src/HeadData.cpp | 9 +-------- 4 files changed, 8 insertions(+), 29 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 0d7bd3f3b3..6fe199aaba 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -112,23 +112,19 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { const HFMModel& hfmModel = getHFMModel(); + MyAvatar* myAvatar = static_cast(_owningAvatar); + assert(myAvatar); + Head* head = _owningAvatar->getHead(); - // AJT: blendshapes TODO: RE-enable this and avoid duplication with - // SkeletonModel::updateRig() - /* - bool eyePosesValid = !head->getHasProceduralEyeMovement(); + bool eyePosesValid = (myAvatar->getControllerPoseInSensorFrame(controller::Action::LEFT_EYE).isValid() || + myAvatar->getControllerPoseInSensorFrame(controller::Action::RIGHT_EYE).isValid()); glm::vec3 lookAt; if (eyePosesValid) { lookAt = head->getLookAtPosition(); // don't apply no-crosseyes code when eyes are being tracked } else { lookAt = avoidCrossedEyes(head->getLookAtPosition()); } - */ - glm::vec3 lookAt = avoidCrossedEyes(head->getLookAtPosition()); - - MyAvatar* myAvatar = static_cast(_owningAvatar); - assert(myAvatar); Rig::ControllerParameters params; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index fc16851942..ccc87c28f3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -110,17 +110,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { assert(!_owningAvatar->isMyAvatar()); Head* head = _owningAvatar->getHead(); - - // AJT: blendshapes TODO: RE-enable this. but move into rig? - /* - bool eyePosesValid = !head->getHasProceduralEyeMovement(); - glm::vec3 lookAt; - if (eyePosesValid) { - lookAt = head->getLookAtPosition(); // don't apply no-crosseyes code etc when eyes are being tracked - } else { - lookAt = avoidCrossedEyes(head->getCorrectedLookAtPosition()); - } - */ glm::vec3 lookAt = avoidCrossedEyes(head->getCorrectedLookAtPosition()); // no need to call Model::updateRig() because otherAvatars get their joint state diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 87d6c57d59..818902968f 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2637,6 +2637,7 @@ enum class JsonAvatarFrameVersion : int { JointRotationsInAbsoluteFrame, JointDefaultPoseBits, JointUnscaledTranslations, + ARKitBlendshapes }; QJsonValue toJsonValue(const JointData& joint) { @@ -2681,7 +2682,7 @@ void AvatarData::avatarEntityDataToJson(QJsonObject& root) const { QJsonObject AvatarData::toJson() const { QJsonObject root; - root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointUnscaledTranslations; + root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::ARKitBlendshapes; if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 93fb0190bb..5e8d5c457f 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -186,14 +186,7 @@ QJsonObject HeadData::toJson() const { void HeadData::fromJson(const QJsonObject& json) { if (json.contains(JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS)) { auto jsonValue = json[JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS]; - if (jsonValue.isArray()) { - QVector blendshapeCoefficients; - QJsonArray blendshapeCoefficientsJson = jsonValue.toArray(); - for (const auto& blendshapeCoefficient : blendshapeCoefficientsJson) { - blendshapeCoefficients.push_back((float)blendshapeCoefficient.toDouble()); - } - setBlendshapeCoefficients(blendshapeCoefficients); - } else if (jsonValue.isObject()) { + if (jsonValue.isObject()) { QJsonObject blendshapeCoefficientsJson = jsonValue.toObject(); for (const QString& name : blendshapeCoefficientsJson.keys()) { float value = (float)blendshapeCoefficientsJson[name].toDouble(); From dc7b611e8e8aded9b618868d1dcfef444dbe25eb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 24 Oct 2019 09:53:11 -0700 Subject: [PATCH 145/203] documentation and warning fixes --- interface/src/avatar/MyAvatar.cpp | 2 -- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f1829db8f6..52083af8f2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6535,7 +6535,6 @@ void MyAvatar::updateEyesLookAtPosition(float deltaTime) { updateLookAtTargetAvatar(); - bool isLookingAtSomeone = false; glm::vec3 lookAtSpot; const MyHead* myHead = getMyHead(); @@ -6596,7 +6595,6 @@ void MyAvatar::updateEyesLookAtPosition(float deltaTime) { avatar && avatar->getLookAtSnappingEnabled() && getLookAtSnappingEnabled(); if (haveLookAtCandidate && mutualLookAtSnappingEnabled) { // If I am looking at someone else, look directly at one of their eyes - isLookingAtSomeone = true; auto lookingAtHead = avatar->getHead(); const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 953b7ef1dc..9197b005be 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -307,7 +307,7 @@ class MyAvatar : public Avatar { * @borrows Avatar.clearAvatarEntity as clearAvatarEntity * @borrows Avatar.hasScriptedBlendshapes as hasScriptedBlendshapes * @borrows Avatar.hasProceduralBlinkFaceMovement as hasProceduralBlinkFaceMovement - * @borrows Avatar.hasEyeFaceMovement as hasEyeFaceMovement + * @borrows Avatar.hasProceduralEyeFaceMovement as hasProceduralEyeFaceMovement * @borrows Avatar.hasAudioEnabledFaceMovement as hasAudioEnabledFaceMovement * @borrows Avatar.setSkeletonModelURL as setSkeletonModelURL * @borrows Avatar.getAttachmentData as getAttachmentData From 83a61555924f42176e7feb09d5cfb1d743b5785c Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Tue, 22 Oct 2019 12:33:47 -0700 Subject: [PATCH 146/203] Added tons of logging to debug interactive window --- interface/resources/qml/InteractiveWindow.qml | 44 +++++++++++++++++++ .../scripting/DesktopScriptingInterface.cpp | 6 ++- interface/src/ui/InteractiveWindow.cpp | 4 ++ .../simplifiedEmote/simplifiedEmote.js | 8 +++- .../ui/qml/SimplifiedEmoteIndicator.qml | 5 ++- scripts/simplifiedUI/ui/simplifiedUI.js | 2 +- 6 files changed, 65 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index b27668f70c..0cf7760886 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -53,6 +53,7 @@ Windows.Window { property var initialized: false; onSourceChanged: { + print("INTERACTIVEWINDOWQML SOURCE CHANGED"); if (dynamicContent) { dynamicContent.destroy(); dynamicContent = null; @@ -67,15 +68,20 @@ Windows.Window { } function updateInteractiveWindowPositionForMode() { + print("INTERACTIVEWINDOWQML UPDATING WINDOW POSITION FOR MODE: PRESENTATION MODE IS ", presentationMode); if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS VIRTUAL"); x = interactiveWindowPosition.x; y = interactiveWindowPosition.y; } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE"); if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) { + print("INTERACTIVEWINDOWQML NATIVE WINDOW IS AT x:0 AND y:0. CALCULATING NEW POSITION."); // default position for native window in center of main application window nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2)); nativeWindow.y = Math.floor(Window.y + (Window.innerHeight / 2) - (interactiveWindowSize.height / 2)); } else { + print("INTERACTIVEWINDOWQML NATIVE WINDOW IS NOT AT x:0 AND y:0. MOVING TO POSITION OF INTERACTIVE WINDOW"); nativeWindow.x = interactiveWindowPosition.x; nativeWindow.y = interactiveWindowPosition.y; } @@ -83,36 +89,46 @@ Windows.Window { } function updateInteractiveWindowSizeForMode() { + print("INTERACTIVEWINDOWQML UPDATING SIZE FOR MODE. SETTING ROOT AND CONTENT HOLDER WIDTH AND HEIGHT TO MATCH INTERACTIVE WINDOW."); root.width = interactiveWindowSize.width; root.height = interactiveWindowSize.height; contentHolder.width = interactiveWindowSize.width; contentHolder.height = interactiveWindowSize.height; if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE SO ALSO SETTING NATIVE WINDOW WIDTH AND HEIGHT TO MATCH INTERACTIVE WINDOW."); nativeWindow.width = interactiveWindowSize.width; nativeWindow.height = interactiveWindowSize.height; } } function updateContentParent() { + print("INTERACTIVEWINDOWQML UPDATE CONTENT PARENT"); if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS VIRTUAL"); contentHolder.parent = root; } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE"); contentHolder.parent = nativeWindow.contentItem; } } function setupPresentationMode() { + print("INTERACTIVEWINDOWQML SET UP PRESENTATION MODE"); if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS VIRTUAL. UPDATING CONTENT PARENT, UPDATING INTERACTIVE WINDOW POSITION FOR MODE, AND SETTING VIRTUAL WINDOW VISIBILITY TO MATCH INTERACTIVE WINDOW VISIBILITY."); if (nativeWindow) { + print("INTERACTIVEWINDOWQML SETTING NATIVE WINDOW TO NOT VISIBLE"); nativeWindow.setVisible(false); } updateContentParent(); updateInteractiveWindowPositionForMode(); shown = interactiveWindowVisible; } else if (presentationMode === Desktop.PresentationMode.NATIVE) { + print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE"); shown = false; if (nativeWindow) { + print("INTERACTIVEWINDOWQML NATIVE EXISTS. UPDATING CONTENT PARENT, UPDATING INTERACTIVE WINDOW POSITION FOR MODE, AND SETTING NATIVE WINDOW VISIBILITY TO MATCH INTERACTIVE WINDOW VISIBILITY."); updateContentParent(); updateInteractiveWindowPositionForMode(); nativeWindow.setVisible(interactiveWindowVisible); @@ -123,6 +139,7 @@ Windows.Window { } Component.onCompleted: { + print("INTERACTIVEWINDOWQML ONROOTCOMPLETED FIX FOR OSX PARENT LOSS!!!!! CONNECTING SIGNALS TO UPDATE CONTENT PARENT WHEN PARENT HEIGHT OR WIDTH CHANGES. SETTING X,Y,WIDTH, AND HEIGHT TO MATCH INTERACTIVE WINDOW. CREATING NATIVE WINDOW. NATIVE WINDOW IS ALWAYS ON TOP FOR NON-WINDOWS."); // Fix for parent loss on OSX: parent.heightChanged.connect(updateContentParent); parent.widthChanged.connect(updateContentParent); @@ -140,10 +157,26 @@ Windows.Window { id: root; width: interactiveWindowSize.width height: interactiveWindowSize.height + Component.onCompleted: { + print("INTERACTIVEWINDOWQML NATIVE WINDOW COMPLETED"); + } Rectangle { color: hifi.colors.baseGray anchors.fill: parent + + MouseArea { + id: helpButtonMouseArea + anchors.fill: parent + hoverEnabled: true + onEntered: { + Tablet.playSound(TabletEnums.ButtonHover); + } + onClicked: { + nativeWindow.x = 0; + nativeWindow.y = 0; + } + } } }', root, 'InteractiveWindow.qml->nativeWindow'); nativeWindow.title = root.title; @@ -167,27 +200,35 @@ Windows.Window { nativeWindow.xChanged.connect(function() { if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + print("NATIVE WINDOW X CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y); } }); + nativeWindow.yChanged.connect(function() { + if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + print("NATIVE WINDOW Y CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y); } }); nativeWindow.widthChanged.connect(function() { if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + print("NATIVE WINDOW WIDTH CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height); } }); + nativeWindow.heightChanged.connect(function() { if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + print("NATIVE WINDOW HEIGHT CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height); } }); nativeWindow.closing.connect(function(closeEvent) { + print("NATIVE WINDOW CLOSING SIGNAL."); closeEvent.accepted = false; windowClosed(); }); @@ -289,7 +330,10 @@ Windows.Window { onYChanged: { if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + print("VIRTUAL WINDOW Y CHANGE"); interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y); + } else { + print("NATIVE WINDOW Y CHANGE"); } } diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index ae4af48cd6..aa4329ef33 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -133,6 +133,7 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title) } InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) { + qDebug() << "CREATING WINDOW WITH URL " << sourceUrl << " AND PROPERTIES " << properties; if (QThread::currentThread() != thread()) { InteractiveWindowPointer interactiveWindow = nullptr; BLOCKING_INVOKE_METHOD(this, "createWindowOnThread", @@ -140,7 +141,8 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& Q_ARG(QString, sourceUrl), Q_ARG(QVariantMap, properties), Q_ARG(QThread*, QThread::currentThread())); - return interactiveWindow; + qDebug() << "RETURNING INTERACTIVE WINDOW " << interactiveWindow; + return interactiveWindow; } @@ -161,8 +163,10 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const Q // that the source URL is permitted const auto& urlValidator = OffscreenQmlSurface::getUrlValidator(); if (!urlValidator(sourceUrl)) { + qDebug("INTERACTIVE WINDOW SOURCE URL WAS NOT VALID"); return nullptr; } + qDebug() << "WINDOW SOURCE URL: " << sourceUrl; InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties, _restricted); window->moveToThread(targetThread); return window; diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index 6cc26e2409..cb6a02d2cf 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -167,6 +167,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection); if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) { + qDebug() << "CREATING WINDOW THAT IS DOCKED WITH NATIVE PRES MODE"; QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap(); Qt::DockWidgetArea dockArea = Qt::TopDockWidgetArea; QString title; @@ -195,6 +196,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap * @property {InteractiveWindow.DockArea} dockArea - The edge of the Interface window to dock to. */ if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) { + qDebug() << "CREATING DOCK AREA FOR NATIVE WINDOW " << DOCK_AREA_PROPERTY; DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt(); int tempWidth = 0; int tempHeight = 0; @@ -253,6 +255,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap _dockWidget->setObjectName("DockedWidget"); mainWindow->addDockWidget(dockArea, _dockWidget.get()); } else { + qDebug() << "CREATING OTHER WINDOW (NOT DOCKED WITH NATIVE PRES MODE)"; auto contextInitLambda = [&](QQmlContext* context) { // If the restricted flag is on, the web content will not be able to access local files ContextAwareProfile::restrictContext(context, restricted); @@ -336,6 +339,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); } + qDebug() << "CREATING OTHER WINDOW WITH SOURCE URL: " << sourceUrl; object->setObjectName("InteractiveWindow"); object->setProperty(SOURCE_PROPERTY, sourceURL); }; diff --git a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js index d7d6279e10..caab3c1e46 100644 --- a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js +++ b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js @@ -380,6 +380,7 @@ function onMessageFromEmoteAppBar(message) { if (message.source !== EMOTE_APP_BAR_MESSAGE_SOURCE) { return; } + print("EMOTE WINDOW MESSAGE"); switch (message.method) { case "positive": if (!message.data.isPressingAndHolding) { @@ -521,9 +522,11 @@ var EMOTE_APP_BAR_WINDOW_FLAGS = 0x00000001 | // Qt::Window var emoteAppBarWindow = false; function showEmoteAppBar() { if (emoteAppBarWindow) { + print("EMOTE APP BAR WINDOW ALREADY EXISTS. DO NOT SHOW AGAIN."); return; } + print("CREATE EMOTE WINDOW"); emoteAppBarWindow = Desktop.createWindow(EMOTE_APP_BAR_QML_PATH, { title: EMOTE_APP_BAR_WINDOW_TITLE, presentationMode: EMOTE_APP_BAR_PRESENTATION_MODE, @@ -539,6 +542,7 @@ function showEmoteAppBar() { overrideFlags: EMOTE_APP_BAR_WINDOW_FLAGS }); + print("EMOTE APP BAR WINDOW CREATED: ", emoteAppBarWindow); emoteAppBarWindow.fromQml.connect(onMessageFromEmoteAppBar); } @@ -548,6 +552,7 @@ function showEmoteAppBar() { // We should add that functionality to the Window Scripting Interface, and remove `isWindowMinimized` below. var isWindowMinimized = false; function maybeChangeEmoteIndicatorVisibility(desiredVisibility) { + print("MAYBE CHANGE EMOTE WINDOW VISIBILITY ", desiredVisibility); if (isWindowMinimized || HMD.active) { desiredVisibility = false; } @@ -582,6 +587,7 @@ var emojiCodeMap; var customEmojiCodeMap; var _this; function setup() { + print("STARTING EMOTE SCRIPT"); deleteOldReticles(); // make a map of just the utf codes to help with accesing @@ -798,4 +804,4 @@ function toggleEmojiApp() { var emote = setup(); -module.exports = emote; \ No newline at end of file +module.exports = emote; diff --git a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml index bbd1d4d735..eb6f105fac 100644 --- a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml +++ b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml @@ -1,4 +1,4 @@ -// +// // SimplifiedEmoteIndicator.qml // // Created by Milad Nazeri on 2019-08-05 @@ -39,6 +39,9 @@ Rectangle { readonly property string emoteIconSource: "images/emote_Icon.svg" property bool allowEmoteDrawerExpansion: Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true) + Component.onCompleted: { + print("EMOTE APP BAR ROOT WINDOW HAS COMPLETED LOADING"); + } onRequestedWidthChanged: { root.requestNewWidth(root.requestedWidth); diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js index 2ce6a3e073..4939f9e426 100644 --- a/scripts/simplifiedUI/ui/simplifiedUI.js +++ b/scripts/simplifiedUI/ui/simplifiedUI.js @@ -718,7 +718,7 @@ function restoreLODSettings() { var nametag = Script.require("./simplifiedNametag/simplifiedNametag.js"); var si = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js"); -var simplifiedEmote = Script.require("../simplifiedEmote/simplifiedEmote.js"); +var simplifiedEmote = Script.require("../simplifiedEmote/simplifiedEmote.js?" + Date.now()); var oldShowAudioTools; var oldShowBubbleTools; var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false); From 8e8c157567b3d09406d790a36d9a6998523b2598 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Wed, 23 Oct 2019 16:03:46 -0700 Subject: [PATCH 147/203] Debugging stuff --- interface/resources/qml/InteractiveWindow.qml | 91 ++++++++----------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 0cf7760886..1f9f48d55e 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -53,7 +53,6 @@ Windows.Window { property var initialized: false; onSourceChanged: { - print("INTERACTIVEWINDOWQML SOURCE CHANGED"); if (dynamicContent) { dynamicContent.destroy(); dynamicContent = null; @@ -66,22 +65,28 @@ Windows.Window { } }); } + + Timer { + id: timer + } + + function delay(delayTimeMS, delayFunction) { + timer.interval = delayTimeMS; + timer.repeat = true; + timer.triggered.connect(delayFunction); + timer.start(); + } function updateInteractiveWindowPositionForMode() { - print("INTERACTIVEWINDOWQML UPDATING WINDOW POSITION FOR MODE: PRESENTATION MODE IS ", presentationMode); if (presentationMode === Desktop.PresentationMode.VIRTUAL) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS VIRTUAL"); x = interactiveWindowPosition.x; y = interactiveWindowPosition.y; } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE"); if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) { - print("INTERACTIVEWINDOWQML NATIVE WINDOW IS AT x:0 AND y:0. CALCULATING NEW POSITION."); // default position for native window in center of main application window nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2)); nativeWindow.y = Math.floor(Window.y + (Window.innerHeight / 2) - (interactiveWindowSize.height / 2)); } else { - print("INTERACTIVEWINDOWQML NATIVE WINDOW IS NOT AT x:0 AND y:0. MOVING TO POSITION OF INTERACTIVE WINDOW"); nativeWindow.x = interactiveWindowPosition.x; nativeWindow.y = interactiveWindowPosition.y; } @@ -89,60 +94,54 @@ Windows.Window { } function updateInteractiveWindowSizeForMode() { - print("INTERACTIVEWINDOWQML UPDATING SIZE FOR MODE. SETTING ROOT AND CONTENT HOLDER WIDTH AND HEIGHT TO MATCH INTERACTIVE WINDOW."); root.width = interactiveWindowSize.width; root.height = interactiveWindowSize.height; contentHolder.width = interactiveWindowSize.width; contentHolder.height = interactiveWindowSize.height; if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE SO ALSO SETTING NATIVE WINDOW WIDTH AND HEIGHT TO MATCH INTERACTIVE WINDOW."); nativeWindow.width = interactiveWindowSize.width; nativeWindow.height = interactiveWindowSize.height; } } function updateContentParent() { - print("INTERACTIVEWINDOWQML UPDATE CONTENT PARENT"); if (presentationMode === Desktop.PresentationMode.VIRTUAL) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS VIRTUAL"); contentHolder.parent = root; + print("INTERACTIVEWINDOWQML CHANGED CONTENT PARENT TO ROOT. CONTENT HOLDER PARENT IS ", contentHolder.parent); } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE"); contentHolder.parent = nativeWindow.contentItem; + print("INTERACTIVEWINDOWQML CHANGED CONTENT PARENT TO NATIVE WINDOW CONTENTITEM: ", nativeWindow.contentItem, ". CONTENT HOLDER PARENT IS ", contentHolder.parent); + } else if (!nativeWindow) { + print("INTERACTIVEWINDOWQML COULD NOT CHANGE CONTENT PARENT. NO NATIVE WINDOW."); + } else { + print("INTERACTIVEWINDOWQML COULD OT CHANGE CONTENT PARENT BUT HAVE A NATIVE WINDOW"); } } function setupPresentationMode() { - print("INTERACTIVEWINDOWQML SET UP PRESENTATION MODE"); if (presentationMode === Desktop.PresentationMode.VIRTUAL) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS VIRTUAL. UPDATING CONTENT PARENT, UPDATING INTERACTIVE WINDOW POSITION FOR MODE, AND SETTING VIRTUAL WINDOW VISIBILITY TO MATCH INTERACTIVE WINDOW VISIBILITY."); if (nativeWindow) { - print("INTERACTIVEWINDOWQML SETTING NATIVE WINDOW TO NOT VISIBLE"); nativeWindow.setVisible(false); } - updateContentParent(); updateInteractiveWindowPositionForMode(); shown = interactiveWindowVisible; } else if (presentationMode === Desktop.PresentationMode.NATIVE) { - print("INTERACTIVEWINDOWQML PRESENTATION MODE IS NATIVE"); shown = false; if (nativeWindow) { - print("INTERACTIVEWINDOWQML NATIVE EXISTS. UPDATING CONTENT PARENT, UPDATING INTERACTIVE WINDOW POSITION FOR MODE, AND SETTING NATIVE WINDOW VISIBILITY TO MATCH INTERACTIVE WINDOW VISIBILITY."); - updateContentParent(); + print("INTERACTIVEWINDOWQML SET UP PRES MODE FOUND NATIVE WINDOW"); updateInteractiveWindowPositionForMode(); nativeWindow.setVisible(interactiveWindowVisible); + } else { + print("INTERACTIVEWINDOWQML SET UP PRES MODE DID NOT FIND NATIVE WINDOW"); } } else if (presentationMode === modeNotSet) { - console.error("presentationMode should be set."); + console.error("presentationMode should be set"); } } Component.onCompleted: { - print("INTERACTIVEWINDOWQML ONROOTCOMPLETED FIX FOR OSX PARENT LOSS!!!!! CONNECTING SIGNALS TO UPDATE CONTENT PARENT WHEN PARENT HEIGHT OR WIDTH CHANGES. SETTING X,Y,WIDTH, AND HEIGHT TO MATCH INTERACTIVE WINDOW. CREATING NATIVE WINDOW. NATIVE WINDOW IS ALWAYS ON TOP FOR NON-WINDOWS."); - // Fix for parent loss on OSX: - parent.heightChanged.connect(updateContentParent); - parent.widthChanged.connect(updateContentParent); + print("INTERACTIVEWINDOWQML ONROOTCOMPLETED COMPLETED. ID IS ", this, " . CONTENT HOLDER PARENT IS ", contentHolder.parent); x = interactiveWindowPosition.x; y = interactiveWindowPosition.y; @@ -158,25 +157,13 @@ Windows.Window { width: interactiveWindowSize.width height: interactiveWindowSize.height Component.onCompleted: { - print("INTERACTIVEWINDOWQML NATIVE WINDOW COMPLETED"); + // calling setupPresentationMode from here does not work because the fn will not find the native window + print("INTERACTIVEWINDOWQML NATIVE WINDOW COMPLETED. ID IS ", this, " CONTENT HOLDER PARENT IS ", contentHolder.parent); } Rectangle { color: hifi.colors.baseGray anchors.fill: parent - - MouseArea { - id: helpButtonMouseArea - anchors.fill: parent - hoverEnabled: true - onEntered: { - Tablet.playSound(TabletEnums.ButtonHover); - } - onClicked: { - nativeWindow.x = 0; - nativeWindow.y = 0; - } - } } }', root, 'InteractiveWindow.qml->nativeWindow'); nativeWindow.title = root.title; @@ -200,50 +187,40 @@ Windows.Window { nativeWindow.xChanged.connect(function() { if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { - print("NATIVE WINDOW X CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y); } }); nativeWindow.yChanged.connect(function() { - if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { - print("NATIVE WINDOW Y CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y); } }); nativeWindow.widthChanged.connect(function() { if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { - print("NATIVE WINDOW WIDTH CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height); } }); nativeWindow.heightChanged.connect(function() { if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { - print("NATIVE WINDOW HEIGHT CHANGED SIGNAL. THE WINDOW WAS NATIVE AND VISIBLE"); interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height); } }); nativeWindow.closing.connect(function(closeEvent) { - print("NATIVE WINDOW CLOSING SIGNAL."); closeEvent.accepted = false; windowClosed(); }); - + // finally set the initial window mode: setupPresentationMode(); - + updateContentParent(); +//TODO move this to wherever initialization is actually completed initialized = true; } - Component.onDestruction: { - parent.heightChanged.disconnect(updateContentParent); - parent.widthChanged.disconnect(updateContentParent); - } - // Handle message traffic from the script that launched us to the loaded QML function fromScript(message) { if (root.dynamicContent && root.dynamicContent.fromScript) { @@ -273,8 +250,8 @@ Windows.Window { interactiveWindowSize.width = newWidth; updateInteractiveWindowSizeForMode(); } - function onRequestNewHeight(newWidth) { - interactiveWindowSize.width = newWidth; + function onRequestNewHeight(newHeight) { + interactiveWindowSize.height = newHeight; updateInteractiveWindowSizeForMode(); } @@ -330,10 +307,7 @@ Windows.Window { onYChanged: { if (presentationMode === Desktop.PresentationMode.VIRTUAL) { - print("VIRTUAL WINDOW Y CHANGE"); interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y); - } else { - print("NATIVE WINDOW Y CHANGE"); } } @@ -352,6 +326,7 @@ Windows.Window { onPresentationModeChanged: { if (initialized) { setupPresentationMode(); + updateContentParent(); } } @@ -367,5 +342,11 @@ Windows.Window { Item { id: contentHolder anchors.fill: parent + Component.onCompleted: { + print("INTERACTIVEWINDOWQML CONTENTHOLDER COMPLETED. ID IS ", contentHolder, " . CONTENT HOLDER PARENT IS ", contentHolder.parent); + } + onParentChanged: { + print("INTERACTIVEWINDOWQML NATIVE WINDOW PARENT CHANGED. CONTENT HOLDER PARENT IS ", contentHolder.parent); + } } } From f8102be74d64adde93ad138cfedb7fd56dfbb263 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Wed, 23 Oct 2019 16:42:28 -0700 Subject: [PATCH 148/203] Added a timeout to update the content holder parent after interactive window has finished loading --- interface/resources/qml/InteractiveWindow.qml | 32 +++++-------------- .../scripting/DesktopScriptingInterface.cpp | 6 +--- interface/src/ui/InteractiveWindow.cpp | 4 --- .../simplifiedEmote/simplifiedEmote.js | 6 ---- .../ui/qml/SimplifiedEmoteIndicator.qml | 3 -- scripts/simplifiedUI/ui/simplifiedUI.js | 2 +- 6 files changed, 10 insertions(+), 43 deletions(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 1f9f48d55e..3741db74c1 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -72,7 +72,7 @@ Windows.Window { function delay(delayTimeMS, delayFunction) { timer.interval = delayTimeMS; - timer.repeat = true; + timer.repeat = false; timer.triggered.connect(delayFunction); timer.start(); } @@ -108,14 +108,8 @@ Windows.Window { function updateContentParent() { if (presentationMode === Desktop.PresentationMode.VIRTUAL) { contentHolder.parent = root; - print("INTERACTIVEWINDOWQML CHANGED CONTENT PARENT TO ROOT. CONTENT HOLDER PARENT IS ", contentHolder.parent); } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { contentHolder.parent = nativeWindow.contentItem; - print("INTERACTIVEWINDOWQML CHANGED CONTENT PARENT TO NATIVE WINDOW CONTENTITEM: ", nativeWindow.contentItem, ". CONTENT HOLDER PARENT IS ", contentHolder.parent); - } else if (!nativeWindow) { - print("INTERACTIVEWINDOWQML COULD NOT CHANGE CONTENT PARENT. NO NATIVE WINDOW."); - } else { - print("INTERACTIVEWINDOWQML COULD OT CHANGE CONTENT PARENT BUT HAVE A NATIVE WINDOW"); } } @@ -129,20 +123,16 @@ Windows.Window { } else if (presentationMode === Desktop.PresentationMode.NATIVE) { shown = false; if (nativeWindow) { - print("INTERACTIVEWINDOWQML SET UP PRES MODE FOUND NATIVE WINDOW"); updateInteractiveWindowPositionForMode(); nativeWindow.setVisible(interactiveWindowVisible); - } else { - print("INTERACTIVEWINDOWQML SET UP PRES MODE DID NOT FIND NATIVE WINDOW"); } } else if (presentationMode === modeNotSet) { - console.error("presentationMode should be set"); + console.error("presentationMode should be set."); } } Component.onCompleted: { - print("INTERACTIVEWINDOWQML ONROOTCOMPLETED COMPLETED. ID IS ", this, " . CONTENT HOLDER PARENT IS ", contentHolder.parent); - + x = interactiveWindowPosition.x; y = interactiveWindowPosition.y; width = interactiveWindowSize.width; @@ -157,8 +147,9 @@ Windows.Window { width: interactiveWindowSize.width height: interactiveWindowSize.height Component.onCompleted: { - // calling setupPresentationMode from here does not work because the fn will not find the native window - print("INTERACTIVEWINDOWQML NATIVE WINDOW COMPLETED. ID IS ", this, " CONTENT HOLDER PARENT IS ", contentHolder.parent); + delay(500, function() { + updateContentParent(); + }); } Rectangle { @@ -213,11 +204,10 @@ Windows.Window { closeEvent.accepted = false; windowClosed(); }); - + // finally set the initial window mode: setupPresentationMode(); - updateContentParent(); -//TODO move this to wherever initialization is actually completed + initialized = true; } @@ -342,11 +332,5 @@ Windows.Window { Item { id: contentHolder anchors.fill: parent - Component.onCompleted: { - print("INTERACTIVEWINDOWQML CONTENTHOLDER COMPLETED. ID IS ", contentHolder, " . CONTENT HOLDER PARENT IS ", contentHolder.parent); - } - onParentChanged: { - print("INTERACTIVEWINDOWQML NATIVE WINDOW PARENT CHANGED. CONTENT HOLDER PARENT IS ", contentHolder.parent); - } } } diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index aa4329ef33..ae4af48cd6 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -133,7 +133,6 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title) } InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) { - qDebug() << "CREATING WINDOW WITH URL " << sourceUrl << " AND PROPERTIES " << properties; if (QThread::currentThread() != thread()) { InteractiveWindowPointer interactiveWindow = nullptr; BLOCKING_INVOKE_METHOD(this, "createWindowOnThread", @@ -141,8 +140,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& Q_ARG(QString, sourceUrl), Q_ARG(QVariantMap, properties), Q_ARG(QThread*, QThread::currentThread())); - qDebug() << "RETURNING INTERACTIVE WINDOW " << interactiveWindow; - return interactiveWindow; + return interactiveWindow; } @@ -163,10 +161,8 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const Q // that the source URL is permitted const auto& urlValidator = OffscreenQmlSurface::getUrlValidator(); if (!urlValidator(sourceUrl)) { - qDebug("INTERACTIVE WINDOW SOURCE URL WAS NOT VALID"); return nullptr; } - qDebug() << "WINDOW SOURCE URL: " << sourceUrl; InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties, _restricted); window->moveToThread(targetThread); return window; diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index cb6a02d2cf..6cc26e2409 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -167,7 +167,6 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection); if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) { - qDebug() << "CREATING WINDOW THAT IS DOCKED WITH NATIVE PRES MODE"; QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap(); Qt::DockWidgetArea dockArea = Qt::TopDockWidgetArea; QString title; @@ -196,7 +195,6 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap * @property {InteractiveWindow.DockArea} dockArea - The edge of the Interface window to dock to. */ if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) { - qDebug() << "CREATING DOCK AREA FOR NATIVE WINDOW " << DOCK_AREA_PROPERTY; DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt(); int tempWidth = 0; int tempHeight = 0; @@ -255,7 +253,6 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap _dockWidget->setObjectName("DockedWidget"); mainWindow->addDockWidget(dockArea, _dockWidget.get()); } else { - qDebug() << "CREATING OTHER WINDOW (NOT DOCKED WITH NATIVE PRES MODE)"; auto contextInitLambda = [&](QQmlContext* context) { // If the restricted flag is on, the web content will not be able to access local files ContextAwareProfile::restrictContext(context, restricted); @@ -339,7 +336,6 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); } - qDebug() << "CREATING OTHER WINDOW WITH SOURCE URL: " << sourceUrl; object->setObjectName("InteractiveWindow"); object->setProperty(SOURCE_PROPERTY, sourceURL); }; diff --git a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js index caab3c1e46..e0ef9aaf5d 100644 --- a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js +++ b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js @@ -380,7 +380,6 @@ function onMessageFromEmoteAppBar(message) { if (message.source !== EMOTE_APP_BAR_MESSAGE_SOURCE) { return; } - print("EMOTE WINDOW MESSAGE"); switch (message.method) { case "positive": if (!message.data.isPressingAndHolding) { @@ -522,11 +521,9 @@ var EMOTE_APP_BAR_WINDOW_FLAGS = 0x00000001 | // Qt::Window var emoteAppBarWindow = false; function showEmoteAppBar() { if (emoteAppBarWindow) { - print("EMOTE APP BAR WINDOW ALREADY EXISTS. DO NOT SHOW AGAIN."); return; } - print("CREATE EMOTE WINDOW"); emoteAppBarWindow = Desktop.createWindow(EMOTE_APP_BAR_QML_PATH, { title: EMOTE_APP_BAR_WINDOW_TITLE, presentationMode: EMOTE_APP_BAR_PRESENTATION_MODE, @@ -542,7 +539,6 @@ function showEmoteAppBar() { overrideFlags: EMOTE_APP_BAR_WINDOW_FLAGS }); - print("EMOTE APP BAR WINDOW CREATED: ", emoteAppBarWindow); emoteAppBarWindow.fromQml.connect(onMessageFromEmoteAppBar); } @@ -552,7 +548,6 @@ function showEmoteAppBar() { // We should add that functionality to the Window Scripting Interface, and remove `isWindowMinimized` below. var isWindowMinimized = false; function maybeChangeEmoteIndicatorVisibility(desiredVisibility) { - print("MAYBE CHANGE EMOTE WINDOW VISIBILITY ", desiredVisibility); if (isWindowMinimized || HMD.active) { desiredVisibility = false; } @@ -587,7 +582,6 @@ var emojiCodeMap; var customEmojiCodeMap; var _this; function setup() { - print("STARTING EMOTE SCRIPT"); deleteOldReticles(); // make a map of just the utf codes to help with accesing diff --git a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml index eb6f105fac..674966c228 100644 --- a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml +++ b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml @@ -39,9 +39,6 @@ Rectangle { readonly property string emoteIconSource: "images/emote_Icon.svg" property bool allowEmoteDrawerExpansion: Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true) - Component.onCompleted: { - print("EMOTE APP BAR ROOT WINDOW HAS COMPLETED LOADING"); - } onRequestedWidthChanged: { root.requestNewWidth(root.requestedWidth); diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js index 4939f9e426..2ce6a3e073 100644 --- a/scripts/simplifiedUI/ui/simplifiedUI.js +++ b/scripts/simplifiedUI/ui/simplifiedUI.js @@ -718,7 +718,7 @@ function restoreLODSettings() { var nametag = Script.require("./simplifiedNametag/simplifiedNametag.js"); var si = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js"); -var simplifiedEmote = Script.require("../simplifiedEmote/simplifiedEmote.js?" + Date.now()); +var simplifiedEmote = Script.require("../simplifiedEmote/simplifiedEmote.js"); var oldShowAudioTools; var oldShowBubbleTools; var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false); From 9a8fda2705c6b3fab2a2adcaefbfdcd7ac542366 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Thu, 24 Oct 2019 10:24:18 -0700 Subject: [PATCH 149/203] Feedback changes --- interface/resources/qml/InteractiveWindow.qml | 18 ++++++++---------- .../simplifiedEmote/simplifiedEmote.js | 2 +- .../ui/qml/SimplifiedEmoteIndicator.qml | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 3741db74c1..3a4ec8838c 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -68,13 +68,11 @@ Windows.Window { Timer { id: timer - } - - function delay(delayTimeMS, delayFunction) { - timer.interval = delayTimeMS; - timer.repeat = false; - timer.triggered.connect(delayFunction); - timer.start(); + interval: 500; + repeat: false; + onTriggered: { + updateContentParent(); + } } function updateInteractiveWindowPositionForMode() { @@ -146,10 +144,10 @@ Windows.Window { id: root; width: interactiveWindowSize.width height: interactiveWindowSize.height + // fix for missing content on OSX initial startup with a non-maximized interface window. It seems that in this case, we cannot update + // the content parent during creation of the Window root. This added delay will update the parent after the root has finished loading. Component.onCompleted: { - delay(500, function() { - updateContentParent(); - }); + timer.start(); } Rectangle { diff --git a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js index e0ef9aaf5d..d7d6279e10 100644 --- a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js +++ b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js @@ -798,4 +798,4 @@ function toggleEmojiApp() { var emote = setup(); -module.exports = emote; +module.exports = emote; \ No newline at end of file diff --git a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml index 674966c228..bbd1d4d735 100644 --- a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml +++ b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml @@ -1,4 +1,4 @@ -// +// // SimplifiedEmoteIndicator.qml // // Created by Milad Nazeri on 2019-08-05 From 5dd5447095e05ea222fb3a2279e3f3ac50d74b75 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Thu, 24 Oct 2019 15:03:55 -0700 Subject: [PATCH 150/203] moved call to hmd device name prior to locking device mutex --- libraries/audio-client/src/AudioClient.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 995fe20886..18e8d11fc9 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -83,7 +83,6 @@ Mutex _recordMutex; HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode); - static QString getHmdAudioDeviceName(QAudio::Mode mode) { foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { if (displayPlugin && displayPlugin->isHmd()) { @@ -100,6 +99,10 @@ static QString getHmdAudioDeviceName(QAudio::Mode mode) { // thread-safe QList getAvailableDevices(QAudio::Mode mode) { + //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client + //cannot properly shut down. + QString hmdDeviceName = getHmdAudioDeviceName(mode); + // NOTE: availableDevices() clobbers the Qt internal device list Lock lock(_deviceMutex); auto devices = QAudioDeviceInfo::availableDevices(mode); @@ -110,8 +113,7 @@ QList getAvailableDevices(QAudio::Mode mode) { } newDevices.push_front(defaultAudioDeviceForMode(mode)); - - QString hmdDeviceName = getHmdAudioDeviceName(mode); + if (!hmdDeviceName.isNull() && !hmdDeviceName.isEmpty()) { HifiAudioDeviceInfo hmdDevice; foreach(auto device, newDevices) { @@ -803,6 +805,7 @@ void AudioClient::stop() { // Destruction of the pointers will occur when the parent object (this) is destroyed) { Lock lock(_checkDevicesMutex); + _checkDevicesTimer->stop(); _checkDevicesTimer = nullptr; } { From a67904212d3c921ea5e0719731311f7185a0da03 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 24 Oct 2019 15:52:44 -0700 Subject: [PATCH 151/203] Remove AvatarTransit log spam --- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index bea9f979b8..277b293728 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -165,9 +165,6 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av _status = Status::ENDED; } - if (previousStatus != _status) { - qDebug(avatars_renderer) << "AvatarTransit " << avatarTransitStatusToStringMap[(int)previousStatus] << "->" << avatarTransitStatusToStringMap[_status]; - } return _status; } From 58a3cf6e675c7f2057c40087098d3adf68e2daac Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 25 Oct 2019 10:13:23 -0700 Subject: [PATCH 152/203] finishing qt-lite build info --- ...e-build-steps-win.md => qt-lite-build-steps.md} | 14 ++++++++++++++ tools/qt-builder/qt-lite-windows-config.opt | 1 - 2 files changed, 14 insertions(+), 1 deletion(-) rename tools/qt-builder/{qt-lite-build-steps-win.md => qt-lite-build-steps.md} (77%) diff --git a/tools/qt-builder/qt-lite-build-steps-win.md b/tools/qt-builder/qt-lite-build-steps.md similarity index 77% rename from tools/qt-builder/qt-lite-build-steps-win.md rename to tools/qt-builder/qt-lite-build-steps.md index a693acbb9f..2f8d115a24 100644 --- a/tools/qt-builder/qt-lite-build-steps-win.md +++ b/tools/qt-builder/qt-lite-build-steps.md @@ -1,3 +1,9 @@ +## Requirements +### Windows +1. Visual Studio 2017 +2. Perl - http://strawberryperl.com/ + + # Windows Command Prompt ``` @@ -24,6 +30,7 @@ cd qt-build ``` Download ssl-static.zip and unzip to ssl-static folder next to qt5 folder +`https://hifi-content.s3.amazonaws.com/dante/ssl-static-windows.zip` remove config.opt in the build folder copy over the config file from qt-builder ``` @@ -64,6 +71,7 @@ cd qt-build ``` Download ssl-static.zip and unzip to ssl-static folder next to qt5 folder +`https://hifi-content.s3.amazonaws.com/dante/openssl-static-osx.zip` copy over the config file from qt-builder ``` cp path-to-your-hifi-directory/tools/qt-builder/qt-lite-osx-config ./config.opt @@ -88,3 +96,9 @@ make install ``` +#Building a static version of openssl on windows +https://wiki.openssl.org/index.php/Compilation_and_Installation#OpenSSL_1.0.2 +follow the instructions in that link. + +Keeping in mind that you need to use the non-dll commands (ex: 'nmake -f ms\ntdll.mak clean for the DLL target and nmake -f ms\nt.mak clean for static libraries.' +so you'd want to use 'ms\nt.mak' \ No newline at end of file diff --git a/tools/qt-builder/qt-lite-windows-config.opt b/tools/qt-builder/qt-lite-windows-config.opt index dae6a6f39f..48dd13ef0c 100644 --- a/tools/qt-builder/qt-lite-windows-config.opt +++ b/tools/qt-builder/qt-lite-windows-config.opt @@ -182,7 +182,6 @@ OPENSSL_LIBS=-lssleay32 -llibeay32 -no-feature-progressdialog -no-feature-quick-positioners -no-feature-sha3-fast --no-feature-shortcut -no-feature-completer -no-feature-image_heuristic_mask -no-feature-image_text From 49465b1b961196ad21dfe06ed7621d616c7b4428 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 25 Oct 2019 12:10:26 -0700 Subject: [PATCH 153/203] null default device --- libraries/audio-client/src/AudioClient.cpp | 79 +++++++++++----------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 18e8d11fc9..0a6b9e1c9c 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -81,7 +81,7 @@ using Lock = std::unique_lock; Mutex _deviceMutex; Mutex _recordMutex; -HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode); +QString defaultAudioDeviceName(QAudio::Mode mode); static QString getHmdAudioDeviceName(QAudio::Mode mode) { foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { @@ -102,18 +102,29 @@ QList getAvailableDevices(QAudio::Mode mode) { //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client //cannot properly shut down. QString hmdDeviceName = getHmdAudioDeviceName(mode); + QString defDeviceName = defaultAudioDeviceName(mode); // NOTE: availableDevices() clobbers the Qt internal device list Lock lock(_deviceMutex); auto devices = QAudioDeviceInfo::availableDevices(mode); - + + HifiAudioDeviceInfo defaultDesktopDevice; QList newDevices; for (auto& device : devices) { newDevices.push_back(HifiAudioDeviceInfo(device, false, mode)); + if (device.deviceName() == defDeviceName.trimmed()) { + defaultDesktopDevice = HifiAudioDeviceInfo(device, true, mode, HifiAudioDeviceInfo::desktop); + } } - - newDevices.push_front(defaultAudioDeviceForMode(mode)); - + + if (!defaultDesktopDevice.getDevice().isNull()) { + newDevices.push_front(defaultDesktopDevice); + } else { + qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list:" << defDeviceName + << "Setting Default to: " << devices.first().deviceName(); + defaultDesktopDevice = HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::desktop); + } + if (!hmdDeviceName.isNull() && !hmdDeviceName.isEmpty()) { HifiAudioDeviceInfo hmdDevice; foreach(auto device, newDevices) { @@ -484,8 +495,27 @@ QString AudioClient::getWinDeviceName(wchar_t* guid) { #endif HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { - QList devices = QAudioDeviceInfo::availableDevices(mode); - + QString deviceName = defaultAudioDeviceName(mode); +#if defined (Q_OS_ANDROID) + if (mode == QAudio::AudioInput) { + Setting::Handle enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED); + bool aecEnabled = enableAEC.get(); + auto audioClient = DependencyManager::get(); + bool headsetOn = audioClient ? audioClient->isHeadsetPluggedIn() : false; + for (QAudioDeviceInfo inputDevice : devices) { + if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || + ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { + return HifiAudioDeviceInfo(inputDevice, false, QAudio::AudioInput); + } + } + } +#endif + return getNamedAudioDeviceForMode(mode, deviceName); +} + +QString defaultAudioDeviceName(QAudio::Mode mode) { + QString deviceName; + #ifdef __APPLE__ if (devices.size() > 1) { AudioDeviceID defaultDeviceID = 0; @@ -516,18 +546,13 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { NULL, &propertySize, &deviceName); if (!getPropertyError && propertySize) { - // find a device in the list that matches the name we have and return it - foreach(QAudioDeviceInfo audioDevice, devices){ - if (audioDevice.deviceName() == CFStringGetCStringPtr(deviceName, kCFStringEncodingMacRoman)) { - return HifiAudioDeviceInfo(audioDevice, true, mode, HifiAudioDeviceInfo::desktop); - } + deviceName = CFStringGetCStringPtr(deviceName, kCFStringEncodingMacRoman)); } } } } #endif #ifdef WIN32 - QString deviceName; //Check for Windows Vista or higher, IMMDeviceEnumerator doesn't work below that. if (!IsWindowsVistaOrGreater()) { // lower then vista if (mode == QAudio::AudioInput) { @@ -571,37 +596,13 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { CoUninitialize(); } - HifiAudioDeviceInfo foundDevice; - foreach(QAudioDeviceInfo audioDevice, devices) { - if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { - foundDevice = HifiAudioDeviceInfo(audioDevice, true, mode, HifiAudioDeviceInfo::desktop); - break; - } - } #if !defined(NDEBUG) qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input") - << " [" << deviceName << "] [" << foundDevice.deviceName() << "]"; -#endif - return foundDevice; + << " [" << deviceName << "] [" << "]"; #endif -#if defined (Q_OS_ANDROID) - if (mode == QAudio::AudioInput) { - Setting::Handle enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED); - bool aecEnabled = enableAEC.get(); - auto audioClient = DependencyManager::get(); - bool headsetOn = audioClient ? audioClient->isHeadsetPluggedIn() : false; - for (QAudioDeviceInfo inputDevice : devices) { - if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || - ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { - return HifiAudioDeviceInfo(inputDevice, false, QAudio::AudioInput); - } - } - } #endif - // fallback for failed lookup is the default device - return (mode == QAudio::AudioInput) ? HifiAudioDeviceInfo(QAudioDeviceInfo::defaultInputDevice(), true, mode, HifiAudioDeviceInfo::desktop) : - HifiAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice(), true, mode, HifiAudioDeviceInfo::desktop); + return deviceName; } bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) { From 52637320f0fe1a9d04e81abea67184c462756236 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 25 Oct 2019 12:49:03 -0700 Subject: [PATCH 154/203] mac build fix --- libraries/audio-client/src/AudioClient.cpp | 51 ++++++++++------------ 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 0a6b9e1c9c..008c15e061 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -517,40 +517,37 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { QString deviceName; #ifdef __APPLE__ - if (devices.size() > 1) { - AudioDeviceID defaultDeviceID = 0; - uint32_t propertySize = sizeof(AudioDeviceID); - AudioObjectPropertyAddress propertyAddress = { - kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; + AudioDeviceID defaultDeviceID = 0; + uint32_t propertySize = sizeof(AudioDeviceID); + AudioObjectPropertyAddress propertyAddress = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - if (mode == QAudio::AudioOutput) { - propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - } + if (mode == QAudio::AudioOutput) { + propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + } - OSStatus getPropertyError = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &propertyAddress, - 0, - NULL, - &propertySize, - &defaultDeviceID); + OSStatus getPropertyError = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &propertyAddress, + 0, + NULL, + &propertySize, + &defaultDeviceID); + + if (!getPropertyError && propertySize) { + CFStringRef devName = NULL; + propertySize = sizeof(devName); + propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; + getPropertyError = AudioObjectGetPropertyData(defaultDeviceID, &propertyAddress, 0, + NULL, &propertySize, &devName); if (!getPropertyError && propertySize) { - CFStringRef deviceName = NULL; - propertySize = sizeof(deviceName); - propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; - getPropertyError = AudioObjectGetPropertyData(defaultDeviceID, &propertyAddress, 0, - NULL, &propertySize, &deviceName); - - if (!getPropertyError && propertySize) { - deviceName = CFStringGetCStringPtr(deviceName, kCFStringEncodingMacRoman)); - } + deviceName = CFStringGetCStringPtr(devName, kCFStringEncodingMacRoman)); } } - } #endif #ifdef WIN32 //Check for Windows Vista or higher, IMMDeviceEnumerator doesn't work below that. From 3e1f3412254fb1536571fee13e52d391bda599db Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 25 Oct 2019 12:50:13 -0700 Subject: [PATCH 155/203] typo fix --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 008c15e061..c4f188fb53 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -545,7 +545,7 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { NULL, &propertySize, &devName); if (!getPropertyError && propertySize) { - deviceName = CFStringGetCStringPtr(devName, kCFStringEncodingMacRoman)); + deviceName = CFStringGetCStringPtr(devName, kCFStringEncodingMacRoman); } } #endif From 6860ac96935732c25626438b816617f4ff8a7f3b Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 25 Oct 2019 13:09:13 -0700 Subject: [PATCH 156/203] fix missing default device --- libraries/audio-client/src/AudioClient.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c4f188fb53..77d8ab6baa 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -117,14 +117,13 @@ QList getAvailableDevices(QAudio::Mode mode) { } } - if (!defaultDesktopDevice.getDevice().isNull()) { - newDevices.push_front(defaultDesktopDevice); - } else { - qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list:" << defDeviceName + if (defaultDesktopDevice.getDevice().isNull()) { + qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list:" << defDeviceName << "Setting Default to: " << devices.first().deviceName(); defaultDesktopDevice = HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::desktop); } - + newDevices.push_front(defaultDesktopDevice); + if (!hmdDeviceName.isNull() && !hmdDeviceName.isEmpty()) { HifiAudioDeviceInfo hmdDevice; foreach(auto device, newDevices) { From 8cf58f203455ca2c326387ef68ddf65cef378827 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 25 Oct 2019 13:56:12 -0700 Subject: [PATCH 157/203] Fix for unused avatar transit warnings on mac and linux This was introduced by PR #16403 https://github.com/highfidelity/hifi/pull/16403 --- .../src/avatars-renderer/Avatar.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 277b293728..9b8ce8cf34 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -133,21 +133,7 @@ void Avatar::setShowNamesAboveHeads(bool show) { showNamesAboveHeads = show; } -static const char* avatarTransitStatusToStringMap[] = { - "IDLE", - "STARTED", - "PRE_TRANSIT", - "START_TRANSIT", - "TRANSITING", - "END_TRANSIT", - "POST_TRANSIT", - "ENDED", - "ABORT_TRANSIT" -}; - AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - AvatarTransit::Status previousStatus = _status; - float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); if (oneFrameDistance > (config._minTriggerDistance * _scale)) { if (oneFrameDistance < (config._maxTriggerDistance * _scale)) { From 4f7028ff0f577f8c67714f706fb9c780cfe4d78d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 25 Oct 2019 14:05:29 -0700 Subject: [PATCH 158/203] DEV-2575: Automatically lower raised hand if talking --- .../simplifiedEmote/simplifiedEmote.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js index d7d6279e10..7ebbce4f58 100644 --- a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js +++ b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js @@ -217,6 +217,37 @@ function targetPointInterpolate() { } } + +function maybeClearInputAudioLevelsInterval() { + if (checkInputAudioLevelsInterval) { + Script.clearInterval(checkInputAudioLevelsInterval); + checkInputAudioLevelsInterval = false; + currentNumTimesAboveThreshold = 0; + } +} + + +var currentNumTimesAboveThreshold = 0; +var checkInputAudioLevelsInterval; +// The values below are determined empirically and may require tweaking over time if users +// notice false-positives or false-negatives. +var CHECK_INPUT_AUDIO_LEVELS_INTERVAL_MS = 200; +var AUDIO_INPUT_THRESHOLD = 130; +var NUM_REQUIRED_LEVELS_ABOVE_AUDIO_INPUT_THRESHOLD = 4; +function checkInputLevelsCallback() { + if (MyAvatar.audioLoudness > AUDIO_INPUT_THRESHOLD) { + currentNumTimesAboveThreshold++; + } else { + currentNumTimesAboveThreshold = 0; + } + + if (currentNumTimesAboveThreshold >= NUM_REQUIRED_LEVELS_ABOVE_AUDIO_INPUT_THRESHOLD) { + endReactionWrapper("raiseHand"); + currentNumTimesAboveThreshold = 0; + } +} + + function beginReactionWrapper(reaction) { maybeDeleteRemoteIndicatorTimeout(); @@ -246,6 +277,10 @@ function beginReactionWrapper(reaction) { Script.update.connect(targetPointInterpolate); targetPointInterpolateConnected = true; } + break; + case ("raiseHand"): + checkInputAudioLevelsInterval = Script.setInterval(checkInputLevelsCallback, CHECK_INPUT_AUDIO_LEVELS_INTERVAL_MS); + break; } } @@ -371,6 +406,9 @@ function endReactionWrapper(reaction) { maybeClearReticleUpdateLimiterTimeout(); deleteOldReticles(); break; + case ("raiseHand"): + maybeClearInputAudioLevelsInterval(); + break; } } @@ -648,6 +686,7 @@ function unload() { maybeClearClapSoundInterval(); maybeClearReticleUpdateLimiterTimeout(); maybeDeleteRemoteIndicatorTimeout(); + maybeClearInputAudioLevelsInterval(); Window.minimizedChanged.disconnect(onWindowMinimizedChanged); HMD.displayModeChanged.disconnect(onDisplayModeChanged); From 397586bd19cb9da07a840c641a73dddcb4001abd Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 25 Oct 2019 15:19:24 -0700 Subject: [PATCH 159/203] setting defauld device code for mac to use QT with optional backup to platform code on failure --- libraries/audio-client/src/AudioClient.cpp | 61 +++++++++++++--------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 77d8ab6baa..d932e91933 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -516,37 +516,50 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { QString deviceName; #ifdef __APPLE__ - AudioDeviceID defaultDeviceID = 0; - uint32_t propertySize = sizeof(AudioDeviceID); - AudioObjectPropertyAddress propertyAddress = { - kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; - - if (mode == QAudio::AudioOutput) { - propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + QAudioDeviceInfo device; + if (mode == QAudio::AudioInput) { + device = QAudioDeviceInfo::defaultInputDevice(); + } else { + device = QAudioDeviceInfo::defaultOutputDevice(); } + if (!device.isNull()) { + if (!device.deviceName().isEmpty()) { + deviceName = device.deviceName(); + } + } else { + qDebug() << "QT's Default device is null, reverting to platoform code"; + AudioDeviceID defaultDeviceID = 0; + uint32_t propertySize = sizeof(AudioDeviceID); + AudioObjectPropertyAddress propertyAddress = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + if (mode == QAudio::AudioOutput) { + propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + } - OSStatus getPropertyError = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &propertyAddress, - 0, - NULL, - &propertySize, - &defaultDeviceID); - - if (!getPropertyError && propertySize) { - CFStringRef devName = NULL; - propertySize = sizeof(devName); - propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; - getPropertyError = AudioObjectGetPropertyData(defaultDeviceID, &propertyAddress, 0, - NULL, &propertySize, &devName); + OSStatus getPropertyError = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &propertyAddress, + 0, + NULL, + &propertySize, + &defaultDeviceID); if (!getPropertyError && propertySize) { - deviceName = CFStringGetCStringPtr(devName, kCFStringEncodingMacRoman); + CFStringRef devName = NULL; + propertySize = sizeof(devName); + propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; + getPropertyError = AudioObjectGetPropertyData(defaultDeviceID, &propertyAddress, 0, + NULL, &propertySize, &devName); + + if (!getPropertyError && propertySize) { + deviceName = CFStringGetCStringPtr(devName, kCFStringEncodingMacRoman); } } + } #endif #ifdef WIN32 //Check for Windows Vista or higher, IMMDeviceEnumerator doesn't work below that. From c39e5a0ea89c0e2d3a52b6201f0e98c03a966106 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Fri, 25 Oct 2019 15:52:12 -0700 Subject: [PATCH 160/203] qt entitlements for mac --- launchers/qt/HQ Launcher.entitlements | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 launchers/qt/HQ Launcher.entitlements diff --git a/launchers/qt/HQ Launcher.entitlements b/launchers/qt/HQ Launcher.entitlements new file mode 100644 index 0000000000..b828d9b56d --- /dev/null +++ b/launchers/qt/HQ Launcher.entitlements @@ -0,0 +1,20 @@ + + + + + com.apple.security.application-groups + + high-fidelity.hifi + + com.apple.security.device.audio-input + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.disable-library-validation + + + From 8dc13ec242959aa813f4528d41dce26dd96e70c5 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Fri, 25 Oct 2019 16:01:24 -0700 Subject: [PATCH 161/203] fixed mac param spacing --- libraries/audio-client/src/AudioClient.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d932e91933..e98c71629a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -542,18 +542,18 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { OSStatus getPropertyError = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &propertyAddress, - 0, - NULL, - &propertySize, - &defaultDeviceID); + &propertyAddress, + 0, + NULL, + &propertySize, + &defaultDeviceID); if (!getPropertyError && propertySize) { CFStringRef devName = NULL; propertySize = sizeof(devName); propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; getPropertyError = AudioObjectGetPropertyData(defaultDeviceID, &propertyAddress, 0, - NULL, &propertySize, &devName); + NULL, &propertySize, &devName); if (!getPropertyError && propertySize) { deviceName = CFStringGetCStringPtr(devName, kCFStringEncodingMacRoman); From 6bceba7300348461d925e16cc2db96c383aa2dd4 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 25 Oct 2019 16:29:05 -0700 Subject: [PATCH 162/203] Use real avatar URL for verification rather than possible stolen-woody --- assignment-client/src/avatars/MixerAvatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index 9ed1d6a9b6..0660e76b03 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -76,8 +76,8 @@ void MixerAvatar::fetchAvatarFST() { _pendingEvent = false; - QUrl avatarURL = getSkeletonModelURL(); - if ((avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") && !isCertifyFailed()) { + QUrl avatarURL = _skeletonModelURL; + if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") { // Not network FST. return; } From 9325ea588a1b0fda23bea233eaa64ad924c2c5d1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 26 Oct 2019 09:20:51 -0700 Subject: [PATCH 163/203] Re-added removed jsdoc comments for removed AvatarInput jsapi. With a comment that these properties/functions have been removed. --- interface/src/ui/AvatarInputs.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 40ee0ea7d4..3b0e57d037 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -32,6 +32,14 @@ class AvatarInputs : public QObject { * @hifi-client-entity * @hifi-avatar * + * @property {boolean} cameraEnabled - true if webcam face tracking is enabled, false if it is + * disabled. + * Read-only. + *

Deprecated: This property is deprecated and has been removed.

+ * @property {boolean} cameraMuted - true if webcam face tracking is muted (temporarily disabled), + * false it if isn't. + * Read-only. + *

Deprecated: This property is deprecated and has been removed.

* @property {boolean} ignoreRadiusEnabled - true if the privacy shield is enabled, false if it * is disabled. * Read-only. @@ -87,6 +95,20 @@ public slots: signals: + /**jsdoc + * Triggered when webcam face tracking is enabled or disabled. + * @deprecated This signal is deprecated and has been removed. + * @function AvatarInputs.cameraEnabledChanged + * @returns {Signal} + */ + + /**jsdoc + * Triggered when webcam face tracking is muted (temporarily disabled) or unmuted. + * @deprecated This signal is deprecated and has been removed. + * @function AvatarInputs.cameraMutedChanged + * @returns {Signal} + */ + /**jsdoc * Triggered when the display mode changes between desktop and HMD. * @function AvatarInputs.isHMDChanged @@ -157,6 +179,12 @@ protected: */ Q_INVOKABLE void resetSensors(); + /**jsdoc + * Toggles the muting (temporary disablement) of webcam face tracking on/off. + *

Deprecated: This function is deprecated and has been removed.

+ * @function AvatarInputs.toggleCameraMute + */ + private: void onAvatarEnteredIgnoreRadius(); void onAvatarLeftIgnoreRadius(); From 62e5b50e247397e10ef89006981b7df17deaeab5 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Fri, 16 Aug 2019 13:44:43 +0300 Subject: [PATCH 164/203] Fixed the sprint speeds of Desktop vs HMD (they were flipped) --- interface/src/avatar/MyAvatar.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6e0bfab69b..36b8d1c2a0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5205,10 +5205,9 @@ bool MyAvatar::isReadyForPhysics() const { void MyAvatar::setSprintMode(bool sprint) { if (qApp->isHMDMode()) { - _walkSpeedScalar = sprint ? AVATAR_DESKTOP_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; - } - else { _walkSpeedScalar = sprint ? AVATAR_HMD_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; + } else { + _walkSpeedScalar = sprint ? AVATAR_DESKTOP_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } } From 35082a730edfddb768f1cafb9dd35aaf06b799d1 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Sun, 27 Oct 2019 09:35:16 +0200 Subject: [PATCH 165/203] Changed the desktop sprint speed to 2.0. This change will not actually be felt by users, because the speed was already effectively 2.0 (because the sprint speeds for Desktop and HMD were reversed). --- libraries/shared/src/AvatarConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index a955e0f0c3..4a79f6b487 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -103,7 +103,7 @@ static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meter static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters static const float MIN_AVATAR_RADIUS = 0.5f * MIN_AVATAR_HEIGHT; static const float AVATAR_WALK_SPEED_SCALAR = 1.0f; -static const float AVATAR_DESKTOP_SPRINT_SPEED_SCALAR = 3.0f; +static const float AVATAR_DESKTOP_SPRINT_SPEED_SCALAR = 2.0f; static const float AVATAR_HMD_SPRINT_SPEED_SCALAR = 2.0f; enum AvatarTriggerReaction { From 70c3bb2748827cffb7bd0f60402a752af31e2c1c Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 10 Sep 2019 15:38:20 +0300 Subject: [PATCH 166/203] Emit an event when failing to load an avatar. Previously, MyAvatar only emitted an event (onLoadComplete) if the load succeeded. Now it also emits an event (onLoadFailed) if the load failed. --- interface/src/avatar/MyAvatar.cpp | 5 ++++- interface/src/avatar/MyAvatar.h | 9 ++++++++- libraries/animation/src/Rig.cpp | 6 +++++- libraries/animation/src/Rig.h | 1 + 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6e0bfab69b..4392505fb3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -349,7 +349,8 @@ MyAvatar::MyAvatar(QThread* thread) : } }); - connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + connect(&(_skeletonModel->getRig()), &Rig::onLoadComplete, this, &MyAvatar::onLoadComplete); + connect(&(_skeletonModel->getRig()), &Rig::onLoadFailed, this, &MyAvatar::onLoadFailed); _characterController.setDensity(_density); } @@ -2626,6 +2627,8 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN if (urlString.isEmpty() || (fullAvatarURL != getSkeletonModelURL())) { setSkeletonModelURL(fullAvatarURL); UserActivityLogger::getInstance().changedModel("skeleton", urlString); + } else { + emit onLoadComplete(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 081fd00d5b..4c7f0c4a75 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2435,10 +2435,17 @@ signals: /**jsdoc * Triggered when the avatar's model finishes loading. * @function MyAvatar.onLoadComplete - * @returns {Signal} + * @returns {Signal} */ void onLoadComplete(); + /**jsdoc + * Triggered when the avatar's model has failed to load. + * @function MyAvatar.onLoadFailed + * @returns {Signal} + */ + void onLoadFailed(); + /**jsdoc * Triggered when your avatar changes from being active to being away. * @function MyAvatar.wentAway diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index fac4e04ce9..fc1885ea2b 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2353,6 +2353,7 @@ void Rig::initAnimGraph(const QUrl& url) { // abort load if the previous skeleton was deleted. auto sharedSkeletonPtr = weakSkeletonPtr.lock(); if (!sharedSkeletonPtr) { + emit onLoadFailed(); return; } @@ -2386,8 +2387,9 @@ void Rig::initAnimGraph(const QUrl& url) { } emit onLoadComplete(); }); - connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { + connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) { qCritical(animation) << "Error loading: code = " << error << "str =" << str; + emit onLoadFailed(); }); connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, networkUrl](AnimNode::Pointer nodeIn) { @@ -2415,6 +2417,8 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { qCritical(animation) << "Error loading: code = " << error << "str =" << str; }); + } else { + emit onLoadComplete(); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 8570ae4441..b2b9ecd5b4 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -260,6 +260,7 @@ public: signals: void onLoadComplete(); + void onLoadFailed(); protected: bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } From c672213a88cd5eb9afa88e1689cb11b0657a2b43 Mon Sep 17 00:00:00 2001 From: milad Date: Mon, 28 Oct 2019 09:03:52 -0700 Subject: [PATCH 167/203] quieted warning --- server-console/packager.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server-console/packager.js b/server-console/packager.js index 00866ee1be..4f743d077c 100644 --- a/server-console/packager.js +++ b/server-console/packager.js @@ -49,11 +49,11 @@ if (argv.out) { } // call the packager to produce the executable -packager(options, function(error, appPath) { - if (error) { +packager(options) + .then(function(appPath) { + console.log("Wrote new app to " + appPath); + }) + .catch(function(error){ console.error("There was an error writing the packaged console: " + error.message); process.exit(1); - } else { - console.log("Wrote new app to " + appPath); - } -}); + }); \ No newline at end of file From 2059168bd3ea4cf02ab792bd8eab7e43889b3636 Mon Sep 17 00:00:00 2001 From: milad Date: Mon, 28 Oct 2019 09:05:38 -0700 Subject: [PATCH 168/203] switched to arrow functions --- server-console/packager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server-console/packager.js b/server-console/packager.js index 4f743d077c..60aff6c540 100644 --- a/server-console/packager.js +++ b/server-console/packager.js @@ -50,10 +50,10 @@ if (argv.out) { // call the packager to produce the executable packager(options) - .then(function(appPath) { + .then(appPath => { console.log("Wrote new app to " + appPath); }) - .catch(function(error){ + .catch(error => { console.error("There was an error writing the packaged console: " + error.message); process.exit(1); - }); \ No newline at end of file + }); From afbb16ea78613a78f616e566dcf2ef8d3c399627 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 28 Oct 2019 09:40:48 -0700 Subject: [PATCH 169/203] undo some changes --- cmake/init.cmake | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmake/init.cmake b/cmake/init.cmake index fb72e828ea..3f632b30f8 100644 --- a/cmake/init.cmake +++ b/cmake/init.cmake @@ -30,14 +30,12 @@ endif () # CMAKE_CURRENT_SOURCE_DIR is the parent folder here set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") -set(HF_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(MACRO_DIR "${HF_CMAKE_DIR}/macros") set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals") -file(GLOB HIFI_CUSTOM_MACROS "${HF_CMAKE_DIR}/macros/*.cmake") -message("CMAKE FILEs: ${CMAKE_CURRENT_LIST_DIR} ${MACRO_DIR} ||") +file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake") foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) - message("CMAKE FILE: ${CUSTOM_MACRO}") include(${CUSTOM_MACRO}) endforeach() unset(HIFI_CUSTOM_MACROS) From f1f67f199637dc60d891dc22ea1136a9408ada08 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Mon, 28 Oct 2019 10:42:25 -0700 Subject: [PATCH 170/203] android build fix --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e98c71629a..fe4fab8415 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -501,7 +501,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { bool aecEnabled = enableAEC.get(); auto audioClient = DependencyManager::get(); bool headsetOn = audioClient ? audioClient->isHeadsetPluggedIn() : false; - for (QAudioDeviceInfo inputDevice : devices) { + for (QAudioDeviceInfo inputDevice : QAudioDeviceInfo::availableDevices(mode)) { if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { return HifiAudioDeviceInfo(inputDevice, false, QAudio::AudioInput); From c578eb2d9b0b0c37ba53357cb587aadadef774cd Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 28 Oct 2019 11:24:42 -0700 Subject: [PATCH 171/203] DEV-2574: Make emote shortcuts case-insensitive --- .../simplifiedEmote/simplifiedEmote.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js index 7ebbce4f58..b19cf9d684 100644 --- a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js +++ b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js @@ -502,20 +502,20 @@ function keyPressHandler(event) { } if (!event.isAutoRepeat && ! event.isMeta && ! event.isControl && ! event.isAlt) { - if (event.text === POSITIVE_KEY) { + if (event.text.toLowerCase() === POSITIVE_KEY) { triggerReactionWrapper("positive"); - } else if (event.text === NEGATIVE_KEY) { + } else if (event.text.toLowerCase() === NEGATIVE_KEY) { triggerReactionWrapper("negative"); - } else if (event.text === RAISE_HAND_KEY) { + } else if (event.text.toLowerCase() === RAISE_HAND_KEY) { toggleReaction("raiseHand"); - } else if (event.text === APPLAUD_KEY) { + } else if (event.text.toLowerCase() === APPLAUD_KEY) { // Make sure this doesn't get triggered if you are flying, falling, or jumping if (!MyAvatar.isInAir()) { toggleReaction("applaud"); } - } else if (event.text === POINT_KEY) { + } else if (event.text.toLowerCase() === POINT_KEY) { toggleReaction("point"); - } else if (event.text === EMOTE_WINDOW && !(Settings.getValue("io.highfidelity.isEditing", false))) { + } else if (event.text.toLowerCase() === EMOTE_WINDOW && !(Settings.getValue("io.highfidelity.isEditing", false))) { toggleEmojiApp(); } } @@ -524,7 +524,7 @@ function keyPressHandler(event) { function keyReleaseHandler(event) { if (!event.isAutoRepeat) { - if (event.text === APPLAUD_KEY) { + if (event.text.toLowerCase() === APPLAUD_KEY) { if (reactionsBegun.indexOf("applaud") > -1) { toggleReaction("applaud"); } From 0797e154a1a2a8a03e1c74fd25e3f0719dcaa27a Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 28 Oct 2019 13:41:15 -0700 Subject: [PATCH 172/203] first round of review changes --- launchers/qt/resources/qml/DisplayName.qml | 76 ---------------------- launchers/qt/src/Helper.cpp | 30 --------- launchers/qt/src/Helper.h | 4 -- launchers/qt/src/LauncherState.cpp | 1 - 4 files changed, 111 deletions(-) delete mode 100644 launchers/qt/resources/qml/DisplayName.qml diff --git a/launchers/qt/resources/qml/DisplayName.qml b/launchers/qt/resources/qml/DisplayName.qml deleted file mode 100644 index af688e549e..0000000000 --- a/launchers/qt/resources/qml/DisplayName.qml +++ /dev/null @@ -1,76 +0,0 @@ -// login - -import QtQuick 2.3 -import QtQuick 2.1 -import "HFControls" - -Item { - id: root - anchors.fill: parent - Text { - id: title - width: 325 - height: 26 - font.family: "Graphik" - font.pixelSize: 28 - color: "#FFFFFF" - text: "Choose a display name" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - anchors { - top: root.top - topMargin: 29 - horizontalCenter: root.horizontalCenter - } - } - - Text { - id: instruction - width: 425 - height: 22 - font.family: "Graphik" - font.pixelSize: 14 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - color: "#C4C4C4" - text: "This is the name that your teammates will see." - anchors { - left: root.left - right: root.right - top: title.bottom - topMargin: 18 - } - } - - HFTextField { - id: password - width: 306 - height: 40 - font.family: "Graphik" - font.pixelSize: 18 - placeholderText: "Display Name" - color: "#808080" - seperatorColor: Qt.rgba(1, 1, 1, 0.3) - anchors { - top: instruction.bottom - horizontalCenter: instruction.horizontalCenter - topMargin: 79 - } - } - - HFButton { - id: button - width: 122 - height: 36 - - font.family: "Graphik" - font.pixelSize: 18 - text: "NEXT" - - anchors { - top: password.bottom - horizontalCenter: instruction.horizontalCenter - topMargin: 59 - } - } -} diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index d09088a0e6..31ec6d773e 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -13,7 +13,6 @@ #include #include #include -//#incluce QString getMetaverseAPIDomain() { @@ -104,32 +103,3 @@ const QString& getInterfaceSharedMemoryName() { static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME"); return applicationName; } - -//#ifdef Q_OS_WIN -//#include -//#include -//#include -//#include -//#include -//#include -// -//bool isProcessRunning(const wchar_t *processName, int& processID) { -// bool exists = false; -// PROCESSENTRY32 entry; -// entry.dwSize = sizeof(PROCESSENTRY32); -// -// HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); -// -// if (Process32First(snapshot, &entry)) { -// while (Process32Next(snapshot, &entry)) { -// if (!_wcsicmp(entry.szExeFile, processName)) { -// exists = true; -// processID = entry.th32ProcessID; -// break; -// } -// } -// } -// CloseHandle(snapshot); -// return exists; -//} -//#endif diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 619b754c5a..dc97dd603e 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -37,7 +37,3 @@ BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode); QString getHTTPUserAgent(); const QString& getInterfaceSharedMemoryName(); - -//#ifdef Q_OS_WIN -//bool isProcessRunning(const wchar_t *processName, int& processID) { -//#endif diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 01463b002d..a9dc47a35d 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -63,7 +63,6 @@ void LauncherState::gotoNextDebugScreen() { #ifdef DEBUG_UI if (_currentDebugScreen < (UIState::UI_STATE_NUM - 1)) { _currentDebugScreen = (UIState)(_currentDebugScreen + 1); - //UIState updatedUIState = getUIState(); emit uiStateChanged(); emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource())); _uiState = getUIState(); From da0ba71373305b57e70a2b0dc3d473b17d744f48 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Mon, 28 Oct 2019 14:18:17 -0700 Subject: [PATCH 173/203] remove un-needed file --- launchers/qt/cmake/installer/Thumbs.db | Bin 7168 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 launchers/qt/cmake/installer/Thumbs.db diff --git a/launchers/qt/cmake/installer/Thumbs.db b/launchers/qt/cmake/installer/Thumbs.db deleted file mode 100644 index 86bc943370371869ad9d9b95e647aa37f386685c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7168 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 From c64b716a07ef3d743a6d1e9d1fec8e190a462662 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 28 Oct 2019 14:21:19 -0700 Subject: [PATCH 174/203] fix hifi_vcpkg.py script crash --- hifi_vcpkg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py index ebecca6226..6ec2184d45 100644 --- a/hifi_vcpkg.py +++ b/hifi_vcpkg.py @@ -273,8 +273,10 @@ endif() url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz' else: print('UNKNOWN LINUX VERSION!!!') + return; else: print('UNKNOWN OPERATING SYSTEM!!!') + return; print('Extracting ' + url + ' to ' + dest) hifi_utils.downloadAndExtract(url, dest) From 285d73a802716b242f653e49a18ef7664970b2e0 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 28 Oct 2019 17:47:46 -0700 Subject: [PATCH 175/203] Fix transition from verify-failed to noncertified --- assignment-client/src/avatars/MixerAvatar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index 0660e76b03..5203d25af6 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -272,7 +272,10 @@ void MixerAvatar::processCertifyEvents() { } } else { // FST doesn't have a certificate, so noncertified rather than failed: _pendingEvent = false; + _certifyFailed = false; + _needsIdentityUpdate = true; _verifyState = nonCertified; + qCDebug(avatars) << "Avatar " << getDisplayName() << "(" << getSessionUUID() << ") isn't certified"; } break; } From ab6631fae302114b49dd02e3415c99fcb51d7ff5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 28 Oct 2019 17:51:29 -0700 Subject: [PATCH 176/203] Fix look at update and allow to release control --- interface/src/avatar/MyAvatar.cpp | 10 +++++++++- interface/src/avatar/MyAvatar.h | 12 ++++++++++++ .../avatars-renderer/src/avatars-renderer/Head.cpp | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 979d33ec5d..cff7806b58 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6807,7 +6807,7 @@ void MyAvatar::updateHeadLookAt(float deltaTime) { if (_scriptControlsHeadLookAt) { _scriptHeadControlTimer += deltaTime; - if (_scriptHeadControlTimer > MAX_LOOK_AT_TIME_SCRIPT_CONTROL) { + if (_scriptHeadControlTimer >= MAX_LOOK_AT_TIME_SCRIPT_CONTROL) { _scriptHeadControlTimer = 0.0f; _scriptControlsHeadLookAt = false; _lookAtCameraTarget = _lookAtScriptTarget; @@ -6839,6 +6839,14 @@ void MyAvatar::setEyesLookAt(const glm::vec3& lookAtTarget) { _scriptControlsEyesLookAt = true; } +void MyAvatar::releaseHeadLookAtControl() { + _scriptHeadControlTimer = MAX_LOOK_AT_TIME_SCRIPT_CONTROL; +} + +void MyAvatar::releaseEyesLookAtControl() { + _scriptEyesControlTimer = MAX_LOOK_AT_TIME_SCRIPT_CONTROL; +} + glm::vec3 MyAvatar::getLookAtPivotPoint() { glm::vec3 avatarUp = getWorldOrientation() * Vectors::UP; glm::vec3 yAxisEyePosition = getWorldPosition() + avatarUp * glm::dot(avatarUp, _skeletonModel->getDefaultEyeModelPosition()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 32ef16f811..09e2207f34 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1769,6 +1769,12 @@ public: */ Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; } + /**jsdoc + * When this function is called the engine regains control of the head immediately. + * @function MyAvatar.releaseHeadLookAtControl + */ + Q_INVOKABLE void releaseHeadLookAtControl(); + /**jsdoc * Force the avatar's eyes to look to the specified location. * Once this method is called, API calls will have full control of the eyes for a limited time. @@ -1785,6 +1791,12 @@ public: */ Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget.get(); } + /**jsdoc + * When this function is called the engine regains control of the eyes immediately. + * @function MyAvatar.releaseEyesLookAtControl + */ + Q_INVOKABLE void releaseEyesLookAtControl(); + /**jsdoc * Aims the pointing directional blending towards the provided target point. * The "point" reaction should be triggered before using this method. diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index aa6dc779d5..6d973c98a2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -358,7 +358,6 @@ float Head::getFinalRoll() const { void Head::setLookAtPosition(const glm::vec3& lookAtPosition) { if (_isEyeLookAtUpdated && _requestLookAtPosition != lookAtPosition) { - _lookAtPositionChanged = usecTimestampNow(); glm::vec3 oldAvatarLookAtVector = _requestLookAtPosition - _owningAvatar->getWorldPosition(); glm::vec3 newAvatarLookAtVector = lookAtPosition - _owningAvatar->getWorldPosition(); const float MIN_BLINK_ANGLE = 0.35f; // 20 degrees @@ -369,6 +368,7 @@ void Head::setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } } + _lookAtPositionChanged = usecTimestampNow(); _requestLookAtPosition = lookAtPosition; } From 37ba50992cd7c6f7d770489088c9d3e183160be5 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 07:16:58 -0700 Subject: [PATCH 177/203] temp reverting the call to display plugin --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index fe4fab8415..7534f8166d 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -101,7 +101,7 @@ static QString getHmdAudioDeviceName(QAudio::Mode mode) { QList getAvailableDevices(QAudio::Mode mode) { //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client //cannot properly shut down. - QString hmdDeviceName = getHmdAudioDeviceName(mode); + QString hmdDeviceName = QString(); //getHmdAudioDeviceName(mode); QString defDeviceName = defaultAudioDeviceName(mode); // NOTE: availableDevices() clobbers the Qt internal device list From 57302dd2233a045b99185e3f412c5bcd7fbfd8b4 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 08:34:35 -0700 Subject: [PATCH 178/203] fixing build warning on mac/linux --- libraries/audio-client/src/AudioClient.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7534f8166d..9f28a61ed8 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -83,25 +83,11 @@ Mutex _recordMutex; QString defaultAudioDeviceName(QAudio::Mode mode); -static QString getHmdAudioDeviceName(QAudio::Mode mode) { - foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { - if (displayPlugin && displayPlugin->isHmd()) { - if (mode == QAudio::AudioInput) { - return displayPlugin->getPreferredAudioInDevice(); - } else { - return displayPlugin->getPreferredAudioOutDevice(); - } - break; - } - } - return QString(); -} - // thread-safe QList getAvailableDevices(QAudio::Mode mode) { //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client //cannot properly shut down. - QString hmdDeviceName = QString(); //getHmdAudioDeviceName(mode); + QString hmdDeviceName = QString(); QString defDeviceName = defaultAudioDeviceName(mode); // NOTE: availableDevices() clobbers the Qt internal device list From 884cbd7de93370a0d9ed0ec053b87bc1c8475cf9 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 09:26:48 -0700 Subject: [PATCH 179/203] working on moving display plugin lookup to audiodevices --- interface/src/scripting/AudioDevices.cpp | 29 +++++++++- libraries/audio-client/src/AudioClient.cpp | 57 ++++++++----------- libraries/audio-client/src/AudioClient.h | 5 +- .../plugins/src/plugins/PluginManager.cpp | 8 +-- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 76c871fa5c..8737cd1296 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "Application.h" @@ -68,6 +69,21 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) { return deviceName; } + +static QString getHmdAudioDeviceName(QAudio::Mode mode) { + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { + if (displayPlugin && displayPlugin->isHmd()) { + if (mode == QAudio::AudioInput) { + return displayPlugin->getPreferredAudioInDevice(); + } else { + return displayPlugin->getPreferredAudioOutDevice(); + } + break; + } + } + return QString(); +} + Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled }; AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) { @@ -263,6 +279,18 @@ void AudioDeviceList::onDevicesChanged(const QList& devices bool hmdIsSelected = false; bool desktopIsSelected = false; + //getting hmd mode + if (devices.size() > 0) { + auto mode = devices.first().getMode(); + QString name = getHmdAudioDeviceName(mode); + if (!name.isEmpty()) { + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setHmdAudioName", + Q_ARG(QAudio::Mode, mode), + Q_ARG(const QString&, name)); + } + } + if (!_backupSelectedDesktopDeviceName.isEmpty() && !_backupSelectedHMDDeviceName.isEmpty()) { foreach(const HifiAudioDeviceInfo& deviceInfo, devices) { for (bool isHMD : {false, true}) { @@ -275,7 +303,6 @@ void AudioDeviceList::onDevicesChanged(const QList& devices _selectedDesktopDevice = deviceInfo; backupSelectedDeviceName.clear(); } - } } } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index fe4fab8415..e966274a9b 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -48,7 +48,6 @@ #include #include #include -#include #include "AudioClientLogging.h" #include "AudioLogging.h" @@ -83,38 +82,31 @@ Mutex _recordMutex; QString defaultAudioDeviceName(QAudio::Mode mode); -static QString getHmdAudioDeviceName(QAudio::Mode mode) { - foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { - if (displayPlugin && displayPlugin->isHmd()) { - if (mode == QAudio::AudioInput) { - return displayPlugin->getPreferredAudioInDevice(); - } else { - return displayPlugin->getPreferredAudioOutDevice(); - } - break; - } +void AudioClient::setHmdAudioName(QAudio::Mode mode, const QString& name) { + if (mode == QAudio::AudioInput) { + _hmdInputName = name; + } else { + _hmdOutputName = name; } - return QString(); } // thread-safe -QList getAvailableDevices(QAudio::Mode mode) { +QList getAvailableDevices(QAudio::Mode mode, const QString& hmdName) { //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client //cannot properly shut down. - QString hmdDeviceName = getHmdAudioDeviceName(mode); QString defDeviceName = defaultAudioDeviceName(mode); // NOTE: availableDevices() clobbers the Qt internal device list Lock lock(_deviceMutex); auto devices = QAudioDeviceInfo::availableDevices(mode); - + HifiAudioDeviceInfo defaultDesktopDevice; QList newDevices; for (auto& device : devices) { newDevices.push_back(HifiAudioDeviceInfo(device, false, mode)); if (device.deviceName() == defDeviceName.trimmed()) { defaultDesktopDevice = HifiAudioDeviceInfo(device, true, mode, HifiAudioDeviceInfo::desktop); - } + } } if (defaultDesktopDevice.getDevice().isNull()) { @@ -123,11 +115,10 @@ QList getAvailableDevices(QAudio::Mode mode) { defaultDesktopDevice = HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::desktop); } newDevices.push_front(defaultDesktopDevice); - - if (!hmdDeviceName.isNull() && !hmdDeviceName.isEmpty()) { + if (!hmdName.isNull()) { HifiAudioDeviceInfo hmdDevice; foreach(auto device, newDevices) { - if (device.getDevice().deviceName() == hmdDeviceName) { + if (device.getDevice().deviceName() == hmdName) { hmdDevice = HifiAudioDeviceInfo(device.getDevice(), true, mode, HifiAudioDeviceInfo::hmd); break; } @@ -149,9 +140,9 @@ void AudioClient::checkDevices() { return; } - auto inputDevices = getAvailableDevices(QAudio::AudioInput); - auto outputDevices = getAvailableDevices(QAudio::AudioOutput); - + auto inputDevices = getAvailableDevices(QAudio::AudioInput, _hmdInputName); + auto outputDevices = getAvailableDevices(QAudio::AudioOutput, _hmdOutputName); + checkDefaultChanges(inputDevices); checkDefaultChanges(outputDevices); @@ -335,8 +326,8 @@ AudioClient::AudioClient() { connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash - getAvailableDevices(QAudio::AudioInput); - getAvailableDevices(QAudio::AudioOutput); + getAvailableDevices(QAudio::AudioInput, _hmdInputName); + getAvailableDevices(QAudio::AudioOutput, _hmdOutputName); // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); @@ -436,9 +427,9 @@ void AudioClient::setAudioPaused(bool pause) { } } -HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName, bool isHmd=false) { +HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName, const QString& hmdName, bool isHmd=false) { HifiAudioDeviceInfo result; - foreach (HifiAudioDeviceInfo audioDevice, getAvailableDevices(mode)) { + foreach (HifiAudioDeviceInfo audioDevice, getAvailableDevices(mode,hmdName)) { if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { if ((!isHmd && audioDevice.getDeviceType() != HifiAudioDeviceInfo::hmd) || (isHmd && audioDevice.getDeviceType() != HifiAudioDeviceInfo::desktop)) { result = audioDevice; @@ -493,7 +484,7 @@ QString AudioClient::getWinDeviceName(wchar_t* guid) { #endif -HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { +HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode, const QString& hmdName) { QString deviceName = defaultAudioDeviceName(mode); #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { @@ -509,7 +500,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { } } #endif - return getNamedAudioDeviceForMode(mode, deviceName); + return getNamedAudioDeviceForMode(mode, deviceName, hmdName); } QString defaultAudioDeviceName(QAudio::Mode mode) { @@ -615,7 +606,8 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { } bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) { - return (getNamedAudioDeviceForMode(mode, deviceName).deviceName() == deviceName); + QString hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; + return (getNamedAudioDeviceForMode(mode, deviceName, hmdName).deviceName() == deviceName); } @@ -778,11 +770,11 @@ void AudioClient::start() { _desiredOutputFormat = _desiredInputFormat; _desiredOutputFormat.setChannelCount(OUTPUT_CHANNEL_COUNT); - HifiAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput); + HifiAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput, _hmdInputName); qCDebug(audioclient) << "The default audio input device is" << inputDeviceInfo.deviceName(); bool inputFormatSupported = switchInputToAudioDevice(inputDeviceInfo); - HifiAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); + HifiAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput, _hmdOutputName); qCDebug(audioclient) << "The default audio output device is" << outputDeviceInfo.deviceName(); bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo); @@ -1021,7 +1013,8 @@ bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo } bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd) { - return switchAudioDevice(mode, getNamedAudioDeviceForMode(mode, deviceName, isHmd)); + QString hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; + return switchAudioDevice(mode, getNamedAudioDeviceForMode(mode, deviceName, hmdName, isHmd)); } void AudioClient::configureReverb() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index ce63c43cff..9628de99e8 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -313,7 +313,7 @@ private: // background tasks void checkDevices(); void checkPeakValues(); - + void setHmdAudioName(QAudio::Mode mode, const QString& name); void outputFormatChanged(); void handleAudioInput(QByteArray& audioBuffer); void prepareLocalAudioInjectors(std::unique_ptr localAudioLock = nullptr); @@ -481,6 +481,9 @@ private: QList _inputDevices; QList _outputDevices; + QString _hmdInputName { QString() }; + QString _hmdOutputName{ QString() }; + AudioFileWav _audioFileWav; bool _hasReceivedFirstPacket { false }; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 660159d9bd..784de6bdea 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -224,13 +224,7 @@ const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() { } DisplayPluginList PluginManager::getAllDisplayPlugins() { - if (thread() != QThread::currentThread()) { - DisplayPluginList list; - QMetaObject::invokeMethod(this, "getAllDisplayPlugins", Qt::BlockingQueuedConnection, Q_RETURN_ARG(DisplayPluginList, list)); - return list; - } else { - return _displayPlugins; - } + return _displayPlugins; } const DisplayPluginList& PluginManager::getDisplayPlugins() { From 9370ce9b763bbacd94449e267d2b38fe4ef259d0 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 10:24:30 -0700 Subject: [PATCH 180/203] removed items --- libraries/audio-client/CMakeLists.txt | 1 - libraries/audio-client/src/AudioClient.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index e1d90334ff..6b88292dd4 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -6,7 +6,6 @@ setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) -include_hifi_library_headers(gpu) if (ANDROID) else () diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9628de99e8..fadc599228 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -243,6 +243,8 @@ public slots: // calling with a null QAudioDevice will use the system default bool switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo = HifiAudioDeviceInfo()); bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd); + void setHmdAudioName(QAudio::Mode mode, const QString& name); + // Qt opensles plugin is not able to detect when the headset is plugged in void setHeadsetPluggedIn(bool pluggedIn); @@ -313,7 +315,6 @@ private: // background tasks void checkDevices(); void checkPeakValues(); - void setHmdAudioName(QAudio::Mode mode, const QString& name); void outputFormatChanged(); void handleAudioInput(QByteArray& audioBuffer); void prepareLocalAudioInjectors(std::unique_ptr localAudioLock = nullptr); From 77f5b985a1a7c0c7e9431de2c397a416307d9308 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 11:19:59 -0700 Subject: [PATCH 181/203] added read/write lock to hmd name --- libraries/audio-client/src/AudioClient.cpp | 43 +++++++++++++++++----- libraries/audio-client/src/AudioClient.h | 2 +- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e966274a9b..c81823d14b 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -83,6 +83,7 @@ Mutex _recordMutex; QString defaultAudioDeviceName(QAudio::Mode mode); void AudioClient::setHmdAudioName(QAudio::Mode mode, const QString& name) { + QWriteLocker lock(&_hmdNameLock); if (mode == QAudio::AudioInput) { _hmdInputName = name; } else { @@ -140,8 +141,17 @@ void AudioClient::checkDevices() { return; } - auto inputDevices = getAvailableDevices(QAudio::AudioInput, _hmdInputName); - auto outputDevices = getAvailableDevices(QAudio::AudioOutput, _hmdOutputName); + QString hmdInputName; + QString hmdOutputName; + + { + QReadLocker readLock(&_hmdNameLock); + hmdInputName = _hmdInputName; + hmdOutputName = _hmdOutputName; + } + + auto inputDevices = getAvailableDevices(QAudio::AudioInput, hmdInputName); + auto outputDevices = getAvailableDevices(QAudio::AudioOutput, hmdOutputName); checkDefaultChanges(inputDevices); checkDefaultChanges(outputDevices); @@ -325,10 +335,12 @@ AudioClient::AudioClient() { connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); - // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash - getAvailableDevices(QAudio::AudioInput, _hmdInputName); - getAvailableDevices(QAudio::AudioOutput, _hmdOutputName); - + { + QReadLocker readLock(&_hmdNameLock); + // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash + getAvailableDevices(QAudio::AudioInput, _hmdInputName); + getAvailableDevices(QAudio::AudioOutput, _hmdOutputName); + } // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; @@ -606,6 +618,7 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { } bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) { + QReadLocker readLock(&_hmdNameLock); QString hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; return (getNamedAudioDeviceForMode(mode, deviceName, hmdName).deviceName() == deviceName); } @@ -769,12 +782,20 @@ void AudioClient::start() { _desiredOutputFormat = _desiredInputFormat; _desiredOutputFormat.setChannelCount(OUTPUT_CHANNEL_COUNT); + + QString inputName; + QString outputName; + { + QReadLocker readLock(&_hmdNameLock); + inputName = _hmdInputName; + outputName = _hmdOutputName; + } - HifiAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput, _hmdInputName); + HifiAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput, inputName); qCDebug(audioclient) << "The default audio input device is" << inputDeviceInfo.deviceName(); bool inputFormatSupported = switchInputToAudioDevice(inputDeviceInfo); - HifiAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput, _hmdOutputName); + HifiAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput, outputName); qCDebug(audioclient) << "The default audio output device is" << outputDeviceInfo.deviceName(); bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo); @@ -1013,7 +1034,11 @@ bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo } bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd) { - QString hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; + QString hmdName; + { + QReadLocker readLock(&_hmdNameLock); + hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; + } return switchAudioDevice(mode, getNamedAudioDeviceForMode(mode, deviceName, hmdName, isHmd)); } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index fadc599228..4e6abd3509 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -508,7 +508,7 @@ private: #endif AudioSolo _solo; - + QReadWriteLock _hmdNameLock; Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; Mutex _checkPeakValuesMutex; From b8d6a2cf0b5de0efa6e79dc913b06491811f70f3 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Tue, 29 Oct 2019 11:20:14 -0700 Subject: [PATCH 182/203] Added extra attempt to parent interactive window content on creation --- interface/resources/qml/InteractiveWindow.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 3a4ec8838c..a8b27762d6 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -205,6 +205,7 @@ Windows.Window { // finally set the initial window mode: setupPresentationMode(); + updateContentParent(); initialized = true; } From 7d5dcbfc77e3cf047504d48f428994689745e9ce Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 11:58:04 -0700 Subject: [PATCH 183/203] space cleanup --- interface/src/scripting/AudioDevices.cpp | 1 - libraries/audio-client/src/AudioClient.cpp | 2 +- libraries/audio-client/src/AudioClient.h | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 8737cd1296..8a4e0925e3 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -69,7 +69,6 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) { return deviceName; } - static QString getHmdAudioDeviceName(QAudio::Mode mode) { foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { if (displayPlugin && displayPlugin->isHmd()) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c81823d14b..4b7d642a71 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -116,6 +116,7 @@ QList getAvailableDevices(QAudio::Mode mode, const QString& defaultDesktopDevice = HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::desktop); } newDevices.push_front(defaultDesktopDevice); + if (!hmdName.isNull()) { HifiAudioDeviceInfo hmdDevice; foreach(auto device, newDevices) { @@ -143,7 +144,6 @@ void AudioClient::checkDevices() { QString hmdInputName; QString hmdOutputName; - { QReadLocker readLock(&_hmdNameLock); hmdInputName = _hmdInputName; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 4e6abd3509..dafa81081c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -244,7 +244,6 @@ public slots: bool switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo = HifiAudioDeviceInfo()); bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd); void setHmdAudioName(QAudio::Mode mode, const QString& name); - // Qt opensles plugin is not able to detect when the headset is plugged in void setHeadsetPluggedIn(bool pluggedIn); @@ -315,6 +314,7 @@ private: // background tasks void checkDevices(); void checkPeakValues(); + void outputFormatChanged(); void handleAudioInput(QByteArray& audioBuffer); void prepareLocalAudioInjectors(std::unique_ptr localAudioLock = nullptr); @@ -508,6 +508,7 @@ private: #endif AudioSolo _solo; + QReadWriteLock _hmdNameLock; Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; From e753010411c40f8bb89fb2dad1a279ee668bea7b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 25 Oct 2019 12:06:48 -0700 Subject: [PATCH 184/203] Fix for head not turning in third person look at camera mode If MyAvatar::setSkeletonModelURL() is called with the same url that has already been loaded the SkeletonModel::skeletonLoaded signal will not be triggered. In this case the MyAvatar local variable MyAvatar::_skeletonModelLoaded will be set to false and never re-set to true. This, in turn, caused MyAvatar::updateHeadLookAt() to skip setting the proper blend values that would turn the head. Rather then try to make MyAvatar::_skeletonModelLoaded handle all the possible edge cases. It has been removed. All conditionals that used to use it have been replased with _skeletonModel->isLoaded(). --- interface/src/avatar/MyAvatar.cpp | 13 +++++-------- interface/src/avatar/MyAvatar.h | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 979d33ec5d..6ad5c09ad6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -733,7 +733,7 @@ void MyAvatar::update(float deltaTime) { _physicsSafetyPending = getCollisionsEnabled(); _characterController.recomputeFlying(); // In case we've gone to into the sky. } - if (_goToFeetAjustment && _skeletonModelLoaded) { + if (_goToFeetAjustment && _skeletonModel->isLoaded()) { auto feetAjustment = getWorldPosition() - getWorldFeetPosition(); _goToPosition = getWorldPosition() + feetAjustment; setWorldPosition(_goToPosition); @@ -2501,7 +2501,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _headBoneSet.clear(); _cauterizationNeedsUpdate = true; - _skeletonModelLoaded = false; std::shared_ptr skeletonConnection = std::make_shared(); *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { @@ -2520,8 +2519,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); initFlowFromFST(); - - _skeletonModelLoaded = true; } QObject::disconnect(*skeletonConnection); }); @@ -6782,7 +6779,7 @@ glm::vec3 MyAvatar::aimToBlendValues(const glm::vec3& aimVector, const glm::quat } void MyAvatar::resetHeadLookAt() { - if (_skeletonModelLoaded) { + if (_skeletonModel->isLoaded()) { _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING); } @@ -6798,7 +6795,7 @@ void MyAvatar::resetLookAtRotation(const glm::vec3& avatarPosition, const glm::q } void MyAvatar::updateHeadLookAt(float deltaTime) { - if (_skeletonModelLoaded) { + if (_skeletonModel->isLoaded()) { glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget; glm::vec3 aimVector = lookAtTarget - getDefaultEyePosition(); glm::vec3 lookAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation()); @@ -6912,7 +6909,7 @@ bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) { Q_ARG(const glm::vec3&, pointAtTarget)); return result; } - if (_skeletonModelLoaded && _pointAtActive) { + if (_skeletonModel->isLoaded() && _pointAtActive) { glm::vec3 aimVector = pointAtTarget - getJointPosition(POINT_REF_JOINT_NAME); _isPointTargetValid = glm::dot(aimVector, getWorldOrientation() * Vectors::FRONT) > 0.0f; if (_isPointTargetValid) { @@ -6926,7 +6923,7 @@ bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) { } void MyAvatar::resetPointAt() { - if (_skeletonModelLoaded) { + if (_skeletonModel->isLoaded()) { _skeletonModel->getRig().setDirectionalBlending(POINT_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 32ef16f811..92af1ffcba 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2918,7 +2918,6 @@ private: bool _haveReceivedHeightLimitsFromDomain { false }; int _disableHandTouchCount { 0 }; - bool _skeletonModelLoaded { false }; bool _reloadAvatarEntityDataFromSettings { true }; TimePoint _nextTraitsSendWindow; From 3f110d2035a70ca29fb7f0fabfdbf7a744a6fb3b Mon Sep 17 00:00:00 2001 From: danteruiz Date: Tue, 29 Oct 2019 13:45:28 -0700 Subject: [PATCH 185/203] fix windows install process --- launchers/qt/src/Helper.cpp | 2 +- launchers/qt/src/LauncherInstaller_windows.cpp | 12 ++++++++---- launchers/qt/src/LauncherInstaller_windows.h | 2 +- launchers/qt/src/main.cpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index d09088a0e6..fd12bf3af8 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -77,7 +77,7 @@ bool swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPat void cleanLogFile() { - QDir launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir launcherDirectory = PathUtils::getLogsDirectory(); launcherDirectory.mkpath(launcherDirectory.absolutePath()); QString filename = launcherDirectory.absoluteFilePath("Log.txt"); QString tmpFilename = launcherDirectory.absoluteFilePath("Log-last.txt"); diff --git a/launchers/qt/src/LauncherInstaller_windows.cpp b/launchers/qt/src/LauncherInstaller_windows.cpp index 7ba8b8e2d2..78c7dadd2b 100644 --- a/launchers/qt/src/LauncherInstaller_windows.cpp +++ b/launchers/qt/src/LauncherInstaller_windows.cpp @@ -15,7 +15,7 @@ #include #include -LauncherInstaller::LauncherInstaller(const QString& applicationFilePath) { +LauncherInstaller::LauncherInstaller() { _launcherInstallDir = PathUtils::getLauncherDirectory(); _launcherApplicationsDir = PathUtils::getApplicationsDirectory(); qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath(); @@ -23,7 +23,10 @@ LauncherInstaller::LauncherInstaller(const QString& applicationFilePath) { _launcherInstallDir.mkpath(_launcherInstallDir.absolutePath()); _launcherApplicationsDir.mkpath(_launcherApplicationsDir.absolutePath()); - QFileInfo fileInfo(applicationFilePath); + char appPath[MAX_PATH]; + GetModuleFileNameA(NULL, appPath, MAX_PATH); + QString applicationRunningPath = appPath; + QFileInfo fileInfo(applicationRunningPath); _launcherRunningFilePath = fileInfo.absoluteFilePath(); _launcherRunningDirPath = fileInfo.absoluteDir().absolutePath(); qDebug() << "Launcher running file path: " << _launcherRunningFilePath; @@ -44,11 +47,12 @@ void LauncherInstaller::install() { bool didRemove = QFile::remove(oldLauncherPath); qDebug() << "did remove file: " << didRemove; } + qDebug() << "Current launcher location: " << _launcherRunningFilePath; bool success = QFile::copy(_launcherRunningFilePath, oldLauncherPath); if (success) { - qDebug() << "successful"; + qDebug() << "Launcher installed: " << oldLauncherPath; } else { - qDebug() << "not successful"; + qDebug() << "Failed to install: " << oldLauncherPath; } deleteShortcuts(); diff --git a/launchers/qt/src/LauncherInstaller_windows.h b/launchers/qt/src/LauncherInstaller_windows.h index 3bd9d0242a..ffb402cce5 100644 --- a/launchers/qt/src/LauncherInstaller_windows.h +++ b/launchers/qt/src/LauncherInstaller_windows.h @@ -3,7 +3,7 @@ #include class LauncherInstaller { public: - LauncherInstaller(const QString& applicationFilePath); + LauncherInstaller(); ~LauncherInstaller() = default; void install(); diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 3918ec944e..42a6229d1f 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -64,7 +64,7 @@ int main(int argc, char *argv[]) { } #ifdef Q_OS_WIN - LauncherInstaller launcherInstaller(argv[0]); + LauncherInstaller launcherInstaller; if (options->contains("--uninstall") || options->contains("--resumeUninstall")) { launcherInstaller.uninstall(); return 0; From 5d69d554f94834f2a624561c2c736178d0267891 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 29 Oct 2019 15:21:54 -0700 Subject: [PATCH 186/203] fix mac update issues --- launchers/qt/src/Launcher.cpp | 1 - launchers/qt/src/LauncherState.cpp | 3 ++- launchers/qt/src/main.cpp | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index cf80604c39..7522529ff9 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -11,7 +11,6 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { _launcherState = std::make_shared(); - QString platform; #ifdef Q_OS_WIN platform = "Windows"; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index a9dc47a35d..300bd37ef8 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -195,6 +195,8 @@ void LauncherState::requestBuilds() { _latestBuilds = request->getLatestBuilds(); CommandlineOptions* options = CommandlineOptions::getInstance(); + qDebug() << "Latest version: " << _latestBuilds.launcherBuild.latestVersion + << "Curretn version: " << getBuildVersion().toInt(); if (shouldDownloadLauncher() && !options->contains("--noUpdate")) { downloadLauncher(); return; @@ -211,7 +213,6 @@ QString LauncherState::getBuildVersion() { if (processEnvironment.contains("HQ_LAUNCHER_BUILD_VERSION")) { buildVersion = processEnvironment.value("HQ_LAUNCHER_BUILD_VERSION"); } - return buildVersion; } diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 3918ec944e..31e879eae1 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -50,9 +50,7 @@ int main(int argc, char *argv[]) { if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { bool success = swapLaunchers(argv[1], argv[2]); qDebug() << "Successfully installed Launcher: " << success; - if (!success) { - options->append("--noUpdate"); - } + options->append("--noUpdate"); didUpdate = true; } } From ddfbb55e19fedae98d1aeba7d4d9a83299f8b025 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Tue, 29 Oct 2019 15:36:43 -0700 Subject: [PATCH 187/203] fixed old ui filtering of audio devices --- interface/resources/qml/hifi/audio/Audio.qml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index fccba12a8a..eef339b854 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -375,14 +375,14 @@ Rectangle { x: margins.paddings interactive: false; height: contentHeight; - spacing: 4; + clip: true; model: AudioScriptingInterface.devices.input; delegate: Item { width: rightMostInputLevelPos - margins.paddings*2 - height: margins.sizeCheckBox > checkBoxInput.implicitHeight ? - margins.sizeCheckBox : checkBoxInput.implicitHeight - + height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ? + (margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxInput.implicitHeight + 4) : 0 + visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1) AudioControls.CheckBox { id: checkBoxInput anchors.left: parent.left @@ -470,13 +470,13 @@ Rectangle { height: contentHeight; anchors.top: outputDeviceHeader.bottom; anchors.topMargin: 10; - spacing: 4; clip: true; model: AudioScriptingInterface.devices.output; delegate: Item { width: rightMostInputLevelPos - height: margins.sizeCheckBox > checkBoxOutput.implicitHeight ? - margins.sizeCheckBox : checkBoxOutput.implicitHeight + height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ? + (margins.sizeCheckBox > checkBoxOutput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxOutput.implicitHeight + 4) : 0 + visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1) AudioControls.CheckBox { id: checkBoxOutput From 9c0003515aa15d46a8d7df3a63a97e30185134a4 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Tue, 29 Oct 2019 13:19:41 -0700 Subject: [PATCH 188/203] Specified color on text edit for console --- interface/ui/console.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/ui/console.ui b/interface/ui/console.ui index d4b0b2bc74..1173e0adae 100644 --- a/interface/ui/console.ui +++ b/interface/ui/console.ui @@ -227,6 +227,9 @@ -1 + + black + QFrame::NoFrame From 7953e3889f4f04b458a09d0a899730f158b605a3 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 10 Sep 2019 11:48:05 +0300 Subject: [PATCH 189/203] Wait for the avatar skeleton to finish loading before adding the avatar entities --- interface/src/AvatarBookmarks.cpp | 42 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index d9ac522dc8..2ebe769bec 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -212,22 +212,36 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { auto myAvatar = DependencyManager::get()->getMyAvatar(); auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - myAvatar->clearWornAvatarEntities(); + + // Once the skeleton URL has been loaded, add the Avatar Entities. + // We have to wait, because otherwise the avatar entities will try to get attached to the joints + // of the *current* avatar at first. But the current avatar might have a different joints scheme + // from the new avatar, and that would cause the entities to be attached to the wrong joints. + + std::shared_ptr connection1 = std::make_shared(); + *connection1 = connect(myAvatar.get(), &MyAvatar::onLoadComplete, [this, bookmark, bookmarkName, myAvatar, connection1]() { + qCDebug(interfaceapp) << "Finish loading avatar bookmark" << bookmarkName; + QObject::disconnect(*connection1); + myAvatar->clearWornAvatarEntities(); + const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); + myAvatar->setAvatarScale(qScale); + QList attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList(); + myAvatar->setAttachmentsVariant(attachments); + QVariantList avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); + addAvatarEntities(avatarEntities); + emit bookmarkLoaded(bookmarkName); + }); + + std::shared_ptr connection2 = std::make_shared(); + *connection2 = connect(myAvatar.get(), &MyAvatar::onLoadFailed, [this, bookmarkName, connection2]() { + qCDebug(interfaceapp) << "Failed to load avatar bookmark" << bookmarkName; + QObject::disconnect(*connection2); + }); + + qCDebug(interfaceapp) << "Start loading avatar bookmark" << bookmarkName; + const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); - qCDebug(interfaceapp) << "Avatar On"; - const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList(); - - qCDebug(interfaceapp) << "Attach " << attachments; - myAvatar->setAttachmentsVariant(attachments); - - const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); - myAvatar->setAvatarScale(qScale); - - const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); - addAvatarEntities(avatarEntities); - - emit bookmarkLoaded(bookmarkName); } } } From 1a42ad0736cb6d596a88db41091c8f51f3e434ee Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 30 Oct 2019 09:27:37 -0700 Subject: [PATCH 190/203] update launcher text --- .../qml/HFBase/CreateAccountBase.qml | 22 ++++--------------- launchers/qt/resources/qml/HFBase/Error.qml | 2 +- .../qt/resources/qml/HFBase/LoginBase.qml | 2 +- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index c419878413..f788eeaa4d 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -8,7 +8,7 @@ import HQLauncher 1.0 Item { id: root anchors.centerIn: parent - property string titleText: "Sign in and pick a password" + property string titleText: "Create Your Username and Password" property string usernamePlaceholder: "Username" property string passwordPlaceholder: "Set a password (must be at least 6 characters)" property int marginLeft: root.width * 0.15 @@ -28,6 +28,7 @@ Item { HFTextHeader { id: title width: 481 + wrapMode: Text.WordWrap lineHeight: 35 lineHeightMode: Text.FixedHeight text: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh" @@ -39,21 +40,6 @@ Item { } } - HFTextRegular { - id: instruction - width: 425 - - text: "Use the email address you applied for access with" - visible: LauncherState.lastSignupErrorMessage.length == 0 - - anchors { - left: root.left - leftMargin: root.marginLeft - top: title.bottom - topMargin: 18 - } - } - HFTextError { id: error @@ -88,10 +74,10 @@ Item { enabled: root.enabled - placeholderText: "Email Address" + placeholderText: "Verify Your Email" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { - top: instruction.bottom + top: error.visible ? error.bottom : title.bottom left: root.left leftMargin: root.marginLeft topMargin: 18 diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index 98eb1d17ac..5f93931ced 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -45,7 +45,7 @@ Item { HFTextRegular { id: description - text: "We seem to have a problem.\n Please restart Launcher." + text: "We seem to have a problem.\n Please restart." anchors { top: header.bottom diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 1df9950dd4..1f526edf19 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -77,7 +77,7 @@ Item { width: 430 text: LauncherState.lastUsedUsername - placeholderText: "Username" + placeholderText: "Username or Email address" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { From d48097ce2c81a52d87e25fd2b566f11f78cf1d02 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Wed, 30 Oct 2019 10:41:17 -0700 Subject: [PATCH 191/203] Make requested changes --- launchers/qt/resources/qml/HFBase/Error.qml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/launchers/qt/resources/qml/HFBase/Error.qml b/launchers/qt/resources/qml/HFBase/Error.qml index 5f93931ced..8e45c6accb 100644 --- a/launchers/qt/resources/qml/HFBase/Error.qml +++ b/launchers/qt/resources/qml/HFBase/Error.qml @@ -45,7 +45,7 @@ Item { HFTextRegular { id: description - text: "We seem to have a problem.\n Please restart." + text: "We seem to have a problem." anchors { top: header.bottom @@ -54,6 +54,16 @@ Item { } } + HFTextRegular { + text: "Please restart." + + anchors { + top: description.bottom + topMargin: 1 + horizontalCenter: header.horizontalCenter + } + } + HFButton { id: button From 39e10926606ef1768c9679f92f4423d4918b1f1f Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 30 Oct 2019 11:34:41 -0700 Subject: [PATCH 192/203] working on hmd fix --- interface/src/scripting/AudioDevices.cpp | 28 +++++++++++------------- interface/src/scripting/AudioDevices.h | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 8a4e0925e3..306d822ed4 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -271,24 +271,22 @@ std::shared_ptr getSimilarDevice(const QString& deviceNa return devices[minDistanceIndex]; } -void AudioDeviceList::onDevicesChanged(const QList& devices) { + +void AudioDeviceList::onDevicesChanged(QAudio::Mode mode, const QList& devices) { beginResetModel(); QList> newDevices; bool hmdIsSelected = false; bool desktopIsSelected = false; - //getting hmd mode - if (devices.size() > 0) { - auto mode = devices.first().getMode(); - QString name = getHmdAudioDeviceName(mode); - if (!name.isEmpty()) { - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setHmdAudioName", - Q_ARG(QAudio::Mode, mode), - Q_ARG(const QString&, name)); - } + QString name = getHmdAudioDeviceName(mode); + if (!name.isEmpty()) { + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setHmdAudioName", + Q_ARG(QAudio::Mode, mode), + Q_ARG(const QString&, name)); } + if (!_backupSelectedDesktopDeviceName.isEmpty() && !_backupSelectedHMDDeviceName.isEmpty()) { foreach(const HifiAudioDeviceInfo& deviceInfo, devices) { @@ -452,8 +450,8 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { const QList& devicesOutput = client->getAudioDevices(QAudio::AudioOutput); //setup devices - _inputs.onDevicesChanged(devicesInput); - _outputs.onDevicesChanged(devicesOutput); + _inputs.onDevicesChanged(QAudio::AudioInput, devicesInput); + _outputs.onDevicesChanged(QAudio::AudioOutput,devicesOutput); } AudioDevices::~AudioDevices() {} @@ -552,14 +550,14 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList& devices); + void onDevicesChanged(QAudio::Mode mode, const QList& devices); protected: friend class AudioDevices; From 4b4b722875cd64aaa786d637aa5cb592586b0600 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Wed, 30 Oct 2019 16:27:19 -0700 Subject: [PATCH 193/203] fixed issues related to context switching and selection with hmd --- interface/src/scripting/AudioDevices.cpp | 36 ++++++++++++---------- libraries/audio-client/src/AudioClient.cpp | 18 +---------- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 306d822ed4..d66cb84dfe 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -69,18 +69,25 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) { return deviceName; } -static QString getHmdAudioDeviceName(QAudio::Mode mode) { +static void checkHmdDefaultsChange(QAudio::Mode mode) { + QString name; foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { if (displayPlugin && displayPlugin->isHmd()) { if (mode == QAudio::AudioInput) { - return displayPlugin->getPreferredAudioInDevice(); + name = displayPlugin->getPreferredAudioInDevice(); } else { - return displayPlugin->getPreferredAudioOutDevice(); + name = displayPlugin->getPreferredAudioOutDevice(); } break; } } - return QString(); + + if (!name.isEmpty()) { + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setHmdAudioName", + Q_ARG(QAudio::Mode, mode), + Q_ARG(const QString&, name)); + } } Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled }; @@ -278,16 +285,8 @@ void AudioDeviceList::onDevicesChanged(QAudio::Mode mode, const QList> newDevices; bool hmdIsSelected = false; bool desktopIsSelected = false; - - QString name = getHmdAudioDeviceName(mode); - if (!name.isEmpty()) { - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setHmdAudioName", - Q_ARG(QAudio::Mode, mode), - Q_ARG(const QString&, name)); - } - + checkHmdDefaultsChange(mode); if (!_backupSelectedDesktopDeviceName.isEmpty() && !_backupSelectedHMDDeviceName.isEmpty()) { foreach(const HifiAudioDeviceInfo& deviceInfo, devices) { for (bool isHMD : {false, true}) { @@ -441,6 +440,9 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { connect(client, &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection); connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); + + checkHmdDefaultsChange(QAudio::AudioInput); + checkHmdDefaultsChange(QAudio::AudioOutput); _inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), contextIsHMD); _outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD); @@ -449,9 +451,11 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { const QList& devicesInput = client->getAudioDevices(QAudio::AudioInput); const QList& devicesOutput = client->getAudioDevices(QAudio::AudioOutput); - //setup devices - _inputs.onDevicesChanged(QAudio::AudioInput, devicesInput); - _outputs.onDevicesChanged(QAudio::AudioOutput,devicesOutput); + if (devicesInput.size() > 0 && devicesOutput.size() > 0) { + //setup devices + _inputs.onDevicesChanged(QAudio::AudioInput, devicesInput); + _outputs.onDevicesChanged(QAudio::AudioOutput, devicesOutput); + } } AudioDevices::~AudioDevices() {} diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4b7d642a71..d8741e4aa7 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -791,23 +791,7 @@ void AudioClient::start() { outputName = _hmdOutputName; } - HifiAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput, inputName); - qCDebug(audioclient) << "The default audio input device is" << inputDeviceInfo.deviceName(); - bool inputFormatSupported = switchInputToAudioDevice(inputDeviceInfo); - - HifiAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput, outputName); - qCDebug(audioclient) << "The default audio output device is" << outputDeviceInfo.deviceName(); - bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo); - - if (!inputFormatSupported) { - qCDebug(audioclient) << "Unable to set up audio input because of a problem with input format."; - qCDebug(audioclient) << "The closest format available is" << inputDeviceInfo.getDevice().nearestFormat(_desiredInputFormat); - } - - if (!outputFormatSupported) { - qCDebug(audioclient) << "Unable to set up audio output because of a problem with output format."; - qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.getDevice().nearestFormat(_desiredOutputFormat); - } + #if defined(Q_OS_ANDROID) connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); _checkInputTimer.start(CHECK_INPUT_READS_MSECS); From 52e0f54d0c76456b7296bf10a321ba7c65817df2 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Thu, 31 Oct 2019 07:21:16 -0700 Subject: [PATCH 194/203] addressing comments --- interface/src/scripting/AudioDevices.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index d66cb84dfe..688b4df8cd 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -554,14 +554,14 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList Date: Thu, 31 Oct 2019 11:29:46 -0700 Subject: [PATCH 195/203] Attempt to fix DEV-2615 and DEV-2618 with more menu scripting interface guards --- .../src/scripting/MenuScriptingInterface.cpp | 119 +++++++++++++++--- 1 file changed, 100 insertions(+), 19 deletions(-) diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index 1020c12733..9e7f6fdc2b 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -32,106 +32,187 @@ void MenuScriptingInterface::menuItemTriggered() { } void MenuScriptingInterface::addMenu(const QString& menu, const QString& grouping) { - QMetaObject::invokeMethod(Menu::getInstance(), "addMenu", Q_ARG(const QString&, menu), Q_ARG(const QString&, grouping)); + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "addMenu", Q_ARG(const QString&, menu), Q_ARG(const QString&, grouping)); } void MenuScriptingInterface::removeMenu(const QString& menu) { - QMetaObject::invokeMethod(Menu::getInstance(), "removeMenu", Q_ARG(const QString&, menu)); + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "removeMenu", Q_ARG(const QString&, menu)); } bool MenuScriptingInterface::menuExists(const QString& menu) { + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return false; + } + if (QThread::currentThread() == qApp->thread()) { - Menu* menuInstance = Menu::getInstance(); return menuInstance && menuInstance->menuExists(menu); } + bool result { false }; - BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuExists", + + BLOCKING_INVOKE_METHOD(menuInstance, "menuExists", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menu)); + return result; } void MenuScriptingInterface::addSeparator(const QString& menuName, const QString& separatorName) { - QMetaObject::invokeMethod(Menu::getInstance(), "addSeparator", + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "addSeparator", Q_ARG(const QString&, menuName), Q_ARG(const QString&, separatorName)); } void MenuScriptingInterface::removeSeparator(const QString& menuName, const QString& separatorName) { - QMetaObject::invokeMethod(Menu::getInstance(), "removeSeparator", + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "removeSeparator", Q_ARG(const QString&, menuName), Q_ARG(const QString&, separatorName)); } void MenuScriptingInterface::addMenuItem(const MenuItemProperties& properties) { - QMetaObject::invokeMethod(Menu::getInstance(), "addMenuItem", Q_ARG(const MenuItemProperties&, properties)); + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "addMenuItem", Q_ARG(const MenuItemProperties&, properties)); } void MenuScriptingInterface::addMenuItem(const QString& menu, const QString& menuitem, const QString& shortcutKey) { + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + MenuItemProperties properties(menu, menuitem, shortcutKey); - QMetaObject::invokeMethod(Menu::getInstance(), "addMenuItem", Q_ARG(const MenuItemProperties&, properties)); + QMetaObject::invokeMethod(menuInstance, "addMenuItem", Q_ARG(const MenuItemProperties&, properties)); } void MenuScriptingInterface::addMenuItem(const QString& menu, const QString& menuitem) { + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + MenuItemProperties properties(menu, menuitem); - QMetaObject::invokeMethod(Menu::getInstance(), "addMenuItem", Q_ARG(const MenuItemProperties&, properties)); + QMetaObject::invokeMethod(menuInstance, "addMenuItem", Q_ARG(const MenuItemProperties&, properties)); } void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& menuitem) { - QMetaObject::invokeMethod(Menu::getInstance(), "removeMenuItem", + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + QMetaObject::invokeMethod(menuInstance, "removeMenuItem", Q_ARG(const QString&, menu), Q_ARG(const QString&, menuitem)); }; bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) { + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return false; + } + if (QThread::currentThread() == qApp->thread()) { - Menu* menuInstance = Menu::getInstance(); return menuInstance && menuInstance->menuItemExists(menu, menuitem); } + bool result { false }; - BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuItemExists", + + BLOCKING_INVOKE_METHOD(menuInstance, "menuItemExists", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menu), Q_ARG(const QString&, menuitem)); + return result; } bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return false; + } + if (QThread::currentThread() == qApp->thread()) { - Menu* menuInstance = Menu::getInstance(); return menuInstance && menuInstance->isOptionChecked(menuOption); } + bool result { false }; - BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isOptionChecked", + + BLOCKING_INVOKE_METHOD(menuInstance, "isOptionChecked", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menuOption)); return result; } void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool isChecked) { - QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "setIsOptionChecked", Q_ARG(const QString&, menuOption), Q_ARG(bool, isChecked)); } bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return false; + } + if (QThread::currentThread() == qApp->thread()) { - Menu* menuInstance = Menu::getInstance(); return menuInstance && menuInstance->isMenuEnabled(menuOption); } + bool result { false }; - BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isMenuEnabled", + + BLOCKING_INVOKE_METHOD(menuInstance, "isMenuEnabled", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menuOption)); + return result; } void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isChecked) { - QMetaObject::invokeMethod(Menu::getInstance(), "setMenuEnabled", + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "setMenuEnabled", Q_ARG(const QString&, menuOption), Q_ARG(bool, isChecked)); } void MenuScriptingInterface::triggerOption(const QString& menuOption) { - QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption)); + Menu* menuInstance = Menu::getInstance(); + if (!menuInstance) { + return; + } + + QMetaObject::invokeMethod(menuInstance, "triggerOption", Q_ARG(const QString&, menuOption)); } From 30564a67eff0de4f186e6a23ed700caba5a2b19d Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 31 Oct 2019 14:50:52 -0700 Subject: [PATCH 196/203] Only log a single "AEC not supported" warning per audio device configuration --- libraries/audio-client/src/AudioClient.cpp | 29 ++++++++++------------ 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d8741e4aa7..c20fb9da8c 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1187,12 +1187,12 @@ void AudioClient::processWebrtcFarEnd(const int16_t* samples, int numFrames, int const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels); const int numChunk = (int)streamConfig.num_frames(); - if (sampleRate > WEBRTC_SAMPLE_RATE_MAX) { - qCWarning(audioclient) << "WebRTC does not support" << sampleRate << "output sample rate."; - return; - } - if (numChannels > WEBRTC_CHANNELS_MAX) { - qCWarning(audioclient) << "WebRTC does not support" << numChannels << "output channels."; + static int32_t lastWarningHash = 0; + if (sampleRate > WEBRTC_SAMPLE_RATE_MAX || numChannels > WEBRTC_CHANNELS_MAX) { + if (lastWarningHash != ((sampleRate << 8) | numChannels)) { + lastWarningHash = ((sampleRate << 8) | numChannels); + qCWarning(audioclient) << "AEC not unsupported for output format: sampleRate =" << sampleRate << "numChannels =" << numChannels; + } return; } @@ -1228,17 +1228,14 @@ void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numC const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels); const int numChunk = (int)streamConfig.num_frames(); + assert(numFrames == chunk); // WebRTC requires exactly 10ms of input - if (sampleRate > WEBRTC_SAMPLE_RATE_MAX) { - qCWarning(audioclient) << "WebRTC does not support" << sampleRate << "input sample rate."; - return; - } - if (numChannels > WEBRTC_CHANNELS_MAX) { - qCWarning(audioclient) << "WebRTC does not support" << numChannels << "input channels."; - return; - } - if (numFrames != numChunk) { - qCWarning(audioclient) << "WebRTC requires exactly 10ms of input."; + static int32_t lastWarningHash = 0; + if (sampleRate > WEBRTC_SAMPLE_RATE_MAX || numChannels > WEBRTC_CHANNELS_MAX) { + if (lastWarningHash != ((sampleRate << 8) | numChannels)) { + lastWarningHash = ((sampleRate << 8) | numChannels); + qCWarning(audioclient) << "AEC not unsupported for input format: sampleRate =" << sampleRate << "numChannels =" << numChannels; + } return; } From 4b34311a0a71765febeb0493e09ba582ed2a7a37 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 31 Oct 2019 16:23:29 -0700 Subject: [PATCH 197/203] Hooked up seated version of aim offset animation --- .../animations/sitting_idle_aimoffsets.fbx | Bin 0 -> 239664 bytes .../resources/avatar/avatar-animation.json | 3530 +++++++++-------- interface/src/avatar/MyAvatar.cpp | 30 +- 3 files changed, 1878 insertions(+), 1682 deletions(-) create mode 100644 interface/resources/avatar/animations/sitting_idle_aimoffsets.fbx diff --git a/interface/resources/avatar/animations/sitting_idle_aimoffsets.fbx b/interface/resources/avatar/animations/sitting_idle_aimoffsets.fbx new file mode 100644 index 0000000000000000000000000000000000000000..275efc88fb66900184e4ef1f43cbc1494ed81281 GIT binary patch literal 239664 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 literal 0 HcmV?d00001 diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 670c520b65..cfcf1cff73 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -594,86 +594,1285 @@ "children": [ { "children": [ + { + "children": [ + ], + "data": { + "endFrame": 271, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_talk02.fbx" + }, + "id": "seatedTalk02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 252, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_talk03.fbx" + }, + "id": "seatedTalk03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 442, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_talk04.fbx" + }, + "id": "seatedTalk04", + "type": "clip" + } ], "data": { - "endFrame": 271, - "loopFlag": true, + "currentState": "seatedTalk02", + "randomSwitchTimeMax": 12, + "randomSwitchTimeMin": 7, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedTalk02", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedTalk03", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedTalk04", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + } + ], + "triggerRandomSwitch": "seatedTalkSwitch" + }, + "id": "seatedTalk", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle.fbx" + }, + "id": "seatedIdle01", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle02.fbx" + }, + "id": "seatedIdle02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle03.fbx" + }, + "id": "seatedIdle03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle04.fbx" + }, + "id": "seatedIdle04", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 332, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle05.fbx" + }, + "id": "seatedIdle05", + "type": "clip" + } + ], + "data": { + "currentState": "seatedIdle01", + "endFrame": 30, + "randomSwitchTimeMax": 40, + "randomSwitchTimeMin": 10, + "startFrame": 10, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedIdle01", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle02", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle03", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle04", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle05", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + } + ], + "timeScale": 1, + "triggerRandomSwitch": "seatedIdleSwitch", + "triggerTimeMax": 10 + }, + "id": "masterSeatedIdle", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 744, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_shifting.fbx" + }, + "id": "seatedFidgetShifting", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 420, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_lookfidget.fbx" + }, + "id": "seatedFidgetLookFidget", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 282, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_shiftweight.fbx" + }, + "id": "seatedFidgetShiftWeight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 428, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_fidget.fbx" + }, + "id": "seatedFidgeting", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 324, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_lookaround.fbx" + }, + "id": "seatedFidgetLookAround", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 120, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_lookleftright.fbx" + }, + "id": "seatedFidgetLookLeftRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 178, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_leanforward.fbx" + }, + "id": "seatedFidgetLeanForward", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 140, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_shakelegs.fbx" + }, + "id": "seatedFidgetShakeLegs", + "type": "clip" + } + ], + "data": { + "currentState": "seatedFidgetShifting", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetShifting", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLookFidget", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetShiftWeight", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgeting", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLookAround", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLookLeftRight", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLeanForward", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetShakeLegs", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "seatedFidget", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "currentState": "masterSeatedIdle", + "randomSwitchTimeMax": 20, + "randomSwitchTimeMin": 10, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "masterSeatedIdle", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedFidget", + "var": "timeToSeatedFidget" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidget", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetShiftingOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLookFidgetOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetShiftWeightOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetingOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLookAroundOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLookLeftRightOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLeanForwardOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetShakeLegsOnDone" + } + ] + } + ], + "transitionVar": "timeToSeatedFidget", + "triggerRandomSwitch": "", + "triggerTimeMax": 45, + "triggerTimeMin": 10 + }, + "id": "seatedIdle", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "alpha": 1, + "alphaVar": "talkOverlayAlpha", + "boneSet": "upperBody" + }, + "id": "seatedTalkOverlay", + "type": "overlay" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 44, + "loopFlag": false, "startFrame": 1, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_talk02.fbx" + "url": "qrc:///avatar/animations/sitting_emote_agree_headnod.fbx" }, - "id": "seatedTalk02", + "id": "seatedReactionPositiveHeadNod", "type": "clip" }, { "children": [ ], "data": { - "endFrame": 252, - "loopFlag": true, + "endFrame": 78, + "loopFlag": false, "startFrame": 1, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_talk03.fbx" + "url": "qrc:///avatar/animations/sitting_emote_agree_headnodyes.fbx" }, - "id": "seatedTalk03", + "id": "seatedReactionPositiveHeadNodYes", "type": "clip" }, { "children": [ ], "data": { - "endFrame": 442, - "loopFlag": true, - "startFrame": 0, + "endFrame": 65, + "loopFlag": false, + "startFrame": 1, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_talk04.fbx" + "url": "qrc:///avatar/animations/sitting_emote_agree_longheadnod.fbx" }, - "id": "seatedTalk04", + "id": "seatedReactionPositiveLongHeadNod", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 78, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_cheer.fbx" + }, + "id": "seatedReactionPositiveCheer", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 64, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_acknowledge.fbx" + }, + "id": "seatedReactionPositiveAcknowledge", "type": "clip" } ], "data": { - "currentState": "seatedTalk02", + "currentState": "seatedReactionPositiveHeadNod", + "endFrame": 30, + "loopFlag": false, "randomSwitchTimeMax": 12, "randomSwitchTimeMin": 7, + "startFrame": 0, "states": [ { - "easingType": "easeInOutQuad", - "id": "seatedTalk02", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", + "id": "seatedReactionPositiveHeadNod", + "interpDuration": 1, + "interpTarget": 1, "priority": 1, - "resume": true, + "resume": false, "transitions": [ ] }, { - "easingType": "easeInOutQuad", - "id": "seatedTalk03", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", + "id": "seatedReactionPositiveHeadNodYes", + "interpDuration": 1, + "interpTarget": 1, "priority": 1, - "resume": true, + "resume": false, "transitions": [ ] }, { - "easingType": "easeInOutQuad", - "id": "seatedTalk04", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", + "id": "seatedReactionPositiveLongHeadNod", + "interpDuration": 1, + "interpTarget": 1, "priority": 1, - "resume": true, + "resume": false, + "transitions": [ + ] + }, + { + "id": "seatedReactionPositiveCheer", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "id": "seatedReactionPositiveAcknowledge", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, "transitions": [ ] } ], - "triggerRandomSwitch": "seatedTalkSwitch" + "timeScale": 1, + "triggerRandomSwitch": "", + "url": "qrc:///avatar/animations/sitting_idle.fbx" }, - "id": "seatedTalk", + "id": "seatedReactionPositive", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 64, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_headshake.fbx" + }, + "id": "seatedReactionNegativeDisagreeHeadshake", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 99, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_drophead.fbx" + }, + "id": "seatedReactionNegativeDisagreeDropHead", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 124, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_disbelief.fbx" + }, + "id": "seatedReactionNegativeDisagreeDisbelief", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 70, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_dismiss.fbx" + }, + "id": "seatedReactionNegativeDisagreeDismiss", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionNegativeDisagreeHeadshake", + "endFrame": 30, + "loopFlag": false, + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "startFrame": 0, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeHeadshake", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeDropHead", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeDisbelief", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeDismiss", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ], + "timeScale": 1, + "triggerRandomSwitch": "", + "url": "qrc:///avatar/animations/sitting_idle.fbx" + }, + "id": "seatedReactionNegative", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 32, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" + }, + "id": "seatedReactionRaiseHandIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 345, + "loopFlag": true, + "startFrame": 32, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" + }, + "id": "seatedReactionRaiseHandLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 400, + "loopFlag": false, + "startFrame": 345, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" + }, + "id": "seatedReactionRaiseHandOutro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 18, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" + }, + "id": "seatedReactionRaiseHand02Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 378, + "loopFlag": true, + "startFrame": 18, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" + }, + "id": "seatedReactionRaiseHand02Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 435, + "loopFlag": false, + "startFrame": 378, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" + }, + "id": "seatedReactionRaiseHand02Outro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" + }, + "id": "seatedReactionRaiseHand03Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 233, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" + }, + "id": "seatedReactionRaiseHand03Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 296, + "loopFlag": false, + "mirrorFlag": false, + "startFrame": 233, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" + }, + "id": "seatedReactionRaiseHand03Outro", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionRaiseHandIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHandIntro", + "interpDuration": 8, + "interpTarget": 9, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHandLoop", + "var": "seatedReactionRaiseHandIntroOnDone" + } + ] + }, + { + "id": "seatedReactionRaiseHandLoop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHandOutro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHandOutro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHandLoop", + "var": "reactionRaiseHandEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand02Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand02Loop", + "var": "seatedReactionRaiseHand02IntroOnDone" + } + ] + }, + { + "id": "seatedReactionRaiseHand02Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand02Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand02Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand02Loop", + "var": "reactionRaiseHandEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand03Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand03Loop", + "var": "seatedReactionRaiseHand03IntroOnDone" + } + ] + }, + { + "id": "seatedReactionRaiseHand03Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand03Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand03Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand03Loop", + "var": "reactionRaiseHandEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "seatedReactionRaiseHand", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 12, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" + }, + "id": "seatedReactionApplaudIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 76, + "loopFlag": true, + "startFrame": 12, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" + }, + "id": "seatedReactionApplaudLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 99, + "loopFlag": false, + "startFrame": 76, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" + }, + "id": "seatedReactionApplaudOutro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 12, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" + }, + "id": "seatedReactionApplaud02Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 112, + "loopFlag": true, + "startFrame": 12, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" + }, + "id": "seatedReactionApplaud02Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 132, + "loopFlag": false, + "startFrame": 112, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" + }, + "id": "seatedReactionApplaud02Outro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 17, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" + }, + "id": "seatedReactionApplaud03Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 111, + "loopFlag": true, + "startFrame": 17, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" + }, + "id": "seatedReactionApplaud03Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 136, + "loopFlag": false, + "startFrame": 111, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" + }, + "id": "seatedReactionApplaud03Outro", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionApplaudIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaudIntro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaudLoop", + "var": "seatedReactionApplaudIntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaudLoop", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaudOutro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaudOutro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaudLoop", + "var": "reactionApplaudEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud02Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud02Loop", + "var": "seatedReactionApplaud02IntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud02Loop", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud02Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud02Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud02Loop", + "var": "reactionApplaudEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud03Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud03Loop", + "var": "seatedReactionApplaud03IntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud03Loop", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud03Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud03Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud03Loop", + "var": "reactionApplaudEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "seatedReactionApplaud", "type": "randomSwitchStateMachine" }, { @@ -682,139 +1881,111 @@ "children": [ { "children": [ + { + "children": [ + ], + "data": { + "endFrame": 21, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + }, + "id": "seatedReactionPointIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 100, + "loopFlag": true, + "startFrame": 21, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + }, + "id": "seatedReactionPointLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 134, + "loopFlag": false, + "mirrorFlag": false, + "startFrame": 100, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + }, + "id": "seatedReactionPointOutro", + "type": "clip" + } ], "data": { - "endFrame": 800, - "loopFlag": true, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle.fbx" + "currentState": "seatedReactionPointIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPointIntro", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionPointLoop", + "var": "seatedReactionPointIntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPointLoop", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionPointOutro", + "var": "reactionPointDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPointOutro", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionPointLoop", + "var": "reactionPointEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" }, - "id": "seatedIdle01", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 800, - "loopFlag": true, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle02.fbx" - }, - "id": "seatedIdle02", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 800, - "loopFlag": true, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle03.fbx" - }, - "id": "seatedIdle03", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 800, - "loopFlag": true, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle04.fbx" - }, - "id": "seatedIdle04", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 332, - "loopFlag": true, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle05.fbx" - }, - "id": "seatedIdle05", - "type": "clip" + "id": "seatedReactionPoint", + "type": "randomSwitchStateMachine" } ], "data": { - "currentState": "seatedIdle01", - "endFrame": 30, - "randomSwitchTimeMax": 40, - "randomSwitchTimeMin": 10, - "startFrame": 10, - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedIdle01", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": 1, - "resume": true, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedIdle02", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": 1, - "resume": true, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedIdle03", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": 1, - "resume": true, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedIdle04", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": 1, - "resume": true, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedIdle05", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": 1, - "resume": true, - "transitions": [ - ] - } - ], - "timeScale": 1, - "triggerRandomSwitch": "seatedIdleSwitch", - "triggerTimeMax": 10 + "alpha": 0, + "alphaVar": "seatedPointBlendAlpha", + "blendType": "addAbsolute" }, - "id": "masterSeatedIdle", - "type": "randomSwitchStateMachine" + "id": "seatedReactionPointBase", + "type": "blendLinear" }, { "children": [ @@ -822,1555 +1993,562 @@ "children": [ ], "data": { - "endFrame": 744, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_shifting.fbx" - }, - "id": "seatedFidgetShifting", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 420, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_lookfidget.fbx" - }, - "id": "seatedFidgetLookFidget", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 282, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_shiftweight.fbx" - }, - "id": "seatedFidgetShiftWeight", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 428, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_fidget.fbx" - }, - "id": "seatedFidgeting", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 324, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_lookaround.fbx" - }, - "id": "seatedFidgetLookAround", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 120, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_lookleftright.fbx" - }, - "id": "seatedFidgetLookLeftRight", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 178, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_leanforward.fbx" - }, - "id": "seatedFidgetLeanForward", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 140, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle_once_shakelegs.fbx" - }, - "id": "seatedFidgetShakeLegs", - "type": "clip" - } - ], - "data": { - "currentState": "seatedFidgetShifting", - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetShifting", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetLookFidget", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetShiftWeight", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgeting", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetLookAround", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetLookLeftRight", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetLeanForward", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidgetShakeLegs", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - } - ] - }, - "id": "seatedFidget", - "type": "randomSwitchStateMachine" - } - ], - "data": { - "currentState": "masterSeatedIdle", - "randomSwitchTimeMax": 20, - "randomSwitchTimeMin": 10, - "states": [ - { - "easingType": "easeInOutQuad", - "id": "masterSeatedIdle", - "interpDuration": 20, - "interpTarget": 20, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedFidget", - "var": "timeToSeatedFidget" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedFidget", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": -1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetShiftingOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetLookFidgetOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetShiftWeightOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetingOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetLookAroundOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetLookLeftRightOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetLeanForwardOnDone" - }, - { - "randomSwitchState": "masterSeatedIdle", - "var": "seatedFidgetShakeLegsOnDone" - } - ] - } - ], - "transitionVar": "timeToSeatedFidget", - "triggerRandomSwitch": "", - "triggerTimeMax": 45, - "triggerTimeMin": 10 - }, - "id": "seatedIdle", - "type": "randomSwitchStateMachine" - } - ], - "data": { - "alpha": 1, - "alphaVar": "talkOverlayAlpha", - "boneSet": "upperBody" - }, - "id": "seatedTalkOverlay", - "type": "overlay" - }, - { - "children": [ - { - "children": [ - ], - "data": { - "endFrame": 44, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_agree_headnod.fbx" - }, - "id": "seatedReactionPositiveHeadNod", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 78, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_agree_headnodyes.fbx" - }, - "id": "seatedReactionPositiveHeadNodYes", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 65, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_agree_longheadnod.fbx" - }, - "id": "seatedReactionPositiveLongHeadNod", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 78, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_agree_cheer.fbx" - }, - "id": "seatedReactionPositiveCheer", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 64, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_agree_acknowledge.fbx" - }, - "id": "seatedReactionPositiveAcknowledge", - "type": "clip" - } - ], - "data": { - "currentState": "seatedReactionPositiveHeadNod", - "endFrame": 30, - "loopFlag": false, - "randomSwitchTimeMax": 12, - "randomSwitchTimeMin": 7, - "startFrame": 0, - "states": [ - { - "id": "seatedReactionPositiveHeadNod", - "interpDuration": 1, - "interpTarget": 1, - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "id": "seatedReactionPositiveHeadNodYes", - "interpDuration": 1, - "interpTarget": 1, - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "id": "seatedReactionPositiveLongHeadNod", - "interpDuration": 1, - "interpTarget": 1, - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "id": "seatedReactionPositiveCheer", - "interpDuration": 1, - "interpTarget": 1, - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "id": "seatedReactionPositiveAcknowledge", - "interpDuration": 1, - "interpTarget": 1, - "priority": 1, - "resume": false, - "transitions": [ - ] - } - ], - "timeScale": 1, - "triggerRandomSwitch": "", - "url": "qrc:///avatar/animations/sitting_idle.fbx" - }, - "id": "seatedReactionPositive", - "type": "randomSwitchStateMachine" - }, - { - "children": [ - { - "children": [ - ], - "data": { - "endFrame": 64, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_disagree_headshake.fbx" - }, - "id": "seatedReactionNegativeDisagreeHeadshake", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 99, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_disagree_drophead.fbx" - }, - "id": "seatedReactionNegativeDisagreeDropHead", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 124, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_disagree_disbelief.fbx" - }, - "id": "seatedReactionNegativeDisagreeDisbelief", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 70, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_disagree_dismiss.fbx" - }, - "id": "seatedReactionNegativeDisagreeDismiss", - "type": "clip" - } - ], - "data": { - "currentState": "seatedReactionNegativeDisagreeHeadshake", - "endFrame": 30, - "loopFlag": false, - "randomSwitchTimeMax": 10, - "randomSwitchTimeMin": 1, - "startFrame": 0, - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedReactionNegativeDisagreeHeadshake", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionNegativeDisagreeDropHead", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionNegativeDisagreeDisbelief", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionNegativeDisagreeDismiss", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - ] - } - ], - "timeScale": 1, - "triggerRandomSwitch": "", - "url": "qrc:///avatar/animations/sitting_idle.fbx" - }, - "id": "seatedReactionNegative", - "type": "randomSwitchStateMachine" - }, - { - "children": [ - { - "children": [ - ], - "data": { - "endFrame": 32, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" - }, - "id": "seatedReactionRaiseHandIntro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 345, - "loopFlag": true, - "startFrame": 32, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" - }, - "id": "seatedReactionRaiseHandLoop", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 400, - "loopFlag": false, - "startFrame": 345, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" - }, - "id": "seatedReactionRaiseHandOutro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 18, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" - }, - "id": "seatedReactionRaiseHand02Intro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 378, - "loopFlag": true, - "startFrame": 18, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" - }, - "id": "seatedReactionRaiseHand02Loop", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 435, - "loopFlag": false, - "startFrame": 378, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" - }, - "id": "seatedReactionRaiseHand02Outro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 15, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" - }, - "id": "seatedReactionRaiseHand03Intro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 233, - "loopFlag": true, - "mirrorFlag": false, - "startFrame": 15, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" - }, - "id": "seatedReactionRaiseHand03Loop", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 296, - "loopFlag": false, - "mirrorFlag": false, - "startFrame": 233, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" - }, - "id": "seatedReactionRaiseHand03Outro", - "type": "clip" - } - ], - "data": { - "currentState": "seatedReactionRaiseHandIntro", - "randomSwitchTimeMax": 10, - "randomSwitchTimeMin": 1, - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHandIntro", - "interpDuration": 8, - "interpTarget": 9, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHandLoop", - "var": "seatedReactionRaiseHandIntroOnDone" - } - ] - }, - { - "id": "seatedReactionRaiseHandLoop", - "interpDuration": 1, - "interpTarget": 1, - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHandOutro", - "var": "reactionRaiseHandDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHandOutro", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHandLoop", - "var": "reactionRaiseHandEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHand02Intro", - "interpDuration": 8, - "interpTarget": 8, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHand02Loop", - "var": "seatedReactionRaiseHand02IntroOnDone" - } - ] - }, - { - "id": "seatedReactionRaiseHand02Loop", - "interpDuration": 1, - "interpTarget": 1, - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHand02Outro", - "var": "reactionRaiseHandDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHand02Outro", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHand02Loop", - "var": "reactionRaiseHandEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHand03Intro", - "interpDuration": 8, - "interpTarget": 8, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHand03Loop", - "var": "seatedReactionRaiseHand03IntroOnDone" - } - ] - }, - { - "id": "seatedReactionRaiseHand03Loop", - "interpDuration": 1, - "interpTarget": 1, - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHand03Outro", - "var": "reactionRaiseHandDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHand03Outro", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionRaiseHand03Loop", - "var": "reactionRaiseHandEnabled" - } - ] - } - ], - "triggerRandomSwitch": "" - }, - "id": "seatedReactionRaiseHand", - "type": "randomSwitchStateMachine" - }, - { - "children": [ - { - "children": [ - ], - "data": { - "endFrame": 12, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" - }, - "id": "seatedReactionApplaudIntro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 76, - "loopFlag": true, - "startFrame": 12, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" - }, - "id": "seatedReactionApplaudLoop", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 99, - "loopFlag": false, - "startFrame": 76, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" - }, - "id": "seatedReactionApplaudOutro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 12, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" - }, - "id": "seatedReactionApplaud02Intro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 112, - "loopFlag": true, - "startFrame": 12, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" - }, - "id": "seatedReactionApplaud02Loop", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 132, - "loopFlag": false, - "startFrame": 112, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" - }, - "id": "seatedReactionApplaud02Outro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 17, - "loopFlag": false, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" - }, - "id": "seatedReactionApplaud03Intro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 111, - "loopFlag": true, - "startFrame": 17, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" - }, - "id": "seatedReactionApplaud03Loop", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 136, - "loopFlag": false, - "startFrame": 111, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" - }, - "id": "seatedReactionApplaud03Outro", - "type": "clip" - } - ], - "data": { - "currentState": "seatedReactionApplaudIntro", - "randomSwitchTimeMax": 10, - "randomSwitchTimeMin": 1, - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaudIntro", - "interpDuration": 8, - "interpTarget": 8, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaudLoop", - "var": "seatedReactionApplaudIntroOnDone" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaudLoop", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaudOutro", - "var": "reactionApplaudDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaudOutro", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaudLoop", - "var": "reactionApplaudEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud02Intro", - "interpDuration": 8, - "interpTarget": 8, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaud02Loop", - "var": "seatedReactionApplaud02IntroOnDone" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud02Loop", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaud02Outro", - "var": "reactionApplaudDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud02Outro", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaud02Loop", - "var": "reactionApplaudEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud03Intro", - "interpDuration": 8, - "interpTarget": 8, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaud03Loop", - "var": "seatedReactionApplaud03IntroOnDone" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud03Loop", - "interpDuration": 1, - "interpTarget": 1, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaud03Outro", - "var": "reactionApplaudDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud03Outro", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionApplaud03Loop", - "var": "reactionApplaudEnabled" - } - ] - } - ], - "triggerRandomSwitch": "" - }, - "id": "seatedReactionApplaud", - "type": "randomSwitchStateMachine" - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "children": [ - ], - "data": { - "endFrame": 21, - "loopFlag": false, - "startFrame": 1, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" - }, - "id": "seatedReactionPointIntro", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 100, + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 11, "loopFlag": true, - "startFrame": 21, + "startFrame": 11, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" }, - "id": "seatedReactionPointLoop", + "id": "seatedPointLeft", "type": "clip" }, { "children": [ ], "data": { - "endFrame": 134, - "loopFlag": false, - "mirrorFlag": false, - "startFrame": 100, + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 30, + "loopFlag": true, + "startFrame": 30, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" }, - "id": "seatedReactionPointOutro", + "id": "seatedPointRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 50, + "loopFlag": true, + "startFrame": 50, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointUp", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 70, + "loopFlag": true, + "startFrame": 70, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointDown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 90, + "loopFlag": true, + "startFrame": 90, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointUpLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 110, + "loopFlag": true, + "startFrame": 110, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointUpRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 130, + "loopFlag": true, + "startFrame": 130, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointDownLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 150, + "loopFlag": true, + "startFrame": 150, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointDownRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 3, + "loopFlag": true, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointCenter", "type": "clip" } ], "data": { - "currentState": "seatedReactionPointIntro", - "randomSwitchTimeMax": 10, - "randomSwitchTimeMin": 1, - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedReactionPointIntro", - "interpDuration": 18, - "interpTarget": 18, - "interpType": "evaluateBoth", - "priority": 1, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionPointLoop", - "var": "seatedReactionPointIntroOnDone" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionPointLoop", - "interpDuration": 18, - "interpTarget": 18, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionPointOutro", - "var": "reactionPointDisabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionPointOutro", - "interpDuration": 18, - "interpTarget": 18, - "interpType": "evaluateBoth", - "priority": 0, - "resume": false, - "transitions": [ - { - "randomSwitchState": "seatedReactionPointLoop", - "var": "reactionPointEnabled" - } - ] - } + "alpha": [ + 0, + 0, + 0 ], - "triggerRandomSwitch": "" + "alphaVar": "pointAroundAlpha", + "centerId": "seatedPointCenter", + "downId": "seatedPointDown", + "downLeftId": "seatedPointDownLeft", + "downRightId": "seatedPointDownRight", + "leftId": "seatedPointLeft", + "rightId": "seatedPointRight", + "upId": "seatedPointUp", + "upLeftId": "seatedPointUpLeft", + "upRightId": "seatedPointUpRight" }, - "id": "seatedReactionPoint", - "type": "randomSwitchStateMachine" + "id": "seatedPointAround", + "type": "blendDirectional" } ], "data": { "alpha": 0, - "alphaVar": "seatedPointBlendAlpha", + "alphaVar": "pointBlendAlpha", "blendType": "addAbsolute" }, - "id": "seatedReactionPointBase", + "id": "seatedReactionPoint", "type": "blendLinear" - }, - { - "children": [ - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 11, - "loopFlag": true, - "startFrame": 11, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointLeft", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 30, - "loopFlag": true, - "startFrame": 30, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointRight", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 50, - "loopFlag": true, - "startFrame": 50, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointUp", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 70, - "loopFlag": true, - "startFrame": 70, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointDown", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 90, - "loopFlag": true, - "startFrame": 90, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointUpLeft", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 110, - "loopFlag": true, - "startFrame": 110, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointUpRight", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 130, - "loopFlag": true, - "startFrame": 130, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointDownLeft", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 150, - "loopFlag": true, - "startFrame": 150, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointDownRight", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "baseFrame": 1, - "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", - "blendType": "addAbsolute", - "endFrame": 3, - "loopFlag": true, - "startFrame": 3, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" - }, - "id": "seatedPointCenter", - "type": "clip" - } - ], - "data": { - "alpha": [ - 0, - 0, - 0 - ], - "alphaVar": "pointAroundAlpha", - "centerId": "seatedPointCenter", - "downId": "seatedPointDown", - "downLeftId": "seatedPointDownLeft", - "downRightId": "seatedPointDownRight", - "leftId": "seatedPointLeft", - "rightId": "seatedPointRight", - "upId": "seatedPointUp", - "upLeftId": "seatedPointUpLeft", - "upRightId": "seatedPointUpRight" - }, - "id": "seatedPointAround", - "type": "blendDirectional" } ], "data": { - "alpha": 0, - "alphaVar": "pointBlendAlpha", - "blendType": "addAbsolute" + "currentState": "seatedTalkOverlay", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedTalkOverlay", + "interpDuration": 25, + "interpTarget": 25, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPositive", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveHeadNodOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveHeadNodYesOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveLongHeadNodOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveCheerOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveAcknowledgeOnDone" + }, + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegative", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeHeadshakeOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeDropHeadOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeDisbeliefOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeDismissOnDone" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedTalkOverlay", + "var": "reactionRaiseHandDisabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedTalkOverlay", + "var": "reactionApplaudDisabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPoint", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedTalkOverlay", + "var": "reactionPointDisabled" + } + ] + } + ] }, - "id": "seatedReactionPoint", - "type": "blendLinear" + "id": "seatedSM", + "type": "stateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 11, + "loopFlag": true, + "startFrame": 11, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 30, + "loopFlag": true, + "startFrame": 30, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 50, + "loopFlag": true, + "startFrame": 50, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookUp", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 70, + "loopFlag": true, + "startFrame": 70, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookDown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 97, + "loopFlag": true, + "startFrame": 97, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookUpLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 110, + "loopFlag": true, + "startFrame": 110, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookUpRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 130, + "loopFlag": true, + "startFrame": 130, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookDownLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 150, + "loopFlag": true, + "startFrame": 150, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookDownRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 3, + "loopFlag": true, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookCenter", + "type": "clip" + } + ], + "data": { + "alpha": [ + 0, + 0, + 0 + ], + "alphaVar": "lookAroundAlpha", + "centerId": "seatedLookCenter", + "downId": "seatedLookDown", + "downLeftId": "seatedLookDownLeft", + "downRightId": "seatedLookDownRight", + "leftId": "seatedLookLeft", + "rightId": "seatedLookRight", + "upId": "seatedLookUp", + "upLeftId": "seatedLookUpLeft", + "upRightId": "seatedLookUpRight" + }, + "id": "seatedLookAroundBlend", + "type": "blendDirectional" } ], "data": { - "currentState": "seatedTalkOverlay", - "states": [ - { - "easingType": "easeInOutQuad", - "id": "seatedTalkOverlay", - "interpDuration": 25, - "interpTarget": 25, - "interpType": "evaluateBoth", - "transitions": [ - { - "state": "seatedReactionPositive", - "var": "reactionPositiveTrigger" - }, - { - "state": "seatedReactionNegative", - "var": "reactionNegativeTrigger" - }, - { - "state": "seatedReactionRaiseHand", - "var": "reactionRaiseHandEnabled" - }, - { - "state": "seatedReactionApplaud", - "var": "reactionApplaudEnabled" - }, - { - "state": "seatedReactionPoint", - "var": "reactionPointEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionPositive", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "transitions": [ - { - "state": "seatedTalkOverlay", - "var": "seatedReactionPositiveHeadNodOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionPositiveHeadNodYesOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionPositiveLongHeadNodOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionPositiveCheerOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionPositiveAcknowledgeOnDone" - }, - { - "state": "seatedReactionNegative", - "var": "reactionNegativeTrigger" - }, - { - "state": "seatedReactionRaiseHand", - "var": "reactionRaiseHandEnabled" - }, - { - "state": "seatedReactionApplaud", - "var": "reactionApplaudEnabled" - }, - { - "state": "seatedReactionPoint", - "var": "reactionPointEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionNegative", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "transitions": [ - { - "state": "seatedReactionPositive", - "var": "reactionPositiveTrigger" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionNegativeDisagreeHeadshakeOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionNegativeDisagreeDropHeadOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionNegativeDisagreeDisbeliefOnDone" - }, - { - "state": "seatedTalkOverlay", - "var": "seatedReactionNegativeDisagreeDismissOnDone" - }, - { - "state": "seatedReactionRaiseHand", - "var": "reactionRaiseHandEnabled" - }, - { - "state": "seatedReactionApplaud", - "var": "reactionApplaudEnabled" - }, - { - "state": "seatedReactionPoint", - "var": "reactionPointEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionRaiseHand", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "transitions": [ - { - "state": "seatedReactionNegative", - "var": "reactionNegativeTrigger" - }, - { - "state": "seatedReactionPositive", - "var": "reactionPositiveTrigger" - }, - { - "state": "seatedTalkOverlay", - "var": "reactionRaiseHandDisabled" - }, - { - "state": "seatedReactionApplaud", - "var": "reactionApplaudEnabled" - }, - { - "state": "seatedReactionPoint", - "var": "reactionPointEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionApplaud", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "transitions": [ - { - "state": "seatedReactionNegative", - "var": "reactionNegativeTrigger" - }, - { - "state": "seatedReactionPositive", - "var": "reactionPositiveTrigger" - }, - { - "state": "seatedReactionRaiseHand", - "var": "reactionRaiseHandEnabled" - }, - { - "state": "seatedTalkOverlay", - "var": "reactionApplaudDisabled" - }, - { - "state": "seatedReactionPoint", - "var": "reactionPointEnabled" - } - ] - }, - { - "easingType": "easeInOutQuad", - "id": "seatedReactionPoint", - "interpDuration": 12, - "interpTarget": 12, - "interpType": "evaluateBoth", - "transitions": [ - { - "state": "seatedReactionNegative", - "var": "reactionNegativeTrigger" - }, - { - "state": "seatedReactionPositive", - "var": "reactionPositiveTrigger" - }, - { - "state": "seatedReactionRaiseHand", - "var": "reactionRaiseHandEnabled" - }, - { - "state": "seatedReactionApplaud", - "var": "reactionApplaudEnabled" - }, - { - "state": "seatedTalkOverlay", - "var": "reactionPointDisabled" - } - ] - } - ] + "alpha": 0, + "alphaVar": "seatedLookBlendAlpha", + "blendType": "addAbsolute" }, "id": "seated", - "type": "stateMachine" + "type": "blendLinear" }, { "children": [ diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6f9cb9d9c7..7c730e865b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -100,7 +100,7 @@ static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("Disa const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha"; const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha"; -const float HEAD_ALPHA_BLENDING = 1.0f; +const QString SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME = "seatedLookBlendAlpha"; const QString POINT_REACTION_NAME = "point"; const QString POINT_BLEND_DIRECTIONAL_ALPHA_NAME = "pointAroundAlpha"; @@ -6672,8 +6672,17 @@ glm::vec3 MyAvatar::aimToBlendValues(const glm::vec3& aimVector, const glm::quat void MyAvatar::resetHeadLookAt() { if (_skeletonModel->isLoaded()) { - _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), - HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING); + if (isSeated()) { + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), + HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f); + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), + SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f); + } else { + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), + HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f); + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(), + SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f); + } } } @@ -6686,13 +6695,22 @@ void MyAvatar::resetLookAtRotation(const glm::vec3& avatarPosition, const glm::q resetHeadLookAt(); } -void MyAvatar::updateHeadLookAt(float deltaTime) { +void MyAvatar::updateHeadLookAt(float deltaTime) { if (_skeletonModel->isLoaded()) { glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget; glm::vec3 aimVector = lookAtTarget - getDefaultEyePosition(); glm::vec3 lookAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation()); - _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend, - HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING); + if (isSeated()) { + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend, + HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f); + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend, + SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f); + } else { + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend, + HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f); + _skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend, + SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f); + } if (_scriptControlsHeadLookAt) { _scriptHeadControlTimer += deltaTime; From 3cdf25f2bc867acabe1dbca678c4d5607599622d Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 1 Nov 2019 07:36:35 -0700 Subject: [PATCH 198/203] Fix compiler warning (unused variable in Release build) --- libraries/audio-client/src/AudioClient.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c20fb9da8c..d6faea4396 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1227,8 +1227,7 @@ void AudioClient::processWebrtcFarEnd(const int16_t* samples, int numFrames, int void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numChannels, int sampleRate) { const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels); - const int numChunk = (int)streamConfig.num_frames(); - assert(numFrames == chunk); // WebRTC requires exactly 10ms of input + assert(numFrames == (int)streamConfig.num_frames()); // WebRTC requires exactly 10ms of input static int32_t lastWarningHash = 0; if (sampleRate > WEBRTC_SAMPLE_RATE_MAX || numChannels > WEBRTC_CHANNELS_MAX) { From e5fcc577f0bc9c2b01dac1f8cb700ef03f5cb8e6 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Fri, 1 Nov 2019 10:47:12 -0700 Subject: [PATCH 199/203] Changed default interface window size on mac --- libraries/ui/src/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index ffa9bacbaa..d319c8dbf0 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -62,7 +62,7 @@ void MainWindow::restoreGeometry() { // see http://doc.qt.io/qt-5/qsettings.html#restoring-the-state-of-a-gui-application QRect windowGeometry = QGuiApplication::primaryScreen()->availableGeometry(); #if defined(Q_OS_MAC) - windowGeometry.setSize((windowGeometry.size() * 0.5f)); + windowGeometry.setSize((windowGeometry.size() * 0.8f)); #endif QRect geometry = _windowGeometry.get(windowGeometry); #if defined(Q_OS_MAC) From 3f19bbfcacc90454e1686d12bef43153b8fb9f61 Mon Sep 17 00:00:00 2001 From: Rebecca Stankus Date: Fri, 1 Nov 2019 11:46:01 -0700 Subject: [PATCH 200/203] Changed variable to not be magic number --- libraries/ui/src/MainWindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index d319c8dbf0..a242058460 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -62,7 +62,8 @@ void MainWindow::restoreGeometry() { // see http://doc.qt.io/qt-5/qsettings.html#restoring-the-state-of-a-gui-application QRect windowGeometry = QGuiApplication::primaryScreen()->availableGeometry(); #if defined(Q_OS_MAC) - windowGeometry.setSize((windowGeometry.size() * 0.8f)); + const float MACOS_INITIAL_WINDOW_SCALE = 0.8f; + windowGeometry.setSize((windowGeometry.size() * MACOS_INITIAL_WINDOW_SCALE)); #endif QRect geometry = _windowGeometry.get(windowGeometry); #if defined(Q_OS_MAC) From a3d8cb8a78e5b44534f5c68bf2bc2336ceac5bba Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 1 Nov 2019 12:52:22 -0700 Subject: [PATCH 201/203] Ending a joke --- .../emojiApp/resources/modules/customEmojiList.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js b/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js index c1857bbafa..8aa10d4de5 100644 --- a/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js +++ b/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js @@ -1,11 +1,5 @@ var customEmojiList = [ { - "name": "sam", - "filename": "sam.png", - "keywords": [ - "sam", - "troll" - ] } ] From 52ac59fad309b17b5c56446403f6fde296c6972d Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 1 Nov 2019 12:55:34 -0700 Subject: [PATCH 202/203] Ending a joke --- .../resources/images/emojis/512px/sam.png | Bin 258707 -> 0 bytes .../resources/images/emojis/52px/sam.png | Bin 14532 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/images/emojis/512px/sam.png delete mode 100644 scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/images/emojis/52px/sam.png diff --git a/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/images/emojis/512px/sam.png b/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/images/emojis/512px/sam.png deleted file mode 100644 index 1011ebbb44ab1b0691a76d1c9639aaede5365c39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258707 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

xWOXt}1}8i22`0RWbe5;3Ws-L_rHvhTZa z*LC5}2Y=(32LkzaA|X~bc0Y7rf@ag(1)d8qeqL8^JcHMzhNu;`vSZ^!~gEG^IrM>V@c?mz5nw9 z{nHi0mf1L{ECwHy2n0LCwrOkgA2vQl5 zvL3Jr1(69Z)fn7_ki9klAt8u|NT<6*5~=4vTQ}(9CDw2e z#{%{;*THHNzYzVjVC19@ZsTdh=``Z>ddBhfjA@)uN`dnZyF-V=n>}vc-r#U|z-~Vv zbYXcONt(=*MF+nWA6ZUfN0+bptirIhk!*Y*a`kU7jRdeX@xNO%r*&Q2a00IF;JNV{ zD0HPUl(nQ(unzHPedTfGMBi8dFHFJq@3*Oe&3#m-QEpW$Wj3x6A-io1PA0ovS}#PF z=26!Tc15?$x9>jV1GA-Dhsww1czJVv-Ng2xG`aYD*!ZysaB`Iyy(7uxtb%bZ|~0VhX` z`IISJmZs2tJ{JH^P$P~u6kt19WL(BJ*}v2MV8-itdVP91etdd)eLSC!rg@^T2=r`|t1Y-S;=R zyC1OIce*!bYKTc!bp2*~yF8S%c1enA4!^or%f@I%m8+b0jazM`B?8oX4h6i~@_E@b$S5dy2_~z-UHw;-;`QlcpMPKDs)lVQ7g}GA_q$y$?CzE=f zaXOwb9!E@31CT=J(H}b8yxrsW?F|n1H`v|mwfHm5sEb(OjVjGyS6O{~#MGUe_i5E6 zHsn0ELUb+ICpX%9X{Pdda;LR9e>=u)i?HB&Qs0U{&@>$Fh(AjRaT#`nFP!MKLB1M( z0yPQ|%u&%s5HqGEZIG(jvEN9SLUgf(pv}gD7$C1CS0N8L74b0$#WWbawJuoM8QA`C zHs`b^!EB$I30(--?RGdE_So%uH54kqbR2Q=0S+Vb%$EVYBV8Hv9r|uSa2+5HIRkwx z@aGA9I)hFpoKGiA=U0>zG35v^1;N||L+24B08T9INLOL-3<^#|zbd1@5-u`;pl*sf zI4CMGOY_9gM`>iuEs?YC&SFmKbUb~2d3pJCdVPMG;%gQ;IUoi*eX3!gC*u+}U+!B2 z@D+UQ_QQJLCz}R91YOGTQ*!LmF6VOF1%4BPf5YT%AT&r4SE!(*0?A@hAP4UVV2|R} zx>dk4u~3ANQZ(?`(&L@ieQp=fg$`X8;Jlw*-*O?8qR?&M2OJK2y#4M5_xF43_Z@u4 zAU2kMO3hT%#A$+-sh{0qtWi;bw&2(CaTWVp7GHo185phG=WPt|);(}GIcXXI=8Jvx zVwTiSd}J}J3=R%Ma0q=szwgoSI&?!1-*<36fXL%4M@*B-?Q&5PCZ`Oa5~jomDWgjn zrOQg-${Ahh;HAKcGgU`52(gBsu70bN-kfe{22xi1D*~X6LRpdSlp(H%e9m?1pKyE~ zaeO`F^m@YiG$N%0A|Uj7sO}Cu_IEoR?)KQNf0Bo^4dzT<#)71 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%0AEW3@Mhr_*^N6+30-$~U3c!sDINO0ySux)y}!S| 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 z�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

    %UF5_v2(bvC8D_kl2#|!JDaxX(Pc-YC#QYmoE zY1z(N6T{+f8Pi*JYGiCe^@`!W4VQ~BRa}CW;k?x&YR_9Yx_xZgQFn8? zu})KPzh)84i94r_-5PPsHR}Nv7Tj2lR$Bdw*LUNwkYkEu`-U$W4Xi)KI5iuzPWK+o zs0p?iNSb1`X{7+av?Qn)7-Ulf6y<0cv0^=3eROTg7H}iSnp+!zSm`4{SHXS8;24Oxoa~Kqoi3vK1q=^ z8!7>Qhtv>%_2vQt8wyZ;UkYi{z#my|YBj07=<0d)wg4~F6CuJjLA+aw?(ut8klvmB zQuDZYI(y2gsjy&-#rG~u$v$WMG|-A(a){iNeuO{{(Smf+#cCd`YFb8VtQn_`NnWkPuEa!-gonmgnb z|EGrv=*DseEjbmMsy?Qh9KjQin&gkA{bBC?VYw{-Zg-Ol9!!y)?j6pE$hB7LaD}cD zltZBec0YRDQEaV;=Y#zR9!f`KQDr6NJ7wh05qaZ}rnZtKfTmquHa6v9NJQcK!#eBM|$Vo-Q08Q6htNURAptGQ{7~)LM0%X zvu!|Tk656=Wz2@w=VPUe?TO(85j9t;~y>6v9#^Xw;HkmGYWF;eXS;p;z+G+grC@jMe0MD&jClW-o4w-%OMwkpj6r$eVpDN5(lLXh_W@O5q>#;xwkF1t z2L{ZAmdDP!#@PQ!Z$1s`q`=c4CE#}ZEp5yhl~~;>c{&B|_&mxdzgV(rXRfK~^CwP# z?0=?H%-fZ;7&;N~mkf(}df(+kk4s&}_=)D6FyaU7NeClKl}=Wo`v}B(r7qzRUP8dw zG$hsKwhK4W#WN%eADA2g+l8dXS_z;ZmR)%gpQS)x-#STa%S;>9Ow#0T$d>)6JAPGs zRnzUUT~_iBHtO}Y3}M@ZmLLo_r8ASinRX%9Rp5;88cjX=!1S7U{H>{g|4x|QyWOMS zuDA3s$DOU(%Gp--2k|aisoGp|a&)R4ZuhX8r9`)f3#<~i8niMqcJb~-TjlJ{d+t@5 z*~UEc#M56?Do(^fcWgi5q~OGDLJ<-k{6Y`rW4gBXRcP2y;@b+bffL7t8}|rK(>|QT zLr6V9b$g%7W*`CLETYe51=xs6j|@#1dUE6Ne0fAEymxUSQFpjTfa-fe@Ni!`oRd`D zzOA8w=F7#sb~jtz-1RVGCsx7d^e+A#GAzQX2NjZRDxALl>IOBFjCLj$($bim_oHEh UcP)GJ0001NtQ;-t%zX*}2dZ#y761SM diff --git a/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/images/emojis/52px/sam.png b/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/images/emojis/52px/sam.png deleted file mode 100644 index 0aae3ad0b9d01789e61f7c00aec6570e0a64bdcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14532 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 Hu8#hH3bjr5 From 52e80f15af604f8ffe1f1c69ad841a1ca749807e Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 1 Nov 2019 12:57:45 -0700 Subject: [PATCH 203/203] Ending a joke --- .../emojiApp/resources/modules/customEmojiList.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js b/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js index 8aa10d4de5..3ddd33096d 100644 --- a/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js +++ b/scripts/simplifiedUI/simplifiedEmote/emojiApp/resources/modules/customEmojiList.js @@ -1,6 +1,5 @@ var customEmojiList = [ - { - } + ] if (module.exports) {

i&r1 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