mirror of
https://github.com/TwilitRealm/dusklight.git
synced 2026-09-12 21:29:43 -07:00
Migrate to Borealis (#2266)
This commit is contained in:
@@ -76,7 +76,7 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusklight-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||
name: dusklight-${{env.APP_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||
path: |
|
||||
build/install/Dusklight-*.AppImage
|
||||
build/install/debug.tar.*
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusklight-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
||||
name: dusklight-${{env.APP_VERSION}}-${{matrix.artifact_name}}
|
||||
path: |
|
||||
build/install/Dusklight.app
|
||||
build/install/debug.tar.*
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusklight-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
|
||||
name: dusklight-${{env.APP_VERSION}}-android-${{matrix.artifact_arch}}
|
||||
path: upload/
|
||||
|
||||
build-windows:
|
||||
@@ -283,7 +283,7 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusklight-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||
name: dusklight-${{env.APP_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||
path: |
|
||||
build/install/*.exe
|
||||
build/install/*.dll
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[submodule "extern/aurora"]
|
||||
path = extern/aurora
|
||||
url = https://github.com/encounter/aurora.git
|
||||
[submodule "extern/borealis"]
|
||||
path = extern/borealis
|
||||
url = https://github.com/encounter/borealis.git
|
||||
|
||||
+12
-127
@@ -5,10 +5,11 @@ if (NOT CMAKE_BUILD_TYPE)
|
||||
"Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE)
|
||||
endif ()
|
||||
|
||||
include(cmake/DetectVersion.cmake)
|
||||
detect_version()
|
||||
include(extern/borealis/cmake/DetectVersion.cmake)
|
||||
borealis_detect_version()
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
project(dusklight LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
|
||||
project(dusklight LANGUAGES C CXX VERSION ${BOREALIS_APP_VERSION})
|
||||
|
||||
if (APPLE)
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif ()
|
||||
@@ -74,6 +75,8 @@ set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
|
||||
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1)
|
||||
|
||||
add_subdirectory(extern/borealis EXCLUDE_FROM_ALL)
|
||||
|
||||
add_subdirectory(libs/freeverb)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
@@ -85,8 +88,6 @@ endif ()
|
||||
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
|
||||
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
|
||||
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
|
||||
option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
|
||||
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
|
||||
option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF)
|
||||
option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT})
|
||||
option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF)
|
||||
@@ -235,18 +236,6 @@ endif ()
|
||||
include(FetchContent)
|
||||
|
||||
# Declare all dependencies first so CMake can download them in parallel
|
||||
message(STATUS "dusklight: Fetching cxxopts")
|
||||
FetchContent_Declare(cxxopts
|
||||
URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz
|
||||
URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
||||
)
|
||||
message(STATUS "dusklight: Fetching nlohmann/json")
|
||||
FetchContent_Declare(json
|
||||
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
|
||||
URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
||||
)
|
||||
message(STATUS "dusklight: Fetching miniz")
|
||||
FetchContent_Declare(miniz
|
||||
URL https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip
|
||||
@@ -254,7 +243,7 @@ FetchContent_Declare(miniz
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
set(_fetch_content_deps cxxopts json miniz)
|
||||
set(_fetch_content_deps miniz)
|
||||
if (DUSK_HAS_FUNCHOOK)
|
||||
message(STATUS "dusklight: Fetching funchook")
|
||||
# cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a
|
||||
@@ -289,40 +278,12 @@ if (DUSK_HAS_FUNCHOOK)
|
||||
endif ()
|
||||
FetchContent_MakeAvailable(${_fetch_content_deps})
|
||||
|
||||
if (DUSK_ENABLE_SENTRY_NATIVE)
|
||||
message(STATUS "dusklight: Fetching sentry-native")
|
||||
set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE)
|
||||
if (WIN32)
|
||||
set(SENTRY_TRANSPORT winhttp CACHE STRING "" FORCE)
|
||||
endif ()
|
||||
set(SENTRY_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(SENTRY_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(SENTRY_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(sentry_native
|
||||
GIT_REPOSITORY https://github.com/getsentry/sentry-native.git
|
||||
GIT_TAG 0.13.6
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
GIT_SUBMODULES_RECURSE TRUE
|
||||
)
|
||||
if (NOT sentry_native_POPULATED)
|
||||
FetchContent_Populate(sentry_native)
|
||||
set(_skip_install_rules ${CMAKE_SKIP_INSTALL_RULES})
|
||||
set(CMAKE_SKIP_INSTALL_RULES ON)
|
||||
add_subdirectory(${sentry_native_SOURCE_DIR} ${sentry_native_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
set(CMAKE_SKIP_INSTALL_RULES ${_skip_install_rules})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Use signed char on ARM to match the original game (and x86)
|
||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _arch)
|
||||
if(_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
|
||||
add_compile_options(-fsigned-char)
|
||||
endif()
|
||||
|
||||
configure_version_header()
|
||||
|
||||
include(files.cmake)
|
||||
|
||||
# TODO: version handling for res includes
|
||||
@@ -335,67 +296,23 @@ set(DUSK_PRODUCT_NAME "Dusklight")
|
||||
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
|
||||
|
||||
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
|
||||
source_group("dusklight" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
|
||||
source_group("dusklight" FILES ${DUSK_FILES})
|
||||
|
||||
include(cmake/GameABIConfig.cmake)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
set(GAME_COMPILE_DEFS DUSK_BUILDING_GAME=1)
|
||||
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
|
||||
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt
|
||||
aurora::card borealis::cli borealis::crash borealis::data borealis::disc borealis::discord borealis::file_select borealis::log borealis::presentation borealis::sentry borealis::update freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt
|
||||
Threads::Threads zstd::libzstd dusklight_game_headers)
|
||||
if (DUSK_HAS_FUNCHOOK)
|
||||
list(APPEND GAME_LIBS funchook-static)
|
||||
endif ()
|
||||
|
||||
if (DUSK_ENABLE_SENTRY_NATIVE)
|
||||
list(APPEND GAME_LIBS sentry)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_SENTRY_NATIVE=1 SENTRY_BUILD_STATIC=1)
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND GAME_LIBS Ws2_32)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
list(APPEND GAME_LIBS dbghelp)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_CRASH_DBGHELP=1)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/no_backend.cpp)
|
||||
if (DUSK_ENABLE_UPDATE_CHECKER)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_UPDATE_CHECKER=1)
|
||||
if (WIN32)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp)
|
||||
list(APPEND GAME_LIBS winhttp)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1)
|
||||
message(STATUS "dusklight: Enabled update checker (WinHTTP)")
|
||||
elseif (ANDROID)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1)
|
||||
message(STATUS "dusklight: Enabled update checker (Android)")
|
||||
elseif (APPLE)
|
||||
find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm)
|
||||
set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK})
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1)
|
||||
message(STATUS "dusklight: Enabled update checker (NSURLSession)")
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||
find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL)
|
||||
if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND)
|
||||
set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp)
|
||||
list(APPEND GAME_LIBS CURL::libcurl)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1)
|
||||
message(STATUS "dusklight: Enabled update checker (libcurl)")
|
||||
else ()
|
||||
message(STATUS "dusklight: Disabled update checker (libcurl + HTTPS/SSL not found)")
|
||||
endif ()
|
||||
else ()
|
||||
message(STATUS "dusklight: Disabled update checker (unsupported platform)")
|
||||
endif ()
|
||||
endif ()
|
||||
list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
|
||||
|
||||
if (DUSK_MOVIE_SUPPORT)
|
||||
if (TARGET libjpeg-turbo::turbojpeg-static)
|
||||
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
|
||||
@@ -405,15 +322,6 @@ if (DUSK_MOVIE_SUPPORT)
|
||||
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
|
||||
endif ()
|
||||
|
||||
set(DUSK_ENABLE_DISCORD_DEFAULT ON)
|
||||
if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD)
|
||||
set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC})
|
||||
endif ()
|
||||
option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT})
|
||||
if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
||||
endif ()
|
||||
|
||||
if (DUSK_ENABLE_CODE_MODS)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_CODE_MODS=1)
|
||||
endif ()
|
||||
@@ -478,10 +386,10 @@ endif ()
|
||||
set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES} ${miniz_SOURCE_DIR}/miniz.c)
|
||||
if(ANDROID)
|
||||
add_library(dusklight SHARED ${DUSK_FILES})
|
||||
set_target_properties(dusklight PROPERTIES OUTPUT_NAME main)
|
||||
else ()
|
||||
add_executable(dusklight ${DUSK_FILES})
|
||||
endif ()
|
||||
borealis_configure_android_application(dusklight)
|
||||
if (ENABLE_ASAN)
|
||||
target_sources(dusklight PRIVATE src/dusk/asan_options.c)
|
||||
endif ()
|
||||
@@ -559,17 +467,9 @@ if (TARGET crashpad_handler)
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (ANDROID)
|
||||
# SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static
|
||||
# archive, force an undefined reference so the linker keeps the SDL_main object.
|
||||
target_link_options(dusklight PRIVATE "-Wl,-u,SDL_main")
|
||||
endif ()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
|
||||
target_link_libraries(dusklight PRIVATE dl)
|
||||
elseif (ANDROID)
|
||||
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
|
||||
endif ()
|
||||
|
||||
if (NOT APPLE)
|
||||
@@ -651,8 +551,8 @@ if (APPLE)
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${BOREALIS_APP_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${BOREALIS_APP_SHORT_VERSION}
|
||||
MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}
|
||||
OUTPUT_NAME Dusklight
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES"
|
||||
@@ -685,21 +585,6 @@ if (APPLE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
find_library(APPKIT_FRAMEWORK AppKit REQUIRED)
|
||||
target_sources(dusklight PRIVATE src/dusk/file_select_macos.mm)
|
||||
set_source_files_properties(src/dusk/file_select_macos.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
target_link_libraries(dusklight PRIVATE ${APPKIT_FRAMEWORK})
|
||||
endif ()
|
||||
|
||||
if (IOS)
|
||||
find_library(UIKIT_FRAMEWORK UIKit REQUIRED)
|
||||
find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED)
|
||||
target_sources(dusklight PRIVATE src/dusk/ios/FileSelectDialog.m)
|
||||
set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
target_link_libraries(dusklight PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
|
||||
endif ()
|
||||
|
||||
include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)
|
||||
aurora_copy_runtime_dlls(dusklight)
|
||||
|
||||
|
||||
+4
-4
@@ -28,12 +28,12 @@
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
|
||||
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
|
||||
"DUSK_ENABLE_SENTRY_NATIVE": {
|
||||
"BOREALIS_ENABLE_SENTRY": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"DUSK_SENTRY_DSN": "$env{SENTRY_DSN}",
|
||||
"DUSK_SENTRY_ENVIRONMENT": "production",
|
||||
"BOREALIS_SENTRY_DSN": "$env{SENTRY_DSN}",
|
||||
"BOREALIS_SENTRY_ENVIRONMENT": "production",
|
||||
"Rust_RUSTUP_INSTALL_MISSING_TARGET": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
@@ -448,7 +448,7 @@
|
||||
"ci"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"DUSK_ENABLE_SENTRY_NATIVE": {
|
||||
"BOREALIS_ENABLE_SENTRY": {
|
||||
"type": "BOOL",
|
||||
"value": false
|
||||
}
|
||||
|
||||
@@ -23,5 +23,5 @@ cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,102
|
||||
cp platforms/freedesktop/dev.twilitrealm.dusk.desktop build/appdir/usr/share/applications
|
||||
|
||||
cd build/install
|
||||
VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
|
||||
VERSION="$APP_VERSION" NO_STRIP=1 "$linuxdeploy" \
|
||||
-l "$lib_dir/libusb-1.0.so" --appdir "$build_dir/appdir" --output appimage
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# Version detection shared by the main build and the mod SDK (sdk/CMakeLists.txt)
|
||||
include_guard(GLOBAL)
|
||||
|
||||
get_filename_component(_DUSK_VERSION_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
|
||||
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
||||
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
|
||||
|
||||
set(DUSK_VERSION_OVERRIDE "" CACHE STRING "Override version string (skips git detection and format validation)")
|
||||
|
||||
macro(detect_version)
|
||||
if (DUSK_VERSION_OVERRIDE)
|
||||
set(DUSK_WC_DESCRIBE "${DUSK_VERSION_OVERRIDE}")
|
||||
set(DUSK_VERSION_STRING "0.0.0.0")
|
||||
set(DUSK_SHORT_VERSION_STRING "0.0.0")
|
||||
set(DUSK_VERSION_CODE "1")
|
||||
set(DUSK_WC_REVISION "")
|
||||
set(DUSK_WC_BRANCH "")
|
||||
set(DUSK_WC_DATE "")
|
||||
message(STATUS "Dusklight version overridden to ${DUSK_WC_DESCRIBE}")
|
||||
else ()
|
||||
# obtain revision info from git
|
||||
find_package(Git)
|
||||
if (GIT_FOUND)
|
||||
# make sure version information gets re-run when the current Git HEAD changes
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD
|
||||
OUTPUT_VARIABLE dusk_git_head_filename
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
get_filename_component(dusk_git_head_filename "${dusk_git_head_filename}" ABSOLUTE BASE_DIR "${_DUSK_VERSION_ROOT}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}")
|
||||
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD
|
||||
OUTPUT_VARIABLE dusk_git_head_symbolic
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT}
|
||||
COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic}
|
||||
OUTPUT_VARIABLE dusk_git_head_symbolic_filename
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
get_filename_component(dusk_git_head_symbolic_filename "${dusk_git_head_symbolic_filename}" ABSOLUTE BASE_DIR "${_DUSK_VERSION_ROOT}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}")
|
||||
|
||||
# defines DUSK_WC_REVISION
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
|
||||
OUTPUT_VARIABLE DUSK_WC_REVISION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
# defines DUSK_WC_DESCRIBE
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*"
|
||||
OUTPUT_VARIABLE DUSK_WC_DESCRIBE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# remove the git hash, then collapse a clean "-0" suffix only
|
||||
string(REGEX REPLACE "-[^-]+(-dirty|)$" "\\1" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
|
||||
string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}")
|
||||
|
||||
# defines DUSK_WC_BRANCH
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
|
||||
OUTPUT_VARIABLE DUSK_WC_BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
# defines DUSK_WC_DATE
|
||||
execute_process(WORKING_DIRECTORY ${_DUSK_VERSION_ROOT} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad
|
||||
OUTPUT_VARIABLE DUSK_WC_DATE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
else ()
|
||||
message(STATUS "Unable to find git, commit information will not be available")
|
||||
endif ()
|
||||
|
||||
if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)([-+].*)?$")
|
||||
set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
|
||||
set(_ver_major ${CMAKE_MATCH_1})
|
||||
set(_ver_minor ${CMAKE_MATCH_2})
|
||||
set(_ver_patch ${CMAKE_MATCH_3})
|
||||
set(DUSK_VERSION_TWEAK "0")
|
||||
if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$")
|
||||
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
|
||||
elseif (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-[0-9A-Za-z.-]+-([0-9]+)(-dirty)?$")
|
||||
set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
|
||||
endif ()
|
||||
set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}")
|
||||
if (DUSK_VERSION_TWEAK GREATER 999)
|
||||
set(_tweak 999)
|
||||
else ()
|
||||
set(_tweak ${DUSK_VERSION_TWEAK})
|
||||
endif ()
|
||||
# encoding: major*1e7 + minor*1e5 + patch*1e3 + tweak; collision-free for major<210, minor<100, patch<100, tweak<=999
|
||||
math(EXPR DUSK_VERSION_CODE
|
||||
"${_ver_major} * 10000000 + ${_ver_minor} * 100000 + ${_ver_patch} * 1000 + ${_tweak}")
|
||||
else ()
|
||||
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
|
||||
set(DUSK_VERSION_STRING "0.0.0.0")
|
||||
set(DUSK_SHORT_VERSION_STRING "0.0.0")
|
||||
set(DUSK_VERSION_CODE "1")
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
|
||||
# Add version information to CI environment variables
|
||||
if (DEFINED ENV{GITHUB_ENV})
|
||||
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n")
|
||||
file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION_CODE=${DUSK_VERSION_CODE}\n")
|
||||
endif ()
|
||||
message(STATUS "Dusklight version set to ${DUSK_WC_DESCRIBE}")
|
||||
endmacro()
|
||||
|
||||
# Sets PLATFORM_NAME and configures version.h into the caller's binary dir.
|
||||
macro(configure_version_header)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
|
||||
set(PLATFORM_NAME win32)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin)
|
||||
if (IOS)
|
||||
set(PLATFORM_NAME ios)
|
||||
elseif (TVOS)
|
||||
set(PLATFORM_NAME tvos)
|
||||
else ()
|
||||
set(PLATFORM_NAME macos)
|
||||
endif ()
|
||||
else ()
|
||||
string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME)
|
||||
endif ()
|
||||
|
||||
configure_file(${_DUSK_VERSION_ROOT}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
endmacro()
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: 6c4c27f9e8...5027ed63a7
+1
Submodule extern/borealis added at eec74b6dec
+2
-18
@@ -1421,30 +1421,21 @@ set(DUSK_FILES
|
||||
src/dusk/achievements.cpp
|
||||
src/dusk/action_bindings.cpp
|
||||
src/dusk/action_bindings.h
|
||||
src/dusk/android_frame_rate.cpp
|
||||
src/dusk/android_frame_rate.hpp
|
||||
src/dusk/asserts.cpp
|
||||
src/dusk/autosave.cpp
|
||||
src/dusk/config.cpp
|
||||
src/dusk/config.hpp
|
||||
src/dusk/crash_handler.cpp
|
||||
src/dusk/crash_reporting.cpp
|
||||
src/dusk/data.cpp
|
||||
src/dusk/data.hpp
|
||||
src/dusk/discord.cpp
|
||||
src/dusk/discord.hpp
|
||||
src/dusk/discord_presence.cpp
|
||||
src/dusk/dvd_asset.cpp
|
||||
src/dusk/dvd_asset.hpp
|
||||
src/dusk/extras.c
|
||||
src/dusk/file_select.cpp
|
||||
src/dusk/file_select.hpp
|
||||
src/dusk/frame_interpolation.cpp
|
||||
src/dusk/game_clock.cpp
|
||||
src/dusk/gamepad_color.cpp
|
||||
src/dusk/globals.cpp
|
||||
src/dusk/gyro.cpp
|
||||
src/dusk/http/http.hpp
|
||||
src/dusk/imgui/ImGuiActorSpawner.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.hpp
|
||||
@@ -1507,6 +1498,8 @@ set(DUSK_FILES
|
||||
src/dusk/mods/svc/stage.cpp
|
||||
src/dusk/mods/svc/stage.hpp
|
||||
src/dusk/mouse.cpp
|
||||
src/dusk/presentation.cpp
|
||||
src/dusk/presentation.hpp
|
||||
src/dusk/scope_guard.hpp
|
||||
src/dusk/settings.cpp
|
||||
src/dusk/speedrun.cpp
|
||||
@@ -1582,8 +1575,6 @@ set(DUSK_FILES
|
||||
src/dusk/ui/warp.hpp
|
||||
src/dusk/ui/window.cpp
|
||||
src/dusk/ui/window.hpp
|
||||
src/dusk/update_check.cpp
|
||||
src/dusk/update_check.hpp
|
||||
src/dusk/version.cpp
|
||||
src/dusk/utilities.cpp
|
||||
src/helpers/batch.cpp
|
||||
@@ -1591,10 +1582,3 @@ set(DUSK_FILES
|
||||
src/helpers/offset_ptr.cpp
|
||||
src/helpers/string.cpp
|
||||
)
|
||||
|
||||
set(DUSK_HTTP_BACKEND_FILES
|
||||
src/dusk/http/no_backend.cpp
|
||||
src/dusk/http/curl.cpp
|
||||
src/dusk/http/winhttp.cpp
|
||||
src/dusk/http/url_session.mm
|
||||
)
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
ninjaFlags = [ "dusklight" ];
|
||||
|
||||
cmakeFlags = [
|
||||
"-DDUSK_VERSION_OVERRIDE=${versionSuffix}"
|
||||
"-DBOREALIS_APP_VERSION_OVERRIDE=${versionSuffix}"
|
||||
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
|
||||
"-DAURORA_DAWN_PROVIDER=package"
|
||||
"-DAURORA_DAWN_LINKAGE=static"
|
||||
|
||||
+12
-14
@@ -1,6 +1,6 @@
|
||||
# Android Shell
|
||||
|
||||
This directory contains a minimal SDLActivity-based Android app wrapper for Dusklight.
|
||||
This directory contains Dusklight's Android shell built on top of Borealis.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -25,24 +25,19 @@ cmake --build --preset android-arm64
|
||||
|
||||
This build produces `build/android-arm64/libmain.so`
|
||||
|
||||
## Refresh SDL Java Shim (Optional)
|
||||
|
||||
If you update SDL and want to refresh the embedded Java shim files:
|
||||
|
||||
```bash
|
||||
./android/scripts/sync-sdl-java.sh
|
||||
```
|
||||
|
||||
## Build APK
|
||||
|
||||
```bash
|
||||
cd android
|
||||
cd platforms/android
|
||||
./gradlew :app:assembleDebug
|
||||
```
|
||||
|
||||
Output APK:
|
||||
|
||||
- `android/app/build/outputs/apk/debug/app-debug.apk`
|
||||
- `app/build/outputs/apk/debug/app-arm64-v8a-debug.apk`
|
||||
|
||||
Aurora needs a hardware-backed graphics adapter. If an AVD has GPU
|
||||
acceleration disabled, launch it with `-gpu host`.
|
||||
|
||||
## Launch With Runtime Args (adb)
|
||||
|
||||
@@ -50,10 +45,13 @@ You can pass command-line args through the activity intent:
|
||||
|
||||
```bash
|
||||
adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
|
||||
--es dusk_args "--backend vulkan"
|
||||
--es borealis_args "--backend vulkan"
|
||||
```
|
||||
|
||||
Supported extras:
|
||||
|
||||
- `dusk_args`: single shell-like argument string
|
||||
- `dusk_argv`: string-array argv
|
||||
- `borealis_args`: single shell-like argument string
|
||||
- `borealis_argv`: string-array argv
|
||||
|
||||
The legacy `dusk_args` and `dusk_argv` names remain accepted during the shell
|
||||
transition.
|
||||
|
||||
@@ -2,163 +2,32 @@ plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def versionNameStr = (System.getenv("DUSK_VERSION") ?: "v0.1.0").replaceFirst("^v", "")
|
||||
def versionCodeInt = (System.getenv("DUSK_VERSION_CODE") ?: "100000").toInteger()
|
||||
|
||||
def duskRepoDir = rootProject.projectDir.parentFile.parentFile
|
||||
def androidNativeBuildDir = new File(duskRepoDir, 'build/android-arm64')
|
||||
def nativeLibrary = new File(androidNativeBuildDir, 'libmain.so')
|
||||
def stageStripValue = providers.gradleProperty('ANDROID_STAGE_STRIP')
|
||||
.orElse(providers.gradleProperty('androidStageStrip'))
|
||||
.orElse(providers.environmentVariable('ANDROID_STAGE_STRIP'))
|
||||
.orElse('1')
|
||||
def androidHome = {
|
||||
def sdkPath = System.getenv('ANDROID_HOME')
|
||||
if (!sdkPath) {
|
||||
throw new GradleException('ANDROID_HOME is not available')
|
||||
}
|
||||
def sdkDir = new File(sdkPath)
|
||||
if (!sdkDir.isDirectory()) {
|
||||
throw new GradleException("ANDROID_HOME points to a missing directory: ${sdkDir}")
|
||||
}
|
||||
sdkDir
|
||||
}.memoize()
|
||||
def borealisDir = new File(duskRepoDir, 'extern/borealis')
|
||||
|
||||
def androidNdkVersion = {
|
||||
def ndkVersion = System.getenv('ANDROID_NDK_VERSION')
|
||||
if (!ndkVersion) {
|
||||
throw new GradleException('ANDROID_NDK_VERSION is not available')
|
||||
}
|
||||
ndkVersion
|
||||
}.memoize()
|
||||
ext.borealisAndroid = [
|
||||
borealisDir: borealisDir,
|
||||
propertiesFile: new File(duskRepoDir, 'build/android-arm64/borealis-android.properties'),
|
||||
namespace: 'dev.twilitrealm.dusk',
|
||||
applicationId: 'dev.twilitrealm.dusk',
|
||||
abis: ['arm64-v8a'],
|
||||
assets: [
|
||||
[
|
||||
from: new File(duskRepoDir, 'res'),
|
||||
into: 'res',
|
||||
excludes: ['**/.DS_Store']
|
||||
],
|
||||
[
|
||||
from: new File(duskRepoDir, 'build/android-arm64/bundled_mods'),
|
||||
into: 'mods',
|
||||
includes: ['*.dusk']
|
||||
]
|
||||
],
|
||||
proguardRules: file('proguard-rules.pro')
|
||||
]
|
||||
|
||||
def androidNdkDir = {
|
||||
def ndkDir = new File(androidHome(), "ndk/${androidNdkVersion()}")
|
||||
if (!new File(ndkDir, 'build/cmake/android.toolchain.cmake').isFile()) {
|
||||
throw new GradleException(
|
||||
"Android NDK ${androidNdkVersion()} is missing or invalid at ${ndkDir}"
|
||||
)
|
||||
}
|
||||
ndkDir
|
||||
}.memoize()
|
||||
|
||||
def llvmPrebuiltDir = {
|
||||
def prebuiltRoot = new File(androidNdkDir(), 'toolchains/llvm/prebuilt')
|
||||
def prebuiltDir = (prebuiltRoot.listFiles()?.findAll { it.isDirectory() } ?: [])
|
||||
.sort { it.name }
|
||||
.find { candidate ->
|
||||
new File(
|
||||
candidate,
|
||||
'sysroot/usr/lib/aarch64-linux-android/libc++_shared.so'
|
||||
).isFile()
|
||||
}
|
||||
if (!prebuiltDir) {
|
||||
throw new GradleException("Cannot find NDK libc++_shared.so under ${prebuiltRoot}")
|
||||
}
|
||||
prebuiltDir
|
||||
}.memoize()
|
||||
|
||||
def stlLibrary = {
|
||||
def library = new File(
|
||||
llvmPrebuiltDir(),
|
||||
'sysroot/usr/lib/aarch64-linux-android/libc++_shared.so'
|
||||
)
|
||||
if (!library.isFile()) {
|
||||
throw new GradleException("libc++_shared.so is missing")
|
||||
}
|
||||
library
|
||||
}.memoize()
|
||||
|
||||
def stagedJniLibsDir = layout.buildDirectory.dir('generated/jniLibs/dusklight')
|
||||
|
||||
def stageJniLibs = tasks.register('stageJniLibs', Sync) {
|
||||
group = 'build'
|
||||
from(nativeLibrary) {
|
||||
rename { 'libmain.so' }
|
||||
into 'arm64-v8a'
|
||||
}
|
||||
from(providers.provider { stlLibrary() }) {
|
||||
rename { 'libc++_shared.so' }
|
||||
into 'arm64-v8a'
|
||||
}
|
||||
into(stagedJniLibsDir)
|
||||
|
||||
doFirst {
|
||||
if (!nativeLibrary.isFile()) {
|
||||
throw new GradleException("Native library is missing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusklight')
|
||||
def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
|
||||
from(new File(duskRepoDir, 'res')) {
|
||||
into 'res'
|
||||
exclude '**/.DS_Store'
|
||||
}
|
||||
from(new File(androidNativeBuildDir, 'bundled_mods')) {
|
||||
into 'mods'
|
||||
include '*.dusk'
|
||||
}
|
||||
into(duskGeneratedAssetsDir)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'dev.twilitrealm.dusk'
|
||||
compileSdk 36
|
||||
ndkVersion androidNdkVersion()
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'dev.twilitrealm.dusk'
|
||||
minSdk 26
|
||||
targetSdk 36
|
||||
versionCode versionCodeInt
|
||||
versionName versionNameStr
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = [stagedJniLibsDir]
|
||||
assets.srcDirs = [duskGeneratedAssetsDir]
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
if (stageStripValue.get() == '0') {
|
||||
keepDebugSymbols += '**/libmain.so'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'arm64-v8a'
|
||||
universalApk false
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
apply from: new File(borealisDir, 'platforms/android/gradle/borealis-application.gradle')
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
tasks.named('preBuild').configure {
|
||||
dependsOn(stageJniLibs, syncDuskAssets)
|
||||
}
|
||||
|
||||
+1
-4
@@ -1,4 +1 @@
|
||||
# Keep SDL activity and related JNI bridge methods.
|
||||
-keep class org.libsdl.app.** { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskHttpClient { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskActivity { *; }
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
android:resource="@xml/game_mode_config" />
|
||||
|
||||
<provider
|
||||
android:name="dev.twilitrealm.dusk.DuskDocumentsProvider"
|
||||
android:authorities="dev.twilitrealm.dusk.documents"
|
||||
android:name="dev.encounter.borealis.BorealisDocumentsProvider"
|
||||
android:authorities="${applicationId}.documents"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,467 +0,0 @@
|
||||
package dev.twilitrealm.dusk;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class DuskDocumentsProvider extends DocumentsProvider {
|
||||
public static final String AUTHORITY = "dev.twilitrealm.dusk.documents";
|
||||
|
||||
private static final String ROOT_ID = "dusk";
|
||||
private static final String ROOT_DOCUMENT_ID = "root";
|
||||
private static final String LOCATION_DESCRIPTOR_NAME = "data_location.json";
|
||||
private static final String DIRECTORY_MIME_TYPE = Document.MIME_TYPE_DIR;
|
||||
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_AVAILABLE_BYTES,
|
||||
Root.COLUMN_SUMMARY
|
||||
};
|
||||
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
if (!isCustomDataPathEnabled()) {
|
||||
ensureUserDirectories();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
|
||||
if (isCustomDataPathEnabled()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final File root = getRootDirectory();
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
|
||||
row.add(Root.COLUMN_ROOT_ID, ROOT_ID);
|
||||
row.add(Root.COLUMN_FLAGS,
|
||||
Root.FLAG_LOCAL_ONLY |
|
||||
Root.FLAG_SUPPORTS_CREATE |
|
||||
Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID);
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.icon);
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, root.getFreeSpace());
|
||||
row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.documents_provider_summary));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||
includeDocument(result, documentId, getFileForDocumentId(documentId));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
return queryChildDocumentsInternal(parentDocumentId, projection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, Bundle queryArgs)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
return queryChildDocumentsInternal(parentDocumentId, projection);
|
||||
}
|
||||
|
||||
private Cursor queryChildDocumentsInternal(String parentDocumentId, String[] projection)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||
final File parent = getFileForDocumentId(parentDocumentId);
|
||||
final File[] files = parent.listFiles();
|
||||
result.setNotificationUri(getContext().getContentResolver(), getChildDocumentsUri(parentDocumentId));
|
||||
|
||||
if (files == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
includeDocument(result, getDocumentIdForFile(file), file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
try {
|
||||
final File parent = getFileForDocumentId(parentDocumentId);
|
||||
final File child = getFileForDocumentId(documentId);
|
||||
return isInside(parent, child);
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
final File parent = getFileForDocumentId(parentDocumentId);
|
||||
if (!parent.isDirectory()) {
|
||||
throw new FileNotFoundException("Parent is not a directory: " + parentDocumentId);
|
||||
}
|
||||
|
||||
final String safeDisplayName = sanitizeDisplayName(displayName);
|
||||
final File file = buildUniqueFile(parent, safeDisplayName);
|
||||
final boolean created;
|
||||
if (DIRECTORY_MIME_TYPE.equals(mimeType)) {
|
||||
created = file.mkdir();
|
||||
} else {
|
||||
try {
|
||||
created = file.createNewFile();
|
||||
} catch (IOException e) {
|
||||
throw asFileNotFound("Unable to create document", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!created) {
|
||||
throw new FileNotFoundException("Unable to create document: " + displayName);
|
||||
}
|
||||
|
||||
notifyChildrenChanged(parentDocumentId);
|
||||
return getDocumentIdForFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
|
||||
final File file = getFileForDocumentId(documentId);
|
||||
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||
throw new FileNotFoundException("Cannot rename root document");
|
||||
}
|
||||
|
||||
final File target = buildUniqueFile(file.getParentFile(), sanitizeDisplayName(displayName));
|
||||
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
|
||||
if (!file.renameTo(target)) {
|
||||
throw new FileNotFoundException("Unable to rename document: " + documentId);
|
||||
}
|
||||
notifyDocumentChanged(documentId);
|
||||
notifyDocumentChanged(getDocumentIdForFile(target));
|
||||
notifyChildrenChanged(parentDocumentId);
|
||||
return getDocumentIdForFile(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||
throw new FileNotFoundException("Cannot delete root document");
|
||||
}
|
||||
|
||||
final File file = getFileForDocumentId(documentId);
|
||||
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
|
||||
deleteRecursively(file);
|
||||
notifyDocumentChanged(documentId);
|
||||
notifyChildrenChanged(parentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
return ParcelFileDescriptor.open(getFileForDocumentId(documentId), modeToParcelMode(mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, android.graphics.Point sizeHint,
|
||||
CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
throw new FileNotFoundException("Thumbnails are not supported");
|
||||
}
|
||||
|
||||
private void includeDocument(MatrixCursor result, String documentId, File file) throws FileNotFoundException {
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
final boolean isDirectory = file.isDirectory();
|
||||
final String displayName = ROOT_DOCUMENT_ID.equals(documentId)
|
||||
? getContext().getString(R.string.documents_provider_root_name)
|
||||
: file.getName();
|
||||
|
||||
int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
|
||||
if (isDirectory) {
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||
flags &= ~(Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME);
|
||||
}
|
||||
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, documentId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_MIME_TYPE, isDirectory ? DIRECTORY_MIME_TYPE : getMimeType(file));
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_SIZE, isDirectory ? null : file.length());
|
||||
}
|
||||
|
||||
private File getRootDirectory() throws FileNotFoundException {
|
||||
if (isCustomDataPathEnabled()) {
|
||||
throw new FileNotFoundException(
|
||||
"Dusk DocumentsProvider is disabled while a custom data path is configured");
|
||||
}
|
||||
|
||||
final File root = getContext().getFilesDir();
|
||||
if (root == null) {
|
||||
throw new FileNotFoundException("Dusklight files directory is unavailable");
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
private File getFileForDocumentId(String documentId) throws FileNotFoundException {
|
||||
final File root = getRootDirectory();
|
||||
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||
return root;
|
||||
}
|
||||
if (!documentId.startsWith(ROOT_DOCUMENT_ID + "/")) {
|
||||
throw new FileNotFoundException("Invalid document id: " + documentId);
|
||||
}
|
||||
|
||||
final String relativePath = documentId.substring(ROOT_DOCUMENT_ID.length() + 1);
|
||||
final File file = new File(root, relativePath);
|
||||
if (!isInside(root, file)) {
|
||||
throw new FileNotFoundException("Document escapes Dusklight files directory: " + documentId);
|
||||
}
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("Document does not exist: " + documentId);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
private String getDocumentIdForFile(File file) throws FileNotFoundException {
|
||||
final File root = getRootDirectory();
|
||||
if (sameFile(root, file)) {
|
||||
return ROOT_DOCUMENT_ID;
|
||||
}
|
||||
if (!isInside(root, file)) {
|
||||
throw new FileNotFoundException("File escapes Dusklight files directory: " + file);
|
||||
}
|
||||
|
||||
final String rootPath = canonicalPath(root);
|
||||
final String filePath = canonicalPath(file);
|
||||
return ROOT_DOCUMENT_ID + "/" + filePath.substring(rootPath.length() + 1);
|
||||
}
|
||||
|
||||
private void ensureUserDirectories() {
|
||||
final File root = getContext().getFilesDir();
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
new File(root, "texture_replacements").mkdirs();
|
||||
new File(root, "USA/Card A").mkdirs();
|
||||
new File(root, "EUR/Card A").mkdirs();
|
||||
}
|
||||
|
||||
private boolean isCustomDataPathEnabled() {
|
||||
if (getContext() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final File filesDir = getContext().getFilesDir();
|
||||
if (filesDir == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final File descriptor = new File(filesDir, LOCATION_DESCRIPTOR_NAME);
|
||||
if (!descriptor.isFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final JSONObject json = new JSONObject(readText(descriptor));
|
||||
return "custom".equals(json.optString("mode", "default"));
|
||||
} catch (IOException | JSONException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String readText(File file) throws IOException {
|
||||
try (FileInputStream input = new FileInputStream(file);
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream())
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
return output.toString(StandardCharsets.UTF_8.name());
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] resolveRootProjection(String[] projection) {
|
||||
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
|
||||
}
|
||||
|
||||
private static String[] resolveDocumentProjection(String[] projection) {
|
||||
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
|
||||
}
|
||||
|
||||
private static String sanitizeDisplayName(String displayName) throws FileNotFoundException {
|
||||
if (displayName == null) {
|
||||
throw new FileNotFoundException("Document name is empty");
|
||||
}
|
||||
|
||||
final String sanitized = displayName.trim();
|
||||
if (sanitized.isEmpty() || ".".equals(sanitized) || "..".equals(sanitized) ||
|
||||
sanitized.contains("/") || sanitized.contains("\\"))
|
||||
{
|
||||
throw new FileNotFoundException("Invalid document name: " + displayName);
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private static File buildUniqueFile(File parent, String displayName) {
|
||||
File file = new File(parent, displayName);
|
||||
if (!file.exists()) {
|
||||
return file;
|
||||
}
|
||||
|
||||
final int dot = displayName.lastIndexOf('.');
|
||||
final String baseName = dot > 0 ? displayName.substring(0, dot) : displayName;
|
||||
final String extension = dot > 0 ? displayName.substring(dot) : "";
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
file = new File(parent, baseName + " (" + i + ")" + extension);
|
||||
if (!file.exists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension);
|
||||
}
|
||||
|
||||
private static int modeToParcelMode(String mode) {
|
||||
if ("r".equals(mode)) {
|
||||
return ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
}
|
||||
if ("w".equals(mode) || "wt".equals(mode)) {
|
||||
return ParcelFileDescriptor.MODE_WRITE_ONLY |
|
||||
ParcelFileDescriptor.MODE_CREATE |
|
||||
ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
}
|
||||
if ("wa".equals(mode)) {
|
||||
return ParcelFileDescriptor.MODE_WRITE_ONLY |
|
||||
ParcelFileDescriptor.MODE_CREATE |
|
||||
ParcelFileDescriptor.MODE_APPEND;
|
||||
}
|
||||
if ("rw".equals(mode)) {
|
||||
return ParcelFileDescriptor.MODE_READ_WRITE |
|
||||
ParcelFileDescriptor.MODE_CREATE;
|
||||
}
|
||||
if ("rwt".equals(mode)) {
|
||||
return ParcelFileDescriptor.MODE_READ_WRITE |
|
||||
ParcelFileDescriptor.MODE_CREATE |
|
||||
ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
}
|
||||
return ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
final int dot = file.getName().lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
final String extension = file.getName().substring(dot + 1).toLowerCase();
|
||||
final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mimeType != null) {
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
private Uri getChildDocumentsUri(String parentDocumentId) {
|
||||
return DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId);
|
||||
}
|
||||
|
||||
private void notifyChildrenChanged(String parentDocumentId) {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
resolver.notifyChange(getChildDocumentsUri(parentDocumentId), null, false);
|
||||
}
|
||||
|
||||
private void notifyDocumentChanged(String documentId) {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
resolver.notifyChange(DocumentsContract.buildDocumentUri(AUTHORITY, documentId), null, false);
|
||||
}
|
||||
|
||||
private static void deleteRecursively(File file) throws FileNotFoundException {
|
||||
if (file.isDirectory()) {
|
||||
final File[] children = file.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
deleteRecursively(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new FileNotFoundException("Unable to delete document: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInside(File parent, File child) {
|
||||
try {
|
||||
final String parentPath = canonicalPath(parent);
|
||||
final String childPath = canonicalPath(child);
|
||||
return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator);
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean sameFile(File a, File b) {
|
||||
try {
|
||||
return canonicalPath(a).equals(canonicalPath(b));
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String canonicalPath(File file) throws FileNotFoundException {
|
||||
try {
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
throw asFileNotFound("Unable to resolve path", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static FileNotFoundException asFileNotFound(String message, IOException cause) {
|
||||
final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage());
|
||||
exception.initCause(cause);
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
package dev.twilitrealm.dusk;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
public final class DuskHttpClient {
|
||||
public static final int ERROR_NONE = 0;
|
||||
public static final int ERROR_INVALID_URL = 1;
|
||||
public static final int ERROR_UNSUPPORTED_SCHEME = 2;
|
||||
public static final int ERROR_TIMEOUT = 3;
|
||||
public static final int ERROR_TOO_LARGE = 4;
|
||||
public static final int ERROR_NETWORK = 5;
|
||||
|
||||
private static final int MAX_REDIRECTS = 5;
|
||||
|
||||
public static final class Response {
|
||||
public int error;
|
||||
public String message;
|
||||
public int statusCode;
|
||||
public String[] headerNames;
|
||||
public String[] headerValues;
|
||||
public byte[] body;
|
||||
|
||||
Response(int error, String message, int statusCode, String[] headerNames,
|
||||
String[] headerValues, byte[] body) {
|
||||
this.error = error;
|
||||
this.message = message;
|
||||
this.statusCode = statusCode;
|
||||
this.headerNames = headerNames != null ? headerNames : new String[0];
|
||||
this.headerValues = headerValues != null ? headerValues : new String[0];
|
||||
this.body = body != null ? body : new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
private DuskHttpClient() {
|
||||
}
|
||||
|
||||
public static Response get(String url, String[] headerNames, String[] headerValues,
|
||||
int timeoutMs, long maxBodyBytes) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return fail(ERROR_INVALID_URL, "URL is empty");
|
||||
}
|
||||
|
||||
try {
|
||||
URL currentUrl = new URL(url);
|
||||
if (!isHttps(currentUrl)) {
|
||||
return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported");
|
||||
}
|
||||
|
||||
for (int redirect = 0; redirect <= MAX_REDIRECTS; ++redirect) {
|
||||
HttpsURLConnection connection =
|
||||
(HttpsURLConnection) currentUrl.openConnection();
|
||||
try {
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setConnectTimeout(timeoutMs);
|
||||
connection.setReadTimeout(timeoutMs);
|
||||
connection.setUseCaches(false);
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
applyHeaders(connection, headerNames, headerValues);
|
||||
|
||||
int statusCode = connection.getResponseCode();
|
||||
if (isRedirect(statusCode)) {
|
||||
String location = connection.getHeaderField("Location");
|
||||
if (location == null || location.isEmpty()) {
|
||||
return fail(ERROR_NETWORK, "Redirect response did not include Location",
|
||||
statusCode, connection, new byte[0]);
|
||||
}
|
||||
|
||||
URL nextUrl = new URL(currentUrl, location);
|
||||
if (!isHttps(nextUrl)) {
|
||||
return fail(ERROR_UNSUPPORTED_SCHEME,
|
||||
"Only https:// redirects are supported", statusCode,
|
||||
connection, new byte[0]);
|
||||
}
|
||||
currentUrl = nextUrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] body = readBody(connection, statusCode, maxBodyBytes);
|
||||
return success(statusCode, connection, body);
|
||||
} catch (ResponseTooLargeException e) {
|
||||
return fail(ERROR_TOO_LARGE, "Response body exceeded the configured limit",
|
||||
safeStatusCode(connection), connection, e.partialBody);
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
return fail(ERROR_NETWORK, "Too many redirects");
|
||||
} catch (MalformedURLException e) {
|
||||
return fail(ERROR_INVALID_URL, "Failed to parse URL");
|
||||
} catch (SocketTimeoutException e) {
|
||||
return fail(ERROR_TIMEOUT, "Request timed out");
|
||||
} catch (IOException e) {
|
||||
String message = e.getMessage();
|
||||
return fail(ERROR_NETWORK, message != null ? message : e.toString());
|
||||
} catch (ClassCastException e) {
|
||||
return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported");
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyHeaders(HttpsURLConnection connection, String[] names,
|
||||
String[] values) {
|
||||
if (names == null || values == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = Math.min(names.length, values.length);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (names[i] != null && values[i] != null) {
|
||||
connection.setRequestProperty(names[i], values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isHttps(URL url) {
|
||||
return "https".equalsIgnoreCase(url.getProtocol());
|
||||
}
|
||||
|
||||
private static boolean isRedirect(int statusCode) {
|
||||
return statusCode == HttpURLConnection.HTTP_MOVED_PERM ||
|
||||
statusCode == HttpURLConnection.HTTP_MOVED_TEMP ||
|
||||
statusCode == HttpURLConnection.HTTP_SEE_OTHER ||
|
||||
statusCode == 307 ||
|
||||
statusCode == 308;
|
||||
}
|
||||
|
||||
private static byte[] readBody(HttpsURLConnection connection, int statusCode,
|
||||
long maxBodyBytes) throws IOException,
|
||||
ResponseTooLargeException {
|
||||
InputStream stream = statusCode >= HttpURLConnection.HTTP_BAD_REQUEST ?
|
||||
connection.getErrorStream() : connection.getInputStream();
|
||||
if (stream == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
try (InputStream bodyStream = stream;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
long total = 0;
|
||||
while (true) {
|
||||
int read = bodyStream.read(buffer);
|
||||
if (read < 0) {
|
||||
return out.toByteArray();
|
||||
}
|
||||
if (read == 0) {
|
||||
continue;
|
||||
}
|
||||
if (read > maxBodyBytes || total > maxBodyBytes - read) {
|
||||
throw new ResponseTooLargeException(out.toByteArray());
|
||||
}
|
||||
out.write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int safeStatusCode(HttpsURLConnection connection) {
|
||||
try {
|
||||
return connection.getResponseCode();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static Response success(int statusCode, HttpsURLConnection connection, byte[] body) {
|
||||
HeaderLists headers = readHeaders(connection);
|
||||
return new Response(ERROR_NONE, "", statusCode, headers.names, headers.values, body);
|
||||
}
|
||||
|
||||
private static Response fail(int error, String message) {
|
||||
return new Response(error, message, 0, null, null, null);
|
||||
}
|
||||
|
||||
private static Response fail(int error, String message, int statusCode,
|
||||
HttpsURLConnection connection, byte[] body) {
|
||||
HeaderLists headers = readHeaders(connection);
|
||||
return new Response(error, message, statusCode, headers.names, headers.values, body);
|
||||
}
|
||||
|
||||
private static HeaderLists readHeaders(HttpsURLConnection connection) {
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
Map<String, List<String>> headerFields = connection.getHeaderFields();
|
||||
if (headerFields == null) {
|
||||
return new HeaderLists(new String[0], new String[0]);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
if (name == null) {
|
||||
continue;
|
||||
}
|
||||
List<String> entryValues = entry.getValue();
|
||||
if (entryValues == null || entryValues.isEmpty()) {
|
||||
names.add(name);
|
||||
values.add("");
|
||||
continue;
|
||||
}
|
||||
for (String value : entryValues) {
|
||||
names.add(name);
|
||||
values.add(value != null ? value : "");
|
||||
}
|
||||
}
|
||||
|
||||
return new HeaderLists(names.toArray(new String[0]), values.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static final class HeaderLists {
|
||||
final String[] names;
|
||||
final String[] values;
|
||||
|
||||
HeaderLists(String[] names, String[] values) {
|
||||
this.names = names;
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResponseTooLargeException extends Exception {
|
||||
final byte[] partialBody;
|
||||
|
||||
ResponseTooLargeException(byte[] partialBody) {
|
||||
this.partialBody = partialBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
interface HIDDevice
|
||||
{
|
||||
public int getId();
|
||||
public int getVendorId();
|
||||
public int getProductId();
|
||||
public String getSerialNumber();
|
||||
public int getVersion();
|
||||
public String getManufacturerName();
|
||||
public String getProductName();
|
||||
public UsbDevice getDevice();
|
||||
public boolean open();
|
||||
public int writeReport(byte[] report, boolean feature);
|
||||
public boolean readReport(byte[] report, boolean feature);
|
||||
public void setFrozen(boolean frozen);
|
||||
public void close();
|
||||
public void shutdown();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user