android: V12 Update

This commit is contained in:
Macdu 2024-06-04 19:15:07 +02:00
parent fab31c81b2
commit a62e232024
18 changed files with 479 additions and 387 deletions

View File

@ -1,234 +1,234 @@
name: C/C++ CI
on: [push, pull_request]
jobs:
format-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Format check
run: .github/format-check.sh
build:
needs: [format-check]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
config: [Release]
version: [zip, appimage]
include:
- os: ubuntu-latest
version: appimage
cache_path: ~/.ccache
extra_cmake_args: -DLINUXDEPLOY_COMMAND=/usr/local/bin/linuxdeploy-x86_64.AppImage
cmake_preset: linux-ninja-clang15-appimage
- os: ubuntu-latest
version: zip
cache_path: ~/.ccache
extra_cmake_args:
cmake_preset: linux-ninja-clang15
- os: windows-latest
version: zip
cache_path: |
C:\vcpkg\installed
C:\vcpkg\packages
C:\Users\runneradmin\AppData\Local\ccache
extra_cmake_args: -DCMAKE_TOOLCHAIN_FILE=C:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static-md
cmake_preset: windows-ninja
- os: macos-latest
version: zip
cache_path: ~/Library/Caches/ccache
extra_cmake_args: -DCMAKE_OSX_ARCHITECTURES="x86_64"
cmake_preset: macos-ninja
exclude:
- os: macos-latest
version: appimage
- os: windows-latest
version: appimage
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Set up build environment (macos-latest)
run: |
brew install ccache ninja create-dmg
brew fetch --force --bottle-tag=x86_64_monterey boost openssl@3 molten-vk
brew install $(brew --cache --bottle-tag=x86_64_monterey boost)
brew install $(brew --cache --bottle-tag=x86_64_monterey molten-vk)
brew reinstall $(brew --cache --bottle-tag=x86_64_monterey openssl@3)
echo "$(brew --prefix ccache)/libexec" >> $GITHUB_PATH
ccache --set-config=compiler_check=content
if: matrix.os == 'macos-latest'
- name: Set up build environment (ubuntu-latest)
run: |
sudo add-apt-repository -y ppa:mhier/libboost-latest
sudo add-apt-repository universe
sudo apt update
sudo apt -y install ccache libboost-filesystem1.83-dev libboost-program-options1.83-dev libboost-system1.83-dev libgtk-3-dev libsdl2-dev ninja-build libfuse2
if: matrix.os == 'ubuntu-latest'
- uses: ilammy/msvc-dev-cmd@v1
if: matrix.os == 'windows-latest'
- uses: actions/cache@v4
with:
path: ${{ matrix.cache_path }}
key: cache-${{ matrix.os }}-${{ matrix.config }}-${{ github.sha }}
restore-keys: |
cache-${{ matrix.os }}-${{ matrix.config }}-
- name: Set up build environment (windows-latest)
run: |
vcpkg install zlib:x64-windows-static-md boost-system:x64-windows-static-md boost-filesystem:x64-windows-static-md boost-program-options:x64-windows-static-md boost-icl:x64-windows-static-md boost-variant:x64-windows-static-md curl:x64-windows-static-md openssl:x64-windows-static-md
choco install ccache
if: matrix.os == 'windows-latest'
- name: Set up SDL 2.28.3 (ubuntu-latest)
run: |
SDL2VER=2.28.3
if [[ ! -e ~/.ccache ]]; then
mkdir ~/.ccache
fi
cd ~/.ccache
if [[ ! -e SDL2-${SDL2VER} ]]; then
curl -sLO https://libsdl.org/release/SDL2-${SDL2VER}.tar.gz
tar -xzf SDL2-${SDL2VER}.tar.gz
cd SDL2-${SDL2VER}
./configure --prefix=/usr/local
make && cd ../
rm SDL2-${SDL2VER}.tar.gz
fi
sudo make -C SDL2-${SDL2VER} install
if: matrix.os == 'ubuntu-latest'
- name: Set up linuxdeploy (ubuntu-latest, appimage)
run: |
if [[ ! -e linuxdeploy-x86_64.AppImage ]]; then
curl -sLO https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-x86_64.AppImage
fi
sudo cp -f linuxdeploy-x86_64.AppImage /usr/local/bin/
sudo chmod +x /usr/local/bin/linuxdeploy-x86_64.AppImage
if: matrix.os == 'ubuntu-latest' && matrix.version == 'appimage'
- name: Ccache setup
run: ccache -z
- name: CMake
run: |
cmake ${{ matrix.extra_cmake_args }} --preset ${{ matrix.cmake_preset }}
cmake --build build/${{ matrix.cmake_preset }} --config ${{ matrix.config }}
- name: CTest
working-directory: build/${{ matrix.cmake_preset }}
run: ctest --build-config ${{ matrix.config }} --output-on-failure
- name: Compute git short sha
shell: bash
run: echo "git_short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Set Build Variable
shell: bash
run: echo "build_variable=$(git rev-list HEAD --count)" >> $GITHUB_ENV
if: matrix.os == 'ubuntu-latest'
- name: Bundle Shared Objects
id: bundle_shared_objects
run: |
cd build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
cp /usr/lib/x86_64-linux-gnu/libssl.so.3 ./libssl.so.3
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.3 ./libcrypto.so.3
if: matrix.os == 'ubuntu-latest'
- name: Ccache statistics
run: ccache -s
- name: Create DMG (macos-latest)
run: |
cd build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
create-dmg \
--volname "Vita3K Installer" \
--volicon Vita3K.app/Contents/Resources/Vita3K.icns \
--window-size 500 300 \
--icon-size 100 \
--icon Vita3K.app 120 115 \
--app-drop-link 360 115 \
vita3k-${{ env.git_short_sha }}-${{ matrix.version }}-${{ matrix.os }}.dmg \
Vita3K.app
rm -rf Vita3K.app
if: matrix.os == 'macos-latest'
- name: Clean appimage build (ubuntu-latest, appimage)
run: |
cd build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
cp -f *AppImage* ../
rm -rf ./*
cp -f ../*AppImage* ./
rm -f ../*AppImage*
if: matrix.os == 'ubuntu-latest' && matrix.version == 'appimage'
- uses: actions/upload-artifact@v4
with:
name: vita3k-${{ env.git_short_sha }}-${{ matrix.version }}-${{ matrix.os }}
# path is set up to be <binary_dir>/bin/<config_type> since that's how multi-config
# generators work on CMake
path: build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
outputs:
BuildTag: ${{ env.build_variable }}
create-release:
needs: [build]
runs-on: "ubuntu-20.04"
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
- name: Get Build Variable
run: echo "Build_Variable=${{ needs.build.outputs.BuildTag }}" >> $GITHUB_ENV
- name: Upload
shell: bash
run: |
mkdir artifacts/
files=$(find . -name "*latest")
for f in $files; do
if [[ $f == *macos-latest ]]
then
cp $(basename $f)/$(basename $f).dmg artifacts/macos-latest.dmg
else
if [[ $f == *ubuntu-latest ]]
then
if [[ $f == *appimage* ]]
then
cp $(basename $f)/*.AppImage* artifacts/
else
rm -f $(basename $f)/*.AppImage*
echo "Compressing $f"
(cd $(basename $f) && zip -r ../artifacts/$(basename $f | cut -d "-" -f 4)-latest.zip *)
fi
else
echo "Compressing $f"
(cd $(basename $f) && zip -r ../artifacts/$(basename $f | cut -d "-" -f 4)-latest.zip *)
fi
fi
done
ls -al artifacts/
wget -c https://github.com/tcnksm/ghr/releases/download/v0.14.0/ghr_v0.14.0_linux_amd64.tar.gz
tar xfv ghr_v0.14.0_linux_amd64.tar.gz
ghr_v0.14.0_linux_amd64/ghr -u Vita3K -r Vita3K -recreate -n 'Automatic CI builds' -b "$(printf "Corresponding commit: ${{ github.sha }}\nVita3K Build: ${{ env.Build_Variable }}")" continuous artifacts/
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
#name: C/C++ CI
#
#on: [push, pull_request]
#
#jobs:
# format-check:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v4
#
# - name: Format check
# run: .github/format-check.sh
#
# build:
# needs: [format-check]
# runs-on: ${{ matrix.os }}
#
# strategy:
# fail-fast: false
# matrix:
# os: [ubuntu-latest, windows-latest, macos-latest]
# config: [Release]
# version: [zip, appimage]
# include:
# - os: ubuntu-latest
# version: appimage
# cache_path: ~/.ccache
# extra_cmake_args: -DLINUXDEPLOY_COMMAND=/usr/local/bin/linuxdeploy-x86_64.AppImage
# cmake_preset: linux-ninja-clang15-appimage
# - os: ubuntu-latest
# version: zip
# cache_path: ~/.ccache
# extra_cmake_args:
# cmake_preset: linux-ninja-clang15
# - os: windows-latest
# version: zip
# cache_path: |
# C:\vcpkg\installed
# C:\vcpkg\packages
# C:\Users\runneradmin\AppData\Local\ccache
# extra_cmake_args: -DCMAKE_TOOLCHAIN_FILE=C:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static-md
# cmake_preset: windows-ninja
# - os: macos-latest
# version: zip
# cache_path: ~/Library/Caches/ccache
# extra_cmake_args: -DCMAKE_OSX_ARCHITECTURES="x86_64"
# cmake_preset: macos-ninja
# exclude:
# - os: macos-latest
# version: appimage
# - os: windows-latest
# version: appimage
#
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0
# submodules: recursive
#
# - name: Set up build environment (macos-latest)
# run: |
# brew install ccache ninja create-dmg
# brew fetch --force --bottle-tag=x86_64_monterey boost openssl@3 molten-vk
# brew install $(brew --cache --bottle-tag=x86_64_monterey boost)
# brew install $(brew --cache --bottle-tag=x86_64_monterey molten-vk)
# brew reinstall $(brew --cache --bottle-tag=x86_64_monterey openssl@3)
# echo "$(brew --prefix ccache)/libexec" >> $GITHUB_PATH
# ccache --set-config=compiler_check=content
# if: matrix.os == 'macos-latest'
#
# - name: Set up build environment (ubuntu-latest)
# run: |
# sudo add-apt-repository -y ppa:mhier/libboost-latest
# sudo add-apt-repository universe
# sudo apt update
# sudo apt -y install ccache libboost-filesystem1.83-dev libboost-program-options1.83-dev libboost-system1.83-dev libgtk-3-dev libsdl2-dev ninja-build libfuse2
# if: matrix.os == 'ubuntu-latest'
#
# - uses: ilammy/msvc-dev-cmd@v1
# if: matrix.os == 'windows-latest'
#
# - uses: actions/cache@v4
# with:
# path: ${{ matrix.cache_path }}
# key: cache-${{ matrix.os }}-${{ matrix.config }}-${{ github.sha }}
# restore-keys: |
# cache-${{ matrix.os }}-${{ matrix.config }}-
#
# - name: Set up build environment (windows-latest)
# run: |
# vcpkg install zlib:x64-windows-static-md boost-system:x64-windows-static-md boost-filesystem:x64-windows-static-md boost-program-options:x64-windows-static-md boost-icl:x64-windows-static-md boost-variant:x64-windows-static-md curl:x64-windows-static-md openssl:x64-windows-static-md
# choco install ccache
# if: matrix.os == 'windows-latest'
#
# - name: Set up SDL 2.28.3 (ubuntu-latest)
# run: |
# SDL2VER=2.28.3
# if [[ ! -e ~/.ccache ]]; then
# mkdir ~/.ccache
# fi
# cd ~/.ccache
# if [[ ! -e SDL2-${SDL2VER} ]]; then
# curl -sLO https://libsdl.org/release/SDL2-${SDL2VER}.tar.gz
# tar -xzf SDL2-${SDL2VER}.tar.gz
# cd SDL2-${SDL2VER}
# ./configure --prefix=/usr/local
# make && cd ../
# rm SDL2-${SDL2VER}.tar.gz
# fi
# sudo make -C SDL2-${SDL2VER} install
# if: matrix.os == 'ubuntu-latest'
#
# - name: Set up linuxdeploy (ubuntu-latest, appimage)
# run: |
# if [[ ! -e linuxdeploy-x86_64.AppImage ]]; then
# curl -sLO https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-x86_64.AppImage
# fi
# sudo cp -f linuxdeploy-x86_64.AppImage /usr/local/bin/
# sudo chmod +x /usr/local/bin/linuxdeploy-x86_64.AppImage
# if: matrix.os == 'ubuntu-latest' && matrix.version == 'appimage'
#
# - name: Ccache setup
# run: ccache -z
#
# - name: CMake
# run: |
# cmake ${{ matrix.extra_cmake_args }} --preset ${{ matrix.cmake_preset }}
# cmake --build build/${{ matrix.cmake_preset }} --config ${{ matrix.config }}
#
# - name: CTest
# working-directory: build/${{ matrix.cmake_preset }}
# run: ctest --build-config ${{ matrix.config }} --output-on-failure
#
# - name: Compute git short sha
# shell: bash
# run: echo "git_short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
#
# - name: Set Build Variable
# shell: bash
# run: echo "build_variable=$(git rev-list HEAD --count)" >> $GITHUB_ENV
# if: matrix.os == 'ubuntu-latest'
#
# - name: Bundle Shared Objects
# id: bundle_shared_objects
# run: |
# cd build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
# cp /usr/lib/x86_64-linux-gnu/libssl.so.3 ./libssl.so.3
# cp /usr/lib/x86_64-linux-gnu/libcrypto.so.3 ./libcrypto.so.3
# if: matrix.os == 'ubuntu-latest'
#
# - name: Ccache statistics
# run: ccache -s
#
# - name: Create DMG (macos-latest)
# run: |
# cd build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
# create-dmg \
# --volname "Vita3K Installer" \
# --volicon Vita3K.app/Contents/Resources/Vita3K.icns \
# --window-size 500 300 \
# --icon-size 100 \
# --icon Vita3K.app 120 115 \
# --app-drop-link 360 115 \
# vita3k-${{ env.git_short_sha }}-${{ matrix.version }}-${{ matrix.os }}.dmg \
# Vita3K.app
# rm -rf Vita3K.app
# if: matrix.os == 'macos-latest'
#
# - name: Clean appimage build (ubuntu-latest, appimage)
# run: |
# cd build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
# cp -f *AppImage* ../
# rm -rf ./*
# cp -f ../*AppImage* ./
# rm -f ../*AppImage*
# if: matrix.os == 'ubuntu-latest' && matrix.version == 'appimage'
#
# - uses: actions/upload-artifact@v4
# with:
# name: vita3k-${{ env.git_short_sha }}-${{ matrix.version }}-${{ matrix.os }}
# # path is set up to be <binary_dir>/bin/<config_type> since that's how multi-config
# # generators work on CMake
# path: build/${{ matrix.cmake_preset }}/bin/${{ matrix.config }}
#
# outputs:
# BuildTag: ${{ env.build_variable }}
#
# create-release:
# needs: [build]
# runs-on: "ubuntu-20.04"
# if: github.ref == 'refs/heads/master'
# steps:
# - uses: actions/checkout@v4
#
# - name: Download Artifacts
# uses: actions/download-artifact@v4
#
# - name: Get Build Variable
# run: echo "Build_Variable=${{ needs.build.outputs.BuildTag }}" >> $GITHUB_ENV
#
# - name: Upload
# shell: bash
# run: |
# mkdir artifacts/
# files=$(find . -name "*latest")
# for f in $files; do
# if [[ $f == *macos-latest ]]
# then
# cp $(basename $f)/$(basename $f).dmg artifacts/macos-latest.dmg
# else
# if [[ $f == *ubuntu-latest ]]
# then
# if [[ $f == *appimage* ]]
# then
# cp $(basename $f)/*.AppImage* artifacts/
# else
# rm -f $(basename $f)/*.AppImage*
# echo "Compressing $f"
# (cd $(basename $f) && zip -r ../artifacts/$(basename $f | cut -d "-" -f 4)-latest.zip *)
# fi
# else
# echo "Compressing $f"
# (cd $(basename $f) && zip -r ../artifacts/$(basename $f | cut -d "-" -f 4)-latest.zip *)
# fi
# fi
# done
# ls -al artifacts/
# wget -c https://github.com/tcnksm/ghr/releases/download/v0.14.0/ghr_v0.14.0_linux_amd64.tar.gz
# tar xfv ghr_v0.14.0_linux_amd64.tar.gz
# ghr_v0.14.0_linux_amd64/ghr -u Vita3K -r Vita3K -recreate -n 'Automatic CI builds' -b "$(printf "Corresponding commit: ${{ github.sha }}\nVita3K Build: ${{ env.Build_Variable }}")" continuous artifacts/
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -4,14 +4,16 @@ android {
namespace "org.vita3k.emulator"
compileSdk 34
ndkVersion "26.3.11579264"
buildToolsVersion "34.0.0"
buildFeatures {
buildConfig true
}
defaultConfig {
applicationId "org.vita3k.emulator"
minSdk 24
targetSdk 34
versionCode 11
versionName "0.2.0-11"
versionCode 12
versionName "0.2.0-12"
externalNativeBuild {
cmake {

View File

@ -45,9 +45,16 @@
<!-- Allow downloading to the external storage on Android 5.1 and older -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
<!-- For android API <= 29 (Up to Android 10), use Legacy storage, otherwise use the MANAGE_EXTERNAL_STORAGE permission -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
android:minSdkVersion="30" />
<!-- Allow access to Bluetooth devices -->
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
@ -70,6 +77,8 @@
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
An example Java class can be found in README-android.md
The requestLegacyExternalStorage parameter only has an effect when running on Android <= 10
-->
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
@ -78,7 +87,9 @@
android:hardwareAccelerated="true"
android:appCategory="game"
android:isGame="true"
android:hasFragileUserData="true" tools:targetApi="q">
android:hasFragileUserData="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="q">
<!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
@ -89,8 +100,10 @@
android:launchMode="singleInstance"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:theme="@style/Theme.Design.NoActionBar"
android:exported="true"
>
tools:targetApi="r">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@ -1,14 +1,24 @@
package org.vita3k.emulator;
import android.content.Context;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import org.libsdl.app.SDLSurface;
import org.vita3k.emulator.overlay.InputOverlay;
public class EmuSurface extends SDLSurface {
private InputOverlay mOverlay;
public InputOverlay getmOverlay() {
return mOverlay;
}
public EmuSurface(Context context){
super(context);
mOverlay = new InputOverlay(context);
}
@Override
@ -23,6 +33,16 @@ public class EmuSurface extends SDLSurface {
super.surfaceDestroyed(holder);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if(mOverlay.onTouch(v, event)){
// priority is given to the overlay
return true;
}
return super.onTouch(v, event);
}
public native void setSurfaceStatus(boolean surface_present);
}

View File

@ -1,28 +1,33 @@
package org.vita3k.emulator;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.view.Surface;
import android.view.ViewGroup;
import androidx.annotation.Keep;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.documentfile.provider.DocumentFile;
import com.jakewharton.processphoenix.ProcessPhoenix;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@ -32,11 +37,11 @@ import org.vita3k.emulator.overlay.InputOverlay;
public class Emulator extends SDLActivity
{
private InputOverlay mOverlay;
private String currentGameId = "";
private EmuSurface mSurface;
public InputOverlay getInputOverlay(){
return mOverlay;
public InputOverlay getmOverlay() {
return mSurface.getmOverlay();
}
@Keep
@ -69,17 +74,17 @@ public class Emulator extends SDLActivity
@Override
protected SDLSurface createSDLSurface(Context context) {
// Create the input overlay in the same time
mOverlay = new InputOverlay(this);
return new EmuSurface(context);
mSurface = new EmuSurface(context);
return mSurface;
}
@Override
protected void setupLayout(ViewGroup layout){
super.setupLayout(layout);
layout.addView(mOverlay);
layout.addView(getmOverlay());
}
static private String APP_RESTART_PARAMETERS = "AppStartParameters";
static private final String APP_RESTART_PARAMETERS = "AppStartParameters";
@Override
protected String[] getArguments() {
@ -107,7 +112,7 @@ public class Emulator extends SDLActivity
@Keep
public void restartApp(String app_path, String exec_path, String exec_args){
ArrayList<String> args = new ArrayList<String>();
ArrayList<String> args = new ArrayList<>();
// first build the args given to Vita3K when it restarts
// this is similar to run_execv in main.cpp
@ -134,17 +139,43 @@ public class Emulator extends SDLActivity
}
static final int FILE_DIALOG_CODE = 545;
static final int FOLDER_DIALOG_CODE = 546;
static final int STORAGE_MANAGER_DIALOG_CODE = 547;
@Keep
public void showFileDialog(){
public void showFileDialog() {
Intent intent = new Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT);
.setAction(Intent.ACTION_GET_CONTENT)
.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
intent = Intent.createChooser(intent, "Choose a file");
startActivityForResult(intent, FILE_DIALOG_CODE);
}
private boolean isStorageManagerEnabled(){
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && Environment.isExternalStorageManager();
}
@Keep
public void showFolderDialog() {
// If running Android 10-, SDL should have already asked for read and write permissions
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || isStorageManagerEnabled()) {
Intent intent = new Intent()
.setAction(Intent.ACTION_OPEN_DOCUMENT_TREE)
.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
intent = Intent.createChooser(intent, "Choose a folder");
startActivityForResult(intent, FOLDER_DIALOG_CODE);
} else {
Intent intent = new Intent()
.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
.setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID));
startActivityForResult(intent, STORAGE_MANAGER_DIALOG_CODE);
}
}
private File getFileFromUri(Uri uri){
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
@ -166,75 +197,76 @@ public class Emulator extends SDLActivity
}
}
// from https://stackoverflow.com/questions/5568874/how-to-extract-the-file-name-from-uri-returned-from-intent-action-get-content
private String getFileName(Uri uri){
String result = null;
if(uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)){
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
try {
if(cursor != null && cursor.moveToFirst()){
int name_index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if(name_index >= 0)
result = cursor.getString(name_index);
}
} finally {
cursor.close();
}
}
if(result == null){
result = uri.getLastPathSegment();
}
return result;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == FILE_DIALOG_CODE){
String result_path = "";
int result_fd = -1;
if(resultCode == RESULT_OK){
Uri result_uri = data.getData();
String filename = getFileName(result_uri);
String result_uri_string = result_uri.toString();
int result_fd = -1;
try {
AssetFileDescriptor asset_fd = getContentResolver().openAssetFileDescriptor(result_uri, "r");
// if the file is less than 64 KB, make a temporary copy
if(asset_fd.getLength() >= 64*1024) {
ParcelFileDescriptor file_descr = getContentResolver().openFileDescriptor(result_uri, "r");
result_fd = file_descr.detachFd();
try (AssetFileDescriptor asset_fd = getContentResolver().openAssetFileDescriptor(result_uri, "r")){
// if the file is less than 4 KB, make a temporary copy
if(asset_fd.getLength() >= 4*1024) {
try (ParcelFileDescriptor file_descr = getContentResolver().openFileDescriptor(result_uri, "r")) {
result_fd = file_descr.detachFd();
// in case the last call returns a ErrnoException
result_path = result_uri.toString();
result_path = Os.readlink("/proc/self/fd/" + result_fd);
}
} else {
File f = getFileFromUri(result_uri);
result_uri_string = f.getAbsolutePath();
result_path = f.getAbsolutePath();
}
} catch (FileNotFoundException e) {
} catch (ErrnoException | IOException e) {
}
filedialogReturn(result_uri_string, result_fd, filename);
} else if(resultCode == RESULT_CANCELED){
filedialogReturn("", -1, "");
}
filedialogReturn(result_path, result_fd);
} else if(requestCode == FOLDER_DIALOG_CODE){
String result_path = "";
if(resultCode == RESULT_OK){
Uri result_uri = data.getData();
DocumentFile tree = DocumentFile.fromTreeUri(getApplicationContext(), result_uri);
try(ParcelFileDescriptor file_descr = getContentResolver().openFileDescriptor(tree.getUri(), "r")) {
int result_fd = file_descr.getFd();
result_path = Os.readlink("/proc/self/fd/" + result_fd);
// replace /mnt/user/{id} with /storage
if(result_path.startsWith("/mnt/user/")){
result_path = result_path.substring("/mnt/user/".length());
result_path = "/storage" + result_path.substring(result_path.indexOf('/'));
}
} catch (ErrnoException | IOException e) {
}
}
filedialogReturn(result_path, 0);
} else if (requestCode == STORAGE_MANAGER_DIALOG_CODE) {
if (isStorageManagerEnabled()) {
showFolderDialog();
} else {
filedialogReturn("", -1);
}
}
}
@Keep
public void setControllerOverlayState(int overlay_mask, boolean edit, boolean reset){
mOverlay.setState(overlay_mask);
mOverlay.setIsInEditMode(edit);
getmOverlay().setState(overlay_mask);
getmOverlay().setIsInEditMode(edit);
if(reset)
mOverlay.resetButtonPlacement();
getmOverlay().resetButtonPlacement();
}
@Keep
public void setControllerOverlayScale(float scale){
mOverlay.setScale(scale);
getmOverlay().setScale(scale);
}
@Keep
public void setControllerOverlayOpacity(int opacity){
mOverlay.setOpacity(opacity);
getmOverlay().setOpacity(opacity);
}
@Keep
@ -270,5 +302,13 @@ public class Emulator extends SDLActivity
return true;
}
public native void filedialogReturn(String result_uri, int result_fd, String filename);
@Keep
public boolean isDefaultOrientationLandscape() {
// we know the current device orientation is landscape
// so the default one is also landscape if and only if the rotation is 0 or 180
int rotation = getWindowManager().getDefaultDisplay().getRotation();
return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180;
}
public native void filedialogReturn(String result_path, int result_fd);
}

View File

@ -106,7 +106,9 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
defaultOverlay();
// Set the on touch listener.
setOnTouchListener(this);
// Do not register the overlay as a touch listener
// Instead let EmuSurface forward touch events
// setOnTouchListener(this);
// Force draw
setWillNotDraw(false);
@ -126,7 +128,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
@Override
public void run() {
Emulator emu = (Emulator) SDL.getContext();
emu.getInputOverlay().tick();
emu.getmOverlay().tick();
}
}, 1000, 1000);

View File

@ -147,12 +147,12 @@ Note: The CMake preset `linux-ninja-clang` makes use of the LLD linker, which wi
- Building can be done with Android studio: select the Vita3K Android folder and click on the build icon or by command line:
```sh
./gradlew --stacktrace --configuration-cache --build-cache --parallel --configure-on-demand assembleRelease
./gradlew --stacktrace --configuration-cache --build-cache --parallel --configure-on-demand assembleReldebug
```
### Building SDL
Note that if you want to build the SDL library yourself for Android instead of using the prebuilt version, its source code must be patched to allow for custom drivers to be loaded. Please refer to (get permalink)
Note that if you want to build the SDL library yourself for Android instead of using the prebuilt version, its source code must be patched to allow for custom drivers to be loaded. Please refer to [this part of the code](https://github.com/Vita3K/Vita3K-Android/blob/2791cf6bbf694a549080b958a7127ff483b11b99/vita3k/app/src/app_init.cpp#L132). If Vita3K is built using an unpatched version of SDL, it will compile and run correctly but crash when trying to load a custom driver.
## Note

View File

@ -88,9 +88,8 @@ void add_custom_driver(EmuEnvState &emuenv) {
if (result != host::dialog::filesystem::SUCCESS)
return;
std::string driver = host::dialog::filesystem::resolve_filename(file_path);
// remove the .zip extension
driver = fs::path(driver).stem().string();
std::string driver = file_path.filename().stem().string();
fs::path driver_path = fs::path(SDL_AndroidGetInternalStoragePath()) / "driver" / driver;

View File

@ -209,7 +209,7 @@ void draw_archive_install_dialog(GuiState &gui, EmuEnvState &emuenv) {
ImGui::Spacing();
ImGui::Separator();
for (const auto &archive : contents_archives) {
ImGui::TextWrapped("%s", host::dialog::filesystem::resolve_filename(archive.first).c_str());
ImGui::TextWrapped("%s", archive.first.filename().string().c_str());
ImGui::Spacing();
const auto count_contents_successed = count_content_state(archive.first, true);
if (count_contents_successed) {

View File

@ -26,9 +26,7 @@ namespace gui {
enum InitialSetup {
SELECT_LANGUAGE,
#ifndef ANDROID
SELECT_PREF_PATH,
#endif
INSTALL_FIRMWARE,
SELECT_INTERFACE_SETTINGS,
FINISHED
@ -106,6 +104,9 @@ void draw_initial_setup(GuiState &gui, EmuEnvState &emuenv) {
ImGui::Text("%s", title_str.c_str());
ImGui::SetCursorPosY(94.f * SCALE.y);
ImGui::Separator();
#ifdef ANDROID
const char* path_warning = "Using a different path requires additional permissions";
#endif
switch (setup) {
case SELECT_LANGUAGE:
title_str = lang["select_language"];
@ -142,12 +143,15 @@ void draw_initial_setup(GuiState &gui, EmuEnvState &emuenv) {
ImGui::PopStyleVar();
break;
#ifndef ANDROID
case SELECT_PREF_PATH:
title_str = lang["select_pref_path"];
ImGui::SetCursorPos(ImVec2((WINDOW_SIZE.x / 2.f) - (ImGui::CalcTextSize(lang["current_emu_path"].c_str()).x / 2.f), (WINDOW_SIZE.y / 2.f) - ImGui::GetFontSize()));
ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", lang["current_emu_path"].c_str());
ImGui::Spacing();
#ifdef ANDROID
ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - (ImGui::CalcTextSize(path_warning).x / 2.f));
ImGui::TextColored(ImVec4(0.98f, 0.01f, 0.20f, 1.0f), "%s", path_warning);
#endif
ImGui::SetCursorPosX((WINDOW_SIZE.x / 2.f) - (ImGui::CalcTextSize(emuenv.cfg.pref_path.c_str()).x / 2.f));
ImGui::TextWrapped("%s", emuenv.cfg.pref_path.c_str());
ImGui::SetCursorPos(!is_default_path ? ImVec2((WINDOW_SIZE.x / 2.f) - BIG_BUTTON_SIZE.x - (20.f * SCALE.x), BIG_BUTTON_POS.y) : BIG_BUTTON_POS);
@ -172,7 +176,6 @@ void draw_initial_setup(GuiState &gui, EmuEnvState &emuenv) {
}
}
break;
#endif
case INSTALL_FIRMWARE:
title_str = lang["install_firmware"];

View File

@ -111,7 +111,7 @@ static void change_emulator_path(GuiState &gui, EmuEnvState &emuenv) {
if (result == host::dialog::filesystem::Result::SUCCESS && emulator_path.native() != emuenv.pref_path.native()) {
// Refresh the working paths
emuenv.pref_path = fs::path(emulator_path.native()) / "";
emuenv.pref_path = emulator_path / "";
// TODO: Move app old to new path
reset_emulator(gui, emuenv);
@ -1081,7 +1081,7 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
ImGui::SetTooltip("%s", lang.emulator["case_insensitive_description"].c_str());
#endif
ImGui::Separator();
#ifndef ANDROID
ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - (ImGui::CalcTextSize(lang.emulator["emu_storage_folder"].c_str()).x / 2.f));
ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", lang.emulator["emu_storage_folder"].c_str());
ImGui::Spacing();
@ -1107,8 +1107,12 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", lang.emulator["reset_emu_path_description"].c_str());
}
#endif
ImGui::Spacing();
#ifdef ANDROID
ImGui::TextColored(GUI_COLOR_TEXT, "%s", "Using a different path requires additional permissions");
ImGui::Spacing();
#endif
ImGui::Separator();
ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - (ImGui::CalcTextSize(lang.emulator["custom_config_settings"].c_str()).x / 2.f));
ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", lang.emulator["custom_config_settings"].c_str());

View File

@ -108,13 +108,6 @@ Result pick_folder(fs::path &resulting_path, const fs::path& default_path = "");
*/
FILE *resolve_host_handle(const fs::path &path);
/**
* @brief Return a print-friendly path
*/
std::string resolve_path_string(const fs::path &path);
std::string resolve_filename(const fs::path &path);
/**
* @brief Get a string describing the last dialog error
*

View File

@ -33,28 +33,23 @@
#include <SDL.h>
#include <jni.h>
static bool file_dialog_running = false;
static std::atomic<bool> file_dialog_running = false;
// the result from the dialog, this is an UTF-8 string
static std::string dialog_result_uri = "";
static fs::path dialog_result_path = "";
// the resulting file descriptor from the dialog
static int dialog_result_fd = -1;
static std::string dialog_result_filename = "";
static std::map<fs::path, std::pair<int, std::string>> path_mapping;
static std::map<fs::path, int> path_mapping;
extern "C" JNIEXPORT void JNICALL
Java_org_vita3k_emulator_Emulator_filedialogReturn(JNIEnv *env, jobject thiz, jstring result_uri, jint result_fd, jstring filename) {
const char *result_ptr = env->GetStringUTFChars(result_uri, nullptr);
dialog_result_uri = std::string(result_ptr);
env->ReleaseStringUTFChars(result_uri, result_ptr);
result_ptr = env->GetStringUTFChars(filename, nullptr);
dialog_result_filename = std::string(result_ptr);
env->ReleaseStringUTFChars(filename, result_ptr);
Java_org_vita3k_emulator_Emulator_filedialogReturn(JNIEnv *env, jobject thiz, jstring result_path, jint result_fd) {
const char *result_ptr = env->GetStringUTFChars(result_path, nullptr);
dialog_result_path = fs::path(result_ptr);
env->ReleaseStringUTFChars(result_path, result_ptr);
dialog_result_fd = result_fd;
file_dialog_running = false;
file_dialog_running.store(false, std::memory_order_release);
}
/**
@ -86,11 +81,15 @@ std::string format_file_filter_extension_list(const std::vector<std::string> &fi
return formatted_string;
};
namespace host {
namespace dialog {
namespace filesystem {
Result open_file(fs::path &resulting_path, const std::vector<FileFilter>& file_filters, const fs::path& default_path) {
SDL_AndroidRequestPermission("android.permission.READ_EXTERNAL_STORAGE");
static void call_dialog_java_function(const char* name, bool need_write){
// These permissions are not needed on Android 11+
if(SDL_GetAndroidSDKVersion() < 30) {
SDL_AndroidRequestPermission("android.permission.READ_EXTERNAL_STORAGE");
if(need_write) {
SDL_AndroidRequestPermission("android.permission.WRITE_EXTERNAL_STORAGE");
}
}
// retrieve the JNI environment.
JNIEnv *env = reinterpret_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
@ -102,7 +101,7 @@ Result open_file(fs::path &resulting_path, const std::vector<FileFilter>& file_f
jclass clazz(env->GetObjectClass(activity));
// find the identifier of the method to call
jmethodID method_id = env->GetMethodID(clazz, "showFileDialog", "()V");
jmethodID method_id = env->GetMethodID(clazz, name, "()V");
file_dialog_running = true;
// effectively call the Java method
@ -112,22 +111,36 @@ Result open_file(fs::path &resulting_path, const std::vector<FileFilter>& file_f
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
while (file_dialog_running)
while (file_dialog_running.load(std::memory_order_acquire))
SDL_Delay(10);
}
if (dialog_result_uri.empty())
namespace host {
namespace dialog {
namespace filesystem {
Result open_file(fs::path &resulting_path, const std::vector<FileFilter>& file_filters, const fs::path& default_path) {
call_dialog_java_function("showFileDialog", false);
if (dialog_result_path.empty())
return Result::CANCEL;
resulting_path = fs::path(dialog_result_uri);
// if dialog_result_fd is -1, it means the file was copied and we can open it with the usual io functions
if (dialog_result_fd != -1)
path_mapping[resulting_path] = {dialog_result_fd, dialog_result_filename};
if(dialog_result_fd > 0)
path_mapping[dialog_result_path] = dialog_result_fd;
resulting_path = std::move(dialog_result_path);
return Result::SUCCESS;
};
Result pick_folder(fs::path &resulting_path, const fs::path& default_path) {
return Result::ERROR;
call_dialog_java_function("showFolderDialog", true);
if(dialog_result_path.empty())
return Result::CANCEL;
resulting_path = std::move(dialog_result_path);
return Result::SUCCESS;
};
std::string get_error() {
@ -143,33 +156,15 @@ std::string get_error() {
FILE *resolve_host_handle(const fs::path &path) {
auto it = path_mapping.find(path);
if (it != path_mapping.end() && it->second.first != -1) {
int fd = it->second.first;
if (it != path_mapping.end()) {
int fd = it->second;
return fdopen(fd, "rb");
} else {
return fopen(path.c_str(), "rb");
}
}
std::string resolve_path_string(const fs::path &path){
auto it = path_mapping.find(path);
if (it != path_mapping.end()) {
// this is only the filename but that's still better than giving the Uri
return it->second.second;
} else {
return std::string(path.c_str());
}
}
std::string resolve_filename(const fs::path &path){
auto it = path_mapping.find(path);
if (it != path_mapping.end()) {
return it->second.second;
} else {
return std::string(path.filename().c_str());
}
}
} // namespace filesystem
} // namespace dialog
} // namespace host

View File

@ -217,14 +217,6 @@ FILE *resolve_host_handle(const fs::path &path) {
return result;
}
std::string resolve_path_string(const fs::path &path){
return path.string();
}
std::string resolve_filename(const fs::path &path){
return path.filename().string();
}
} // namespace filesystem
} // namespace dialog
} // namespace host

View File

@ -8,3 +8,6 @@ add_library(
target_include_directories(motion PUBLIC include)
target_link_libraries(motion PUBLIC emuenv sdl2 util)
target_link_libraries(motion PRIVATE ctrl)
if(ANDROID)
target_link_libraries(host_dialog PRIVATE sdl2)
endif()

View File

@ -25,6 +25,30 @@
#include <SDL.h>
#include <SDL_gamecontroller.h>
#ifdef ANDROID
#include <jni.h>
static bool is_device_landscape = false;
static void init_device_orientation(){
JNIEnv *env = reinterpret_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
jobject activity = reinterpret_cast<jobject>(SDL_AndroidGetActivity());
jclass clazz(env->GetObjectClass(activity));
jmethodID method_id = env->GetMethodID(clazz, "isDefaultOrientationLandscape", "()Z");
is_device_landscape = env->CallBooleanMethod(activity, method_id);
// clean up the local references.
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
}
#else
constexpr bool is_device_landscape = true;
#endif
static void init_device_sensors(MotionState& state){
const int num_sensors = SDL_NumSensors();
for(int idx = 0; idx < num_sensors; idx++){
@ -48,6 +72,10 @@ static void init_device_sensors(MotionState& state){
SDL_SensorClose(sensor);
}
state.has_device_motion_support = (state.device_accel && state.device_gyro);
#ifdef ANDROID
init_device_orientation();
#endif
}
void MotionState::init(){
@ -189,16 +217,15 @@ void refresh_motion(MotionState &state, CtrlState &ctrl_state) {
}
gyro /= static_cast<float>(2.0 * M_PI);
if(gyro_from_device)
std::tie(gyro.x, gyro.y, gyro.z) = std::make_tuple(-gyro.y, gyro.z, -gyro.x);
std::swap(gyro.y, gyro.z);
gyro.y *= -1;
accel /= -SDL_STANDARD_GRAVITY;
if(accel_from_device)
std::tie(accel.x, accel.y, accel.z) = std::make_tuple(-accel.y, accel.z, -accel.x);
std::swap(accel.y, accel.z);
accel.y *= -1;
if(gyro_from_device && !is_device_landscape){
std::tie(gyro.x, gyro.y, gyro.z) = std::make_tuple(-gyro.y, gyro.x, gyro.z);
std::tie(accel.x, accel.y, accel.z) = std::make_tuple(-accel.y, accel.x, accel.z);
} else if (!gyro_from_device) {
std::tie(gyro.x, gyro.y, gyro.z) = std::make_tuple(gyro.x, -gyro.z, gyro.y);
std::tie(accel.x, accel.y, accel.z) = std::make_tuple(accel.x, -accel.z, accel.y);
}
std::lock_guard<std::mutex> guard(state.mutex);

View File

@ -252,6 +252,7 @@ bool install_pkg(const fs::path &pkg_path, EmuEnvState &emuenv, std::string &p_z
EVP_DecryptFinal_ex(cipher_ctx, data + dec_len, &dec_len);
};
std::vector<uint8_t> buffer(0x10000);
for (uint32_t i = 0; i < byte_swap(pkg_header.file_count); i++) {
PkgEntry entry;
uint64_t file_offset = items_offset + i * 32;
@ -289,16 +290,14 @@ bool install_pkg(const fs::path &pkg_path, EmuEnvState &emuenv, std::string &p_z
EVP_DecryptInit_ex(cipher_ctx, cipher_CTR, nullptr, main_key, counter);
EVP_CIPHER_CTX_set_padding(cipher_ctx, 0);
std::vector<uint8_t> buffer(0x10000);
fseek(infile, byte_swap(pkg_header.data_offset) + offset, SEEK_SET);
while (data_size != 0) {
auto size = data_size < sizeof(buffer) ? data_size : sizeof(buffer);
fseek(infile, byte_swap(pkg_header.data_offset) + offset, SEEK_SET);
size_t size = data_size < buffer.size() ? data_size : buffer.size();
fread(buffer.data(), size, 1, infile);
EVP_DecryptUpdate(cipher_ctx, buffer.data(), &dec_len, buffer.data(), size);
outfile.write(reinterpret_cast<char *>(buffer.data()), dec_len);
offset += size;
data_size -= size;
}

View File

@ -241,8 +241,8 @@ void convert_f32m_to_f32(void *dest, const void *data, const uint32_t width, con
for (uint32_t row = 0; row < height; ++row) {
for (uint32_t col = 0; col < width; ++col) {
const uint32_t src_value = src[row * width + height];
dst[row * width + height] = src_value & 0x7FFFFFFF;
const uint32_t src_value = src[row * width + col];
dst[row * width + col] = src_value & 0x7FFFFFFF;
}
}
}