1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Merge branch 'develop' into feature/nullkiller2

This commit is contained in:
Mircea TheHonestCTO
2025-09-01 04:26:01 +02:00
122 changed files with 1572 additions and 14857 deletions

View File

@@ -17,7 +17,7 @@ jobs:
matrix:
include:
- platform: mac-intel
os: macos-13
os: macos-14
pack: 1
upload: 1
pack_type: Release
@@ -26,11 +26,11 @@ jobs:
preset: macos-conan-ninja-release
conan_profile: macos-intel
conan_prebuilts: dependencies-mac-intel
conan_options: --options with_apple_system_libs=True
conan_options: --profile=dependencies/conan_profiles/base/apple-system
artifact_platform: intel
- platform: mac-arm
os: macos-13
os: macos-14
pack: 1
upload: 1
pack_type: Release
@@ -39,11 +39,11 @@ jobs:
preset: macos-arm-conan-ninja-release
conan_profile: macos-arm
conan_prebuilts: dependencies-mac-arm
conan_options: --options with_apple_system_libs=True
conan_options: --profile=dependencies/conan_profiles/base/apple-system
artifact_platform: arm
- platform: ios
os: macos-13
os: macos-14
pack: 1
upload: 1
pack_type: Release
@@ -52,7 +52,7 @@ jobs:
preset: ios-release-conan-ccache
conan_profile: ios-arm64
conan_prebuilts: dependencies-ios
conan_options: --options with_apple_system_libs=True
conan_options: --profile=dependencies/conan_profiles/base/apple-system
- platform: msvc-x64
arch: x64
@@ -61,8 +61,10 @@ jobs:
upload: 0
pack_type: RelWithDebInfo
extension: zip
before_install: msvc.sh
preset: windows-msvc-ninja-release
conan_profile: msvc-x64
conan_prebuilts: dependencies-windows-x64
conan_options: -s "&:build_type=RelWithDebInfo" -c tools.env.virtualenv:powershell=pwsh -o "&:target_pre_windows10=True"
artifact_platform: x64
cl: Hostx64/x64/cl.exe
@@ -73,8 +75,10 @@ jobs:
upload: 0
pack_type: RelWithDebInfo
extension: zip
before_install: msvc.sh
preset: windows-msvc-ninja-release-x86
conan_profile: msvc-x86
conan_prebuilts: dependencies-windows-x86
conan_options: -s "&:build_type=RelWithDebInfo" -c tools.env.virtualenv:powershell=pwsh -o "&:target_pre_windows10=True"
artifact_platform: x86
cl: Hostx64/x86/cl.exe
@@ -85,55 +89,43 @@ jobs:
upload: 0
pack_type: RelWithDebInfo
extension: zip
before_install: msvc.sh
preset: windows-msvc-ninja-release-arm64
conan_profile: msvc-arm64
conan_prebuilts: dependencies-windows-arm64
conan_options: -s "&:build_type=RelWithDebInfo" -c tools.env.virtualenv:powershell=pwsh -o "&:lua_lib=lua"
artifact_platform: arm64
cl: HostARM64/ARM64/cl.exe
- platform: mingw_x86_64
arch: x86_64
os: ubuntu-24.04
pack: 1
pack_type: Release
extension: zip
cmake_args: -G Ninja
before_install: mingw.sh
preset: windows-mingw-conan-linux
conan_profile: mingw64-linux.jinja
conan_prebuilts: dependencies-mingw-x86-64
- platform: mingw_x86
arch: x86
os: ubuntu-24.04
pack: 1
pack_type: Release
extension: zip
cmake_args: -G Ninja
before_install: mingw.sh
preset: windows-mingw-conan-linux
conan_profile: mingw32-linux.jinja
conan_prebuilts: dependencies-mingw-x86
- platform: android-32
os: ubuntu-24.04
os: ubuntu-latest
upload: 1
extension: apk
preset: android-conan-ninja-release
before_install: android.sh
conan_profile: android-32-ndk
conan_prebuilts: dependencies-android-armeabi-v7a
conan_options: --profile=dependencies/conan_profiles/base/android-system
artifact_platform: armeabi-v7a
- platform: android-64
os: ubuntu-24.04
os: ubuntu-latest
upload: 1
extension: apk
preset: android-conan-ninja-release
before_install: android.sh
conan_profile: android-64-ndk
conan_prebuilts: dependencies-android-arm64-v8a
conan_options: --profile=dependencies/conan_profiles/base/android-system
artifact_platform: arm64-v8a
- platform: android-64-intel
os: ubuntu-latest
upload: 1
extension: apk
preset: android-conan-ninja-release
conan_profile: android-x64-ndk
conan_prebuilts: dependencies-android-x64
conan_options: --profile=dependencies/conan_profiles/base/android-system
artifact_platform: x64
runs-on: ${{ matrix.os }}
# Allow non-MSVC builds to fail without failing whole job
# This keeps pipeline moving so Windows Installer job can still run
@@ -148,11 +140,11 @@ jobs:
uses: actions/checkout@v5
with:
submodules: recursive
- name: Prepare APT staging dir
if: contains(matrix.os, 'ubuntu')
run: mkdir -p "$RUNNER_TEMP/apt-cache"
- name: APT cache restore
if: contains(matrix.os, 'ubuntu')
id: aptcache
@@ -166,7 +158,7 @@ jobs:
- name: Prepare CI
if: "${{ matrix.before_install != '' }}"
run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}' '${{matrix.arch}}'
# Save only on cache miss, GitHub caches are immutable per key
- name: APT cache save
if: contains(matrix.os, 'ubuntu') && steps.aptcache.outputs.cache-hit != 'true'
@@ -177,11 +169,9 @@ jobs:
- name: Install Conan Dependencies
if: "${{ matrix.conan_prebuilts != '' }}"
run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}'
- name: Install vcpkg Dependencies
if: ${{ startsWith(matrix.platform, 'msvc') }}
run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}' 'vcpkg'
run: |
pipx install conan
source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}'
- name: Setup MSVC Developer Command Prompt
if: ${{ startsWith(matrix.platform, 'msvc') }}
@@ -206,7 +196,7 @@ jobs:
max-size: '5G'
verbose: 2
job-summary: "" # <-- disable built-in summary to avoid duplicate block
- name: Setup compiler cache for branch builds
uses: hendrikmuhs/ccache-action@v1.2
if: ${{ github.event.number == '' }}
@@ -220,7 +210,7 @@ jobs:
max-size: '5G'
verbose: 2
job-summary: "" # <-- disable built-in summary to avoid duplicate block
- name: CCache tuning (Android)
if: ${{ startsWith(matrix.platform, 'android') }}
run: |
@@ -230,33 +220,36 @@ jobs:
ccache --set-config=hash_dir=true
ccache --set-config=sloppiness=time_macros
- name: Install Conan
if: "${{ matrix.conan_profile != '' }}"
run: pipx install 'conan<2.0'
- name: Install Conan profile
if: "${{ matrix.conan_profile != '' }}"
run: |
conan profile new default --detect
conan profile detect
conan install . \
--install-folder=conan-generated \
--no-imports \
--output-folder=conan-generated \
--build=never \
--profile:build=default \
--profile:host=CI/conan/${{ matrix.conan_profile }} \
--profile=dependencies/conan_profiles/${{ matrix.conan_profile }} \
${{ matrix.conan_options }}
env:
GENERATE_ONLY_BUILT_CONFIG: 1
# Can't be set in Gradle project
- name: Configure enableUncompressedNativeLibs
if: ${{ startsWith(matrix.platform, 'android') }}
run: mkdir -p ~/.gradle && echo "android.bundle.enableUncompressedNativeLibs=true" > ~/.gradle/gradle.properties
# Workaround for gradle not discovering SDK that was installed via conan
- name: Find Android NDK
# Workaround for gradle not discovering SDK that was installed via Сonan
- name: Link Android NDK from Conan cache to Android SDK path
if: ${{ startsWith(matrix.platform, 'android') }}
run: sudo ln -s -T /home/runner/.conan/data/android-ndk/r25c/_/_/package/4db1be536558d833e52e862fd84d64d75c2b3656/bin /usr/local/lib/android/sdk/ndk/25.2.9519653
run: |
ndkPackage='android-ndk'
hexRegex='[[:xdigit:]]+'
ndkPackageRevision=$(conan list "$ndkPackage/*:*" --format=compact \
| egrep --only-matching "$ndkPackage/\\w+#$hexRegex:$hexRegex")
ndkPackagePath=$(conan cache path "$ndkPackageRevision")
# format: Pkg.Revision = 25.2.9519653
ndkPath="$ndkPackagePath/bin"
ndkVersion=$(fgrep 'Pkg.Revision' "$ndkPath/source.properties" | cut -d ' ' -f 3)
ln -s -T "$ndkPath" "$ANDROID_HOME/ndk/$ndkVersion"
- name: Install Java
uses: actions/setup-java@v5
@@ -266,6 +259,7 @@ jobs:
java-version: '17'
# a hack to build ID for x64 build in order for Google Play to allow upload of both 32 and 64 bit builds
# TODO: x86_64
- name: Bump Android x64 build ID
if: ${{ matrix.platform == 'android-64' }}
run: perl -i -pe 's/versionCode (\d+)/$x=$1+1; "versionCode $x"/e' android/vcmi-app/build.gradle
@@ -283,27 +277,33 @@ jobs:
env:
PULL_REQUEST: ${{ github.event.pull_request.number }}
- name: Configure
- name: Configure (non-MSVC)
if: ${{ !startsWith(matrix.platform, 'msvc') }}
run: |
if [[ ("${{ matrix.preset }}" == "android-conan-ninja-release") && ("${{ github.ref }}" != 'refs/heads/master') ]]; then
cmake -DENABLE_CCACHE:BOOL=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily;applicationVariant=daily" \
--preset ${{ matrix.preset }}
elif ${{ startsWith(matrix.platform, 'msvc') }}; then
CL="$VCToolsInstallDir/bin/${{ matrix.cl }}"
cmake \
-D CMAKE_C_COMPILER:FILEPATH="$CL" \
-D CMAKE_CXX_COMPILER:FILEPATH="$CL" \
--preset ${{ matrix.preset }}
else
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
# key1=value1;key2=value2;...
gradleProperties=$(python3 CI/android/gradle_daily_props.py)
androidOptions=("-DANDROID_GRADLE_PROPERTIES=$gradleProperties")
androidOptions+=("-DCMAKE_C_COMPILER_LAUNCHER=ccache" "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache")
fi
cmake -DENABLE_CCACHE:BOOL=ON "${androidOptions[@]}" --preset ${{ matrix.preset }}
- name: Configure (MSVC)
if: ${{ startsWith(matrix.platform, 'msvc') }}
run: |
& conan-generated\conanrun.ps1
$CL = "$($env:VCToolsInstallDir)/bin/${{ matrix.cl }}"
cmake `
-D "CMAKE_C_COMPILER:FILEPATH=$CL" `
-D "CMAKE_CXX_COMPILER:FILEPATH=$CL" `
--preset ${{ matrix.preset }}
shell: pwsh
- name: Build
run: |
${{ startsWith(matrix.platform, 'msvc') && '& conan-generated\conanrun.ps1' }}
cmake --build --preset ${{matrix.preset}}
shell: pwsh
env:
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
@@ -320,15 +320,10 @@ jobs:
id: cpack
if: ${{ matrix.pack == 1 }}
run: |
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
# Workaround for CPack bug on macOS 13
counter=0
until cpack -C ${{matrix.pack_type}} || ((counter > 20)); do
sleep 3
((counter++))
done
rm -rf _CPack_Packages
${{ startsWith(matrix.platform, 'msvc') && '& conan-generated\conanrun.ps1' }}
cd "${{github.workspace}}/out/build/${{matrix.preset}}"
cpack -C ${{matrix.pack_type}}
shell: pwsh
- name: Find Android package
if: ${{ startsWith(matrix.platform, 'android') }}
@@ -410,14 +405,14 @@ jobs:
run: |
source '${{github.workspace}}/CI/get_package_name.sh'
echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV
- name: Create source code archive (including submodules)
run: |
git archive HEAD -o "release.tar" --worktree-attributes -v
git submodule update --init --recursive
git submodule --quiet foreach 'cd "$toplevel"; tar -rvf "release.tar" "$sm_path"'
gzip release.tar
- name: Upload source code archive
id: upload_source
uses: actions/upload-artifact@v4
@@ -434,7 +429,7 @@ jobs:
cat > .summary/source.json <<JSON
{"source_url": "${{ steps.upload_source.outputs.artifact-url }}"}
JSON
- name: Upload partial JSON with source informations
uses: actions/upload-artifact@v4
with:
@@ -477,7 +472,7 @@ jobs:
compiler_cxx: clang++-13
compiler_cc: clang-13
preset: linux-clang-test
runs-on: ${{ matrix.os }}
defaults:
run:
@@ -504,7 +499,7 @@ jobs:
${{ matrix.platform }}-apt-${{ matrix.os }}
- name: Prepare CI
run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}'
run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}' '${{matrix.arch}}'
- name: APT cache save
if: contains(matrix.os, 'ubuntu') && steps.aptcache.outputs.cache-hit != 'true'
@@ -549,7 +544,7 @@ jobs:
7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD
mkdir -p ~/.local/share/vcmi/
mv h3_assets/* ~/.local/share/vcmi/
- name: Configure
run: |
cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=${{ matrix.compiler_cc }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler_cxx }} --preset ${{ matrix.preset }}
@@ -595,7 +590,6 @@ jobs:
- name: Extract version info
id: extract-version
shell: bash
run: |
filePath="${GITHUB_WORKSPACE}/cmake_modules/VersionDefinition.cmake"
@@ -609,8 +603,8 @@ jobs:
echo "short_version=${short_version}" >> "$GITHUB_OUTPUT"
echo "version_timestamp=${version_timestamp}" >> "$GITHUB_OUTPUT"
- name: Install ucrt Dependencies
run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}' 'ucrt'
- name: Download UCRT
run: source '${{github.workspace}}/CI/wininstaller/download_ucrt.sh' '${{matrix.platform}}'
- name: Build Number
run: |
@@ -630,13 +624,11 @@ jobs:
path: ${{github.workspace}}/artifact
- name: Extract Artifact
shell: bash
run: |
mkdir artifact/extracted
unzip "artifact/${{ env.VCMI_PACKAGE_FILE_NAME }}.zip" -d artifact/extracted
- name: Ensure Inno Setup is installed
shell: bash
run: |
if [ ! -f "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" ] && [ ! -f "/c/ProgramData/Chocolatey/bin/ISCC.exe" ]; then
choco install innosetup --no-progress -y
@@ -728,7 +720,7 @@ jobs:
steps:
- name: Checkout (for script path)
uses: actions/checkout@v5
- name: Download all partial JSON artifacts
continue-on-error: true
uses: actions/download-artifact@v5
@@ -736,7 +728,7 @@ jobs:
pattern: partial-json-*
merge-multiple: true
path: partials
- name: Run final summary
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,44 @@
name: Weekly Qt translation refresh
on:
schedule:
- cron: '0 0 * * 0' # Runs every Sunday at 00:00 UTC
workflow_dispatch:
permissions:
contents: write
jobs:
refresh:
name: Qt translations refresh
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
submodules: false
- name: Install Qt tools (lupdate)
run: |
sudo apt install -y --no-install-recommends qttools5-dev-tools
- name: Update TS files (Launcher)
working-directory: launcher
run: |
lupdate . -ts translation/*.ts
- name: Update TS files (Map Editor)
working-directory: mapeditor
run: |
lupdate . -ts translation/*.ts
- name: Commit and push translation updates
uses: EndBug/add-and-commit@v9
with:
add: "launcher/translation mapeditor/translation"
default_author: github_actions
message: "Auto-update VCMI Qt translation files"
push: true

2
.gitignore vendored
View File

@@ -56,8 +56,6 @@ fuzzylite.pc
/client/RD
/launcher/RD
/lib/RD
/lib/minizip/RD
/lib/minizip/*/RD
/scripting/erm/RD
/scripting/erm/*/RD
/server/RD

3
.gitmodules vendored
View File

@@ -10,3 +10,6 @@
path = launcher/lib/innoextract
url = https://github.com/vcmi/innoextract.git
branch = vcmi
[submodule "dependencies"]
path = dependencies
url = https://github.com/vcmi/vcmi-dependencies.git

View File

@@ -19,10 +19,6 @@ else()
set(fuzzylite_FOUND FALSE)
endif()
if(TARGET fuzzylite::fuzzylite AND MSVC)
install_vcpkg_imported_tgt(fuzzylite::fuzzylite)
endif()
if(NOT fuzzylite_FOUND)
set(FL_BUILD_BINARY OFF CACHE BOOL "")
set(FL_BUILD_SHARED OFF CACHE BOOL "")

View File

@@ -0,0 +1,7 @@
dic = {
"applicationIdSuffix": ".daily",
"applicationLabel": "VCMI daily",
"applicationVariant": "daily",
"signingConfig": "dailySigning",
}
print(";".join([f"{key}={value}" for key, value in dic.items()]))

View File

@@ -1,4 +0,0 @@
#!/bin/sh
sudo apt-get update
sudo apt-get install ninja-build

View File

@@ -24,4 +24,4 @@ sudo eatmydata apt -yq --no-install-recommends \
sudo rm -f "$APT_CACHE/lock" || true
sudo rm -rf "$APT_CACHE/partial" || true
sudo chown -R "$USER:$USER" "$APT_CACHE"
sudo chown -R "$USER:$USER" "$APT_CACHE"

View File

@@ -24,4 +24,4 @@ sudo eatmydata apt -yq --no-install-recommends \
sudo rm -f "$APT_CACHE/lock" || true
sudo rm -rf "$APT_CACHE/partial" || true
sudo chown -R "$USER:$USER" "$APT_CACHE"
sudo chown -R "$USER:$USER" "$APT_CACHE"

View File

@@ -1,3 +1,3 @@
#!/usr/bin/env bash
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
echo DEVELOPER_DIR=/Applications/Xcode_16.2.app >> $GITHUB_ENV

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
ARCH="${1:-x86_64}"
case "$ARCH" in
x86) triplet=i686-w64-mingw32 ;;
x86_64) triplet=x86_64-w64-mingw32 ;;
*) echo "Unsupported ARCH '$ARCH' (use: x86 | x86_64)"; exit 2 ;;
esac
APT_CACHE="${APT_CACHE:-${RUNNER_TEMP:-/tmp}/apt-cache}"
sudo mkdir -p "$APT_CACHE"
sudo apt -yq -o Acquire::Retries=3 update
sudo apt -yq install eatmydata
sudo eatmydata apt -yq --no-install-recommends \
-o Dir::Cache::archives="$APT_CACHE" \
-o APT::Keep-Downloaded-Packages=true \
-o Acquire::Retries=3 -o Dpkg::Use-Pty=0 \
install \
ninja-build nsis mingw-w64 g++-mingw-w64
if [[ -x "/usr/bin/${triplet}-g++-posix" ]]; then
sudo update-alternatives --set "${triplet}-g++" "/usr/bin/${triplet}-g++-posix"
fi
if [[ -x "/usr/bin/${triplet}-gcc-posix" ]]; then
sudo update-alternatives --set "${triplet}-gcc" "/usr/bin/${triplet}-gcc-posix"
fi
sudo rm -f "$APT_CACHE/lock" || true
sudo rm -rf "$APT_CACHE/partial" || true
sudo chown -R "$USER:$USER" "$APT_CACHE"

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
MSVC_INSTALL_PATH=$(vswhere -latest -property installationPath)
echo "MSVC_INSTALL_PATH = $MSVC_INSTALL_PATH"
echo "Installed toolset versions:"
ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC"
TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC/" | head -1)
DUMPBIN_PATH="$MSVC_INSTALL_PATH/VC/Tools/MSVC/$TOOLS_DIR/bin/Hostx64/x64/dumpbin.exe"
# This command should work as well, but for some reason it is *extremely* slow on the Github CI (~7 minutes)
#DUMPBIN_PATH=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
echo "TOOLS_DIR = $TOOLS_DIR"
echo "DUMPBIN_PATH = $DUMPBIN_PATH"
dirname "$DUMPBIN_PATH" > "$GITHUB_PATH"

View File

@@ -1,5 +0,0 @@
include(base/android)
[settings]
arch=armv7
os.api_level=19

View File

@@ -1,4 +0,0 @@
include(android-32)
[tool_requires]
android-ndk/r25c

View File

@@ -1,5 +0,0 @@
include(base/android)
[settings]
arch=armv8
os.api_level=21

View File

@@ -1,4 +0,0 @@
include(android-64)
[tool_requires]
android-ndk/r25c

View File

@@ -1,10 +0,0 @@
[settings]
build_type=Release
compiler=clang
compiler.libcxx=c++_shared
compiler.version=14
os=Android
[buildenv]
# fixes shared libiconv build
LD=ld

View File

@@ -1,12 +0,0 @@
[settings]
compiler=apple-clang
compiler.version=14
compiler.libcxx=libc++
build_type=Release
# required for Boost.Locale in versions >= 1.81
compiler.cppstd=11
[conf]
tools.apple:enable_bitcode = False
tools.cmake.cmaketoolchain:generator = Ninja

View File

@@ -1,21 +0,0 @@
{% macro generate_env(target_host) -%}
CONAN_CROSS_COMPILE={{ target_host }}-
CHOST={{ target_host }}
AR={{ target_host }}-ar
AS={{ target_host }}-as
CC={{ target_host }}-gcc
CXX={{ target_host }}-g++
RANLIB={{ target_host }}-ranlib
STRIP={{ target_host }}-strip
{%- endmacro -%}
{% macro generate_env_win32(target_host) -%}
CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/13-posix/
RC={{ target_host }}-windres
{%- endmacro -%}
{% macro generate_conf(target_host) -%}
tools.build:compiler_executables = {"c": "{{ target_host }}-gcc", "cpp": "{{ target_host }}-g++"}
tools.build:sysroot = /usr/{{ target_host }}
tools.build:defines = ["WINVER=0x0601", "_WIN32_WINNT=0x0601"]
{%- endmacro -%}

View File

@@ -1,10 +0,0 @@
[settings]
os=Windows
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=10
compiler.cppstd=11
build_type=Release
[conf]
tools.cmake.cmaketoolchain:generator = Ninja

View File

@@ -1,5 +0,0 @@
include(apple)
[settings]
os=iOS
os.sdk=iphoneos

View File

@@ -1,4 +0,0 @@
include(apple)
[settings]
os=Macos

View File

@@ -1,5 +0,0 @@
include(base/ios)
[settings]
os.version=12.0
arch=armv8

View File

@@ -1,8 +0,0 @@
include(base/ios)
[settings]
os.version=10.0
arch=armv7
# Xcode 13.x is the last version that can build for armv7
compiler.version=13

View File

@@ -1,5 +0,0 @@
include(base/macos)
[settings]
os.version=11.0
arch=armv8

View File

@@ -1,5 +0,0 @@
include(base/macos)
[settings]
os.version=10.13
arch=x86_64

View File

@@ -1,15 +0,0 @@
{% import 'base/cross-macro.j2' as cross -%}
include(base/cross-windows)
{% set target_host="i686-w64-mingw32" %}
[settings]
arch=x86
[conf]
{{ cross.generate_conf(target_host)}}
tools.build:cflags = ["-msse2"]
tools.build:cxxflags = ["-msse2"]
[env]
{{ cross.generate_env(target_host) }}
{{ cross.generate_env_win32(target_host) }}

View File

@@ -1,13 +0,0 @@
{% import 'base/cross-macro.j2' as cross -%}
include(base/cross-windows)
{% set target_host="x86_64-w64-mingw32" %}
[settings]
arch=x86_64
[conf]
{{ cross.generate_conf(target_host)}}
[env]
{{ cross.generate_env(target_host) }}
{{ cross.generate_env_win32(target_host) }}

View File

@@ -83,7 +83,7 @@ def arch_label(platform: str, arch_env: Optional[str]) -> str:
return arch_env
mapping = {
"mac-intel": "Intel",
"mac-arm": "ARM64",
"mac-arm": "Apple Silicon",
"ios": "ARM64",
"msvc-x64": "x64",
"msvc-x86": "x86",
@@ -92,6 +92,7 @@ def arch_label(platform: str, arch_env: Optional[str]) -> str:
"mingw_x86_64": "x64",
"android-32": "ARMv7",
"android-64": "ARM64",
"android-64-intel": "x86_64",
}
return mapping.get(platform, platform)

View File

@@ -184,7 +184,7 @@
// MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md040.md
"MD040": {
// List of languages
"allowed_languages": [ "cpp", "json", "sh", "text", "nix", "powershell", "lua" ],
"allowed_languages": [ "cpp", "json", "sh", "text", "nix", "powershell", "lua", "batchfile" ],
// Require language only
"language_only": true
},

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash
RELEASE_TAG="1.3"
FILENAME="$1"
DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME.txz"
RELEASE_TAG="2025-08-24"
FILENAME="$1.tgz"
DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME"
mkdir ~/.conan
cd ~/.conan
curl -L "$DOWNLOAD_URL" | tar -xf - --xz
downloadedFile="$RUNNER_TEMP/$FILENAME"
curl -Lo "$downloadedFile" "$DOWNLOAD_URL"
conan cache restore "$downloadedFile"

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
ACCOUNT="vcmi"
# Fetch latest release tag from GitHub API
# RELEASE_TAG=$(curl -s "https://api.github.com/repos/$ACCOUNT/vcmi-deps-windows/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
RELEASE_TAG="v1.9"
# 2. parameter: all | vcpkg | ucrt (default: all)
PART="${2:-all}"
# --- VCPKG ---
if [[ "$PART" == "all" || "$PART" == "vcpkg" ]]; then
DEP_FILENAME="dependencies-$1"
DEP_URL="https://github.com/$ACCOUNT/vcmi-deps-windows/releases/download/$RELEASE_TAG/$DEP_FILENAME.txz"
curl -L "$DEP_URL" | tar -xf - --xz
fi
# --- UCRT ---
if [[ "$PART" == "all" || "$PART" == "ucrt" ]]; then
UCRT_FILENAME="ucrtRedist-$1"
UCRT_URL="https://github.com/$ACCOUNT/vcmi-deps-windows/releases/download/$RELEASE_TAG/$UCRT_FILENAME.txz"
mkdir -p ucrt
curl -L "$UCRT_URL" | tar -xf - --xz -C ucrt
fi

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
RELEASE_TAG="v1.9"
UCRT_FILENAME="ucrtRedist-$1"
UCRT_URL="https://github.com/vcmi/vcmi-deps-windows/releases/download/$RELEASE_TAG/$UCRT_FILENAME.txz"
UCRT_DIR="ucrt"
mkdir -p "$UCRT_DIR"
curl -L "$UCRT_URL" | tar -xf - --xz -C "$UCRT_DIR"

View File

@@ -17,12 +17,8 @@ project(VCMI)
# It's used currently to make sure that 3rd-party dependencies in git submodules get proper FOLDER property
# - Make FindFuzzyLite check for the right version and disable FORCE_BUNDLED_FL by default
if(APPLE)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(APPLE_MACOS 1)
else()
set(APPLE_IOS 1)
endif()
if(APPLE AND NOT IOS)
set(MACOS 1)
endif()
if(POLICY CMP0142)
@@ -77,12 +73,12 @@ else()
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
endif()
if(APPLE_IOS)
if(IOS)
set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
endif()
if(APPLE_IOS OR ANDROID)
if(IOS OR ANDROID)
set(ENABLE_MONOLITHIC_INSTALL OFF)
set(ENABLE_SINGLE_APP_BUILD ON)
set(ENABLE_EDITOR OFF)
@@ -155,7 +151,7 @@ if(ENABLE_CCACHE)
endif()
endif()
if(ENABLE_CCACHE AND (CMAKE_GENERATOR STREQUAL "Xcode"))
if(ENABLE_CCACHE AND XCODE)
# https://stackoverflow.com/a/36515503/2278742
# Set up wrapper scripts
configure_file(xcode/launch-c.in xcode/launch-c)
@@ -183,7 +179,7 @@ include(VersionDefinition)
vcmi_print_important_variables()
# Options to enable folders in CMake generated projects for Visual Studio, Xcode, etc
# Very useful to put 3rd-party libraries such as Minizip, GoogleTest and FuzzyLite in their own folders
# Very useful to put 3rd-party libraries such as GoogleTest and FuzzyLite in their own folders
set_property(GLOBAL PROPERTY USE_FOLDERS TRUE)
# Make FOLDER property inheritable
# So when we set FOLDER property on AI directory all and targets inside will inherit it
@@ -287,7 +283,7 @@ if(ENABLE_GOLDMASTER)
add_definitions(-DENABLE_GOLDMASTER)
endif()
if(APPLE_IOS)
if(IOS)
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0)
@@ -303,14 +299,14 @@ if(APPLE_IOS)
set(CMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2")
endif()
if(APPLE_MACOS)
if(MACOS)
# Not supported by standard library of our current minimal target - macOS 10.14 or newer required
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-aligned-allocation")
endif()
if(MINGW OR MSVC)
# Windows Vista or newer for FuzzyLite 6 to compile
# Except for conan which already has this definition in its preset
# Except for conan which already has this definition in its profile
if(NOT USING_CONAN)
add_definitions(-D_WIN32_WINNT=0x0600)
endif()
@@ -373,14 +369,9 @@ if(MINGW OR MSVC)
# Reported to Microsoft here:
# https://developercommunity.visualstudio.com/content/problem/224597/linker-failing-because-of-multiple-definitions-of.html
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE")
# Required at least for compatibility with Boost 1.68 on Vcpkg
# psapi required for ARM64 builds
set(SYSTEM_LIBS ${SYSTEM_LIBS} bcrypt psapi)
endif(MSVC)
if(MINGW)
# Temporary (?) workaround for failing builds on MinGW CI due to bug in TBB
set(CMAKE_CXX_EXTENSIONS ON)
@@ -446,7 +437,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR NOT WIN32)
endif()
# Check if some platform-specific libraries are needed for linking
if(NOT WIN32 AND NOT APPLE_IOS)
if(NOT WIN32 AND NOT IOS)
include(CheckLibraryExists)
# Shared memory functions used by Boost.Interprocess
@@ -505,14 +496,7 @@ if(TARGET zlib::zlib)
add_library(ZLIB::ZLIB ALIAS zlib::zlib)
endif()
option(FORCE_BUNDLED_MINIZIP "Force bundled Minizip library" OFF)
if(NOT FORCE_BUNDLED_MINIZIP)
find_package(minizip)
if(TARGET minizip::minizip)
add_definitions(-DUSE_SYSTEM_MINIZIP)
endif()
endif()
find_package(minizip REQUIRED)
if (ENABLE_CLIENT)
if (ENABLE_VIDEO)
@@ -608,7 +592,7 @@ elseif(APPLE)
set(LIB_DIR "." CACHE STRING "Where to install main library")
set(DATA_DIR "." CACHE STRING "Where to install data files")
else()
if(APPLE_MACOS)
if(MACOS)
set(APP_BUNDLE_DIR "${CMAKE_PROJECT_NAME}.app")
set(APP_BUNDLE_CONTENTS_DIR "${APP_BUNDLE_DIR}/Contents")
set(APP_BUNDLE_BINARY_DIR "${APP_BUNDLE_CONTENTS_DIR}/MacOS")
@@ -626,6 +610,9 @@ elseif(ANDROID)
include(GNUInstallDirs)
set(LIB_DIR "libs/${ANDROID_ABI}")
file(READ "${CMAKE_ANDROID_NDK}/meta/abis.json" ndkAbiInfo)
string(JSON ANDROID_SYSROOT_LIB_SUBDIR GET "${ndkAbiInfo}" "${ANDROID_ABI}" "triple")
# required by Qt
set(androidPackageSourceDir "${CMAKE_SOURCE_DIR}/android")
set(androidQtBuildDir "${CMAKE_BINARY_DIR}/android-build")
@@ -666,7 +653,7 @@ else()
endif()
# iOS/Android have flat libs directory structure
if(APPLE_IOS OR ANDROID)
if(IOS OR ANDROID)
set(AI_LIB_DIR "${LIB_DIR}")
set(SCRIPTING_LIB_DIR "${LIB_DIR}")
else()
@@ -685,7 +672,7 @@ endif()
# Add subdirectories #
#######################################
if(APPLE_IOS)
if(IOS)
add_subdirectory(ios)
endif()
@@ -707,11 +694,6 @@ if(ENABLE_LUA)
add_subdirectory(scripting/lua)
endif()
if(NOT TARGET minizip::minizip)
add_subdirectory_with_folder("3rdparty" lib/minizip)
add_library(minizip::minizip ALIAS minizip)
endif()
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
add_subdirectory(vcmiqt)
endif()
@@ -746,6 +728,8 @@ endif()
# Installation section #
#######################################
vcmi_install_conan_deps()
if(ANDROID)
string(REPLACE ";" "\n" ANDROID_GRADLE_PROPERTIES_MULTILINE "${ANDROID_GRADLE_PROPERTIES}")
file(WRITE "${androidPackageSourceDir}/vcmi-app/gradle.properties" "signingRoot=${CMAKE_SOURCE_DIR}/CI/android\n${ANDROID_GRADLE_PROPERTIES_MULTILINE}")
@@ -767,66 +751,13 @@ if(ENABLE_LUA)
endif()
# that script is useless for Windows / iOS / Android
if(NOT WIN32 AND NOT APPLE_IOS AND NOT ANDROID)
if(NOT WIN32 AND NOT IOS AND NOT ANDROID)
install(FILES vcmibuilder DESTINATION ${BIN_DIR} PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
endif()
if(WIN32)
if(TBB_FOUND AND MSVC)
install_vcpkg_imported_tgt(TBB::tbb)
endif()
if(USING_CONAN)
#Conan imports enabled
vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
file(GLOB dep_files
${dep_files}
"${CMAKE_SYSROOT}/bin/*.dll"
"${CMAKE_SYSROOT}/lib/*.dll"
"${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_dw2-1.dll" # for 32-bit only?
"${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_seh-1.dll" # for 64-bit only?
"${CONAN_SYSTEM_LIBRARY_LOCATION}/libstdc++-6.dll")
else()
file(GLOB dep_files
${dep_files}
"${CMAKE_FIND_ROOT_PATH}/bin/*.dll")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# Copy debug versions of libraries if build type is debug
set(debug_postfix d)
endif()
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
get_target_property(QtCore_location Qt${QT_VERSION_MAJOR}::Core LOCATION)
get_filename_component(Qtbin_folder ${QtCore_location} PATH)
file(GLOB dep_files
${dep_files}
${Qtbin_folder}/Qt5Core${debug_postfix}.dll
${Qtbin_folder}/Qt5Gui${debug_postfix}.dll
${Qtbin_folder}/Qt5Widgets${debug_postfix}.dll
${Qtbin_folder}/Qt5Network${debug_postfix}.dll
${Qtbin_folder}/icu*.dll)
get_target_property(integration_type Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin TYPE)
if(NOT(integration_type STREQUAL "INTERFACE_LIBRARY"))
get_target_property(integration_loc Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin LOCATION)
install(
FILES ${integration_loc}
DESTINATION ${BIN_DIR}/platforms
)
install(
FILES "$<TARGET_FILE:Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin>"
DESTINATION ${BIN_DIR}/styles)
endif()
endif()
install(FILES ${dep_files} DESTINATION ${BIN_DIR})
endif(WIN32)
#######################################
# Packaging section #
#######################################
@@ -920,7 +851,7 @@ if(WIN32)
if(NOT (${CMAKE_CROSSCOMPILING}))
add_subdirectory(win)
endif()
elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
elseif(MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
set(CPACK_MONOLITHIC_INSTALL 1)
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/osx/dmg_background.png")
@@ -964,7 +895,7 @@ elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
# Bundle fixing code must be in separate directory to be executed after all other install code
add_subdirectory(osx)
elseif(APPLE_IOS)
elseif(IOS)
set(CPACK_GENERATOR ZIP)
set(CPACK_ARCHIVE_FILE_EXTENSION ipa)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)

View File

@@ -10,8 +10,7 @@
"name": "build-with-conan",
"hidden": true,
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan-generated/conan_toolchain.cmake",
"FORCE_BUNDLED_MINIZIP": "OFF"
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan-generated/conan_toolchain.cmake"
}
},
{
@@ -155,28 +154,30 @@
"name": "windows-msvc-release",
"displayName": "Windows x64 RelWithDebInfo",
"description": "VCMI RelWithDebInfo build",
"inherits": "default-release",
"inherits": [
"build-with-conan",
"default-release"
],
"generator": "Visual Studio 17 2022",
"architecture": {
"value": "x64",
"strategy": "set"
},
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
"FORCE_BUNDLED_MINIZIP": "ON"
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW"
}
},
{
"name": "windows-msvc-release-x86",
"displayName": "Windows x86 RelWithDebInfo",
"description": "VCMI RelWithDebInfo build",
"inherits": "default-release",
"inherits": [
"build-with-conan",
"default-release"
],
"generator": "Visual Studio 17 2022",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
"FORCE_BUNDLED_MINIZIP": "ON",
"CMAKE_GENERATOR_PLATFORM": "WIN32"
}
},
@@ -184,16 +185,17 @@
"name": "windows-msvc-release-arm64",
"displayName": "Windows ARM64 RelWithDebInfo",
"description": "VCMI Windows ARM64 build",
"inherits": "default-release",
"inherits": [
"build-with-conan",
"default-release"
],
"generator": "Visual Studio 17 2022",
"architecture": {
"value": "ARM64",
"strategy": "set"
},
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
"FORCE_BUNDLED_MINIZIP": "ON"
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW"
}
},
{
@@ -206,57 +208,38 @@
}
},
{
"name": "windows-msvc-ninja-release",
"displayName": "Windows x64 RelWithDebInfo (Ninja)",
"description": "VCMI RelWithDebInfo build using Ninja + sccache",
"inherits": "default-release",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
"FORCE_BUNDLED_MINIZIP": "ON",
"ENABLE_CCACHE": "ON",
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded",
"ENABLE_MULTI_PROCESS_BUILDS": "OFF",
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_C_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7",
"CMAKE_CXX_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7"
}
},
{
"name": "windows-msvc-ninja-release-x86",
"displayName": "Windows x86 RelWithDebInfo (Ninja)",
"description": "VCMI RelWithDebInfo build using Ninja + sccache (x86)",
"inherits": "windows-msvc-ninja-release",
"cacheVariables": {
"VCPKG_TARGET_TRIPLET": "x86-windows"
}
},
{
"name": "windows-msvc-ninja-release-arm64",
"displayName": "Windows ARM64 RelWithDebInfo (Ninja)",
"description": "VCMI RelWithDebInfo build using Ninja + sccache (ARM64)",
"inherits": "windows-msvc-ninja-release",
"cacheVariables": {
"VCPKG_TARGET_TRIPLET": "arm64-windows"
}
},
{
"name": "windows-mingw-conan-linux",
"displayName": "Ninja+Conan release",
"description": "VCMI Windows Ninja using Conan on Linux",
"name": "windows-msvc-ninja-release",
"displayName": "Windows x64 RelWithDebInfo (Ninja)",
"description": "VCMI RelWithDebInfo build using Ninja + sccache",
"inherits": [
"build-with-conan",
"default-release"
],
"generator": "Ninja",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"FORCE_BUNDLED_FL": "ON",
"ENABLE_TEMPLATE_EDITOR": "OFF"
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
"ENABLE_CCACHE": "ON",
"CMAKE_C_COMPILER_LAUNCHER": "sccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache",
"CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded",
"ENABLE_MULTI_PROCESS_BUILDS": "OFF",
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_C_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7",
"CMAKE_CXX_FLAGS_RELWITHDEBINFO": "/O2 /Ob1 /DNDEBUG /Z7"
}
},
{
"name": "windows-msvc-ninja-release-x86",
"displayName": "Windows x86 RelWithDebInfo (Ninja)",
"description": "VCMI RelWithDebInfo build using Ninja + sccache (x86)",
"inherits": "windows-msvc-ninja-release"
},
{
"name": "windows-msvc-ninja-release-arm64",
"displayName": "Windows ARM64 RelWithDebInfo (Ninja)",
"description": "VCMI RelWithDebInfo build using Ninja + sccache (ARM64)",
"inherits": "windows-msvc-ninja-release"
},
{
"name": "macos-ninja-release",
"displayName": "Ninja release",
@@ -272,8 +255,7 @@
"default-release"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ENABLE_TEMPLATE_EDITOR": "OFF"
"CMAKE_BUILD_TYPE": "Release"
}
},
{
@@ -290,36 +272,20 @@
"generator": "Xcode"
},
{
"name": "ios-device",
"displayName": "Base iOS device",
"description": "Base VCMI preset for iOS device",
"name": "ios-device-conan",
"displayName": "Base iOS device using Conan",
"description": "Base VCMI preset for iOS device using Conan",
"inherits": [
"build-with-conan"
],
"generator": "Xcode",
"binaryDir": "../build-${presetName}",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "iOS",
"FORCE_BUNDLED_FL": "ON",
"FORCE_BUNDLED_MINIZIP": "ON",
"ENABLE_EDITOR" : "OFF"
}
},
{
"name": "ios-simulator",
"displayName": "Base iOS simulator",
"description": "Base VCMI preset for iOS simulator",
"inherits": "ios-device",
"cacheVariables": {
"CMAKE_OSX_SYSROOT": "iphonesimulator"
}
},
{
"name": "ios-device-conan",
"displayName": "Base iOS device using Conan",
"description": "Base VCMI preset for iOS device using Conan",
"inherits": [
"build-with-conan",
"ios-device"
]
},
{
"name": "base-ios-release",
"displayName": "Base iOS release",
@@ -352,18 +318,6 @@
"ENABLE_CCACHE": "ON"
}
},
{
"name": "ios-release-legacy",
"displayName": "iOS release using legacy dependencies",
"description": "VCMI iOS release using legacy dependencies",
"inherits": [
"base-ios-release",
"ios-device"
],
"cacheVariables": {
"CMAKE_PREFIX_PATH": "${sourceDir}/build/iphoneos"
}
},
{
"name": "android-conan-ninja-release",
"displayName": "Android release",
@@ -522,12 +476,6 @@
"configurePreset": "windows-msvc-ninja-release-arm64",
"inherits": "default-release"
},
{
"name": "windows-mingw-conan-linux",
"configurePreset": "windows-mingw-conan-linux",
"inherits": "default-release",
"configuration": "Release"
},
{
"name": "ios-release-conan",
"configurePreset": "ios-release-conan",
@@ -543,11 +491,6 @@
"configurePreset": "ios-release-conan-ccache",
"inherits": "ios-release-conan"
},
{
"name": "ios-release-legacy",
"configurePreset": "ios-release-legacy",
"inherits": "ios-release-conan"
},
{
"name": "android-conan-ninja-release",
"configurePreset": "android-conan-ninja-release",
@@ -613,11 +556,6 @@
"name": "windows-msvc-release",
"configurePreset": "windows-msvc-release",
"inherits": "default-release"
},
{
"name": "windows-mingw-conan-linux",
"configurePreset": "windows-mingw-conan-linux",
"inherits": "default-release"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -952,6 +952,11 @@
"creatures.core.marksman.bonus.extraAttack" : "{Shoots twice}\nThis unit can shoot twice",
"creatures.core.azureDragon.bonus.fearful" : "{Fear}\nEnemy units have a 10% chance of freezing in fear",
"creatures.core.azureDragon.bonus.fearless" : "{Fearless}\nImmune to Fear ability",
"creatures.core.halfling.bonus.lucky" : "{Lucky}\nHalfling's luck cannot be decreased below +1",
"creatures.core.nomad.bonus.sandWalker" : "{Sand walker}\nHero ignores terrain penalty on sand",
"creatures.core.rogue.bonus.visionsMonsters" : "{Visions Monsters}\nHero can see detailed informations about neutral monsters",
"creatures.core.rogue.bonus.visionsHeroes" : "{Visions Heroes}\nHero can see detailed informations about enemy heroes",
"creatures.core.rogue.bonus.visionsTowns" : "{Visions Towns}\nHero can see detailed informations about enemy towns",
"core.bonus.ADDITIONAL_ATTACK.description" : "{Additional attacks}\nUnit can attack an additional {$val} times", // TODO: alternative descriptions for melee/ranged effect range
"core.bonus.ADDITIONAL_RETALIATION.description" : "{Additional retaliations}\nUnit can retaliate ${val} extra times",

View File

@@ -46,7 +46,7 @@ android {
manifest.srcFile '../AndroidManifest.xml'
jniLibs.srcDirs = ['../libs']
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
java.srcDirs = [qt5AndroidDir + '/src', 'src', SDL_JAVA_SRC_DIR]
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'src/main/res', '../res']
}
@@ -102,24 +102,6 @@ android {
}
}
def CommandOutput(final cmd, final arguments, final cwd) {
try {
new ByteArrayOutputStream().withStream { final os ->
exec {
executable cmd
args arguments
workingDir cwd
standardOutput os
}
return os.toString().trim()
}
}
catch (final Exception ex) {
print("Broken: " + cmd + " " + arguments + " in " + cwd + " :: " + ex.toString())
return ""
}
}
def SigningPropertiesPath(final basePath, final signingConfigKey) {
return file("${basePath}/${signingConfigKey}.properties")
}
@@ -142,7 +124,6 @@ def LoadSigningConfig(final signingConfigKey) {
&& props.containsKey('KEY_ALIAS')) {
signingConfig.storeFile = SigningKeystorePath(signingRoot, props['STORE_FILE'])
signingConfig.storePassword = props['STORE_PASSWORD']
signingConfig.keyAlias = props['KEY_ALIAS']
if(props.containsKey('STORE_PASSWORD'))

View File

@@ -77,7 +77,7 @@ public class VcmiSDLActivity extends SDLActivity
@Override
protected String[] getLibraries() {
// SDL is linked statically, no need to load anything
// app main library and SDL are loaded when launcher starts, no extra work to do
return new String[] {
};
}

View File

@@ -1,22 +0,0 @@
package org.libsdl.app;
import android.hardware.usb.UsbDevice;
interface HIDDevice
{
public int getId();
public int getVendorId();
public int getProductId();
public String getSerialNumber();
public int getVersion();
public String getManufacturerName();
public String getProductName();
public UsbDevice getDevice();
public boolean open();
public int sendFeatureReport(byte[] report);
public int sendOutputReport(byte[] report);
public boolean getFeatureReport(byte[] report);
public void setFrozen(boolean frozen);
public void close();
public void shutdown();
}

View File

@@ -1,650 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothGattService;
import android.hardware.usb.UsbDevice;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.os.*;
//import com.android.internal.util.HexDump;
import java.lang.Runnable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.UUID;
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
private static final String TAG = "hidapi";
private HIDDeviceManager mManager;
private BluetoothDevice mDevice;
private int mDeviceId;
private BluetoothGatt mGatt;
private boolean mIsRegistered = false;
private boolean mIsConnected = false;
private boolean mIsChromebook = false;
private boolean mIsReconnecting = false;
private boolean mFrozen = false;
private LinkedList<GattOperation> mOperations;
GattOperation mCurrentOperation = null;
private Handler mHandler;
private static final int TRANSPORT_AUTO = 0;
private static final int TRANSPORT_BREDR = 1;
private static final int TRANSPORT_LE = 2;
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
static class GattOperation {
private enum Operation {
CHR_READ,
CHR_WRITE,
ENABLE_NOTIFICATION
}
Operation mOp;
UUID mUuid;
byte[] mValue;
BluetoothGatt mGatt;
boolean mResult = true;
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mValue = value;
}
public void run() {
// This is executed in main thread
BluetoothGattCharacteristic chr;
switch (mOp) {
case CHR_READ:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
if (!mGatt.readCharacteristic(chr)) {
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
mResult = false;
break;
}
mResult = true;
break;
case CHR_WRITE:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
chr.setValue(mValue);
if (!mGatt.writeCharacteristic(chr)) {
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
mResult = false;
break;
}
mResult = true;
break;
case ENABLE_NOTIFICATION:
chr = getCharacteristic(mUuid);
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
if (chr != null) {
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
int properties = chr.getProperties();
byte[] value;
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
} else {
Log.e(TAG, "Unable to start notifications on input characteristic");
mResult = false;
return;
}
mGatt.setCharacteristicNotification(chr, true);
cccd.setValue(value);
if (!mGatt.writeDescriptor(cccd)) {
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
mResult = false;
return;
}
mResult = true;
}
}
}
}
public boolean finish() {
return mResult;
}
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
return null;
return valveService.getCharacteristic(uuid);
}
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.CHR_READ, uuid);
}
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
}
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
}
}
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
mManager = manager;
mDevice = device;
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
mIsRegistered = false;
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
mOperations = new LinkedList<GattOperation>();
mHandler = new Handler(Looper.getMainLooper());
mGatt = connectGatt();
// final HIDDeviceBLESteamController finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// finalThis.checkConnectionForChromebookIssue();
// }
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
public String getIdentifier() {
return String.format("SteamController.%s", mDevice.getAddress());
}
public BluetoothGatt getGatt() {
return mGatt;
}
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
private BluetoothGatt connectGatt(boolean managed) {
if (Build.VERSION.SDK_INT >= 23) {
try {
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
} catch (Exception e) {
return mDevice.connectGatt(mManager.getContext(), managed, this);
}
} else {
return mDevice.connectGatt(mManager.getContext(), managed, this);
}
}
private BluetoothGatt connectGatt() {
return connectGatt(false);
}
protected int getConnectionState() {
Context context = mManager.getContext();
if (context == null) {
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
return BluetoothProfile.STATE_DISCONNECTED;
}
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
if (btManager == null) {
// This device doesn't support Bluetooth. We should never be here, because how did
// we instantiate a device to start with?
return BluetoothProfile.STATE_DISCONNECTED;
}
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
}
public void reconnect() {
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
mGatt.disconnect();
mGatt = connectGatt();
}
}
protected void checkConnectionForChromebookIssue() {
if (!mIsChromebook) {
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
// over and over.
return;
}
int connectionState = getConnectionState();
switch (connectionState) {
case BluetoothProfile.STATE_CONNECTED:
if (!mIsConnected) {
// We are in the Bad Chromebook Place. We can force a disconnect
// to try to recover.
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
}
else if (!isRegistered()) {
if (mGatt.getServices().size() > 0) {
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
probeService(this);
}
else {
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
}
}
else {
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
return;
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
break;
case BluetoothProfile.STATE_CONNECTING:
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
break;
}
final HIDDeviceBLESteamController finalThis = this;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finalThis.checkConnectionForChromebookIssue();
}
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
private boolean isRegistered() {
return mIsRegistered;
}
private void setRegistered() {
mIsRegistered = true;
}
private boolean probeService(HIDDeviceBLESteamController controller) {
if (isRegistered()) {
return true;
}
if (!mIsConnected) {
return false;
}
Log.v(TAG, "probeService controller=" + controller);
for (BluetoothGattService service : mGatt.getServices()) {
if (service.getUuid().equals(steamControllerService)) {
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(inputCharacteristic)) {
Log.v(TAG, "Found input characteristic");
// Start notifications
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (cccd != null) {
enableNotification(chr.getUuid());
}
}
}
return true;
}
}
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
mIsConnected = false;
mIsReconnecting = true;
mGatt.disconnect();
mGatt = connectGatt(false);
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
private void finishCurrentGattOperation() {
GattOperation op = null;
synchronized (mOperations) {
if (mCurrentOperation != null) {
op = mCurrentOperation;
mCurrentOperation = null;
}
}
if (op != null) {
boolean result = op.finish(); // TODO: Maybe in main thread as well?
// Our operation failed, let's add it back to the beginning of our queue.
if (!result) {
mOperations.addFirst(op);
}
}
executeNextGattOperation();
}
private void executeNextGattOperation() {
synchronized (mOperations) {
if (mCurrentOperation != null)
return;
if (mOperations.isEmpty())
return;
mCurrentOperation = mOperations.removeFirst();
}
// Run in main thread
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mOperations) {
if (mCurrentOperation == null) {
Log.e(TAG, "Current operation null in executor?");
return;
}
mCurrentOperation.run();
// now wait for the GATT callback and when it comes, finish this operation
}
}
});
}
private void queueGattOperation(GattOperation op) {
synchronized (mOperations) {
mOperations.add(op);
}
executeNextGattOperation();
}
private void enableNotification(UUID chrUuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
queueGattOperation(op);
}
public void writeCharacteristic(UUID uuid, byte[] value) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
queueGattOperation(op);
}
public void readCharacteristic(UUID uuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
queueGattOperation(op);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////////////// BluetoothGattCallback overridden methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
mIsReconnecting = false;
if (newState == 2) {
mIsConnected = true;
// Run directly, without GattOperation
if (!isRegistered()) {
mHandler.post(new Runnable() {
@Override
public void run() {
mGatt.discoverServices();
}
});
}
}
else if (newState == 0) {
mIsConnected = false;
}
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
}
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onServicesDiscovered status=" + status);
if (status == 0) {
if (gatt.getServices().size() == 0) {
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
mIsReconnecting = true;
mIsConnected = false;
gatt.disconnect();
mGatt = connectGatt(false);
}
else {
probeService(this);
}
}
}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
}
finishCurrentGattOperation();
}
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
if (characteristic.getUuid().equals(reportCharacteristic)) {
// Only register controller with the native side once it has been fully configured
if (!isRegistered()) {
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
setRegistered();
}
}
finishCurrentGattOperation();
}
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// Enable this for verbose logging of controller input reports
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
}
}
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//Log.v(TAG, "onDescriptorRead status=" + status);
}
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
if (chr.getUuid().equals(inputCharacteristic)) {
boolean hasWrittenInputDescriptor = true;
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
if (reportChr != null) {
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
gatt.writeCharacteristic(reportChr);
}
}
finishCurrentGattOperation();
}
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
}
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
//Log.v(TAG, "onReadRemoteRssi status=" + status);
}
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
//Log.v(TAG, "onMtuChanged status=" + status);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////// Public API
//////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public int getId() {
return mDeviceId;
}
@Override
public int getVendorId() {
// Valve Corporation
final int VALVE_USB_VID = 0x28DE;
return VALVE_USB_VID;
}
@Override
public int getProductId() {
// We don't have an easy way to query from the Bluetooth device, but we know what it is
final int D0G_BLE2_PID = 0x1106;
return D0G_BLE2_PID;
}
@Override
public String getSerialNumber() {
// This will be read later via feature report by Steam
return "12345";
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getManufacturerName() {
return "Valve Corporation";
}
@Override
public String getProductName() {
return "Steam Controller";
}
@Override
public UsbDevice getDevice() {
return null;
}
@Override
public boolean open() {
return true;
}
@Override
public int sendFeatureReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
// We need to skip the first byte, as that doesn't go over the air
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
writeCharacteristic(reportCharacteristic, actual_report);
return report.length;
}
@Override
public int sendOutputReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
}
@Override
public boolean getFeatureReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return false;
}
//Log.v(TAG, "getFeatureReport");
readCharacteristic(reportCharacteristic);
return true;
}
@Override
public void close() {
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
}
@Override
public void shutdown() {
close();
BluetoothGatt g = mGatt;
if (g != null) {
g.disconnect();
g.close();
mGatt = null;
}
mManager = null;
mIsRegistered = false;
mIsConnected = false;
mOperations.clear();
}
}

View File

@@ -1,679 +0,0 @@
package org.libsdl.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.os.Build;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.*;
import android.os.Handler;
import android.os.Looper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class HIDDeviceManager {
private static final String TAG = "hidapi";
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
private static HIDDeviceManager sManager;
private static int sManagerRefCount = 0;
public static HIDDeviceManager acquire(Context context) {
if (sManagerRefCount == 0) {
sManager = new HIDDeviceManager(context);
}
++sManagerRefCount;
return sManager;
}
public static void release(HIDDeviceManager manager) {
if (manager == sManager) {
--sManagerRefCount;
if (sManagerRefCount == 0) {
sManager.close();
sManager = null;
}
}
}
private Context mContext;
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
private int mNextDeviceId = 0;
private SharedPreferences mSharedPreferences = null;
private boolean mIsChromebook = false;
private UsbManager mUsbManager;
private Handler mHandler;
private BluetoothManager mBluetoothManager;
private List<BluetoothDevice> mLastBluetoothDevices;
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDeviceAttached(usbDevice);
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDeviceDetached(usbDevice);
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
}
}
};
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Bluetooth device was connected. If it was a Steam Controller, handle it
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Bluetooth device connected: " + device);
if (isSteamController(device)) {
connectBluetoothDevice(device);
}
}
// Bluetooth device was disconnected, remove from controller manager (if any)
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Bluetooth device disconnected: " + device);
disconnectBluetoothDevice(device);
}
}
};
private HIDDeviceManager(final Context context) {
mContext = context;
HIDDeviceRegisterCallback();
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
// if (shouldClear) {
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
// spedit.clear();
// spedit.commit();
// }
// else
{
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
}
}
public Context getContext() {
return mContext;
}
public int getDeviceIDForIdentifier(String identifier) {
SharedPreferences.Editor spedit = mSharedPreferences.edit();
int result = mSharedPreferences.getInt(identifier, 0);
if (result == 0) {
result = mNextDeviceId++;
spedit.putInt("next_device_id", mNextDeviceId);
}
spedit.putInt(identifier, result);
spedit.commit();
return result;
}
private void initializeUSB() {
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
if (mUsbManager == null) {
return;
}
/*
// Logging
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
Log.i(TAG,"Path: " + device.getDeviceName());
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
Log.i(TAG,"Product: " + device.getProductName());
Log.i(TAG,"ID: " + device.getDeviceId());
Log.i(TAG,"Class: " + device.getDeviceClass());
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
Log.i(TAG,"Vendor ID " + device.getVendorId());
Log.i(TAG,"Product ID: " + device.getProductId());
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
Log.i(TAG,"---------------------------------------");
// Get interface details
for (int index = 0; index < device.getInterfaceCount(); index++) {
UsbInterface mUsbInterface = device.getInterface(index);
Log.i(TAG," ***** *****");
Log.i(TAG," Interface index: " + index);
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
// Get endpoint details
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
{
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
Log.i(TAG," ++++ ++++ ++++");
Log.i(TAG," Endpoint index: " + epi);
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
Log.i(TAG," Direction: " + mEndpoint.getDirection());
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
Log.i(TAG," Interval: " + mEndpoint.getInterval());
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
Log.i(TAG," Type: " + mEndpoint.getType());
}
}
}
Log.i(TAG," No more devices connected.");
*/
// Register for USB broadcasts and permission completions
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
mContext.registerReceiver(mUsbBroadcast, filter);
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
handleUsbDeviceAttached(usbDevice);
}
}
UsbManager getUSBManager() {
return mUsbManager;
}
private void shutdownUSB() {
try {
mContext.unregisterReceiver(mUsbBroadcast);
} catch (Exception e) {
// We may not have registered, that's okay
}
}
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
return true;
}
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
return true;
}
return false;
}
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
final int XB360_IFACE_SUBCLASS = 93;
final int XB360_IFACE_PROTOCOL = 1; // Wired
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
final int[] SUPPORTED_VENDORS = {
0x0079, // GPD Win 2
0x044f, // Thrustmaster
0x045e, // Microsoft
0x046d, // Logitech
0x056e, // Elecom
0x06a3, // Saitek
0x0738, // Mad Catz
0x07ff, // Mad Catz
0x0e6f, // PDP
0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
0x1532, // Razer Sabertooth
0x15e4, // Numark
0x162e, // Joytech
0x1689, // Razer Onza
0x1949, // Lab126, Inc.
0x1bad, // Harmonix
0x20d6, // PowerA
0x24c6, // PowerA
0x2c22, // Qanba
};
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
int vendor_id = usbDevice.getVendorId();
for (int supportedVid : SUPPORTED_VENDORS) {
if (vendor_id == supportedVid) {
return true;
}
}
}
return false;
}
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
final int XB1_IFACE_SUBCLASS = 71;
final int XB1_IFACE_PROTOCOL = 208;
final int[] SUPPORTED_VENDORS = {
0x045e, // Microsoft
0x0738, // Mad Catz
0x0e6f, // PDP
0x0f0d, // Hori
0x1532, // Razer Wildcat
0x20d6, // PowerA
0x24c6, // PowerA
0x2dc8, /* 8BitDo */
0x2e24, // Hyperkin
};
if (usbInterface.getId() == 0 &&
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
int vendor_id = usbDevice.getVendorId();
for (int supportedVid : SUPPORTED_VENDORS) {
if (vendor_id == supportedVid) {
return true;
}
}
}
return false;
}
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
connectHIDDeviceUSB(usbDevice);
}
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
List<Integer> devices = new ArrayList<Integer>();
for (HIDDevice device : mDevicesById.values()) {
if (usbDevice.equals(device.getDevice())) {
devices.add(device.getId());
}
}
for (int id : devices) {
HIDDevice device = mDevicesById.get(id);
mDevicesById.remove(id);
device.shutdown();
HIDDeviceDisconnected(id);
}
}
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
for (HIDDevice device : mDevicesById.values()) {
if (usbDevice.equals(device.getDevice())) {
boolean opened = false;
if (permission_granted) {
opened = device.open();
}
HIDDeviceOpenResult(device.getId(), opened);
}
}
}
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
synchronized (this) {
int interface_mask = 0;
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
// Check to see if we've already added this interface
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
int interface_id = usbInterface.getId();
if ((interface_mask & (1 << interface_id)) != 0) {
continue;
}
interface_mask |= (1 << interface_id);
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
int id = device.getId();
mDevicesById.put(id, device);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
}
}
}
}
private void initializeBluetooth() {
Log.d(TAG, "Initializing Bluetooth");
if (Build.VERSION.SDK_INT <= 30 &&
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
return;
}
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
return;
}
// Find bonded bluetooth controllers and create SteamControllers for them
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
// This device doesn't support Bluetooth.
return;
}
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
if (btAdapter == null) {
// This device has Bluetooth support in the codebase, but has no available adapters.
return;
}
// Get our bonded devices.
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
Log.d(TAG, "Bluetooth device available: " + device);
if (isSteamController(device)) {
connectBluetoothDevice(device);
}
}
// NOTE: These don't work on Chromebooks, to my undying dismay.
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mContext.registerReceiver(mBluetoothBroadcast, filter);
if (mIsChromebook) {
mHandler = new Handler(Looper.getMainLooper());
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
// final HIDDeviceManager finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// finalThis.chromebookConnectionHandler();
// }
// }, 5000);
}
}
private void shutdownBluetooth() {
try {
mContext.unregisterReceiver(mBluetoothBroadcast);
} catch (Exception e) {
// We may not have registered, that's okay
}
}
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
// This function provides a sort of dummy version of that, watching for changes in the
// connected devices and attempting to add controllers as things change.
public void chromebookConnectionHandler() {
if (!mIsChromebook) {
return;
}
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
for (BluetoothDevice bluetoothDevice : currentConnected) {
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
connected.add(bluetoothDevice);
}
}
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
if (!currentConnected.contains(bluetoothDevice)) {
disconnected.add(bluetoothDevice);
}
}
mLastBluetoothDevices = currentConnected;
for (BluetoothDevice bluetoothDevice : disconnected) {
disconnectBluetoothDevice(bluetoothDevice);
}
for (BluetoothDevice bluetoothDevice : connected) {
connectBluetoothDevice(bluetoothDevice);
}
final HIDDeviceManager finalThis = this;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finalThis.chromebookConnectionHandler();
}
}, 10000);
}
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
synchronized (this) {
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
device.reconnect();
return false;
}
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
int id = device.getId();
mBluetoothDevices.put(bluetoothDevice, device);
mDevicesById.put(id, device);
// The Steam Controller will mark itself connected once initialization is complete
}
return true;
}
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
synchronized (this) {
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
if (device == null)
return;
int id = device.getId();
mBluetoothDevices.remove(bluetoothDevice);
mDevicesById.remove(id);
device.shutdown();
HIDDeviceDisconnected(id);
}
}
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
if (bluetoothDevice == null) {
return false;
}
// If the device has no local name, we really don't want to try an equality check against it.
if (bluetoothDevice.getName() == null) {
return false;
}
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
}
private void close() {
shutdownUSB();
shutdownBluetooth();
synchronized (this) {
for (HIDDevice device : mDevicesById.values()) {
device.shutdown();
}
mDevicesById.clear();
mBluetoothDevices.clear();
HIDDeviceReleaseCallback();
}
}
public void setFrozen(boolean frozen) {
synchronized (this) {
for (HIDDevice device : mDevicesById.values()) {
device.setFrozen(frozen);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
private HIDDevice getDevice(int id) {
synchronized (this) {
HIDDevice result = mDevicesById.get(id);
if (result == null) {
Log.v(TAG, "No device for id: " + id);
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
}
return result;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// JNI interface functions
//////////////////////////////////////////////////////////////////////////////////////////////////////
public boolean initialize(boolean usb, boolean bluetooth) {
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
if (usb) {
initializeUSB();
}
if (bluetooth) {
initializeBluetooth();
}
return true;
}
public boolean openDevice(int deviceID) {
Log.v(TAG, "openDevice deviceID=" + deviceID);
HIDDevice device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return false;
}
// Look to see if this is a USB device and we have permission to access it
UsbDevice usbDevice = device.getDevice();
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
HIDDeviceOpenPending(deviceID);
try {
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
int flags;
if (Build.VERSION.SDK_INT >= 31) {
flags = FLAG_MUTABLE;
} else {
flags = 0;
}
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);
}
return false;
}
try {
return device.open();
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public int sendOutputReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendOutputReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public int sendFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public boolean getFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return false;
}
return device.getFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public void closeDevice(int deviceID) {
try {
Log.v(TAG, "closeDevice deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return;
}
device.close();
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////// Native methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
private native void HIDDeviceRegisterCallback();
private native void HIDDeviceReleaseCallback();
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
native void HIDDeviceOpenPending(int deviceID);
native void HIDDeviceOpenResult(int deviceID, boolean opened);
native void HIDDeviceDisconnected(int deviceID);
native void HIDDeviceInputReport(int deviceID, byte[] report);
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
}

View File

@@ -1,309 +0,0 @@
package org.libsdl.app;
import android.hardware.usb.*;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
class HIDDeviceUSB implements HIDDevice {
private static final String TAG = "hidapi";
protected HIDDeviceManager mManager;
protected UsbDevice mDevice;
protected int mInterfaceIndex;
protected int mInterface;
protected int mDeviceId;
protected UsbDeviceConnection mConnection;
protected UsbEndpoint mInputEndpoint;
protected UsbEndpoint mOutputEndpoint;
protected InputThread mInputThread;
protected boolean mRunning;
protected boolean mFrozen;
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
mManager = manager;
mDevice = usbDevice;
mInterfaceIndex = interface_index;
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
mRunning = false;
}
public String getIdentifier() {
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
}
@Override
public int getId() {
return mDeviceId;
}
@Override
public int getVendorId() {
return mDevice.getVendorId();
}
@Override
public int getProductId() {
return mDevice.getProductId();
}
@Override
public String getSerialNumber() {
String result = null;
if (Build.VERSION.SDK_INT >= 21) {
try {
result = mDevice.getSerialNumber();
}
catch (SecurityException exception) {
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
}
}
if (result == null) {
result = "";
}
return result;
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getManufacturerName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21) {
result = mDevice.getManufacturerName();
}
if (result == null) {
result = String.format("%x", getVendorId());
}
return result;
}
@Override
public String getProductName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21) {
result = mDevice.getProductName();
}
if (result == null) {
result = String.format("%x", getProductId());
}
return result;
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
public String getDeviceName() {
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
}
@Override
public boolean open() {
mConnection = mManager.getUSBManager().openDevice(mDevice);
if (mConnection == null) {
Log.w(TAG, "Unable to open USB device " + getDeviceName());
return false;
}
// Force claim our interface
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
if (!mConnection.claimInterface(iface, true)) {
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
close();
return false;
}
// Find the endpoints
for (int j = 0; j < iface.getEndpointCount(); j++) {
UsbEndpoint endpt = iface.getEndpoint(j);
switch (endpt.getDirection()) {
case UsbConstants.USB_DIR_IN:
if (mInputEndpoint == null) {
mInputEndpoint = endpt;
}
break;
case UsbConstants.USB_DIR_OUT:
if (mOutputEndpoint == null) {
mOutputEndpoint = endpt;
}
break;
}
}
// Make sure the required endpoints were present
if (mInputEndpoint == null || mOutputEndpoint == null) {
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
close();
return false;
}
// Start listening for input
mRunning = true;
mInputThread = new InputThread();
mInputThread.start();
return true;
}
@Override
public int sendFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
0x09/*HID set_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
return -1;
}
if (skipped_report_id) {
++length;
}
return length;
}
@Override
public int sendOutputReport(byte[] report) {
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
if (r != report.length) {
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
}
return r;
}
@Override
public boolean getFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
/* Offset the return buffer by 1, so that the report ID
will remain in byte 0. */
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
0x01/*HID get_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
return false;
}
if (skipped_report_id) {
++res;
++length;
}
byte[] data;
if (res == length) {
data = report;
} else {
data = Arrays.copyOfRange(report, 0, res);
}
mManager.HIDDeviceFeatureReport(mDeviceId, data);
return true;
}
@Override
public void close() {
mRunning = false;
if (mInputThread != null) {
while (mInputThread.isAlive()) {
mInputThread.interrupt();
try {
mInputThread.join();
} catch (InterruptedException e) {
// Keep trying until we're done
}
}
mInputThread = null;
}
if (mConnection != null) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
mConnection.close();
mConnection = null;
}
}
@Override
public void shutdown() {
close();
mManager = null;
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
}
protected class InputThread extends Thread {
@Override
public void run() {
int packetSize = mInputEndpoint.getMaxPacketSize();
byte[] packet = new byte[packetSize];
while (mRunning) {
int r;
try
{
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
}
catch (Exception e)
{
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
break;
}
if (r < 0) {
// Could be a timeout or an I/O error
}
if (r > 0) {
byte[] data;
if (r == packetSize) {
data = packet;
} else {
data = Arrays.copyOfRange(packet, 0, r);
}
if (!mFrozen) {
mManager.HIDDeviceInputReport(mDeviceId, data);
}
}
}
}
}
}

View File

@@ -1,85 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import java.lang.Class;
import java.lang.reflect.Method;
/**
SDL library initialization
*/
public class SDL {
// This function should be called first and sets up the native code
// so it can call into the Java classes
public static void setupJNI() {
SDLActivity.nativeSetupJNI();
SDLAudioManager.nativeSetupJNI();
SDLControllerManager.nativeSetupJNI();
}
// This function should be called each time the activity is started
public static void initialize() {
setContext(null);
SDLActivity.initialize();
SDLAudioManager.initialize();
SDLControllerManager.initialize();
}
// This function stores the current activity (SDL or not)
public static void setContext(Context context) {
mContext = context;
}
public static Context getContext() {
return mContext;
}
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
if (libraryName == null) {
throw new NullPointerException("No library name provided.");
}
try {
// Let's see if we have ReLinker available in the project. This is necessary for
// some projects that have huge numbers of local libraries bundled, and thus may
// trip a bug in Android's native library loader which ReLinker works around. (If
// loadLibrary works properly, ReLinker will simply use the normal Android method
// internally.)
//
// 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");
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
// they've changed during updates.
Method forceMethod = relinkClass.getDeclaredMethod("force");
Object relinkInstance = forceMethod.invoke(null);
Class<?> relinkInstanceClass = relinkInstance.getClass();
// Actually load the library!
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
}
catch (final Throwable e) {
// Fall back
try {
System.loadLibrary(libraryName);
}
catch (final UnsatisfiedLinkError ule) {
throw ule;
}
catch (final SecurityException se) {
throw se;
}
}
}
protected static Context mContext;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,394 +0,0 @@
package org.libsdl.app;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
public class SDLAudioManager
{
protected static final String TAG = "SDLAudio";
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;
public static void initialize() {
mAudioTrack = null;
mAudioRecord = null;
}
// Audio
protected static String getAudioFormatString(int audioFormat) {
switch (audioFormat) {
case AudioFormat.ENCODING_PCM_8BIT:
return "8-bit";
case AudioFormat.ENCODING_PCM_16BIT:
return "16-bit";
case AudioFormat.ENCODING_PCM_FLOAT:
return "float";
default:
return Integer.toString(audioFormat);
}
}
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
int channelConfig;
int sampleSize;
int frameSize;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
/* On older devices let's use known good settings */
if (Build.VERSION.SDK_INT < 21) {
if (desiredChannels > 2) {
desiredChannels = 2;
}
}
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
if (Build.VERSION.SDK_INT < 22) {
if (sampleRate < 8000) {
sampleRate = 8000;
} else if (sampleRate > 48000) {
sampleRate = 48000;
}
}
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
int minSDKVersion = (isCapture ? 23 : 21);
if (Build.VERSION.SDK_INT < minSDKVersion) {
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
}
}
switch (audioFormat)
{
case AudioFormat.ENCODING_PCM_8BIT:
sampleSize = 1;
break;
case AudioFormat.ENCODING_PCM_16BIT:
sampleSize = 2;
break;
case AudioFormat.ENCODING_PCM_FLOAT:
sampleSize = 4;
break;
default:
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
sampleSize = 2;
break;
}
if (isCapture) {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_IN_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
}
} else {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
case 3:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 4:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
break;
case 5:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 6:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break;
case 7:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
if (Build.VERSION.SDK_INT >= 23) {
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else {
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
desiredChannels = 6;
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
}
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
}
/*
Log.v(TAG, "Speaker configuration (and order of channels):");
if ((channelConfig & 0x00000004) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
}
if ((channelConfig & 0x00000008) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
}
if ((channelConfig & 0x00000010) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
}
if ((channelConfig & 0x00000020) != 0) {
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
}
if ((channelConfig & 0x00000040) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
}
if ((channelConfig & 0x00000080) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
}
if ((channelConfig & 0x00000100) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
}
if ((channelConfig & 0x00000200) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
}
if ((channelConfig & 0x00000400) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
}
if ((channelConfig & 0x00000800) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
}
if ((channelConfig & 0x00001000) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
}
*/
}
frameSize = (sampleSize * desiredChannels);
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
int minBufferSize;
if (isCapture) {
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
} else {
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
}
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
int[] results = new int[4];
if (isCapture) {
if (mAudioRecord == null) {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize);
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of AudioRecord");
mAudioRecord.release();
mAudioRecord = null;
return null;
}
mAudioRecord.startRecording();
}
results[0] = mAudioRecord.getSampleRate();
results[1] = mAudioRecord.getAudioFormat();
results[2] = mAudioRecord.getChannelCount();
} else {
if (mAudioTrack == null) {
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
/* Try again, with safer values */
Log.e(TAG, "Failed during initialization of Audio Track");
mAudioTrack.release();
mAudioTrack = null;
return null;
}
mAudioTrack.play();
}
results[0] = mAudioTrack.getSampleRate();
results[1] = mAudioTrack.getAudioFormat();
results[2] = mAudioTrack.getChannelCount();
}
results[3] = desiredFrames;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
return results;
}
/**
* This method is called by SDL using JNI.
*/
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteFloatBuffer(float[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(float)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteShortBuffer(short[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(short)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteByteBuffer(byte[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(byte)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
}
/** This method is called by SDL using JNI. */
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
/** This method is called by SDL using JNI. */
public static void captureClose() {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
/** This method is called by SDL using JNI. */
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
try {
/* Set thread name */
if (iscapture) {
Thread.currentThread().setName("SDLAudioC" + device_id);
} else {
Thread.currentThread().setName("SDLAudioP" + device_id);
}
/* Set thread priority */
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
} catch (Exception e) {
Log.v(TAG, "modify thread properties failed " + e.toString());
}
}
public static native int nativeSetupJNI();
}

View File

@@ -1,814 +0,0 @@
package org.libsdl.app;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.content.Context;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
public class SDLControllerManager
{
public static native int nativeSetupJNI();
public static native int nativeAddJoystick(int device_id, String name, String desc,
int vendor_id, int product_id,
boolean is_accelerometer, int button_mask,
int naxes, int nhats, int nballs);
public static native int nativeRemoveJoystick(int device_id);
public static native int nativeAddHaptic(int device_id, String name);
public static native int nativeRemoveHaptic(int device_id);
public static native int onNativePadDown(int device_id, int keycode);
public static native int onNativePadUp(int device_id, int keycode);
public static native void onNativeJoy(int device_id, int axis,
float value);
public static native void onNativeHat(int device_id, int hat_id,
int x, int y);
protected static SDLJoystickHandler mJoystickHandler;
protected static SDLHapticHandler mHapticHandler;
private static final String TAG = "SDLControllerManager";
public static void initialize() {
if (mJoystickHandler == null) {
if (Build.VERSION.SDK_INT >= 19) {
mJoystickHandler = new SDLJoystickHandler_API19();
} else {
mJoystickHandler = new SDLJoystickHandler_API16();
}
}
if (mHapticHandler == null) {
if (Build.VERSION.SDK_INT >= 26) {
mHapticHandler = new SDLHapticHandler_API26();
} else {
mHapticHandler = new SDLHapticHandler();
}
}
}
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
public static boolean handleJoystickMotionEvent(MotionEvent event) {
return mJoystickHandler.handleMotionEvent(event);
}
/**
* This method is called by SDL using JNI.
*/
public static void pollInputDevices() {
mJoystickHandler.pollInputDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void pollHapticDevices() {
mHapticHandler.pollHapticDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticRun(int device_id, float intensity, int length) {
mHapticHandler.run(device_id, intensity, length);
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticStop(int device_id)
{
mHapticHandler.stop(device_id);
}
// Check if a given device is considered a possible SDL joystick
public static boolean isDeviceSDLJoystick(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
// We cannot use InputDevice.isVirtual before API 16, so let's accept
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
if ((device == null) || (deviceId < 0)) {
return false;
}
int sources = device.getSources();
/* This is called for every button press, so let's not spam the logs */
/*
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
}
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
}
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
}
*/
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
);
}
}
class SDLJoystickHandler {
/**
* Handles given MotionEvent.
* @param event the event to be handled.
* @return if given event was processed.
*/
public boolean handleMotionEvent(MotionEvent event) {
return false;
}
/**
* Handles adding and removing of input devices.
*/
public void pollInputDevices() {
}
}
/* Actual joystick functionality available for API >= 12 devices */
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
static class SDLJoystick {
public int device_id;
public String name;
public String desc;
public ArrayList<InputDevice.MotionRange> axes;
public ArrayList<InputDevice.MotionRange> hats;
}
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
int arg0Axis = arg0.getAxis();
int arg1Axis = arg1.getAxis();
if (arg0Axis == MotionEvent.AXIS_GAS) {
arg0Axis = MotionEvent.AXIS_BRAKE;
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
arg0Axis = MotionEvent.AXIS_GAS;
}
if (arg1Axis == MotionEvent.AXIS_GAS) {
arg1Axis = MotionEvent.AXIS_BRAKE;
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
arg1Axis = MotionEvent.AXIS_GAS;
}
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
// This is because the usual pairing are:
// - AXIS_X + AXIS_Y (left stick).
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
// This sorts the axes in the above order, which tends to be correct
// for Xbox-ish game pads that have the right stick on RX/RY and the
// triggers on Z/RZ.
//
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
//
// References:
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
if (arg0Axis == MotionEvent.AXIS_Z) {
arg0Axis = MotionEvent.AXIS_RZ - 1;
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
--arg0Axis;
}
if (arg1Axis == MotionEvent.AXIS_Z) {
arg1Axis = MotionEvent.AXIS_RZ - 1;
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
--arg1Axis;
}
return arg0Axis - arg1Axis;
}
}
private final ArrayList<SDLJoystick> mJoysticks;
public SDLJoystickHandler_API16() {
mJoysticks = new ArrayList<SDLJoystick>();
}
@Override
public void pollInputDevices() {
int[] deviceIds = InputDevice.getDeviceIds();
for (int device_id : deviceIds) {
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
SDLJoystick joystick = getJoystick(device_id);
if (joystick == null) {
InputDevice joystickDevice = InputDevice.getDevice(device_id);
joystick = new SDLJoystick();
joystick.device_id = device_id;
joystick.name = joystickDevice.getName();
joystick.desc = getJoystickDescriptor(joystickDevice);
joystick.axes = new ArrayList<InputDevice.MotionRange>();
joystick.hats = new ArrayList<InputDevice.MotionRange>();
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
joystick.hats.add(range);
} else {
joystick.axes.add(range);
}
}
}
mJoysticks.add(joystick);
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
getVendorId(joystickDevice), getProductId(joystickDevice), false,
getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = null;
for (SDLJoystick joystick : mJoysticks) {
int device_id = joystick.device_id;
int i;
for (i = 0; i < deviceIds.length; i++) {
if (device_id == deviceIds[i]) break;
}
if (i == deviceIds.length) {
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
}
}
if (removedDevices != null) {
for (int device_id : removedDevices) {
SDLControllerManager.nativeRemoveJoystick(device_id);
for (int i = 0; i < mJoysticks.size(); i++) {
if (mJoysticks.get(i).device_id == device_id) {
mJoysticks.remove(i);
break;
}
}
}
}
}
protected SDLJoystick getJoystick(int device_id) {
for (SDLJoystick joystick : mJoysticks) {
if (joystick.device_id == device_id) {
return joystick;
}
}
return null;
}
@Override
public boolean handleMotionEvent(MotionEvent event) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
SDLJoystick joystick = getJoystick(event.getDeviceId());
if (joystick != null) {
for (int i = 0; i < joystick.axes.size(); i++) {
InputDevice.MotionRange range = joystick.axes.get(i);
/* Normalize the value to -1...1 */
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
}
for (int i = 0; i < joystick.hats.size() / 2; i++) {
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
}
}
}
return true;
}
public String getJoystickDescriptor(InputDevice joystickDevice) {
String desc = joystickDevice.getDescriptor();
if (desc != null && !desc.isEmpty()) {
return desc;
}
return joystickDevice.getName();
}
public int getProductId(InputDevice joystickDevice) {
return 0;
}
public int getVendorId(InputDevice joystickDevice) {
return 0;
}
public int getButtonMask(InputDevice joystickDevice) {
return -1;
}
}
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
@Override
public int getProductId(InputDevice joystickDevice) {
return joystickDevice.getProductId();
}
@Override
public int getVendorId(InputDevice joystickDevice) {
return joystickDevice.getVendorId();
}
@Override
public int getButtonMask(InputDevice joystickDevice) {
int button_mask = 0;
int[] keys = new int[] {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BACK,
KeyEvent.KEYCODE_MENU,
KeyEvent.KEYCODE_BUTTON_MODE,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_THUMBL,
KeyEvent.KEYCODE_BUTTON_THUMBR,
KeyEvent.KEYCODE_BUTTON_L1,
KeyEvent.KEYCODE_BUTTON_R1,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER,
// These don't map into any SDL controller buttons directly
KeyEvent.KEYCODE_BUTTON_L2,
KeyEvent.KEYCODE_BUTTON_R2,
KeyEvent.KEYCODE_BUTTON_C,
KeyEvent.KEYCODE_BUTTON_Z,
KeyEvent.KEYCODE_BUTTON_1,
KeyEvent.KEYCODE_BUTTON_2,
KeyEvent.KEYCODE_BUTTON_3,
KeyEvent.KEYCODE_BUTTON_4,
KeyEvent.KEYCODE_BUTTON_5,
KeyEvent.KEYCODE_BUTTON_6,
KeyEvent.KEYCODE_BUTTON_7,
KeyEvent.KEYCODE_BUTTON_8,
KeyEvent.KEYCODE_BUTTON_9,
KeyEvent.KEYCODE_BUTTON_10,
KeyEvent.KEYCODE_BUTTON_11,
KeyEvent.KEYCODE_BUTTON_12,
KeyEvent.KEYCODE_BUTTON_13,
KeyEvent.KEYCODE_BUTTON_14,
KeyEvent.KEYCODE_BUTTON_15,
KeyEvent.KEYCODE_BUTTON_16,
};
int[] masks = new int[] {
(1 << 0), // A -> A
(1 << 1), // B -> B
(1 << 2), // X -> X
(1 << 3), // Y -> Y
(1 << 4), // BACK -> BACK
(1 << 6), // MENU -> START
(1 << 5), // MODE -> GUIDE
(1 << 6), // START -> START
(1 << 7), // THUMBL -> LEFTSTICK
(1 << 8), // THUMBR -> RIGHTSTICK
(1 << 9), // L1 -> LEFTSHOULDER
(1 << 10), // R1 -> RIGHTSHOULDER
(1 << 11), // DPAD_UP -> DPAD_UP
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
(1 << 4), // SELECT -> BACK
(1 << 0), // DPAD_CENTER -> A
(1 << 15), // L2 -> ??
(1 << 16), // R2 -> ??
(1 << 17), // C -> ??
(1 << 18), // Z -> ??
(1 << 20), // 1 -> ??
(1 << 21), // 2 -> ??
(1 << 22), // 3 -> ??
(1 << 23), // 4 -> ??
(1 << 24), // 5 -> ??
(1 << 25), // 6 -> ??
(1 << 26), // 7 -> ??
(1 << 27), // 8 -> ??
(1 << 28), // 9 -> ??
(1 << 29), // 10 -> ??
(1 << 30), // 11 -> ??
(1 << 31), // 12 -> ??
// We're out of room...
0xFFFFFFFF, // 13 -> ??
0xFFFFFFFF, // 14 -> ??
0xFFFFFFFF, // 15 -> ??
0xFFFFFFFF, // 16 -> ??
};
boolean[] has_keys = joystickDevice.hasKeys(keys);
for (int i = 0; i < keys.length; ++i) {
if (has_keys[i]) {
button_mask |= masks[i];
}
}
return button_mask;
}
}
class SDLHapticHandler_API26 extends SDLHapticHandler {
@Override
public void run(int device_id, float intensity, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
if (intensity == 0.0f) {
stop(device_id);
return;
}
int vibeValue = Math.round(intensity * 255);
if (vibeValue > 255) {
vibeValue = 255;
}
if (vibeValue < 1) {
stop(device_id);
return;
}
try {
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
}
catch (Exception e) {
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
// something went horribly wrong with the Android 8.0 APIs.
haptic.vib.vibrate(length);
}
}
}
}
class SDLHapticHandler {
static class SDLHaptic {
public int device_id;
public String name;
public Vibrator vib;
}
private final ArrayList<SDLHaptic> mHaptics;
public SDLHapticHandler() {
mHaptics = new ArrayList<SDLHaptic>();
}
public void run(int device_id, float intensity, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.vibrate(length);
}
}
public void stop(int device_id) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.cancel();
}
}
public void pollHapticDevices() {
final int deviceId_VIBRATOR_SERVICE = 999999;
boolean hasVibratorService = false;
int[] deviceIds = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
for (int i = deviceIds.length - 1; i > -1; i--) {
SDLHaptic haptic = getHaptic(deviceIds[i]);
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);
}
}
}
/* Check VIBRATOR_SERVICE */
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vib != null) {
hasVibratorService = vib.hasVibrator();
if (hasVibratorService) {
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
if (haptic == null) {
haptic = new SDLHaptic();
haptic.device_id = deviceId_VIBRATOR_SERVICE;
haptic.name = "VIBRATOR_SERVICE";
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = null;
for (SDLHaptic haptic : mHaptics) {
int device_id = haptic.device_id;
int i;
for (i = 0; i < deviceIds.length; i++) {
if (device_id == deviceIds[i]) break;
}
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
if (i == deviceIds.length) {
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
}
} // else: don't remove the vibrator if it is still present
}
if (removedDevices != null) {
for (int device_id : removedDevices) {
SDLControllerManager.nativeRemoveHaptic(device_id);
for (int i = 0; i < mHaptics.size(); i++) {
if (mHaptics.get(i).device_id == device_id) {
mHaptics.remove(i);
break;
}
}
}
}
}
protected SDLHaptic getHaptic(int device_id) {
for (SDLHaptic haptic : mHaptics) {
if (haptic.device_id == device_id) {
return haptic;
}
}
return null;
}
}
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
// Generic Motion (mouse hover, joystick...) events go here
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
public boolean supportsRelativeMouse() {
return false;
}
public boolean inRelativeMode() {
return false;
}
public boolean setRelativeMouseEnabled(boolean enabled) {
return false;
}
public void reclaimRelativeMouseModeIfNeeded()
{
}
public float getEventX(MotionEvent event) {
return event.getX(0);
}
public float getEventY(MotionEvent event) {
return event.getY(0);
}
}
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
// Handle relative mouse mode
if (mRelativeModeEnabled) {
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_HOVER_MOVE) {
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
}
}
}
// Event was not managed, call SDLGenericMotionListener_API12 method
return super.onGenericMotion(v, event);
}
@Override
public boolean supportsRelativeMouse() {
return true;
}
@Override
public boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
mRelativeModeEnabled = enabled;
return true;
}
@Override
public float getEventX(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
} else {
return event.getX(0);
}
}
@Override
public float getEventY(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
} else {
return event.getY(0);
}
}
}
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
// DeX desktop mouse cursor is a separate non-standard input type.
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
case InputDevice.SOURCE_MOUSE_RELATIVE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
@Override
public boolean supportsRelativeMouse() {
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
}
@Override
public boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
if (enabled) {
SDLActivity.getContentView().requestPointerCapture();
} else {
SDLActivity.getContentView().releasePointerCapture();
}
mRelativeModeEnabled = enabled;
return true;
} else {
return false;
}
}
@Override
public void reclaimRelativeMouseModeIfNeeded()
{
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
SDLActivity.getContentView().requestPointerCapture();
}
}
@Override
public float getEventX(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getX(0);
}
@Override
public float getEventY(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getY(0);
}
}

View File

@@ -1,405 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
// Keep track of the surface size to normalize touch events
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
public boolean mIsSurfaceReady;
// Startup
public SDLSurface(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
setOnGenericMotionListener(SDLActivity.getMotionListener());
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f;
mHeight = 1.0f;
mIsSurfaceReady = false;
}
public void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
public void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public Surface getNativeSurface() {
return getHolder().getSurface();
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
SDLActivity.onNativeSurfaceCreated();
}
// Called when we lose the surface
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
SDLActivity.handleNativeState();
mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
// Called when the surface is resized
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
if (SDLActivity.mSingleton == null) {
return;
}
mWidth = width;
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
try
{
if (Build.VERSION.SDK_INT >= 17) {
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
}
} catch(Exception ignored) {
}
synchronized(SDLActivity.getContext()) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity.getContext().notifyAll();
}
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false;
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (mWidth > mHeight) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(mWidth, mHeight);
double max = Math.max(mWidth, mHeight);
if (max / min < 1.20) {
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
// Don't skip in MultiWindow.
if (skip) {
if (Build.VERSION.SDK_INT >= 24) {
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
Log.v("SDL", "Don't skip in Multi-Window");
skip = false;
}
}
}
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
mIsSurfaceReady = false;
return;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
/* Surface is ready */
mIsSurfaceReady = true;
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
}
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
}
// Touch events
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerFingerId;
int i = -1;
float x,y,p;
/*
* Prevent id to be -1, since it's used in SDL internal for synthetic events
* Appears when using Android emulator, eg:
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
*/
if (touchDevId < 0) {
touchDevId -= 1;
}
// 12290 = Samsung DeX mode desktop mouse
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
// 0x2 = SOURCE_CLASS_POINTER
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
int mouseButton = 1;
try {
Object object = event.getClass().getMethod("getButtonState").invoke(event);
if (object != null) {
mouseButton = (Integer) object;
}
} catch(Exception ignored) {
}
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event);
y = motionListener.getEventY(event);
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
} else {
switch(action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
/* fallthrough */
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
case MotionEvent.ACTION_CANCEL:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
default:
break;
}
}
return true;
}
// Sensor events
public void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME, null);
} else {
mSensorManager.unregisterListener(this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newOrientation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
break;
}
if (newOrientation != SDLActivity.mCurrentOrientation) {
SDLActivity.mCurrentOrientation = newOrientation;
SDLActivity.onNativeOrientationChanged(newOrientation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
// Captured pointer events for API 26.
public boolean onCapturedPointerEvent(MotionEvent event)
{
int action = event.getActionMasked();
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_BUTTON_RELEASE:
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(0);
y = event.getY(0);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
return false;
}
}

View File

@@ -437,7 +437,7 @@ set(vcmiclientcommon_HEADERS
UIHelper.h
)
if(APPLE_IOS)
if(IOS)
set(vcmiclientcommon_SRCS ${vcmiclientcommon_SRCS}
ios/utils.mm
)
@@ -461,7 +461,7 @@ if(NOT ENABLE_STATIC_LIBS)
add_dependencies(vcmiclientcommon Nullkiller2)
endif()
endif()
if(APPLE_IOS)
if(IOS)
if(ENABLE_ERM)
add_dependencies(vcmiclientcommon vcmiERM)
endif()
@@ -481,7 +481,7 @@ if(WIN32)
target_link_libraries(vcmiclientcommon SDL2::SDL2main)
endif()
target_compile_definitions(vcmiclientcommon PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
elseif(APPLE_IOS)
elseif(IOS)
target_link_libraries(vcmiclientcommon PRIVATE
iOS_utils

View File

@@ -281,10 +281,11 @@ void GameEngine::setStatusbar(const std::shared_ptr<IStatusBar> & newStatusBar)
currentStatusBar = newStatusBar;
}
void GameEngine::onScreenResize(bool resolutionChanged)
void GameEngine::onScreenResize(bool resolutionChanged, bool windowResized)
{
if(resolutionChanged)
screenHandler().onScreenResize();
if(!screenHandler().onScreenResize(windowResized))
return;
windows().onScreenResize();
ENGINE->cursor().onScreenResize();

View File

@@ -122,7 +122,7 @@ public:
[[noreturn]] void mainLoop();
/// called whenever SDL_WINDOWEVENT_RESTORED is reported or the user selects a different resolution, requiring to center/resize all windows
void onScreenResize(bool resolutionChanged);
void onScreenResize(bool resolutionChanged, bool windowResized);
/// Simulate mouse movement to force refresh UI state that updates on mouse move
void fakeMouseMove();

View File

@@ -572,9 +572,9 @@ bool AdventureMapShortcuts::optionCanToggleLevel()
return optionSidePanelActive() && GAME->interface()->cb->getMapSize().z > 1;
}
bool AdventureMapShortcuts::optionMapLevelSurface()
int AdventureMapShortcuts::optionMapLevel()
{
return mapLevel == 0;
return mapLevel;
}
bool AdventureMapShortcuts::optionHeroSleeping()

View File

@@ -84,7 +84,7 @@ public:
bool optionCanViewQuests();
bool optionCanToggleLevel();
bool optionMapLevelSurface();
int optionMapLevel();
bool optionHeroSleeping();
bool optionHeroAwake();
bool optionHeroSelected();

View File

@@ -30,7 +30,9 @@
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../../lib/callback/CCallback.h"
#include "../../lib/constants/StringConstants.h"
#include "../../lib/mapping/CMapHeader.h"
#include "../../lib/filesystem/ResourcePath.h"
AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
@@ -402,6 +404,8 @@ void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget)
{
auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
int mapLevels = GAME->interface()->cb->getMapHeader()->mapLevels;
if (container)
{
if (container->disableCondition == "heroAwake")
@@ -410,11 +414,14 @@ void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget)
if (container->disableCondition == "heroSleeping")
container->setEnabled(shortcuts->optionHeroSleeping());
if (container->disableCondition == "mapLayerSurface") // TODO: multilevel support
container->setEnabled(shortcuts->optionMapLevelSurface());
if (container->disableCondition == "mapLayerSurface")
container->setEnabled(shortcuts->optionMapLevel() == 0);
if (container->disableCondition == "mapLayerUnderground")
container->setEnabled(!shortcuts->optionMapLevelSurface());
container->setEnabled(shortcuts->optionMapLevel() == mapLevels - 1);
if (container->disableCondition == "mapLayerOther")
container->setEnabled(shortcuts->optionMapLevel() > 0 && shortcuts->optionMapLevel() < mapLevels - 1);
if (container->disableCondition == "mapViewMode")
container->setEnabled(shortcuts->optionInWorldView());

View File

@@ -248,17 +248,15 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
#ifndef VCMI_IOS
{
std::scoped_lock interfaceLock(ENGINE->interfaceMutex);
ENGINE->onScreenResize(false);
ENGINE->onScreenResize(false, false);
}
#endif
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
#ifdef VCMI_MOBILE
{
std::scoped_lock interfaceLock(ENGINE->interfaceMutex);
ENGINE->onScreenResize(true);
ENGINE->onScreenResize(true, true);
}
#endif
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
{

View File

@@ -97,7 +97,7 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
{
Settings full = settings.write["video"]["fullscreen"];
full->Bool() = !full->Bool();
ENGINE->onScreenResize(true);
ENGINE->onScreenResize(true, false);
}
if (vstd::contains(shortcutsVector, EShortcut::SPECTATE_TRACK_HERO))

View File

@@ -269,7 +269,7 @@ void MusicEntry::load(const AudioPath & musicURI)
auto * musicFile = MakeSDLRWops(std::move(stream));
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
}
catch(std::exception & e)
catch(const std::exception & e)
{
logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
logGlobal->error("Exception: %s", e.what());

View File

@@ -56,6 +56,8 @@ void AssetGenerator::initialize()
for(int i = 1; i < 9; i++)
imageFiles[ImagePath::builtin("CampaignHc" + std::to_string(i) + "Image.png")] = [this, i](){ return createChroniclesCampaignImages(i);};
animationFiles[AnimationPath::builtin("SPRITES/adventureLayersButton")] = createAdventureMapButton(ImagePath::builtin("adventureLayers.png"));
createPaletteShiftedSprites();
}
@@ -428,5 +430,108 @@ AssetGenerator::CanvasPtr AssetGenerator::createPaletteShiftedImage(const Animat
canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2));
return image;
}
void meanImage(AssetGenerator::CanvasPtr dst, std::vector<Canvas> & images)
{
auto image = dst->getCanvas();
for(int x = 0; x < dst->width(); x++)
for(int y = 0; y < dst->height(); y++)
{
int sumR = 0;
int sumG = 0;
int sumB = 0;
int sumA = 0;
for(auto & img : images)
{
auto color = img.getPixel(Point(x, y));
sumR += color.r;
sumG += color.g;
sumB += color.b;
sumA += color.a;
}
int ct = images.size();
image.drawPoint(Point(x, y), ColorRGBA(sumR / ct, sumG / ct, sumB / ct, sumA / ct));
}
}
AssetGenerator::CanvasPtr AssetGenerator::createAdventureMapButtonClear(const PlayerColor & player) const
{
auto imageNames = { "iam002", "iam003", "iam004", "iam005", "iam006", "iam007", "iam008", "iam009", "iam010", "iam011" };
std::vector<Canvas> images;
CanvasPtr dst = nullptr;
for(auto & imageName : imageNames)
{
auto animation = ENGINE->renderHandler().loadAnimation(AnimationPath::builtin(imageName), EImageBlitMode::COLORKEY);
animation->playerColored(player);
auto image = ENGINE->renderHandler().createImage(animation->getImage(2)->dimensions(), CanvasScalingPolicy::IGNORE);
if(!dst)
dst = ENGINE->renderHandler().createImage(animation->getImage(2)->dimensions(), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
canvas.draw(animation->getImage(2), Point(0, 0));
images.push_back(image->getCanvas());
}
meanImage(dst, images);
return dst;
}
AssetGenerator::AnimationLayoutMap AssetGenerator::createAdventureMapButton(const ImagePath & overlay)
{
std::shared_ptr<IImage> overlayImg = ENGINE->renderHandler().loadImage(ImageLocator(overlay, EImageBlitMode::OPAQUE));
auto overlayCanvasImg = ENGINE->renderHandler().createImage(overlayImg->dimensions(), CanvasScalingPolicy::IGNORE);
Canvas overlayCanvas = overlayCanvasImg->getCanvas();
overlayCanvas.draw(overlayImg, Point(0, 0));
AnimationLayoutMap layout;
for (PlayerColor color(0); color < PlayerColor::PLAYER_LIMIT; ++color)
{
auto clearButtonImg = createAdventureMapButtonClear(color);
for(int i = 0; i < 4; i++)
{
ImagePath spriteName = ImagePath::builtin(overlay.getOriginalName() + "Btn" + std::to_string(i) + ".png");
ImagePath spriteNameColor = ImagePath::builtin(overlay.getOriginalName() + "Btn" + std::to_string(i) + "-" + color.toString() + ".png");
imageFiles[spriteNameColor] = [overlayCanvasImg, clearButtonImg, i](){
auto newImg = ENGINE->renderHandler().createImage(overlayCanvasImg->dimensions(), CanvasScalingPolicy::IGNORE);
auto canvas = newImg->getCanvas();
canvas.draw(clearButtonImg, Point(0, 0));
switch (i)
{
case 0:
canvas.draw(overlayCanvasImg, Point(0, 0));
return newImg;
case 1:
canvas.draw(clearButtonImg, Point(1, 1));
canvas.draw(overlayCanvasImg, Point(1, 1));
canvas.drawLine(Point(0, 0), Point(newImg->width() - 1, 0), ColorRGBA(0, 0, 0), ColorRGBA(0, 0, 0));
canvas.drawLine(Point(0, 0), Point(0, newImg->height() - 1), ColorRGBA(0, 0, 0), ColorRGBA(0, 0, 0));
canvas.drawColorBlended(Rect(0, 0, newImg->width(), 4), ColorRGBA(0, 0, 0, 160));
canvas.drawColorBlended(Rect(0, 0, 4, newImg->height()), ColorRGBA(0, 0, 0, 160));
return newImg;
case 2:
canvas.drawTransparent(overlayCanvasImg->getCanvas(), Point(0, 0), 0.25);
return newImg;
default:
canvas.draw(overlayCanvasImg, Point(0, 0));
canvas.drawLine(Point(0, 0), Point(newImg->width() - 1, 0), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255));
canvas.drawLine(Point(newImg->width() - 1, 0), Point(newImg->width() - 1, newImg->height() - 1), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255));
canvas.drawLine(Point(newImg->width() - 1, newImg->height() - 1), Point(0, newImg->height() - 1), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255));
canvas.drawLine(Point(0, newImg->height() - 1), Point(0, 0), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255));
return newImg;
}
};
if(color == PlayerColor(0))
{
layout[0].push_back(ImageLocator(spriteName, EImageBlitMode::SIMPLE));
imageFiles[spriteName] = imageFiles[spriteNameColor];
}
}
}
return layout;
}

View File

@@ -53,6 +53,8 @@ private:
CanvasPtr createSpellTabNone() const;
CanvasPtr createChroniclesCampaignImages(int chronicle) const;
CanvasPtr createPaletteShiftedImage(const AnimationPath & source, const std::vector<PaletteAnimation> & animation, int frameIndex, int paletteShiftCounter) const;
CanvasPtr createAdventureMapButtonClear(const PlayerColor & player) const;
AnimationLayoutMap createAdventureMapButton(const ImagePath & overlay);
void createPaletteShiftedSprites();
void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector<PaletteAnimation> & animation);

View File

@@ -238,6 +238,13 @@ Rect Canvas::getRenderArea() const
return renderArea;
}
ColorRGBA Canvas::getPixel(const Point & position) const
{
SDL_Color color;
SDL_GetRGBA(CSDL_Ext::getPixel(surface, position.x, position.y), surface->format, &color.r, &color.g, &color.b, &color.a);
return ColorRGBA(color.r, color.g, color.b, color.a);
}
CanvasClipRectGuard::CanvasClipRectGuard(Canvas & canvas, const Rect & rect): surf(canvas.surface)
{
CSDL_Ext::getClipRect(surf, oldRect);

View File

@@ -122,6 +122,9 @@ public:
/// get the render area
Rect getRenderArea() const;
/// get pixel color
ColorRGBA getPixel(const Point & position) const;
};
class CanvasClipRectGuard : boost::noncopyable

View File

@@ -128,6 +128,10 @@ public:
/// Returns true if this image is still loading and can't be used
virtual bool isLoading() const = 0;
/// When disabled upscaling needs to be done in sync (e.g. because there is no 1x base image)
virtual void setAsyncUpscale(bool on) = 0;
virtual bool getAsyncUpscale() const = 0;
virtual ~ISharedImage() = default;
virtual const SDL_Palette * getPalette() const = 0;

View File

@@ -25,7 +25,7 @@ public:
virtual ~IScreenHandler() = default;
/// Updates window state after fullscreen state has been changed in settings
virtual void onScreenResize() = 0;
virtual bool onScreenResize(bool keepWindowResolution) = 0;
/// Fills screen with black color, erasing any existing content
virtual void clearScreen() = 0;

View File

@@ -322,6 +322,8 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato
img = std::make_shared<SDLImageShared>(imagePathData, optimizeImage);
else if(CResourceHandler::get()->existsResource(imagePath))
img = std::make_shared<SDLImageShared>(imagePath, optimizeImage);
else if(locator.scalingFactor == 1)
img = std::dynamic_pointer_cast<SDLImageShared>(assetGenerator->generateImage(imagePath));
if(img)
{
@@ -331,6 +333,9 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato
img = img->drawShadow((*locator.generateShadow) == SharedImageLocator::ShadowMode::SHADOW_SHEAR);
if(isOverlay && generateOverlay && (*locator.generateOverlay) == SharedImageLocator::OverlayMode::OVERLAY_OUTLINE)
img = img->drawOutline(Colors::WHITE, 1);
if(locator.scalingFactor == 1)
img->setAsyncUpscale(false); // no base image, needs to be done in sync
}
return img;

View File

@@ -271,7 +271,7 @@ std::shared_ptr<SDLImageShared> SDLImageShared::createScaled(const SDLImageShare
self->upscalingInProgress = false;
};
if(settings["video"]["asyncUpscaling"].Bool())
if(settings["video"]["asyncUpscaling"].Bool() && from->getAsyncUpscale())
ENGINE->async().run(scalingTask);
else
scalingTask();
@@ -284,6 +284,16 @@ bool SDLImageShared::isLoading() const
return upscalingInProgress;
}
void SDLImageShared::setAsyncUpscale(bool on)
{
asyncUpscale = on;
}
bool SDLImageShared::getAsyncUpscale() const
{
return asyncUpscale;
}
std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
{
if(upscalingInProgress)

View File

@@ -36,6 +36,7 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from
Point fullSize;
std::atomic_bool upscalingInProgress = false;
bool asyncUpscale = true;
// Keep the original palette, in order to do color switching operation
void savePalette();
@@ -63,6 +64,8 @@ public:
Rect contentRect() const override;
bool isLoading() const override;
void setAsyncUpscale(bool on) override;
bool getAsyncUpscale() const override;
const SDL_Palette * getPalette() const override;

View File

@@ -298,7 +298,6 @@ void ScreenHandler::updateWindowState()
Point resolution = getPreferredWindowResolution();
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
return;
}
}
@@ -480,9 +479,24 @@ SDL_Window * ScreenHandler::createWindow()
#endif
}
void ScreenHandler::onScreenResize()
bool ScreenHandler::onScreenResize(bool keepWindowResolution)
{
if(getPreferredWindowMode() == EWindowMode::WINDOWED && keepWindowResolution)
{
auto res = getRenderResolution();
if(res.x < heroes3Resolution.x || res.y < heroes3Resolution.y)
return false;
Settings video = settings.write["video"];
video["resolution"]["width"].Integer() = res.x;
video["resolution"]["height"].Integer() = res.y;
}
else if(keepWindowResolution)
return false;
recreateWindowAndScreenBuffers();
return true;
}
void ScreenHandler::validateSettings()

View File

@@ -100,7 +100,7 @@ public:
~ScreenHandler();
/// Updates and potentially recreates target screen to match selected fullscreen status
void onScreenResize() final;
bool onScreenResize(bool keepWindowResolution) final;
/// Fills screen with black color, erasing any existing content
void clearScreen() final;

View File

@@ -868,10 +868,10 @@ void CStackWindow::initBonusesList()
return info->stackNode->bonusToString(v1) < info->stackNode->bonusToString(v2);
};
// these bonuses require special handling. For example they come with own descriptions, for use in morale/luck description
// also, this information is already available in creature window
receivedBonuses.remove_if(Selector::type()(BonusType::MORALE));
receivedBonuses.remove_if(Selector::type()(BonusType::LUCK));
receivedBonuses.remove_if([](const Bonus* b)
{
return !LIBRARY->bth->shouldPropagateDescription(b->type);
});
std::vector<BonusList> groupedBonuses;
while (!receivedBonuses.empty())

View File

@@ -336,7 +336,7 @@ void GeneralOptionsTab::setGameResolution(int index)
widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(resolution.x, resolution.y));
ENGINE->dispatchMainThread([](){
ENGINE->onScreenResize(true);
ENGINE->onScreenResize(true, false);
});
}
@@ -360,7 +360,7 @@ void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive)
updateResolutionSelector();
ENGINE->dispatchMainThread([](){
ENGINE->onScreenResize(true);
ENGINE->onScreenResize(true, false);
});
}
@@ -419,7 +419,7 @@ void GeneralOptionsTab::setGameScaling(int index)
widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling));
ENGINE->dispatchMainThread([](){
ENGINE->onScreenResize(true);
ENGINE->onScreenResize(true, false);
});
}

View File

@@ -7,7 +7,7 @@ set(clientapp_HEADERS
StdInc.h
)
if(APPLE_IOS)
if(IOS)
set(clientapp_SRCS ${clientapp_SRCS}
CFocusableHelper.cpp
ios/GameChatKeyboardHandler.m
@@ -30,6 +30,7 @@ if(ANDROID)
)
else()
add_executable(vcmiclient ${clientapp_SRCS} ${clientapp_HEADERS})
vcmi_create_exe_shim(vcmiclient)
endif()
target_link_libraries(vcmiclient PRIVATE vcmiclientcommon)
@@ -55,16 +56,7 @@ if(WIN32)
target_link_libraries(vcmiclient SDL2::SDL2main)
endif()
target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
# TODO: very hacky, find proper solution to copy AI dlls into bin dir
if(MSVC)
add_custom_command(TARGET vcmiclient POST_BUILD
WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
)
endif()
elseif(APPLE_IOS)
elseif(IOS)
set_target_properties(vcmiclient PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/ios/Info.plist"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
@@ -89,8 +81,7 @@ endif()
vcmi_set_output_dir(vcmiclient "")
enable_pch(vcmiclient)
if(APPLE_IOS)
vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
if(IOS)
add_custom_command(TARGET vcmiclient POST_BUILD
COMMAND ios/set_build_version.sh "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}" --config "$<CONFIG>" --prefix "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
@@ -103,11 +94,10 @@ elseif(ANDROID)
find_program(androidDeployQt androiddeployqt
PATHS "${qtBinDir}"
)
vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
add_custom_target(android_deploy ALL
COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${androidQtBuildDir}"
COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
COMMAND ${CMAKE_COMMAND} -E env "ORG_GRADLE_PROJECT_SDL_JAVA_SRC_DIR=${SDL_JAVA_SRC_DIR}" --
"${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
COMMAND_EXPAND_LISTS
VERBATIM
COMMENT "Create android package"

View File

@@ -273,7 +273,7 @@ if(SDL2_LIBRARY)
# I think it has something to do with the CACHE STRING.
# So I use a temporary variable until the end so I can set the
# "real" variable in one-shot.
if(APPLE_MACOS)
if(MACOS)
set(SDL2_LIBRARIES ${SDL2_LIBRARIES} -framework Cocoa)
endif()
@@ -335,10 +335,10 @@ if(SDL2_FOUND)
if(APPLE)
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
# For more details, please see above.
if (APPLE_MACOS)
if (MACOS)
set_property(TARGET SDL2::SDL2 APPEND PROPERTY
INTERFACE_LINK_OPTIONS -framework Cocoa)
elseif (APPLE_IOS)
elseif (IOS)
target_link_libraries(SDL2::SDL2 INTERFACE
"-framework AudioToolbox"
"-framework AVFoundation"

View File

@@ -227,7 +227,7 @@ if(SDL2_IMAGE_FOUND)
IMPORTED_LOCATION "${SDL2_IMAGE_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_IMAGE_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES SDL2::SDL2)
if (APPLE_IOS)
if (IOS)
target_link_libraries(SDL2::Image INTERFACE
"-framework CoreGraphics"
"-framework Foundation"

View File

@@ -216,7 +216,7 @@ if(SDL2_MIXER_FOUND)
IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES SDL2::SDL2)
if (APPLE_IOS)
if (IOS)
target_link_libraries(SDL2::Mixer INTERFACE
"-framework AudioToolbox"
"-framework CoreServices"

View File

@@ -33,7 +33,7 @@ else()
set(VC_LIB_PATH_SUFFIX lib/x86)
endif()
if (NOT WIN32 AND NOT APPLE_IOS)
if (NOT WIN32 AND NOT IOS)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_MINIZIP minizip)

View File

@@ -118,25 +118,35 @@ function(vcmi_print_git_commit_hash)
endfunction()
#install imported target on windows
function(install_vcpkg_imported_tgt tgt)
get_target_property(TGT_LIB_LOCATION ${tgt} LOCATION)
get_filename_component(TGT_LIB_FOLDER ${TGT_LIB_LOCATION} PATH)
get_filename_component(tgt_name ${TGT_LIB_LOCATION} NAME_WE)
get_filename_component(TGT_DLL ${TGT_LIB_FOLDER}/../bin/${tgt_name}.dll ABSOLUTE)
message("${tgt_name}: ${TGT_DLL}")
install(FILES ${TGT_DLL} DESTINATION ${BIN_DIR})
endfunction(install_vcpkg_imported_tgt)
# install dependencies from Conan, install_dir should contain \${CMAKE_INSTALL_PREFIX}
function(vcmi_install_conan_deps install_dir)
# install dependencies from Conan, CONAN_RUNTIME_LIBS_FILE is set in conanfile.py
function(vcmi_install_conan_deps)
if(NOT USING_CONAN)
return()
endif()
install(CODE "
execute_process(COMMAND
conan imports \"${CMAKE_SOURCE_DIR}\" --install-folder \"${CONAN_INSTALL_FOLDER}\" --import-folder \"${install_dir}\"
)
file(REMOVE \"${install_dir}/conan_imports_manifest.txt\")
")
file(STRINGS "${CONAN_RUNTIME_LIBS_FILE}" runtimeLibs)
install(FILES ${runtimeLibs} DESTINATION ${LIB_DIR})
endfunction()
function(vcmi_deploy_qt deployQtToolName deployQtOptions)
# TODO: use qt_generate_deploy_app_script() with Qt 6
find_program(TOOL_DEPLOYQT NAMES ${deployQtToolName} PATHS "${qtBinDir}")
if(TOOL_DEPLOYQT)
install(CODE "
execute_process(COMMAND \"${TOOL_DEPLOYQT}\" ${deployQtOptions} -verbose=2)
")
else()
message(WARNING "${deployQtToolName} not found, running cpack would result in broken package")
endif()
endfunction()
# generate .bat for .exe with proper PATH
function(vcmi_create_exe_shim tgt)
if(NOT CONAN_RUNENV_SCRIPT)
return()
endif()
file(GENERATE OUTPUT "$<TARGET_FILE_DIR:${tgt}>/$<TARGET_FILE_BASE_NAME:${tgt}>.bat" CONTENT
"call ${CONAN_RUNENV_SCRIPT}
@start $<TARGET_FILE_NAME:${tgt}>"
)
endfunction()

View File

@@ -1,351 +1,54 @@
from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.apple import is_apple_os
from conan.tools.build import cross_building
from conan.tools.cmake import CMakeDeps, CMakeToolchain
from conans import tools
from dependencies.conanfile import VCMI
required_conan_version = ">=1.51.3"
from conan.tools.cmake import CMakeToolchain
from conan.tools.files import save
class VCMI(ConanFile):
settings = "os", "compiler", "build_type", "arch"
from glob import glob
import os
_libRequires = [
"boost/[^1.69]",
"minizip/[~1.2.12]",
]
_clientRequires = [
# Versions between 2.5-2.8 have broken loading of palette sdl images which a lot of mods use
# there is workaround that require disabling cmake flag which is not available in conan recipes.
# Bug is fixed in version 2.8, however it is not available in conan at the moment
"sdl_image/2.0.5",
"sdl_ttf/[>=2.0.18]",
"onetbb/[^2021.7 <2021.10]", # 2021.10+ breaks mobile builds due to added hwloc dependency
"xz_utils/[>=5.2.5]", # Required for innoextract
]
class VCMIApp(VCMI):
generators = "CMakeDeps"
requires = _libRequires + _clientRequires
def _pathForCmake(self, path: str) -> str:
# CMake doesn't like \ in strings
return path.replace(os.path.sep, os.path.altsep) if os.path.altsep else path
options = {
"default_options_of_requirements": [True, False],
"with_apple_system_libs": [True, False],
"with_ffmpeg": [True, False],
"with_luajit": [True, False],
}
default_options = {
"default_options_of_requirements": False,
"with_apple_system_libs": False,
"with_ffmpeg": True,
"with_luajit": False,
def _generateRuntimeLibsFile(self) -> str:
# create file with list of libs to copy to the package for distribution
runtimeLibsFile = self._pathForCmake(os.path.join(self.build_folder, "_runtime_libs.txt"))
"boost/*:shared": True,
"minizip/*:shared": True,
}
runtimeLibExtension = {
"Android": "so",
"iOS": "dylib",
"Macos": "dylib",
"Windows": "dll",
}.get(str(self.settings.os))
def configure(self):
self.options["ffmpeg"].shared = self.settings.os == "Android" # using shared version results in less total project size on Android
self.options["freetype"].shared = self.settings.os == "Android"
runtimeLibs = []
for _, dep in self.dependencies.host.items():
# Qt libs are copied using *deployqt
if dep.ref.name == "qt":
continue
# SDL_image and Qt depend on it, in iOS both are static
self.options["libpng"].shared = self.settings.os != "iOS"
# static Qt for iOS is the only viable option at the moment
self.options["qt"].shared = self.settings.os != "iOS"
runtimeLibDir = ''
if self.settings.os == "Windows":
if len(dep.cpp_info.bindirs) > 0:
runtimeLibDir = dep.cpp_info.bindir
elif len(dep.cpp_info.libdirs) > 0:
runtimeLibDir = dep.cpp_info.libdir
if len(runtimeLibDir) > 0:
runtimeLibs += map(self._pathForCmake, glob(os.path.join(runtimeLibDir, f"*.{runtimeLibExtension}")))
save(self, runtimeLibsFile, "\n".join(runtimeLibs))
# TODO: enable for all platforms
if self.settings.os == "Android":
self.options["bzip2"].shared = True
self.options["libiconv"].shared = True
self.options["zlib"].shared = True
# TODO: enable for all platforms?
if self.settings.os == "Windows":
self.options["sdl"].shared = True
self.options["sdl_image"].shared = True
self.options["sdl_mixer"].shared = True
self.options["sdl_ttf"].shared = True
if self.settings.os == "iOS":
# TODO: ios - newer sdl fails to link
self.requires("sdl/2.26.1")
self.requires("sdl_mixer/2.0.4")
elif self.settings.os == "Android":
# On Android SDL version must be same as version of Java wrapper for SDL in VCMI source code
# Wrapper can be found in following directory: android/vcmi-app/src/main/java/org/libsdl/app
self.requires("sdl/2.26.5")
self.requires("sdl_mixer/2.0.4")
else:
# upcoming SDL version 3.0+ is not supported at the moment due to API breakage
# SDL versions between 2.22-2.26.1 have broken sound
self.requires("sdl/[^2.26 || >=2.0.20 <=2.22.0]")
self.requires("sdl_mixer/[>=2.0.4]")
if self.settings.os == "Android":
self.options["qt"].android_sdk = tools.get_env("ANDROID_HOME", default="")
if self.options.default_options_of_requirements:
return
# we need only the following Boost parts:
# date_time filesystem iostreams locale program_options system thread
# some other parts are also enabled because they're dependents
# see e.g. conan-center-index/recipes/boost/all/dependencies
self.options["boost"].without_context = True
self.options["boost"].without_contract = True
self.options["boost"].without_coroutine = True
self.options["boost"].without_fiber = True
self.options["boost"].without_graph = True
self.options["boost"].without_graph_parallel = True
self.options["boost"].without_json = True
self.options["boost"].without_log = True
self.options["boost"].without_math = True
self.options["boost"].without_mpi = True
self.options["boost"].without_nowide = True
self.options["boost"].without_python = True
self.options["boost"].without_serialization = True
self.options["boost"].without_stacktrace = True
self.options["boost"].without_test = True
self.options["boost"].without_timer = True
self.options["boost"].without_type_erasure = True
self.options["boost"].without_wave = True
self.options["boost"].without_url = True
self.options["ffmpeg"].disable_all_bitstream_filters = True
self.options["ffmpeg"].disable_all_decoders = True
self.options["ffmpeg"].disable_all_demuxers = True
self.options["ffmpeg"].disable_all_encoders = True
self.options["ffmpeg"].disable_all_filters = True
self.options["ffmpeg"].disable_all_hardware_accelerators = True
self.options["ffmpeg"].disable_all_muxers = True
self.options["ffmpeg"].disable_all_parsers = True
self.options["ffmpeg"].disable_all_protocols = True
self.options["ffmpeg"].with_asm = False
self.options["ffmpeg"].with_bzip2 = False
self.options["ffmpeg"].with_freetype = False
self.options["ffmpeg"].with_libaom = False
self.options["ffmpeg"].with_libdav1d = False
self.options["ffmpeg"].with_libiconv = False
self.options["ffmpeg"].with_libmp3lame = False
self.options["ffmpeg"].with_libsvtav1 = False
self.options["ffmpeg"].with_libvpx = False
self.options["ffmpeg"].with_libwebp = False
self.options["ffmpeg"].with_libx264 = False
self.options["ffmpeg"].with_libx265 = False
self.options["ffmpeg"].with_lzma = True
self.options["ffmpeg"].with_openh264 = False
self.options["ffmpeg"].with_openjpeg = False
self.options["ffmpeg"].with_opus = False
self.options["ffmpeg"].with_programs = False
self.options["ffmpeg"].with_sdl = False
self.options["ffmpeg"].with_ssl = False
self.options["ffmpeg"].with_vorbis = False
self.options["ffmpeg"].with_zlib = False
if self.settings.os != "Android":
self.options["ffmpeg"].with_libfdk_aac = False
self.options["ffmpeg"].avcodec = True
self.options["ffmpeg"].avdevice = False
self.options["ffmpeg"].avfilter = False
self.options["ffmpeg"].avformat = True
self.options["ffmpeg"].postproc = False
self.options["ffmpeg"].swresample = True # For resampling of audio in 'planar' formats
self.options["ffmpeg"].swscale = True # For video scaling
# We want following options supported:
# H3:SoD - .bik and .smk
# H3:HD - ogg container / theora video / vorbis sound (not supported by vcmi at the moment, but might be supported in future)
# and for mods - webm container / vp8 or vp9 video / opus sound
# TODO: add av1 support for mods (requires enabling libdav1d which currently fails to build via Conan)
self.options["ffmpeg"].enable_protocols = "file"
self.options["ffmpeg"].enable_demuxers = "bink,binka,ogg,smacker,webm_dash_manifest"
self.options["ffmpeg"].enable_parsers = "opus,vorbis,vp8,vp9,webp"
self.options["ffmpeg"].enable_decoders = "bink,binkaudio_dct,binkaudio_rdft,smackaud,smacker,theora,vorbis,vp8,vp9,opus"
#optionally, for testing - enable ffplay/ffprobe binaries in conan package:
#if self.settings.os == "Windows":
# self.options["ffmpeg"].with_programs = True
# self.options["ffmpeg"].avfilter = True
# self.options["ffmpeg"].with_sdl = True
# self.options["ffmpeg"].enable_filters = "aresample,scale"
self.options["sdl"].sdl2main = self.settings.os != "iOS"
self.options["sdl"].vulkan = False
# bmp, png are the only ones that needs to be supported
# dds support may be useful for HD edition, but not supported by sdl_image at the moment
self.options["sdl_image"].gif = False
self.options["sdl_image"].lbm = False
self.options["sdl_image"].pnm = False
self.options["sdl_image"].pcx = False
#self.options["sdl_image"].qoi = False # sdl_image >=2.6
self.options["sdl_image"].svg = False
self.options["sdl_image"].tga = False
self.options["sdl_image"].with_libjpeg = False
self.options["sdl_image"].with_libtiff = False
self.options["sdl_image"].with_libwebp = False
self.options["sdl_image"].xcf = False
self.options["sdl_image"].xpm = False
self.options["sdl_image"].xv = False
if is_apple_os(self):
self.options["sdl_image"].imageio = True
# mp3, ogg and wav are the only ones that needs to be supported
# opus is nice to have, but fails to build in CI
# flac can be considered, but generally unnecessary
self.options["sdl_mixer"].flac = False
self.options["sdl_mixer"].modplug = False
self.options["sdl_mixer"].opus = False
if self.settings.os == "iOS" or self.settings.os == "Android":
# only available in older sdl_mixer version, removed in newer version
self.options["sdl_mixer"].mad = False
self.options["sdl_mixer"].mikmod = False
self.options["sdl_mixer"].nativemidi = False
def _disableQtOptions(disableFlag, options):
return " ".join([f"-{disableFlag}-{tool}" for tool in options])
_qtOptions = [
_disableQtOptions("no", [
"gif",
"ico",
]),
_disableQtOptions("no-feature", [
# xpm format is required for Drag'n'Drop support
"imageformat_bmp",
"imageformat_jpeg",
"imageformat_ppm",
"imageformat_xbm",
# we need only macdeployqt
# TODO: disabling these doesn't disable generation of CMake targets
# TODO: in Qt 6.3 it's a part of qtbase
# "assistant",
# "designer",
# "distancefieldgenerator",
# "kmap2qmap",
# "linguist",
# "makeqpf",
# "pixeltool",
# "qdbus",
# "qev",
# "qtattributionsscanner",
# "qtdiag",
# "qtpaths",
# "qtplugininfo",
]),
]
self.options["qt"].config = " ".join(_qtOptions)
self.options["qt"].qttools = True
self.options["qt"].qtandroidextras = self.settings.os == "Android" # TODO: in Qt 6 it's part of Core
self.options["qt"].with_freetype = self.settings.os == "Android"
self.options["qt"].with_libjpeg = False
self.options["qt"].with_md4c = False
self.options["qt"].with_mysql = False
self.options["qt"].with_odbc = False
self.options["qt"].with_openal = False
self.options["qt"].with_pq = False
self.options["qt"].openssl = not is_apple_os(self)
if self.settings.os == "iOS" or self.settings.os == "Android":
self.options["qt"].opengl = "es2"
if not is_apple_os(self) and self.settings.os != "Android" and cross_building(self):
self.options["qt"].cross_compile = self.env["CONAN_CROSS_COMPILE"]
# TODO: add for all platforms after updating recipe
if self.settings.os == "Android":
self.options["qt"].essential_modules = False
# No Qt OpenGL for cross-compiling for Windows, Conan does not support it
if self.settings.os == "Windows" and cross_building(self):
self.options["qt"].opengl = "no"
# transitive deps
# doesn't link to overridden bzip2 & zlib, the tool isn't needed anyway
self.options["pcre2"].build_pcre2grep = False
# executable not needed
if self.settings.os == "Android":
self.options["sqlite3"].build_executable = False
def requirements(self):
self.requires("freetype/[~2.12.1]", override=True) # sdl_ttf / Qt
self.requires("libpng/[~1.6.39]", override=True) # freetype / qt / sdl_image
# client
if self.options.with_ffmpeg:
self.requires("ffmpeg/[>=4.4]")
# launcher
if self.settings.os == "Android":
self.requires("qt/[~5.15.14]")
else:
self.requires("qt/[~5.15.2]")
# TODO: version range doesn't work in Conan v1
if self.options["qt"].openssl:
self.requires("openssl/1.1.1s")
# use Apple system libraries instead of external ones
if self.options.with_apple_system_libs and is_apple_os(self):
systemLibsOverrides = [
"bzip2/1.0.8",
"libiconv/1.17",
"sqlite3/3.39.2",
"zlib/1.2.12",
]
for lib in systemLibsOverrides:
self.requires(f"{lib}@vcmi/apple", override=True)
elif self.settings.os == "Android":
self.requires("zlib/1.2.12@vcmi/android", override=True)
else:
self.requires("zlib/[~1.2.13]", override=True) # minizip / Qt
self.requires("libiconv/[~1.17]", override=True) # ffmpeg / sdl
# TODO: the latest official release of LuaJIT (which is quite old) can't be built for arm
if self.options.with_luajit and not str(self.settings.arch).startswith("arm"):
self.requires("luajit/[~2.0.5]")
def validate(self):
if self.options.with_apple_system_libs and not is_apple_os(self):
raise ConanInvalidConfiguration("with_apple_system_libs is only for Apple platforms")
if self.options.with_apple_system_libs and self.options.default_options_of_requirements:
raise ConanInvalidConfiguration("with_apple_system_libs and default_options_of_requirements can't be True at the same time")
return runtimeLibsFile
def generate(self):
tc = CMakeToolchain(self)
tc.variables["USING_CONAN"] = True
tc.variables["CONAN_INSTALL_FOLDER"] = self.install_folder
tc.variables["CONAN_RUNTIME_LIBS_FILE"] = self._generateRuntimeLibsFile()
if self.settings.os == "Android":
tc.variables["CMAKE_ANDROID_API"] = str(self.settings.os.api_level)
tc.variables["ANDROID_SYSROOT_LIB_SUBDIR"] = {
'armv7': 'arm-linux-androideabi',
'armv8': 'aarch64-linux-android',
'x86': 'i686-linux-android',
'x86_64': 'x86_64-linux-android',
}.get(str(self.settings.arch))
if cross_building(self) and self.settings.os == "Windows":
tc.variables["CONAN_SYSTEM_LIBRARY_LOCATION"] = self.env["CONAN_SYSTEM_LIBRARY_LOCATION"]
tc.generate()
deps = CMakeDeps(self)
if tools.get_env("GENERATE_ONLY_BUILT_CONFIG", default=False):
deps.generate()
return
# allow using prebuilt deps with all configs
# credits to https://github.com/conan-io/conan/issues/11607#issuecomment-1188500937 for the workaround
configs = [
"Debug",
"MinSizeRel",
"Release",
"RelWithDebInfo",
]
for config in configs:
print(f"generating CMakeDeps for {config}")
deps.configuration = config
deps.generate()
def imports(self):
if is_apple_os(self):
self.copy("*.dylib", "Frameworks", "lib")
tc.variables["SDL_JAVA_SRC_DIR"] = os.path.join(self.dependencies.host["sdl"].package_folder, "share", "java", "SDL2")
elif self.settings.os == "Windows":
self.copy("*.dll", src="bin/archdatadir/plugins/platforms", dst="platforms")
self.copy("*.dll", src="bin/archdatadir/plugins/styles", dst="styles")
self.copy("*.dll", src="@bindirs", dst="", excludes="archdatadir/*")
elif self.settings.os == "Android":
self.copy("*.so", ".", "lib")
tc.variables["CONAN_RUNENV_SCRIPT"] = self._pathForCmake(os.path.join(self.build_folder, "conanrun.bat"))
tc.generate()

View File

@@ -1,4 +1,14 @@
{
"ARTIFACT_GROWING":
{
"blockDescriptionPropagation": true
},
"ARTIFACT_CHARGE":
{
"blockDescriptionPropagation": true
},
"ADDITIONAL_ATTACK":
{
},
@@ -10,6 +20,31 @@
"ATTACKS_ALL_ADJACENT":
{
},
"BASE_TILE_MOVEMENT_COST":
{
"blockDescriptionPropagation": true
},
"BATTLE_NO_FLEEING":
{
"blockDescriptionPropagation": true
},
"BEFORE_BATTLE_REPOSITION":
{
"blockDescriptionPropagation": true
},
"BEFORE_BATTLE_REPOSITION_BLOCK":
{
"blockDescriptionPropagation": true
},
"BIND_EFFECT":
{
"blockDescriptionPropagation": true
},
"BLOCKS_RANGED_RETALIATION":
{
@@ -39,11 +74,32 @@
"CHARGE_IMMUNITY":
{
},
"COMBAT_MANA_BONUS":
{
"blockDescriptionPropagation": true
},
"CREATURE_GROWTH":
{
"blockDescriptionPropagation": true
},
"CREATURE_GROWTH_PERCENT":
{
"blockDescriptionPropagation": true
},
"DARKNESS":
{
"hidden": true
},
"DISGUISED":
{
"hidden": true,
"blockDescriptionPropagation": true
},
"DEATH_STARE":
{
@@ -65,11 +121,6 @@
{
},
"DISGUISED":
{
"hidden": true
},
"ENCHANTER":
{
},
@@ -112,6 +163,17 @@
"bonusSubtype.movementTeleporting" : null,
}
},
"FLYING_MOVEMENT":
{
"blockDescriptionPropagation": true
},
"FREE_SHIP_BOARDING":
{
"blockDescriptionPropagation": true
},
"FREE_SHOOTING":
{
@@ -119,10 +181,12 @@
"FULL_MAP_DARKNESS":
{
"blockDescriptionPropagation": true
},
"FULL_MAP_SCOUTING":
{
"blockDescriptionPropagation": true
},
"GARGOYLE":
@@ -137,6 +201,11 @@
"bonusSubtype.damageTypeMelee" : null,
}
},
"GENERATE_RESOURCE":
{
"blockDescriptionPropagation": true
},
"HATE":
{
@@ -150,6 +219,21 @@
{
},
"HERO_EXPERIENCE_GAIN_PERCENT":
{
"blockDescriptionPropagation": true
},
"HERO_SPELL_CASTS_PER_COMBAT_TURN":
{
"blockDescriptionPropagation": true
},
"IMPROVED_NECROMANCY":
{
"blockDescriptionPropagation": true
},
"JOUSTING":
{
},
@@ -164,12 +248,19 @@
"LEARN_BATTLE_SPELL_CHANCE":
{
"hidden": true
"hidden": true,
"blockDescriptionPropagation": true
},
"LEARN_BATTLE_SPELL_LEVEL_LIMIT":
{
"hidden": true
"hidden": true,
"blockDescriptionPropagation": true
},
"LEARN_MEETING_SPELL_LIMIT":
{
"blockDescriptionPropagation": true
},
"LEVEL_SPELL_IMMUNITY":
@@ -188,6 +279,11 @@
{
"creatureNature" : true
},
"LUCK":
{
"blockDescriptionPropagation": true
},
"MANA_CHANNELING":
{
@@ -205,6 +301,26 @@
{
},
"MAGIC_SCHOOL_SKILL":
{
"blockDescriptionPropagation": true
},
"MANA_PERCENTAGE_REGENERATION":
{
"blockDescriptionPropagation": true
},
"MANA_PER_KNOWLEDGE_PERCENTAGE":
{
"blockDescriptionPropagation": true
},
"MAX_LEARNABLE_SPELL_LEVEL":
{
"blockDescriptionPropagation": true
},
"MECHANICAL":
{
"creatureNature" : true
@@ -214,6 +330,16 @@
{
},
"MORALE":
{
"blockDescriptionPropagation": true
},
"MOVEMENT":
{
"blockDescriptionPropagation": true
},
"NEGATIVE_EFFECTS_IMMUNITY" :
{
},
@@ -241,7 +367,8 @@
"NO_TERRAIN_PENALTY":
{
"hidden": true
"hidden": true,
"blockDescriptionPropagation": true
},
"NON_LIVING":
@@ -270,9 +397,24 @@
{
},
"PRIMARY_SKILL":
{
"blockDescriptionPropagation": true
},
"REBIRTH":
{
},
"RESOURCES_CONSTANT_BOOST":
{
"blockDescriptionPropagation": true
},
"RESOURCES_TOWN_MULTIPLYING_BOOST":
{
"blockDescriptionPropagation": true
},
"RETURN_AFTER_STRIKE":
{
@@ -282,6 +424,11 @@
{
},
"ROUGH_TERRAIN_DISCOUNT":
{
"blockDescriptionPropagation": true
},
"SIEGE_WEAPON":
{
"creatureNature" : true
@@ -299,6 +446,11 @@
{
},
"SIGHT_RADIUS":
{
"blockDescriptionPropagation": true
},
"SOUL_STEAL":
{
},
@@ -350,6 +502,11 @@
"SUMMON_GUARDIANS":
{
},
"SURRENDER_DISCOUNT":
{
"blockDescriptionPropagation": true
},
"TWO_HEX_ATTACK_BREATH":
{
@@ -370,25 +527,46 @@
"TRANSMUTATION_IMMUNITY":
{
},
"THIEVES_GUILD_ACCESS":
{
"blockDescriptionPropagation": true
},
"UNDEAD":
{
"creatureNature" : true,
},
"UNDEAD_RAISE_PERCENTAGE":
{
"blockDescriptionPropagation": true
},
"UNLIMITED_RETALIATIONS":
{
},
"VISIONS":
{
"hidden": true
"hidden": true,
"blockDescriptionPropagation": true
},
"VULNERABLE_FROM_BACK":
{
},
"WANDERING_CREATURES_JOIN_BONUS":
{
"blockDescriptionPropagation": true
},
"WATER_WALKING":
{
"blockDescriptionPropagation": true
},
"WIDE_BREATH":
{
},

View File

@@ -512,7 +512,8 @@
{
"type" : "LUCK",
"val" : 1,
"valueType" : "INDEPENDENT_MAX"
"valueType" : "INDEPENDENT_MAX",
"description" : "PLACEHOLDER"
}
},
"graphics" :
@@ -621,7 +622,8 @@
{
"type" : "NO_TERRAIN_PENALTY",
"subtype" : "terrain.sand",
"propagator" : "HERO"
"propagator" : "HERO",
"description" : "PLACEHOLDER"
}
},
"graphics" :
@@ -652,7 +654,8 @@
"subtype" : "visionsMonsters",
"val" : 3,
"valueType" : "INDEPENDENT_MAX",
"propagator" : "HERO"
"propagator" : "HERO",
"description" : "PLACEHOLDER"
},
"visionsHeroes" :
{
@@ -660,7 +663,8 @@
"subtype" : "visionsHeroes",
"val" : 3,
"valueType" : "INDEPENDENT_MAX",
"propagator" : "HERO"
"propagator" : "HERO",
"description" : "PLACEHOLDER"
},
"visionsTowns" :
{
@@ -668,7 +672,8 @@
"subtype" : "visionsTowns",
"val" : 3,
"valueType" : "INDEPENDENT_MAX",
"propagator" : "HERO"
"propagator" : "HERO",
"description" : "PLACEHOLDER"
}
},
"graphics" :

View File

@@ -15,6 +15,11 @@
"type" : "boolean",
"description" : "If set to true, this bonus will be considered 'creature nature' bonus, and such creature won't be automatically granted LIVING bonus"
},
"blockDescriptionPropagation" : {
"type" : "boolean",
"description" : "If set to true, this ability description will not be displayed if a creature receives it by propagation"
},
"description" : {
"type" : "string"

View File

@@ -144,6 +144,22 @@
}
]
},
{
"type": "adventureMapContainer",
"hideWhen" : "mapLayerOther",
"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 },
"items" : [
{
"type": "adventureMapButton",
"name": "worldViewOther",
"image" : "adventureLayersButton",
"help" : "core.help.294",
"hotkey": "adventureToggleMapLevel",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
}
]
},
{
"type": "adventureMapButton",
"name": "buttonQuestLog",
@@ -513,8 +529,8 @@
"items" : [
{
"type": "adventureMapButton",
"name": "worldViewSurface",
"image" : "IAM003.DEF",
"name": "worldViewUnderground",
"image" : "IAM010.DEF",
"hotkey": "adventureToggleMapLevel",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
@@ -536,8 +552,23 @@
"items" : [
{
"type": "adventureMapButton",
"name": "worldViewUnderground",
"image" : "IAM010.DEF",
"name": "worldViewSurface",
"image" : "IAM003.DEF",
"playerColored" : true,
"hotkey": "adventureToggleMapLevel",
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
}
]
},
{
"type": "adventureMapContainer",
"hideWhen" : "mapLayerOther",
"area": { "top" : 343, "left": 79, "width" : 32, "height" : 32 },
"items" : [
{
"type": "adventureMapButton",
"name": "worldViewOther",
"image" : "adventureLayersButton",
"playerColored" : true,
"hotkey": "adventureToggleMapLevel",
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }

1
dependencies Submodule

Submodule dependencies added at b6f03bd541

View File

@@ -6,35 +6,34 @@ RUN apt-get update && apt-get install -y openjdk-17-jdk python3 pipx cmake ccach
ENV PIPX_HOME="/opt/pipx"
ENV PIPX_BIN_DIR="/usr/local/bin"
ENV PIPX_MAN_DIR="/usr/local/share/man"
RUN pipx install 'conan<2.0'
RUN pipx install 'sdkmanager==0.6.10'
RUN pipx install 'conan'
RUN pipx install 'sdkmanager'
RUN conan profile new conan --detect
RUN conan profile detect
RUN wget https://github.com/vcmi/vcmi-dependencies/releases/download/1.3/dependencies-android-arm64-v8a.txz
RUN tar -xf dependencies-android-arm64-v8a.txz -C ~/.conan
RUN rm dependencies-android-arm64-v8a.txz
ENV DEPS_VERSION="2025-08-24"
ENV DEPS="dependencies-android-arm64-v8a.tgz"
RUN wget --https-only --max-redirect=20 https://github.com/vcmi/vcmi-dependencies/releases/download/$DEPS_VERSION/$DEPS
RUN conan cache restore $DEPS
RUN rm $DEPS
ENV JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
ENV ANDROID_HOME="/usr/lib/android-sdk"
ENV GRADLE_USER_HOME="/vcmi/.cache/grandle"
ENV GENERATE_ONLY_BUILT_CONFIG=1
ENV GRADLE_USER_HOME="/vcmi/.cache/gradle"
ENV NDK_VERSION="25.2.9519653"
RUN sdkmanager --install "platform-tools"
RUN sdkmanager --install "platforms;android-34"
RUN sdkmanager --install "platform-tools" "platforms;android-35" "ndk;$NDK_VERSION"
RUN yes | sdkmanager --licenses
RUN conan download android-ndk/r25c@:4db1be536558d833e52e862fd84d64d75c2b3656 -r conancenter
CMD ["sh", "-c", " \
# switch to mounted dir
cd /vcmi ; \
# install conan stuff
conan install . --install-folder=conan-generated --no-imports --build=never --profile:build=default --profile:host=CI/conan/android-64-ndk ; \
# link conan ndk that grandle can find it
mkdir -p /usr/lib/android-sdk/ndk ; \
ln -s -T ~/.conan/data/android-ndk/r25c/_/_/package/4db1be536558d833e52e862fd84d64d75c2b3656/bin /usr/lib/android-sdk/ndk/25.2.9519653 ; \
# prepare Gradle config
mkdir -p $GRADLE_USER_HOME ; \
echo android.bundle.enableUncompressedNativeLibs=true > $GRADLE_USER_HOME/gradle.properties ; \
# generate CMake toolchain
conan install . --output-folder=conan-generated --build=never --profile=dependencies/conan_profiles/android-64 --profile=dependencies/conan_profiles/base/android-system -c tools.android:ndk_path=$ANDROID_HOME/ndk/$NDK_VERSION && \
# build
cmake --preset android-daily-release ; \
cmake --preset android-daily-release && \
cmake --build --preset android-daily-release \
"]

View File

@@ -1,19 +1,19 @@
# Building Android
The following instructions apply to **v1.2 and later**. For earlier versions the best documentation is <https://github.com/vcmi/vcmi-android/blob/master/building.txt> (and reading scripts in that repo), however very limited to no support will be provided from our side if you wish to go down that rabbit hole.
The following instructions apply to **v1.7 and later**. For earlier versions see older Git revision, e.g. from [1.6.8 release](https://github.com/vcmi/vcmi/blob/1.6.8/docs/developers/Building_Android.md).
*Note*: building has been tested only on Linux and macOS. It may or may not work on Windows out of the box.
## Requirements
1. CMake 3.20+: download from your package manager or from <https://cmake.org/download/>
2. JDK 11, not necessarily from Oracle
1. CMake 3.26+: download from your package manager or from <https://cmake.org/download/>
2. JDK 17, not necessarily from Oracle
3. Android command line tools or Android Studio for your OS: <https://developer.android.com/studio/>
4. Android NDK version **r25c (25.2.9519653)**, there're multiple ways to obtain it:
4. Android NDK version **r25c (25.2.9519653)** (later version would also probably work), there're multiple ways to obtain it:
- recommended: download with Conan, especially if you're going to build VCMI dependencies from source, see [#NDK and Conan](#ndk-and-conan)
- install with Android Studio
- install with `sdkmanager` command line tool
- download from <https://developer.android.com/ndk/downloads>
- download with Conan, see [#NDK and Conan](#ndk-and-conan)
5. Optional:
- Ninja: download from your package manager or from <https://github.com/ninja-build/ninja/releases>
- Ccache: download from your package manager or from <https://github.com/ccache/ccache/releases>
@@ -32,15 +32,18 @@ We use Conan package manager to build/consume dependencies, find detailed usage
On the step where you need to replace **PROFILE**, choose:
- `android-32` to build for 32-bit architecture (armeabi-v7a)
- `android-64` to build for 64-bit architecture (aarch64-v8a)
- `android-32-ndk` to build for ARM 32-bit (armeabi-v7a)
- `android-64-ndk` to build for ARM 64-bit (aarch64-v8a)
- `android-x64-ndk` to build for Intel 64-bit (x86_64)
Advanced users may choose profile without `-ndk` suffix to use NDK that's already installed in their system.
### NDK and Conan
Conan must be aware of the NDK location when you execute `conan install`. There're multiple ways to achieve that as written in the [Conan docs](https://docs.conan.io/1/integrations/cross_platform/android.html):
Conan must be aware of the NDK location when you execute `conan install`. There're multiple ways to achieve that as written in the [Conan docs](https://docs.conan.io/2/examples/cross_build/android/ndk.html#examples-cross-build-android-ndk):
- the easiest is to download NDK from Conan (option 1 in the docs), then all the magic happens automatically. On the step where you need to replace **PROFILE**, choose *android-**X**-ndk* where ***X*** is either `32` or `64`.
- to use an already installed NDK, you can simply pass it on the command line to `conan install`: (note that this will work only when consuming the pre-built binaries)
- the easiest is to download NDK from Conan (option 1 in the docs), then all the magic happens automatically
- to use an already installed NDK, you can simply pass it on the command line to `conan install`: (note that this will likely work only when consuming the prebuilt binaries)
```sh
conan install -c tools.android:ndk_path=/path/to/ndk ...
@@ -48,37 +51,48 @@ conan install -c tools.android:ndk_path=/path/to/ndk ...
## Build process
Before starting the build, local Gradle configuration must be created (it's a requirement for Qt 5):
```sh
mkdir ~/.gradle
echo "android.bundle.enableUncompressedNativeLibs=true" > ~/.gradle/gradle.properties
```
Building for Android is a 2-step process. First, native C++ code is compiled to a shared library (unlike an executable on other platforms), then Java code is compiled to an actual executable which will be loading the native shared library at runtime.
This is a traditional CMake project, you can build it from command line or some IDE. You're not required to pass any custom options (except Conan toolchain file), defaults are already good. If you wish to use your own CMake presets, inherit them from our `build-with-conan` preset.
This is a traditional CMake project, you can build it from command line or some IDE. You're not required to pass any custom options (except Conan toolchain file), defaults are already good.
The Java code (located in the `android` directory of the repo) will be built automatically after the native code using the `androiddeployqt` tool. But you must set `JAVA_HOME` and `ANDROID_HOME` environment variables.
APK will appear in `<build dir>/android-build/vcmi-app/build/outputs/apk/debug` directory which you can then install to your device with `adb install -r /path/to/apk` (`adb` command is from Android command line tools).
APK will appear in `<build dir>/android-build/vcmi-app/build/outputs/apk/<build configuration>` directory which you can then install to your device with `adb install -r /path/to/apk` (`adb` command is from Android command line tools).
### Example
```sh
# the following environment variables must be set
export JAVA_HOME=/path/to/jdk11
export JAVA_HOME=/path/to/jdk17
export ANDROID_HOME=/path/to/android/sdk
cmake -S . -B ../build -G Ninja -D CMAKE_BUILD_TYPE=Debug -D ENABLE_CCACHE:BOOL=ON --toolchain ...
cmake --build ../build
```
You can also see a more detailed walkthrough on CMake configuration at [How to build VCMI (macOS)](./Building_macOS.md).
## Docker
For developing it's also possible to use Docker to build android APK. The only requirement is to have Docker installed. The container image contains all the other prerequisites.
To build using docker just open a terminal with `vcmi` as working directory.
To build using docker just open a terminal with `vcmi` repo root as working directory.
Build the image with (only needed once):
`docker build -f docker/BuildAndroid-aarch64.dockerfile -t vcmi-android-build .`
```sh
docker build -f docker/BuildAndroid-aarch64.dockerfile -t vcmi-android-build .
```
After building the image you can compile vcmi with:
`docker run -it --rm -v $PWD/:/vcmi vcmi-android-build`
The current dockerfile is aarch64 only but can adjusted manually for armv7.
```sh
docker run -it --rm -v $PWD/:/vcmi vcmi-android-build
```
The current dockerfile is aarch64 only but can be easily adjusted for other architectures.

View File

@@ -2,18 +2,17 @@
## Preparations
Windows builds can be made in more than one way and with more than one tool. This guide focuses on the simplest building process using Microsoft Visual Studio 2022
Windows builds can be made in more than one way and with more than one tool. This guide will show how to do it using Microsoft Visual Studio 2022 (MSVC compiler) and MSYS2 (MinGW compiler).
## Prerequisites
- Windows Vista or newer.
- [Microsoft Visual Studio](https://visualstudio.microsoft.com/downloads/)
- Git or git GUI, for example, SourceTree [download link](http://www.sourcetreeapp.com/download)
- CMake [download link](https://cmake.org/download/). During install after accepting license agreement make sure to check "Add CMake to the system PATH for all users".
- To unpack pre-build Vcpkg: [7-zip](http://www.7-zip.org/download.html)
- Optional:
- To create installer: [NSIS](http://nsis.sourceforge.net/Main_Page)
- To speed up recompilation: [CCache](https://github.com/ccache/ccache/releases)
1. Windows 7 or newer
2. [Microsoft Visual Studio](https://visualstudio.microsoft.com/downloads/)
3. Git or git GUI, for example, SourceTree [download link](http://www.sourcetreeapp.com/download)
4. CMake [download link](https://cmake.org/download/). During install after accepting license agreement make sure to check "Add CMake to the system PATH for all users". You can also install CMake from the Visual Studio Installer or from a package manager like [WinGet](https://github.com/microsoft/winget-cli).
5. Optional:
- To create installer: [Inno Setup](https://jrsoftware.org/isinfo.php)
- To speed up recompilation: [CCache](https://github.com/ccache/ccache/releases)
### Choose an installation directory
@@ -34,6 +33,111 @@ Bad locations:
## Install VCMI dependencies
This step is needed only for MSVC compiler. You can also find legacy Vcpkg instructions in the bottom of the document.
We use Conan package manager to build/consume dependencies, find detailed usage instructions [here](./Conan.md). Note that the link points to the state of the current branch, for the latest release check the same document in the [master branch](https://github.com/vcmi/vcmi/blob/master/docs/developers/Conan.md).
On the step where you need to replace **PROFILE**, choose:
- `msvc-x64` to build for Intel 64-bit (x64 / x86_64)
- `msvc-arm64` to build for ARM 64-bit (arm64)
- `msvc-x86` to build for Intel 32-bit (x86)
*Note*: we recommend using CMD (`cmd.exe`) for the next steps. If you absolutely want to use Powershell, then run `conan install` twice appending `-c tools.env.virtualenv:powershell=powershell.exe` on the second run.
## Install CCache
Extract `ccache` to a folder of your choosing, add the folder to the `PATH` environment variable and log out and back in.
## Build VCMI
### Clone VCMI repository
#### From Git GUI
1. Open SourceTree
2. File -> Clone
3. Select `https://github.com/vcmi/vcmi/` as source
4. Select `%VCMI_DIR%/source` as destination
5. Expand Advanced Options and change Checkout Branch to `develop`
6. Tick `Recursive submodules`
7. Click Clone
#### From command line
```sh
git clone --recursive https://github.com/vcmi/vcmi.git %VCMI_DIR%/source
```
### Compile VCMI with Visual Studio
#### Generate solution
1. Open command line prompt (`cmd.exe`)
2. Execute `cd %VCMI_DIR%`
3. Now you need to run a script* from the Conan directory that you passed to `conan install` (the one that you passed in `--output-folder` parameter). For example, if you passed `conan-msvc`, then the script will be in `source\conan-msvc`.
- for CMD: `source\conan-msvc\conanrun.bat`
- for Powershell: `source\conan-msvc\conanrun.ps1`. If it gives an error, also run `Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted`
4. Create VS solution: `cmake -S source -B build --toolchain source\conan-msvc\conan_toolchain.cmake`
\* This script sets up `PATH` required for Qt tools (`moc`, `uic` etc.) that run during CMake configure and build steps. Those tools depend on `zlib.dll` that was built with Conan, therefore its directory must be in `PATH`.
#### Build solution
You must launch Visual Studio in a modified `PATH` environment, see `*` in the previous subsection. You can launch it right from the current shell by pasting path to `devenv.exe` (Visual Studio executable, e.g. `"C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe"`). To launch it later with correct environment, you can create batch script (a file with `bat` extension) which you can double-click, here's an example (use your own path on the first line):
```batchfile
call "c:\Users\kamba\source\repos\vcmi\conan-msvc\conanrun.bat"
call "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe"
```
1. Open `%VCMI_DIR%/build/VCMI.sln` in Visual Studio
2. Select `RelWithDebInfo` build type in the combobox
3. If you want to use ccache:
- Select `Manage Configurations...` in the combobox
- Specify the following CMake variable: `ENABLE_CCACHE=ON`
- See the [Visual Studio documentation](https://learn.microsoft.com/en-us/cpp/build/customize-cmake-settings?view=msvc-170#cmake-variables-and-cache) for details
4. Right click on `BUILD_ALL` project. This `BUILD_ALL` project should be in `CMakePredefinedTargets` tree in Solution Explorer. You can also build individual targets if you want.
5. VCMI will be built in `%VCMI_DIR%/build/bin` folder
### Compile VCMI with MinGW via MSYS2
- Install MSYS2 from <https://www.msys2.org/>
- Start the `MSYS MinGW x64`-shell
- Install dependencies: `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-boost mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-static mingw-w64-x86_64-qt5-tools mingw-w64-x86_64-tbb`
- Generate and build solution from VCMI-root dir: `cmake --preset windows-mingw-release && cmake --build --preset windows-mingw-release`
**NOTE:** This will link Qt5 statically to `VCMI_launcher.exe` and `VCMI_Mapeditor.exe`. See [PR #3421](https://github.com/vcmi/vcmi/pull/3421) for some background.
## Troubleshooting and workarounds
Vcpkg might be very unstable due to limited popularity and fact of using bleeding edge packages (such as most recent Boost). Using latest version of dependencies could also expose both problems in VCMI code or library interface changes that developers not checked yet. So if you're built Vcpkg yourself and can't get it working please try to use binary package.
Pre-built version we provide is always manually tested with all supported versions of MSVC for both Release and Debug builds and all known quirks are listed below.
### Build is successful but can not start new game
Make sure you have:
* Installed Heroes III from disk or using GOG installer
* Copied `Data`, `Maps` and `Mp3` folders from Heroes III to: `%USERPROFILE%\Documents\My Games\vcmi\`
### VCMI won't run since some library is missing
**If you open solution using vcmi.sln** Try to build INSTALL target and see if its output works as expected. Copy missing libraries or even all libraries from there to your build directory. Or run cpack and use produced installer and see if you can get libs from there. cpack -V will give more info. If cpack complains that it can not find dumpbin try Visual Studio Command Prompt (special version of cmd provided with Visual Studio with additional PATH) or modify PATH to have this tool available. Another alternative if you use prebuilt vcpkg package is to download latest msvc build, install it and copy missing/all libraries from there.
### Debug build is very slow
Debug builds with MSVC are generally extremely slow since it's not just VCMI binaries are built as debug, but every single dependency too and this usually means no optimizations at all. Debug information that available for release builds is often sufficient so just avoid full debug builds unless absolutely necessary. Instead use RelWithDebInfo configuration, optionally with Optimization Disabled (/Od) to avoid variables being optimized away. Also Debug configuration might have some compilation issues because it is not checked via CI for now.
### I got crash within library XYZ.dll
VCPKG generated projects quite often have both debug and regular libs available to linker so it can select wrong lib. For stable RelWithDebInfo build you may try to remove debug folder from VCPKG/installed/x64-windows. Same is done on CI. Also it reduces package size at least twice.
## Legacy instructions for Vcpkg package manager
We have switched to the Conan package manager in v1.7. Legacy Vcpkg integration is no longer supported and may or may not work out of the box.
You have two options: to use pre-built libraries or build your own. We strongly recommend start with using pre-built ones.
### Option A. Use pre-built Vcpkg
@@ -43,7 +147,7 @@ You have two options: to use pre-built libraries or build your own. We strongly
Vcpkg Archives are available at our GitHub: <https://github.com/vcmi/vcmi-deps-windows/releases>
- Download latest version available.
EG: v1.6 assets - [vcpkg-export-x64-windows-v143.7z](https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-x64-windows-v143.7z)
EG: v1.6 assets - [vcpkg-export-x64-windows-v143.7z](https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-x64-windows-v143.7z)
- Extract archive by right clicking on it and choosing "7-zip -> Extract Here".
#### Move dependencies to target directory
@@ -83,82 +187,6 @@ git clone https://github.com/microsoft/vcpkg.git %VCMI_DIR%/vcpkg
For the list of the packages used you can also consult [vcmi-deps-windows readme](https://github.com/vcmi/vcmi-deps-windows) in case this article gets outdated a bit.
## Install CCache
### CMake integration
Extract `ccache` to a folder of your choosing, add the folder to the `PATH` environment variable and log out and back in.
## Build VCMI
#### From GIT GUI
- Open SourceTree
- File -> Clone
- select `https://github.com/vcmi/vcmi/` as source
- select `%VCMI_DIR%/source` as destination
- expand Advanced Options and change Checkout Branch to `develop`
- tick `Recursive submodules`
- click Clone
#### From command line
- `git clone --recursive https://github.com/vcmi/vcmi.git %VCMI_DIR%/source`
### Generate solution for VCMI
- Create `%VCMI_DIR%/build` folder
- Open a command line prompt at `%VCMI_DIR%/build`
- Execute `cd %VCMI_DIR%/build`
- Create solution (Visual Studio 2022 64-bit) `cmake %VCMI_DIR%/source -DCMAKE_TOOLCHAIN_FILE=%VCMI_DIR%/vcpkg/scripts/buildsystems/vcpkg.cmake -G "Visual Studio 17 2022" -A x64`
### Compile VCMI with Visual Studio
- Open `%VCMI_DIR%/build/VCMI.sln` in Visual Studio
- Select `Release` build type in the combobox
- If you want to use ccache:
- Select `Manage Configurations...` in the combobox
- Specify the following CMake variable: `ENABLE_CCACHE=ON`
- See the [Visual Studio documentation](https://learn.microsoft.com/en-us/cpp/build/customize-cmake-settings?view=msvc-170#cmake-variables-and-cache) for details
- Right click on `BUILD_ALL` project. This `BUILD_ALL` project should be in `CMakePredefinedTargets` tree in Solution Explorer.
- VCMI will be built in `%VCMI_DIR%/build/bin` folder!
### Compile VCMI with MinGW via MSYS2
- Install MSYS2 from <https://www.msys2.org/>
- Start the `MSYS MinGW x64`-shell
- Install dependencies: `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-boost mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-static mingw-w64-x86_64-qt5-tools mingw-w64-x86_64-tbb`
- Generate and build solution from VCMI-root dir: `cmake --preset windows-mingw-release && cmake --build --preset windows-mingw-release`
**NOTE:** This will link Qt5 statically to `VCMI_launcher.exe` and `VCMI_Mapeditor.exe`. See [PR #3421](https://github.com/vcmi/vcmi/pull/3421) for some background.
## Create VCMI installer (This step is not required for just building & development)
Make sure NSIS is installed to default directory or have registry entry so CMake can find it.
After you build VCMI execute following commands from `%VCMI_DIR%/build`.
- for release build: `cpack`
- for debug build: `cpack -C Debug`
## Troubleshooting and workarounds
Vcpkg might be very unstable due to limited popularity and fact of using bleeding edge packages (such as most recent Boost). Using latest version of dependencies could also expose both problems in VCMI code or library interface changes that developers not checked yet. So if you're built Vcpkg yourself and can't get it working please try to use binary package.
Pre-built version we provide is always manually tested with all supported versions of MSVC for both Release and Debug builds and all known quirks are listed below.
### Build is successful but can not start new game
Make sure you have:
* Installed Heroes III from disk or using GOG installer
* Copied `Data`, `Maps` and `Mp3` folders from Heroes III to: `%USERPROFILE%\Documents\My Games\vcmi\`
### VCMI won't run since some library is missing
**If you open solution using vcmi.sln** Try to build INSTALL target and see if its output works as expected. Copy missing libraries or even all libraries from there to your build directory. Or run cpack and use produced installer and see if you can get libs from there. cpack -V will give more info. If cpack complains that it can not find dumpbin try Visual Studio Command Prompt (special version of cmd provided with Visual Studio with additional PATH) or modify PATH to have this tool available. Another alternative if you use prebuilt vcpkg package is to download latest msvc build, install it and copy missing/all libraries from there.
### Debug build is very slow
Debug builds with MSVC are generally extremely slow since it's not just VCMI binaries are built as debug, but every single dependency too and this usually means no optimizations at all. Debug information that available for release builds is often sufficient so just avoid full debug builds unless absolutely necessary. Instead use RelWithDebInfo configuration, optionally with Optimization Disabled (/Od) to avoid variables being optimized away Also Debug configuration might have some compilation issues because it is not checked via CI for now.
### I got crash within library XYZ.dll
VCPKG generated projects quite often have both debug and regular libs available to linker so it can select wrong lib. For stable RelWithDebInfo build you may try to remove debug folder from VCPKG/installed/x64-windows. Same is done on CI. Also it reduces package size at least twice.
When executing `cmake` configure step, pass `--toolchain %VCMI_DIR%/vcpkg/scripts/buildsystems/vcpkg.cmake`

View File

@@ -18,10 +18,9 @@ git clone --recurse-submodules https://github.com/vcmi/vcmi.git
## Obtaining dependencies
There are 2 ways to get prebuilt dependencies:
The primary and officially supported way is [Conan package manager](./Conan.md). Note that the link points to the state of the current branch, for the latest release check the same document in the [master branch](https://github.com/vcmi/vcmi/blob/master/docs/developers/Conan.md).
- [Conan package manager](./Conan.md) - recommended. Note that the link points to the state of the current branch, for the latest release check the same document in the [master branch](https://github.com/vcmi/vcmi/blob/master/docs/developers/Conan.md).
- [legacy manually built libraries](https://github.com/vcmi/vcmi-ios-deps) - can be used if you have Xcode 11/12 or to build for simulator / armv7 device
There are also [legacy manually built libraries](https://github.com/vcmi/vcmi-ios-deps) which can be used if you have Xcode 11/12 or to build for simulator / armv7 device, but this way is no longer supported. Using Conan will also let you build with any Xcode version and for any architecture / SDK.
## Configuring project
@@ -30,9 +29,8 @@ Only Xcode generator (`-G Xcode`) is supported!
As a minimum, you must pass the following variables to CMake:
- `BUNDLE_IDENTIFIER_PREFIX`: unique bundle identifier prefix, something like `com.MY-NAME`
- (if using legacy dependencies) `CMAKE_PREFIX_PATH`: path to the downloaded dependencies, e.g. `~/Downloads/vcmi-ios-depends/build/iphoneos`
There're a few [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html): for device (Conan and legacy dependencies) and for simulator, named `ios-device-conan`, `ios-device` and `ios-simulator` respectively. You can also create your local "user preset" to avoid typing variables each time, see example [here](https://gist.github.com/kambala-decapitator/59438030c34b53aed7d3895aaa48b718).
There's a [CMake preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) for device named `ios-device-conan`. You can also create your local "user preset" to avoid typing variables each time, see example [here](https://gist.github.com/kambala-decapitator/59438030c34b53aed7d3895aaa48b718).
Open terminal and `cd` to the directory with source code. Configuration example for device with Conan:
@@ -76,4 +74,4 @@ Invoke `cpack` after building:
`cpack -C Release`
This will generate file with extension **ipa** if you use CMake 3.25+and **zip** otherwise (simply change extension to ipa).
This will generate file with extension **ipa** if you use CMake 3.25+ and **zip** otherwise (simply change extension to ipa).

View File

@@ -23,7 +23,9 @@ git clone --recurse-submodules https://github.com/vcmi/vcmi.git
There're 2 ways to get dependencies automatically.
### Conan package manager
### Conan package manager (recommended)
We use this to produce builds on CI.
Please find detailed instructions [here](./Conan.md). Note that the link points to the state of the current branch, for the latest release check the same document in the [master branch](https://github.com/vcmi/vcmi/blob/master/docs/developers/Conan.md).
@@ -32,47 +34,42 @@ On the step where you need to replace **PROFILE**, choose:
- if you're on an Intel Mac: `macos-intel`
- if you're on an Apple Silicon Mac: `macos-arm`
Note: if you wish to build 1.0 release in non-`Release` configuration, you should define `USE_CONAN_WITH_ALL_CONFIGS=1` environment variable when executing `conan install`.
### Homebrew
1. [Install Homebrew](https://brew.sh/)
2. Install dependencies: `brew install boost minizip sdl2 sdl2_image sdl2_mixer sdl2_ttf tbb`
3. If you want to watch in-game videos, also install FFmpeg: `brew install ffmpeg@4`
4. Install Qt dependency in either of the ways (note that you can skip this if you're not going to build Launcher):
3. If you want to watch in-game videos, also install FFmpeg: `brew install ffmpeg` (you can also use an earlier FFmpeg version)
4. Install Qt dependency in either of the ways (note that you can skip this if you're not going to build Launcher and Map editor):
- `brew install qt@5` for Qt 5 or `brew install qt` for Qt 6
- using [Qt Online Installer](https://www.qt.io/download) - choose **Go open source**
- using [Qt Online Installer](https://www.qt.io/download-qt-installer-oss)
## Preparing build environment
This applies only to Xcode-based toolchain. If `xcrun -f clang` prints errors, then use either of the following ways:
- select an Xcode instance from Xcode application - Preferences - Locations - Command Line Tools
- use `xcode-select` utility to set Xcode or Xcode Command Line Tools path: for example, `sudo xcode-select -s /Library/Developer/CommandLineTools`
- set `DEVELOPER_DIR` environment variable pointing to Xcode or Xcode Command Line Tools path: for example, `export DEVELOPER_DIR=/Applications/Xcode.app`
- use `xcode-select` utility to set Xcode or Xcode Command Line Tools path, example: `sudo xcode-select -s /Library/Developer/CommandLineTools`
- set `DEVELOPER_DIR` environment variable pointing to Xcode or Xcode Command Line Tools path, example: `export DEVELOPER_DIR=/Applications/Xcode.app`
## Configuring project for building
Note that if you wish to use Qt Creator IDE, you should skip this step and configure respective variables inside the IDE.
Note that if you wish to use Qt Creator or CLion IDE, you should skip this step and configure respective variables inside the IDE. Or you could create a [CMake preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) to avoid manual configuration.
The following walkthrough lists only the bare minimum of required CMake options.
1. In Terminal `cd` to the source code directory
2. Start assembling CMake invocation: type `cmake -S . -B BUILD_DIR` where *BUILD_DIR* can be any path, **don't press Return**
3. Decide which CMake generator you want to use:
- Makefiles: no extra option needed or pass `-G 'Unix Makefiles'`
- Ninja (if you have installed it): pass `-G Ninja`
- Xcode IDE (if you have installed it): pass `-G Xcode`
4. If you picked Makefiles or Ninja, pick desired *build type* - either of Debug / RelWithDebInfo / Release / MinSizeRel - and pass it in `CMAKE_BUILD_TYPE` option, for example: `-D CMAKE_BUILD_TYPE=Release`. If you don't pass this option, `RelWithDebInfo` will be used.
5. If you don't want to build Launcher, pass `-D ENABLE_LAUNCHER=OFF`
6. You can also pass `-Wno-dev` if you're not interested in CMake developer warnings
7. Next step depends on the dependency manager you have picked:
- Conan: pass `-D CMAKE_TOOLCHAIN_FILE=conan-generated/conan_toolchain.cmake` where **conan-generated** must be replaced with your directory choice
- Homebrew: if you installed FFmpeg or Qt 5, you need to pass `-D "CMAKE_PREFIX_PATH="` variable. See below what you can insert after `=` (but **before the closing quote**), multiple values must be separated with `;` (semicolon):
- if you installed FFmpeg, insert `$(brew --prefix ffmpeg@4)`
- if you installed Qt 5 from Homebrew, insert:`$(brew --prefix qt@5)`
- if you installed Qt from Online Installer, insert your path to Qt directory, for example: `/Users/kambala/dev/Qt-libs/5.15.2/Clang64`
- example for FFmpeg + Qt 5: `-D "CMAKE_PREFIX_PATH=$(brew --prefix ffmpeg@4);$(brew --prefix qt@5)"`
8. If you want to speed up the recompilation, add `-D ENABLE_CCACHE=ON`
9. Now press Return
- Ninja (if you have installed it): pass `-G Ninja`
- Makefiles: no extra option needed or pass `-G 'Unix Makefiles'`
4. If you picked Makefiles or Ninja, pick desired *build type* - either of `Debug` / `RelWithDebInfo` / `Release` / `MinSizeRel` - and pass it in `CMAKE_BUILD_TYPE` option, example: `-D CMAKE_BUILD_TYPE=Debug`. If you use don't pass this option, `RelWithDebInfo` will be used.
5. Next step depends on the dependency manager you have picked:
- Conan: pass `--toolchain conan-generated/conan_toolchain.cmake` (or via `CMAKE_TOOLCHAIN_FILE` variable) where **conan-generated** must be replaced with your directory choice
- Homebrew: if you installed Qt 5 or from the Online Installer, you need to pass `-D "CMAKE_PREFIX_PATH="` variable. See below what you can insert after `=` (but **before the closing quote**), multiple values must be separated with `;` (semicolon):
- if you installed Qt 5 from Homebrew, insert `$(brew --prefix qt@5)`
- if you installed Qt from Online Installer, insert your path to Qt directory, example: `/Users/kambala/dev/Qt-libs/5.15.2/Clang64`
6. Now press Return
## Building project
@@ -87,35 +84,13 @@ Open `VCMI.xcodeproj` from the build directory, select `vcmiclient` scheme and h
`cmake --build <path to build directory>`
- If using Makefiles generator, you'd want to utilize all your CPU cores by appending `-- -j$(sysctl -n hw.ncpu)` to the above
- If using Xcode generator, you can also choose which configuration to build by appending `--config <configuration name>` to the above, for example: `--config Debug`
## Packaging project into DMG file
After building, run `cpack` from the build directory. If using Xcode generator, also pass `-C <configuration name>` with the same configuration that you used to build the project.
If you use Conan, it's expected that you use **conan-generated** directory at step 4 of [Conan package manager](Conan.md).
- If using Xcode generator, you can also choose which configuration to build by appending `--config <configuration name>` to the above, example: `--config Debug`
## Running VCMI
You can run VCMI from DMG, but it's will also work from your IDE be it Xcode or Qt Creator.
Alternatively you can run binaries directly from the **bin** directory:
You can run binaries from your IDE or directly from the **bin** directory:
- BUILD_DIR/bin/vcmilauncher
- BUILD_DIR/bin/vcmiclient
- BUILD_DIR/bin/vcmiserver
CMake include commands to copy all needed assets from source directory into the **bin** directory on each build. They'll work when you build from Xcode too.
## Some useful debugging tips
Anyone who might want to debug builds, but new to macOS could find following commands useful:
- To attach DMG file from command line use `hdiutil attach vcmi-1.0.dmg`
- Detach volume: `hdiutil detach /Volumes/vcmi-1.0`
- To view dependency paths: `otool -L /Volumes/vcmi-1.0/VCMI.app/Contents/MacOS/vcmiclient`
- To display load commands such as `LC_RPATH`: `otool -l /Volumes/vcmi-1.0/VCMI.app/Contents/MacOS/vcmiclient`
## Troubleshooting
In case of troubles you can always consult our CI build scripts or contact the dev team via discord.
- BUILD_DIR/bin/vcmimapeditor

View File

@@ -6,137 +6,255 @@
The following platforms are supported and known to work, others might require changes to our [conanfile.py](../../conanfile.py) or upstream recipes.
- **macOS**: x86_64 (Intel) - target 10.13 (High Sierra), arm64 (Apple Silicon) - target 11.0 (Big Sur)
- **macOS**:
- x86_64 (Intel) - target 10.13 (High Sierra)
- arm64 (Apple Silicon) - target 11.0 (Big Sur)
- **iOS**: arm64 - target 12.0
- **Windows**: x86_64 and x86 fully supported with building from Linux
- **Android**: armeabi-v7a (32-bit ARM) - target 4.4 (API level 19), aarch64-v8a (64-bit ARM) - target 5.0 (API level 21)
- **Windows** using MSVC compiler:
- x86_64 (x64) - target Windows 7
- x86 - target Windows 7
- arm64 - target Windows 11
- **Android**:
- arvm7 / armeabi-v7a (32-bit ARM) - target 4.4 (API level 19)
- arm64 / aarch64-v8a (64-bit ARM) - target 5.0 (API level 21)
- x86_64 (64-bit Intel) - target 5.0 (API level 21)
## Getting started
1. [Install Conan](https://docs.conan.io/1/installation.html). Currently we support only Conan v1, you can install it with `pip` like this: `pip3 install 'conan<2.0'`
2. Execute in terminal: `conan profile new default --detect`
1. [Install Conan](https://docs.conan.io/2/installation.html). For example: `pip3 install conan`
2. Execute in terminal: `conan profile detect`. It will create *build profile* for your machine.
## Download dependencies
0. If your platform is not on the list of supported ones or you don't want to use our prebuilt binaries, you can still build dependencies from source or try consuming prebuilt binaries from the central Conan repository - [ConanCenter](https://conan.io/center/). In this case skip to the [next section](#generate-cmake-integration) directly.
1. Check if your build environment can use the prebuilt binaries: basically, that your compiler version (or Xcode major version) matches the information below. If you're unsure, simply advance to the next step.
- *macOS*: libraries are built with Apple clang 16 (Xcode 16.2), should be consumable by Xcode / Xcode CLT 14.x and later
- *iOS*: libraries are built with Apple clang 16 (Xcode 16.2), should be consumable by Xcode 14.x and later
- *Windows*: libraries are built with MSVC 19.4x (v143 toolset)
- *Android*: libraries are built with NDK r25c (25.2.9519653)
- **macOS**: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode / Xcode CLT 14.x and later (older library versions are also available for Xcode 13, see Releases in the respective repo)
- **iOS**: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode 14.x and later (older library versions are also available for Xcode 13, see Releases in the respective repo)
- **Windows**: libraries are built with x86_64-mingw-w64-gcc version 10 (which is available in repositories of Ubuntu 22.04)
- **Android**: libraries are built with NDK r25c (25.2.9519653)
2. Download the binaries archive from <https://github.com/vcmi/vcmi-dependencies/releases> (pre-release is for development version and the latest release is for respective VCMI release) and use `conan cache restore <path to archive>` command to unpack them.
- *macOS*: pick **dependencies-mac-intel.tgz** if you have Intel Mac, otherwise - **dependencies-mac-arm.tgz**
- *iOS*: pick **dependencies-ios.tgz**
- *Windows*: pick **dependencies-windows-x64.tgz** for Windows x64, **dependencies-windows-arm64.tgz** for Windows ARM64 or **dependencies-windows-x86.tgz** for Windows x86
- *Android*: pick **dependencies-android-arm64-v8a.tgz** for arm64 (ARM 64-bit), **dependencies-android-armeabi-v7a.tgz** for armv7 (ARM 32-bit) or **dependencies-android-x64.tgz** for x86_64 (Intel 64-bit)
2. Download the binaries archive and unpack it to `~/.conan` directory from <https://github.com/vcmi/vcmi-dependencies/releases/latest>
### Platform-specific preparation
- macOS: pick **dependencies-mac-intel.txz** if you have Intel Mac, otherwise - **dependencies-mac-arm.txz**
- iOS: pick ***dependencies-ios.txz***
- Windows: currently only mingw is supported. Pick **dependencies-mingw.tgz** if you want x86_64, otherwise pick **dependencies-mingw-32.tgz**
- Android: current limitation is that building works only on a macOS host due to Qt 5 for Android being compiled on macOS. Simply delete directory `~/.conan/data/qt/5.15.x/_/_/package` (`5.15.x` is a placeholder) after unpacking the archive and build Qt from source. Alternatively, if you have (or are [willing to build](https://github.com/vcmi/vcmi-ios-deps#note-for-arm-macs)) Qt host tools for your platform, then simply replace those in the archive with yours and most probably it would work.
Follow this subsection only if any of the following applies to you, otherwise skip to the [next section](#generate-cmake-integration) directly.
3. Only if you have Apple Silicon Mac and trying to build for macOS or iOS:
- you're trying to build for Android and your OS is Windows or macOS or Linux aarch64
- you're trying to build for iOS and you have Intel Mac
1. Open file `~/.conan/data/qt/5.15.x/_/_/export/conanfile.py` (`5.15.x` is a placeholder), search for string `Designer` (there should be only one match), comment this line and the one above it by inserting `#` in the beginning, and save the file.
2. (optional) If you don't want to use Rosetta, follow [instructions how to build Qt host tools for Apple Silicon](https://github.com/vcmi/vcmi-ios-deps#note-for-arm-macs), on step 3 copy them to `~/.conan/data/qt/5.15.x/_/_/package/SOME_HASH/bin` (`5.15.x` and `SOME_HASH` are placeholders). Make sure **not** to copy `qt.conf`!
Qt 5 has some utilities required for the build process (moc, uic etc.) that are built for your desktop OS. Our CI (GitHub Actions) makes Android builds on Ubuntu Linux amd64 and iOS builds on an Apple Silicon Mac, therefore those Qt utilities are built for Linux amd64 and macOS arm64 respectively and can't be used/run on other OS / CPU architectures. To solve that, you must add/replace those utilities built for your desktop OS.
Once you have the executable files of those utilities, copy them to the `bin` directory of Qt package. You can find the package at `~/.conan2/p/qt<some hash>/p`.
#### Option 1
The easiest way would be downloading prebuilt dependencies for your platform (Windows / macOS), unpacking the archive (using `conan cache restore` isn't required, unpack as an ordinary archive) and copying (or making a hard or soft link) executable files from `<unpacked dir>/b/qt<some hash>/p/bin` directory. We don't provide prebuilts for Linux - simply install Qt development package from your package manager and copy executables from its `bin` directory. But you can also build all the executables manually, see [below](#option-2).
However, Qt for Android requires one more executable that can't be found in your desktop Qt build - `androiddeployqt`. And you'll have to build it manually, see next subsection.
#### Option 2
Building all those executables is rather fast as it doesn't require building whole Qt. This is how you do it in Bash shell:
```sh
# set Qt version that you're going to download and build
qtVer=5.15.16
# for Android:
# ensure that ANDROID_HOME environment variable is set pointing to Android SDK directory
# also set Min SDK and NDK versions
minSdkVersion=21
ndkVersion=25.2.9519653
qtSrcDir="qt-everywhere-src-$qtVer"
qtInstallDir="$(pwd)/install"
# download Qt sources, unpack only the required subset of them
# use URL from a mirror if needed
curl -L "https://download.qt.io/official_releases/qt/5.15/$qtVer/single/qt-everywhere-opensource-src-$qtVer.tar.xz" \
| tar -xf - --xz "$qtSrcDir"/{'.*','LICENSE*','configure*',qt.pro,'qtbase/*','qttools/*'}
# create build directory
mkdir build
cd build
# configure Qt for building, also pass to the following command:
# for Android: -shared -xplatform android-clang -android-sdk "$ANDROID_HOME" -android-ndk "$ANDROID_HOME/ndk/$ndkVersion" -android-ndk-platform android-$minSdkVersion -android-abis arm64-v8a
# for iOS: -static -xplatform macx-ios-clang -no-framework
"../$qtSrcDir/configure" -prefix "$qtInstallDir" -opensource -confirm-license -release -strip -appstore-compliant -make libs -no-compile-examples -no-dbus -system-zlib -no-openssl -opengl es2 -no-gif -no-ico -nomake examples -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdeclarative -skip qtdoc -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats -skip qtlocation -skip qtlottie -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquick3d -skip qtquickcontrols -skip qtquickcontrols2 -skip qtquicktimeline -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtsvg -skip qttranslations -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebglplugin -skip qtwebsockets -skip qtwebview -skip qtwinextras -skip qtx11extras -skip qtxmlpatterns
make --jobs=$(getconf _NPROCESSORS_ONLN) module-qtbase-qmake_all module-qttools-qmake_all
# if you need to build just androiddeployqt, on the second line pass only: sub-androiddeployqt
make --directory=qtbase/src --jobs=$(getconf _NPROCESSORS_ONLN) \
sub-bootstrap sub-moc sub-qlalr sub-qvkgen sub-rcc sub-tracegen sub-uic sub-androiddeployqt
make --directory=qttools/src/linguist --jobs=$(getconf _NPROCESSORS_ONLN) \
sub-lconvert sub-lprodump sub-lrelease sub-lrelease-pro sub-lupdate sub-lupdate-pro
```
After that you'll find all the executables in `build/qtbase/bin` and `build/qttools/bin` directories.
#### Option 3
Simply build whole Qt from source.
1. Remove Qt binary package: `conan remove "qt/5.15.*"`
2. In the next section use `--build=missing`
## Generate CMake integration
Conan needs to generate CMake toolchain file to make dependencies available to CMake. See `CMakeDeps` and `CMakeToolchain` [in the official documentation](https://docs.conan.io/1/reference/conanfile/tools/cmake.html) for details.
Conan needs to generate CMake toolchain file to make dependencies available to CMake. See `CMakeDeps` and `CMakeToolchain` [in the official documentation](https://docs.conan.io/2/reference/tools/cmake.html) for details.
In terminal `cd` to the VCMI source directory and run the following command. Also check subsections for additional requirements on consuming prebuilt binaries.
Make sure that you've cloned VCMI repository with submodules! (or initialized them afterwards)
In terminal `cd` to the VCMI source directory and run the following command (it's written in Bash syntax, for other shells like Cmd or Powershell use appropriate line continuation character instead of `\` or type everything on a single line). Also check subsections for additional requirements on consuming prebuilt binaries.
*Note*: if you're going to build for Windows MSVC, it's recommended to use Cmd shell. If you absolutely want to use Powershell, then run the below command twice appending `-c tools.env.virtualenv:powershell=powershell.exe` on the second run.
<pre>
conan install . \
--install-folder=<b><i>conan-generated</i></b> \
--no-imports \
--output-folder=<b><i>conan-generated</i></b> \
--build=<b><i>never</i></b> \
--profile:build=default \
--profile:host=<b><i>CI/conan/PROFILE</i></b>
--profile=<b><i>dependencies/conan_profiles/PROFILE</i></b> \
<b><i>EXTRA PARAMS</i></b>
</pre>
The highlighted parts can be adjusted:
- ***conan-generated***: directory (absolute or relative) where the generated files will appear. This value is used in CMake presets from VCMI, but you can actually use any directory and override it in your local CMake presets.
- ***never***: use this value to avoid building any dependency from source. You can also use `missing` to build recipes, that are not present in your local cache, from source.
- ***CI/conan/PROFILE***: if you want to consume our prebuilt binaries, ***PROFILE*** must be replaced with one of filenames from our [Conan profiles directory](../../CI/conan) (determining the right file should be straight-forward). Otherwise, either select one of our profiles or replace ***CI/conan/PROFILE*** with `default` (your default profile).
- ***note for Windows x86***: use profile mingw32-linux.jinja for building instead of mingw64-linux.jinja
- ***never***: use this value to avoid building any dependency from source. You can also use `missing` to build binary packages, that are not present in your local cache, from source. There're also other values, see `conan install -h` or the full documentation linked below.
- ***dependencies/conan_profiles/PROFILE***: if you want to consume our prebuilt binaries, ***PROFILE*** must be replaced with one of filenames from our [Conan profiles directory](../../dependencies/conan_profiles) (determining the right file should be straight-forward). Otherwise, either select one of our profiles or replace ***CI/conan/PROFILE*** with `default` (your default profile, will build for your desktop OS) or create your own profile for the desired platform.
- ***EXTRA PARAMS***: additional params to the `conan install` command, you can specify multiple:
- if you want to consume our prebuilt binaries for Apple platforms (macOS / iOS), pass `--profile=dependencies/conan_profiles/base/apple-system`
- if you want to consume our prebuilt binaries for Android, pass `--profile=dependencies/conan_profiles/base/android-system`
- if your intention is to make a Debug build, pass `-s "&:build_type=RelWithDebInfo"` for Windows MSVC and `-s "&:build_type=Debug"` for other platforms
- if you're building on Windows 11 ARM64, pass `-o "&:lua_lib=lua"`
- if you're building on (or for) Windows < 10, pass `-o "&:target_pre_windows10=True"`
If you use `--build=never` and this command fails, then it means that you can't use prebuilt binaries out of the box. For example, try using `--build=missing` instead.
VCMI "recipe" also has some options that you can specify. For example, if you don't care about game videos, you can disable FFmpeg dependency by passing `-o with_ffmpeg=False`. If you only want to make release build, you can use `GENERATE_ONLY_BUILT_CONFIG=1` environment variable to skip generating files for other configurations (our CI does this).
VCMI "recipe" also has some options that you can specify. For example, if you don't care about game videos, you can disable FFmpeg dependency by passing `-o "&:with_ffmpeg=False"`. Check [the recipe](../../dependencies/conanfile.py) for details.
*Note*: you can find full reference of this command [in the official documentation](https://docs.conan.io/1/reference/commands/consumer/install.html) or by executing `conan help install`.
### Using our prebuilt binaries for macOS/iOS
We use custom recipes for some libraries that are provided by the OS. You must additionally pass `-o with_apple_system_libs=True` for `conan install` to use them.
*Note*: you can find full reference of this command [in the official documentation](https://docs.conan.io/2/reference/commands/install.html) or by executing `conan install -h`.
### Using prebuilt binaries from ConanCenter
First, check if binaries for [your platform](https://github.com/conan-io/conan-center-index/blob/master/docs/supported_platforms_and_configurations.md) are produced.
You must adjust the above `conan install` command:
1. Replace ***CI/conan/PROFILE*** with `default`.
2. Additionally pass `-o default_options_of_requirements=True`: this disables all custom options of our `conanfile.py` to match ConanCenter builds.
You must adjust the above `conan install` command by replacing ***dependencies/conan_profiles/PROFILE*** with `default` (or simply omit the `--profile` parameter).
### Building dependencies from source
This subsection describes platform specifics to build libraries from source properly.
This subsection describes platform specifics to build libraries from source properly. Commands that our CI uses to build the dependencies can also be used as a reference, you can find them inside the [`dependencies` submodule](../../dependencies/.github/workflows/rebuildDependencies.yml) or in the [repository](https://github.com/vcmi/vcmi-dependencies/blob/main/.github/workflows/rebuildDependencies.yml).
You can use our Conan profiles or create your own (e.g. with different options), the choice is yours.
*Note:* our profiles expect you to have CMake and Ninja in `PATH`, otherwise build would fail.
#### Building for macOS/iOS
- To build Locale module of Boost in versions >= 1.81, you must use `compiler.cppstd=11` Conan setting (our profiles already contain it). To use it with another profile, either add this setting to your *host* profile or pass `-s compiler.cppstd=11` on the command line.
- If you wish to build dependencies against system libraries (like our prebuilt ones do), follow [below instructions](#using-recipes-for-system-libraries) executing `conan create` for all directories. Don't forget to pass `-o with_apple_system_libs=True` to `conan install` afterwards.
If you wish to build dependencies against system libraries (like our prebuilts do), follow [below instructions](#using-recipes-for-system-libraries) executing `conan create` for all directories.
If you're going to build for iOS without using our profile: to build Qt 5 with `md4c` library, make sure to enforce this library's version 0.5 or later. This is what we have in our profile:
> [replace_requires]\
> md4c/0.4.8: md4c/0.5.2
#### Building for Android
It's highly recommended to use NDK recipe provided by Conan, otherwise build may fail. If you're using your own Conan profile, you can include our [NDK profile](../../dependencies/conan_profiles/base/android-ndk) via an additional `--profile` parameter on the command line.
Android has issues loading self-built shared zlib library because binary name is identical to the system one, so we enforce using the OS-provided library. To achieve that, follow [below instructions](#using-recipes-for-system-libraries), you only need `zlib` directory.
Also, Android requires a few Qt patches. They are needed only for specific use cases, so you may evaluate whether you need them. The patches can be found [here](https://github.com/kambala-decapitator/Qt5-iOS-patches/tree/master/5.15.14/android). To apply selected patch(es), let Conan finish building dependencies first. Then `cd` to `qtbase` **source directory** (e.g. `~/.conan/data/qt/5.15.14/_/_/source/qt5/qtbase`) and run `patch -p1 < /path/to/patch` for each patch file (on Windows you'll need some sort of GNU environment like Git Bash to access `patch` utility).
Also, Android requires a few patches, but they are needed only for specific use cases, so you may evaluate whether you need them. The patches can be found inside the [`dependencies` submodule](../../dependencies/conan_patches/qt/patches) or in the [repository](https://github.com/vcmi/vcmi-dependencies/blob/main/conan_patches/qt/patches).
1. [Safety measure for Xiaomi devices](https://github.com/kambala-decapitator/Qt5-iOS-patches/blob/master/5.15.14/android/xiaomi.diff). It's unclear whether it's really needed, but without it [I faced a crash once](https://bugreports.qt.io/browse/QTBUG-111960).
2. [Fix running on Android 5.0-5.1](https://github.com/kambala-decapitator/Qt5-iOS-patches/blob/master/5.15.14/android/android-21-22.diff) (API level 21-22).
3. [Enable running on Android 4.4](https://github.com/kambala-decapitator/Qt5-iOS-patches/blob/master/5.15.14/android/android-19-jar.diff) (API level 19-20, 32-bit only). You must also apply [one more patch](https://github.com/kambala-decapitator/Qt5-iOS-patches/blob/master/5.15.14/android/android-19-java.diff), but you must `cd` to the **package directory** for that, e.g. `~/.conan/data/qt/5.15.14/_/_/package/SOME_HASH`.
##### Qt patches
After applying patch(es):
1. [Safety measure for Xiaomi devices](https://github.com/vcmi/vcmi-dependencies/blob/main/conan_patches/qt/patches/xiaomi.diff). It's unclear whether it's really needed, but without it [I faced a crash once](https://bugreports.qt.io/browse/QTBUG-111960).
2. [Fix running on Android 5.0-5.1](https://github.com/vcmi/vcmi-dependencies/blob/main/conan_patches/qt/patches/android-21-22.diff) (API level 21-22).
3. Enable running on Android 4.4 (API level 19-20, 32-bit only): [patch 1](https://github.com/vcmi/vcmi-dependencies/blob/main/conan_patches/qt/patches/android-19-jar.diff), [patch 1](https://github.com/vcmi/vcmi-dependencies/blob/main/conan_patches/qt/patches/android-19-java.diff).
1. `cd` to `qtbase/src/android/jar` in the **build directory**, e.g. `~/.conan/data/qt/5.15.14/_/_/build/SOME_HASH/build_folder/qtbase/src/android/jar`.
2. Run `make`
3. Copy file `qtbase/jar/QtAndroid.jar` from the build directory to the **package directory**, e.g. `~/.conan/data/qt/5.15.14/_/_/package/SOME_HASH/jar`.
##### Patches for other libraries
*Note*: if you plan to build Qt from source again, then you don't need to perform the above *After applying patch(es)* steps after building.
- Flac requires patch to be able to build it for 32-bit targeting API Level < 24
- Minizip requires patch to be able to build it for 32-bit targeting API Level < 21
##### Using recipes for system libraries
Also, to build Flac, Luajit and Opusfile for 32-bit targeting API Level < 24, a couple of C defines must be added to your Conan profile - see our [profile for ARM 32-bit](https://github.com/vcmi/vcmi-dependencies/blob/main/conan_profiles/android-32).
#### Using recipes for system libraries
1. Clone/download <https://github.com/kambala-decapitator/conan-system-libs>
2. Execute `conan create PACKAGE vcmi/CHANNEL`, where `PACKAGE` is a directory path in that repository and `CHANNEL` is **apple** for macOS/iOS and **android** for Android. Do it for each library you need.
3. Now you can execute `conan install` to build all dependencies.
2. Execute `conan create PACKAGE --user system`, where `PACKAGE` is a directory path in that repository. Do it for each library you need. (basically just read repo's readme)
#### Build it
First, you must build separate libraries with `conan create` if:
- you chose to use patches (ours listed above or your own) for a library. You must also pass `--core-conf core.sources.patch:extra_path=<patches path>` parameter where `<patches path>` is the path to the patches directory, ours is located at [dependencies/conan_patches](../../dependencies/conan_patches).
- you want to build LuaJIT for iOS or Android (they also require patches, see the above point). Upstream Conan recipe doesn't support this yet (but there's a [pull request](https://github.com/conan-io/conan-center-index/pull/26577)), so you'll have to use [fork](https://github.com/kambala-decapitator/conan-center-index/tree/package/luajit) where it works. *Note*: to build for 32-bit architecture (e.g. Android armv7) your OS must be able to run 32-bit executables, see [this issue](https://github.com/LuaJIT/LuaJIT/issues/664) for details (for example, macOS since 10.15 can't do that); on Linux amd64 you'll have to install `libc6-dev-i386` package.
After that you can execute `conan install` to build the rest of the dependencies.
## Configure project for building
You must pass the generated toolchain file to CMake invocation.
- if using custom CMake presets, just make sure to inherit our `build-with-conan` preset. If you store Conan generated files in a non-default directory, define the path to the generated toolchain in `toolchainFile` field (or `CMAKE_TOOLCHAIN_FILE` cache variable) or include CMake presets file generated by Conan.
- if using custom CMake presets, define the path to the generated toolchain in `toolchainFile` field (or `CMAKE_TOOLCHAIN_FILE` cache variable) or include CMake presets file generated by Conan.
- otherwise, if passing CMake options on the command line, use `--toolchain` option (available in CMake 3.21+) or `CMAKE_TOOLCHAIN_FILE` variable.
## Examples
In these examples only the minimum required amount of options is passed to `cmake` invocation, you can pass additional ones as needed.
### Use our prebuilt binaries to build for macOS x86_64 with Xcode
### Use our prebuilt binaries to build for Windows x64 with Visual Studio (CMD shell)
```batchfile
conan install . ^
--output-folder=conan-msvc ^
--build=never ^
--profile=dependencies\conan_profiles\msvc-x64 ^
-s "&:build_type=RelWithDebInfo"
REM this is important!
conan-msvc\conanrun.bat
cmake -S . -B build ^
--toolchain conan-msvc\conan_toolchain.cmake
```
### Use our prebuilt binaries to build for macOS arm64 with Xcode
```sh
conan install . \
--install-folder=conan-generated \
--no-imports \
--output-folder=conan-macos \
--build=never \
--profile:build=default \
--profile:host=CI/conan/macos-intel \
-o with_apple_system_libs=True
--profile=dependencies/conan_profiles/macos-arm \
--profile=dependencies/conan_profiles/base/apple-system \
-s "&:build_type=Debug"
cmake -S . -B build -G Xcode \
--toolchain conan-generated/conan_toolchain.cmake
--toolchain conan-macos/conan_toolchain.cmake
```
### Use our prebuilt binaries to build for Android arm64 with Ninja
```sh
conan install . \
--output-folder=conan-android64 \
--build=never \
--profile=dependencies/conan_profiles/android-64-ndk \
--profile=dependencies/conan_profiles/base/android-system \
-s "&:build_type=Debug"
cmake -S . -B build -G Ninja \
--toolchain conan-android64/conan_toolchain.cmake
```
### Try to use binaries from ConanCenter for your platform
@@ -146,11 +264,8 @@ If you also want to build the missing binaries from source, use `--build=missing
```sh
conan install . \
--install-folder=~/my-dir \
--no-imports \
--build=never \
--profile:build=default \
--profile:host=default \
-o default_options_of_requirements=True
-s "&:build_type=Debug"
cmake -S . -B build \
-D CMAKE_TOOLCHAIN_FILE=~/my-dir/conan_toolchain.cmake
@@ -160,12 +275,11 @@ cmake -S . -B build \
```sh
conan install . \
--install-folder=~/my-dir \
--no-imports \
--output-folder=conan-ios \
--build=never \
--profile:build=default \
--profile:host=CI/conan/ios-arm64 \
-o with_apple_system_libs=True
--profile=dependencies/conan_profiles/ios-arm64 \
--profile=dependencies/conan_profiles/base/apple-system \
-s "&:build_type=Debug"
cmake --preset ios-conan
```
@@ -184,8 +298,8 @@ cmake --preset ios-conan
{
"name": "ios-conan",
"displayName": "iOS",
"inherits": ["build-with-conan", "ios-device"],
"toolchainFile": "~/my-dir/conan_toolchain.cmake",
"inherits": ["ios-device"],
"toolchainFile": "conan-ios/conan_toolchain.cmake",
"cacheVariables": {
"BUNDLE_IDENTIFIER_PREFIX": "com.YOUR-NAME",
"CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM": "YOUR_TEAM_ID"
@@ -194,32 +308,3 @@ cmake --preset ios-conan
]
}
```
### Build VCMI with all deps for 32-bit windows in Ubuntu 22.04 WSL
```powershell
wsl --install
wsl --install -d Ubuntu
ubuntu
```
Next steps are identical both in WSL and in real Ubuntu 22.04
```sh
sudo pip3 install conan
sudo apt install cmake build-essential
sed -i 's/x86_64-w64-mingw32/i686-w64-mingw32/g' CI/mingw-ubuntu/before-install.sh
sed -i 's/x86-64/i686/g' CI/mingw-ubuntu/before-install.sh
sudo ./CI/mingw-ubuntu/before-install.sh
conan install . \
--install-folder=conan-generated \
--no-imports \
--build=missing \
--profile:build=default \
--profile:host=CI/conan/mingw32-linux \
-c tools.cmake.cmaketoolchain.presets:max_schema_version=2
cmake --preset windows-mingw-conan-linux
cmake --build --preset windows-mingw-conan-linux --target package
```
After that, you will have functional VCMI installer for 32-bit windows.

View File

@@ -1,4 +1,6 @@
if(ENABLE_INNOEXTRACT)
# libiconv not required for our use case
set(WITH_CONV "builtin" CACHE STRING "" FORCE)
add_subdirectory("lib/innoextract")
endif()
@@ -25,7 +27,7 @@ set(launcher_SRCS
updatedialog_moc.cpp
prepare.cpp
)
if(APPLE_IOS)
if(IOS)
list(APPEND launcher_SRCS
ios/launchGame.m
ios/revealdirectoryinfiles.h
@@ -153,6 +155,7 @@ if(ENABLE_SINGLE_APP_BUILD OR ANDROID)
add_library(vcmilauncher OBJECT ${launcher_QM})
else()
add_executable(vcmilauncher WIN32 ${launcher_QM} ${launcher_ICON})
vcmi_create_exe_shim(vcmilauncher)
endif()
if(ENABLE_TRANSLATIONS)
@@ -222,7 +225,7 @@ endif()
if(ANDROID)
target_link_libraries(vcmilauncher Qt${QT_VERSION_MAJOR}::AndroidExtras)
elseif(NOT APPLE_IOS)
elseif(NOT IOS)
target_link_libraries(vcmilauncher SDL2::SDL2)
endif()
@@ -242,17 +245,12 @@ if(ENABLE_INNOEXTRACT)
target_link_libraries(vcmilauncher innoextract)
endif()
if(APPLE_IOS)
if(IOS)
target_link_libraries(vcmilauncher
iOS_utils
"-framework UniformTypeIdentifiers"
)
# TODO: remove after switching prebuilt deps to a newer Conan's Qt recipe
if(XCODE_VERSION VERSION_GREATER_EQUAL 14.0)
target_link_libraries(vcmilauncher "-framework IOKit")
endif()
# workaround https://github.com/conan-io/conan-center-index/issues/13332
if(USING_CONAN)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/QIOSIntegrationPlugin.h

File diff suppressed because it is too large Load Diff

View File

@@ -142,6 +142,7 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con
dest.identifier = name;
dest.hidden = source["hidden"].Bool(); //Null -> false
dest.creatureNature = source["creatureNature"].Bool(); //Null -> false
dest.blockDescriptionPropagation = source["blockDescriptionPropagation"].Bool(); //Null -> false
if (!dest.hidden)
LIBRARY->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"]);
@@ -196,6 +197,12 @@ bool CBonusTypeHandler::isCreatureNatureBonus(BonusType bonus) const
return bonusTypes.at(static_cast<int>(bonus))->creatureNature;
}
bool CBonusTypeHandler::shouldPropagateDescription(BonusType bonus) const
{
return !bonusTypes.at(static_cast<int>(bonus))->blockDescriptionPropagation;
}
std::vector<BonusType> CBonusTypeHandler::getAllObjets() const
{
std::vector<BonusType> ret;

View File

@@ -38,6 +38,7 @@ private:
bool creatureNature = false;
bool hidden = true;
bool blockDescriptionPropagation = false;
};
class DLL_LINKAGE CBonusTypeHandler : public IBonusTypeHandler
@@ -57,6 +58,7 @@ public:
const std::string & bonusToString(BonusType bonus) const;
bool isCreatureNatureBonus(BonusType bonus) const;
bool shouldPropagateDescription(BonusType bonus) const;
std::vector<BonusType> getAllObjets() const;
private:

View File

@@ -847,11 +847,11 @@ endif()
# no longer necessary, but might be useful to keep in future
# unfortunately at the moment tests do not support namespaced build, so enable only on some systems
if(APPLE_IOS OR ANDROID)
if(IOS OR ANDROID)
target_compile_definitions(vcmi PUBLIC VCMI_LIB_NAMESPACE=VCMI)
endif()
if(APPLE_IOS)
if(IOS)
target_link_libraries(vcmi PUBLIC iOS_utils)
endif()
@@ -902,7 +902,7 @@ if(NOT ENABLE_STATIC_LIBS)
install(TARGETS vcmi RUNTIME DESTINATION ${LIB_DIR} LIBRARY DESTINATION ${LIB_DIR})
endif()
if(APPLE_IOS AND NOT USING_CONAN)
if(IOS AND NOT USING_CONAN)
install(IMPORTED_RUNTIME_ARTIFACTS TBB::tbb LIBRARY DESTINATION ${LIB_DIR}) # CMake 3.21+
get_target_property(LINKED_LIBS vcmi LINK_LIBRARIES)

View File

@@ -246,7 +246,6 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
{
int mask = 0;
BattleHexArray res;
if (!attackerPos.isValid())
@@ -254,43 +253,24 @@ BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const bat
if (!defenderPos.isValid())
defenderPos = defender->getPosition();
BattleHex otherAttackerPos = attackerPos.toInt() + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1);
BattleHex otherDefenderPos = defenderPos.toInt() + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1);
BattleHexArray defenderHexes = defender->getHexes(defenderPos);
BattleHexArray attackerHexes = attacker->getHexes(attackerPos);
if(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front
for (BattleHex defenderHex : defenderHexes)
{
if((mask & 1) == 0)
if (attackerHexes.contains(defenderHex))
{
mask |= 1;
res.insert(defenderPos);
logGlobal->debug("CStack::meleeAttackHexes: defender and attacker positions overlap");
return res;
}
}
if (attacker->doubleWide() //back <=> front
&& BattleHex::mutualPosition(otherAttackerPos, defenderPos) >= 0)
const BattleHexArray attackableHxs = attacker->getSurroundingHexes(attackerPos);
for (BattleHex defenderHex : defenderHexes)
{
if((mask & 1) == 0)
{
mask |= 1;
res.insert(defenderPos);
}
}
if (defender->doubleWide()//front <=> back
&& BattleHex::mutualPosition(attackerPos, otherDefenderPos) >= 0)
{
if((mask & 2) == 0)
{
mask |= 2;
res.insert(otherDefenderPos);
}
}
if (defender->doubleWide() && attacker->doubleWide()//back <=> back
&& BattleHex::mutualPosition(otherAttackerPos, otherDefenderPos) >= 0)
{
if((mask & 2) == 0)
{
mask |= 2;
res.insert(otherDefenderPos);
}
if (attackableHxs.contains(defenderHex))
res.insert(defenderHex);
}
return res;

View File

@@ -9,15 +9,9 @@
*/
#pragma once
#ifdef USE_SYSTEM_MINIZIP
#include <minizip/unzip.h>
#include <minizip/zip.h>
#include <minizip/ioapi.h>
#else
#include "../minizip/unzip.h"
#include "../minizip/zip.h"
#include "../minizip/ioapi.h"
#endif
// system zlib on old Androids isn't capable of using _64 functions: https://github.com/madler/zlib/pull/436
#if defined(__ANDROID_API__) && (__ANDROID_API__ < 24)

View File

@@ -1,22 +0,0 @@
project(minizip)
include_directories(${ZLIB_INCLUDE_DIR})
#NOTE: full library consists from several more files
# but right now VCMI does not need any extra functionality
set(lib_SRCS
unzip.c
zip.c
ioapi.c
)
add_library(minizip SHARED ${lib_SRCS})
if(MSVC)
set_target_properties(minizip PROPERTIES COMPILE_DEFINITIONS "MINIZIP_DLL;ZLIB_DLL;ZLIB_INTERNAL")
endif()
vcmi_set_output_dir(minizip "")
target_link_libraries(minizip ${ZLIB_LIBRARIES})
install(TARGETS minizip RUNTIME DESTINATION ${LIB_DIR} LIBRARY DESTINATION ${LIB_DIR})

View File

@@ -1,6 +0,0 @@
MiniZip 1.1 was derived from MiniZip at version 1.01f
Change in 1.0 (Okt 2009)
- **TODO - Add history**

View File

@@ -1,74 +0,0 @@
MiniZip - Copyright (c) 1998-2010 - by Gilles Vollant - version 1.1 64 bits from Mathias Svensson
Introduction
---------------------
MiniZip 1.1 is built from MiniZip 1.0 by Gilles Vollant ( http://www.winimage.com/zLibDll/minizip.html )
When adding ZIP64 support into minizip it would result into risk of breaking compatibility with minizip 1.0.
All possible work was done for compatibility.
Background
---------------------
When adding ZIP64 support Mathias Svensson found that Even Rouault have added ZIP64
support for unzip.c into minizip for a open source project called gdal ( http://www.gdal.org/ )
That was used as a starting point. And after that ZIP64 support was added to zip.c
some refactoring and code cleanup was also done.
Changed from MiniZip 1.0 to MiniZip 1.1
---------------------------------------
* Added ZIP64 support for unzip ( by Even Rouault )
* Added ZIP64 support for zip ( by Mathias Svensson )
* Reverted some changed that Even Rouault did.
* Bunch of patches received from Gulles Vollant that he received for MiniZip from various users.
* Added unzip patch for BZIP Compression method (patch create by Daniel Borca)
* Added BZIP Compress method for zip
* Did some refactoring and code cleanup
Credits
Gilles Vollant - Original MiniZip author
Even Rouault - ZIP64 unzip Support
Daniel Borca - BZip Compression method support in unzip
Mathias Svensson - ZIP64 zip support
Mathias Svensson - BZip Compression method support in zip
Resources
ZipLayout http://result42.com/projects/ZipFileLayout
Command line tool for Windows that shows the layout and information of the headers in a zip archive.
Used when debugging and validating the creation of zip files using MiniZip64
ZIP App Note http://www.pkware.com/documents/casestudies/APPNOTE.TXT
Zip File specification
Notes.
* To be able to use BZip compression method in zip64.c or unzip64.c the BZIP2 lib is needed and HAVE_BZIP2 need to be defined.
License
----------------------------------------------------------
Condition of use and distribution are the same than zlib :
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
----------------------------------------------------------

View File

@@ -1,131 +0,0 @@
/* crypt.h -- base code for crypt/uncrypt ZIPfile
Version 1.01e, February 12th, 2005
Copyright (C) 1998-2005 Gilles Vollant
This code is a modified version of crypting code in Infozip distribution
The encryption/decryption parts of this source code (as opposed to the
non-echoing password parts) were originally written in Europe. The
whole source package can be freely distributed, including from the USA.
(Prior to January 2000, re-export from the US was a violation of US law.)
This encryption code is a direct transcription of the algorithm from
Roger Schlafly, described by Phil Katz in the file appnote.txt. This
file (appnote.txt) is distributed with the PKZIP program (even in the
version without encryption capabilities).
If you don't need crypting in your application, just define symbols
NOCRYPT and NOUNCRYPT.
This code support the "Traditional PKWARE Encryption".
The new AES encryption added on Zip format by Winzip (see the page
http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong
Encryption is not supported.
*/
#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8))
/***********************************************************************
* Return the next byte in the pseudo-random sequence
*/
static int decrypt_byte(unsigned long* pkeys, const z_crc_t* pcrc_32_tab)
{
unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an
* unpredictable manner on 16-bit systems; not a problem
* with any known compiler so far, though */
temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2;
return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
}
/***********************************************************************
* Update the encryption keys with the next byte of plain text
*/
static int update_keys(unsigned long* pkeys,const z_crc_t* pcrc_32_tab,int c)
{
(*(pkeys+0)) = CRC32((*(pkeys+0)), c);
(*(pkeys+1)) += (*(pkeys+0)) & 0xff;
(*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1;
{
register int keyshift = (int)((*(pkeys+1)) >> 24);
(*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift);
}
return c;
}
/***********************************************************************
* Initialize the encryption keys and the random header according to
* the given password.
*/
static void init_keys(const char* passwd,unsigned long* pkeys,const z_crc_t* pcrc_32_tab)
{
*(pkeys+0) = 305419896L;
*(pkeys+1) = 591751049L;
*(pkeys+2) = 878082192L;
while (*passwd != '\0') {
update_keys(pkeys,pcrc_32_tab,(int)*passwd);
passwd++;
}
}
#define zdecode(pkeys,pcrc_32_tab,c) \
(update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab)))
#define zencode(pkeys,pcrc_32_tab,c,t) \
(t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c))
#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED
#define RAND_HEAD_LEN 12
/* "last resort" source for second part of crypt seed pattern */
# ifndef ZCR_SEED2
# define ZCR_SEED2 3141592654UL /* use PI as default pattern */
# endif
static int crypthead(const char* passwd, /* password string */
unsigned char* buf, /* where to write header */
int bufSize,
unsigned long* pkeys,
const z_crc_t* pcrc_32_tab,
unsigned long crcForCrypting)
{
int n; /* index in random header */
int t; /* temporary */
int c; /* random byte */
unsigned char header[RAND_HEAD_LEN-2]; /* random header */
static unsigned calls = 0; /* ensure different random header each time */
if (bufSize<RAND_HEAD_LEN)
return 0;
/* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the
* output of rand() to get less predictability, since rand() is
* often poorly implemented.
*/
if (++calls == 1)
{
srand((unsigned)(time(NULL) ^ ZCR_SEED2));
}
init_keys(passwd, pkeys, pcrc_32_tab);
for (n = 0; n < RAND_HEAD_LEN-2; n++)
{
c = (rand() >> 7) & 0xff;
header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t);
}
/* Encrypt random header (last two bytes is high word of crc) */
init_keys(passwd, pkeys, pcrc_32_tab);
for (n = 0; n < RAND_HEAD_LEN-2; n++)
{
buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t);
}
buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t);
buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t);
return n;
}
#endif

View File

@@ -1,247 +0,0 @@
/* ioapi.h -- IO base function header for compress/uncompress .zip
part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
Modifications for Zip64 support
Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
For more info read MiniZip_info.txt
*/
#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS)))
#define _CRT_SECURE_NO_WARNINGS
#endif
#if defined(__APPLE__) || defined(IOAPI_NO_64)
// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
#define FTELLO_FUNC(stream) ftello(stream)
#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
#else
#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
#define FTELLO_FUNC(stream) ftello64(stream)
#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
#endif
#include "ioapi.h"
voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
{
if (pfilefunc->zfile_func64.zopen64_file != NULL)
return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
else
{
return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
}
}
long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
{
if (pfilefunc->zfile_func64.zseek64_file != NULL)
return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
else
{
uLong offsetTruncated = (uLong)offset;
if (offsetTruncated != offset)
return -1;
else
return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
}
}
ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
{
if (pfilefunc->zfile_func64.zseek64_file != NULL)
return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
else
{
uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
if ((tell_uLong) == MAXU32)
return (ZPOS64_T)-1;
else
return tell_uLong;
}
}
void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
{
p_filefunc64_32->zfile_func64.zopen64_file = NULL;
p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
p_filefunc64_32->zfile_func64.ztell64_file = NULL;
p_filefunc64_32->zfile_func64.zseek64_file = NULL;
p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
}
static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
{
FILE* file = NULL;
const char* mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else
if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else
if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename!=NULL) && (mode_fopen != NULL))
file = fopen(filename, mode_fopen);
return file;
}
static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
{
FILE* file = NULL;
const char* mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else
if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else
if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename!=NULL) && (mode_fopen != NULL))
file = FOPEN_FUNC((const char*)filename, mode_fopen);
return file;
}
static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
{
uLong ret;
ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
return ret;
}
static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
{
uLong ret;
ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
return ret;
}
static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
{
long ret;
ret = ftell((FILE *)stream);
return ret;
}
static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
{
ZPOS64_T ret;
ret = FTELLO_FUNC((FILE *)stream);
return ret;
}
static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin)
{
int fseek_origin=0;
long ret;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR :
fseek_origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END :
fseek_origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET :
fseek_origin = SEEK_SET;
break;
default: return -1;
}
ret = 0;
if (fseek((FILE *)stream, offset, fseek_origin) != 0)
ret = -1;
return ret;
}
static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
{
int fseek_origin=0;
long ret;
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR :
fseek_origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END :
fseek_origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET :
fseek_origin = SEEK_SET;
break;
default: return -1;
}
ret = 0;
if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0)
ret = -1;
return ret;
}
static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
{
int ret;
ret = fclose((FILE *)stream);
return ret;
}
static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
{
int ret;
ret = ferror((FILE *)stream);
return ret;
}
void fill_fopen_filefunc(pzlib_filefunc_def)
zlib_filefunc_def* pzlib_filefunc_def;
{
pzlib_filefunc_def->zopen_file = fopen_file_func;
pzlib_filefunc_def->zread_file = fread_file_func;
pzlib_filefunc_def->zwrite_file = fwrite_file_func;
pzlib_filefunc_def->ztell_file = ftell_file_func;
pzlib_filefunc_def->zseek_file = fseek_file_func;
pzlib_filefunc_def->zclose_file = fclose_file_func;
pzlib_filefunc_def->zerror_file = ferror_file_func;
pzlib_filefunc_def->opaque = NULL;
}
void fill_fopen64_filefunc(zlib_filefunc64_def* pzlib_filefunc_def)
{
pzlib_filefunc_def->zopen64_file = fopen64_file_func;
pzlib_filefunc_def->zread_file = fread_file_func;
pzlib_filefunc_def->zwrite_file = fwrite_file_func;
pzlib_filefunc_def->ztell64_file = ftell64_file_func;
pzlib_filefunc_def->zseek64_file = fseek64_file_func;
pzlib_filefunc_def->zclose_file = fclose_file_func;
pzlib_filefunc_def->zerror_file = ferror_file_func;
pzlib_filefunc_def->opaque = NULL;
}

Some files were not shown because too many files have changed in this diff Show More