diff --git a/.gitattributes b/.gitattributes index 38bc2e14d..80cf0c50e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,10 +15,7 @@ src/external/* -diff linguist-vendored src/midiprogram.h -diff linguist-vendored src/midisequencer.* -diff linguist-vendored src/midisynth.* -diff linguist-vendored -src/picojson.h -diff linguist-vendored CMakePresets.json -diff linguist-generated -builds/android/app/src/main/java/org/libsdl/app/SDL*.java -diff linguist-vendored +builds/android/app/src/main/java/org/libsdl/app/*.java -diff linguist-vendored builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java diff -builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java diff -builds/android/app/src/main/java/org/libsdl/app/HID*.java -diff linguist-vendored src/generated/* -diff linguist-generated diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..202e28ba0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly + labels: + - "Building" + #reviewers: + # - carstene1ns + commit-message: + prefix: "CI" diff --git a/.github/gcc_comment_matcher.json b/.github/gcc_comment_matcher.json new file mode 100644 index 000000000..81995b459 --- /dev/null +++ b/.github/gcc_comment_matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "gcc-problem-matcher", + "pattern": [ + { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + ] + } + ] +} diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..972b28d35 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,98 @@ +# +# See https://github.com/actions/labeler +# and workflows/pr_labels.yml for reference +# + +Building: +- changed-files: + - any-glob-to-any-file: + - .github/** + - CMakeLists.txt + - builds/** + - '!builds/android/app/**' + - Makefile.am + - configure.ac + +Documentation: +- changed-files: + - any-glob-to-any-file: + - docs/** + - '**/*.md' + - '**/*.adoc' + - src/docmain.h + - '**/Doxyfile*' + - resources/unix/*.metainfo.xml + +Tests: +- changed-files: + - any-glob-to-any-file: [ tests/** ] + +Window/Scenes: +- changed-files: + - any-glob-to-any-file: [ src/**/window_*, src/**/scene_* ] + +# misc + +Audio: +- changed-files: + - any-glob-to-any-file: [ src/**/*audio* ] + +FileFinder: +- changed-files: + - any-glob-to-any-file: [ src/**/filefinder*, src/**/filesystem* ] + +Fonts: +- changed-files: + - any-glob-to-any-file: + - resources/exfont.png + - resources/ttyp0/** + - resources/shinonome/** + - resources/wenquanyi/** + - src/**/*font* + - src/generated/bitmapfont_* + +MIDI: +- changed-files: + - any-glob-to-any-file: [ src/**/*midi* ] + +# platforms + +3DS: +- changed-files: + - any-glob-to-any-file: [ src/platform/3ds/**, resources/3ds/** ] + +Android: +- changed-files: + - any-glob-to-any-file: [ src/platform/android/**, builds/android/app/** ] + +Emscripten: +- changed-files: + - any-glob-to-any-file: [ src/platform/emscripten/**, resources/emscripten/** ] + +libretro: +- changed-files: + - any-glob-to-any-file: [ src/platform/libretro/** ] + +macOS: +- changed-files: + - any-glob-to-any-file: [ src/platform/macos/**, resources/macos/** ] + +PSVita: +- changed-files: + - any-glob-to-any-file: [ src/platform/psvita/**, resources/psvita/** ] + +Switch: +- changed-files: + - any-glob-to-any-file: [ src/platform/switch/**, resources/switch/** ] + +Wii: +- changed-files: + - any-glob-to-any-file: [ src/platform/wii/**, resources/wii/** ] + +WiiU: +- changed-files: + - any-glob-to-any-file: [ src/platform/wiiu/**, resources/wiiu/** ] + +Win32: +- changed-files: + - any-glob-to-any-file: [ src/platform/windows/**, resources/windows/** ] diff --git a/.github/workflows/pr_labels.yml b/.github/workflows/pr_labels.yml new file mode 100644 index 000000000..e4d84c449 --- /dev/null +++ b/.github/workflows/pr_labels.yml @@ -0,0 +1,27 @@ +name: "Label Pull Requests" +on: + pull_request_target: + types: [opened, ready_for_review] + +jobs: + update: + permissions: + contents: read + pull-requests: write + + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + # pull_request_target is run under forks, use main repo source + fetch-depth: 0 + repository: EasyRPG/Player + ref: master + + - name: Update labels + uses: actions/labeler@v5 + with: + sync-labels: true + dot: true diff --git a/.github/workflows/stable-compilation.yml b/.github/workflows/stable-compilation.yml index 5971edd83..53908fec3 100644 --- a/.github/workflows/stable-compilation.yml +++ b/.github/workflows/stable-compilation.yml @@ -42,9 +42,10 @@ jobs: apt-get update apt-get install -yqq --no-install-recommends --no-install-suggests \ ca-certificates build-essential cmake ninja-build git \ - libicu-dev libexpat1-dev libsdl2-dev libpng-dev libpixman-1-dev \ - libfmt-dev libfreetype6-dev libharfbuzz-dev libmpg123-dev \ - libsndfile-dev libvorbis-dev libopusfile-dev libspeexdsp-dev \ + libicu-dev libexpat1-dev libinih-dev nlohmann-json3-dev \ + libsdl2-dev libpng-dev libpixman-1-dev libfmt-dev \ + libfreetype6-dev libharfbuzz-dev libmpg123-dev libsndfile-dev \ + libvorbis-dev libopusfile-dev libspeexdsp-dev \ libdrm-dev libgbm-dev # only needed for sdl2 on debian 11 - name: Clone Repository @@ -56,6 +57,9 @@ jobs: with: ref: ${{ github.event.inputs.git-ref }} + - name: Use gcc problem matcher + run: echo "::add-matcher::.github/gcc_comment_matcher.json" + - name: Compile run: | VER="(GA, `date +%Y-%m-%d`)" diff --git a/CMakeLists.txt b/CMakeLists.txt index 40b0b2faf..1d31d47e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,9 @@ include(PlayerBuildType) include(PlayerMisc) include(GetGitRevisionDescription) +# Dependencies provided by CMake Presets +list(APPEND CMAKE_PREFIX_PATH "${PLAYER_PREFIX_PATH_APPEND}") + # C++17 is required set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -120,6 +123,8 @@ add_library(${PROJECT_NAME} OBJECT src/fileext_guesser.h src/filesystem.cpp src/filesystem.h + src/filesystem_hook.cpp + src/filesystem_hook.h src/filesystem_lzh.cpp src/filesystem_lzh.h src/filesystem_native.cpp @@ -174,6 +179,8 @@ add_library(${PROJECT_NAME} OBJECT src/game_interpreter.h src/game_interpreter_map.cpp src/game_interpreter_map.h + src/game_interpreter_shared.cpp + src/game_interpreter_shared.h src/game_map.cpp src/game_map.h src/game_message.cpp @@ -419,6 +426,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_input_settings.h src/window_item.cpp src/window_item.h + src/window_interpreter.cpp + src/window_interpreter.h src/window_keyboard.cpp src/window_keyboard.h src/window_menustatus.cpp @@ -453,6 +462,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_skill.h src/window_skillstatus.cpp src/window_skillstatus.h + src/window_stringview.cpp + src/window_stringview.h src/window_targetstatus.cpp src/window_targetstatus.h src/window_teleport.cpp @@ -522,12 +533,12 @@ if(NOT GIT_TAG) list(GET GIT_DESCRIPTION 2 GIT_HASH) string(APPEND GIT_MESSAGE "${GIT_COMMITS} commits since tag \"${GIT_TAG}\", ") string(PREPEND GIT_COMMITS "+") + # strip the g prefix + string(SUBSTRING ${GIT_HASH} 1 -1 GIT_HASH) else() # no tags found, only hash list(GET GIT_DESCRIPTION 0 GIT_HASH) endif() - # strip the g prefix - string(SUBSTRING ${GIT_HASH} 1 -1 GIT_HASH) set(PLAYER_VERSION_GIT "git${GIT_COMMITS}@${GIT_HASH}") string(APPEND GIT_MESSAGE "object hash is ${GIT_HASH}") git_local_changes(GIT_DIRTY) @@ -554,8 +565,22 @@ set_property(SOURCE src/version.cpp PROPERTY COMPILE_DEFINITIONS ) # Platform setup -set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for. Options: SDL2 SDL1 libretro psvita 3ds switch wii amigaos4") -set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL2 SDL1 libretro psvita 3ds switch wii amigaos4) +if(NINTENDO_3DS) + set(PLAYER_TARGET_PLATFORM "3ds" CACHE STRING "Platform to compile for.") +elseif(NINTENDO_SWITCH) + set(PLAYER_TARGET_PLATFORM "switch" CACHE STRING "Platform to compile for.") +elseif(VITA) + set(PLAYER_TARGET_PLATFORM "psvita" CACHE STRING "Platform to compile for.") +elseif(NINTENDO_WII) + set(PLAYER_TARGET_PLATFORM "wii" CACHE STRING "Platform to compile for.") +elseif(NINTENDO_WIIU) + set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for.") +elseif(AMIGA) + set(PLAYER_TARGET_PLATFORM "SDL1" CACHE STRING "Platform to compile for.") +else() + set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for. Options: SDL2 SDL1 libretro") + set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL2 SDL1 libretro) +endif() set(PLAYER_BUILD_EXECUTABLE ON) set(PLAYER_TEST_LIBRARIES ${PROJECT_NAME}) @@ -588,15 +613,19 @@ if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") endif() if(ANDROID) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/android/app/src/gamebrowser) set(PLAYER_BUILD_EXECUTABLE OFF) endif() if(NINTENDO_WIIU) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) target_sources(${PROJECT_NAME} PRIVATE + src/platform/wiiu/main.h src/platform/wiiu/input_buttons.cpp) endif() + + if(WIN32) + target_link_libraries(${PROJECT_NAME} "Dwmapi") + endif() elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") target_sources(${PROJECT_NAME} PRIVATE src/platform/sdl/sdl_audio.cpp @@ -613,9 +642,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/libretro) target_link_libraries(${PROJECT_NAME} retro_common) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "3ds") - if(NOT NINTENDO_3DS) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/3DS.cmake' option.") - endif() target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=CtrUi PLAYER_NINTENDO) target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi) # Remove abi warning after devkitarm ships newer gcc # generate gfx assets @@ -646,9 +672,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "3ds") src/platform/3ds/ui.h) target_link_libraries(${PROJECT_NAME} 3ds-assets) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "psvita") - if(NOT VITA) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake' option.") - endif() include("$ENV{VITASDK}/share/vita.cmake" REQUIRED) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=Psp2Ui) target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi) # Remove abi warning after vitasdk ships newer gcc @@ -661,9 +684,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "psvita") src/platform/psvita/ui.cpp src/platform/psvita/ui.h) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "switch") - if(NOT NINTENDO_SWITCH) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Switch.cmake' option.") - endif() target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=NxUi PLAYER_NINTENDO) find_package(OpenGL CONFIG REQUIRED) find_library(GLAD glad REQUIRED) @@ -683,9 +703,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "switch") src/platform/switch/ui.h) target_link_libraries(${PROJECT_NAME} switch-assets) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "wii") - if(NOT NINTENDO_WII) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Wii.cmake' option.") - endif() find_package(SDL REQUIRED) target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1 PLAYER_NINTENDO) target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) @@ -698,19 +715,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "wii") src/platform/sdl/axis.h src/platform/sdl/sdl_ui.cpp src/platform/sdl/sdl_ui.h) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "amigaos4") - if(NOT AMIGAOS4) - message(FATAL_ERROR "Missing toolchain file! Use '-DCMAKE_TOOLCHAIN_FILE=path/to/ppc-amigaos.cmake' option.") - endif() - find_package(SDL REQUIRED) - target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1) - target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) - target_sources(${PROJECT_NAME} PRIVATE - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h - src/platform/sdl/axis.h - src/platform/sdl/sdl_ui.cpp - src/platform/sdl/sdl_ui.h) else() message(FATAL_ERROR "Invalid target platform") endif() @@ -722,13 +726,17 @@ if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|psvita|switch|wii)$" OR NINTENDO_WII set(PLAYER_ENABLE_TESTS OFF) option(PLAYER_VERSIONED_PACKAGES "Create zip packages with versioned name (for internal use)" ON) endif() -# Make romfs available -if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|switch)$") - option(PLAYER_ROMFS "Embedd a directory in the executable" OFF) - set(PLAYER_ROMFS_PATH "romfs" CACHE PATH "Directory to include in executable as romfs:/ path") - set(ROMFS_ARG "NO_ROMFS_IGNORE_ME") - if(PLAYER_ROMFS) - set(ROMFS_ARG "ROMFS") +# Make content available (romfs/wuhb bundle) +if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|switch)$" OR NINTENDO_WIIU) + option(PLAYER_BUNDLE "Embed a directory in the executable" OFF) + set(PLAYER_BUNDLE_PATH "content" CACHE PATH "Directory to include in executable") + set(BUNDLE_ARG "_IGNORE_ME") + if(PLAYER_BUNDLE) + if(NINTENDO_WIIU) + set(BUNDLE_ARG "CONTENT") + else() + set(BUNDLE_ARG "ROMFS") + endif() endif() endif() @@ -798,7 +806,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") set(PLAYER_JS_GAME_URL "games/" CACHE STRING "Game URL/directory where the web player searches for games") set(PLAYER_JS_OUTPUT_NAME "easyrpg-player" CACHE STRING "Output name of the js, html and wasm files") set_property(SOURCE src/async_handler.cpp APPEND PROPERTY COMPILE_DEFINITIONS "EM_GAME_URL=\"${PLAYER_JS_GAME_URL}\"") - target_sources(${PROJECT_NAME} PRIVATE src/external/picojson.h) endif() # Endianess check @@ -899,6 +906,23 @@ player_find_package(NAME lhasa TARGET LHASA::liblhasa ) +# json support +if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + player_find_package(NAME nlohmann_json + DEFINITION HAVE_NLOHMANN_JSON + TARGET nlohmann_json::nlohmann_json + REQUIRED + ) +else() + option(PLAYER_WITH_NLOHMANN_JSON "Support processing of JSON files" ON) + player_find_package(NAME nlohmann_json + CONDITION PLAYER_WITH_NLOHMANN_JSON + DEFINITION HAVE_NLOHMANN_JSON + TARGET nlohmann_json::nlohmann_json + ONLY_CONFIG + ) +endif() + # Sound system to use if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") @@ -911,21 +935,17 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") set(PLAYER_AUDIO_BACKEND "SDL1" CACHE STRING "Audio system to use. Options: SDL1 OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 OFF) else() + # Assuming that all platforms not targeting SDL have only one audio backend set(PLAYER_AUDIO_BACKEND "Default" CACHE STRING "Audio system to use. Options: Default OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS Default OFF) - - if(${PLAYER_AUDIO_BACKEND} STREQUAL "Default") - # Assuming that all platforms not targeting SDL have only one audio backend - set(PLAYER_AUDIO_BACKEND ${PLAYER_TARGET_PLATFORM}) - endif() endif() # Configure Audio backends -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|amigaos4)$") +if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") set(PLAYER_HAS_AUDIO ON) target_compile_definitions(${PROJECT_NAME} PUBLIC SUPPORT_AUDIO=1) - if(${PLAYER_AUDIO_BACKEND} STREQUAL "libretro") + if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") if (WIN32 OR UNIX OR APPLE) set(SUPPORT_NATIVE_MIDI ON) endif() @@ -980,7 +1000,7 @@ CMAKE_DEPENDENT_OPTION(PLAYER_WITH_FLUIDLITE "Play MIDI audio with fluidlite" ON CMAKE_DEPENDENT_OPTION(PLAYER_WITH_XMP "Play MOD audio with libxmp" ON "PLAYER_HAS_AUDIO" OFF) CMAKE_DEPENDENT_OPTION(PLAYER_ENABLE_DRWAV "Play WAV audio with dr_wav (built-in). Unsupported files are played by libsndfile." ON "PLAYER_HAS_AUDIO" OFF) -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|amigaos4)$") +if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") set(PLAYER_AUDIO_RESAMPLER "Auto" CACHE STRING "Audio resampler to use. Options: Auto speexdsp samplerate OFF") set_property(CACHE PLAYER_AUDIO_RESAMPLER PROPERTY STRINGS Auto speexdsp samplerate OFF) @@ -1067,7 +1087,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|a player_find_package(NAME FluidLite CONDITION PLAYER_WITH_FLUIDLITE DEFINITION HAVE_FLUIDLITE - TARGET FluidLite::fluidlite) + TARGET "fluidlite::fluidlite;fluidlite::fluidlite-static") endif() # xmp (lite) @@ -1096,7 +1116,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|a endif() # Executable -if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL(1|2)$" AND NOT NINTENDO_WIIU) +if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL[12]$" AND NOT NINTENDO_WIIU) if(APPLE) set(EXE_NAME "EasyRPG-Player.app") set_source_files_properties(${${PROJECT_NAME}_BUNDLE_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") @@ -1149,6 +1169,7 @@ if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL(1|2)$" -sEXIT_RUNTIME --bind --pre-js ${PLAYER_JS_PREJS} --post-js ${PLAYER_JS_POSTJS} \ -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$autoResumeAudioContext','$dynCall'] \ -sEXPORTED_FUNCTIONS=_main,_malloc,_free \ + -sEXPORTED_RUNTIME_METHODS=['FS'] \ -sGL_ENABLE_GET_PROC_ADDRESS") set_source_files_properties("src/platform/sdl/main.cpp" PROPERTIES OBJECT_DEPENDS "${PLAYER_JS_PREJS};${PLAYER_JS_POSTJS};${PLAYER_JS_SHELL}") @@ -1214,7 +1235,7 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/3ds/icon.png) ctr_create_3dsx(easyrpg-player SMDH easyrpg-player.smdh - ${ROMFS_ARG} ${PLAYER_ROMFS_PATH}) + ${BUNDLE_ARG} ${PLAYER_BUNDLE_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.3dsx resources/3ds/easyrpg-player.xml # consider removing, *hax² is mostly obsolete DESTINATION easyrpg-player COMPONENT 3ds) @@ -1230,7 +1251,7 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO nx_create_nro(easyrpg-player NACP easyrpg-player.nacp ICON "${PROJECT_SOURCE_DIR}/resources/switch/icon.jpg" - ${ROMFS_ARG} ${PLAYER_ROMFS_PATH}) + ${BUNDLE_ARG} ${PLAYER_BUNDLE_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.nro DESTINATION easyrpg-player COMPONENT switch) elseif(NINTENDO_WII) @@ -1251,14 +1272,17 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO ${CMAKE_CURRENT_BINARY_DIR}/resources/wii/meta.xml DESTINATION easyrpg-player COMPONENT wii) elseif(NINTENDO_WIIU) - add_executable(easyrpg-player src/platform/sdl/main.cpp) + add_executable(easyrpg-player src/platform/wiiu/main.cpp) target_link_libraries(easyrpg-player ${PROJECT_NAME}) wut_create_rpx(easyrpg-player) wut_create_wuhb(easyrpg-player NAME "EasyRPG Player ${PLAYER_VERSION_FULL}" - SHORT_NAME "Player" + SHORTNAME "EasyRPG Player" AUTHOR "EasyRPG Team" - ) # todo icon, splashs) + ICON resources/wiiu/icon.png + TVSPLASH resources/wiiu/splash-tv.png + DRCSPLASH resources/wiiu/splash-drc.png + ${BUNDLE_ARG} ${PLAYER_BUNDLE_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.rpx ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.wuhb DESTINATION . COMPONENT wiiu) @@ -1314,12 +1338,6 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO set(CPACK_ARCHIVE_DEBUG_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${CPACK_PLATFORM}-debug") set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) # we do this manually include(CPack) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "amigaos4") - add_executable(easyrpg-player "src/platform/sdl/main.cpp") - target_link_libraries(easyrpg-player - ${PROJECT_NAME} - ${SDL_LIBRARIES}) - # FIXME: packaging? else() # library if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") add_library(easyrpg_libretro @@ -1345,6 +1363,7 @@ else() # library endif() elseif(ANDROID AND ${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") add_library(easyrpg_android + src/platform/android/android.cpp src/platform/android/android.h src/platform/android/org_easyrpg_player_player_EasyRpgPlayerActivity.cpp src/platform/android/org_easyrpg_player_player_EasyRpgPlayerActivity.h @@ -1355,6 +1374,7 @@ else() # library src/platform/sdl/main.cpp) target_link_libraries(easyrpg_android ${PROJECT_NAME}) set_target_properties(easyrpg_android PROPERTIES DEBUG_POSTFIX "") + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/android/app/src/gamebrowser) set(PLAYER_TEST_LIBRARIES "easyrpg_android") else() message(FATAL_ERROR "Unsupported library target platform ${PLAYER_TARGET_PLATFORM}") @@ -1490,9 +1510,15 @@ endif() # Print summary message(STATUS "") -message(STATUS "Target system: ${PLAYER_TARGET_PLATFORM}") -if(PLAYER_ROMFS) - message(STATUS "RomFS: Embedding directory \"${PLAYER_ROMFS_PATH}\"") +set(TARGET_STATUS "${PLAYER_TARGET_PLATFORM}") +if(NINTENDO_WIIU) + set(TARGET_STATUS "Wii U (SDL2)") +elseif(AMIGA) + set(TARGET_STATUS "Amiga (SDL1)") +endif() +message(STATUS "Target system: ${TARGET_STATUS}") +if(PLAYER_BUNDLE) + message(STATUS "Embedding directory \"${PLAYER_BUNDLE_PATH}\"") endif() message(STATUS "") @@ -1502,7 +1528,7 @@ if(PLAYER_BUILD_LIBLCF) endif() message(STATUS "Audio backend: ${PLAYER_AUDIO_BACKEND}") -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii)$") +if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") message(STATUS "") set(WAV_LIBS) @@ -1523,7 +1549,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii)$ if(TARGET FluidSynth::libfluidsynth) list(APPEND MIDI_LIBS "FluidSynth") endif() - if(TARGET FluidLite::fluidlite) + if(TARGET fluidlite::fluidlite OR TARGET fluidlite::fluidlite-static) list(APPEND MIDI_LIBS "FluidLite") endif() if(TARGET WildMidi::libwildmidi OR TARGET WildMidi::libwildmidi-static) @@ -1593,6 +1619,12 @@ else() message(STATUS "LZH archive support: No") endif() +if(TARGET nlohmann_json::nlohmann_json) + message(STATUS "JSON support: nlohmann_json") +else() + message(STATUS "JSON support: No") +endif() + message(STATUS "") message(STATUS "Manual page: ${MANUAL_STATUS}") diff --git a/CMakePresets.json b/CMakePresets.json index ff2cde875..53ff62878 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -76,7 +76,7 @@ "name": "linux-parent", "toolchainFile": "${sourceDir}/builds/cmake/LinuxToolchain.cmake", "cacheVariables": { - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/linux-static" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/linux-static" }, "hidden": true, "inherits": "base-user" @@ -133,61 +133,61 @@ ] }, { - "name": "windows-x86-parent", + "name": "windows-parent", "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "x86-windows-static" + "VCPKG_TARGET_TRIPLET": "$env{VSCMD_ARG_TGT_ARCH}-windows-static" }, "inherits": "win-user", "hidden": true }, { - "name": "windows-x86-debug", - "displayName": "Windows (x86) (Debug)", + "name": "windows-debug", + "displayName": "Windows (Debug)", "inherits": [ - "windows-x86-parent", + "windows-parent", "type-debug" ] }, { - "name": "windows-x86-relwithdebinfo", - "displayName": "Windows (x86) (RelWithDebInfo)", + "name": "windows-relwithdebinfo", + "displayName": "Windows (RelWithDebInfo)", "inherits": [ - "windows-x86-parent", + "windows-parent", "type-relwithdebinfo" ] }, { - "name": "windows-x86-release", - "displayName": "Windows (x86) (Release)", + "name": "windows-release", + "displayName": "Windows (Release)", "inherits": [ - "windows-x86-parent", + "windows-parent", "type-release" ] }, { - "name": "windows-x86-libretro-debug", - "displayName": "Windows (x86) (libretro core) (Debug)", + "name": "windows-libretro-debug", + "displayName": "Windows (libretro core) (Debug)", "inherits": [ "build-libretro", - "windows-x86-parent", + "windows-parent", "type-debug" ] }, { - "name": "windows-x86-libretro-relwithdebinfo", - "displayName": "Windows (x86) (libretro core) (RelWithDebInfo)", + "name": "windows-libretro-relwithdebinfo", + "displayName": "Windows (libretro core) (RelWithDebInfo)", "inherits": [ "build-libretro", - "windows-x86-parent", + "windows-parent", "type-relwithdebinfo" ] }, { - "name": "windows-x86-libretro-release", - "displayName": "Windows (x86) (libretro core) (Release)", + "name": "windows-libretro-release", + "displayName": "Windows (libretro core) (Release)", "inherits": [ "build-libretro", - "windows-x86-parent", + "windows-parent", "type-release" ] }, @@ -252,66 +252,6 @@ "type-release" ] }, - { - "name": "windows-x64-parent", - "architecture": "x64", - "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "x64-windows-static" - }, - "inherits": "win-user", - "hidden": true - }, - { - "name": "windows-x64-debug", - "displayName": "Windows (x64) (Debug)", - "inherits": [ - "windows-x64-parent", - "type-debug" - ] - }, - { - "name": "windows-x64-relwithdebinfo", - "displayName": "Windows (x64) (RelWithDebInfo)", - "inherits": [ - "windows-x64-parent", - "type-relwithdebinfo" - ] - }, - { - "name": "windows-x64-release", - "displayName": "Windows (x64) (Release)", - "inherits": [ - "windows-x64-parent", - "type-release" - ] - }, - { - "name": "windows-x64-libretro-debug", - "displayName": "Windows (x64) (libretro core) (Debug)", - "inherits": [ - "build-libretro", - "windows-x64-parent", - "type-debug" - ] - }, - { - "name": "windows-x64-libretro-relwithdebinfo", - "displayName": "Windows (x64) (libretro core) (RelWithDebInfo)", - "inherits": [ - "build-libretro", - "windows-x64-parent", - "type-relwithdebinfo" - ] - }, - { - "name": "windows-x64-libretro-release", - "displayName": "Windows (x64) (libretro core) (Release)", - "inherits": [ - "build-libretro", - "windows-x64-parent", - "type-release" - ] - }, { "name": "windows-x64-vs2022-parent", "generator": "Visual Studio 17 2022", @@ -376,7 +316,7 @@ { "name": "macos-parent", "cacheVariables": { - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/osx", + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/osx", "CMAKE_OSX_DEPLOYMENT_TARGET": "10.9" }, "condition": { @@ -442,7 +382,7 @@ "name": "emscripten-parent", "toolchainFile": "$env{EASYRPG_BUILDSCRIPTS}/emscripten/emsdk-portable/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", "cacheVariables": { - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", "CMAKE_FIND_ROOT_PATH": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", "PLAYER_JS_BUILD_SHELL": "ON" }, @@ -477,8 +417,7 @@ "name": "3ds-parent", "toolchainFile": "$env{DEVKITPRO}/cmake/3DS.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "3ds", - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/3ds" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/3ds" }, "inherits": "dkp-user", "hidden": true @@ -538,8 +477,7 @@ "name": "switch-parent", "toolchainFile": "$env{DEVKITPRO}/cmake/Switch.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "switch", - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/switch" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/switch" }, "inherits": "dkp-user", "hidden": true @@ -599,8 +537,7 @@ "name": "wii-parent", "toolchainFile": "$env{DEVKITPRO}/cmake/Wii.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "wii", - "CMAKE_PREFIX_PATH": "$env{EASYRPG_BUILDSCRIPTS}/wii" + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/wii" }, "inherits": "dkp-user", "hidden": true @@ -656,11 +593,70 @@ "type-release" ] }, + { + "name": "wiiu-parent", + "toolchainFile": "$env{DEVKITPRO}/cmake/WiiU.cmake", + "cacheVariables": { + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/wiiu" + }, + "inherits": "dkp-user", + "hidden": true + }, + { + "name": "wiiu-debug", + "displayName": "Nintendo WiiU (Debug)", + "inherits": [ + "wiiu-parent", + "type-debug" + ] + }, + { + "name": "wiiu-relwithdebinfo", + "displayName": "Nintendo WiiU (RelWithDebInfo)", + "inherits": [ + "wiiu-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "wiiu-release", + "displayName": "Nintendo WiiU (Release)", + "inherits": [ + "wiiu-parent", + "type-release" + ] + }, + { + "name": "wiiu-libretro-debug", + "displayName": "Nintendo WiiU (libretro core) (Debug)", + "inherits": [ + "build-libretro", + "wiiu-parent", + "type-debug" + ] + }, + { + "name": "wiiu-libretro-relwithdebinfo", + "displayName": "Nintendo WiiU (libretro core) (RelWithDebInfo)", + "inherits": [ + "build-libretro", + "wiiu-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "wiiu-libretro-release", + "displayName": "Nintendo WiiU (libretro core) (Release)", + "inherits": [ + "build-libretro", + "wiiu-parent", + "type-release" + ] + }, { "name": "psvita-parent", "toolchainFile": "$env{EASYRPG_BUILDSCRIPTS}/vita/vitasdk/share/vita.toolchain.cmake", "cacheVariables": { - "PLAYER_TARGET_PLATFORM": "psvita", "BUILD_SHARED_LIBS": "OFF" }, "hidden": true, @@ -768,28 +764,28 @@ "configurePreset": "linux-libretro-release" }, { - "name": "windows-x86-debug", - "configurePreset": "windows-x86-debug" + "name": "windows-debug", + "configurePreset": "windows-debug" }, { - "name": "windows-x86-relwithdebinfo", - "configurePreset": "windows-x86-relwithdebinfo" + "name": "windows-relwithdebinfo", + "configurePreset": "windows-relwithdebinfo" }, { - "name": "windows-x86-release", - "configurePreset": "windows-x86-release" + "name": "windows-release", + "configurePreset": "windows-release" }, { - "name": "windows-x86-libretro-debug", - "configurePreset": "windows-x86-libretro-debug" + "name": "windows-libretro-debug", + "configurePreset": "windows-libretro-debug" }, { - "name": "windows-x86-libretro-relwithdebinfo", - "configurePreset": "windows-x86-libretro-relwithdebinfo" + "name": "windows-libretro-relwithdebinfo", + "configurePreset": "windows-libretro-relwithdebinfo" }, { - "name": "windows-x86-libretro-release", - "configurePreset": "windows-x86-libretro-release" + "name": "windows-libretro-release", + "configurePreset": "windows-libretro-release" }, { "name": "windows-x86-vs2022-debug", @@ -815,30 +811,6 @@ "name": "windows-x86-vs2022-libretro-release", "configurePreset": "windows-x86-vs2022-libretro-release" }, - { - "name": "windows-x64-debug", - "configurePreset": "windows-x64-debug" - }, - { - "name": "windows-x64-relwithdebinfo", - "configurePreset": "windows-x64-relwithdebinfo" - }, - { - "name": "windows-x64-release", - "configurePreset": "windows-x64-release" - }, - { - "name": "windows-x64-libretro-debug", - "configurePreset": "windows-x64-libretro-debug" - }, - { - "name": "windows-x64-libretro-relwithdebinfo", - "configurePreset": "windows-x64-libretro-relwithdebinfo" - }, - { - "name": "windows-x64-libretro-release", - "configurePreset": "windows-x64-libretro-release" - }, { "name": "windows-x64-vs2022-debug", "configurePreset": "windows-x64-vs2022-debug" @@ -971,6 +943,30 @@ "name": "wii-libretro-release", "configurePreset": "wii-libretro-release" }, + { + "name": "wiiu-debug", + "configurePreset": "wiiu-debug" + }, + { + "name": "wiiu-relwithdebinfo", + "configurePreset": "wiiu-relwithdebinfo" + }, + { + "name": "wiiu-release", + "configurePreset": "wiiu-release" + }, + { + "name": "wiiu-libretro-debug", + "configurePreset": "wiiu-libretro-debug" + }, + { + "name": "wiiu-libretro-relwithdebinfo", + "configurePreset": "wiiu-libretro-relwithdebinfo" + }, + { + "name": "wiiu-libretro-release", + "configurePreset": "wiiu-libretro-release" + }, { "name": "psvita-debug", "configurePreset": "psvita-debug" diff --git a/Makefile.am b/Makefile.am index c05af3518..942640420 100644 --- a/Makefile.am +++ b/Makefile.am @@ -103,6 +103,8 @@ libeasyrpg_player_a_SOURCES = \ src/fileext_guesser.h \ src/filesystem.cpp \ src/filesystem.h \ + src/filesystem_hook.cpp \ + src/filesystem_hook.h \ src/filesystem_lzh.cpp \ src/filesystem_lzh.h \ src/filesystem_native.cpp \ @@ -157,6 +159,8 @@ libeasyrpg_player_a_SOURCES = \ src/game_interpreter_control_variables.h \ src/game_interpreter_map.cpp \ src/game_interpreter_map.h \ + src/game_interpreter_shared.cpp \ + src/game_interpreter_shared.h \ src/game_map.cpp \ src/game_map.h \ src/game_message.cpp \ @@ -398,6 +402,8 @@ libeasyrpg_player_a_SOURCES = \ src/window_input_settings.h \ src/window_item.cpp \ src/window_item.h \ + src/window_interpreter.cpp \ + src/window_interpreter.h \ src/window_keyboard.cpp \ src/window_keyboard.h \ src/window_menustatus.cpp \ @@ -432,6 +438,8 @@ libeasyrpg_player_a_SOURCES = \ src/window_skill.h \ src/window_skillstatus.cpp \ src/window_skillstatus.h \ + src/window_stringview.cpp \ + src/window_stringview.h \ src/window_targetstatus.cpp \ src/window_targetstatus.h \ src/window_teleport.cpp \ @@ -513,7 +521,6 @@ EXTRA_DIST += \ bench/text.cpp \ bench/utils.cpp \ bench/variables.cpp \ - src/external/picojson.h \ src/platform/3ds/audio.cpp \ src/platform/3ds/audio.h \ src/platform/3ds/clock.cpp \ @@ -579,6 +586,7 @@ libeasyrpg_player_a_CXXFLAGS = \ $(FREETYPE_CFLAGS) \ $(HARFBUZZ_CFLAGS) \ $(LHASA_CFLAGS) \ + $(NLOHMANN_JSON_CFLAGS) \ $(SDL_CFLAGS) \ $(PNG_CFLAGS) \ $(ZLIB_CFLAGS) \ @@ -632,6 +640,7 @@ easyrpg_player_LDADD = libeasyrpg-player.a libplayer-version.a \ $(FREETYPE_LIBS) \ $(HARFBUZZ_LIBS) \ $(LHASA_LIBS) \ + $(NLOHMANN_JSON_LIBS) \ $(SDL_LIBS) \ $(PNG_LIBS) \ $(ZLIB_LIBS) \ diff --git a/README.md b/README.md index d28fe0726..d9b746e1a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Documentation is available at the documentation wiki: https://wiki.easyrpg.org - libxmp for tracker music support. - SpeexDSP or libsamplerate for proper audio resampling. - lhasa for LHA (.lzh) archive support. +- nlohmann_json for processing JSON files (required when targetting Emscripten) The older SDL version 1.2 is still supported, but deprecated. Please do not add new platform code for this library. @@ -134,8 +135,6 @@ EasyRPG Player makes use of the following 3rd party software: (Yoshio Uno), provided under the (3-clause) BSD license * [dr_wav] WAV audio loader and writer - Copyright (c) David Reid, provided under public domain or MIT-0 -* [PicoJSON] JSON parser/serializer - Copyright (c) 2009-2010 Cybozu Labs, Inc. - Copyright (c) 2011-2015 Kazuho Oku, provided under the (2-clause) BSD license ### 3rd party resources @@ -160,7 +159,6 @@ EasyRPG Player makes use of the following 3rd party software: [Logo2]: resources/logo2.png [FMMidi]: http://unhaut.epizy.com/fmmidi [dr_wav]: https://github.com/mackron/dr_libs -[PicoJSON]: https://github.com/kazuho/picojson [baekmuk]: https://kldp.net/baekmuk [Shinonome]: http://openlab.ring.gr.jp/efont/shinonome [ttyp0]: https://people.mpi-inf.mpg.de/~uwe/misc/uw-ttyp0 diff --git a/builds/android/app/build.gradle b/builds/android/app/build.gradle index 4c227e130..b00b9396a 100644 --- a/builds/android/app/build.gradle +++ b/builds/android/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 33 - buildToolsVersion '33.0.0' + namespace "org.easyrpg.player" ndkVersion '21.4.7075529' assetPacks = [":assets"] defaultConfig { applicationId "org.easyrpg.player" + compileSdk 34 minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionName VERSION_NAME versionCode Integer.parseInt(VERSION_CODE) } @@ -74,10 +74,10 @@ allprojects { } dependencies { - implementation 'androidx.appcompat:appcompat:1.6.0' - implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'org.ini4j:ini4j:0.5.4' diff --git a/builds/android/app/src/gamebrowser/CMakeLists.txt b/builds/android/app/src/gamebrowser/CMakeLists.txt index d578ec7b9..79017e5aa 100644 --- a/builds/android/app/src/gamebrowser/CMakeLists.txt +++ b/builds/android/app/src/gamebrowser/CMakeLists.txt @@ -3,12 +3,13 @@ cmake_minimum_required(VERSION 3.7) project(easyrpg_android VERSION 1.0 LANGUAGES CXX) add_library(gamebrowser - org_easyrpg_player_game_browser_GameScanner.cpp - org_easyrpg_player_game_browser_GameScanner.h + org_easyrpg_player_game_browser.cpp + org_easyrpg_player_game_browser.h ) -find_package(PNG REQUIRED) -target_link_libraries(gamebrowser PNG::PNG) +set_target_properties(gamebrowser PROPERTIES DEBUG_POSTFIX "") + +target_link_libraries(gamebrowser easyrpg_android) if(BUILD_SHARED_LIBS) set_property(TARGET gamebrowser PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp new file mode 100644 index 000000000..15e1954f3 --- /dev/null +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp @@ -0,0 +1,521 @@ +/* + * This file is part of EasyRPG Player + * + * Copyright (c) EasyRPG Project. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "org_easyrpg_player_game_browser.h" + +#include +#include +#include +#include +#include +#include + +#include "filefinder.h" +#include "utils.h" +#include "string_view.h" +#include "platform/android/android.h" +#include "bitmap.h" +#include "font.h" +#include "cache.h" +#include "rtp.h" + +#include +#include +#include +#include + +// via https://stackoverflow.com/q/1821806 +static void custom_png_write_func(png_structp png_ptr, png_bytep data, png_size_t length) { + std::vector *p = reinterpret_cast*>(png_get_io_ptr(png_ptr)); + p->insert(p->end(), data, data + length); +} + +jbyteArray readXyz(JNIEnv *env, std::istream& stream) { + char header[4]; + + stream.read(header, 4); + if(memcmp(header, "XYZ1", 4) != 0) { + return nullptr; + } + + unsigned short width; + unsigned short height; + stream.read((char*) &width, 2); + stream.read((char*) &height, 2); + + constexpr int buffer_incr = 8192; + std::vector compressed_xyz_data; + do { + compressed_xyz_data.resize(compressed_xyz_data.size() + buffer_incr); + stream.read(compressed_xyz_data.data() + compressed_xyz_data.size() - buffer_incr, buffer_incr); + } while (stream.gcount() == buffer_incr); + compressed_xyz_data.resize(compressed_xyz_data.size() - buffer_incr + stream.gcount()); + + uLongf xyz_size = 768 + (width * height); + std::vector xyz_data( + xyz_size); + + int status = uncompress(&xyz_data.front(), + &xyz_size, reinterpret_cast(compressed_xyz_data.data()), + compressed_xyz_data.size()); + + if(status != Z_OK) { + return nullptr; + } + + png_structp png_ptr; + png_infop info_ptr; + + // Create PNG write structure + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, + NULL, NULL); + if(png_ptr == NULL) + { + return nullptr; + } + + // Create PNG info structure + info_ptr = png_create_info_struct(png_ptr); + if(info_ptr == NULL) + { + png_destroy_write_struct(&png_ptr, NULL); + return nullptr; + } + + // Init I/O functions + if(setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + + std::vector png_outbuf; + + png_set_write_fn(png_ptr, &png_outbuf, custom_png_write_func, nullptr); + + // Set compression parameters + png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); + png_set_compression_mem_level(png_ptr, MAX_MEM_LEVEL); + png_set_compression_buffer_size(png_ptr, 1024 * 1024); + + // Write header + if(setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + // Write palette + if(setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + png_colorp palette = (png_colorp) png_malloc(png_ptr, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))); + + for(int i = 0; i < PNG_MAX_PALETTE_LENGTH; i++) + { + palette[i].red = xyz_data[i * 3]; + palette[i].green = xyz_data[i * 3 + 1]; + palette[i].blue = xyz_data[i * 3 + 2]; + } + png_set_PLTE(png_ptr, info_ptr, palette, + PNG_MAX_PALETTE_LENGTH); + + png_write_info(png_ptr, info_ptr); + + png_bytep* row_pointers = new png_bytep[height]; + for(int i = 0; i < height; i++) { + row_pointers[i] = + &xyz_data[768 + width * i]; + } + png_write_image(png_ptr, row_pointers); + delete[] row_pointers; + + png_write_end(png_ptr, info_ptr); + + png_free(png_ptr, palette); + palette = NULL; + + jbyteArray result = env->NewByteArray(png_outbuf.size()); + + env->SetByteArrayRegion(result, 0, png_outbuf.size(), reinterpret_cast(png_outbuf.data())); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + return result; +} + +std::string jstring_to_string(JNIEnv* env, jstring j_str) { + if (!j_str) { + return {}; + } + const char* chars = env->GetStringUTFChars(j_str, NULL); + std::string str(chars); + env->ReleaseStringUTFChars(j_str, chars); + return str; +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass, jstring jpath, jstring jmain_dir_name) { + EpAndroid::env = env; + + // jpath is the SAF path to the game, is converted to FilesystemView "root" + std::string spath = jstring_to_string(env, jpath); + auto root = FileFinder::Root().Create(spath); + root.ClearCache(); + + std::vector fs_list = FileFinder::FindGames(root); + + jclass jgame_class = env->FindClass("org/easyrpg/player/game_browser/Game"); + jobjectArray jgame_array = env->NewObjectArray(fs_list.size(), jgame_class, nullptr); + + if (fs_list.empty()) { + // No games found + return jgame_array; + } + + jmethodID jgame_constructor = env->GetMethodID(jgame_class, "", "(Ljava/lang/String;Ljava/lang/String;[B)V"); + + std::string root_path = FileFinder::GetFullFilesystemPath(root); + bool game_in_main_dir = false; + if (fs_list.size() == 1) { + if (root_path == FileFinder::GetFullFilesystemPath(fs_list[0])) { + game_in_main_dir = true; + } + } + + for (size_t i = 0; i < fs_list.size(); ++i) { + auto& fs = fs_list[i]; + + std::string full_path = FileFinder::GetFullFilesystemPath(fs); + std::string game_dir_name; + if (game_in_main_dir) { + // The main dir is URI encoded, the human readable name is in jmain_dir_name + game_dir_name = jstring_to_string(env, jmain_dir_name); + } else { + // In all other cases the folder name is "clean" and can be used + game_dir_name = std::get<1>(FileFinder::GetPathAndFilename(fs.GetFullPath())); + } + + std::string save_path; + if (!fs.IsFeatureSupported(Filesystem::Feature::Write)) { + // Is an archive and needs a redirected save path + // Get archive name + save_path = jstring_to_string(env, jmain_dir_name); + + // Compatibility with original GameScanner Java code + // Everything after the extension is removed + size_t ext = save_path.find_last_of('.'); + if (ext != std::string::npos) { + save_path = save_path.substr(0, ext); + } + + // Append subdirectory when the archive contains more than one game + if (fs_list.size() > 1) { + save_path += FileFinder::GetFullFilesystemPath(fs).substr(root_path.size()); + } + + // Recursion is annoying in SAF so flatten the path + // SAF already does this replacement but better do not rely on this implementation detail + save_path = Utils::ReplaceAll(save_path, "/", "_"); + } + + /* Obtaining of the game_dir_name image */ + + // 1. When the game_dir_name directory contains only one image: Load it + // 2. Attempt to fetch it from the database + // 3. If this fails grab the first from the game_dir_name folder + jbyteArray title_image = nullptr; + + auto load_image = [&](Filesystem_Stream::InputStream& stream) { + if (!stream) { + return; + } + + if (stream.GetName().ends_with(".xyz")) { + title_image = readXyz(env, stream); + } else if (stream.GetName().ends_with(".png") || stream.GetName().ends_with(".bmp")) { + auto vec = Utils::ReadStream(stream); + title_image = env->NewByteArray(vec.size()); + env->SetByteArrayRegion(title_image, 0, vec.size(), reinterpret_cast(vec.data())); + } + }; + + // 1. When the game_dir_name directory contains only one image: Load it + auto title_fs = fs.Subtree("Title"); + if (title_fs) { + auto& content = *title_fs.ListDirectory(); + if (content.size() == 1 && content[0].second.type == DirectoryTree::FileType::Regular) { + auto is = title_fs.OpenInputStream(content[0].second.name); + if (!is) { + // When opening of the image fails it is in an unsupported archive format + // Skip this game + continue; + } + load_image(is); + } + } + + // 2. Attempt to fetch it from the database + if (!title_image) { + std::string db_file = fs.FindFile("RPG_RT.ldb"); + if (!db_file.empty()) { + // This can fail when the database file is renamed, is not an error condition + auto is = fs.OpenInputStream(db_file); + if (!is) { + // When opening of the db fails it is in an unsupported archive format + // Skip this game + continue; + } else { + auto db = lcf::LDB_Reader::Load(is); + if (!db) { + // Database corrupted? Skip + continue; + } + + if (!db->system.title_name.empty()) { + auto encodings = lcf::ReaderUtil::DetectEncodings(*db); + for (auto &enc: encodings) { + if (lcf::Encoder encoder(enc); encoder.IsOk()) { + std::string title_name = lcf::ToString(db->system.title_name); + encoder.Encode(title_name); + auto title_is = fs.OpenFile("Title", title_name, FileFinder::IMG_TYPES); + // Title image was found -> Load it + load_image(title_is); + } + } + } + } + } + } + + // 3. Simply grab the first from the game_dir_name folder + if (!title_image) { + // No image loaded yet: Grab the first from the game_dir_name folder + if (title_fs) { + for (auto &[name, entry]: *title_fs.ListDirectory()) { + if (entry.type == DirectoryTree::FileType::Regular) { + auto is = title_fs.OpenInputStream(entry.name); + load_image(is); + if (title_image) { + break; + } + } + } + } + } + + /* Setting the game title */ + // By default it is just the name of the directory + std::string title = game_dir_name; + bool title_from_ini = false; + + // Try to grab a title from the INI file + if (auto ini_is = fs.OpenFile("RPG_RT.ini"); ini_is) { + if (lcf::INIReader ini(ini_is); !ini.ParseError()) { + if (std::string ini_title = ini.GetString("RPG_RT", "GameTitle", ""); !ini_title.empty()) { + title = ini_title; + title_from_ini = true; + } + } + } + + /* Create an instance of "Game" */ + jstring jgame_path = env->NewStringUTF(("content://" + full_path).c_str()); + jstring jsave_path = env->NewStringUTF(save_path.c_str()); + jobject jgame_object = env->NewObject(jgame_class, jgame_constructor, jgame_path, jsave_path, title_image); + + if (title_from_ini) { + // Store the raw string in the Game instance so it can be reencoded later via user setting + jbyteArray jtitle_raw = env->NewByteArray(title.size()); + env->SetByteArrayRegion(jtitle_raw, 0, title.size(), reinterpret_cast(title.data())); + jfieldID jtitle_raw_field = env->GetFieldID(jgame_class, "titleRaw", "[B"); + env->SetObjectField(jgame_object, jtitle_raw_field, jtitle_raw); + Java_org_easyrpg_player_game_1browser_Game_reencodeTitle(env, jgame_object); + } else { + // Use the folder name as the title + jstring jtitle = env->NewStringUTF(title.c_str()); + jmethodID jset_title_method = env->GetMethodID(jgame_class, "setTitle", "(Ljava/lang/String;)V"); + env->CallVoidMethod(jgame_object, jset_title_method, jtitle); + } + + env->SetObjectArrayElement(jgame_array, i, jgame_object); + } + + // Some fields of the Array can be NULL when a game was skipped due to an error + // This is sanitized on the Java site + return jgame_array; +} + +extern "C" +JNIEXPORT void JNICALL +Java_org_easyrpg_player_game_1browser_Game_reencodeTitle(JNIEnv *env, jobject thiz) { + jclass jgame_class = env->GetObjectClass(thiz); + + // Fetch the raw title string (result will be at the end in "title" variable) + jfieldID jtitle_raw_field = env->GetFieldID(jgame_class, "titleRaw", "[B"); + jbyteArray jtitle_raw = reinterpret_cast(env->GetObjectField(thiz, jtitle_raw_field)); + + if (!jtitle_raw) { + return; + } + + jbyte* title_data = env->GetByteArrayElements(jtitle_raw, NULL); + jsize title_len = env->GetArrayLength(jtitle_raw); + std::string title(reinterpret_cast(title_data), title_len); + env->ReleaseByteArrayElements(jtitle_raw, title_data, 0); + + // Obtain the encoding + jmethodID jget_encoding_method = env->GetMethodID(jgame_class, "getEncodingCode", "()Ljava/lang/String;"); + jstring jencoding = (jstring)env->CallObjectMethod(thiz, jget_encoding_method); + std::string encoding = jstring_to_string(env, jencoding); + if (encoding == "auto") { + auto det_encodings = lcf::ReaderUtil::DetectEncodings(title); + for (auto &det_enc: det_encodings) { + if (det_enc == "UTF-16BE" || det_enc == "UTF-16LE") { + // Skip obviously wrong title encodings + continue; + } + + if (lcf::Encoder encoder(det_enc); encoder.IsOk()) { + encoder.Encode(title); + break; + } + } + } else { + lcf::Encoder enc(encoding); + enc.Encode(title); + } + + if (title.empty()) { + // Something failed, do not set a new title + return; + } + + // Set the new title after reencoding + jstring jtitle = env->NewStringUTF(title.c_str()); + jmethodID jset_title_method = env->GetMethodID(jgame_class, "setTitle", "(Ljava/lang/String;)V"); + env->CallVoidMethod(thiz, jset_title_method, jtitle); +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_org_easyrpg_player_settings_SettingsFontActivity_DrawText(JNIEnv *env, jclass, jstring jfont, jint jsize, jboolean jfirst_font) { + EpAndroid::env = env; + + std::string font = jstring_to_string(env, jfont); + + FontRef font_file; + FontRef def_bitmap = Font::DefaultBitmapFont(!jfirst_font); + + if (font.empty()) { + // Option "Built-in Font" selected + font_file = def_bitmap; + } else { + auto is = FileFinder::Root().OpenInputStream(font); + if (!is) { + return nullptr; + } + + font_file = Font::CreateFtFont(std::move(is), jsize, false, false); + if (!font_file) { + return nullptr; + } + + font_file->SetFallbackFont(def_bitmap); + } + + int width = MESSAGE_BOX_WIDTH - 16; + int height = 16 * 6; + + jbyteArray buffer_array = env->NewByteArray(width * height * 4); + jbyte* buffer_raw = env->GetByteArrayElements(buffer_array, 0); + + Bitmap::SetFormat(Bitmap::ChooseFormat(format_R8G8B8A8_a().format())); + auto sys = Cache::System(CACHE_DEFAULT_BITMAP); + BitmapRef draw_area = Bitmap::Create(reinterpret_cast(buffer_raw), width, height, 0, format_R8G8B8A8_a().format()); + draw_area->Fill(Color(0, 0, 0, 255)); + + Text::Draw(*draw_area, 0, 16 * 0 + 2, *font_file, *sys, Font::ColorDefault, "TheQuickBrownFoxJumpsOverTheLazyDog.!?1234567890=&#%"); + Text::Draw(*draw_area, 0, 16 * 1 + 2, *font_file, *sys, Font::ColorDefault, "色は匂えど散りぬるを我が世誰ぞ常ならん有為の奥山今#日越えて浅き夢見じ酔いもせず"); + Text::Draw(*draw_area, 0, 16 * 2 + 2, *font_file, *sys, Font::ColorDefault, "天地玄黃宇宙洪荒日月盈昃辰宿列張寒來暑往秋收冬藏閏#餘成歲律呂調陽"); + Text::Draw(*draw_area, 0, 16 * 3 + 2, *font_file, *sys, Font::ColorDefault, "키스의고유조건은입술끼리만나야하고특별한기술은필요#치않다"); + Text::Draw(*draw_area, 0, 16 * 4 + 2, *font_file, *sys, Font::ColorDefault, "(+)[-]{*}ÀÁÂÃÄÅÆÇАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬ#ЭЮЯ"); + Text::Draw(*draw_area, 0, 16 * 5 + 2, *font_file, *sys, Font::ColorDefault, "ÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúû#üýþÿ"); + + return buffer_array; +} + +extern "C" +JNIEXPORT void JNICALL +Java_org_easyrpg_player_settings_SettingsGamesFolderActivity_DetectRtp(JNIEnv *env, jobject, jstring jpath, jobject hit_info, int version) { + EpAndroid::env = env; + + + std::string path = jstring_to_string(env, jpath); + + auto fs = FileFinder::Root().Create(path); + if (!fs) { + return; + } + + fs.ClearCache(); + + auto hits = RTP::Detect(fs, version); + + if (hits.empty()) { + return; + } + + // Find RTP with highest hit rate + double best_rate = 0; + int best_index = 0; + + for (size_t i = 0; i < hits.size(); ++i) { + double rate = static_cast(hits[i].hits) / hits[i].max; + if (rate > best_rate) { + best_index = i; + best_rate = rate; + } + } + + RTP::RtpHitInfo& best_hit = hits[best_index]; + + jclass hit_info_cls = env->GetObjectClass(hit_info); + jfieldID name_field = env->GetFieldID(hit_info_cls, "name", "Ljava/lang/String;"); + jfieldID version_field = env->GetFieldID(hit_info_cls, "version", "I"); + jfieldID hits_field = env->GetFieldID(hit_info_cls, "hits", "I"); + jfieldID max_field = env->GetFieldID(hit_info_cls, "max", "I"); + + jstring jname = env->NewStringUTF(best_hit.name.c_str()); + env->SetObjectField(hit_info, name_field, jname); + + env->SetIntField(hit_info, version_field, best_hit.version); + env->SetIntField(hit_info, hits_field, best_hit.hits); + env->SetIntField(hit_info, max_field, best_hit.max); +} diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h new file mode 100644 index 000000000..a9a059492 --- /dev/null +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.h @@ -0,0 +1,48 @@ +/* + * This file is part of EasyRPG Player + * + * Copyright (c) EasyRPG Project. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#ifndef _Included_org_easyrpg_player_game_browser_GameScanner +#define _Included_org_easyrpg_player_game_browser_GameScanner +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jobject JNICALL +Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass clazz, jstring path, jstring jmain_dir_name); + +JNIEXPORT void JNICALL +Java_org_easyrpg_player_game_1browser_Game_reencodeTitle(JNIEnv *env, jobject thiz); + +JNIEXPORT jbyteArray JNICALL +Java_org_easyrpg_player_settings_SettingsFontActivity_DrawText(JNIEnv *env, jclass clazz, jstring font, jint size, jboolean first_font); + +JNIEXPORT void JNICALL +Java_org_easyrpg_player_settings_SettingsGamesFolderActivity_DetectRtp(JNIEnv *env, jobject thiz, jstring path, jobject hit_info, int version); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp deleted file mode 100644 index 6a61c6e5a..000000000 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * This file is part of EasyRPG Player - * - * Copyright (c) 2021 EasyRPG Project. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "org_easyrpg_player_game_browser_GameScanner.h" - -#include -#include -#include -#include -#include -#include - -class FdStreamBufIn : public std::streambuf { -public: - FdStreamBufIn(int fd) : std::streambuf(), fd(fd) { - setg(buffer.data(), buffer.data() + buffer.size(), buffer.data() + buffer.size()); - } - - ~FdStreamBufIn() override { - close(fd); - } - - int underflow() override { - ssize_t res = read(fd, buffer.data(), buffer.size()); - if (res <= 0) { - return traits_type::eof(); - } - setg(buffer.data(), buffer.data(), buffer.data() + res); - return traits_type::to_int_type(*gptr()); - } - -private: - int fd = 0; - std::array buffer; -}; - -class BufferStreamBufIn : public std::streambuf { -public: - BufferStreamBufIn(char* buffer, jsize size) : std::streambuf(), buffer(buffer), size(size) { - setg(buffer, buffer, buffer + size); - } - -private: - char* buffer; - jsize size; - jsize index = 0; -}; - -// via https://stackoverflow.com/q/1821806 -static void custom_png_write_func(png_structp png_ptr, png_bytep data, png_size_t length) { - std::vector *p = reinterpret_cast*>(png_get_io_ptr(png_ptr)); - p->insert(p->end(), data, data + length); -} - -jbyteArray readXyz(JNIEnv *env, std::istream& stream); - -extern "C" -JNIEXPORT jbyteArray JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZbuffer( - JNIEnv *env, jclass, jbyteArray buffer) { - jbyte* elements = env->GetByteArrayElements(buffer, nullptr); - jsize size = env->GetArrayLength(buffer); - - std::istream stream(new BufferStreamBufIn(reinterpret_cast(elements), size)); - jbyteArray array = readXyz(env, stream); - - env->ReleaseByteArrayElements(buffer, elements, 0); - - return array; -} - -extern "C" -JNIEXPORT jbyteArray JNICALL Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZfd - (JNIEnv * env, jclass, jint fd) { - std::istream stream(new FdStreamBufIn(fd)); - return readXyz(env, stream); -} - -jbyteArray readXyz(JNIEnv *env, std::istream& stream) { - char header[4]; - - stream.read(header, 4); - if(memcmp(header, "XYZ1", 4) != 0) { - return nullptr; - } - - unsigned short width; - unsigned short height; - stream.read((char*) &width, 2); - stream.read((char*) &height, 2); - - constexpr int buffer_incr = 8192; - std::vector compressed_xyz_data; - do { - compressed_xyz_data.resize(compressed_xyz_data.size() + buffer_incr); - stream.read(compressed_xyz_data.data() + compressed_xyz_data.size() - buffer_incr, buffer_incr); - } while (stream.gcount() == buffer_incr); - compressed_xyz_data.resize(compressed_xyz_data.size() - buffer_incr + stream.gcount()); - - uLongf xyz_size = 768 + (width * height); - std::vector xyz_data( - xyz_size); - - int status = uncompress(&xyz_data.front(), - &xyz_size, reinterpret_cast(compressed_xyz_data.data()), - compressed_xyz_data.size()); - - if(status != Z_OK) { - return nullptr; - } - - png_structp png_ptr; - png_infop info_ptr; - - // Create PNG write structure - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, - NULL, NULL); - if(png_ptr == NULL) - { - return nullptr; - } - - // Create PNG info structure - info_ptr = png_create_info_struct(png_ptr); - if(info_ptr == NULL) - { - png_destroy_write_struct(&png_ptr, NULL); - return nullptr; - } - - // Init I/O functions - if(setjmp(png_jmpbuf(png_ptr))) - { - png_destroy_write_struct(&png_ptr, &info_ptr); - return nullptr; - } - - std::vector png_outbuf; - - png_set_write_fn(png_ptr, &png_outbuf, custom_png_write_func, nullptr); - - // Set compression parameters - png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); - png_set_compression_mem_level(png_ptr, MAX_MEM_LEVEL); - png_set_compression_buffer_size(png_ptr, 1024 * 1024); - - // Write header - if(setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &info_ptr); - return nullptr; - } - png_set_IHDR(png_ptr, info_ptr, width, height, 8, - PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - // Write palette - if(setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &info_ptr); - return nullptr; - } - png_colorp palette = (png_colorp) png_malloc(png_ptr, - PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))); - - for(int i = 0; i < PNG_MAX_PALETTE_LENGTH; i++) - { - palette[i].red = xyz_data[i * 3]; - palette[i].green = xyz_data[i * 3 + 1]; - palette[i].blue = xyz_data[i * 3 + 2]; - } - png_set_PLTE(png_ptr, info_ptr, palette, - PNG_MAX_PALETTE_LENGTH); - - png_write_info(png_ptr, info_ptr); - - png_bytep* row_pointers = new png_bytep[height]; - for(int i = 0; i < height; i++) { - row_pointers[i] = - &xyz_data[768 + width * i]; - } - png_write_image(png_ptr, row_pointers); - delete[] row_pointers; - - png_write_end(png_ptr, info_ptr); - - png_free(png_ptr, palette); - palette = NULL; - - jbyteArray result = env->NewByteArray(png_outbuf.size()); - - env->SetByteArrayRegion(result, 0, png_outbuf.size(), reinterpret_cast(png_outbuf.data())); - - png_destroy_write_struct(&png_ptr, &info_ptr); - - return result; -} diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h deleted file mode 100644 index 893b67271..000000000 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_easyrpg_player_game_browser_GameScanner */ - -#ifndef _Included_org_easyrpg_player_game_browser_GameScanner -#define _Included_org_easyrpg_player_game_browser_GameScanner -#ifdef __cplusplus -extern "C" { -#endif - -JNIEXPORT jbyteArray JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZfd(JNIEnv *env, jclass clazz, jint fd); - -JNIEXPORT jbyteArray JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_decodeXYZbuffer(JNIEnv *env, jclass clazz, - jbyteArray buffer); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/builds/android/app/src/main/AndroidManifest.xml b/builds/android/app/src/main/AndroidManifest.xml index 9ef5c3332..6b69aa1ae 100644 --- a/builds/android/app/src/main/AndroidManifest.xml +++ b/builds/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,10 @@ + android:versionName="1.0" + android:installLocation="auto"> + @@ -38,6 +38,10 @@ android:label="@string/app_name" android:theme="@style/AppTheme"> + + + + + + diff --git a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java index 41acd3c00..b01272a5c 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; @@ -29,7 +30,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; -import java.util.Set; public class Helper { /** @@ -112,6 +112,7 @@ public static void createEasyRPGFolders(Context context, Uri easyRPGFolderURI){ createFolder(context, easyRPGFolder, SettingsManager.GAMES_FOLDER_NAME); createFolder(context, easyRPGFolder, SettingsManager.SOUND_FONTS_FOLDER_NAME); createFolder(context, easyRPGFolder, SettingsManager.SAVES_FOLDER_NAME); + createFolder(context, easyRPGFolder, SettingsManager.FONTS_FOLDER_NAME); // The .nomedia file (avoid media app to scan games and RTP folders) if (Helper.findFile(context, easyRPGFolder.getUri(), ".nomedia") == null) { @@ -178,7 +179,7 @@ public static GameBrowserHelper.SafError testContentProvider(Context context, Ur return GameBrowserHelper.SafError.BAD_CONTENT_PROVIDER_READ; } - try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(testFile.getUri(), "r")) { + try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(testFile.getUri(), "w")) { } catch (IOException | IllegalArgumentException e) { return GameBrowserHelper.SafError.BAD_CONTENT_PROVIDER_WRITE; } @@ -215,17 +216,21 @@ public static List listChildrenDocumentID(Context context, Uri folderUri return filesList; } - /** List files (with DOCUMENT_ID and MIME_TYPE) in the folder pointed by "folderURI" */ - public static List listChildrenDocumentIDAndType(Context context, Uri folderUri){ + /** + * List files in the folder pointed by "folderURI" + * @return Array of Document ID, mimeType, display name (filename) + */ + public static List listChildrenDocuments(Context context, Uri folderUri){ final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); List filesList = new ArrayList<>(); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); String mimeType = c.getString(1); - filesList.add(new String[] {documentID, mimeType}); + String fileName = c.getString(2); + filesList.add(new String[] {documentID, mimeType, fileName}); } c.close(); } catch (Exception e) { @@ -238,10 +243,10 @@ public static Uri findFileUri(Context context, Uri folderUri, String fileNameToF final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); - String fileName = getFileNameFromDocumentID(documentID); + String fileName = c.getString(1); if (fileName.equals(fileNameToFind)) { Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, documentID); c.close(); @@ -261,10 +266,10 @@ public static List findFileUriWithRegex(Context context, Uri folderUri, Str final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); - String fileName = getFileNameFromDocumentID(documentID); + String fileName = c.getString(1); if (fileName.matches(regex)) { Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, documentID); uriList.add(uri); @@ -282,13 +287,6 @@ public static DocumentFile findFile(Context context, Uri folderUri, String fileN return getFileFromURI(context, uri); } - public static String getFileNameFromDocumentID(String documentID) { - if (documentID != null) { - return documentID.substring(documentID.lastIndexOf('/') + 1); - } - return ""; - } - public static DocumentFile getFileFromURI (Context context, Uri fileURI) { try { return DocumentFile.fromTreeUri(context, fileURI); @@ -331,4 +329,27 @@ public static double getTouchScale(Context context) { return Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels) / (float) outLargestSize.x; } + + public static Bitmap createBitmapFromRGBA(byte[] rgba, int width, int height) { + if (rgba == null || rgba.length != width * height * 4) { + throw new IllegalArgumentException("Invalid RGBA array length"); + } + + int[] pixels = new int[width * height]; + + for (int i = 0; i < width * height; i++) { + int r = rgba[i * 4] & 0xFF; + int g = rgba[i * 4 + 1] & 0xFF; + int b = rgba[i * 4 + 2] & 0xFF; + int a = rgba[i * 4 + 3] & 0xFF; + + pixels[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + + return bitmap; + } + } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java index 2f4966681..b3aab24ca 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java @@ -125,7 +125,8 @@ private void startGameStandalone() { String saveDir = getExternalFilesDir(null).getAbsolutePath() + "/Save"; new File(saveDir).mkdirs(); - Game project = new Game(gameDir, saveDir); + Game project = new Game(gameDir, saveDir, null); + project.setStandalone(true); GameBrowserHelper.launchGame(this, project); finish(); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java b/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java index 15b4412d1..d6fcff175 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/button_mapping/InputLayout.java @@ -79,7 +79,6 @@ public String toStringForSave(Activity activity) { private static LinkedList getDefaultHorizontalButtonList(Activity activity) { LinkedList l = new LinkedList<>(); l.add(new MenuButton(activity, 0.01, 0.01, 90)); - l.add(VirtualButton.Create(activity, VirtualButton.KEY_FAST_FORWARD, 0.9, 0.01, 90)); l.add(new VirtualCross(activity, 0.01, 0.4, 100)); l.add(VirtualButton.Create(activity, VirtualButton.ENTER, 0.80, 0.55, 100)); l.add(VirtualButton.Create(activity, VirtualButton.CANCEL, 0.90, 0.45, 100)); @@ -90,7 +89,6 @@ private static LinkedList getDefaultHorizontalButtonList(Activity private static LinkedList getDefaultVerticalButtonList(Activity activity) { LinkedList l = new LinkedList<>(); l.add(new MenuButton(activity, 0.01, 0.5, 90)); - l.add(VirtualButton.Create(activity, VirtualButton.KEY_FAST_FORWARD, 0.70, 0.5, 90)); l.add(new VirtualCross(activity, 0.05, 0.65, 100)); l.add(VirtualButton.Create(activity, VirtualButton.ENTER, 0.60, 0.75, 100)); l.add(VirtualButton.Create(activity, VirtualButton.CANCEL, 0.70, 0.65, 100)); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java index a408e495a..fb2286582 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java @@ -5,6 +5,7 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Base64; +import android.util.Log; import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; @@ -15,54 +16,140 @@ public class Game implements Comparable { final static char escapeCode = '\u0001'; - private final String title; + /** The title shown in the Game Browser */ + private String title; + /** Bytes of the title string in an unspecified encoding */ + private byte[] titleRaw = null; + /** Path to the game folder (forwarded via --project-path */ private final String gameFolderPath; - private String savePath; - private boolean isFavorite; - private final DocumentFile gameFolder; - private Bitmap titleScreen; - /** Path to the game folder inside of the zip */ - private String zipInnerPath; - - private Game(DocumentFile gameFolder) { - this.gameFolder = gameFolder; - this.title = gameFolder.getName(); - Uri folderURI = gameFolder.getUri(); - this.gameFolderPath = folderURI.toString(); - this.savePath = gameFolderPath; + /** Relative path to the save directory, made absolute by launchGame (forwarded via --save-path) */ + private String savePath = ""; + /** Whether the game was tagged as a favourite */ + private boolean isFavorite; + /** Title image shown in the Game Browser */ + private Bitmap titleScreen = null; + /** Game is launched from the APK via standalone mode */ + private boolean standalone = false; + + public Game(String gameFolderPath, String saveFolder, byte[] titleScreen) { + this.gameFolderPath = gameFolderPath; + + // is only relative here, launchGame will put this in the "saves" directory + if (!saveFolder.isEmpty()) { + savePath = saveFolder; + } + + if (titleScreen != null) { + this.titleScreen = BitmapFactory.decodeByteArray(titleScreen, 0, titleScreen.length); + }; this.isFavorite = isFavoriteFromSettings(); - } + } + + public String getTitle() { + String customTitle = getCustomTitle(); + if (!customTitle.isEmpty()) { + return customTitle; + } + + return title; + } + + public void setTitle(String title) { + this.title = title; + } - public Game(DocumentFile gameFolder, Bitmap titleScreen) { - this(gameFolder); - this.titleScreen = titleScreen; + public native void reencodeTitle(); + + public String getCustomTitle() { + return SettingsManager.getCustomGameTitle(this); + } + + public void setCustomTitle(String customTitle) { + SettingsManager.setCustomGameTitle(this, customTitle); + } + + public String getGameFolderPath() { + return gameFolderPath; + } + + public String getSavePath() { + return savePath; + } + + public void setSavePath(String path) { + savePath = path; + } + + public boolean isFavorite() { + return isFavorite; + } + + public void setFavorite(boolean isFavorite) { + this.isFavorite = isFavorite; + if(isFavorite){ + SettingsManager.addFavoriteGame(this); + } else { + SettingsManager.removeAFavoriteGame(this); + } + } + + private boolean isFavoriteFromSettings() { + return SettingsManager.getFavoriteGamesList().contains(this.getKey()); + } + + @Override + public int compareTo(Game game) { + if (this.isFavorite() && !game.isFavorite()) { + return -1; + } + if (!this.isFavorite() && game.isFavorite()) { + return 1; + } + return this.getTitle().compareTo(game.getTitle()); } /** - * Constructor for standalone mode + * Returns a unique key to be used for storing settings related to the game. * - * @param gameFolder - * @param saveFolder + * @return unique key */ - public Game(String gameFolder, String saveFolder) { - this.title = "Standalone"; - this.gameFolderPath = gameFolder; - this.savePath = saveFolder; - this.gameFolder = null; - this.isFavorite = false; + public String getKey() { + return gameFolderPath.replaceAll("[/ ]", "_"); } - private Game(DocumentFile gameFolder, String pathInZip, Bitmap titleScreen) { - this(gameFolder, titleScreen); - zipInnerPath = pathInZip; + public Encoding getEncoding() { + return SettingsManager.getGameEncoding(this); } - public static Game fromZip(DocumentFile gameFolder, String pathInZip, String saveFolder, Bitmap titleScreen) { - Game game = new Game(gameFolder, pathInZip, titleScreen); - // is only relative here, launchGame will put this in the "saves" directory - game.setSavePath(saveFolder); - return game; + public void setEncoding(Encoding encoding) { + SettingsManager.setGameEncoding(this, encoding); + reencodeTitle(); + } + + /** + * @return The encoding number or "auto" when not configured (for use via JNI) + */ + public String getEncodingCode() { + return getEncoding().getRegionCode(); + } + + public Bitmap getTitleScreen() { + return titleScreen; + } + + public boolean isStandalone() { + return standalone; + } + + public void setStandalone(boolean standalone) { + this.standalone = standalone; + } + + @NonNull + @Override + public String toString() { + return getTitle(); } public static Game fromCacheEntry(Context context, String cache) { @@ -78,99 +165,45 @@ public static Game fromCacheEntry(Context context, String cache) { return null; } - boolean isZip = Boolean.parseBoolean(entries[2]); - String zipInnerPath = null; + String title = entries[2]; - if (isZip) { - zipInnerPath = entries[3]; + byte[] titleRaw = null; + if (!entries[3].equals("null")) { + titleRaw = Base64.decode(entries[3], 0); } - Bitmap titleScreen = null; + byte[] titleScreen = null; if (!entries[4].equals("null")) { - byte[] decodedByte = Base64.decode(entries[4], 0); - titleScreen = BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length); - } - - if (isZip) { - return fromZip(gameFolder, zipInnerPath, savePath, titleScreen); - } else { - return new Game(gameFolder, titleScreen); + titleScreen = Base64.decode(entries[4], 0); } - } - - public String getTitle() { - return title; - } - public String getGameFolderPath() { - return gameFolderPath; - } + Game g = new Game(entries[1], savePath, titleScreen); + g.setTitle(title); + g.titleRaw = titleRaw; - public String getSavePath() { - return savePath; - } + if (g.titleRaw != null) { + g.reencodeTitle(); + } - public void setSavePath(String path) { - savePath = path; - } - - public boolean isFavorite() { - return isFavorite; - } - - public void setFavorite(boolean isFavorite) { - this.isFavorite = isFavorite; - if(isFavorite){ - SettingsManager.addFavoriteGame(this); - } else { - SettingsManager.removeAFavoriteGame(this); - } - } - - private boolean isFavoriteFromSettings() { - return SettingsManager.getFavoriteGamesList().contains(this.title); - } - - @Override - public int compareTo(Game game) { - if (this.isFavorite() && !game.isFavorite()) { - return -1; - } - if (!this.isFavorite() && game.isFavorite()) { - return 1; - } - return this.title.compareTo(game.title); - } - - public Encoding getEncoding() { - return SettingsManager.getGameEncoding(this); - } - - public void setEncoding(Encoding encoding) { - SettingsManager.setGameEncoding(this, encoding); - } - - @NonNull - @Override - public String toString() { - return getTitle(); + return g; } public String toCacheEntry() { StringBuilder sb = new StringBuilder(); + // Cache structure: savePath | gameFolderPath | title | titleRaw | titleScreen sb.append(savePath); sb.append(escapeCode); - - sb.append(gameFolder.getUri()); + sb.append(gameFolderPath); sb.append(escapeCode); - - sb.append(isZipArchive()); + sb.append(title); sb.append(escapeCode); - - sb.append(zipInnerPath); + if (titleRaw != null) { + sb.append(Base64.encodeToString(titleRaw, Base64.NO_WRAP)); + } else { + sb.append("null"); + } sb.append(escapeCode); - if (titleScreen != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); titleScreen.compress(Bitmap.CompressFormat.PNG, 90, baos); @@ -182,24 +215,4 @@ public String toCacheEntry() { return sb.toString(); } - - public DocumentFile getGameFolder() { - return gameFolder; - } - - public Bitmap getTitleScreen() { - return titleScreen; - } - - public Boolean isStandalone() { - return gameFolder == null; - } - - public Boolean isZipArchive() { - return zipInnerPath != null; - } - - public String getZipInnerPath() { - return zipInnerPath; - } } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java index 7f9d2514e..f6fa1cb5d 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java @@ -8,6 +8,7 @@ import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; +import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; @@ -16,6 +17,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; @@ -35,6 +37,7 @@ import org.easyrpg.player.R; import org.easyrpg.player.settings.SettingsManager; +import org.libsdl.app.SDL; import java.util.Collections; import java.util.List; @@ -57,13 +60,17 @@ protected void onCreate(Bundle savedInstanceState) { if (!libraryLoaded) { try { + System.loadLibrary("easyrpg_android"); System.loadLibrary("gamebrowser"); libraryLoaded = true; } catch (UnsatisfiedLinkError e) { - Log.e("EasyRPG Player", "Couldn't load libgamebrowser. XYZ parsing will be unavailable: " + e.getMessage()); + Log.e("EasyRPG Player", "Couldn't load libgamebrowser: " + e.getMessage()); + throw e; } } + SDL.setContext(getApplicationContext()); + setContentView(R.layout.activity_games_browser); // Configure the toolbar @@ -311,6 +318,7 @@ public void onBindViewHolder(final ViewHolder holder, final int position) { holder.settingsButton.setOnClickListener(v -> { String[] choices_list = { activity.getResources().getString(R.string.select_game_region), + activity.getResources().getString(R.string.game_rename), activity.getResources().getString(R.string.launch_debug_mode) }; @@ -319,8 +327,10 @@ public void onBindViewHolder(final ViewHolder holder, final int position) { .setTitle(R.string.settings) .setItems(choices_list, (dialog, which) -> { if (which == 0) { - chooseRegion(activity, gameList.get(position)); + chooseRegion(activity, holder, gameList.get(position)); } else if (which == 1) { + renameGame(activity, holder, gameList.get(position)); + } else if (which == 2) { launchGame(position, true); } }); @@ -363,7 +373,7 @@ public void updateFavoriteButton(ViewHolder holder, Game game){ holder.favoriteButton.setImageResource(buttonImageResource); } - public void chooseRegion(final Context context, final Game game) { + public void chooseRegion(final Context context, final ViewHolder holder, final Game game) { // The list of region choices String[] region_array = Encoding.getEncodingDescriptions(context); @@ -381,12 +391,36 @@ public void chooseRegion(final Context context, final Game game) { if (!selectedEncoding.equals(encoding)) { game.setEncoding(selectedEncoding); + holder.title.setText(game.getTitle()); } }) .setNegativeButton(R.string.cancel, null); builder.show(); } + public void renameGame(final Context context, final ViewHolder holder, final Game game) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + + // Set up text input + final EditText input = new EditText(context); + input.setInputType(InputType.TYPE_CLASS_TEXT); + input.setText(holder.title.getText()); + builder.setView(input); + + builder + .setTitle(R.string.game_rename) + .setPositiveButton(R.string.ok, (dialog, id) -> { + game.setCustomTitle(input.getText().toString()); + holder.title.setText(game.getTitle()); + }) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.revert, (dialog, id) -> { + game.setCustomTitle(""); + holder.title.setText(game.getTitle()); + }); + builder.show(); + } + public static class ViewHolder extends RecyclerView.ViewHolder { public TextView title; public ImageView titleScreen; diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java index e20b51837..dcb6d3246 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java @@ -8,7 +8,6 @@ import android.net.Uri; import android.preference.PreferenceManager; import android.util.Log; -import android.widget.Toast; import androidx.documentfile.provider.DocumentFile; @@ -50,74 +49,62 @@ public static void launchGame(Context context, Game game) { public static void launchGame(Context context, Game game, boolean debugMode) { String path = game.getGameFolderPath(); - // Test again in case somebody messed with the file system - boolean valid = game.isStandalone() || - (game.isZipArchive() && game.getGameFolder().canRead()) || - (game.getGameFolder().isDirectory() && game.getGameFolder().canRead()); + Intent intent = new Intent(context, EasyRpgPlayerActivity.class); + ArrayList args = new ArrayList<>(); - if (valid) { - Intent intent = new Intent(context, EasyRpgPlayerActivity.class); - ArrayList args = new ArrayList<>(); + // Command line passed via intent "command_line" + args.add("--project-path"); + args.add(path); - // Command line passed via intent "command_line" - String savePath; + String savePath = path; + if (!game.getSavePath().isEmpty()) { + DocumentFile saveFolder = Helper.createFolderInSave(context, game.getSavePath()); - if (game.isZipArchive()) { - // Create the redirected save folder - DocumentFile saveFolder = Helper.createFolderInSave(context, game.getSavePath()); - - args.add("--project-path"); - args.add(path + "/" + game.getZipInnerPath()); - - // In error case the native code will try to put a save folder next to the zip - if (saveFolder != null) { - savePath = saveFolder.getUri().toString(); - args.add("--save-path"); - args.add(savePath); - } else { - savePath = path; - } - } else { - args.add("--project-path"); - args.add(path); - - savePath = game.getSavePath(); + // In error case the native code will try to put a save folder next to the zip + if (saveFolder != null) { + savePath = saveFolder.getUri().toString(); args.add("--save-path"); args.add(savePath); } + } - Encoding enc = game.getEncoding(); - if (enc.getIndex() > 0) { - // 0 = Auto, in that case let the Player figure it out - args.add("--encoding"); - args.add(enc.getRegionCode()); - } + Encoding enc = game.getEncoding(); + if (enc.getIndex() > 0) { + // 0 = Auto, in that case let the Player figure it out + args.add("--encoding"); + args.add(enc.getRegionCode()); + } - args.add("--config-path"); - args.add(context.getExternalFilesDir(null).getAbsolutePath()); + args.add("--config-path"); + args.add(context.getExternalFilesDir(null).getAbsolutePath()); - // Soundfont - Uri soundfontUri = SettingsManager.getSoundFountFileURI(context); - if (soundfontUri != null) { - args.add("--soundfont"); - args.add(soundfontUri.toString()); - } - - if (debugMode) { - args.add("--test-play"); - } + /* FIXME: Currently disabled because the built-in scene cannot handle URI-encoded paths + // Sound Font Folder path (used by the settings scene) + Uri soundFontFolderUri = SettingsManager.getSoundFontsFolderURI(context); + if (soundFontFolderUri != null) { + args.add("--soundfont-path"); + args.add(soundFontFolderUri.toString()); + } - intent.putExtra(EasyRpgPlayerActivity.TAG_SAVE_PATH, savePath); - intent.putExtra(EasyRpgPlayerActivity.TAG_COMMAND_LINE, args.toArray(new String[0])); - intent.putExtra(EasyRpgPlayerActivity.TAG_STANDALONE, game.isStandalone()); + // Font Folder path (used by the settings scene) + Uri fontFolderUri = SettingsManager.getFontsFolderURI(context); + if (fontFolderUri != null) { + args.add("--font-path"); + args.add(fontFolderUri.toString()); + } + */ - Log.i("EasyRPG", "Start EasyRPG Player with following arguments : " + args); - Log.i("EasyRPG", "The RTP folder is : " + SettingsManager.getRTPFolderURI(context)); - context.startActivity(intent); - } else { - String msg = context.getString(R.string.not_valid_game).replace("$PATH", game.getTitle()); - Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); + if (debugMode) { + args.add("--test-play"); } + + intent.putExtra(EasyRpgPlayerActivity.TAG_SAVE_PATH, savePath); + intent.putExtra(EasyRpgPlayerActivity.TAG_COMMAND_LINE, args.toArray(new String[0])); + intent.putExtra(EasyRpgPlayerActivity.TAG_STANDALONE, game.isStandalone()); + + Log.i("EasyRPG", "Start EasyRPG Player with following arguments : " + args); + Log.i("EasyRPG", "The RTP folder is : " + SettingsManager.getRTPFolderURI(context)); + context.startActivity(intent); } public static void openSettingsActivity(Context context) { @@ -199,7 +186,7 @@ public static SafError dealAfterFolderSelected(Activity activity, int requestCod return SafError.BAD_CONTENT_PROVIDER_BASE_FOLDER_NOT_FOUND; } - List items = Helper.listChildrenDocumentIDAndType(activity, folder.getUri()); + List items = Helper.listChildrenDocuments(activity, folder.getUri()); int item_count = 0; for (String[] item: items) { if (item[0] == null || Helper.isDirectoryFromMimeType(item[1]) || item[0].endsWith(".nomedia")) { @@ -242,31 +229,31 @@ public static void showErrorMessage(Context context, SafError error) { break; case BAD_CONTENT_PROVIDER_CREATE: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_create); + errorMsg += "File creation failed."; break; case BAD_CONTENT_PROVIDER_READ: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_read); + errorMsg += "Read operation failed."; break; case BAD_CONTENT_PROVIDER_WRITE: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_write); + errorMsg += "Write operation failed."; break; case BAD_CONTENT_PROVIDER_DELETE: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_delete); + errorMsg += "File deletion failed."; break; case BAD_CONTENT_PROVIDER_FILENAME_IGNORED: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_filename_ignored); + errorMsg += "Provided filename ignored."; break; case BAD_CONTENT_PROVIDER_BASE_FOLDER_NOT_FOUND: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_base_folder_not_found); + errorMsg += "Selected folder not found."; break; case BAD_CONTENT_PROVIDER_FILE_ACCESS: errorMsg = context.getString(R.string.error_saf_bad_content_provider); - errorMsg += context.getString(R.string.error_saf_bad_content_provider_file_access); + errorMsg += "A file was successfully created but cannot be accessed."; break; case FOLDER_NOT_ALMOST_EMPTY: errorMsg = context.getString(R.string.error_saf_folder_not_empty); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java index f10e6be13..7fddba236 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java @@ -1,37 +1,22 @@ package org.easyrpg.player.game_browser; -import android.content.ContentResolver; +import android.app.Activity; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; -import android.provider.MediaStore; import android.util.Log; +import android.widget.TextView; import androidx.documentfile.provider.DocumentFile; import org.easyrpg.player.Helper; import org.easyrpg.player.R; import org.easyrpg.player.settings.SettingsManager; +import org.libsdl.app.SDL; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; public class GameScanner { // We use a singleton pattern to allow further optimizations @@ -51,7 +36,7 @@ private GameScanner() { this.errorList = new ArrayList<>(); } - public static GameScanner getInstance(Context context) { + public static GameScanner getInstance(Activity activity) { // Singleton pattern if (GameScanner.instance == null) { synchronized(GameScanner.class) { @@ -62,12 +47,14 @@ public static GameScanner getInstance(Context context) { } //Scan the folder - instance.scanGames(context); + instance.scanGames(activity); return instance; } - private void scanGames(Context context){ + private void scanGames(Activity activity){ + Context context = activity.getApplicationContext(); + gameList.clear(); errorList.clear(); @@ -107,7 +94,7 @@ private void scanGames(Context context){ } } - if (gameList.size() > 0) { + if (!gameList.isEmpty()) { Log.i("EasyRPG", gameList.size() + " game(s) found in cache."); return; @@ -118,7 +105,7 @@ private void scanGames(Context context){ } // Scan the games folder - scanFolderRecursive(context, gamesFolder.getUri(), GAME_SCANNING_DEPTH); + scanRootFolder(activity, gamesFolder.getUri()); // If the scan brings nothing in this folder : we notify the errorList if (gameList.size() <= 0) { @@ -140,7 +127,8 @@ private void scanGames(Context context){ private int scanFolderHash(Context context, Uri folderURI) { StringBuilder sb = new StringBuilder(); - for (String[] array : Helper.listChildrenDocumentIDAndType(context, folderURI)) { + sb.append("2"); // Bump this when the cache layout changes + for (String[] array : Helper.listChildrenDocuments(context, folderURI)) { sb.append(array[0]); sb.append(array[1]); } @@ -148,289 +136,47 @@ private int scanFolderHash(Context context, Uri folderURI) { return sb.toString().hashCode(); } - private void scanFolderRecursive(Context context, Uri folderURI, int depth) { - if (depth > 0) { - for (String[] array : Helper.listChildrenDocumentIDAndType(context, folderURI)) { - String fileDocumentID = array[0]; - String fileDocumentType = array[1]; - - String name = Helper.getFileNameFromDocumentID(fileDocumentID); - if (name.equals("")) { - continue; - } - if (!name.startsWith(".")) { - boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); - if (isDirectory) { - // Is the file/folder a RPG Maker game? - Uri fileURI = Helper.getURIFromDocumentID(folderURI, fileDocumentID); - Game game = isAGame(context, fileURI); - if (game != null) { - gameList.add(game); - } - else if (depth > 1) { - // Not a RPG2k Game but a directory -> recurse - // (We don't check if it's a directory or if its readable because of slow - // Android SAF calls and scanFolder(...) already check that) - scanFolderRecursive(context, fileURI, depth - 1); - } - } else { - String nameLower = name.toLowerCase(Locale.ROOT); - if (nameLower.endsWith(".zip") || nameLower.endsWith(".easyrpg")) { - Uri fileURI = Helper.getURIFromDocumentID(folderURI, fileDocumentID); - Game game = isAGameZipped(context, fileURI, true); - if (game != null) { - gameList.add(game); - } - } - } - } - } - } - } - - /** Return a game if "folder" is a game folder, or return null. - * This method is designed to reduce the number of sys calls */ - public static Game isAGame(Context context, Uri uri) { - Game game = null; - Uri titleFolderURI = null; - - // Create a lookup by extension as we go, in case we are dealing with non-standard extensions. - int rpgRtCount = 0; - boolean databaseFound = false; - boolean treemapFound = false; - boolean isARpgGame = false; - - for (String filePath : Helper.listChildrenDocumentID(context, uri)) { - String fileName = Helper.getFileNameFromDocumentID(filePath); - String fileNameLower = fileName.toLowerCase(Locale.ROOT); - - if (!databaseFound && fileName.equals(DATABASE_NAME)) { - databaseFound = true; - } else if (!treemapFound && fileName.equals(TREEMAP_NAME)) { - treemapFound = true; - } - // Count non-standard files. - // NOTE: Do not put this in the 'else' statement, since only 1 extension may be non-standard and we want to count both. - // We might be dealing with a non-standard extension. - // Show it, and let the C++ code sort out which file is which. - if (fileNameLower.startsWith("rpg_rt.")) { - if (!(fileName.equals(INI_FILE) || fileName.equals(EXE_FILE))) { - rpgRtCount += 1; - } - } - - if ((databaseFound && treemapFound) || rpgRtCount == 2) { - isARpgGame = true; - } - - // If we encounter a Title folder, we keep it for the title screen - // We do that here in order to avoid syscalls - if (fileNameLower.equals("title")) { - titleFolderURI = DocumentsContract.buildDocumentUriUsingTree(uri, filePath); - } - } - - if (isARpgGame) { - Bitmap titleScreen = GameScanner.extractTitleScreenImage(context, titleFolderURI); - game = new Game(Helper.getFileFromURI(context, uri), titleScreen); - } - - return game; - } - - static class ZipFoundStats { - int rpgRtCount = 0; - boolean databaseFound = false; - boolean treemapFound = false; - boolean isARpgGame = false; - byte[] titleImage = null; - - ZipFoundStats() { - } - } - - /** Return a game if "folder" is a game folder, or return null. - * This method is designed to reduce the number of sys calls */ - public static Game isAGameZipped(Context context, Uri zipUri, boolean unicode) { - ContentResolver resolver = context.getContentResolver(); - - // Create a lookup by extension as we go, in case we are dealing with non-standard extensions. - Map games = new HashMap<>(); - - try (InputStream zipIStream = resolver.openInputStream(zipUri)) { - ZipInputStream zipStream; - if (unicode) { - zipStream = new ZipInputStream(zipIStream); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // arbitrary encoding that does not crash - zipStream = new ZipInputStream(zipIStream, StandardCharsets.ISO_8859_1); - } else { - return null; - } - - ZipEntry entry; - - while ((entry = zipStream.getNextEntry()) != null) { - if (entry.isDirectory()) { - continue; - } - - String fullPath = entry.getName(); - String fileName; - - if (fullPath.isEmpty()) { - continue; - } - - int slash = fullPath.lastIndexOf('/'); - if (slash == -1) { - slash = fullPath.lastIndexOf('\\'); - } - - if (slash == -1) { - fileName = fullPath; - } else if (slash == fullPath.length() - 1) { - continue; - } else { - fileName = fullPath.substring(slash + 1); - } - - String fileNameLower = fileName.toLowerCase(Locale.ROOT); - - String gameDirectory; - if (slash <= 0) { - gameDirectory = ""; - } else { - gameDirectory = fullPath.substring(0, slash); - } - - String gameDirectoryLower = gameDirectory.toLowerCase(Locale.ROOT); - if (gameDirectoryLower.endsWith("/title") || gameDirectoryLower.endsWith("\\title")) { - // Check for a title image - ZipFoundStats stats = games.get(gameDirectory.substring(0, gameDirectory.length() - "/title".length())); - - if (stats != null) { - if (fileNameLower.endsWith(".xyz") || fileNameLower.endsWith(".png") || fileNameLower.endsWith(".bmp")) { - int count; - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - byte[] buffer = new byte[16 * 1024]; - while ((count = zipStream.read(buffer)) != -1) - out.write(buffer, 0, count); - stats.titleImage = out.toByteArray(); - if (stats.isARpgGame) { - break; - } - } - } - } - - continue; - } - - ZipFoundStats stats = games.get(gameDirectory); - - if (stats == null) { - stats = new ZipFoundStats(); - games.put(gameDirectory, stats); - } + private void scanRootFolder(Activity activity, Uri folderURI) { + Context context = activity.getApplicationContext(); + SDL.setContext(context); - if (!stats.databaseFound && fileNameLower.equals(DATABASE_NAME)) { - stats.databaseFound = true; - } else if (!stats.treemapFound && fileNameLower.equalsIgnoreCase(TREEMAP_NAME)) { - stats.treemapFound = true; - } + final ArrayList names = new ArrayList<>(); + final ArrayList fileURIs = new ArrayList<>(); - // Count non-standard files. - // NOTE: Do not put this in the 'else' statement, since only 1 extension may be non-standard and we want to count both. - // We might be dealing with a non-standard extension. - // Show it, and let the C++ code sort out which file is which. - if (fileNameLower.startsWith("rpg_rt.")) { - if (!(fileNameLower.equals(INI_FILE) || fileNameLower.equals(EXE_FILE))) { - stats.rpgRtCount += 1; - } - } + // Precalculate how many folders are to be scanned + for (String[] array : Helper.listChildrenDocuments(context, folderURI)) { + String fileDocumentID = array[0]; + String name = array[2]; - if ((stats.databaseFound && stats.treemapFound) || stats.rpgRtCount == 2) { - stats.isARpgGame = true; - if (stats.titleImage != null) { - break; - } - } - } - } catch (IOException e) { - return null; - } catch (IllegalArgumentException e) { - if (unicode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return isAGameZipped(context, zipUri, false); + if (name.isEmpty()) { + continue; } - return null; - } - for (Map.Entry entry : games.entrySet()) { - if (entry.getValue().isARpgGame) { - String name = new File(zipUri.getPath()).getName(); - String saveFolder = name.substring(0, name.lastIndexOf(".")); - Bitmap titleScreen = extractTitleScreenImage(context, entry.getValue().titleImage); - String key = entry.getKey(); - if (!unicode) { - // FIXME: This will launch the built-in Game Browser when the ZIP archive contains more than one folder in the root - // But the game can be at least launched this way. - key = ""; - } - return Game.fromZip(Helper.getFileFromURI(context, zipUri), key, saveFolder, titleScreen); + if (!name.startsWith(".")) { + Uri fileURI = Helper.getURIFromDocumentID(folderURI, fileDocumentID); + names.add(name); + fileURIs.add(fileURI); } } - return null; - } - - /** The VERY SLOW way of testing if a folder is a RPG2k Game. (contains DATABASE_NAME and TREEMAP_NAME) - * It shouldn't be use unless Google forces the use of DocumentFile over ContentResolver - * @param dir The directory to test - * @return true if RPG2k game - */ - @Deprecated - public static boolean isAGameSlowWay(DocumentFile dir) { - if (!dir.isDirectory() || !dir.canRead()) { - return false; - } - - boolean databaseFound = false; - boolean treemapFound = false; - - // Create a lookup by extension as we go, in case we are dealing with non-standard extensions. - int rpgRtCount = 0; - - for (DocumentFile entry : dir.listFiles()) { - if (entry.isFile() && entry.canRead()) { - String entryName = entry.getName(); - if (entryName == null) { - continue; - } - - if (!databaseFound && entryName.equalsIgnoreCase(DATABASE_NAME)) { - databaseFound = true; - } else if (!treemapFound && entryName.equalsIgnoreCase(TREEMAP_NAME)) { - treemapFound = true; - } + // Scan all the folders and show the current scanning progress + for (int i = 0; i < names.size(); ++i) { + final String name = names.get(i); // only "final" variables can be passed to lambdas + final int j = i; + activity.runOnUiThread(() -> { + // Update Ui progress + TextView myTextView = activity.findViewById(R.id.progressText); + myTextView.setText(String.format("%s (%d/%d)", name, j + 1, names.size())); + }); - // Count non-standard files. - // NOTE: Do not put this in the 'else' statement, since only 1 extension may be non-standard and we want to count both. - if (entryName.toLowerCase().startsWith("rpg_rt.")) { - if (!(entryName.equalsIgnoreCase(INI_FILE) || entryName.equalsIgnoreCase(EXE_FILE))) { - rpgRtCount += 1; - } - } + Game[] candidates = findGames(fileURIs.get(i).toString(), names.get(i)); - if (databaseFound && treemapFound) { - return true; + for (Game candidate: candidates) { + if (candidate != null) { + gameList.add(candidate); } } } - - // We might be dealing with a non-standard extension. - // Show it, and let the C++ code sort out which file is which. - return rpgRtCount == 2; } public List getGameList() { @@ -445,70 +191,5 @@ public boolean hasError() { return !errorList.isEmpty(); } - /** Return the game title screen, in a dumb way following last Enterbrain conventions */ - public static Bitmap extractTitleScreenImage(Context context, byte[] titleScreenBuffer) { - if (titleScreenBuffer == null) { - return null; - } - - Bitmap bmp = BitmapFactory.decodeByteArray(titleScreenBuffer, 0, titleScreenBuffer.length); - - if (bmp == null) { - byte[] xyz = decodeXYZbuffer(titleScreenBuffer); - if (xyz == null) { - return null; - } - return BitmapFactory.decodeByteArray(xyz, 0, xyz.length); - } - - return bmp; - } - - /** Return the game title screen, in a dumb way following last Enterbrain conventions */ - public static Bitmap extractTitleScreenImage(Context context, Uri titleScreenFolderURI) { - try { - // Retrieve the Title folder, containing titles screens - DocumentFile titleFolder = Helper.getFileFromURI(context, titleScreenFolderURI); - - if (titleFolder != null && titleFolder.isDirectory()) { - - // Display the first image found in the Title folder - for (String fileID : Helper.listChildrenDocumentID(context, titleScreenFolderURI)) { - String fileName = Helper.getFileNameFromDocumentID(fileID).toLowerCase().trim(); - - if (!fileName.startsWith(".")) { - if (fileName.endsWith("png") || fileName.endsWith("bmp")) { - Uri imageUri = Helper.getURIFromDocumentID(titleScreenFolderURI, fileID); - return MediaStore.Images.Media.getBitmap(context.getContentResolver(), imageUri); - } - else if (fileName.endsWith("xyz")) { - Uri imageUri = Helper.getURIFromDocumentID(titleScreenFolderURI, fileID); - Bitmap b = MediaStore.Images.Media.getBitmap(context.getContentResolver(), imageUri); - if (b == null && GameBrowserActivity.libraryLoaded) { - // Check for XYZ - try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(imageUri, "r")) { - byte[] xyz = decodeXYZfd(fd.detachFd()); - if (xyz == null) { - return null; - } - return BitmapFactory.decodeByteArray(xyz, 0, xyz.length); - } catch (IOException e) { - return null; - } - } - return b; - } - } - } - } - } catch (Exception e) { - Log.e("EasyRPG", e.getMessage()); - } - - return null; - } - - private static native byte[] decodeXYZfd(int fd); - - private static native byte[] decodeXYZbuffer(byte[] buffer); + private static native Game[] findGames(String path, String mainFolderName); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java index fc37f6144..828be979e 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/player/EasyRpgPlayerActivity.java @@ -84,6 +84,14 @@ public class EasyRpgPlayerActivity extends SDLActivity implements NavigationView private boolean uiVisible = true; SurfaceView surface; + @Override + protected String[] getLibraries() { + return new String[] { + "SDL2", + "easyrpg_android" + }; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -145,9 +153,6 @@ public void onDrawerStateChanged(int newState) { mLayout.addView(surface); updateScreenPosition(); - // Set speed multiplier - setFastForwardMultiplier(SettingsManager.getFastForwardMultiplier()); - showInputLayout(); } @@ -176,10 +181,14 @@ public boolean onNavigationItemSelected(MenuItem item) { } else if (item.getItemId() == R.id.toggle_ui) { uiVisible = !uiVisible; showInputLayout(); + } else if (item.getItemId() == R.id.toggle_fps) { + toggleFps(); } else if (item.getItemId() == R.id.edit_layout) { editLayout(); } else if (item.getItemId() == R.id.report_bug) { reportBug(); + } else if (item.getItemId() == R.id.reset_game) { + showResetGameDialog(); } else if (item.getItemId() == R.id.end_game) { showEndGameDialog(); } @@ -307,6 +316,20 @@ public void openOrCloseMenu() { } } + private void showResetGameDialog() { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(R.string.app_name); + + // set dialog message + alertDialogBuilder.setMessage(R.string.do_want_reset).setCancelable(false) + .setPositiveButton(R.string.yes, (dialog, id) -> resetGame()).setNegativeButton(R.string.no, (dialog, id) -> dialog.cancel()); + + // create alert dialog + AlertDialog alertDialog = alertDialogBuilder.create(); + + alertDialog.show(); + } + private void showEndGameDialog() { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); alertDialogBuilder.setTitle(R.string.app_name); @@ -325,7 +348,9 @@ private void showEndGameDialog() { public static native void endGame(); - public static native void setFastForwardMultiplier(int m); + public static native void resetGame(); + + public static native void toggleFps(); protected String[] getArguments() { return getIntent().getStringArrayExtra(TAG_COMMAND_LINE); @@ -356,7 +381,7 @@ public String getRtpPath() { return ""; } - public SafFile getHandleForPath(String path) { + public static SafFile getHandleForPath(String path) { return SafFile.fromPath(getContext(), path); } @@ -412,8 +437,8 @@ public void updateScreenPosition() { * * @return asset manager */ - public AssetManager getAssetManager() { - return getAssets(); + public static AssetManager getAssetManager() { + return getContext().getAssets(); } /** diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java index 9276bfde5..1220dd87b 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/IniFile.java @@ -10,6 +10,7 @@ public class IniFile { public SectionView video; public SectionView audio; + public SectionView input; public SectionView engine; public IniFile(File iniFile) { @@ -28,7 +29,8 @@ public IniFile(File iniFile) { video = new SectionView("Video"); audio = new SectionView("Audio"); - engine = new SectionView("Engine"); + input = new SectionView("Input"); + engine = new SectionView("Player"); } public boolean save() { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java index 1fed83876..6a8426518 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java @@ -7,7 +7,6 @@ import android.provider.DocumentsContract; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.Button; import android.widget.LinearLayout; import android.widget.RadioButton; @@ -15,7 +14,6 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.AppCompatSpinner; import androidx.documentfile.provider.DocumentFile; import org.easyrpg.player.Helper; @@ -90,14 +88,14 @@ private List scanAvailableSoundfonts(){ Uri soundFontsFolder = SettingsManager.getSoundFontsFolderURI(this); if (soundFontsFolder != null) { - for (String[] array : Helper.listChildrenDocumentIDAndType(this, soundFontsFolder)) { + for (String[] array : Helper.listChildrenDocuments(this, soundFontsFolder)) { String fileDocumentID = array[0]; String fileDocumentType = array[1]; + String name = array[2]; // Is it a soundfont file ? boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); - String name = Helper.getFileNameFromDocumentID(fileDocumentID); - if (!isDirectory && name.toLowerCase().endsWith(".sf2")) { + if (!isDirectory && (name.toLowerCase().endsWith(".sf2") || name.toLowerCase().endsWith(".soundfont"))) { DocumentFile soundFontFile = Helper.getFileFromDocumentID(this, soundFontsFolder, fileDocumentID); if (soundFontFile != null) { soundfontList.add(new SoundfontItemList(this, name, soundFontFile.getUri())); @@ -113,7 +111,7 @@ public SoundfontItemList getDefaultSoundfont(Context context) { } public static boolean isSelectedSoundfontFile(Context context, Uri soundfontUri) { - Uri selectedSoundFontUri = SettingsManager.getSoundFountFileURI(context); + Uri selectedSoundFontUri = SettingsManager.getSoundFontFileURI(); if (soundfontUri == null && selectedSoundFontUri == null) { return true; } @@ -147,7 +145,7 @@ public SoundfontItemList(Context context, String name, Uri uri) { } public void select() { - SettingsManager.setSoundFountFileURI(uri); + SettingsManager.setSoundFontFileURI(uri); // Uncheck other RadioButton for (SoundfontItemList s : soundfontList) { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java index 5735bb6e4..d06a03c0b 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsEnum.java @@ -11,19 +11,24 @@ enum SettingsEnum { LAYOUT_SIZE("PREF_SIZE_EVERY_BUTTONS"), EASYRPG_FOLDER_URI("PREF_EASYRPG_FOLDER_URI"), ENABLE_RTP_SCANNING("PREF_ENABLE_RTP_SCANNING"), - SOUNDFONT_URI("PREF_SOUNDFONT_URI"), + SOUNDFONT_URI("Soundfont"), FAVORITE_GAMES("PREF_FAVORITE_GAMES_NEW"), CACHE_GAMES_HASH("PREF_CACHE_GAMES_HASH"), CACHE_GAMES("PREF_CACHE_GAMES"), FORCED_LANDSCAPE("PREF_FORCED_LANDSCAPE"), FAST_FORWARD_MODE("FAST_FORWARD_MODE"), - FAST_FORWARD_MULTIPLIER("FAST_FORWARD_MULTIPLIER"), INPUT_LAYOUT_HORIZONTAL("INPUT_LAYOUT_HORIZONTAL"), INPUT_LAYOUT_VERTICAL("INPUT_LAYOUT_VERTICAL"), MUSIC_VOLUME("MusicVolume"), SOUND_VOLUME("SoundVolume"), STRETCH("Stretch"), - GAME_RESOLUTION("GameResolution") + GAME_RESOLUTION("GameResolution"), + SPEED_MODIFIER_A("SpeedModifierA"), + FONT1_URI("Font1"), + FONT2_URI("Font2"), + FONT1_SIZE("Font1Size"), + FONT2_SIZE("Font2Size") + ; diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java new file mode 100644 index 000000000..2429ad646 --- /dev/null +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java @@ -0,0 +1,308 @@ +package org.easyrpg.player.settings; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.DocumentsContract; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.documentfile.provider.DocumentFile; + +import org.easyrpg.player.Helper; +import org.easyrpg.player.R; +import org.libsdl.app.SDL; + +import java.util.ArrayList; +import java.util.List; + +public class SettingsFontActivity extends AppCompatActivity { + private LinearLayout fonts1ListLayout; + private LinearLayout fonts2ListLayout; + private String[] extensions = new String[] {".fon", ".fnt", ".bdf", ".ttf", ".ttc", ".otf", ".woff2", ".woff"}; + + List font1List; + List font2List; + private final int SIZE_MIN = 6; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings_font); + + fonts1ListLayout = findViewById(R.id.settings_font1_list); + fonts2ListLayout = findViewById(R.id.settings_font2_list); + + SettingsManager.init(getApplicationContext()); + SDL.setContext(getApplicationContext()); + + // Setup UI components + // The Font Button + Button button = this.findViewById(R.id.button_open_font_folder); + // We can open the file picker in a specific folder only with API >= 26 + if (android.os.Build.VERSION.SDK_INT >= 26) { + button.setOnClickListener(v -> { + // Open the file explorer in the "fonts" folder + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, SettingsManager.getFontsFolderURI(this)); + startActivity(intent); + }); + } else { + ViewGroup layout = (ViewGroup) button.getParent(); + if(layout != null) { + layout.removeView(button); + } + } + + configureFont1Size(); + configureFont2Size(); + } + + @Override + protected void onResume() { + super.onResume(); + + updateFontsListView(); + } + + private void updateFontsListView() { + fonts1ListLayout.removeAllViews(); + fonts2ListLayout.removeAllViews(); + + boolean font1Selected = false; + boolean font2Selected = false; + + scanAvailableFonts(); + for (FontItemList i : font1List) { + fonts1ListLayout.addView(i.getRadioButton()); + if (i.isSelected()) { + font1Selected = true; + } + } + for (FontItemList i : font2List) { + fonts2ListLayout.addView(i.getRadioButton()); + if (i.isSelected()) { + font2Selected = true; + } + } + + // If no font is selected, select the default one + if (!font1Selected) { + font1List.get(0).setSelected(true); + } + if (!font2Selected) { + font2List.get(0).setSelected(true); + } + } + + private void updatePreview(boolean firstFont) { + ImageView imageView; + String font = ""; + int size; + if (firstFont) { + imageView = findViewById(R.id.settings_font1_preview); + Uri fontUri = SettingsManager.getFont1FileURI(); + if (fontUri != null) { + font = fontUri.toString(); + } + size = SettingsManager.getFont1Size(); + } else { + imageView = findViewById(R.id.settings_font2_preview); + Uri fontUri = SettingsManager.getFont2FileURI(); + if (fontUri != null) { + font = fontUri.toString(); + } + size = SettingsManager.getFont2Size(); + } + + byte[] image = DrawText(font, size, firstFont); + if (image != null) { + Bitmap bitmap = Helper.createBitmapFromRGBA(image,304, 16 * 6); + imageView.setImageBitmap(bitmap); + } + } + + private void scanAvailableFonts(){ + font1List = new ArrayList<>(); + font2List = new ArrayList<>(); + font1List.add(new FontItemList(this, this.getString(R.string.settings_font_default), null, true)); + font2List.add(new FontItemList(this, this.getString(R.string.settings_font_default), null, false)); + + Uri fontsFolder = SettingsManager.getFontsFolderURI(this); + if (fontsFolder != null) { + for (String[] array : Helper.listChildrenDocuments(this, fontsFolder)) { + String fileDocumentID = array[0]; + String fileDocumentType = array[1]; + String name = array[2]; + + // Is it a font file ? + boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); + String lname = name.toLowerCase(); + boolean fontOk = false; + for (String ext: extensions) { + if (lname.endsWith(ext)) { + fontOk = true; + break; + } + } + if (!isDirectory && fontOk) { + DocumentFile fontFile = Helper.getFileFromDocumentID(this, fontsFolder, fileDocumentID); + if (fontFile != null) { + font1List.add(new FontItemList(this, name, fontFile.getUri(), true)); + font2List.add(new FontItemList(this, name, fontFile.getUri(), false)); + } + } + } + } + } + + public static boolean isSelectedFontFile(Context context, Uri fontUri, boolean firstFont) { + Uri selectedFontUri = null; + if (firstFont) { + selectedFontUri = SettingsManager.getFont1FileURI(); + } else { + selectedFontUri = SettingsManager.getFont2FileURI(); + } + if (fontUri == null && selectedFontUri == null) { + return true; + } + else if (fontUri != null) { + return fontUri.equals(selectedFontUri); + } else { + return false; + } + } + + class FontItemList { + private final String name; + private final Uri uri; + private final RadioButton radioButton; + private final boolean firstFont; + + public FontItemList(Context context, String name, Uri uri, boolean firstFont) { + this.name = name; + this.uri = uri; + this.firstFont = firstFont; + + // The Radio Button + View layout = getLayoutInflater().inflate(R.layout.settings_soundfont_item_list, null); + this.radioButton = layout.findViewById(R.id.settings_soundfont_radio_button); + radioButton.setOnClickListener(v -> select()); + if (isSelectedFontFile(context, uri, firstFont)) { + setSelected(true); + } + + // The name + radioButton.setText(name); + radioButton.setOnClickListener(v -> select()); + } + + public void select() { + if (firstFont) { + SettingsManager.setFont1FileURI(uri); + for (FontItemList s : font1List) { + s.getRadioButton().setChecked(false); + } + radioButton.setChecked(true); + updatePreview(true); + } else { + SettingsManager.setFont2FileURI(uri); + for (FontItemList s : font2List) { + s.getRadioButton().setChecked(false); + } + radioButton.setChecked(true); + updatePreview(false); + } + } + + public String getName() { + return name; + } + + public Uri getUri() { + return uri; + } + + public boolean isSelected() { + return radioButton.isChecked(); + } + + public void setSelected(boolean selected) { + radioButton.setChecked(selected); + } + + public RadioButton getRadioButton() { + return radioButton; + } + } + + private void configureFont1Size() { + SeekBar fontSize1SeekBar = findViewById(R.id.settings_font1_size); + fontSize1SeekBar.setProgress(SettingsManager.getFont1Size() - SIZE_MIN); + + TextView t = findViewById(R.id.settings_font1_size_text_view); + + fontSize1SeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + SettingsManager.setFont1Size(seekBar.getProgress() + SIZE_MIN); + updatePreview(true); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + t.setText(String.valueOf(fontSize1SeekBar.getProgress() + SIZE_MIN)); + } + }); + + t.setText(String.valueOf(fontSize1SeekBar.getProgress() + SIZE_MIN)); + updatePreview(true); + } + + private void configureFont2Size() { + SeekBar fontSize2SeekBar = findViewById(R.id.settings_font2_size); + fontSize2SeekBar.setProgress(SettingsManager.getFont2Size() - SIZE_MIN); + + TextView t = findViewById(R.id.settings_font2_size_text_view); + + fontSize2SeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + SettingsManager.setFont2Size(seekBar.getProgress() + SIZE_MIN); + updatePreview(false); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + t.setText(String.valueOf(fontSize2SeekBar.getProgress() + SIZE_MIN)); + } + }); + + t.setText(String.valueOf(fontSize2SeekBar.getProgress() + SIZE_MIN)); + updatePreview(false); + } + + private static native byte[] DrawText(String font, int size, boolean firstFont); +} + diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java index 5c7e54cdd..37e4aad8f 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java @@ -8,12 +8,14 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; +import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import org.easyrpg.player.R; import org.easyrpg.player.game_browser.GameBrowserActivity; import org.easyrpg.player.game_browser.GameBrowserHelper; +import org.libsdl.app.SDL; public class SettingsGamesFolderActivity extends AppCompatActivity { private GameBrowserHelper.SafError safError = GameBrowserHelper.SafError.OK; @@ -22,6 +24,7 @@ public class SettingsGamesFolderActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_settings_easyrpg_folders); + SDL.setContext(getApplicationContext()); safError = GameBrowserHelper.SafError.OK; @@ -78,6 +81,8 @@ public void onCreate(Bundle savedInstanceState) { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(GameBrowserHelper.VIDEO_URL)); startActivity(browserIntent); }); + + DetectRtp(); } @Override @@ -87,6 +92,8 @@ public void onResume() { if (safError != GameBrowserHelper.SafError.OK && safError != GameBrowserHelper.SafError.ABORTED) { GameBrowserHelper.showErrorMessage(this, safError); safError = GameBrowserHelper.SafError.OK; + } else { + DetectRtp(); } } @@ -110,4 +117,52 @@ public void onActivityResult(int requestCode, int resultCode, Intent resultData) intent = new Intent(this, GameBrowserActivity.class); startActivity(intent); } + + private void DetectRtp() { + Uri rtpUri = SettingsManager.getRTPFolderURI(this); + if (rtpUri == null) { + return; + } + + RtpHitInfo hitInfo = new RtpHitInfo(); + DetectRtp(rtpUri + "/2000", hitInfo, 2000); + + TextView res = findViewById(R.id.settings_rtp_2000_result); + String rtpText; + if (hitInfo.version == 2000) { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_found); + rtpText = rtpText.replace("$YEAR", "2000") + .replace("$NAME", hitInfo.name) + .replace("$FOUND", Integer.toString(hitInfo.hits)) + .replace("$MAX", Integer.toString(hitInfo.max)); + } else { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_not_found); + rtpText = rtpText.replace("$YEAR", "2000"); + } + res.setText(rtpText); + + DetectRtp(rtpUri + "/2003", hitInfo, 2003); + + res = findViewById(R.id.settings_rtp_2003_result); + if (hitInfo.version == 2003) { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_found); + rtpText = rtpText.replace("$YEAR", "2003") + .replace("$NAME", hitInfo.name) + .replace("$FOUND", Integer.toString(hitInfo.hits)) + .replace("$MAX", Integer.toString(hitInfo.max)); + } else { + rtpText = getApplicationContext().getResources().getString(R.string.rtp_not_found); + rtpText = rtpText.replace("$YEAR", "2003"); + } + res.setText(rtpText); + } + + public static class RtpHitInfo { + String name; + int version = 0; + int hits = 0; + int max = 0; + }; + + private native void DetectRtp(String path, RtpHitInfo hitInfo, int version); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java index 006977f20..924c3b4d6 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsInputActivity.java @@ -76,13 +76,13 @@ public void onNothingSelected(AdapterView adapterView) { }); fastForwardMultiplierSeekBar = findViewById(R.id.settings_fast_forward_multiplier); - fastForwardMultiplierSeekBar.setProgress(SettingsManager.getFastForwardMultiplier() - 2); + fastForwardMultiplierSeekBar.setProgress(SettingsManager.getSpeedModifierA() - 2); fastForwardMultiplierSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { - // The seekbar has values 0-8, we want 2-10 - SettingsManager.setFastForwardMultiplier(seekBar.getProgress() + 2); + // The seekbar has values 0-98, we want 2-100 + SettingsManager.setSpeedModifierA(seekBar.getProgress() + 2); } @Override diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java index ae94fcbfb..b13979b2f 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsMainActivity.java @@ -24,6 +24,8 @@ public void onCreate(Bundle savedInstanceState) { audioButton.setOnClickListener(this); Button inputsButton = findViewById(R.id.settings_main_input); inputsButton.setOnClickListener(this); + Button fontButton = findViewById(R.id.settings_main_font); + fontButton.setOnClickListener(this); Button folderButton = findViewById(R.id.settings_main_easyrpg_folders); folderButton.setOnClickListener(this); } @@ -41,6 +43,8 @@ public void onClick(View v) { intent = new Intent(this, SettingsGamesFolderActivity.class); } else if (id == R.id.settings_main_input) { intent = new Intent(this, SettingsInputActivity.class); + } else if (id == R.id.settings_main_font) { + intent = new Intent(this, SettingsFontActivity.class); } if (intent != null) { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java index fdcf39857..0c414d7ba 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsManager.java @@ -37,15 +37,18 @@ public class SettingsManager { private static int imageSize, gameResolution; private static int layoutTransparency, layoutSize, fastForwardMode, fastForwardMultiplier; private static int musicVolume, soundVolume; + private static int speedModifierA; private static InputLayout inputLayoutHorizontal, inputLayoutVertical; // Note: don't store DocumentFile as they can be nullify with a change of context - private static Uri easyRPGFolderURI, soundFountFileURI; + private static Uri easyRPGFolderURI, soundFontFileURI, font1FileURI, font2FileURI; + private static int font1Size, font2Size; private static Set favoriteGamesList = new HashSet<>(); private static int gamesCacheHash; private static Set gamesCache = new HashSet<>(); public static String RTP_FOLDER_NAME = "rtp", RTP_2000_FOLDER_NAME = "2000", RTP_2003_FOLDER_NAME = "2003", SOUND_FONTS_FOLDER_NAME = "soundfonts", - GAMES_FOLDER_NAME = "games", SAVES_FOLDER_NAME = "saves"; + GAMES_FOLDER_NAME = "games", SAVES_FOLDER_NAME = "saves", + FONTS_FOLDER_NAME = "fonts"; public static int FAST_FORWARD_MODE_HOLD = 0, FAST_FORWARD_MODE_TAP = 1; private static List imageSizeOption = Arrays.asList("nearest", "integer", "bilinear"); @@ -76,11 +79,15 @@ private static void loadSettings(Context context) { forcedLandscape = sharedPref.getBoolean(FORCED_LANDSCAPE.toString(), false); stretch = configIni.video.getBoolean(STRETCH.toString(), false); fastForwardMode = sharedPref.getInt(FAST_FORWARD_MODE.toString(), FAST_FORWARD_MODE_TAP); - fastForwardMultiplier = sharedPref.getInt(FAST_FORWARD_MULTIPLIER.toString(), 3); musicVolume = configIni.audio.getInteger(MUSIC_VOLUME.toString(), 100); soundVolume = configIni.audio.getInteger(SOUND_VOLUME.toString(), 100); + font1Size = configIni.engine.getInteger(FONT1_SIZE.toString(), 12); + font2Size = configIni.engine.getInteger(FONT2_SIZE.toString(), 12); + + speedModifierA = configIni.input.getInteger(SPEED_MODIFIER_A.toString(), 3); + favoriteGamesList = new HashSet<>(sharedPref.getStringSet(FAVORITE_GAMES.toString(), new HashSet<>())); gamesCacheHash = sharedPref.getInt(CACHE_GAMES_HASH.toString(), 0); @@ -103,13 +110,12 @@ public static Set getFavoriteGamesList() { public static void addFavoriteGame(Game game) { // Update user's preferences - favoriteGamesList.add(game.getTitle()); - + favoriteGamesList.add(game.getKey()); setFavoriteGamesList(favoriteGamesList); } public static void removeAFavoriteGame(Game game) { - favoriteGamesList.remove(game.getTitle()); + favoriteGamesList.remove(game.getKey()); setFavoriteGamesList(favoriteGamesList); } @@ -197,16 +203,6 @@ public static void setFastForwardMode(int i) { editor.commit(); } - public static int getFastForwardMultiplier() { - return fastForwardMultiplier; - } - - public static void setFastForwardMultiplier(int i) { - fastForwardMultiplier = i; - editor.putInt(SettingsEnum.FAST_FORWARD_MULTIPLIER.toString(), i); - editor.commit(); - } - public static boolean isVibrateWhenSlidingDirectionEnabled() { return vibrateWhenSlidingDirectionEnabled; } @@ -324,6 +320,15 @@ public static Uri getRTPFolderURI(Context context) { } } + public static Uri getFontsFolderURI(Context context) { + DocumentFile easyRPGFolder = Helper.getFileFromURI(context, easyRPGFolderURI); + if (easyRPGFolder != null) { + return Helper.findFileUri(context, easyRPGFolder.getUri(), FONTS_FOLDER_NAME); + } else { + return null; + } + } + public static Uri getSoundFontsFolderURI(Context context) { DocumentFile easyRPGFolder = Helper.getFileFromURI(context, easyRPGFolderURI); if (easyRPGFolder != null) { @@ -333,27 +338,93 @@ public static Uri getSoundFontsFolderURI(Context context) { } } - public static Uri getSoundFountFileURI(Context context) { - if (soundFountFileURI == null) { - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); - String soundfontURI = sharedPref.getString(SettingsEnum.SOUNDFONT_URI.toString(), ""); + public static int getFont1Size() { + return font1Size; + } + + public static void setFont1Size(int i) { + font1Size = i; + configIni.engine.set(FONT1_SIZE.toString(), i); + configIni.save(); + } + + public static int getFont2Size() { + return font2Size; + } + + public static void setFont2Size(int i) { + font2Size = i; + configIni.engine.set(FONT2_SIZE.toString(), i); + configIni.save(); + } + + public static Uri getSoundFontFileURI() { + if (soundFontFileURI == null) { + String soundfontURI = configIni.audio.getString(SOUNDFONT_URI.toString(), ""); if (soundfontURI.isEmpty()) { - soundFountFileURI = null; + soundFontFileURI = null; } else { - soundFountFileURI = Uri.parse(soundfontURI); + soundFontFileURI = Uri.parse(soundfontURI); } } - return soundFountFileURI; + return soundFontFileURI; } - public static void setSoundFountFileURI(Uri soundFountFileURI) { + public static void setSoundFontFileURI(Uri soundFontFileURI) { String st = ""; - SettingsManager.soundFountFileURI = soundFountFileURI; - if (soundFountFileURI != null) { - st = soundFountFileURI.toString(); + SettingsManager.soundFontFileURI = soundFontFileURI; + if (soundFontFileURI != null) { + configIni.audio.set(SOUNDFONT_URI.toString(), soundFontFileURI.toString()); + } else { + configIni.audio.set(SOUNDFONT_URI.toString(), ""); } - editor.putString(SettingsEnum.SOUNDFONT_URI.toString(), st); - editor.commit(); + configIni.save(); + } + + public static Uri getFont1FileURI() { + if (font1FileURI == null) { + String fontURI = configIni.engine.getString(FONT1_URI.toString(), ""); + if (fontURI.isEmpty()) { + font1FileURI = null; + } else { + font1FileURI = Uri.parse(fontURI); + } + } + return font1FileURI; + } + + public static void setFont1FileURI(Uri fontFileURI) { + String st = ""; + SettingsManager.font1FileURI = fontFileURI; + if (fontFileURI != null) { + configIni.engine.set(FONT1_URI.toString(), fontFileURI.toString()); + } else { + configIni.engine.set(FONT1_URI.toString(), ""); + } + configIni.save(); + } + + public static Uri getFont2FileURI() { + if (font2FileURI == null) { + String fontURI = configIni.engine.getString(FONT2_URI.toString(), ""); + if (fontURI.isEmpty()) { + font2FileURI = null; + } else { + font2FileURI = Uri.parse(fontURI); + } + } + return font2FileURI; + } + + public static void setFont2FileURI(Uri fontFileURI) { + String st = ""; + SettingsManager.font2FileURI = fontFileURI; + if (fontFileURI != null) { + configIni.engine.set(FONT2_URI.toString(), fontFileURI.toString()); + } else { + configIni.engine.set(FONT2_URI.toString(), ""); + } + configIni.save(); } public static InputLayout getInputLayoutHorizontal(Activity activity) { @@ -391,11 +462,30 @@ public static void setInputLayoutVertical(Activity activity, InputLayout inputLa } public static Encoding getGameEncoding(Game game) { - return Encoding.regionCodeToEnum(pref.getString(game.getTitle() + "_Encoding", "")); + return Encoding.regionCodeToEnum(pref.getString(game.getKey() + "_Encoding", "")); } public static void setGameEncoding(Game game, Encoding encoding) { - editor.putString(game.getTitle() + "_Encoding", encoding.getRegionCode()); + editor.putString(game.getKey() + "_Encoding", encoding.getRegionCode()); editor.commit(); } + + public static String getCustomGameTitle(Game game) { + return pref.getString(game.getKey() + "_Title", ""); + } + + public static void setCustomGameTitle(Game game, String customTitle) { + editor.putString(game.getKey() + "_Title", customTitle); + editor.commit(); + } + + public static int getSpeedModifierA() { + return speedModifierA; + } + + public static void setSpeedModifierA(int speedModifierA) { + SettingsManager.speedModifierA = speedModifierA; + configIni.input.set(SPEED_MODIFIER_A.toString(), speedModifierA); + configIni.save(); + } } diff --git a/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index 5310d6016..21a1c1d18 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -277,6 +277,7 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa 0x044f, // Thrustmaster 0x045e, // Microsoft 0x0738, // Mad Catz + 0x0b05, // ASUS 0x0e6f, // PDP 0x0f0d, // Hori 0x10f5, // Turtle Beach @@ -285,6 +286,7 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa 0x24c6, // PowerA 0x2dc8, // 8BitDo 0x2e24, // Hyperkin + 0x3537, // GameSir }; if (usbInterface.getId() == 0 && @@ -358,6 +360,12 @@ private void connectHIDDeviceUSB(UsbDevice usbDevice) { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); + if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ && + mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT"); + return; + } + if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); @@ -583,7 +591,13 @@ public boolean openDevice(int deviceID) { } else { flags = 0; } - mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); + if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) { + Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION); + intent.setPackage(mContext.getPackageName()); + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags)); + } else { + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); + } } catch (Exception e) { Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); HIDDeviceOpenResult(deviceID, false); diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDL.java b/builds/android/app/src/main/java/org/libsdl/app/SDL.java index 44c21c1c7..139be9d15 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDL.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDL.java @@ -38,6 +38,10 @@ public static Context getContext() { } public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + loadLibrary(libraryName, mContext); + } + + public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException { if (libraryName == null) { throw new NullPointerException("No library name provided."); @@ -53,10 +57,10 @@ public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, // To use ReLinker, just add it as a dependency. For more information, see // https://github.com/KeepSafe/ReLinker for ReLinker's repository. // - Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); - Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); - Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); - Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); + Class relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); + Class relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); + Class contextClass = context.getClassLoader().loadClass("android.content.Context"); + Class stringClass = context.getClassLoader().loadClass("java.lang.String"); // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if // they've changed during updates. @@ -66,7 +70,7 @@ public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, // Actually load the library! Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); - loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); + loadMethod.invoke(relinkInstance, context, libraryName, null, null); } catch (final Throwable e) { // Fall back diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java index e1f5e8434..77053ba71 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -62,8 +62,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; - private static final int SDL_MINOR_VERSION = 28; - private static final int SDL_MICRO_VERSION = 5; + private static final int SDL_MINOR_VERSION = 30; + private static final int SDL_MICRO_VERSION = 6; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -276,14 +276,14 @@ protected String[] getLibraries() { // "SDL2_mixer", // "SDL2_net", // "SDL2_ttf", - "easyrpg_android" + "main" }; } // Load the .so public void loadLibraries() { for (String lib : getLibraries()) { - SDL.loadLibrary(lib); + SDL.loadLibrary(lib, this); } } @@ -1004,8 +1004,8 @@ public void setOrientationBis(int w, int h, boolean resizable, String hint) /* No valid hint, nothing is explicitly allowed */ if (!is_portrait_allowed && !is_landscape_allowed) { if (resizable) { - /* All orientations are allowed */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + /* All orientations are allowed, respecting user orientation lock setting */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Fixed window and nothing specified. Get orientation from w/h of created window */ req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); @@ -1014,8 +1014,8 @@ public void setOrientationBis(int w, int h, boolean resizable, String hint) /* At least one orientation is allowed */ if (resizable) { if (is_portrait_allowed && is_landscape_allowed) { - /* hint allows both landscape and portrait, promote to full sensor */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + /* hint allows both landscape and portrait, promote to full user */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java index d6913f157..9d8b20b7b 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -546,13 +546,15 @@ public void pollHapticDevices() { if (haptic == null) { InputDevice device = InputDevice.getDevice(deviceIds[i]); Vibrator vib = device.getVibrator(); - if (vib.hasVibrator()) { - haptic = new SDLHaptic(); - haptic.device_id = deviceIds[i]; - haptic.name = device.getName(); - haptic.vib = vib; - mHaptics.add(haptic); - SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + if (vib != null) { + if (vib.hasVibrator()) { + haptic = new SDLHaptic(); + haptic.device_id = deviceIds[i]; + haptic.name = device.getName(); + haptic.vib = vib; + mHaptics.add(haptic); + SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + } } } } diff --git a/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java b/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java index d894c9791..0857e4b6f 100644 --- a/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/builds/android/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -354,11 +354,11 @@ public void onSensorChanged(SensorEvent event) { SDLActivity.onNativeOrientationChanged(newOrientation); } - /* EasyRPG modification: prevent movement to be input SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, y / SensorManager.GRAVITY_EARTH, event.values[2] / SensorManager.GRAVITY_EARTH); - */ + + } } diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_autorenew.png deleted file mode 100644 index 184d154f9..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_autorenew.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_settings.png deleted file mode 100644 index 23e0c73bf..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_action_settings.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_content_clear.png deleted file mode 100644 index 85f7b680e..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_content_clear.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_black.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_black.png deleted file mode 100644 index f350724d3..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_white.png deleted file mode 100644 index e09bac46e..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_off_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 2e1df8b4d..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_white.png deleted file mode 100644 index ff9d1dc74..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_favorite_on_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_black.png deleted file mode 100755 index dffd0a4fe..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_white.png deleted file mode 100644 index d2b6e3e69..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_action_settings_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png deleted file mode 100644 index 1a9cd75a0..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_clear_black_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index f7d4fecf5..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_folder_open_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index 785307a7e..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_gamepad_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png deleted file mode 100644 index ffa7be933..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png deleted file mode 100644 index 97ded33b5..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_video_label_black_36dp.png deleted file mode 100644 index eb0aeb43c..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_video_label_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-hdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-hdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index 757ee1a07..000000000 Binary files a/builds/android/app/src/main/res/drawable-hdpi/ic_volume_up_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_action_autorenew.png deleted file mode 100644 index c122e7330..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_action_autorenew.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_action_settings.png deleted file mode 100644 index 52d23eadd..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_action_settings.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_content_clear.png deleted file mode 100644 index 14bbbbf0e..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_content_clear.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_black.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_black.png deleted file mode 100644 index 5c06d3988..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_white.png deleted file mode 100644 index f9fd017bd..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_off_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 444f95645..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_white.png deleted file mode 100644 index 4014ef3bf..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_favorite_on_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_black.png deleted file mode 100755 index 789f4e769..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_white.png deleted file mode 100644 index a17c9773a..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_action_settings_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png deleted file mode 100644 index 40a1a84e3..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_clear_black_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index 4f90c6310..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_folder_open_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index 16d509055..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_gamepad_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 97e42b525..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png deleted file mode 100644 index 8909c3553..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_video_label_black_36dp.png deleted file mode 100644 index e5faf83bd..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_video_label_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-mdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-mdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index 159d9c278..000000000 Binary files a/builds/android/app/src/main/res/drawable-mdpi/ic_volume_up_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_autorenew.png deleted file mode 100644 index 2d3f96c4b..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_autorenew.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_settings.png deleted file mode 100644 index f1d378050..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_action_settings.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_content_clear.png deleted file mode 100644 index 972993e41..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_content_clear.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_black.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_black.png deleted file mode 100644 index cd35cfd55..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_white.png deleted file mode 100644 index 64f0ef6da..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_off_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 0c2ef9301..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_white.png deleted file mode 100644 index f38e4aa90..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_favorite_on_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_black.png deleted file mode 100755 index 1efa404f3..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_white.png deleted file mode 100644 index 03ebf2f7c..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_action_settings_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png deleted file mode 100644 index 6bc437298..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index ac4de2caf..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_folder_open_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index d3d33362c..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_gamepad_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 1989184b1..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png deleted file mode 100644 index 5caedc8e5..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_video_label_black_36dp.png deleted file mode 100644 index d593d30aa..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_video_label_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xhdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-xhdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index c1e9affba..000000000 Binary files a/builds/android/app/src/main/res/drawable-xhdpi/ic_volume_up_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_autorenew.png deleted file mode 100644 index 0b322ffb9..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_autorenew.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_settings.png deleted file mode 100644 index 7cb7726a1..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_action_settings.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_content_clear.png deleted file mode 100644 index 0b295f3de..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_content_clear.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_black.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_black.png deleted file mode 100644 index 273a508d9..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_white.png deleted file mode 100644 index 63b98a2ad..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_off_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 8bc0088da..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_white.png deleted file mode 100644 index 72bd1b81a..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_favorite_on_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_black.png deleted file mode 100755 index 6c9dd340a..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_white.png deleted file mode 100644 index 8fe9e5f95..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_action_settings_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png deleted file mode 100644 index 51b4401ca..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index 1eb3365e0..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_folder_open_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index c32339a09..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_gamepad_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 1692d8a24..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png deleted file mode 100644 index eabb0a2ba..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_video_label_black_36dp.png deleted file mode 100644 index 700d0bcbd..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_video_label_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxhdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-xxhdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index 9242456f3..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxhdpi/ic_volume_up_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_autorenew.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_autorenew.png deleted file mode 100644 index ea913dbc4..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_autorenew.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_settings.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_settings.png deleted file mode 100644 index 6f4ae03ff..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_action_settings.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_content_clear.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_content_clear.png deleted file mode 100644 index 94b505a40..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_content_clear.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_black.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_black.png deleted file mode 100644 index 06b126805..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_white.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_white.png deleted file mode 100644 index 7b203e489..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_off_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_black.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_black.png deleted file mode 100644 index 08e80657a..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_white.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_white.png deleted file mode 100644 index 52dca5aa1..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_favorite_on_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_black.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_black.png deleted file mode 100755 index 708dd7b55..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_black.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_white.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_white.png deleted file mode 100644 index a966d5edb..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_action_settings_white.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png deleted file mode 100644 index df42feecb..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_folder_open_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_folder_open_black_36dp.png deleted file mode 100644 index 11bc67e56..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_folder_open_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_gamepad_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_gamepad_black_36dp.png deleted file mode 100644 index f075f37f1..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_gamepad_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index f5beca251..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png deleted file mode 100644 index 507c5edd4..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_video_label_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_video_label_black_36dp.png deleted file mode 100644 index 769f4b95d..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_video_label_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_volume_up_black_36dp.png b/builds/android/app/src/main/res/drawable-xxxhdpi/ic_volume_up_black_36dp.png deleted file mode 100644 index c44990e20..000000000 Binary files a/builds/android/app/src/main/res/drawable-xxxhdpi/ic_volume_up_black_36dp.png and /dev/null differ diff --git a/builds/android/app/src/main/res/drawable/ic_action_autorenew.xml b/builds/android/app/src/main/res/drawable/ic_action_autorenew.xml new file mode 100644 index 000000000..eb61bab5e --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_autorenew.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml new file mode 100644 index 000000000..0660d17a3 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml new file mode 100644 index 000000000..cd319196e --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_off_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml new file mode 100644 index 000000000..c40df29b4 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml new file mode 100644 index 000000000..d4e7acf83 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_action_favorite_on_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_article_black.xml b/builds/android/app/src/main/res/drawable/ic_article_black.xml new file mode 100644 index 000000000..65aa6380f --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_article_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_folder_open_black.xml b/builds/android/app/src/main/res/drawable/ic_folder_open_black.xml new file mode 100644 index 000000000..147578267 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_folder_open_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_gamepad_black.xml b/builds/android/app/src/main/res/drawable/ic_gamepad_black.xml new file mode 100644 index 000000000..726cd8946 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_gamepad_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_home_dark.xml b/builds/android/app/src/main/res/drawable/ic_home_dark.xml new file mode 100644 index 000000000..020b4a395 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_home_dark.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_info.xml b/builds/android/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 000000000..fbd717f75 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_refresh_white.xml b/builds/android/app/src/main/res/drawable/ic_refresh_white.xml new file mode 100644 index 000000000..1c312cf67 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_refresh_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_settings_black.xml b/builds/android/app/src/main/res/drawable/ic_settings_black.xml new file mode 100644 index 000000000..ab81e9457 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_settings_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_settings_white.xml b/builds/android/app/src/main/res/drawable/ic_settings_white.xml new file mode 100644 index 000000000..207639d9d --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_settings_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_video_label_black.xml b/builds/android/app/src/main/res/drawable/ic_video_label_black.xml new file mode 100644 index 000000000..5532709f0 --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_video_label_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/drawable/ic_volume_up_black.xml b/builds/android/app/src/main/res/drawable/ic_volume_up_black.xml new file mode 100644 index 000000000..64f91a05b --- /dev/null +++ b/builds/android/app/src/main/res/drawable/ic_volume_up_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/builds/android/app/src/main/res/layout/activity_settings_audio.xml b/builds/android/app/src/main/res/layout/activity_settings_audio.xml index 41920d092..e2274a949 100644 --- a/builds/android/app/src/main/res/layout/activity_settings_audio.xml +++ b/builds/android/app/src/main/res/layout/activity_settings_audio.xml @@ -89,11 +89,6 @@ android:layout_height="wrap_content" android:text="@string/settings_soundfont_explanation" android:textSize="18sp" /> - -