Commit d3a0f12f authored by Mahmoud Aglan's avatar Mahmoud Aglan

Import upstream mcpelauncher v1.8.4-qt6 as the baseline

engine/ pins minecraft-linux/mcpelauncher-manifest (qt6 line) as a submodule.
deps/ pins the launcher UI's libraries at the commits used by
mcpelauncher-ui-manifest v1.8.4-qt6. launcher/ is mcpelauncher-ui-qt at
024618f, imported verbatim so later changes are reviewable as diffs.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parents
/build/
/dist/
/upstream/
.DS_Store
*.dmg
/.cache/
[submodule "engine"]
path = engine
url = https://github.com/minecraft-linux/mcpelauncher-manifest.git
ignore = dirty
[submodule "deps/axml-parser"]
path = deps/axml-parser
url = https://github.com/MCMrARM/axml-parser.git
[submodule "deps/file-util"]
path = deps/file-util
url = https://github.com/minecraft-linux/file-util.git
[submodule "deps/google-play-api"]
path = deps/google-play-api
url = https://github.com/minecraft-linux/Google-Play-API.git
[submodule "deps/mcpelauncher-apkinfo"]
path = deps/mcpelauncher-apkinfo
url = https://github.com/minecraft-linux/mcpelauncher-apkinfo.git
[submodule "deps/mcpelauncher-common"]
path = deps/mcpelauncher-common
url = https://github.com/minecraft-linux/mcpelauncher-common.git
[submodule "deps/mcpelauncher-extract"]
path = deps/mcpelauncher-extract
url = https://github.com/minecraft-linux/mcpelauncher-extract.git
[submodule "deps/playdl-signin-ui-qt"]
path = deps/playdl-signin-ui-qt
url = https://github.com/minecraft-linux/playdl-signin-ui-qt.git
Subproject commit e5d26109797c69d66260097b315446e774bc3639
Subproject commit 47193f42d10f0bb4bfcab021f305acb6f76192a0
Subproject commit 873b957403ec9c8aa4c45a277c985a68ff1176bf
Subproject commit b2f3760545450783771288144b56b3a2455b66e2
Subproject commit 277c84a3e238043b0b63732ce9108fab60ce58c9
Subproject commit 365c00ffafd3ef798ec3d775e318c9cefddcf1a6
Subproject commit 1f080c927c070b67fc03a258186591f29bf8eb87
Subproject commit ef9ceb8e0f7f2dcfae8c39e61040b8143bbffef4
on:
workflow_dispatch:
jobs:
sync:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v4
- name: Set up Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Pull translations from Weblate
run: |
git pull https://hosted.weblate.org/git/minecraft-linux/mcpelauncher-ui-qt/
- name: Push changes
run: |
git push
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
CMakeLists.txt.user
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
cmake_minimum_required(VERSION 3.1)
project(mcpelauncher-ui-qt LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# protobuf now requires c++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt6 COMPONENTS Core Quick Concurrent Svg REQUIRED)
find_package(OpenSSL)
option(ENABLE_UPDATE_CHECK "Enables update checking with a remote server")
set(UPDATE_CHECK_URL "" CACHE STRING "Specify the URL for the server to check for updates on")
set(UPDATE_CHECK_BUILD_ID "" CACHE STRING "Specify the numeral version number for the current build")
set(LAUNCHER_VERSIONDB_URL "" CACHE STRING "Specify the remote url of the versiondb to use")
set(LAUNCHER_VERSIONDB_PATH "" CACHE STRING "Specify a local versiondb checkout to embed into this binary")
set(LAUNCHER_CHANGE_LOG "" CACHE STRING "Specify the changelog to show after update")
set(LAUNCHER_VERSION_NAME "" CACHE STRING "Specify the version name shown in settings")
set(LAUNCHER_VERSION_CODE "" CACHE STRING "Specify the version code (integer) shown in settings")
set(LAUNCHER_FLATPAK_CONFIG_URL "" CACHE STRING "Specify the url to the flatpak config to compare the current version")
set(LAUNCHER_VERSION_COMPAT "" CACHE STRING "Specify Minecraft extraVersion compat level")
option(LAUNCHER_DISABLE_DEV_MODE "Disables the dev tab by default" ON)
option(LAUNCHER_ENABLE_GOOGLE_PLAY_LICENCE_CHECK "Enables checking license of the Game" ON)
option(LAUNCHER_MACOS_HAVE_ARMLAUNCHER "Enables use of the arm launcher" ON)
set(LAUNCHER_ENABLE_GLFW_DEFAULT ON)
if(APPLE)
set(LAUNCHER_ENABLE_GLFW_DEFAULT OFF)
endif()
option(LAUNCHER_ENABLE_GLFW "Enables glfw integration" ${LAUNCHER_ENABLE_GLFW_DEFAULT})
find_package(Qt6LinguistTools)
set(LAUNCHER_LINGUIST_RESOURCES "")
if (Qt6LinguistTools_FOUND)
file(GLOB TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.ts")
if(UPDATE_TRANSLATIONS)
qt6_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES})
else()
qt6_add_translation(QM_FILES ${TS_FILES})
endif()
# Generate a .qrc file dynamically
set(QRC_FILE "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc")
file(WRITE ${QRC_FILE} "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
file(APPEND ${QRC_FILE} "<RCC>\n")
file(APPEND ${QRC_FILE} " <qresource prefix=\"/translations\">\n")
foreach(TS_FILE IN LISTS QM_FILES)
# Extract just the filename (basename)
get_filename_component(TS_BASENAME "${TS_FILE}" NAME)
file(APPEND ${QRC_FILE} " <file>${TS_BASENAME}</file>\n")
endforeach()
file(APPEND ${QRC_FILE} " </qresource>\n")
file(APPEND ${QRC_FILE} "</RCC>\n")
set(LAUNCHER_LINGUIST_RESOURCES ${QRC_FILE} ${QM_FILES})
endif()
qt6_add_resources(RESOURCES qml.qrc)
set(RESOURCES ${RESOURCES} ${LAUNCHER_LINGUIST_RESOURCES})
if (NOT "${LAUNCHER_VERSIONDB_PATH}" STREQUAL "")
configure_file(archivalversionlist.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
configure_file("${LAUNCHER_VERSIONDB_PATH}/versions.armeabi-v7a.json.min" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
configure_file("${LAUNCHER_VERSIONDB_PATH}/versions.x86.json.min" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
configure_file("${LAUNCHER_VERSIONDB_PATH}/versions.arm64-v8a.json.min" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
configure_file("${LAUNCHER_VERSIONDB_PATH}/versions.x86_64.json.min" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
set(RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/archivalversionlist.qrc ${RESOURCES})
endif()
add_executable(${PROJECT_NAME} main.cpp crash_handler.cpp crash_handler.h launcherapp.cpp launcherapp.h googleloginhelper.cpp googleloginhelper.h googleaccount.h versionmanager.cpp versionmanager.h apkextractiontask.cpp apkextractiontask.h zipextractiontask.cpp zipextractiontask.h googleplayapi.cpp googleplayapi.h googleversionchannel.cpp googleversionchannel.h googleapkdownloadtask.cpp googleapkdownloadtask.h downloadtask.cpp downloadtask.h modmanager.cpp modmanager.h gamelauncher.cpp gamelauncher.h profilemanager.cpp profilemanager.h qmlurlutils.h archivalversionlist.cpp archivalversionlist.h launchersettings.h troubleshooter.cpp troubleshooter.h updatechecker.cpp updatechecker.h supportedandroidabis.cpp supportedandroidabis.h cpuid.cpp cpuid.h gamepad.h encryption.h encrypted_file_login_cache.cpp encrypted_file_login_cache.h stdio_helper.cpp stdio_helper.h updatemanager.cpp updatemanager.h ${RESOURCES})
if (ENABLE_UPDATE_CHECK)
target_compile_definitions(${PROJECT_NAME} PUBLIC UPDATE_CHECK UPDATE_CHECK_BUILD_ID=${UPDATE_CHECK_BUILD_ID} UPDATE_CHECK_URL="${UPDATE_CHECK_URL}")
endif()
if (NOT "${LAUNCHER_VERSIONDB_URL}" STREQUAL "")
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_VERSIONDB_URL="${LAUNCHER_VERSIONDB_URL}")
endif()
if (NOT "${LAUNCHER_CHANGE_LOG}" STREQUAL "")
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_CHANGE_LOG="${LAUNCHER_CHANGE_LOG}")
endif()
if (NOT "${LAUNCHER_VERSION_CODE}" STREQUAL "")
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_VERSION_CODE="${LAUNCHER_VERSION_CODE}")
endif()
if (NOT "${LAUNCHER_VERSION_NAME}" STREQUAL "")
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_VERSION_NAME="${LAUNCHER_VERSION_NAME}")
endif()
if (NOT "${LAUNCHER_FLATPAK_CONFIG_URL}" STREQUAL "")
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_FLATPAK_CONFIG_URL="${LAUNCHER_FLATPAK_CONFIG_URL}")
endif()
if (NOT "${LAUNCHER_VERSION_COMPAT}" STREQUAL "")
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_VERSION_COMPAT=${LAUNCHER_VERSION_COMPAT})
endif()
if(LAUNCHER_DISABLE_DEV_MODE)
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_DISABLE_DEV_MODE)
endif()
if(LAUNCHER_ENABLE_GOOGLE_PLAY_LICENCE_CHECK)
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_ENABLE_GOOGLE_PLAY_LICENCE_CHECK)
endif()
if(LAUNCHER_MACOS_HAVE_ARMLAUNCHER)
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_MACOS_HAVE_ARMLAUNCHER)
endif()
if(LAUNCHER_ENABLE_GLFW)
include(ext/glfw.cmake)
target_compile_definitions(${PROJECT_NAME} PUBLIC LAUNCHER_ENABLE_GLFW)
target_link_libraries(${PROJECT_NAME} glfw)
endif()
if (APPLE)
target_sources(${PROJECT_NAME} PUBLIC launcherapp_osx.mm supportedandroidabis.mm)
target_link_libraries(${PROJECT_NAME} "-framework AppKit")
if(ENABLE_SPARKLE_UPDATE_CHECK)
target_sources(${PROJECT_NAME} PUBLIC sparkle_updatechecker.mm)
target_compile_definitions(${PROJECT_NAME} PUBLIC SPARKLE_UPDATE_CHECK=1 SPARKLE_UPDATE_CHECK_URL="${SPARKLE_UPDATE_CHECK_URL}")
target_link_libraries(${PROJECT_NAME} "-framework Sparkle")
endif()
endif()
target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Quick Qt6::Svg Qt6::Concurrent playdl-signin-ui-qt-lib mcpelauncher-extract-lib mcpelauncher-common mcpelauncher-apkinfo file-util gplayapi)
if(OpenSSL_FOUND)
target_link_libraries(${PROJECT_NAME} OpenSSL::Crypto)
target_sources(${PROJECT_NAME} PUBLIC encryption.cpp)
else()
target_sources(${PROJECT_NAME} PUBLIC encryption_stub.cpp)
endif()
if (ENABLE_APPIMAGE_UPDATE_CHECK)
target_compile_definitions(${PROJECT_NAME} PUBLIC APPIMAGE_UPDATE_CHECK=1)
target_link_libraries(${PROJECT_NAME} z libappimageupdate)
endif()
install(TARGETS mcpelauncher-ui-qt RUNTIME COMPONENT mcpelauncher-ui-qt DESTINATION bin)
if (NOT APPLE)
install(FILES mcpelauncher-ui-qt.desktop COMPONENT mcpelauncher-ui-qt DESTINATION share/applications)
install(FILES Resources/mcpelauncher-icon.svg RENAME mcpelauncher-ui-qt.svg
COMPONENT mcpelauncher-ui-qt DESTINATION share/pixmaps)
endif()
include(CPackSettings.cmake)
set(CPACK_PACKAGE_NAME "mcpelauncher-ui-qt")
set(CPACK_PACKAGE_VENDOR "mcpelauncher")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Metalauncher for mcpelauncher (allows you to download Minecraft and start the actual launcher)")
set(CPACK_PACKAGE_CONTACT "https://github.com/minecraft-linux/mcpelauncher-ui-manifest/issues")
set(CPACK_PACKAGE_VERSION "${MANIFEST_GIT_COMMIT_HASH}")
set(CPACK_GENERATOR "TGZ;DEB")
set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_CURRENT_BINARY_DIR};mcpelauncher-ui-qt;mcpelauncher-ui-qt;/")
set(CPACK_OUTPUT_CONFIG_FILE CPackConfig.cmake)
set(CPACK_DEBIAN_PACKAGE_VERSION "${BUILD_TIMESTAMP}-${MANIFEST_GIT_COMMIT_HASH}")
if (DEB_OS_NAME MATCHES "ubuntu-xenial")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>=2.14), libssl1.0.0, libuv1, libzip4, libprotobuf9v5, qt59base, qt59declarative, qt59quickcontrols, qt59quickcontrols2, qt59svg, qt59webengine")
set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_DEBIAN_PACKAGE_VERSION}~xenial")
elseif(DEB_OS_NAME MATCHES "ubuntu-bionic")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>=2.14), libssl1.1, libuv1, libzip4, libqt5widgets5, libqt5webenginewidgets5, libqt5quick5, libqt5svg5, libqt5quickcontrols2-5, libqt5quicktemplates2-5, libqt5concurrent5, libprotobuf10, qml-module-qtquick2, qml-module-qtquick-layouts, qml-module-qtquick-controls, qml-module-qtquick-controls2, qml-module-qtquick-window2, qml-module-qtquick-dialogs, qml-module-qt-labs-settings, qml-module-qt-labs-folderlistmodel")
elseif(DEB_OS_NAME MATCHES "ubuntu-disco")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>=2.14), libssl1.1, libuv1, libzip5, libqt5widgets5, libqt5webenginewidgets5, libqt5quick5, libqt5svg5, libqt5quickcontrols2-5, libqt5quicktemplates2-5, libqt5concurrent5, libprotobuf17, qml-module-qtquick2, qml-module-qtquick-layouts, qml-module-qtquick-controls, qml-module-qtquick-controls2, qml-module-qtquick-window2, qml-module-qtquick-dialogs, qml-module-qt-labs-settings, qml-module-qt-labs-folderlistmodel")
set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_DEBIAN_PACKAGE_VERSION}~disco")
endif()
include(CPack)
1
\ No newline at end of file
This diff is collapsed.
_We evaluate to use weblate for translation <https://hosted.weblate.org/projects/minecraft-linux/mcpelauncher-ui-qt/>_
- `cmake -DUPDATE_TRANSLATIONS=ON` updates qm files with source code content
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="icon-edit.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#232323"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#1f1f1f"
inkscape:document-units="mm"
inkscape:zoom="22.062402"
inkscape:cx="22.27772"
inkscape:cy="27.218252"
inkscape:window-width="1918"
inkscape:window-height="1058"
inkscape:window-x="0"
inkscape:window-y="20"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="false" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#cccccc;fill-opacity:0;stroke:#b0b0b0;stroke-width:0.79375;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="M 1.5875,7.9375 1.0583333,11.641667 4.7625,11.1125 l 6.879167,-6.8791667 -3.1750003,-3.175 z"
id="path1"
sodipodi:nodetypes="cccccc" />
<path
style="fill:#cccccc;fill-opacity:0;stroke:#b0b0b0;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 6.6145833,2.9104167 3.175,3.175"
id="path4"
sodipodi:nodetypes="cc" />
<path
style="fill:#cccccc;fill-opacity:0;stroke:#b0b0b0;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="M 3.6143784,8.9853217 8.376927,4.2229293"
id="path5"
sodipodi:nodetypes="cc" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
[
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:layerName CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:mask CDATA #IMPLIED >
<!ATTLIST text
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:width CDATA #IMPLIED >
<!ATTLIST path
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:blendMode CDATA #IMPLIED >
<!ATTLIST path
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowColor CDATA #IMPLIED >
<!ATTLIST path
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowOpacity CDATA #IMPLIED >
<!ATTLIST path
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowRadius CDATA #IMPLIED >
<!ATTLIST path
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowOffset CDATA #IMPLIED >
<!ATTLIST path
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowAngle CDATA #IMPLIED >
<!ATTLIST image
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:blendMode CDATA #IMPLIED >
<!ATTLIST image
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowColor CDATA #IMPLIED >
<!ATTLIST image
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowOpacity CDATA #IMPLIED >
<!ATTLIST image
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowRadius CDATA #IMPLIED >
<!ATTLIST image
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowOffset CDATA #IMPLIED >
<!ATTLIST image
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowAngle CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:blendMode CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowColor CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowOpacity CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowRadius CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowOffset CDATA #IMPLIED >
<!ATTLIST g
xmlns:vectornator CDATA #FIXED "http://vectornator.io"
vectornator:shadowAngle CDATA #IMPLIED >
<!ENTITY % SVG.filter.extra.content "| feDropShadow" >
<!ELEMENT feDropShadow EMPTY>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
dx CDATA #IMPLIED>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
dy CDATA #IMPLIED>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
stdDeviation CDATA #IMPLIED>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
flood-opacity CDATA #IMPLIED>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
in CDATA #IMPLIED>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
result CDATA #IMPLIED>
<!ATTLIST feDropShadow
xmlns:svg CDATA #FIXED "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
flood-color CDATA #IMPLIED>
]
>
<!-- Created with Vectornator (http://vectornator.io/) -->
<svg height="100%" stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="0 0 1024 1024" width="100%" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:vectornator="http://vectornator.io" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient gradientTransform="matrix(1 0 0 1 0 0)" gradientUnits="userSpaceOnUse" id="LinearGradient" x1="289.201" x2="356.853" y1="668.737" y2="512">
<stop offset="0" stop-color="#000000" stop-opacity="0.0813788"/>
<stop offset="1" stop-color="#000000" stop-opacity="0.35149"/>
</linearGradient>
<linearGradient gradientTransform="matrix(-1 0 0 1 1024 0)" gradientUnits="userSpaceOnUse" id="LinearGradient_2" x1="289.201" x2="319.636" y1="668.737" y2="489.357">
<stop offset="0" stop-color="#000000" stop-opacity="0.0813788"/>
<stop offset="1" stop-color="#000000" stop-opacity="0.35149"/>
</linearGradient>
<linearGradient gradientTransform="matrix(-1 0 0 1 1024 0)" gradientUnits="userSpaceOnUse" id="LinearGradient_3" x1="298.03" x2="343.03" y1="512" y2="415.721">
<stop offset="0" stop-color="#000000" stop-opacity="0.0813788"/>
<stop offset="1" stop-color="#1e3905" stop-opacity="0.212173"/>
</linearGradient>
<linearGradient gradientTransform="matrix(1 0 0 1 0 0)" gradientUnits="userSpaceOnUse" id="LinearGradient_4" x1="300.97" x2="331.97" y1="512" y2="404.721">
<stop offset="0" stop-color="#000000" stop-opacity="0.0813788"/>
<stop offset="1" stop-color="#1e3905" stop-opacity="0.244728"/>
</linearGradient>
</defs>
<g id="Layer-1" vectornator:layerName="Layer 1">
<path d="M955.405 768L512 1024L68.595 768L68.595 256L955.405 256L955.405 768Z" fill="#565656" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M68.595 768L512 1024L512 512L68.595 256L68.595 768Z" fill="#636363" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M512 768L622.851 704L622.851 768L512 832L512 768Z" fill="#494949" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M789.128 608L899.979 544L899.979 608L789.128 672L789.128 608Z" fill="#414141" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M595.138 848L705.99 784L705.99 848L595.138 912L595.138 848Z" fill="#404040" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M816.841 752L927.692 688L927.692 752L816.841 816L816.841 752Z" fill="#4a4a4a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M678.277 640L844.554 544L844.554 608L678.277 704L678.277 640Z" fill="#4a4a4a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M456.574 736L512 768L512 832L456.574 800L456.574 736Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M290.298 800L401.149 864L401.149 928L290.298 864L290.298 800Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M234.872 640L345.723 704L345.723 768L234.872 704L234.872 640Z" fill="#4f4f4f" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M151.733 560L262.585 624L262.585 688L151.733 624L151.733 560Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M68.595 640L151.733 688L151.733 752L68.595 704L68.595 640Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M68.595 448L96.3078 464L96.3078 528L68.595 512L68.595 448Z" fill="#515151" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M650.564 784L761.415 720L761.415 784L650.564 848L650.564 784Z" fill="#474747" fill-rule="evenodd" opacity="1" stroke="none"/>
</g>
<g id="Layer-3" vectornator:layerName="Layer 3">
<path d="M68.595 768L512 1024L512 512L68.595 256L68.595 768Z" fill="url(#LinearGradient)" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M955.405 768L512 1024L512 512L955.405 256L955.405 768Z" fill="url(#LinearGradient_2)" fill-rule="evenodd" opacity="1" stroke="none"/>
</g>
<g id="Layer-2" vectornator:layerName="Layer 2">
<path d="M512 480L927.692 240L955.405 256L955.405 384L899.979 416L899.979 480L789.128 544L789.813 479.605L733.703 512L733.703 576L678.277 608L678.277 544L567.426 608L567.426 672L512 704L512 640L512 480Z" fill="#74a500" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M68.595 256L96.3078 240L512 480L512 704L456.574 672L456.574 608L401.149 576L401.149 640L290.298 576L290.298 512L234.872 480L234.872 544L124.021 480L124.021 416L68.595 384L68.595 256Z" fill="#8abb14" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M512 2.05549e-05L955.405 256L512 512L68.595 256L512 2.05549e-05Z" fill="#8ec115" fill-rule="evenodd" opacity="1" stroke="none"/>
</g>
<g id="Layer-5" vectornator:layerName="Layer 5"/>
<g id="Layer-4" vectornator:layerName="Layer 4">
<path d="M955.405 768L512 1024L512 512L955.405 256L955.405 768Z" fill="url(#LinearGradient_3)" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M68.595 768L512 1024L512 512L68.595 256L68.595 768Z" fill="url(#LinearGradient_4)" fill-rule="evenodd" opacity="1" stroke="none"/>
</g>
</svg>
This diff is collapsed.
#include "apkextractiontask.h"
#include <QUrl>
#include <QDebug>
#include <mcpelauncher/zip_extractor.h>
#include <mcpelauncher/minecraft_extract_utils.h>
#include <mcpelauncher/apkinfo.h>
#include <sstream>
#include "versionmanager.h"
#include "supportedandroidabis.h"
ApkExtractionTask::ApkExtractionTask(QObject *parent) : QThread(parent) {
connect(this, &QThread::started, this, &ApkExtractionTask::emitActiveChanged);
connect(this, &QThread::finished, this, &ApkExtractionTask::emitActiveChanged);
connect(this, &ApkExtractionTask::versionInformationObtained, this, &ApkExtractionTask::onVersionInformationObtained);
}
bool ApkExtractionTask::setSourceUrls(QList<QUrl> const& urls) {
QStringList list;
for (auto&& url : urls) {
if (!url.isLocalFile()) {
return false;
}
list.append(url.toLocalFile());
}
setSources(list);
return true;
}
static bool mergeDirsRecusive(QString from, QString to) {
if (!QDir(to).exists()) {
qDebug() << "Moving " << from << " to " << to;
if (!QDir().rename(from, to))
throw std::runtime_error(QObject::tr("renaming versionsfolder failed").toStdString());
return true;
} else {
qDebug() << "Merging " << from << " to " << to;
for (auto&& item : QDir(from).entryList()) {
auto f = from + "/" + item;
auto t = to + "/" + item;
qDebug() << "Checking " << f << " to " << t << " Isdir=" << QDir(f).exists() << " IsFile=" << QFile(f).exists();
if (item == "." || item == "..") {
continue;
}
if (QDir(f).exists()) {
mergeDirsRecusive(f, t);
} else if (QFile(f).exists()) {
QFile().rename(f, t);
}
}
return false;
}
}
void ApkExtractionTask::run() {
QTemporaryDir dir (versionManager()->getTempTemplate());
try {
std::string path = dir.path().toStdString();
ApkInfo apkInfo;
apkInfo.versionCode = 0;
for (auto && source : sources()) {
ZipExtractor extractor (source.toStdString());
bool isBaseApk;
{
auto manifest = extractor.readFile("AndroidManifest.xml");
axml::AXMLFile manifestFile (manifest.data(), manifest.size());
axml::AXMLParser manifestParser (manifestFile);
ApkInfo capkInfo = ApkInfo::fromXml(manifestParser);
if (!apkInfo.versionCode) {
apkInfo = capkInfo;
if(!m_allowedPackages.empty() && !m_allowedPackages.contains(QString::fromStdString(apkInfo.package))) {
throw std::runtime_error(QObject::tr("Trying to import a forbidden apk").toStdString());
}
} else if(apkInfo.versionCode != capkInfo.versionCode) {
throw std::runtime_error(QObject::tr("Trying to extract multiple apks with different versionsCodes is forbidden").toStdString());
} else if(apkInfo.package != capkInfo.package) {
throw std::runtime_error(QObject::tr("Trying to extract multiple apks with different package is forbidden").toStdString());
} else if(apkInfo.versionName.empty()) {
apkInfo.versionName = capkInfo.versionName;
}
isBaseApk = capkInfo.split.empty();
}
qDebug() << "Apk info: versionCode=" << apkInfo.versionCode
<< " versionName=" << QString::fromStdString(apkInfo.versionName);
extractor.extractTo(MinecraftExtractUtils::filterMinecraftFiles(path, isBaseApk),
[this](size_t current, size_t max, ZipExtractor::FileHandle const&, size_t, size_t) {
emit progress((float) current / max);
});
}
bool supported = false;
bool invalidapk = true;
std::stringstream errormsg;
for (auto &&abi : SupportedAndroidAbis::getAbis()) {
if (QFile(dir.path() + "/lib/" + QString::fromStdString(abi.first) + "/libminecraftpe.so").exists()) {
invalidapk = false;
if (!abi.second.compatible) {
errormsg << QObject::tr("This Launcher cannot load Minecraft (%1) on this PC:<br/>%2<br/>").arg(QString::fromStdString(abi.second.details)).arg(QString::fromStdString(abi.second.details)).toStdString();
} else {
supported = true;
}
}
}
if (!m_allowIncompatible && !supported) {
if (invalidapk) {
if (sources().size() == 1) {
errormsg << QObject::tr("The specified file is not a valid Minecraft apk, it doesn't contain libminecraftpe.so").toStdString();
} else {
errormsg << QObject::tr("The specified files are not a valid collection of Minecraft apks, they don't contain libminecraftpe.so").toStdString();
}
}
int i = 0;
for (auto &&abi : SupportedAndroidAbis::getAbis()) {
if (abi.second.compatible) {
if (i++) {
errormsg << ", ";
} else {
errormsg << "<br/>" << QObject::tr("Valid Minecraft apk CPU architectures for this pc / launcher are ").toStdString();
}
errormsg << abi.first;
}
}
if (!i) {
errormsg << "<br/>" << QObject::tr("No Minecraft apk's are valid for this pc / launcher").toStdString();
}
errormsg << "<br/>";
throw std::runtime_error(errormsg.str());
}
if (!m_versionName.isEmpty()) {
apkInfo.versionName = m_versionName.toStdString();
m_versionName.clear();
}
if (apkInfo.versionName.empty()) {
throw std::runtime_error(QObject::tr("unsupported, versionsname of the apk is empty").toStdString());
}
QString targetDir = versionManager()->getDirectoryFor(apkInfo.versionName);
if (mergeDirsRecusive(dir.path(), targetDir)) {
dir.setAutoRemove(false);
}
emit versionInformationObtained(QDir(targetDir).dirName(), QString::fromStdString(apkInfo.versionName), apkInfo.versionCode);
} catch (std::exception& e) {
m_versionName.clear();
emit error(e.what());
return;
}
m_versionName.clear();
emit finished();
}
void ApkExtractionTask::onVersionInformationObtained(const QString &directory, const QString &versionName, int versionCode) {
versionManager()->addVersion(directory, versionName, versionCode);
}
#ifndef APKEXTRACTIONTASK_H
#define APKEXTRACTIONTASK_H
#include <QThread>
#include <QMutex>
#include <QTemporaryDir>
#include "versionmanager.h"
class ApkExtractionTask : public QThread {
Q_OBJECT
Q_PROPERTY(VersionManager* versionManager READ versionManager WRITE setVersionManager)
Q_PROPERTY(QStringList sources READ sources WRITE setSources)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
Q_PROPERTY(bool allowIncompatible READ allowIncompatible WRITE SetAllowIncompatible)
Q_PROPERTY(QString versionName READ versionName WRITE setVersionName)
Q_PROPERTY(QStringList allowedPackages READ allowedPackages WRITE setAllowedPackages)
QMutex mutex;
QStringList m_sources;
VersionManager* m_versionManager;
bool m_allowIncompatible;
QString m_versionName;
QStringList m_allowedPackages;
void run() override;
void emitActiveChanged() {
emit activeChanged();
}
private slots:
void onVersionInformationObtained(QString const& directory, QString const& versionName, int versionCode);
public:
explicit ApkExtractionTask(QObject *parent = nullptr);
bool active() const { return isRunning(); }
QStringList sources() {
QMutexLocker locker(&mutex);
return m_sources;
}
void setSources(QStringList const& value) {
QMutexLocker locker(&mutex);
m_sources = value;
}
VersionManager* versionManager() {
QMutexLocker locker(&mutex);
return m_versionManager;
}
bool allowIncompatible() {
return m_allowIncompatible;
}
void setVersionManager(VersionManager* value) {
QMutexLocker locker(&mutex);
m_versionManager = value;
}
QString versionName() {
return m_versionName;
}
void setVersionName(QString versionName) {
m_versionName = versionName;
}
QStringList allowedPackages() {
return m_allowedPackages;
}
void setAllowedPackages(QStringList const& value) {
m_allowedPackages = value;
}
public slots:
bool setSourceUrls(QList<QUrl> const& urls);
void SetAllowIncompatible(bool c) {
m_allowIncompatible = c;
}
signals:
void progress(qreal progress);
void versionInformationObtained(QString const& directory, QString const& versionName, int versionCode);
void finished();
void error(QString const& err);
void activeChanged();
};
#endif // APKEXTRACTIONTASK_H
#include "archivalversionlist.h"
#include <QNetworkDiskCache>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonArray>
#include <QDir>
#include <QStandardPaths>
ArchivalVersionList::ArchivalVersionList(QString baseUrl) {
m_defBaseUrl = baseUrl;
m_netManager = new QNetworkAccessManager(this);
QNetworkDiskCache* cache = new QNetworkDiskCache(m_netManager);
cache->setCacheDirectory(QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath("versionCache"));
m_netManager->setCache(cache);
}
void ArchivalVersionList::downloadLists(QStringList abis, QString baseUrl) {
m_versionsnext.clear();
m_rollforwardVersionRange.clear();
m_baseUrl = baseUrl.isEmpty() ? m_defBaseUrl : baseUrl;
if (abis.size()) {
auto && versiondburl = m_baseUrl + "/versions." + abis.at(abis.size() - 1) + ".json.min";
qDebug() << "Downloading Versionsdb" << versiondburl;
QNetworkRequest request{QUrl(versiondburl)};
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
QNetworkReply* reply = m_netManager->get(request);
connect(reply, &QNetworkReply::finished, std::bind(&ArchivalVersionList::onListDownloaded, this, reply, abis.at(abis.size() - 1), abis));
} else {
m_versions = m_versionsnext;
emit versionsChanged();
}
}
void ArchivalVersionList::onListDownloaded(QNetworkReply* reply, QString abi, QStringList abis) {
QByteArray data;
QIODevice * result;
if (reply->error() != QNetworkReply::NoError) {
result = m_netManager->cache()->data(QUrl(m_baseUrl + "/versions." + abi + ".json.min"));
if (!result) {
if(!result) {
QString fileName(":/archivalversionlist/" + abi);
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly)) {
m_versions = m_versionsnext;
qDebug() << "Version list failed to load, entry count:" << m_versions.size();
emit versionsChanged();
return;
}
else
{
qDebug() << "Version list failed to update use embedded version";
data = file.readAll();
}
file.close();
}
} else {
data = result->readAll();
delete result;
}
} else {
data = reply->readAll();
}
QJsonDocument doc = QJsonDocument::fromJson(data);
for (QJsonValue const& el : doc.array()) {
QJsonArray ela = el.toArray();
ArchivalVersionInfo* info = new ArchivalVersionInfo(this);
info->versionCode = ela.at(0).toInt();
info->versionName = ela.at(1).toString();
info->isBeta = ela.at(2).toInt() == 1;
// Roll forward
if(ela.count() > 3 && ela.at(0).toInt() < ela.at(3).toInt()) {
RollforwardVersionRange* rollfwd = new RollforwardVersionRange(this);
rollfwd->minVersionCode = ela.at(0).toInt();
rollfwd->maxVersionCode = ela.at(3).toInt();
m_rollforwardVersionRange.push_back(rollfwd);
}
info->abi = abi;
m_versionsnext.push_front(info);
}
auto i = abis.indexOf(abi);
if(i == 0) {
m_versions = m_versionsnext;
qDebug() << "Version list loaded, entry count:" << m_versions.size();
emit versionsChanged();
} else {
auto && versiondburl = m_baseUrl + "/versions." + abis.at(i - 1) + ".json.min";
qDebug() << "Downloading Versionsdb" << versiondburl;
QNetworkRequest request{QUrl(versiondburl)};
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
QNetworkReply* reply = m_netManager->get(request);
connect(reply, &QNetworkReply::finished, std::bind(&ArchivalVersionList::onListDownloaded, this, reply, abis.at(i - 1), abis));
}
}
#ifndef ARCHIVEVERSIONLIST_H
#define ARCHIVEVERSIONLIST_H
#include <QNetworkAccessManager>
class ArchivalVersionInfo : public QObject {
Q_OBJECT
Q_PROPERTY(QString versionName MEMBER versionName CONSTANT)
Q_PROPERTY(int versionCode MEMBER versionCode CONSTANT)
Q_PROPERTY(bool isBeta MEMBER isBeta CONSTANT)
Q_PROPERTY(QString abi MEMBER abi CONSTANT)
public:
QString versionName;
int versionCode;
bool isBeta;
QString abi;
ArchivalVersionInfo(QObject* parent = nullptr) : QObject(parent) {}
};
class RollforwardVersionRange : public QObject {
Q_OBJECT
Q_PROPERTY(int minVersionCode MEMBER minVersionCode CONSTANT)
Q_PROPERTY(int maxVersionCode MEMBER maxVersionCode CONSTANT)
public:
int minVersionCode;
int maxVersionCode;
RollforwardVersionRange(QObject* parent = nullptr) : QObject(parent) {}
};
class ArchivalVersionList : public QObject {
Q_OBJECT
Q_PROPERTY(QList<QObject*> versions READ versions NOTIFY versionsChanged)
Q_PROPERTY(QList<QObject*> rollforwardVersionRange READ rollforwardVersionRange NOTIFY versionsChanged)
private:
QNetworkAccessManager* m_netManager;
QList<QObject*> m_versions;
QList<QObject*> m_versionsnext;
QList<QObject*> m_rollforwardVersionRange;
QString m_baseUrl;
QString m_defBaseUrl;
void onListDownloaded(QNetworkReply* reply, QString abi, QStringList abis);
public:
ArchivalVersionList(QString baseUrl);
QList<QObject*> const& versions() const { return m_versions; }
QList<QObject*> const& rollforwardVersionRange() const { return m_rollforwardVersionRange; }
void downloadLists(QStringList abis, QString versionDBUrl);
signals:
void versionsChanged();
};
#endif // ARCHIVEVERSIONLIST_H
<RCC>
<qresource prefix="/archivalversionlist">
<file alias="armeabi-v7a">versions.armeabi-v7a.json.min</file>
<file alias="x86">versions.x86.json.min</file>
<file alias="arm64-v8a">versions.arm64-v8a.json.min</file>
<file alias="x86_64">versions.x86_64.json.min</file>
</qresource>
</RCC>
\ No newline at end of file
#if defined(__i386__) || defined(__x86_64__)
#include "cpuid.h"
#include <string.h>
void CpuId::cpuid(int *data, int leaf) {
__asm__ ("cpuid"
: "=a" (data[0]), "=b" (data[1]), "=c" (data[2]), "=d" (data[3])
: "a" (leaf));
}
CpuId::CpuId() {
int data[4];
cpuid(data, 0);
hiLeaf = (unsigned int) data[0];
memcpy(&manufacturerId[0], &data[1], 4);
memcpy(&manufacturerId[4], &data[3], 4);
memcpy(&manufacturerId[8], &data[2], 4);
manufacturerId[12] = 0;
cpuid(data, 0x80000000);
hiExtLeaf = (unsigned int) data[0];
}
const char* CpuId::getBrandString() {
if (hasBrandString)
return brandString;
if (hiExtLeaf < 0x80000004)
return nullptr;
int data[4];
for (int i = 0; i < 3; i++) {
cpuid(data, 0x80000002 + i);
memcpy(&brandString[i * 16], data, 16);
}
brandString[48] = 0;
return brandString;
}
void CpuId::queryFeatureFlags() {
if (hasFeatureFlags)
return;
if (hiLeaf < 1)
return;
hasFeatureFlags = true;
int data[4];
cpuid(data, 1);
featureFlagsC = data[2];
featureFlagsD = data[3];
}
bool CpuId::queryFeatureFlag(CpuId::FeatureFlag flag) {
queryFeatureFlags();
auto flagi = (unsigned char) flag;
if (flagi & 128)
return (featureFlagsD & (1 << (flagi & 0x7f))) != 0;
else
return (featureFlagsC & (1 << (flagi & 0x7f))) != 0;
}
#endif
\ No newline at end of file
#pragma once
class CpuId {
private:
unsigned int hiLeaf;
unsigned int hiExtLeaf;
char manufacturerId[4 * 3 + 1];
bool hasBrandString = false;
char brandString[48 + 1];
bool hasFeatureFlags = false;
int featureFlagsC = 0, featureFlagsD = 0;
static void cpuid(int data[4], int leaf);
void queryFeatureFlags();
public:
enum class FeatureFlag : unsigned char {
SSSE3 = 9,
SSE41 = 19,
SSE42 = 20,
POPCNT = 23
};
CpuId();
const char* getManufacturer() {
return manufacturerId;
}
const char* getBrandString();
bool queryFeatureFlag(FeatureFlag flag);
};
\ No newline at end of file
#include "crash_handler.h"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <csignal>
#include <cxxabi.h>
#include <execinfo.h>
#include <thread>
#include <dlfcn.h>
#include <unistd.h>
#include <EnvPathUtil.h>
bool CrashHandler::hasCrashed = false;
int CrashHandler::argc = 0;
char** CrashHandler::argv = nullptr;
#define __ASYNC_SAFE_LOG(msg) write(STDERR_FILENO, msg "\n", sizeof(msg "\n"));
void CrashHandler::handleSignal(int signal, void *aptr) {
__ASYNC_SAFE_LOG("Please try to restart this application using one of the following methods:");
__ASYNC_SAFE_LOG("1. SAFE_MODE=1 USE_WEBENGINE=0 mcpelauncher-ui-qt");
#ifdef __APPLE__
__ASYNC_SAFE_LOG("2. SAFE_MODE=1 USE_WEBENGINE=0 open -a /Applications/Minecraft\\ Bedrock\\ Launcher.app");
#else
__ASYNC_SAFE_LOG("2. flatpak run --env=SAFE_MODE=1 --env=USE_WEBENGINE=0 io.mrarm.mcpelauncher");
#endif
printf("Signal %i received\n", signal);
struct sigaction act;
act.sa_handler = nullptr;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGSEGV, &act, 0);
sigaction(SIGABRT, &act, 0);
sigaction(SIGFPE, &act, 0);
sigaction(SIGBUS, &act, 0);
sigaction(SIGILL, &act, 0);
if (hasCrashed)
return;
hasCrashed = true;
// Workaround against application freeze while dumping stacktrace
// stop app from bouncing more than one sec. on crash macOS x86_64
std::thread([signal](){
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
printf("Backtrace or dumping stack hung up, aborting\n");
printf("Signal Number %d\n", signal);
fflush(stdout);
if(getenv("SAFE_MODE") == nullptr) {
putenv("USE_WEBENGINE=0");
putenv("SAFE_MODE=1");
execv(argv[0], argv);
}
_Exit(signal);
}).detach();
void** ptr = &aptr;
void *array[25];
int count = backtrace(array, 25);
char **symbols = backtrace_symbols(array, count);
char *nameBuf = (char*) malloc(256);
size_t nameBufLen = 256;
printf("Backtrace elements: %i\n", count);
for (int i = 0; i < count; i++) {
if (symbols[i] == nullptr) {
printf("# %i unk [%4p]\n", i, array[i]);
continue;
}
if (symbols[i][0] == '[') { // unknown symbol
Dl_info symInfo;
if (dladdr(array[i], &symInfo)) {
int status = 0;
nameBuf = abi::__cxa_demangle(symInfo.dli_sname, nameBuf, &nameBufLen, &status);
printf("\\# %i NATIVE %s+%p in %s+0x%4p [0x%4p]\n", i, nameBuf, (void *) ((size_t) array[i] - (size_t) symInfo.dli_saddr), symInfo.dli_fname, (void *) ((size_t) array[i] - (size_t) symInfo.dli_fbase), array[i]);
continue;
}
}
printf("# %i %s\n", i, symbols[i]);
}
printf("Dumping stack...\n");
for (int i = 0; i < 1000; i++) {
void* pptr = *ptr;
Dl_info symInfo;
if (pptr && dladdr(pptr, &symInfo)) {
int status = 0;
nameBuf = abi::__cxa_demangle(symInfo.dli_sname, nameBuf, &nameBufLen, &status);
printf("\\# %i NATIVE %s+%p in %s+%4p [%4p]\n", i, nameBuf, (void *) ((size_t) pptr - (size_t) symInfo.dli_saddr), symInfo.dli_fname, (void *) ((size_t) pptr - (size_t) symInfo.dli_fbase), pptr);
}
ptr++;
}
printf("program failed with unix signal number: %d\n", signal);
fflush(stdout);
if(getenv("SAFE_MODE") == nullptr) {
putenv("USE_WEBENGINE=0");
putenv("SAFE_MODE=1");
execv(argv[0], argv);
}
_exit(signal);
}
void CrashHandler::registerCrashHandler(int argc, char**argv) {
printf("SAFE_MODE before exec: %s\n", getenv("SAFE_MODE"));
CrashHandler::argc = argc;
CrashHandler::argv = argv;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = (void (*)(int)) handleSignal;
act.sa_flags = 0;
sigaction(SIGSEGV, &act, 0);
sigaction(SIGABRT, &act, 0);
sigaction(SIGFPE, &act, 0);
sigaction(SIGBUS, &act, 0);
sigaction(SIGILL, &act, 0);
}
#pragma once
class CrashHandler {
private:
static bool hasCrashed;
static int argc;
static char**argv;
static void handleSignal(int signal, void* aptr);
public:
static void registerCrashHandler(int argc, char**argv);
};
\ No newline at end of file
This diff is collapsed.
#ifndef DownloadTask_H
#define DownloadTask_H
#include <QObject>
#include <QTemporaryFile>
#include <QMutex>
#include <zlib.h>
#include <utility>
#include <playapi/api.h>
// struct DownloadProgress {
// std::mutex mtx;
// size_t downloadsize;
// size_t downloads;
// std::vector<size_t> progress;
// };
#include <googleapkdownloadtask.h>
struct DownloadData {
std::string url;
std::string gzippedUrl;
std::string cookie;
std::shared_ptr<DownloadProgress> progress;
std::string componentName;
size_t id;
bool isGzipped = false;
size_t downloadSize = 0;
size_t gzippedDownloadSize = 0;
DownloadData(std::string url, std::string cookie, std::shared_ptr<DownloadProgress> progress, std::string componentName, size_t id)
: url(std::move(url)), cookie(std::move(cookie)), progress(std::move(progress)), componentName(std::move(componentName)), id(id) {}
};
#include <QObject>
#include <QString>
#include <memory>
class DownloadDataWrapper : public QObject {
Q_OBJECT
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QString gzippedUrl READ gzippedUrl WRITE setGzippedUrl NOTIFY gzippedUrlChanged)
Q_PROPERTY(QString cookie READ cookie WRITE setCookie NOTIFY cookieChanged)
Q_PROPERTY(QString componentName READ componentName WRITE setComponentName NOTIFY componentNameChanged)
Q_PROPERTY(bool isGzipped READ isGzipped WRITE setIsGzipped NOTIFY isGzippedChanged)
Q_PROPERTY(size_t id READ id WRITE setId NOTIFY idChanged)
Q_PROPERTY(size_t downloadSize READ downloadSize WRITE setDownloadSize NOTIFY downloadSizeChanged)
Q_PROPERTY(size_t gzippedDownloadSize READ gzippedDownloadSize WRITE setGzippedDownloadSize NOTIFY gzippedDownloadSizeChanged)
public:
explicit DownloadDataWrapper(QObject *parent = nullptr);
// Getters
QString url() const;
QString gzippedUrl() const;
QString cookie() const;
QString componentName() const;
bool isGzipped() const;
size_t id() const;
size_t downloadSize() const;
size_t gzippedDownloadSize() const;
// Setters
void setUrl(const QString &value);
void setGzippedUrl(const QString &value);
void setCookie(const QString &value);
void setComponentName(const QString &value);
void setIsGzipped(bool value);
void setId(size_t value);
void setDownloadSize(size_t value);
void setGzippedDownloadSize(size_t value);
DownloadData toNative() {
return DownloadData(
this->url().toStdString(),
this->cookie().toStdString(),
nullptr, // You'd need logic for wrapping/unwrapping progress
this->componentName().toStdString(),
this->id()
);
}
signals:
void urlChanged();
void gzippedUrlChanged();
void cookieChanged();
void componentNameChanged();
void isGzippedChanged();
void idChanged();
void downloadSizeChanged();
void gzippedDownloadSizeChanged();
private:
QString m_url;
QString m_gzippedUrl;
QString m_cookie;
QString m_componentName;
bool m_isGzipped = false;
size_t m_id = 0;
size_t m_downloadSize = 0;
size_t m_gzippedDownloadSize = 0;
};
class DownloadTask : public QObject {
Q_OBJECT
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
Q_PROPERTY(QStringList filePaths READ filePaths)
Q_PROPERTY(bool keepDownload READ keepApks WRITE setKeepApks)
Q_PROPERTY(bool dryrun MEMBER m_dryrun)
private:
QMutex fileMutex;
std::vector<std::shared_ptr<QTemporaryFile>> files;
std::atomic_bool m_active;
bool m_keepDownload = false;
bool m_dryrun = false;
void startDownload(std::vector<DownloadData> const& dd);
static bool curlDoZlibInflate(z_stream& zs, int file, char* data, size_t len, int flags);
void downloadFile(DownloadData const&dd, std::function<void()> success, std::function<void()> error, std::shared_ptr<DownloadProgress> progress, std::string componentName, size_t id);
public:
explicit DownloadTask(QObject *parent = nullptr);
bool active() const { return m_active; }
bool keepApks() const { return m_keepDownload; }
void setKeepApks(bool keepApks) { m_keepDownload = keepApks; }
Q_INVOKABLE void startDownload(const QList<DownloadDataWrapper*> &downloadList);
QStringList filePaths();
signals:
void progress(qreal progress);
void finished();
void error(QString const& err);
void activeChanged();
void queueDownload(std::vector<DownloadData> dd);
void downloadInfo(QString const& url);
public slots:
void start(bool skipMainApk = false);
};
#endif // DownloadTask_H
#include "encrypted_file_login_cache.h"
#include <fstream>
#include <sstream>
#include <playapi/util/config.h>
#include "encryption.h"
using namespace playapi;
void encrypted_file_login_cache::load() {
std::lock_guard<std::mutex> l (mutex);
config c;
{
std::ifstream fs(path);
if(key.empty()) {
c.load(fs);
} else {
std::stringstream buf;
buf << fs.rdbuf();
Encryption enc;
std::stringstream out;
out << enc.Decrypt(buf.str(), key);
c.load(out);
}
}
int n = c.get_int("token_count", 0);
for (int i = 0; i < n; i++) {
auto p = "token." + std::to_string(i) + ".";
auto exp = std::chrono::system_clock::time_point(std::chrono::milliseconds(c.get_long(p + "expires")));
auth_cookies[{c.get(p + "service"), c.get(p + "app")}] = {c.get(p + "token"), exp};
}
}
void encrypted_file_login_cache::save() {
std::lock_guard<std::mutex> l (mutex);
config c;
int count = 0;
for (auto const& e : auth_cookies) {
if (e.second.second <= std::chrono::system_clock::now())
continue;
auto p = "token." + std::to_string(count) + ".";
c.set(p + "service", e.first.first);
c.set(p + "app", e.first.second);
c.set(p + "token", e.second.first);
c.set_long(p + "expires", std::chrono::duration_cast<std::chrono::milliseconds>(
e.second.second.time_since_epoch()).count());
count++;
}
c.set_int("token_count", count);
{
std::ofstream fs(path);
if(key.empty()) {
c.save(fs);
} else {
std::stringstream buf;
c.save(buf);
Encryption enc;
fs << enc.Encrypt(buf.str(), key);
}
}
}
#pragma once
#include <playapi/login_cache.h>
#include <unordered_map>
#include <mutex>
namespace playapi {
class encrypted_file_login_cache : public login_cache {
private:
std::mutex mutex;
std::string path;
std::string key;
std::map<std::pair<std::string, std::string>,
std::pair<std::string, std::chrono::system_clock::time_point>> auth_cookies;
void load();
void save();
public:
encrypted_file_login_cache(std::string path) : path(std::move(path)) {
load();
}
void clear() {
{
std::lock_guard<std::mutex> l (mutex);
auth_cookies.clear();
}
save();
}
void cache(std::string const& service, std::string const& app, std::string const& token,
std::chrono::system_clock::time_point expire) override {
{
std::lock_guard<std::mutex> l (mutex);
auth_cookies[{service, app}] = {token, expire};
}
save();
}
std::string get_cached(std::string const& service, std::string const& app) override {
std::lock_guard<std::mutex> l (mutex);
auto ret = auth_cookies.find({service, app});
if (ret != auth_cookies.end() && ret->second.second > std::chrono::system_clock::now())
return ret->second.first;
return std::string();
}
void setKey(std::string key) {
this->key = key;
{
std::lock_guard<std::mutex> l (mutex);
auth_cookies.clear();
}
load();
}
};
}
#include <encryption.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <vector>
std::string Encryption::Encrypt(std::string raw, std::string key) {
unsigned char aeskey[48];
if(!PKCS5_PBKDF2_HMAC_SHA1(key.data(), key.size(), NULL, 0, 1000, sizeof(aeskey), aeskey)) {
return "";
}
auto e_ctx = EVP_CIPHER_CTX_new();
int r = EVP_EncryptInit_ex(e_ctx, EVP_aes_256_cbc(), NULL, aeskey, aeskey + 32);
std::vector<unsigned char> encypted((raw.length() / 16 + 1) * 16);
size_t wholelen = 0;
int len = 0;
r = EVP_EncryptUpdate(e_ctx, encypted.data(), &len, (const unsigned char*)raw.data(), raw.length());
wholelen += len;
r = EVP_EncryptFinal_ex(e_ctx, encypted.data() + wholelen, &len);
wholelen += len;
EVP_CIPHER_CTX_free(e_ctx);
std::vector<unsigned char> b64enc(EVP_ENCODE_LENGTH(wholelen));
auto ol = EVP_EncodeBlock(b64enc.data(), encypted.data(), wholelen);
return std::string(b64enc.data(), b64enc.data() + ol);
}
std::string Encryption::Decrypt(std::string raw, std::string key) {
std::vector<unsigned char> decoded(EVP_DECODE_LENGTH(raw.size()));
auto ol = EVP_DecodeBlock(decoded.data(), (const unsigned char*)raw.data(), raw.length()) / 16 * 16;
decoded.resize(ol);
unsigned char aeskey[48];
if(!PKCS5_PBKDF2_HMAC_SHA1(key.data(), key.size(), NULL, 0, 1000, sizeof(aeskey), aeskey)) {
return "";
}
auto d_ctx = EVP_CIPHER_CTX_new();
int r = EVP_DecryptInit_ex(d_ctx, EVP_aes_256_cbc(), NULL, aeskey, aeskey + 32);
std::vector<char> dec(raw.length() + 32);
size_t wholelen = 0;
int outlen = 0;
r = EVP_DecryptUpdate(d_ctx, (unsigned char *)dec.data(), &outlen, (const unsigned char *)decoded.data(), decoded.size());
wholelen += outlen;
r = EVP_DecryptFinal_ex(d_ctx, (unsigned char *)dec.data() + outlen, &outlen);
wholelen += outlen;
EVP_CIPHER_CTX_free(d_ctx);
return std::string(dec.data(), dec.data() + wholelen);
}
std::string Encryption::RandomKey() {
unsigned char buf[255];
RAND_bytes(buf, sizeof(buf));
std::vector<unsigned char> b64enc(EVP_ENCODE_LENGTH(sizeof(buf)));
auto ol = EVP_EncodeBlock(b64enc.data(), buf, sizeof(buf));
return std::string(b64enc.data(), b64enc.data() + ol);
}
#include <string>
class Encryption
{
public:
std::string Encrypt(std::string raw, std::string key);
std::string Decrypt(std::string raw, std::string key);
std::string RandomKey();
};
#include <encryption.h>
std::string Encryption::Encrypt(std::string raw, std::string key) {
return raw;
}
std::string Encryption::Decrypt(std::string raw, std::string key) {
return raw;
}
std::string Encryption::RandomKey() {
return "";
}
include(FetchContent)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_WAYLAND OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
glfw3_ext
URL "https://github.com/glfw/glfw/archive/master.zip"
)
FetchContent_GetProperties(glfw3_ext)
if(NOT glfw3_ext_POPULATED)
FetchContent_Populate(glfw3_ext)
add_subdirectory(${glfw3_ext_SOURCE_DIR} ${glfw3_ext_BINARY_DIR})
endif()
add_library(glfw3 ALIAS glfw)
This diff is collapsed.
#ifndef GAMELAUNCHER_H
#define GAMELAUNCHER_H
#include <QObject>
#include <QProcess>
#include "profilemanager.h"
class GameLauncher : public QObject {
Q_OBJECT
Q_PROPERTY(QString gameDir READ gameDir WRITE setGameDir)
Q_PROPERTY(ProfileInfo* profile READ profile WRITE setProfile)
Q_PROPERTY(bool crashed READ crashed NOTIFY stateChanged)
Q_PROPERTY(bool running READ running NOTIFY stateChanged)
private:
QScopedPointer<QProcess> process;
QScopedPointer<QProcess> fileprocess;
QString m_gameDir;
ProfileInfo* m_profile;
bool m_crashed = false;
bool m_gamelogopen = false;
bool m_disableGameLog = false;
void handleStdOutAvailable();
void handleFinished(int exitCode, QProcess::ExitStatus exitStatus);
void handleError(QProcess::ProcessError error);
public:
explicit GameLauncher(QObject *parent = nullptr);
static std::string findLauncher(std::string name);
QString const& gameDir() { return m_gameDir; }
void setGameDir(QString const& value) { m_gameDir = value; }
ProfileInfo* profile() { return m_profile; }
void setProfile(ProfileInfo* value) { m_profile = value; }
bool running() const { return !process.isNull() && !m_crashed; }
bool crashed() const { return m_crashed; }
public slots:
void start(bool disableGameLog, QString arch = "", bool hasVerifiedLicense = true, QString filepath = "");
void startFile(QString file);
void kill();
void logAttached();
void logDetached();
signals:
void logCleared();
void logAppended(QString const& text);
void stateChanged();
void launchFailed();
void corruptedInstall();
void fileStarted(bool success);
};
#endif // GAMELAUNCHER_H
#pragma once
#include <QObject>
#include <fstream>
class Gamepad : public QObject {
Q_OBJECT
Q_PROPERTY(int id MEMBER m_id NOTIFY metaChanged)
Q_PROPERTY(QString guid MEMBER m_guid NOTIFY metaChanged)
Q_PROPERTY(QString name MEMBER m_name NOTIFY metaChanged)
Q_PROPERTY(QVector<int> buttons READ buttons NOTIFY inputChanged)
Q_PROPERTY(QVector<int> hats READ hats NOTIFY inputChanged)
Q_PROPERTY(QVector<qreal> axes READ axes NOTIFY inputChanged)
Q_PROPERTY(QString fakeGamePadMapping MEMBER m_fakeGamePadMapping NOTIFY metaChanged)
Q_PROPERTY(bool hasMapping MEMBER m_hasMapping NOTIFY mappingChanged)
private:
int m_id;
QString m_guid;
QString m_name;
QVector<int> m_buttons;
QVector<int> m_hats;
QVector<qreal> m_axes;
bool m_isGamePad = false;
QString m_fakeGamePadMapping;
bool m_hasMapping = false;
public:
Gamepad(QObject* parent, int id, QString guid, QString name, QString fakeGamePadMapping) : QObject(parent) {
m_id = id;
m_guid = guid;
m_name = name;
m_fakeGamePadMapping = fakeGamePadMapping;
metaChanged();
}
int id() const {
return m_id;
}
QVector<int> buttons() const {
return m_buttons;
}
QVector<int> hats() const {
return m_hats;
}
QVector<qreal> axes() const {
return m_axes;
}
void updateInput(const unsigned char* buttons, size_t numButtons, const unsigned char* hats, size_t numHats, const float* axes, size_t numAxes) {
bool changed = false;
if(m_buttons.size() != numButtons) {
m_buttons.resize(numButtons);
changed = true;
}
if(m_hats.size() != numHats) {
m_hats.resize(numHats);
changed = true;
}
if(m_axes.size() != numAxes) {
m_axes.resize(numAxes);
changed = true;
}
auto qbuttons = m_buttons.data();
for(int i = 0; i < numButtons; i++) {
if(qbuttons[i] != buttons[i]) {
qbuttons[i] = buttons[i];
changed = true;
}
}
auto qhats = m_hats.data();
for(int i = 0; i < numHats; i++) {
if(qhats[i] != hats[i]) {
qhats[i] = hats[i];
changed = true;
}
}
auto qaxes = m_axes.data();
for(int i = 0; i < numAxes; i++) {
if(qaxes[i] != axes[i]) {
qaxes[i] = axes[i];
changed = true;
}
}
if(changed) {
inputChanged();
}
}
void setHasMapping(bool hasMapping) {
if(m_hasMapping != hasMapping) {
m_hasMapping = hasMapping;
mappingChanged();
}
}
signals:
void inputChanged();
void metaChanged();
void mappingChanged();
};
class GamepadManager : public QObject {
Q_OBJECT
Q_PROPERTY(QStringList errors READ errors NOTIFY errorAdded)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled)
// QList<Gamepad*> works in qt5.15+
Q_PROPERTY(QList<QObject*> gamepads READ gamepads NOTIFY gamepadsChanged)
Q_PROPERTY(bool gameRunning READ gameRunning WRITE setGameRunning)
private:
bool m_enabled = true;
bool m_gameRunning = true;
QStringList m_errors;
QList<QObject*> m_gamepads;
public:
void addError(QString error) {
m_errors << error;
errorAdded();
}
QStringList errors() {
return m_errors;
}
bool gameRunning() {
return m_gameRunning;
}
void setGameRunning(bool running) {
m_gameRunning = running;
}
bool enabled() {
return m_enabled;
}
void setEnabled(bool enabled) {
m_enabled = enabled;
}
QList<QObject*>& gamepads() {
return m_gamepads;
}
signals:
void errorAdded();
void gamepadsChanged();
public slots:
void clearErrors() {
m_errors.clear();
}
void saveMapping(QString datadir, QString mapping) {
std::ofstream gamepaddb((datadir.toStdString() + "/gamecontrollerdb.txt").data(), std::ios::binary | std::ios::ate | std::ios::app);
if(gamepaddb.is_open()) {
gamepaddb << "\n" << mapping.toStdString() << "\n";
}
}
};
#ifndef GOOGLEACCOUNT_H
#define GOOGLEACCOUNT_H
#include <QObject>
class GoogleAccount : public QObject {
Q_OBJECT
Q_PROPERTY(QString accountIdentifier READ accountIdentifier WRITE setAccountIdentifier NOTIFY stateChanged)
Q_PROPERTY(QString accountUserId READ accountUserId WRITE setAccountUserId NOTIFY stateChanged)
Q_PROPERTY(QString accountToken READ accountToken WRITE setAccountToken NOTIFY stateChanged)
private:
QString m_accountIdentifier;
QString m_accountUserId;
QString m_accountToken;
public:
QString const& accountIdentifier() const { return m_accountIdentifier; }
QString const& accountUserId() const { return m_accountUserId; }
QString const& accountToken() const { return m_accountToken; }
void setAccountIdentifier(QString const& value) { m_accountIdentifier = value; stateChanged(); }
void setAccountUserId(QString const& value) { m_accountUserId = value; stateChanged(); }
void setAccountToken(QString const& value) { m_accountToken = value; stateChanged(); }
bool isValid() {
return m_accountIdentifier.size() > 0 && m_accountUserId.size()> 0 && m_accountToken.size() > 0;
}
signals:
void stateChanged();
};
#endif // GOOGLEACCOUNT_H
This diff is collapsed.
#ifndef GOOGLEAPKDOWNLOADTASK_H
#define GOOGLEAPKDOWNLOADTASK_H
#include <QObject>
#include <QTemporaryFile>
#include <QMutex>
#include <zlib.h>
#include <playapi/api.h>
#include <utility>
#include "googleplayapi.h"
struct DownloadProgress {
std::mutex mtx;
size_t downloadsize;
size_t downloads;
std::vector<size_t> progress;
std::vector<size_t> downloadedSizes;
bool failed;
};
class GoogleApkDownloadTask : public QObject {
Q_OBJECT
Q_PROPERTY(GooglePlayApi* playApi WRITE setPlayApi)
Q_PROPERTY(QString packageName WRITE setPackageName READ packageName)
Q_PROPERTY(qint32 versionCode WRITE setVersionCode READ versionCode)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
Q_PROPERTY(QStringList filePaths READ filePaths)
Q_PROPERTY(bool keepApks READ keepApks WRITE setKeepApks)
Q_PROPERTY(bool dryrun MEMBER m_dryrun)
private:
GooglePlayApi* m_playApi = nullptr;
QString m_packageName;
qint32 m_versionCode;
QMutex fileMutex;
std::vector<std::shared_ptr<QTemporaryFile>> files;
std::atomic_bool m_active;
bool m_keepApks = false;
bool m_dryrun = false;
void startDownload(playapi::proto::finsky::download::AndroidAppDeliveryData const &dd, bool skipMainApk = false);
static bool curlDoZlibInflate(z_stream& zs, int file, char* data, size_t len, int flags);
template<class T, class U> void downloadFile(T const&dd, U cookie, std::function<void()> success, std::function<void()> error, std::shared_ptr<DownloadProgress> progress, std::string componentName, size_t id);
public:
explicit GoogleApkDownloadTask(QObject *parent = nullptr);
void setPlayApi(GooglePlayApi* value);
bool active() const { return m_active; }
bool keepApks() const { return m_keepApks; }
void setKeepApks(bool keepApks) { m_keepApks = keepApks; }
QString packageName() const { return m_packageName; }
void setPackageName(QString packageName) { m_packageName = packageName; }
qint32 versionCode() const { return m_versionCode; }
void setVersionCode(qint32 versionCode) { m_versionCode = versionCode; }
QStringList filePaths();
signals:
void progress(qreal progress);
void finished();
void error(QString const& err);
void activeChanged();
void queueDownload(playapi::proto::finsky::download::AndroidAppDeliveryData dd, bool skipMainApk);
void downloadInfo(QString const& url);
public slots:
void start(bool skipMainApk = false);
};
#endif // GOOGLEAPKDOWNLOADTASK_H
This diff is collapsed.
#ifndef GOOGLELOGINHELPER_H
#define GOOGLELOGINHELPER_H
#include <QObject>
#include <QSettings>
#include <playapi/login.h>
#include <playapi/device_info.h>
#include "encrypted_file_login_cache.h"
#include "googleaccount.h"
class QWindow;
class GoogleLoginWindow;
class GoogleLoginHelper : public QObject {
Q_OBJECT
Q_PROPERTY(GoogleAccount* account READ account NOTIFY accountInfoChanged)
Q_PROPERTY(bool includeIncompatible READ getIncludeIncompatible WRITE setIncludeIncompatible)
Q_PROPERTY(QString singleArch READ getSingleArch WRITE setSingleArch)
Q_PROPERTY(bool hideLatest READ hideLatest NOTIFY accountInfoChanged)
Q_PROPERTY(bool hasEncryptedCredentials READ getHasEncryptedCredentials NOTIFY accountInfoChanged)
Q_PROPERTY(QString unlockkey READ getUnlockkey WRITE setUnlockkey NOTIFY accountInfoChanged)
Q_PROPERTY(bool chromeOS MEMBER chromeOS WRITE setChromeOS)
private:
QSettings settings;
GoogleLoginWindow* window = nullptr;
GoogleAccount currentAccount;
playapi::device_info device;
playapi::encrypted_file_login_cache loginCache;
playapi::login_api login;
bool hasAccount = false;
bool includeIncompatible = false;
bool hasEncryptedCredentials = false;
QString singleArch;
QString unlockkey;
bool chromeOS = false;
static std::string getTokenCachePath();
void loadDeviceState();
void saveDeviceState();
void onLoginFinished(int code);
bool getIncludeIncompatible() {
return includeIncompatible;
}
QString getSingleArch() {
return singleArch;
}
QString getUnlockkey() {
return unlockkey;
}
void loadAccount();
void updateDevice();
void setIncludeIncompatible(bool includeIncompatible) {
if (this->includeIncompatible != includeIncompatible) {
this->includeIncompatible = includeIncompatible;
updateDevice();
}
}
void setSingleArch(QString singleArch) {
if (this->singleArch != singleArch) {
this->singleArch = singleArch;
updateDevice();
}
}
void setUnlockkey(QString key) {
if (this->unlockkey != key) {
this->unlockkey = key;
loadAccount();
}
}
public:
bool getHasEncryptedCredentials() {
return hasEncryptedCredentials;
}
void setChromeOS(bool isChromeOS) {
auto shouldUpdateDevice = chromeOS != isChromeOS;
chromeOS = isChromeOS;
if(shouldUpdateDevice) {
updateDevice();
}
}
bool isChromeOS() {
return chromeOS;
}
GoogleLoginHelper();
~GoogleLoginHelper();
GoogleAccount* account() {
return hasAccount ? &currentAccount : nullptr;
}
playapi::device_info& getDevice() { return device; }
playapi::login_api& getLoginApi() { return login; }
bool hideLatest();
public slots:
void acquireAccount(QWindow *parent);
void signOut();
QStringList getAbis(bool includeIncompatible);
QString GetSupportReport();
bool isSupported();
signals:
void accountAcquireFinished(GoogleAccount* account);
void accountInfoChanged();
void loginError(QString error);
};
#endif // GOOGLELOGINHELPER_H
#include "googleplayapi.h"
#include "googleloginhelper.h"
#include "supportedandroidabis.h"
#include <QtConcurrent>
GooglePlayApi::GooglePlayApi(QObject *parent) : QObject(parent) {
}
void GooglePlayApi::setLogin(GoogleLoginHelper *helper) {
if (loginHelper != helper) {
setStatus(GooglePlayApiStatus::NOT_READY);
if (loginHelper) {
disconnect(loginHelper, &GoogleLoginHelper::accountInfoChanged, this, &GooglePlayApi::updateLogin);
}
loginHelper = helper;
if (loginHelper) {
api.reset(new playapi::api(loginHelper->getDevice()));
connect(loginHelper, &GoogleLoginHelper::accountInfoChanged, this, &GooglePlayApi::updateLogin);
updateLogin();
}
}
}
void GooglePlayApi::requestAppInfo(const QString &packageName) {
if (status == GooglePlayApiStatus::SUCCEDED) {
api->details(packageName.toStdString())->call([this, packageName](playapi::proto::finsky::response::ResponseWrapper&& resp) {
auto details = resp.payload().detailsresponse().docv2();
emit appInfoReceived(packageName, QString::fromStdString(details.details().appdetails().versionstring()), details.details().appdetails().versioncode(), details.details().appdetails().testingprograminfo().subscribed() || details.details().appdetails().testingprograminfo().subscribed1() );
}, [this, packageName](std::exception_ptr e) {
try {
std::rethrow_exception(e);
} catch (std::exception&ex) {
emit appInfoFailed(packageName, ex.what());
}
});
} else {
emit appInfoFailed(packageName, tr("GooglePlayApi not Ready status=%1").arg((int)status));
}
}
QString GooglePlayApi::CheckinInfoGroup() {
std::stringstream ss;
ss << "checkin_2025_09_";
for (auto&& abi : loginHelper->getDevice().config_native_platforms) {
ss << "__" << abi;
}
if(loginHelper->isChromeOS()) {
ss << "__chromeos";
}
return QString::fromStdString(ss.str());
}
void GooglePlayApi::loadCheckinInfo() {
QSettings settings;
settings.beginGroup(CheckinInfoGroup());
checkinResult.time = settings.value("time").toLongLong();
checkinResult.android_id = settings.value("android_id").toULongLong();
checkinResult.security_token = settings.value("security_token").toULongLong();
checkinResult.device_data_version_info = settings.value("device_data_version_info").toString().toStdString();
settings.endGroup();
}
void GooglePlayApi::saveCheckinInfo() {
QSettings settings;
settings.beginGroup(CheckinInfoGroup());
settings.setValue("time", checkinResult.time);
settings.setValue("android_id", checkinResult.android_id);
settings.setValue("security_token", checkinResult.security_token);
settings.setValue("device_data_version_info", QString::fromStdString(checkinResult.device_data_version_info));
settings.endGroup();
}
void GooglePlayApi::loadApiInfo() {
std::lock_guard<std::mutex> lock (api->info_mutex);
QSettings settings;
settings.beginGroup("playapi");
api->device_config_token = settings.value("device_config_token").toString().toStdString();
api->toc_cookie = settings.value("toc_cookie").toString().toStdString();
api->experiments.set_targets(settings.value("experiments").toString().toStdString());
settings.endGroup();
}
void GooglePlayApi::saveApiInfo() {
std::lock_guard<std::mutex> lock (api->info_mutex);
QSettings settings;
settings.beginGroup("playapi");
settings.setValue("device_config_token", QString::fromStdString(api->device_config_token));
settings.setValue("toc_cookie", QString::fromStdString(api->toc_cookie));
settings.setValue("experiments", QString::fromStdString(api->experiments.get_comma_separated_target_list()));
settings.endGroup();
}
void GooglePlayApi::cleanupLogin() {
QSettings settings;
settings.remove("playapi");
for (auto && group : settings.childGroups()) {
if (group.startsWith("checkin")) {
settings.remove(group);
}
}
}
void GooglePlayApi::updateLogin() {
auto previousUpdateLoginTask = updateLoginTask;
updateLoginTask = QtConcurrent::run([this, previousUpdateLoginTask]() {
try {
QFuture<void> ptask = previousUpdateLoginTask;
ptask.waitForFinished();
QMutexLocker checkinMutexLocker (&checkinMutex);
if (status == GooglePlayApiStatus::PENDING) {
emit initError(tr("<b>Please report this error</b><br>GooglePlayApi already in progress status reporting not working status=%1").arg((int)status));
}
setStatus(GooglePlayApiStatus::PENDING);
loadCheckinInfo();
if (!loginHelper) {
setStatus(GooglePlayApiStatus::FAILED);
emit initError(tr("<b>Please report this error</b><br>GooglePlayApi needs the loginHelper"));
return;
} else if (loginHelper->account() == nullptr) {
if(!loginHelper->getHasEncryptedCredentials()) {
cleanupLogin();
}
setStatus(GooglePlayApiStatus::FAILED);
return;
} else if (checkinResult.android_id == 0) {
playapi::checkin_api checkin(loginHelper->getDevice());
checkin.add_auth(loginHelper->getLoginApi())->call();
checkinResult = checkin.perform_checkin()->call();
saveCheckinInfo();
}
api->set_checkin_data(checkinResult);
checkinMutexLocker.unlock();
api->set_auth(loginHelper->getLoginApi())->call();
loadApiInfo();
api->info_mutex.lock();
bool needsAcceptTos = api->toc_cookie.empty() || api->device_config_token.empty();
api->info_mutex.unlock();
if (needsAcceptTos) {
api->fetch_user_settings()->call();
auto toc = api->fetch_toc()->call();
if (toc.payload().tocresponse().has_cookie())
api->set_toc_cookie(toc.payload().tocresponse().cookie());
if (api->fetch_toc()->call().payload().tocresponse().requiresuploaddeviceconfig()) {
auto resp = api->upload_device_config()->call();
api->set_device_config_token(resp.payload().uploaddeviceconfigresponse().uploaddeviceconfigtoken());
toc = api->fetch_toc()->call();
if (toc.payload().tocresponse().requiresuploaddeviceconfig() || !toc.payload().tocresponse().has_cookie())
throw std::runtime_error("Invalid state");
api->set_toc_cookie(toc.payload().tocresponse().cookie());
if (toc.payload().tocresponse().has_toscontent() && toc.payload().tocresponse().has_tostoken()) {
auto tos = api->accept_tos(toc.payload().tocresponse().tostoken(), false)->call();
if (!tos.payload().has_accepttosresponse())
throw std::runtime_error("Invalid state");
saveApiInfo();
}
}
}
setStatus(GooglePlayApiStatus::SUCCEDED);
emit ready();
} catch (const std::exception& ex) {
setStatus(GooglePlayApiStatus::FAILED);
cleanupLogin();
emit initError(ex.what());
}
});
}
void GooglePlayApi::validateLicense(std::string packagename, int versionscode, std::function<void(bool)> callback) {
api->delivery(packagename, versionscode, std::string())->call([callback](playapi::proto::finsky::response::ResponseWrapper&& resp) {
auto dd = resp.payload().deliveryresponse().appdeliverydata();
callback((dd.has_gzippeddownloadurl() ? dd.gzippeddownloadurl() : dd.downloadurl()) != "");
}, [callback](std::exception_ptr e) {
callback(false);
});
}
#ifndef GOOGLEPLAYAPI_H
#define GOOGLEPLAYAPI_H
#include <QFuture>
#include <QObject>
#include <QMutex>
#include <QThread>
#include <QSettings>
#include <future>
#include <playapi/api.h>
class GoogleLoginHelper;
class GooglePlayApi : public QObject {
Q_OBJECT
Q_PROPERTY(GoogleLoginHelper* login READ getLogin WRITE setLogin)
Q_PROPERTY(GooglePlayApiStatus status READ getStatus NOTIFY statusChanged)
public:
enum class GooglePlayApiStatus {
NOT_READY, PENDING, FAILED, SUCCEDED
};
Q_ENUM(GooglePlayApiStatus)
private:
QScopedPointer<playapi::api> api;
GoogleLoginHelper* loginHelper = nullptr;
QMutex checkinMutex;
playapi::checkin_result checkinResult;
std::promise<std::pair<bool, bool>> tosApprovalPromise;
GooglePlayApiStatus status = GooglePlayApiStatus::NOT_READY;
QFuture<void> updateLoginTask;
QString CheckinInfoGroup();
void loadCheckinInfo();
void saveCheckinInfo();
void loadApiInfo();
void saveApiInfo();
void setStatus(GooglePlayApiStatus status) {
if (this->status != status) {
this->status = status;
statusChanged();
}
}
void updateLogin();
public:
explicit GooglePlayApi(QObject *parent = nullptr);
void setLogin(GoogleLoginHelper* helper);
GoogleLoginHelper* getLogin() { return loginHelper; }
playapi::api* getApi() { return api.data(); }
GooglePlayApiStatus getStatus() const { return status; }
signals:
void ready();
void initError(QString const& text);
void tosApprovalRequired(QString const& tosText, QString const& marketingText);
void appInfoReceived(QString const& packageName, QString const& version, int versionCode, bool isBeta);
void appInfoFailed(QString const& packageName, QString errorMessage);
void statusChanged();
public slots:
void requestAppInfo(QString const& packageName);
void validateLicense(std::string packagename, int versionscode, std::function<void (bool)> callback);
void setTosApproved(bool approved, bool marketing) {
tosApprovalPromise.set_value({approved, marketing});
}
void cleanupLogin();
};
#endif // GOOGLEPLAYAPI_H
#include "googleversionchannel.h"
#include "googleplayapi.h"
#include "googleloginhelper.h"
#include "googleversionchannel.h"
GoogleVersionChannel::GoogleVersionChannel() {
m_settings.beginGroup("googleversionchannel");
m_latestVersion = m_settings.value("latest_version").toString();
m_latestVersionCode = m_settings.value("latest_version_code").toInt();
m_latestVersionIsBeta = m_settings.value("latest_version_isbeta").toBool();
}
void GoogleVersionChannel::setPlayApi(GooglePlayApi *value) {
licenseStatus = GoogleVersionChannelLicenceStatus::NOT_READY;
setStatus(GoogleVersionChannelStatus::NOT_READY);
if (m_playApi != nullptr) {
disconnect(m_playApi, &GooglePlayApi::ready, this, &GoogleVersionChannel::onApiReady);
disconnect(m_playApi, &GooglePlayApi::appInfoReceived, this, &GoogleVersionChannel::onAppInfoReceived);
disconnect(m_playApi, &GooglePlayApi::appInfoFailed, this, &GoogleVersionChannel::onAppInfoFailed);
}
m_playApi = value;
if (value) {
connect(value, &GooglePlayApi::statusChanged, this, &GoogleVersionChannel::onStatusChanged);
connect(value, &GooglePlayApi::ready, this, &GoogleVersionChannel::onApiReady);
connect(value, &GooglePlayApi::appInfoReceived, this, &GoogleVersionChannel::onAppInfoReceived);
connect(value, &GooglePlayApi::appInfoFailed, this, &GoogleVersionChannel::onAppInfoFailed);
}
onStatusChanged();
}
void GoogleVersionChannel::onApiReady() {
setStatus(GoogleVersionChannelStatus::PENDING);
m_playApi->requestAppInfo(m_trialMode ? "com.mojang.minecrafttrialpe" : "com.mojang.minecraftpe");
}
void GoogleVersionChannel::onStatusChanged() {
auto trialMode = m_trialMode;
auto status = m_playApi ? m_playApi->getStatus() : GooglePlayApi::GooglePlayApiStatus::FAILED;
if(status != GooglePlayApi::GooglePlayApiStatus::SUCCEDED) {
setStatus(GoogleVersionChannelStatus::PENDING);
m_hasVerifiedLicense = trialMode;
licenseStatus = status == GooglePlayApi::GooglePlayApiStatus::FAILED ? trialMode ? GoogleVersionChannelLicenceStatus::OFFLINE : GoogleVersionChannelLicenceStatus::FAILED : GoogleVersionChannelLicenceStatus::NOT_READY;
setStatus(status == GooglePlayApi::GooglePlayApiStatus::FAILED ? GoogleVersionChannelStatus::FAILED : GoogleVersionChannelStatus::NOT_READY);
} else {
onApiReady();
}
}
void GoogleVersionChannel::onAppInfoReceived(const QString &packageName, const QString &version, int versionCode, bool isBeta) {
auto trialMode = m_trialMode;
auto pkgName = trialMode ? "com.mojang.minecrafttrialpe" : "com.mojang.minecraftpe";
if (packageName == pkgName) {
m_latestVersion = version;
m_latestVersionCode = versionCode;
m_latestVersionIsBeta = isBeta;
m_settings.setValue("latest_version", m_latestVersion);
m_settings.setValue("latest_version_code", m_latestVersionCode);
m_settings.setValue("latest_version_isbeta", m_latestVersionIsBeta);
emit latestVersionChanged();
licenseStatus = GoogleVersionChannelLicenceStatus::PENDING;
setStatus(GoogleVersionChannelStatus::SUCCEDED);
m_playApi->validateLicense(pkgName, versionCode, [this, trialMode](bool hasVerifiedLicense) {
if(!trialMode && m_playApi->getLogin()->isChromeOS() && !hasVerifiedLicense) {
m_playApi->getLogin()->setChromeOS(false);
licenseStatus = GoogleVersionChannelLicenceStatus::NOT_READY;
setStatus(GoogleVersionChannelStatus::NOT_READY);
return;
}
if(trialMode) {
this->m_hasVerifiedLicense = true;
licenseStatus = hasVerifiedLicense ? GoogleVersionChannelLicenceStatus::SUCCEDED : GoogleVersionChannelLicenceStatus::OFFLINE;
} else {
this->m_hasVerifiedLicense |= hasVerifiedLicense;
licenseStatus = hasVerifiedLicense ? GoogleVersionChannelLicenceStatus::SUCCEDED : GoogleVersionChannelLicenceStatus::FAILED;
m_settings.setValue("latest_version_id", hasVerifiedLicense ? (m_latestVersion + QChar((char)m_latestVersionCode) + QChar((char)m_latestVersionIsBeta)) : "");
}
statusChanged();
});
}
}
void GoogleVersionChannel::onAppInfoFailed(QString const& packageName, const QString &errorMessage) {
auto trialMode = m_trialMode;
auto pkgName = trialMode ? "com.mojang.minecrafttrialpe" : "com.mojang.minecraftpe";
if(packageName == pkgName) {
if(errorMessage.contains("401") || errorMessage.contains("403")) {
licenseStatus = GoogleVersionChannelLicenceStatus::FAILED;
m_hasVerifiedLicense = false;
if(!trialMode) {
m_settings.setValue("latest_version_id", "");
}
} else if(trialMode || m_settings.value("latest_version_id").toString() == (m_latestVersion + QChar((char)m_latestVersionCode) + QChar((char)m_latestVersionIsBeta))) {
m_hasVerifiedLicense = true;
licenseStatus = GoogleVersionChannelLicenceStatus::OFFLINE;
}
setStatus(GoogleVersionChannelStatus::FAILED);
}
}
#ifndef GOOGLEVERSIONCHANNEL_H
#define GOOGLEVERSIONCHANNEL_H
#include <QObject>
#include <QSettings>
#include "googleplayapi.h"
class GooglePlayApi;
class GoogleVersionChannel : public QObject {
Q_OBJECT
Q_PROPERTY(GooglePlayApi* playApi MEMBER m_playApi WRITE setPlayApi)
Q_PROPERTY(QString latestVersion READ latestVersion NOTIFY latestVersionChanged)
Q_PROPERTY(qint32 latestVersionCode READ latestVersionCode NOTIFY latestVersionChanged)
Q_PROPERTY(bool latestVersionIsBeta READ latestVersionIsBeta NOTIFY latestVersionChanged)
Q_PROPERTY(GoogleVersionChannelStatus status READ getStatus NOTIFY statusChanged)
Q_PROPERTY(bool trialMode MEMBER m_trialMode WRITE setTrialMode NOTIFY statusChanged)
Q_PROPERTY(bool hasVerifiedLicense MEMBER m_hasVerifiedLicense NOTIFY statusChanged)
Q_PROPERTY(GoogleVersionChannelLicenceStatus licenseStatus READ getLicenseStatus NOTIFY statusChanged)
public:
enum class GoogleVersionChannelStatus {
NOT_READY, PENDING, FAILED, SUCCEDED
};
Q_ENUM(GoogleVersionChannelStatus)
enum class GoogleVersionChannelLicenceStatus {
NOT_READY, PENDING, FAILED, SUCCEDED, OFFLINE
};
Q_ENUM(GoogleVersionChannelLicenceStatus)
private:
QSettings m_settings;
GooglePlayApi* m_playApi = nullptr;
QString m_latestVersion;
qint32 m_latestVersionCode;
qint32 m_latestVersionIsBeta;
bool m_hasVerifiedLicense = false;
bool m_trialMode = false;
GoogleVersionChannelStatus status = GoogleVersionChannelStatus::NOT_READY;
GoogleVersionChannelLicenceStatus licenseStatus = GoogleVersionChannelLicenceStatus::NOT_READY;
void onApiReady();
void onStatusChanged();
void onAppInfoReceived(QString const& packageName, QString const& version, int versionCode, bool isBeta);
void onAppInfoFailed(QString const& packageName, const QString &errorMessage);
void setStatus(GoogleVersionChannelStatus status) {
if (this->status != status) {
this->status = status;
statusChanged();
}
}
void setTrialMode(bool trialMode) {
if (m_trialMode != trialMode ) {
m_trialMode = trialMode;
onStatusChanged();
}
}
public:
GoogleVersionChannel();
void setPlayApi(GooglePlayApi* value);
QString const& latestVersion() const { return m_latestVersion; }
qint32 latestVersionCode() const { return m_latestVersionCode; }
bool latestVersionIsBeta() const { return m_latestVersionIsBeta; }
GoogleVersionChannelStatus getStatus() const { return status; }
GoogleVersionChannelLicenceStatus getLicenseStatus() const { return licenseStatus; }
public slots:
signals:
void latestVersionChanged();
void statusChanged();
};
#endif // GOOGLEVERSIONCHANNEL_H
#include "launcherapp.h"
#include <QIcon>
#include <QFileOpenEvent>
#include <QDebug>
#include "profilemanager.h"
#include "versionmanager.h"
#include "gamelauncher.h"
#include "googleversionchannel.h"
LauncherApp::LauncherApp(int &argc, char **argv) : QApplication(argc, argv) {
auto appdir = getenv("APPDIR");
if(appdir != nullptr)
setWindowIcon(QIcon(QString::fromUtf8(appdir) + "/mcpelauncher-ui-qt.png"));
}
bool LauncherApp::event(QEvent *event) {
if (event->type() == QEvent::Close) {
AppCloseEvent qmlEvent;
emit closing(&qmlEvent);
if (!qmlEvent.isAccepted()) {
event->setAccepted(false);
return true;
}
} else if (event->type() == QEvent::FileOpen) {
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
auto url = openEvent->url();
qDebug() << "Open Url " << url;
if(url.isLocalFile()) {
launchProfileFile("", url.toLocalFile(), false);
} else if(url.isValid()) {
launchProfileFile("", url.toString(), false);
} else {
launchProfileFile("", openEvent->file(), false);
}
}
return QApplication::event(event);
}
int LauncherApp::launchProfileFile(QString profileName, QString filePath, bool startEventLoop) {
VersionManager vmanager;
ProfileManager manager;
ProfileInfo * profile = nullptr;
if(profileName.length() > 0) {
for(auto&& pro : manager.profiles()) {
if(((ProfileInfo *)pro)->name == profileName) {
profile = (ProfileInfo *)pro;
}
}
if(profile == nullptr) {
printf("Profile not found: %s\n", profileName.toStdString().data());
return 1;
}
} else {
profile = manager.activeProfile();
}
GameLauncher launcher;
launcher.logAttached();
QObject::connect(&launcher, &GameLauncher::logAppended, [](QString str) {
printf("%s", str.toStdString().data());
});
int exitCode = -1;
bool exited = false;
auto shouldExit = [&](int code) {
exitCode = code;
exited = true;
this->exit(code);
};
QObject::connect(&launcher, &GameLauncher::stateChanged, [&]() {
if(!launcher.running() && startEventLoop) {
this->exit(launcher.crashed() ? 1 : 0);
}
});
QObject::connect(&launcher, &GameLauncher::launchFailed, [&]() {
if(startEventLoop) {
shouldExit(1);
}
});
QSettings m_settings;
auto trialMode = m_settings.value("trialMode", false).toBool();
m_settings.beginGroup("googleversionchannel");
auto m_latestVersion = m_settings.value("latest_version").toString();
auto m_latestVersionCode = m_settings.value("latest_version_code").toInt();
auto m_latestVersionIsBeta = m_settings.value("latest_version_isbeta").toBool();
if(!trialMode && m_settings.value("latest_version_id").toString() != (m_latestVersion + QChar((char)m_latestVersionCode) + QChar((char)m_latestVersionIsBeta))) {
printf("Something went wrong\n");
shouldExit(1);
}
QObject::connect(&launcher, &GameLauncher::fileStarted, [&](bool success) {
if(success) {
if(startEventLoop) {
shouldExit(success ? 0 : 1);
}
} else {
launcher.start(false, profile->arch, !trialMode, filePath);
}
});
launcher.setProfile(profile);
if(profile->versionType == ProfileInfo::LATEST_GOOGLE_PLAY) {
GoogleVersionChannel playChannel;
auto versionInfo = vmanager.versionList()->get(playChannel.latestVersionCode());
if(versionInfo == nullptr) {
printf("Couldn't find Google Play Latest version %d\n", playChannel.latestVersionCode());
versionInfo = vmanager.versionList()->latestDownloadedVersion();
}
if(versionInfo == nullptr) {
printf("Couldn't find any Latest Downloaded version!\n");
}
launcher.setGameDir(vmanager.getDirectoryFor(versionInfo));
} else if(profile->versionType == ProfileInfo::LOCKED_NAME) {
launcher.setGameDir(vmanager.getDirectoryFor(profile->versionDirName));
} else if(profile->versionType == ProfileInfo::LOCKED_CODE && profile->versionCode) {
launcher.setGameDir(vmanager.getDirectoryFor(vmanager.versionList()->get(profile->versionCode)));
}
if(filePath.length() > 0) {
launcher.startFile(filePath);
} else {
launcher.start(false, profile->arch, !trialMode);
}
return exited ? exitCode : startEventLoop ? this->exec() : 0;
}
#ifndef __APPLE__
void LauncherApp::setVisibleInDock(bool) {
// stub
}
#endif
#ifndef LAUNCHERAPP_H
#define LAUNCHERAPP_H
#include <QApplication>
class AppCloseEvent : public QObject {
Q_OBJECT
Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
bool accepted;
public:
AppCloseEvent(bool accepted = true) : accepted(accepted) {}
void setAccepted(bool accepted) { this->accepted = accepted; }
bool isAccepted() const { return accepted; }
};
class LauncherApp : public QApplication {
Q_OBJECT
public:
LauncherApp(int &argc, char **argv);
int launchProfileFile(QString profileName, QString filePath, bool startEventLoop = true);
public slots:
void setVisibleInDock(bool visible);
protected:
bool event(QEvent*) override;
signals:
void closing(AppCloseEvent* close);
};
#endif // LAUNCHERAPP_H
#include "launcherapp.h"
#import <AppKit/AppKit.h>
void LauncherApp::setVisibleInDock(bool visible) {
if (!visible)
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];
else
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
}
\ No newline at end of file
#ifndef LAUNCHERSETTINGS_H
#define LAUNCHERSETTINGS_H
#include <QObject>
#include <QSettings>
#include <QDir>
#include <QStandardPaths>
#include <QUrl>
#include <QClipboard>
#include <QApplication>
class LauncherSettings : public QObject {
Q_OBJECT
Q_PROPERTY(bool startHideLauncher READ startHideLauncher WRITE setStartHideLauncher NOTIFY settingsChanged)
Q_PROPERTY(bool startOpenLog READ startOpenLog WRITE setStartOpenLog NOTIFY settingsChanged)
Q_PROPERTY(bool disableGameLog READ disableGameLog WRITE setDisableGameLog NOTIFY settingsChanged)
Q_PROPERTY(bool checkForUpdates READ checkForUpdates WRITE setCheckForUpdates NOTIFY settingsChanged)
Q_PROPERTY(bool showExitButton READ showExitButton WRITE setShowExitButton NOTIFY settingsChanged)
Q_PROPERTY(bool showUnverified READ showUnverified WRITE setShowUnverified NOTIFY settingsChanged)
Q_PROPERTY(bool showUnsupported READ showUnsupported WRITE setShowUnsupported NOTIFY settingsChanged)
Q_PROPERTY(bool showBetaVersions READ showBetaVersions WRITE setShowBetaVersions NOTIFY settingsChanged)
Q_PROPERTY(bool downloadOnly READ downloadOnly WRITE setDownloadOnly NOTIFY settingsChanged)
Q_PROPERTY(QString singleArch READ singleArch WRITE setSingleArch NOTIFY settingsChanged)
Q_PROPERTY(long long lastVersion READ lastVersion WRITE setLastVersion NOTIFY settingsChanged)
Q_PROPERTY(QUrl gameDataDir READ gameDataDir)
Q_PROPERTY(QString versionsFeedBaseUrl READ versionsFeedBaseUrl WRITE setVersionsFeedBaseUrl NOTIFY settingsChanged)
Q_PROPERTY(bool showNotifications READ showNotifications WRITE setShowNotifications NOTIFY settingsChanged)
Q_PROPERTY(QString clipboard READ clipboard WRITE setClipboard NOTIFY clipboardChanged)
Q_PROPERTY(bool chromeOSMode READ chromeOSMode WRITE setChromeOSMode NOTIFY settingsChanged)
Q_PROPERTY(bool keepApks READ keepApks WRITE setKeepApks NOTIFY settingsChanged)
Q_PROPERTY(bool trialMode READ trialMode WRITE setTrialMode NOTIFY settingsChanged)
private:
QSettings settings;
public:
static bool disableDevMode;
explicit LauncherSettings(QObject *parent = nullptr) : QObject(parent), settings() {}
bool startHideLauncher() const { return settings.value("startHideLauncher", true).toBool(); }
void setStartHideLauncher(bool value) { settings.setValue("startHideLauncher", value); emit settingsChanged(); }
bool startOpenLog() const { return settings.value("startOpenLog", false).toBool(); }
void setStartOpenLog(bool value) { settings.setValue("startOpenLog", value); emit settingsChanged(); }
bool disableGameLog() const { return settings.value("disableGameLog", false).toBool(); }
void setDisableGameLog(bool value) { settings.setValue("disableGameLog", value); emit settingsChanged(); }
bool checkForUpdates() const { return settings.value("checkForUpdates", true).toBool(); }
void setCheckForUpdates(bool value) { settings.setValue("checkForUpdates", value); emit settingsChanged(); }
bool showExitButton() const { return settings.value("showExitButton", false).toBool(); }
void setShowExitButton(bool value) { settings.setValue("showExitButton", value); emit settingsChanged(); }
bool showUnverified() const { return !disableDevMode && settings.value("showUnverified", false).toBool(); }
void setShowUnverified(bool value) { settings.setValue("showUnverified", value); emit settingsChanged(); }
bool showUnsupported() const { return !disableDevMode && !singleArch().isEmpty(); }
void setShowUnsupported(bool value) { settings.setValue("showUnsupported", value); emit settingsChanged(); }
bool downloadOnly() const { return false; }
void setDownloadOnly(bool value) { emit settingsChanged(); }
QString singleArch() const { return !disableDevMode ? settings.value("singleArch", "").toString() : ""; }
void setSingleArch(QString value) { settings.setValue("singleArch", value); emit settingsChanged(); }
bool showBetaVersions() const { return !disableDevMode; }
void setShowBetaVersions(bool value) { emit settingsChanged(); }
long long lastVersion() const { return settings.value("lastVersion", 0).toLongLong(); }
void setLastVersion(long long value) { settings.setValue("lastVersion", QVariant(value)); emit settingsChanged(); }
bool showNotifications() const { return settings.value("showNotifications", true).toBool(); }
void setShowNotifications(bool value) { settings.setValue("showNotifications", value); emit settingsChanged(); }
QUrl gameDataDir() {
return QUrl::fromLocalFile(QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).filePath("mcpelauncher"));
}
QString versionsFeedBaseUrl() const {
if(disableDevMode) {
return "";
}
auto val = settings.value("versionsFeedBaseUrl", "").toString();
if(val.isEmpty()) {
return showUnverified() ? "https://raw.githubusercontent.com/minecraft-linux/mcpelauncher-versiondb/master" : "";
}
return val;
}
void setVersionsFeedBaseUrl(QString value) { settings.setValue("versionsFeedBaseUrl", value); emit settingsChanged(); }
QString clipboard() const {
return QApplication::clipboard()->text();
}
void setClipboard(QString text) const {
return QApplication::clipboard()->setText(text);
}
bool chromeOSMode() const { return settings.value("chromeOSMode", false).toBool(); }
void setChromeOSMode(bool value) { settings.setValue("chromeOSMode", value); emit settingsChanged(); }
bool keepApks() const { return settings.value("keepApks", false).toBool(); }
void setKeepApks(bool value) { settings.setValue("keepApks", value); emit settingsChanged(); }
bool trialMode() const { return settings.value("trialMode", false).toBool(); }
void setTrialMode(bool value) { settings.setValue("trialMode", value); emit settingsChanged(); }
public slots:
void resetSettings() {
settings.clear();
}
signals:
void settingsChanged();
void clipboardChanged();
};
#endif // LAUNCHERSETTINGS_H
This diff is collapsed.
[Desktop Entry]
Name=Minecraft Bedrock Launcher
Comment=A Minecraft: Bedrock Edition (earlier: Pocket Edition launcher)
Exec=mcpelauncher-ui-qt %U
Icon=mcpelauncher-ui-qt
MimeType=application/zip
Type=Application
Categories=Game;
Terminal=false
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment