add android project to branch

This commit is contained in:
Dan Ware 2024-10-02 16:47:04 +01:00
parent 92117c299f
commit 09d96310fc
85 changed files with 10152 additions and 0 deletions

12
src/RyujinxAndroid/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.idea/
*.iml
.gradle
local.properties
.DS_Store
build/
captures
.externalNativeBuild
.cxx/
app/src/main/jniLibs/arm64-v8a/**
!app/src/main/jniLibs/arm64-v8a/.gitkeep

View File

@ -0,0 +1,117 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'org.ryujinx.android'
compileSdk 34
defaultConfig {
applicationId "org.ryujinx.android"
minSdk 30
targetSdk 34
versionCode 10044
versionName '1.0.44'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
ndk {
//noinspection ChromeOsAbiSupport
abiFilters 'arm64-v8a'
}
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
arguments "-DANDROID_STL=c++_shared"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
buildFeatures {
compose true
prefab true
buildConfig true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.13'
}
packagingOptions {
jniLibs {
keepDebugSymbols += '**/libryujinx.so'
useLegacyPackaging true
}
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
}
tasks.named("preBuild") {
dependsOn ':libryujinx:assemble'
}
dependencies {
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.14.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation platform('androidx.compose:compose-bom:2024.05.00')
implementation platform('androidx.compose:compose-bom:2024.05.00')
androidTestImplementation platform('androidx.compose:compose-bom:2024.05.00')
androidTestImplementation platform('androidx.compose:compose-bom:2024.05.00')
runtimeOnly project(":libryujinx")
implementation 'androidx.core:core-ktx:1.13.1'
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.9.24')
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation "androidx.navigation:navigation-compose:2.7.7"
implementation 'androidx.activity:activity-compose:1.9.0'
implementation platform('androidx.compose:compose-bom:2024.05.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'com.github.swordfish90:radialgamepad:2.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "com.anggrayudi:storage:1.5.5"
implementation "androidx.preference:preference-ktx:1.2.1"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'net.lingala.zip4j:zip4j:2.11.5'
implementation("br.com.devsrsouza.compose.icons:css-gg:1.1.0")
implementation 'io.coil-kt:coil-compose:2.6.0'
implementation("com.halilibo.compose-richtext:richtext-ui:0.20.0")
implementation("com.halilibo.compose-richtext:richtext-commonmark:0.20.0")
implementation("com.halilibo.compose-richtext:richtext-ui-material3:0.20.0")
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2024.05.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}

View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package org.ryujinx.android
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.ryujinx.android", appContext.packageName)
}
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.audio.output"
android:required="true" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".RyujinxApplication"
android:allowBackup="true"
android:appCategory="game"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:isGame="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.RyujinxAndroid"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/Theme.RyujinxAndroid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.ryujinx.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name=".providers.DocumentProvider"
android:authorities="org.ryujinx.android.providers"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
</manifest>

View File

@ -0,0 +1,77 @@
include(FetchContent)
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.22.1)
# Declares and names the project.
project("ryujinxjni")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
FetchContent_Declare(
adrenotools
GIT_REPOSITORY https://github.com/bylaws/libadrenotools.git
GIT_TAG deec5f75ee1a8ccbe32c8780b1d17284fc87b0f1 # v1.0-14-gdeec5f7
)
FetchContent_MakeAvailable(adrenotools)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
ryujinxjni
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
vulkan_wrapper.cpp
ryujinx.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
ryujinxjni
# Links the target library to the log library
# included in the NDK.
${log-lib}
-lvulkan
-landroid
adrenotools
)
# Build external libraries if prebuilt files don't exist
set(JNI_PATH ../jniLibs/${CMAKE_ANDROID_ARCH_ABI})
cmake_path(ABSOLUTE_PATH JNI_PATH NORMALIZE)
cmake_path(APPEND JNI_PATH libcrypto.so OUTPUT_VARIABLE LIBCRYPTO_JNI_PATH)
cmake_path(APPEND JNI_PATH libssl.so OUTPUT_VARIABLE LIBSSL_JNI_PATH)
if (NOT (EXISTS ${LIBCRYPTO_JNI_PATH} AND EXISTS ${LIBSSL_JNI_PATH}))
include(../../../../libryujinx/libs/OpenSSL.cmake)
add_dependencies(ryujinxjni openssl)
endif ()

View File

@ -0,0 +1,305 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2021 The Android Open Source Project
#pragma once
/* A collection of various types from AOSP that allow us to access private APIs for Native Window which we utilize for emulating the guest SF more accurately */
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=29;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
#define ANDROID_NATIVE_UNSIGNED_CAST(x) static_cast<unsigned int>(x)
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=34-38;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
#define ANDROID_NATIVE_MAKE_CONSTANT(a, b, c, d) \
((ANDROID_NATIVE_UNSIGNED_CAST(a) << 24) | \
(ANDROID_NATIVE_UNSIGNED_CAST(b) << 16) | \
(ANDROID_NATIVE_UNSIGNED_CAST(c) << 8) | \
(ANDROID_NATIVE_UNSIGNED_CAST(d)))
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=60;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
#define ANDROID_NATIVE_WINDOW_MAGIC ANDROID_NATIVE_MAKE_CONSTANT('_','w','n','d')
constexpr int AndroidNativeWindowMagic{ANDROID_NATIVE_WINDOW_MAGIC};
#undef ANDROID_NATIVE_WINDOW_MAGIC
#undef ANDROID_NATIVE_MAKE_CONSTANT
#undef ANDROID_NATIVE_UNSIGNED_CAST
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=325-331;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
constexpr int64_t NativeWindowTimestampAuto{-9223372036854775807LL - 1};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=198-259;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
enum {
NATIVE_WINDOW_CONNECT = 1, /* deprecated */
NATIVE_WINDOW_DISCONNECT = 2, /* deprecated */
NATIVE_WINDOW_SET_CROP = 3, /* private */
NATIVE_WINDOW_SET_BUFFER_COUNT = 4,
NATIVE_WINDOW_SET_BUFFERS_TRANSFORM = 6,
NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP = 7,
NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS = 8,
NATIVE_WINDOW_SET_SCALING_MODE = 10, /* private */
NATIVE_WINDOW_LOCK = 11, /* private */
NATIVE_WINDOW_UNLOCK_AND_POST = 12, /* private */
NATIVE_WINDOW_API_CONNECT = 13, /* private */
NATIVE_WINDOW_API_DISCONNECT = 14, /* private */
NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS = 15, /* private */
NATIVE_WINDOW_SET_POST_TRANSFORM_CROP = 16, /* deprecated, unimplemented */
NATIVE_WINDOW_SET_BUFFERS_STICKY_TRANSFORM = 17, /* private */
NATIVE_WINDOW_SET_SIDEBAND_STREAM = 18,
NATIVE_WINDOW_SET_BUFFERS_DATASPACE = 19,
NATIVE_WINDOW_SET_SURFACE_DAMAGE = 20, /* private */
NATIVE_WINDOW_SET_SHARED_BUFFER_MODE = 21,
NATIVE_WINDOW_SET_AUTO_REFRESH = 22,
NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION = 23,
NATIVE_WINDOW_GET_NEXT_FRAME_ID = 24,
NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25,
NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26,
NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27,
NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28,
NATIVE_WINDOW_GET_HDR_SUPPORT = 29,
NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31,
NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32,
NATIVE_WINDOW_SET_BUFFERS_CTA861_3_METADATA = 33,
NATIVE_WINDOW_SET_BUFFERS_HDR10_PLUS_METADATA = 34,
NATIVE_WINDOW_SET_AUTO_PREROTATION = 35,
NATIVE_WINDOW_GET_LAST_DEQUEUE_START = 36, /* private */
NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT = 37, /* private */
NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION = 38, /* private */
NATIVE_WINDOW_GET_LAST_QUEUE_DURATION = 39, /* private */
NATIVE_WINDOW_SET_FRAME_RATE = 40,
NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR = 41, /* private */
NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR = 42, /* private */
NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR = 43, /* private */
NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR = 44, /* private */
NATIVE_WINDOW_ALLOCATE_BUFFERS = 45, /* private */
NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER = 46, /* private */
NATIVE_WINDOW_SET_QUERY_INTERCEPTOR = 47, /* private */
NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2 = 50, /* private */
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=43-56;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
struct android_native_base_t {
int magic;
int version;
void *reserved[4];
void (*incRef)(android_native_base_t *);
void (*decRef)(android_native_base_t *);
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=341-560;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
struct ANativeWindow {
struct android_native_base_t common;
/* flags describing some attributes of this surface or its updater */
const uint32_t flags;
/* min swap interval supported by this updated */
const int minSwapInterval;
/* max swap interval supported by this updated */
const int maxSwapInterval;
/* horizontal and vertical resolution in DPI */
const float xdpi;
const float ydpi;
/* Some storage reserved for the OEM's driver. */
intptr_t oem[4];
/*
* Set the swap interval for this surface.
*
* Returns 0 on success or -errno on error.
*/
int (*setSwapInterval)(struct ANativeWindow *window,
int interval);
/*
* Hook called by EGL to acquire a buffer. After this call, the buffer
* is not locked, so its content cannot be modified. This call may block if
* no buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new dequeueBuffer function that
* outputs a fence file descriptor should be used in its place.
*/
int (*dequeueBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer **buffer);
/*
* hook called by EGL to lock a buffer. This MUST be called before modifying
* the content of a buffer. The buffer must have been acquired with
* dequeueBuffer first.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but it is essentially a no-op, and calls
* to it should be removed.
*/
int (*lockBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Buffers MUST be queued in the same order than they were dequeued.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new queueBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*queueBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* hook used to retrieve information about the native window.
*
* Returns 0 on success or -errno on error.
*/
int (*query)(const struct ANativeWindow *window,
int what, int *value);
/*
* hook used to perform various operations on the surface.
* (*perform)() is a generic mechanism to add functionality to
* ANativeWindow while keeping backward binary compatibility.
*
* DO NOT CALL THIS HOOK DIRECTLY. Instead, use the helper functions
* defined below.
*
* (*perform)() returns -ENOENT if the 'what' parameter is not supported
* by the surface's implementation.
*
* See above for a list of valid operations, such as
* NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT
*/
int (*perform)(struct ANativeWindow *window,
int operation, ...);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new cancelBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*cancelBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* Hook called by EGL to acquire a buffer. This call may block if no
* buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The libsync fence file descriptor returned in the int pointed to by the
* fenceFd argument will refer to the fence that must signal before the
* dequeued buffer may be written to. A value of -1 indicates that the
* caller may access the buffer immediately without waiting on a fence. If
* a valid file descriptor is returned (i.e. any value except -1) then the
* caller is responsible for closing the file descriptor.
*
* Returns 0 on success or -errno on error.
*/
int (*dequeueBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer **buffer, int *fenceFd);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file descriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used. The
* caller must not use the file descriptor after it is passed to
* queueBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*queueBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer, int fenceFd);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file decsriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used.
*
* Note that if the client has not waited on the fence that was returned
* from dequeueBuffer, that same fence should be passed to cancelBuffer to
* ensure that future uses of the buffer are preceded by a wait on that
* fence. The caller must not use the file descriptor after it is passed
* to cancelBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*cancelBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer, int fenceFd);
};

View File

@ -0,0 +1,49 @@
//
// Created by Emmanuel Hansen on 6/19/2023.
//
#ifndef RYUJINXNATIVE_RYUIJNX_H
#define RYUJINXNATIVE_RYUIJNX_H
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <string>
#include <jni.h>
#include <exception>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "vulkan_wrapper.h"
#include <vulkan/vulkan_android.h>
#include <cassert>
#include <fcntl.h>
#include "adrenotools/driver.h"
#include "native_window.h"
// A macro to pass call to Vulkan and check for return value for success
#define CALL_VK(func) \
if (VK_SUCCESS != (func)) { \
__android_log_print(ANDROID_LOG_ERROR, "Tutorial ", \
"Vulkan error. File[%s], line[%d]", __FILE__, \
__LINE__); \
assert(false); \
}
// A macro to check value is VK_SUCCESS
// Used also for non-vulkan functions but return VK_SUCCESS
#define VK_CHECK(x) CALL_VK(x)
#define LoadLib(a) dlopen(a, RTLD_NOW)
void *_ryujinxNative = NULL;
// Ryujinx imported functions
bool (*initialize)(char *) = NULL;
long _renderingThreadId = 0;
JavaVM *_vm = nullptr;
jobject _mainActivity = nullptr;
jclass _mainActivityClass = nullptr;
#endif //RYUJINXNATIVE_RYUIJNX_H

View File

@ -0,0 +1,250 @@
// Write C++ code here.
//
// Do not forget to dynamically load the C++ library into your application.
//
// For instance,
//
// In MainActivity.java:
// static {
// System.loadLibrary("ryuijnx");
// }
//
// Or, in MainActivity.kt:
// companion object {
// init {
// System.loadLibrary("ryuijnx")
// }
// }
#include "ryuijnx.h"
#include "pthread.h"
#include <chrono>
#include <csignal>
std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _currentTimePoint;
extern "C"
{
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getNativeWindow(
JNIEnv *env,
jobject instance,
jobject surface) {
auto nativeWindow = ANativeWindow_fromSurface(env, surface);
return nativeWindow == NULL ? -1 : (jlong) nativeWindow;
}
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_releaseNativeWindow(
JNIEnv *env,
jobject instance,
jlong window) {
auto nativeWindow = (ANativeWindow *) window;
if (nativeWindow != NULL)
ANativeWindow_release(nativeWindow);
}
long createSurface(long native_surface, long instance) {
auto nativeWindow = (ANativeWindow *) native_surface;
VkSurfaceKHR surface;
auto vkInstance = (VkInstance) instance;
auto fpCreateAndroidSurfaceKHR =
reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(vkGetInstanceProcAddr(vkInstance,
"vkCreateAndroidSurfaceKHR"));
if (!fpCreateAndroidSurfaceKHR)
return -1;
VkAndroidSurfaceCreateInfoKHR info = {VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
info.window = nativeWindow;
VK_CHECK(fpCreateAndroidSurfaceKHR(vkInstance, &info, nullptr, &surface));
return (long) surface;
}
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getCreateSurfacePtr(
JNIEnv *env,
jobject instance) {
return (jlong) createSurface;
}
char *getStringPointer(
JNIEnv *env,
jstring jS) {
const char *cparam = env->GetStringUTFChars(jS, 0);
auto len = env->GetStringUTFLength(jS);
char *s = new char[len];
strcpy(s, cparam);
env->ReleaseStringUTFChars(jS, cparam);
return s;
}
jstring createString(
JNIEnv *env,
char *ch) {
auto str = env->NewStringUTF(ch);
return str;
}
jstring createStringFromStdString(
JNIEnv *env,
std::string s) {
auto str = env->NewStringUTF(s.c_str());
return str;
}
}
extern "C"
void setRenderingThread() {
auto currentId = pthread_self();
_renderingThreadId = currentId;
_currentTimePoint = std::chrono::high_resolution_clock::now();
}
extern "C"
JNIEXPORT void JNICALL
Java_org_ryujinx_android_MainActivity_initVm(JNIEnv *env, jobject thiz) {
JavaVM *vm = nullptr;
auto success = env->GetJavaVM(&vm);
_vm = vm;
_mainActivity = thiz;
_mainActivityClass = env->GetObjectClass(thiz);
}
bool isInitialOrientationFlipped = true;
extern "C"
void setCurrentTransform(long native_window, int transform) {
if (native_window == 0 || native_window == -1)
return;
auto nativeWindow = (ANativeWindow *) native_window;
auto nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
transform = transform >> 1;
// transform is a valid VkSurfaceTransformFlagBitsKHR
switch (transform) {
case 0x1:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
break;
case 0x2:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90;
break;
case 0x4:
nativeTransform = isInitialOrientationFlipped
? ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY
: ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180;
break;
case 0x8:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270;
break;
case 0x10:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL;
break;
case 0x20:
nativeTransform = static_cast<ANativeWindowTransform>(
ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL |
ANATIVEWINDOW_TRANSFORM_ROTATE_90);
break;
case 0x40:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL;
break;
case 0x80:
nativeTransform = static_cast<ANativeWindowTransform>(
ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL |
ANATIVEWINDOW_TRANSFORM_ROTATE_90);
break;
case 0x100:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
break;
}
nativeWindow->perform(nativeWindow, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM,
static_cast<int32_t>(nativeTransform));
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_loadDriver(JNIEnv *env, jobject thiz,
jstring native_lib_path,
jstring private_apps_path,
jstring driver_name) {
auto libPath = getStringPointer(env, native_lib_path);
auto privateAppsPath = getStringPointer(env, private_apps_path);
auto driverName = getStringPointer(env, driver_name);
auto handle = adrenotools_open_libvulkan(
RTLD_NOW,
ADRENOTOOLS_DRIVER_CUSTOM,
nullptr,
libPath,
privateAppsPath,
driverName,
nullptr,
nullptr
);
delete libPath;
delete privateAppsPath;
delete driverName;
return (jlong) handle;
}
extern "C"
void debug_break(int code) {
if (code >= 3)
int r = 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_setTurboMode(JNIEnv *env, jobject thiz, jboolean enable) {
adrenotools_set_turbo(enable);
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getMaxSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->maxSwapInterval;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getMinSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->minSwapInterval;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_setSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window, jint swap_interval) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->setSwapInterval(nativeWindow, swap_interval);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong ptr) {
return createString(env, (char*)ptr);
}
extern "C"
JNIEXPORT void JNICALL
Java_org_ryujinx_android_NativeHelpers_setIsInitialOrientationFlipped(JNIEnv *env, jobject thiz,
jboolean is_flipped) {
isInitialOrientationFlipped = is_flipped;
}

View File

@ -0,0 +1,404 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is generated.
#include "vulkan_wrapper.h"
#include <dlfcn.h>
int InitVulkan(void) {
void* libvulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!libvulkan)
return 0;
// Vulkan supported, set function addresses
vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(dlsym(libvulkan, "vkCreateInstance"));
vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(dlsym(libvulkan, "vkDestroyInstance"));
vkEnumeratePhysicalDevices = reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(dlsym(libvulkan, "vkEnumeratePhysicalDevices"));
vkGetPhysicalDeviceFeatures = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures>(dlsym(libvulkan, "vkGetPhysicalDeviceFeatures"));
vkGetPhysicalDeviceFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceFormatProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceFormatProperties"));
vkGetPhysicalDeviceImageFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceImageFormatProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceImageFormatProperties"));
vkGetPhysicalDeviceProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceProperties"));
vkGetPhysicalDeviceQueueFamilyProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceQueueFamilyProperties"));
vkGetPhysicalDeviceMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceMemoryProperties"));
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(libvulkan, "vkGetInstanceProcAddr"));
vkGetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(dlsym(libvulkan, "vkGetDeviceProcAddr"));
vkCreateDevice = reinterpret_cast<PFN_vkCreateDevice>(dlsym(libvulkan, "vkCreateDevice"));
vkDestroyDevice = reinterpret_cast<PFN_vkDestroyDevice>(dlsym(libvulkan, "vkDestroyDevice"));
vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(dlsym(libvulkan, "vkEnumerateInstanceExtensionProperties"));
vkEnumerateDeviceExtensionProperties = reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(dlsym(libvulkan, "vkEnumerateDeviceExtensionProperties"));
vkEnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(dlsym(libvulkan, "vkEnumerateInstanceLayerProperties"));
vkEnumerateDeviceLayerProperties = reinterpret_cast<PFN_vkEnumerateDeviceLayerProperties>(dlsym(libvulkan, "vkEnumerateDeviceLayerProperties"));
vkGetDeviceQueue = reinterpret_cast<PFN_vkGetDeviceQueue>(dlsym(libvulkan, "vkGetDeviceQueue"));
vkQueueSubmit = reinterpret_cast<PFN_vkQueueSubmit>(dlsym(libvulkan, "vkQueueSubmit"));
vkQueueWaitIdle = reinterpret_cast<PFN_vkQueueWaitIdle>(dlsym(libvulkan, "vkQueueWaitIdle"));
vkDeviceWaitIdle = reinterpret_cast<PFN_vkDeviceWaitIdle>(dlsym(libvulkan, "vkDeviceWaitIdle"));
vkAllocateMemory = reinterpret_cast<PFN_vkAllocateMemory>(dlsym(libvulkan, "vkAllocateMemory"));
vkFreeMemory = reinterpret_cast<PFN_vkFreeMemory>(dlsym(libvulkan, "vkFreeMemory"));
vkMapMemory = reinterpret_cast<PFN_vkMapMemory>(dlsym(libvulkan, "vkMapMemory"));
vkUnmapMemory = reinterpret_cast<PFN_vkUnmapMemory>(dlsym(libvulkan, "vkUnmapMemory"));
vkFlushMappedMemoryRanges = reinterpret_cast<PFN_vkFlushMappedMemoryRanges>(dlsym(libvulkan, "vkFlushMappedMemoryRanges"));
vkInvalidateMappedMemoryRanges = reinterpret_cast<PFN_vkInvalidateMappedMemoryRanges>(dlsym(libvulkan, "vkInvalidateMappedMemoryRanges"));
vkGetDeviceMemoryCommitment = reinterpret_cast<PFN_vkGetDeviceMemoryCommitment>(dlsym(libvulkan, "vkGetDeviceMemoryCommitment"));
vkBindBufferMemory = reinterpret_cast<PFN_vkBindBufferMemory>(dlsym(libvulkan, "vkBindBufferMemory"));
vkBindImageMemory = reinterpret_cast<PFN_vkBindImageMemory>(dlsym(libvulkan, "vkBindImageMemory"));
vkGetBufferMemoryRequirements = reinterpret_cast<PFN_vkGetBufferMemoryRequirements>(dlsym(libvulkan, "vkGetBufferMemoryRequirements"));
vkGetImageMemoryRequirements = reinterpret_cast<PFN_vkGetImageMemoryRequirements>(dlsym(libvulkan, "vkGetImageMemoryRequirements"));
vkGetImageSparseMemoryRequirements = reinterpret_cast<PFN_vkGetImageSparseMemoryRequirements>(dlsym(libvulkan, "vkGetImageSparseMemoryRequirements"));
vkGetPhysicalDeviceSparseImageFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceSparseImageFormatProperties"));
vkQueueBindSparse = reinterpret_cast<PFN_vkQueueBindSparse>(dlsym(libvulkan, "vkQueueBindSparse"));
vkCreateFence = reinterpret_cast<PFN_vkCreateFence>(dlsym(libvulkan, "vkCreateFence"));
vkDestroyFence = reinterpret_cast<PFN_vkDestroyFence>(dlsym(libvulkan, "vkDestroyFence"));
vkResetFences = reinterpret_cast<PFN_vkResetFences>(dlsym(libvulkan, "vkResetFences"));
vkGetFenceStatus = reinterpret_cast<PFN_vkGetFenceStatus>(dlsym(libvulkan, "vkGetFenceStatus"));
vkWaitForFences = reinterpret_cast<PFN_vkWaitForFences>(dlsym(libvulkan, "vkWaitForFences"));
vkCreateSemaphore = reinterpret_cast<PFN_vkCreateSemaphore>(dlsym(libvulkan, "vkCreateSemaphore"));
vkDestroySemaphore = reinterpret_cast<PFN_vkDestroySemaphore>(dlsym(libvulkan, "vkDestroySemaphore"));
vkCreateEvent = reinterpret_cast<PFN_vkCreateEvent>(dlsym(libvulkan, "vkCreateEvent"));
vkDestroyEvent = reinterpret_cast<PFN_vkDestroyEvent>(dlsym(libvulkan, "vkDestroyEvent"));
vkGetEventStatus = reinterpret_cast<PFN_vkGetEventStatus>(dlsym(libvulkan, "vkGetEventStatus"));
vkSetEvent = reinterpret_cast<PFN_vkSetEvent>(dlsym(libvulkan, "vkSetEvent"));
vkResetEvent = reinterpret_cast<PFN_vkResetEvent>(dlsym(libvulkan, "vkResetEvent"));
vkCreateQueryPool = reinterpret_cast<PFN_vkCreateQueryPool>(dlsym(libvulkan, "vkCreateQueryPool"));
vkDestroyQueryPool = reinterpret_cast<PFN_vkDestroyQueryPool>(dlsym(libvulkan, "vkDestroyQueryPool"));
vkGetQueryPoolResults = reinterpret_cast<PFN_vkGetQueryPoolResults>(dlsym(libvulkan, "vkGetQueryPoolResults"));
vkCreateBuffer = reinterpret_cast<PFN_vkCreateBuffer>(dlsym(libvulkan, "vkCreateBuffer"));
vkDestroyBuffer = reinterpret_cast<PFN_vkDestroyBuffer>(dlsym(libvulkan, "vkDestroyBuffer"));
vkCreateBufferView = reinterpret_cast<PFN_vkCreateBufferView>(dlsym(libvulkan, "vkCreateBufferView"));
vkDestroyBufferView = reinterpret_cast<PFN_vkDestroyBufferView>(dlsym(libvulkan, "vkDestroyBufferView"));
vkCreateImage = reinterpret_cast<PFN_vkCreateImage>(dlsym(libvulkan, "vkCreateImage"));
vkDestroyImage = reinterpret_cast<PFN_vkDestroyImage>(dlsym(libvulkan, "vkDestroyImage"));
vkGetImageSubresourceLayout = reinterpret_cast<PFN_vkGetImageSubresourceLayout>(dlsym(libvulkan, "vkGetImageSubresourceLayout"));
vkCreateImageView = reinterpret_cast<PFN_vkCreateImageView>(dlsym(libvulkan, "vkCreateImageView"));
vkDestroyImageView = reinterpret_cast<PFN_vkDestroyImageView>(dlsym(libvulkan, "vkDestroyImageView"));
vkCreateShaderModule = reinterpret_cast<PFN_vkCreateShaderModule>(dlsym(libvulkan, "vkCreateShaderModule"));
vkDestroyShaderModule = reinterpret_cast<PFN_vkDestroyShaderModule>(dlsym(libvulkan, "vkDestroyShaderModule"));
vkCreatePipelineCache = reinterpret_cast<PFN_vkCreatePipelineCache>(dlsym(libvulkan, "vkCreatePipelineCache"));
vkDestroyPipelineCache = reinterpret_cast<PFN_vkDestroyPipelineCache>(dlsym(libvulkan, "vkDestroyPipelineCache"));
vkGetPipelineCacheData = reinterpret_cast<PFN_vkGetPipelineCacheData>(dlsym(libvulkan, "vkGetPipelineCacheData"));
vkMergePipelineCaches = reinterpret_cast<PFN_vkMergePipelineCaches>(dlsym(libvulkan, "vkMergePipelineCaches"));
vkCreateGraphicsPipelines = reinterpret_cast<PFN_vkCreateGraphicsPipelines>(dlsym(libvulkan, "vkCreateGraphicsPipelines"));
vkCreateComputePipelines = reinterpret_cast<PFN_vkCreateComputePipelines>(dlsym(libvulkan, "vkCreateComputePipelines"));
vkDestroyPipeline = reinterpret_cast<PFN_vkDestroyPipeline>(dlsym(libvulkan, "vkDestroyPipeline"));
vkCreatePipelineLayout = reinterpret_cast<PFN_vkCreatePipelineLayout>(dlsym(libvulkan, "vkCreatePipelineLayout"));
vkDestroyPipelineLayout = reinterpret_cast<PFN_vkDestroyPipelineLayout>(dlsym(libvulkan, "vkDestroyPipelineLayout"));
vkCreateSampler = reinterpret_cast<PFN_vkCreateSampler>(dlsym(libvulkan, "vkCreateSampler"));
vkDestroySampler = reinterpret_cast<PFN_vkDestroySampler>(dlsym(libvulkan, "vkDestroySampler"));
vkCreateDescriptorSetLayout = reinterpret_cast<PFN_vkCreateDescriptorSetLayout>(dlsym(libvulkan, "vkCreateDescriptorSetLayout"));
vkDestroyDescriptorSetLayout = reinterpret_cast<PFN_vkDestroyDescriptorSetLayout>(dlsym(libvulkan, "vkDestroyDescriptorSetLayout"));
vkCreateDescriptorPool = reinterpret_cast<PFN_vkCreateDescriptorPool>(dlsym(libvulkan, "vkCreateDescriptorPool"));
vkDestroyDescriptorPool = reinterpret_cast<PFN_vkDestroyDescriptorPool>(dlsym(libvulkan, "vkDestroyDescriptorPool"));
vkResetDescriptorPool = reinterpret_cast<PFN_vkResetDescriptorPool>(dlsym(libvulkan, "vkResetDescriptorPool"));
vkAllocateDescriptorSets = reinterpret_cast<PFN_vkAllocateDescriptorSets>(dlsym(libvulkan, "vkAllocateDescriptorSets"));
vkFreeDescriptorSets = reinterpret_cast<PFN_vkFreeDescriptorSets>(dlsym(libvulkan, "vkFreeDescriptorSets"));
vkUpdateDescriptorSets = reinterpret_cast<PFN_vkUpdateDescriptorSets>(dlsym(libvulkan, "vkUpdateDescriptorSets"));
vkCreateFramebuffer = reinterpret_cast<PFN_vkCreateFramebuffer>(dlsym(libvulkan, "vkCreateFramebuffer"));
vkDestroyFramebuffer = reinterpret_cast<PFN_vkDestroyFramebuffer>(dlsym(libvulkan, "vkDestroyFramebuffer"));
vkCreateRenderPass = reinterpret_cast<PFN_vkCreateRenderPass>(dlsym(libvulkan, "vkCreateRenderPass"));
vkDestroyRenderPass = reinterpret_cast<PFN_vkDestroyRenderPass>(dlsym(libvulkan, "vkDestroyRenderPass"));
vkGetRenderAreaGranularity = reinterpret_cast<PFN_vkGetRenderAreaGranularity>(dlsym(libvulkan, "vkGetRenderAreaGranularity"));
vkCreateCommandPool = reinterpret_cast<PFN_vkCreateCommandPool>(dlsym(libvulkan, "vkCreateCommandPool"));
vkDestroyCommandPool = reinterpret_cast<PFN_vkDestroyCommandPool>(dlsym(libvulkan, "vkDestroyCommandPool"));
vkResetCommandPool = reinterpret_cast<PFN_vkResetCommandPool>(dlsym(libvulkan, "vkResetCommandPool"));
vkAllocateCommandBuffers = reinterpret_cast<PFN_vkAllocateCommandBuffers>(dlsym(libvulkan, "vkAllocateCommandBuffers"));
vkFreeCommandBuffers = reinterpret_cast<PFN_vkFreeCommandBuffers>(dlsym(libvulkan, "vkFreeCommandBuffers"));
vkBeginCommandBuffer = reinterpret_cast<PFN_vkBeginCommandBuffer>(dlsym(libvulkan, "vkBeginCommandBuffer"));
vkEndCommandBuffer = reinterpret_cast<PFN_vkEndCommandBuffer>(dlsym(libvulkan, "vkEndCommandBuffer"));
vkResetCommandBuffer = reinterpret_cast<PFN_vkResetCommandBuffer>(dlsym(libvulkan, "vkResetCommandBuffer"));
vkCmdBindPipeline = reinterpret_cast<PFN_vkCmdBindPipeline>(dlsym(libvulkan, "vkCmdBindPipeline"));
vkCmdSetViewport = reinterpret_cast<PFN_vkCmdSetViewport>(dlsym(libvulkan, "vkCmdSetViewport"));
vkCmdSetScissor = reinterpret_cast<PFN_vkCmdSetScissor>(dlsym(libvulkan, "vkCmdSetScissor"));
vkCmdSetLineWidth = reinterpret_cast<PFN_vkCmdSetLineWidth>(dlsym(libvulkan, "vkCmdSetLineWidth"));
vkCmdSetDepthBias = reinterpret_cast<PFN_vkCmdSetDepthBias>(dlsym(libvulkan, "vkCmdSetDepthBias"));
vkCmdSetBlendConstants = reinterpret_cast<PFN_vkCmdSetBlendConstants>(dlsym(libvulkan, "vkCmdSetBlendConstants"));
vkCmdSetDepthBounds = reinterpret_cast<PFN_vkCmdSetDepthBounds>(dlsym(libvulkan, "vkCmdSetDepthBounds"));
vkCmdSetStencilCompareMask = reinterpret_cast<PFN_vkCmdSetStencilCompareMask>(dlsym(libvulkan, "vkCmdSetStencilCompareMask"));
vkCmdSetStencilWriteMask = reinterpret_cast<PFN_vkCmdSetStencilWriteMask>(dlsym(libvulkan, "vkCmdSetStencilWriteMask"));
vkCmdSetStencilReference = reinterpret_cast<PFN_vkCmdSetStencilReference>(dlsym(libvulkan, "vkCmdSetStencilReference"));
vkCmdBindDescriptorSets = reinterpret_cast<PFN_vkCmdBindDescriptorSets>(dlsym(libvulkan, "vkCmdBindDescriptorSets"));
vkCmdBindIndexBuffer = reinterpret_cast<PFN_vkCmdBindIndexBuffer>(dlsym(libvulkan, "vkCmdBindIndexBuffer"));
vkCmdBindVertexBuffers = reinterpret_cast<PFN_vkCmdBindVertexBuffers>(dlsym(libvulkan, "vkCmdBindVertexBuffers"));
vkCmdDraw = reinterpret_cast<PFN_vkCmdDraw>(dlsym(libvulkan, "vkCmdDraw"));
vkCmdDrawIndexed = reinterpret_cast<PFN_vkCmdDrawIndexed>(dlsym(libvulkan, "vkCmdDrawIndexed"));
vkCmdDrawIndirect = reinterpret_cast<PFN_vkCmdDrawIndirect>(dlsym(libvulkan, "vkCmdDrawIndirect"));
vkCmdDrawIndexedIndirect = reinterpret_cast<PFN_vkCmdDrawIndexedIndirect>(dlsym(libvulkan, "vkCmdDrawIndexedIndirect"));
vkCmdDispatch = reinterpret_cast<PFN_vkCmdDispatch>(dlsym(libvulkan, "vkCmdDispatch"));
vkCmdDispatchIndirect = reinterpret_cast<PFN_vkCmdDispatchIndirect>(dlsym(libvulkan, "vkCmdDispatchIndirect"));
vkCmdCopyBuffer = reinterpret_cast<PFN_vkCmdCopyBuffer>(dlsym(libvulkan, "vkCmdCopyBuffer"));
vkCmdCopyImage = reinterpret_cast<PFN_vkCmdCopyImage>(dlsym(libvulkan, "vkCmdCopyImage"));
vkCmdBlitImage = reinterpret_cast<PFN_vkCmdBlitImage>(dlsym(libvulkan, "vkCmdBlitImage"));
vkCmdCopyBufferToImage = reinterpret_cast<PFN_vkCmdCopyBufferToImage>(dlsym(libvulkan, "vkCmdCopyBufferToImage"));
vkCmdCopyImageToBuffer = reinterpret_cast<PFN_vkCmdCopyImageToBuffer>(dlsym(libvulkan, "vkCmdCopyImageToBuffer"));
vkCmdUpdateBuffer = reinterpret_cast<PFN_vkCmdUpdateBuffer>(dlsym(libvulkan, "vkCmdUpdateBuffer"));
vkCmdFillBuffer = reinterpret_cast<PFN_vkCmdFillBuffer>(dlsym(libvulkan, "vkCmdFillBuffer"));
vkCmdClearColorImage = reinterpret_cast<PFN_vkCmdClearColorImage>(dlsym(libvulkan, "vkCmdClearColorImage"));
vkCmdClearDepthStencilImage = reinterpret_cast<PFN_vkCmdClearDepthStencilImage>(dlsym(libvulkan, "vkCmdClearDepthStencilImage"));
vkCmdClearAttachments = reinterpret_cast<PFN_vkCmdClearAttachments>(dlsym(libvulkan, "vkCmdClearAttachments"));
vkCmdResolveImage = reinterpret_cast<PFN_vkCmdResolveImage>(dlsym(libvulkan, "vkCmdResolveImage"));
vkCmdSetEvent = reinterpret_cast<PFN_vkCmdSetEvent>(dlsym(libvulkan, "vkCmdSetEvent"));
vkCmdResetEvent = reinterpret_cast<PFN_vkCmdResetEvent>(dlsym(libvulkan, "vkCmdResetEvent"));
vkCmdWaitEvents = reinterpret_cast<PFN_vkCmdWaitEvents>(dlsym(libvulkan, "vkCmdWaitEvents"));
vkCmdPipelineBarrier = reinterpret_cast<PFN_vkCmdPipelineBarrier>(dlsym(libvulkan, "vkCmdPipelineBarrier"));
vkCmdBeginQuery = reinterpret_cast<PFN_vkCmdBeginQuery>(dlsym(libvulkan, "vkCmdBeginQuery"));
vkCmdEndQuery = reinterpret_cast<PFN_vkCmdEndQuery>(dlsym(libvulkan, "vkCmdEndQuery"));
vkCmdResetQueryPool = reinterpret_cast<PFN_vkCmdResetQueryPool>(dlsym(libvulkan, "vkCmdResetQueryPool"));
vkCmdWriteTimestamp = reinterpret_cast<PFN_vkCmdWriteTimestamp>(dlsym(libvulkan, "vkCmdWriteTimestamp"));
vkCmdCopyQueryPoolResults = reinterpret_cast<PFN_vkCmdCopyQueryPoolResults>(dlsym(libvulkan, "vkCmdCopyQueryPoolResults"));
vkCmdPushConstants = reinterpret_cast<PFN_vkCmdPushConstants>(dlsym(libvulkan, "vkCmdPushConstants"));
vkCmdBeginRenderPass = reinterpret_cast<PFN_vkCmdBeginRenderPass>(dlsym(libvulkan, "vkCmdBeginRenderPass"));
vkCmdNextSubpass = reinterpret_cast<PFN_vkCmdNextSubpass>(dlsym(libvulkan, "vkCmdNextSubpass"));
vkCmdEndRenderPass = reinterpret_cast<PFN_vkCmdEndRenderPass>(dlsym(libvulkan, "vkCmdEndRenderPass"));
vkCmdExecuteCommands = reinterpret_cast<PFN_vkCmdExecuteCommands>(dlsym(libvulkan, "vkCmdExecuteCommands"));
vkDestroySurfaceKHR = reinterpret_cast<PFN_vkDestroySurfaceKHR>(dlsym(libvulkan, "vkDestroySurfaceKHR"));
vkGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceSupportKHR"));
vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceFormatsKHR"));
vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfacePresentModesKHR"));
vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(dlsym(libvulkan, "vkCreateSwapchainKHR"));
vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(dlsym(libvulkan, "vkDestroySwapchainKHR"));
vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(dlsym(libvulkan, "vkGetSwapchainImagesKHR"));
vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(dlsym(libvulkan, "vkAcquireNextImageKHR"));
vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(dlsym(libvulkan, "vkQueuePresentKHR"));
vkGetPhysicalDeviceDisplayPropertiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceDisplayPropertiesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceDisplayPropertiesKHR"));
vkGetPhysicalDeviceDisplayPlanePropertiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR"));
vkGetDisplayPlaneSupportedDisplaysKHR = reinterpret_cast<PFN_vkGetDisplayPlaneSupportedDisplaysKHR>(dlsym(libvulkan, "vkGetDisplayPlaneSupportedDisplaysKHR"));
vkGetDisplayModePropertiesKHR = reinterpret_cast<PFN_vkGetDisplayModePropertiesKHR>(dlsym(libvulkan, "vkGetDisplayModePropertiesKHR"));
vkCreateDisplayModeKHR = reinterpret_cast<PFN_vkCreateDisplayModeKHR>(dlsym(libvulkan, "vkCreateDisplayModeKHR"));
vkGetDisplayPlaneCapabilitiesKHR = reinterpret_cast<PFN_vkGetDisplayPlaneCapabilitiesKHR>(dlsym(libvulkan, "vkGetDisplayPlaneCapabilitiesKHR"));
vkCreateDisplayPlaneSurfaceKHR = reinterpret_cast<PFN_vkCreateDisplayPlaneSurfaceKHR>(dlsym(libvulkan, "vkCreateDisplayPlaneSurfaceKHR"));
vkCreateSharedSwapchainsKHR = reinterpret_cast<PFN_vkCreateSharedSwapchainsKHR>(dlsym(libvulkan, "vkCreateSharedSwapchainsKHR"));
#ifdef VK_USE_PLATFORM_XLIB_KHR
vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>(dlsym(libvulkan, "vkCreateXlibSurfaceKHR"));
vkGetPhysicalDeviceXlibPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceXlibPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
vkCreateXcbSurfaceKHR = reinterpret_cast<PFN_vkCreateXcbSurfaceKHR>(dlsym(libvulkan, "vkCreateXcbSurfaceKHR"));
vkGetPhysicalDeviceXcbPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceXcbPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>(dlsym(libvulkan, "vkCreateWaylandSurfaceKHR"));
vkGetPhysicalDeviceWaylandPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceWaylandPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_MIR_KHR
vkCreateMirSurfaceKHR = reinterpret_cast<PFN_vkCreateMirSurfaceKHR>(dlsym(libvulkan, "vkCreateMirSurfaceKHR"));
vkGetPhysicalDeviceMirPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceMirPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceMirPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
vkCreateAndroidSurfaceKHR = reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(dlsym(libvulkan, "vkCreateAndroidSurfaceKHR"));
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(dlsym(libvulkan, "vkCreateWin32SurfaceKHR"));
vkGetPhysicalDeviceWin32PresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceWin32PresentationSupportKHR"));
#endif
#ifdef USE_DEBUG_EXTENTIONS
vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(dlsym(libvulkan, "vkCreateDebugReportCallbackEXT"));
vkDestroyDebugReportCallbackEXT = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(dlsym(libvulkan, "vkDestroyDebugReportCallbackEXT"));
vkDebugReportMessageEXT = reinterpret_cast<PFN_vkDebugReportMessageEXT>(dlsym(libvulkan, "vkDebugReportMessageEXT"));
#endif
return 1;
}
// No Vulkan support, do not set function addresses
PFN_vkCreateInstance vkCreateInstance;
PFN_vkDestroyInstance vkDestroyInstance;
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties;
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
PFN_vkCreateDevice vkCreateDevice;
PFN_vkDestroyDevice vkDestroyDevice;
PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
PFN_vkGetDeviceQueue vkGetDeviceQueue;
PFN_vkQueueSubmit vkQueueSubmit;
PFN_vkQueueWaitIdle vkQueueWaitIdle;
PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
PFN_vkAllocateMemory vkAllocateMemory;
PFN_vkFreeMemory vkFreeMemory;
PFN_vkMapMemory vkMapMemory;
PFN_vkUnmapMemory vkUnmapMemory;
PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
PFN_vkGetDeviceMemoryCommitment vkGetDeviceMemoryCommitment;
PFN_vkBindBufferMemory vkBindBufferMemory;
PFN_vkBindImageMemory vkBindImageMemory;
PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements;
PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties;
PFN_vkQueueBindSparse vkQueueBindSparse;
PFN_vkCreateFence vkCreateFence;
PFN_vkDestroyFence vkDestroyFence;
PFN_vkResetFences vkResetFences;
PFN_vkGetFenceStatus vkGetFenceStatus;
PFN_vkWaitForFences vkWaitForFences;
PFN_vkCreateSemaphore vkCreateSemaphore;
PFN_vkDestroySemaphore vkDestroySemaphore;
PFN_vkCreateEvent vkCreateEvent;
PFN_vkDestroyEvent vkDestroyEvent;
PFN_vkGetEventStatus vkGetEventStatus;
PFN_vkSetEvent vkSetEvent;
PFN_vkResetEvent vkResetEvent;
PFN_vkCreateQueryPool vkCreateQueryPool;
PFN_vkDestroyQueryPool vkDestroyQueryPool;
PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
PFN_vkCreateBuffer vkCreateBuffer;
PFN_vkDestroyBuffer vkDestroyBuffer;
PFN_vkCreateBufferView vkCreateBufferView;
PFN_vkDestroyBufferView vkDestroyBufferView;
PFN_vkCreateImage vkCreateImage;
PFN_vkDestroyImage vkDestroyImage;
PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
PFN_vkCreateImageView vkCreateImageView;
PFN_vkDestroyImageView vkDestroyImageView;
PFN_vkCreateShaderModule vkCreateShaderModule;
PFN_vkDestroyShaderModule vkDestroyShaderModule;
PFN_vkCreatePipelineCache vkCreatePipelineCache;
PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
PFN_vkGetPipelineCacheData vkGetPipelineCacheData;
PFN_vkMergePipelineCaches vkMergePipelineCaches;
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
PFN_vkCreateComputePipelines vkCreateComputePipelines;
PFN_vkDestroyPipeline vkDestroyPipeline;
PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
PFN_vkCreateSampler vkCreateSampler;
PFN_vkDestroySampler vkDestroySampler;
PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
PFN_vkResetDescriptorPool vkResetDescriptorPool;
PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
PFN_vkCreateFramebuffer vkCreateFramebuffer;
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
PFN_vkCreateRenderPass vkCreateRenderPass;
PFN_vkDestroyRenderPass vkDestroyRenderPass;
PFN_vkGetRenderAreaGranularity vkGetRenderAreaGranularity;
PFN_vkCreateCommandPool vkCreateCommandPool;
PFN_vkDestroyCommandPool vkDestroyCommandPool;
PFN_vkResetCommandPool vkResetCommandPool;
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
PFN_vkEndCommandBuffer vkEndCommandBuffer;
PFN_vkResetCommandBuffer vkResetCommandBuffer;
PFN_vkCmdBindPipeline vkCmdBindPipeline;
PFN_vkCmdSetViewport vkCmdSetViewport;
PFN_vkCmdSetScissor vkCmdSetScissor;
PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
PFN_vkCmdDraw vkCmdDraw;
PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
PFN_vkCmdDispatch vkCmdDispatch;
PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect;
PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
PFN_vkCmdCopyImage vkCmdCopyImage;
PFN_vkCmdBlitImage vkCmdBlitImage;
PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
PFN_vkCmdUpdateBuffer vkCmdUpdateBuffer;
PFN_vkCmdFillBuffer vkCmdFillBuffer;
PFN_vkCmdClearColorImage vkCmdClearColorImage;
PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage;
PFN_vkCmdClearAttachments vkCmdClearAttachments;
PFN_vkCmdResolveImage vkCmdResolveImage;
PFN_vkCmdSetEvent vkCmdSetEvent;
PFN_vkCmdResetEvent vkCmdResetEvent;
PFN_vkCmdWaitEvents vkCmdWaitEvents;
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
PFN_vkCmdBeginQuery vkCmdBeginQuery;
PFN_vkCmdEndQuery vkCmdEndQuery;
PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
PFN_vkCmdWriteTimestamp vkCmdWriteTimestamp;
PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
PFN_vkCmdPushConstants vkCmdPushConstants;
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
PFN_vkCmdNextSubpass vkCmdNextSubpass;
PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
PFN_vkQueuePresentKHR vkQueuePresentKHR;
PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR;
PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR;
PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR;
PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR;
PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR;
PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR;
PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR;
PFN_vkCreateSharedSwapchainsKHR vkCreateSharedSwapchainsKHR;
#ifdef VK_USE_PLATFORM_XLIB_KHR
PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR;
PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_MIR_KHR
PFN_vkCreateMirSurfaceKHR vkCreateMirSurfaceKHR;
PFN_vkGetPhysicalDeviceMirPresentationSupportKHR vkGetPhysicalDeviceMirPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR;
#endif
PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT;

View File

@ -0,0 +1,236 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is generated.
#ifndef VULKAN_WRAPPER_H
#define VULKAN_WRAPPER_H
#define VK_NO_PROTOTYPES 1
#include <vulkan/vulkan.h>
/* Initialize the Vulkan function pointer variables declared in this header.
* Returns 0 if vulkan is not available, non-zero if it is available.
*/
int InitVulkan(void);
// VK_core
extern PFN_vkCreateInstance vkCreateInstance;
extern PFN_vkDestroyInstance vkDestroyInstance;
extern PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
extern PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
extern PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
extern PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties;
extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
extern PFN_vkCreateDevice vkCreateDevice;
extern PFN_vkDestroyDevice vkDestroyDevice;
extern PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
extern PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
extern PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
extern PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
extern PFN_vkGetDeviceQueue vkGetDeviceQueue;
extern PFN_vkQueueSubmit vkQueueSubmit;
extern PFN_vkQueueWaitIdle vkQueueWaitIdle;
extern PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
extern PFN_vkAllocateMemory vkAllocateMemory;
extern PFN_vkFreeMemory vkFreeMemory;
extern PFN_vkMapMemory vkMapMemory;
extern PFN_vkUnmapMemory vkUnmapMemory;
extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
extern PFN_vkGetDeviceMemoryCommitment vkGetDeviceMemoryCommitment;
extern PFN_vkBindBufferMemory vkBindBufferMemory;
extern PFN_vkBindImageMemory vkBindImageMemory;
extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
extern PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements;
extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties;
extern PFN_vkQueueBindSparse vkQueueBindSparse;
extern PFN_vkCreateFence vkCreateFence;
extern PFN_vkDestroyFence vkDestroyFence;
extern PFN_vkResetFences vkResetFences;
extern PFN_vkGetFenceStatus vkGetFenceStatus;
extern PFN_vkWaitForFences vkWaitForFences;
extern PFN_vkCreateSemaphore vkCreateSemaphore;
extern PFN_vkDestroySemaphore vkDestroySemaphore;
extern PFN_vkCreateEvent vkCreateEvent;
extern PFN_vkDestroyEvent vkDestroyEvent;
extern PFN_vkGetEventStatus vkGetEventStatus;
extern PFN_vkSetEvent vkSetEvent;
extern PFN_vkResetEvent vkResetEvent;
extern PFN_vkCreateQueryPool vkCreateQueryPool;
extern PFN_vkDestroyQueryPool vkDestroyQueryPool;
extern PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
extern PFN_vkCreateBuffer vkCreateBuffer;
extern PFN_vkDestroyBuffer vkDestroyBuffer;
extern PFN_vkCreateBufferView vkCreateBufferView;
extern PFN_vkDestroyBufferView vkDestroyBufferView;
extern PFN_vkCreateImage vkCreateImage;
extern PFN_vkDestroyImage vkDestroyImage;
extern PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
extern PFN_vkCreateImageView vkCreateImageView;
extern PFN_vkDestroyImageView vkDestroyImageView;
extern PFN_vkCreateShaderModule vkCreateShaderModule;
extern PFN_vkDestroyShaderModule vkDestroyShaderModule;
extern PFN_vkCreatePipelineCache vkCreatePipelineCache;
extern PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
extern PFN_vkGetPipelineCacheData vkGetPipelineCacheData;
extern PFN_vkMergePipelineCaches vkMergePipelineCaches;
extern PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
extern PFN_vkCreateComputePipelines vkCreateComputePipelines;
extern PFN_vkDestroyPipeline vkDestroyPipeline;
extern PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
extern PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
extern PFN_vkCreateSampler vkCreateSampler;
extern PFN_vkDestroySampler vkDestroySampler;
extern PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
extern PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
extern PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
extern PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
extern PFN_vkResetDescriptorPool vkResetDescriptorPool;
extern PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
extern PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
extern PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
extern PFN_vkCreateFramebuffer vkCreateFramebuffer;
extern PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
extern PFN_vkCreateRenderPass vkCreateRenderPass;
extern PFN_vkDestroyRenderPass vkDestroyRenderPass;
extern PFN_vkGetRenderAreaGranularity vkGetRenderAreaGranularity;
extern PFN_vkCreateCommandPool vkCreateCommandPool;
extern PFN_vkDestroyCommandPool vkDestroyCommandPool;
extern PFN_vkResetCommandPool vkResetCommandPool;
extern PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
extern PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
extern PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
extern PFN_vkEndCommandBuffer vkEndCommandBuffer;
extern PFN_vkResetCommandBuffer vkResetCommandBuffer;
extern PFN_vkCmdBindPipeline vkCmdBindPipeline;
extern PFN_vkCmdSetViewport vkCmdSetViewport;
extern PFN_vkCmdSetScissor vkCmdSetScissor;
extern PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
extern PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
extern PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
extern PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
extern PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
extern PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
extern PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
extern PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
extern PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
extern PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
extern PFN_vkCmdDraw vkCmdDraw;
extern PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
extern PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
extern PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
extern PFN_vkCmdDispatch vkCmdDispatch;
extern PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect;
extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
extern PFN_vkCmdCopyImage vkCmdCopyImage;
extern PFN_vkCmdBlitImage vkCmdBlitImage;
extern PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
extern PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
extern PFN_vkCmdUpdateBuffer vkCmdUpdateBuffer;
extern PFN_vkCmdFillBuffer vkCmdFillBuffer;
extern PFN_vkCmdClearColorImage vkCmdClearColorImage;
extern PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage;
extern PFN_vkCmdClearAttachments vkCmdClearAttachments;
extern PFN_vkCmdResolveImage vkCmdResolveImage;
extern PFN_vkCmdSetEvent vkCmdSetEvent;
extern PFN_vkCmdResetEvent vkCmdResetEvent;
extern PFN_vkCmdWaitEvents vkCmdWaitEvents;
extern PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
extern PFN_vkCmdBeginQuery vkCmdBeginQuery;
extern PFN_vkCmdEndQuery vkCmdEndQuery;
extern PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
extern PFN_vkCmdWriteTimestamp vkCmdWriteTimestamp;
extern PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
extern PFN_vkCmdPushConstants vkCmdPushConstants;
extern PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
extern PFN_vkCmdNextSubpass vkCmdNextSubpass;
extern PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
extern PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
// VK_KHR_surface
extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
// VK_KHR_swapchain
extern PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
extern PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
extern PFN_vkQueuePresentKHR vkQueuePresentKHR;
// VK_KHR_display
extern PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR;
extern PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR;
extern PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR;
extern PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR;
extern PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR;
extern PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR;
extern PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR;
// VK_KHR_display_swapchain
extern PFN_vkCreateSharedSwapchainsKHR vkCreateSharedSwapchainsKHR;
#ifdef VK_USE_PLATFORM_XLIB_KHR
// VK_KHR_xlib_surface
extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
// VK_KHR_xcb_surface
extern PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
// VK_KHR_wayland_surface
extern PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
extern PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_MIR_KHR
// VK_KHR_mir_surface
extern PFN_vkCreateMirSurfaceKHR vkCreateMirSurfaceKHR;
extern PFN_vkGetPhysicalDeviceMirPresentationSupportKHR vkGetPhysicalDeviceMirPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
// VK_KHR_android_surface
extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
// VK_KHR_win32_surface
extern PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
extern PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR;
#endif
#ifdef USE_DEBUG_EXTENTIONS
#include <vulkan/vk_sdk_platform.h>
// VK_EXT_debug_report
extern PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
extern PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
extern PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT;
#endif
#endif // VULKAN_WRAPPER_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,7 @@
package org.ryujinx.android
enum class BackendThreading {
Auto,
Off,
On
}

View File

@ -0,0 +1,9 @@
package org.ryujinx.android
import androidx.activity.ComponentActivity
abstract class BaseActivity : ComponentActivity() {
companion object {
val crashHandler = CrashHandler()
}
}

View File

@ -0,0 +1,15 @@
package org.ryujinx.android
import java.io.File
import java.lang.Thread.UncaughtExceptionHandler
class CrashHandler : UncaughtExceptionHandler {
var crashLog: String = ""
override fun uncaughtException(t: Thread, e: Throwable) {
crashLog += e.toString() + "\n"
File(MainActivity.AppPath + "${File.separator}Logs${File.separator}crash.log").writeText(
crashLog
)
}
}

View File

@ -0,0 +1,435 @@
package org.ryujinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings
typealias GamePad = RadialGamePad
typealias GamePadConfig = RadialGamePadConfig
class GameController(var activity: Activity) {
companion object {
private fun Create(context: Context, controller: GameController): View {
val inflator = LayoutInflater.from(context)
val view = inflator.inflate(R.layout.game_layout, null)
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
return view
}
@Composable
fun Compose(viewModel: MainViewModel): Unit {
AndroidView(
modifier = Modifier.fillMaxSize(), factory = { context ->
val controller = GameController(viewModel.activity)
val c = Create(context, controller)
viewModel.activity.lifecycleScope.apply {
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
)
.shareIn(viewModel.activity.lifecycleScope, SharingStarted.Lazily)
events.safeCollect {
controller.handleEvent(it)
}
}
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
})
}
}
private var controllerView: View? = null
var leftGamePad: GamePad
var rightGamePad: GamePad
var controllerId: Int = -1
val isVisible: Boolean
get() {
controllerView?.apply {
return this.isVisible
}
return false
}
init {
leftGamePad = GamePad(generateConfig(true), 16f, activity)
rightGamePad = GamePad(generateConfig(false), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible)
connect()
}
}
fun connect() {
if (controllerId == -1)
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
val action = ev.action
when (action) {
KeyEvent.ACTION_UP -> {
RyujinxNative.jnaInstance.inputSetButtonReleased(ev.id, this)
}
KeyEvent.ACTION_DOWN -> {
RyujinxNative.jnaInstance.inputSetButtonPressed(ev.id, this)
}
}
}
is Event.Direction -> {
val direction = ev.id
when (direction) {
GamePadButtonInputId.DpadUp.ordinal -> {
if (ev.xAxis > 0) {
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadRight.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
} else if (ev.xAxis < 0) {
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
this
)
} else {
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
this
)
}
if (ev.yAxis < 0) {
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadUp.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
this
)
} else if (ev.yAxis > 0) {
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadDown.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
this
)
} else {
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
this
)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
RyujinxNative.jnaInstance.inputSetStickAxis(
1,
x,
-y,
this
)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
RyujinxNative.jnaInstance.inputSetStickAxis(
2,
x,
-y,
this
)
}
}
}
}
}
}
}
suspend fun <T> Flow<T>.safeCollect(
block: suspend (T) -> Unit
) {
this.catch {}
.collect {
block(it)
}
}
private fun generateConfig(isLeft: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
12,
PrimaryDialConfig.Stick(
GamePadButtonInputId.LeftStick.ordinal,
GamePadButtonInputId.LeftStickButton.ordinal,
setOf(),
"LeftStick",
null
),
listOf(
SecondaryDialConfig.Cross(
10,
3,
2.5f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.SingleButton(
1,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.DoubleButton(
2,
distance,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.SingleButton(
9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
12,
PrimaryDialConfig.PrimaryButtons(
listOf(
ButtonConfig(
GamePadButtonInputId.A.ordinal,
"A",
true,
null,
"A",
setOf(),
true,
null
),
ButtonConfig(
GamePadButtonInputId.X.ordinal,
"X",
true,
null,
"X",
setOf(),
true,
null
),
ButtonConfig(
GamePadButtonInputId.Y.ordinal,
"Y",
true,
null,
"Y",
setOf(),
true,
null
),
ButtonConfig(
GamePadButtonInputId.B.ordinal,
"B",
true,
null,
"B",
setOf(),
true,
null
)
),
null,
0f,
true,
null
),
listOf(
SecondaryDialConfig.Stick(
7,
2,
2f,
distance,
GamePadButtonInputId.RightStick.ordinal,
GamePadButtonInputId.RightStickButton.ordinal,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.SingleButton(
6,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.DoubleButton(
3,
distance,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.SingleButton(
9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
)
)
)
}
}

View File

@ -0,0 +1,179 @@
package org.ryujinx.android
import android.annotation.SuppressLint
import android.content.Context
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.compose.runtime.MutableState
import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.MainViewModel
import kotlin.concurrent.thread
@SuppressLint("ViewConstructor")
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context),
SurfaceHolder.Callback {
private var _currentWindow: Long = -1
private var isProgressHidden: Boolean = false
private var progress: MutableState<String>? = null
private var progressValue: MutableState<Float>? = null
private var showLoading: MutableState<Boolean>? = null
private var game: GameModel? = null
private var _isClosed: Boolean = false
private var _renderingThreadWatcher: Thread? = null
private var _height: Int = 0
private var _width: Int = 0
private var _updateThread: Thread? = null
private var _guestThread: Thread? = null
private var _isInit: Boolean = false
private var _isStarted: Boolean = false
private val _nativeWindow: NativeWindow
val currentSurface:Long
get() {
return _currentWindow
}
val currentWindowhandle: Long
get() {
return _nativeWindow.nativePointer
}
init {
holder.addCallback(this)
_nativeWindow = NativeWindow(this)
mainViewModel.gameHost = this
}
override fun surfaceCreated(holder: SurfaceHolder) {
}
fun setProgress(info : String, progressVal: Float) {
showLoading?.apply {
progressValue?.apply {
this.value = progressVal
}
progress?.apply {
this.value = info
}
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
if (_isClosed)
return
if (_width != width || _height != height) {
_currentWindow = _nativeWindow.requeryWindowHandle()
_nativeWindow.swapInterval = 0
}
_width = width
_height = height
start(holder)
RyujinxNative.jnaInstance.graphicsRendererSetSize(
width,
height
)
if (_isStarted) {
RyujinxNative.jnaInstance.inputSetClientSize(width, height)
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
fun close() {
_isClosed = true
_isInit = false
_isStarted = false
RyujinxNative.jnaInstance.uiHandlerSetResponse(false, "")
_updateThread?.join()
_renderingThreadWatcher?.join()
}
private fun start(surfaceHolder: SurfaceHolder) {
if (_isStarted)
return
_isStarted = true
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
RyujinxNative.jnaInstance.inputInitialize(width, height)
val id = mainViewModel.physicalControllerManager?.connect()
mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
RyujinxNative.jnaInstance.graphicsRendererSetSize(
surfaceHolder.surfaceFrame.width(),
surfaceHolder.surfaceFrame.height()
)
NativeHelpers.instance.setIsInitialOrientationFlipped(mainViewModel.activity.display?.rotation == 3)
_guestThread = thread(start = true) {
runGame()
}
_updateThread = thread(start = true) {
var c = 0
val helper = NativeHelpers.instance
while (_isStarted) {
RyujinxNative.jnaInstance.inputUpdate()
Thread.sleep(1)
c++
if (c >= 1000) {
if (progressValue?.value == -1f)
progress?.apply {
this.value =
"Loading ${if (mainViewModel.isMiiEditorLaunched) "Mii Editor" else game!!.titleName}"
}
c = 0
mainViewModel.updateStats(
RyujinxNative.jnaInstance.deviceGetGameFifo(),
RyujinxNative.jnaInstance.deviceGetGameFrameRate(),
RyujinxNative.jnaInstance.deviceGetGameFrameTime()
)
}
}
}
}
private fun runGame() {
RyujinxNative.jnaInstance.graphicsRendererRunLoop()
game?.close()
}
fun setProgressStates(
showLoading: MutableState<Boolean>?,
progressValue: MutableState<Float>?,
progress: MutableState<String>?
) {
this.showLoading = showLoading
this.progressValue = progressValue
this.progress = progress
showLoading?.apply {
showLoading.value = !isProgressHidden
}
}
fun hideProgressIndicator() {
isProgressHidden = true
showLoading?.apply {
if (value == isProgressHidden)
value = !isProgressHidden
}
}
}

View File

@ -0,0 +1,27 @@
package org.ryujinx.android
enum class GamePadButtonInputId {
None,
// Buttons
A,
B,
X,
Y,
LeftStickButton,
RightStickButton,
LeftShoulder,
RightShoulder,
LeftTrigger,
RightTrigger,
DpadUp,
DpadDown,
DpadLeft,
DpadRight,
Minus,
Plus,
// Stick
LeftStick,
RightStick
}

View File

@ -0,0 +1,223 @@
package org.ryujinx.android
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import androidx.compose.runtime.MutableState
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.SimpleStorageHelper
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.file.copyFileTo
import com.anggrayudi.storage.file.openInputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.lingala.zip4j.io.inputstream.ZipInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
class Helpers {
companion object {
fun getPath(context: Context, uri: Uri): String? {
// DocumentProvider
if (DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
java.lang.Long.valueOf(id)
)
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
val type = split[0]
var contentUri: Uri? = null
when (type) {
"image" -> {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
"video" -> {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
"audio" -> {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
return getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
fun copyToData(
file: DocumentFile, path: String, storageHelper: SimpleStorageHelper,
isCopying: MutableState<Boolean>,
copyProgress: MutableState<Float>,
currentProgressName: MutableState<String>,
finish: () -> Unit
) {
var fPath = path + "/${file.name}"
var callback: FileCallback? = object : FileCallback() {
override fun onFailed(errorCode: ErrorCode) {
super.onFailed(errorCode)
File(fPath).delete()
finish()
}
override fun onStart(file: Any, workerThread: Thread): Long {
copyProgress.value = 0f
(file as DocumentFile).apply {
currentProgressName.value = "Copying ${file.name}"
}
return super.onStart(file, workerThread)
}
override fun onReport(report: Report) {
super.onReport(report)
if (!isCopying.value) {
Thread.currentThread().interrupt()
}
copyProgress.value = report.progress / 100f
}
override fun onCompleted(result: Any) {
super.onCompleted(result)
isCopying.value = false
finish()
}
}
val ioScope = CoroutineScope(Dispatchers.IO)
isCopying.value = true
File(fPath).delete()
file.apply {
val f = this
ioScope.launch {
f.copyFileTo(
storageHelper.storage.context,
File(path),
callback = callback!!
)
}
}
}
private fun getDataColumn(
context: Context,
uri: Uri?,
selection: String?,
selectionArgs: Array<String>?
): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = uri?.let {
context.contentResolver.query(
it,
projection,
selection,
selectionArgs,
null
)
}
if (cursor != null && cursor.moveToFirst()) {
val column_index: Int = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
} finally {
cursor?.close()
}
return null
}
private fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
fun importAppData(
file: DocumentFile,
isImporting: MutableState<Boolean>
) {
isImporting.value = true
try {
MainActivity.StorageHelper?.apply {
val stream = file.openInputStream(storage.context)
stream?.apply {
val folders = listOf("bis", "games", "profiles", "system")
for (f in folders) {
val dir = File(MainActivity.AppPath + "${File.separator}${f}")
if (dir.exists()) {
dir.deleteRecursively()
}
dir.mkdirs()
}
ZipInputStream(stream).use { zip ->
while (true) {
val header = zip.nextEntry ?: break
if (!folders.any { header.fileName.startsWith(it) }) {
continue
}
val filePath =
MainActivity.AppPath + File.separator + header.fileName
if (!header.isDirectory) {
val bos = BufferedOutputStream(FileOutputStream(filePath))
val bytesIn = ByteArray(4096)
var read: Int = 0
while (zip.read(bytesIn).also { read = it } > 0) {
bos.write(bytesIn, 0, read)
}
bos.close()
} else {
val dir = File(filePath)
dir.mkdir()
}
}
}
stream.close()
}
}
} finally {
isImporting.value = false
RyujinxNative.jnaInstance.deviceReloadFilesystem()
}
}
}
}

View File

@ -0,0 +1,826 @@
package org.ryujinx.android
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import compose.icons.CssGgIcons
import compose.icons.cssggicons.Games
class Icons {
companion object {
/// Icons exported from https://www.composables.com/icons
@Composable
fun circle(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "circle",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(20f, 36.375f)
quadToRelative(-3.375f, 0f, -6.375f, -1.292f)
quadToRelative(-3f, -1.291f, -5.208f, -3.521f)
quadToRelative(-2.209f, -2.229f, -3.5f, -5.208f)
quadTo(3.625f, 23.375f, 3.625f, 20f)
quadToRelative(0f, -3.417f, 1.292f, -6.396f)
quadToRelative(1.291f, -2.979f, 3.521f, -5.208f)
quadToRelative(2.229f, -2.229f, 5.208f, -3.5f)
reflectiveQuadTo(20f, 3.625f)
quadToRelative(3.417f, 0f, 6.396f, 1.292f)
quadToRelative(2.979f, 1.291f, 5.208f, 3.5f)
quadToRelative(2.229f, 2.208f, 3.5f, 5.187f)
reflectiveQuadTo(36.375f, 20f)
quadToRelative(0f, 3.375f, -1.292f, 6.375f)
quadToRelative(-1.291f, 3f, -3.5f, 5.208f)
quadToRelative(-2.208f, 2.209f, -5.187f, 3.5f)
quadToRelative(-2.979f, 1.292f, -6.396f, 1.292f)
close()
moveToRelative(0f, -2.625f)
quadToRelative(5.75f, 0f, 9.75f, -4.021f)
reflectiveQuadToRelative(4f, -9.729f)
quadToRelative(0f, -5.75f, -4f, -9.75f)
reflectiveQuadToRelative(-9.75f, -4f)
quadToRelative(-5.708f, 0f, -9.729f, 4f)
quadToRelative(-4.021f, 4f, -4.021f, 9.75f)
quadToRelative(0f, 5.708f, 4.021f, 9.729f)
quadTo(14.292f, 33.75f, 20f, 33.75f)
close()
moveTo(20f, 20f)
close()
}
}.build()
}
}
@Composable
fun listView(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "list",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(13.375f, 14.458f)
quadToRelative(-0.583f, 0f, -0.958f, -0.395f)
quadToRelative(-0.375f, -0.396f, -0.375f, -0.938f)
quadToRelative(0f, -0.542f, 0.375f, -0.937f)
quadToRelative(0.375f, -0.396f, 0.958f, -0.396f)
horizontalLineToRelative(20.083f)
quadToRelative(0.584f, 0f, 0.959f, 0.396f)
quadToRelative(0.375f, 0.395f, 0.375f, 0.937f)
reflectiveQuadToRelative(-0.375f, 0.938f)
quadToRelative(-0.375f, 0.395f, -0.959f, 0.395f)
close()
moveToRelative(0f, 6.834f)
quadToRelative(-0.583f, 0f, -0.958f, -0.375f)
reflectiveQuadTo(12.042f, 20f)
quadToRelative(0f, -0.583f, 0.375f, -0.958f)
reflectiveQuadToRelative(0.958f, -0.375f)
horizontalLineToRelative(20.083f)
quadToRelative(0.584f, 0f, 0.959f, 0.395f)
quadToRelative(0.375f, 0.396f, 0.375f, 0.938f)
quadToRelative(0f, 0.542f, -0.375f, 0.917f)
reflectiveQuadToRelative(-0.959f, 0.375f)
close()
moveToRelative(0f, 6.916f)
quadToRelative(-0.583f, 0f, -0.958f, -0.396f)
quadToRelative(-0.375f, -0.395f, -0.375f, -0.937f)
reflectiveQuadToRelative(0.375f, -0.937f)
quadToRelative(0.375f, -0.396f, 0.958f, -0.396f)
horizontalLineToRelative(20.083f)
quadToRelative(0.584f, 0f, 0.959f, 0.396f)
quadToRelative(0.375f, 0.395f, 0.375f, 0.937f)
reflectiveQuadToRelative(-0.375f, 0.937f)
quadToRelative(-0.375f, 0.396f, -0.959f, 0.396f)
close()
moveToRelative(-6.833f, -13.75f)
quadToRelative(-0.584f, 0f, -0.959f, -0.395f)
quadToRelative(-0.375f, -0.396f, -0.375f, -0.938f)
quadToRelative(0f, -0.583f, 0.375f, -0.958f)
reflectiveQuadToRelative(0.959f, -0.375f)
quadToRelative(0.583f, 0f, 0.958f, 0.375f)
reflectiveQuadToRelative(0.375f, 0.958f)
quadToRelative(0f, 0.542f, -0.375f, 0.938f)
quadToRelative(-0.375f, 0.395f, -0.958f, 0.395f)
close()
moveToRelative(0f, 6.875f)
quadToRelative(-0.584f, 0f, -0.959f, -0.375f)
reflectiveQuadTo(5.208f, 20f)
quadToRelative(0f, -0.583f, 0.375f, -0.958f)
reflectiveQuadToRelative(0.959f, -0.375f)
quadToRelative(0.583f, 0f, 0.958f, 0.375f)
reflectiveQuadToRelative(0.375f, 0.958f)
quadToRelative(0f, 0.583f, -0.375f, 0.958f)
reflectiveQuadToRelative(-0.958f, 0.375f)
close()
moveToRelative(0f, 6.875f)
quadToRelative(-0.584f, 0f, -0.959f, -0.375f)
reflectiveQuadToRelative(-0.375f, -0.958f)
quadToRelative(0f, -0.542f, 0.375f, -0.937f)
quadToRelative(0.375f, -0.396f, 0.959f, -0.396f)
quadToRelative(0.583f, 0f, 0.958f, 0.396f)
quadToRelative(0.375f, 0.395f, 0.375f, 0.937f)
quadToRelative(0f, 0.583f, -0.375f, 0.958f)
reflectiveQuadToRelative(-0.958f, 0.375f)
close()
}
}.build()
}
}
@Composable
fun gridView(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "grid_view",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(7.875f, 18.667f)
quadToRelative(-1.083f, 0f, -1.854f, -0.771f)
quadToRelative(-0.771f, -0.771f, -0.771f, -1.854f)
verticalLineTo(7.875f)
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
horizontalLineToRelative(8.167f)
quadToRelative(1.083f, 0f, 1.875f, 0.771f)
quadToRelative(0.791f, 0.771f, 0.791f, 1.854f)
verticalLineToRelative(8.167f)
quadToRelative(0f, 1.083f, -0.791f, 1.854f)
quadToRelative(-0.792f, 0.771f, -1.875f, 0.771f)
close()
moveToRelative(0f, 16.083f)
quadToRelative(-1.083f, 0f, -1.854f, -0.771f)
quadToRelative(-0.771f, -0.771f, -0.771f, -1.854f)
verticalLineToRelative(-8.167f)
quadToRelative(0f, -1.083f, 0.771f, -1.875f)
quadToRelative(0.771f, -0.791f, 1.854f, -0.791f)
horizontalLineToRelative(8.167f)
quadToRelative(1.083f, 0f, 1.875f, 0.791f)
quadToRelative(0.791f, 0.792f, 0.791f, 1.875f)
verticalLineToRelative(8.167f)
quadToRelative(0f, 1.083f, -0.791f, 1.854f)
quadToRelative(-0.792f, 0.771f, -1.875f, 0.771f)
close()
moveToRelative(16.083f, -16.083f)
quadToRelative(-1.083f, 0f, -1.854f, -0.771f)
quadToRelative(-0.771f, -0.771f, -0.771f, -1.854f)
verticalLineTo(7.875f)
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
horizontalLineToRelative(8.167f)
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
verticalLineToRelative(8.167f)
quadToRelative(0f, 1.083f, -0.771f, 1.854f)
quadToRelative(-0.771f, 0.771f, -1.854f, 0.771f)
close()
moveToRelative(0f, 16.083f)
quadToRelative(-1.083f, 0f, -1.854f, -0.771f)
quadToRelative(-0.771f, -0.771f, -0.771f, -1.854f)
verticalLineToRelative(-8.167f)
quadToRelative(0f, -1.083f, 0.771f, -1.875f)
quadToRelative(0.771f, -0.791f, 1.854f, -0.791f)
horizontalLineToRelative(8.167f)
quadToRelative(1.083f, 0f, 1.854f, 0.791f)
quadToRelative(0.771f, 0.792f, 0.771f, 1.875f)
verticalLineToRelative(8.167f)
quadToRelative(0f, 1.083f, -0.771f, 1.854f)
quadToRelative(-0.771f, 0.771f, -1.854f, 0.771f)
close()
moveTo(7.875f, 16.042f)
horizontalLineToRelative(8.167f)
verticalLineTo(7.875f)
horizontalLineTo(7.875f)
close()
moveToRelative(16.083f, 0f)
horizontalLineToRelative(8.167f)
verticalLineTo(7.875f)
horizontalLineToRelative(-8.167f)
close()
moveToRelative(0f, 16.083f)
horizontalLineToRelative(8.167f)
verticalLineToRelative(-8.167f)
horizontalLineToRelative(-8.167f)
close()
moveToRelative(-16.083f, 0f)
horizontalLineToRelative(8.167f)
verticalLineToRelative(-8.167f)
horizontalLineTo(7.875f)
close()
moveToRelative(16.083f, -16.083f)
close()
moveToRelative(0f, 7.916f)
close()
moveToRelative(-7.916f, 0f)
close()
moveToRelative(0f, -7.916f)
close()
}
}.build()
}
}
@Composable
fun applets(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "apps",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(9.708f, 33.125f)
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.02f)
quadToRelative(0f, -1.167f, 0.813f, -2f)
quadToRelative(0.812f, -0.834f, 2.02f, -0.834f)
quadToRelative(1.167f, 0f, 2f, 0.813f)
quadToRelative(0.834f, 0.812f, 0.834f, 2.021f)
quadToRelative(0f, 1.208f, -0.813f, 2.02f)
quadToRelative(-0.812f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.02f)
quadToRelative(0f, -1.167f, 0.813f, -2f)
quadToRelative(0.812f, -0.834f, 1.979f, -0.834f)
reflectiveQuadToRelative(2f, 0.813f)
quadToRelative(0.833f, 0.812f, 0.833f, 2.021f)
quadToRelative(0f, 1.208f, -0.812f, 2.02f)
quadToRelative(-0.813f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -2f, -0.813f)
quadToRelative(-0.834f, -0.812f, -0.834f, -2.02f)
quadToRelative(0f, -1.167f, 0.813f, -2f)
quadToRelative(0.812f, -0.834f, 2.021f, -0.834f)
quadToRelative(1.208f, 0f, 2.02f, 0.813f)
quadToRelative(0.813f, 0.812f, 0.813f, 2.021f)
quadToRelative(0f, 1.208f, -0.813f, 2.02f)
quadToRelative(-0.812f, 0.813f, -2.02f, 0.813f)
close()
moveTo(9.708f, 22.792f)
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -1.979f)
reflectiveQuadToRelative(0.813f, -2f)
quadToRelative(0.812f, -0.833f, 2.02f, -0.833f)
quadToRelative(1.167f, 0f, 2f, 0.812f)
quadToRelative(0.834f, 0.813f, 0.834f, 2.021f)
quadToRelative(0f, 1.167f, -0.813f, 1.979f)
quadToRelative(-0.812f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -1.979f)
reflectiveQuadToRelative(0.813f, -2f)
quadToRelative(0.812f, -0.833f, 1.979f, -0.833f)
reflectiveQuadToRelative(2f, 0.812f)
quadToRelative(0.833f, 0.813f, 0.833f, 2.021f)
quadToRelative(0f, 1.167f, -0.812f, 1.979f)
quadToRelative(-0.813f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -2f, -0.813f)
quadToRelative(-0.834f, -0.812f, -0.834f, -1.979f)
reflectiveQuadToRelative(0.813f, -2f)
quadToRelative(0.812f, -0.833f, 2.021f, -0.833f)
quadToRelative(1.208f, 0f, 2.02f, 0.812f)
quadToRelative(0.813f, 0.813f, 0.813f, 2.021f)
quadToRelative(0f, 1.167f, -0.813f, 1.979f)
quadToRelative(-0.812f, 0.813f, -2.02f, 0.813f)
close()
moveTo(9.708f, 12.542f)
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.021f)
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
quadToRelative(0.812f, -0.813f, 2.02f, -0.813f)
quadToRelative(1.167f, 0f, 2f, 0.813f)
quadToRelative(0.834f, 0.812f, 0.834f, 2.02f)
quadToRelative(0f, 1.167f, -0.813f, 2f)
quadToRelative(-0.812f, 0.834f, -2.021f, 0.834f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.021f)
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
quadToRelative(0.812f, -0.813f, 1.979f, -0.813f)
reflectiveQuadToRelative(2f, 0.813f)
quadToRelative(0.833f, 0.812f, 0.833f, 2.02f)
quadToRelative(0f, 1.167f, -0.812f, 2f)
quadToRelative(-0.813f, 0.834f, -2.021f, 0.834f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -2f, -0.813f)
quadToRelative(-0.834f, -0.812f, -0.834f, -2.021f)
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
quadToRelative(0.812f, -0.813f, 2.021f, -0.813f)
quadToRelative(1.208f, 0f, 2.02f, 0.813f)
quadToRelative(0.813f, 0.812f, 0.813f, 2.02f)
quadToRelative(0f, 1.167f, -0.813f, 2f)
quadToRelative(-0.812f, 0.834f, -2.02f, 0.834f)
close()
}
}.build()
}
}
@Composable
fun playArrow(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "play_arrow",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(15.542f, 30f)
quadToRelative(-0.667f, 0.458f, -1.334f, 0.062f)
quadToRelative(-0.666f, -0.395f, -0.666f, -1.187f)
verticalLineTo(10.917f)
quadToRelative(0f, -0.75f, 0.666f, -1.146f)
quadToRelative(0.667f, -0.396f, 1.334f, 0.062f)
lineToRelative(14.083f, 9f)
quadToRelative(0.583f, 0.375f, 0.583f, 1.084f)
quadToRelative(0f, 0.708f, -0.583f, 1.083f)
close()
moveToRelative(0.625f, -10.083f)
close()
moveToRelative(0f, 6.541f)
lineToRelative(10.291f, -6.541f)
lineToRelative(-10.291f, -6.542f)
close()
}
}.build()
}
}
@Composable
fun folderOpen(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "folder_open",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(6.25f, 33.125f)
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
quadToRelative(-0.771f, -0.791f, -0.771f, -1.875f)
verticalLineTo(9.667f)
quadToRelative(0f, -1.084f, 0.771f, -1.854f)
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
horizontalLineToRelative(10.042f)
quadToRelative(0.541f, 0f, 1.041f, 0.208f)
quadToRelative(0.5f, 0.208f, 0.834f, 0.583f)
lineToRelative(1.875f, 1.834f)
horizontalLineTo(33.75f)
quadToRelative(1.083f, 0f, 1.854f, 0.791f)
quadToRelative(0.771f, 0.792f, 0.771f, 1.834f)
horizontalLineTo(18.917f)
lineTo(16.25f, 9.667f)
horizontalLineToRelative(-10f)
verticalLineTo(30.25f)
lineToRelative(3.542f, -13.375f)
quadToRelative(0.25f, -0.875f, 0.979f, -1.396f)
quadToRelative(0.729f, -0.521f, 1.604f, -0.521f)
horizontalLineToRelative(23.25f)
quadToRelative(1.292f, 0f, 2.104f, 1.021f)
quadToRelative(0.813f, 1.021f, 0.438f, 2.271f)
lineToRelative(-3.459f, 12.833f)
quadToRelative(-0.291f, 1f, -1f, 1.521f)
quadToRelative(-0.708f, 0.521f, -1.75f, 0.521f)
close()
moveToRelative(2.708f, -2.667f)
horizontalLineToRelative(23.167f)
lineToRelative(3.417f, -12.875f)
horizontalLineTo(12.333f)
close()
moveToRelative(0f, 0f)
lineToRelative(3.375f, -12.875f)
lineToRelative(-3.375f, 12.875f)
close()
moveToRelative(-2.708f, -15.5f)
verticalLineTo(9.667f)
verticalLineToRelative(5.291f)
close()
}
}.build()
}
}
@Composable
fun gameUpdate(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary
return remember {
ImageVector.Builder(
name = "game_update_alt",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
stroke = SolidColor(primaryColor),
fillAlpha = 1f,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(6.25f, 33.083f)
quadToRelative(-1.083f, 0f, -1.854f, -0.791f)
quadToRelative(-0.771f, -0.792f, -0.771f, -1.834f)
verticalLineTo(9.542f)
quadToRelative(0f, -1.042f, 0.771f, -1.854f)
quadToRelative(0.771f, -0.813f, 1.854f, -0.813f)
horizontalLineToRelative(8.458f)
quadToRelative(0.584f, 0f, 0.959f, 0.396f)
reflectiveQuadToRelative(0.375f, 0.937f)
quadToRelative(0f, 0.584f, -0.375f, 0.959f)
reflectiveQuadToRelative(-0.959f, 0.375f)
horizontalLineTo(6.25f)
verticalLineToRelative(20.916f)
horizontalLineToRelative(27.542f)
verticalLineTo(9.542f)
horizontalLineToRelative(-8.5f)
quadToRelative(-0.584f, 0f, -0.959f, -0.375f)
reflectiveQuadToRelative(-0.375f, -0.959f)
quadToRelative(0f, -0.541f, 0.375f, -0.937f)
reflectiveQuadToRelative(0.959f, -0.396f)
horizontalLineToRelative(8.5f)
quadToRelative(1.041f, 0f, 1.833f, 0.813f)
quadToRelative(0.792f, 0.812f, 0.792f, 1.854f)
verticalLineToRelative(20.916f)
quadToRelative(0f, 1.042f, -0.792f, 1.834f)
quadToRelative(-0.792f, 0.791f, -1.833f, 0.791f)
close()
moveTo(20f, 25f)
quadToRelative(-0.25f, 0f, -0.479f, -0.083f)
quadToRelative(-0.229f, -0.084f, -0.396f, -0.292f)
lineTo(12.75f, 18.25f)
quadToRelative(-0.375f, -0.333f, -0.375f, -0.896f)
quadToRelative(0f, -0.562f, 0.417f, -0.979f)
quadToRelative(0.375f, -0.375f, 0.916f, -0.375f)
quadToRelative(0.542f, 0f, 0.959f, 0.375f)
lineToRelative(4.041f, 4.083f)
verticalLineTo(8.208f)
quadToRelative(0f, -0.541f, 0.375f, -0.937f)
reflectiveQuadTo(20f, 6.875f)
quadToRelative(0.542f, 0f, 0.938f, 0.396f)
quadToRelative(0.395f, 0.396f, 0.395f, 0.937f)
verticalLineToRelative(12.25f)
lineToRelative(4.084f, -4.083f)
quadToRelative(0.333f, -0.333f, 0.875f, -0.333f)
quadToRelative(0.541f, 0f, 0.916f, 0.375f)
quadToRelative(0.417f, 0.416f, 0.417f, 0.958f)
reflectiveQuadToRelative(-0.375f, 0.917f)
lineToRelative(-6.333f, 6.333f)
quadToRelative(-0.209f, 0.208f, -0.438f, 0.292f)
quadTo(20.25f, 25f, 20f, 25f)
close()
}
}.build()
}
}
@Composable
fun download(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary
return remember {
ImageVector.Builder(
name = "download",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
stroke = SolidColor(primaryColor),
fillAlpha = 1f,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(20f, 26.25f)
quadToRelative(-0.25f, 0f, -0.479f, -0.083f)
quadToRelative(-0.229f, -0.084f, -0.438f, -0.292f)
lineToRelative(-6.041f, -6.083f)
quadToRelative(-0.417f, -0.375f, -0.396f, -0.917f)
quadToRelative(0.021f, -0.542f, 0.396f, -0.917f)
reflectiveQuadToRelative(0.916f, -0.396f)
quadToRelative(0.542f, -0.02f, 0.959f, 0.396f)
lineToRelative(3.791f, 3.792f)
verticalLineTo(8.292f)
quadToRelative(0f, -0.584f, 0.375f, -0.959f)
reflectiveQuadTo(20f, 6.958f)
quadToRelative(0.542f, 0f, 0.938f, 0.375f)
quadToRelative(0.395f, 0.375f, 0.395f, 0.959f)
verticalLineTo(21.75f)
lineToRelative(3.792f, -3.792f)
quadToRelative(0.375f, -0.416f, 0.917f, -0.396f)
quadToRelative(0.541f, 0.021f, 0.958f, 0.396f)
quadToRelative(0.375f, 0.375f, 0.375f, 0.917f)
reflectiveQuadToRelative(-0.375f, 0.958f)
lineToRelative(-6.083f, 6.042f)
quadToRelative(-0.209f, 0.208f, -0.438f, 0.292f)
quadToRelative(-0.229f, 0.083f, -0.479f, 0.083f)
close()
moveTo(9.542f, 32.958f)
quadToRelative(-1.042f, 0f, -1.834f, -0.791f)
quadToRelative(-0.791f, -0.792f, -0.791f, -1.834f)
verticalLineToRelative(-4.291f)
quadToRelative(0f, -0.542f, 0.395f, -0.938f)
quadToRelative(0.396f, -0.396f, 0.938f, -0.396f)
quadToRelative(0.542f, 0f, 0.917f, 0.396f)
reflectiveQuadToRelative(0.375f, 0.938f)
verticalLineToRelative(4.291f)
horizontalLineToRelative(20.916f)
verticalLineToRelative(-4.291f)
quadToRelative(0f, -0.542f, 0.375f, -0.938f)
quadToRelative(0.375f, -0.396f, 0.917f, -0.396f)
quadToRelative(0.583f, 0f, 0.958f, 0.396f)
reflectiveQuadToRelative(0.375f, 0.938f)
verticalLineToRelative(4.291f)
quadToRelative(0f, 1.042f, -0.791f, 1.834f)
quadToRelative(-0.792f, 0.791f, -1.834f, 0.791f)
close()
}
}.build()
}
}
@Composable
fun vSync(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary
return remember {
ImageVector.Builder(
name = "60fps",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
stroke = SolidColor(primaryColor),
fillAlpha = 1f,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(7.292f, 31.458f)
quadToRelative(-1.542f, 0f, -2.625f, -1.041f)
quadToRelative(-1.084f, -1.042f, -1.084f, -2.625f)
verticalLineTo(12.208f)
quadToRelative(0f, -1.583f, 1.084f, -2.625f)
quadTo(5.75f, 8.542f, 7.292f, 8.542f)
horizontalLineTo(14f)
quadToRelative(0.75f, 0f, 1.292f, 0.541f)
quadToRelative(0.541f, 0.542f, 0.541f, 1.292f)
reflectiveQuadToRelative(-0.541f, 1.292f)
quadToRelative(-0.542f, 0.541f, -1.292f, 0.541f)
horizontalLineTo(7.208f)
verticalLineToRelative(5.084f)
horizontalLineToRelative(6.709f)
quadToRelative(1.541f, 0f, 2.583f, 1.041f)
quadToRelative(1.042f, 1.042f, 1.042f, 2.625f)
verticalLineToRelative(6.834f)
quadToRelative(0f, 1.583f, -1.042f, 2.625f)
quadToRelative(-1.042f, 1.041f, -2.583f, 1.041f)
close()
moveToRelative(-0.084f, -10.5f)
verticalLineToRelative(6.834f)
horizontalLineToRelative(6.709f)
verticalLineToRelative(-6.834f)
close()
moveToRelative(17.125f, 6.834f)
horizontalLineToRelative(8.459f)
verticalLineTo(12.208f)
horizontalLineToRelative(-8.459f)
verticalLineToRelative(15.584f)
close()
moveToRelative(0f, 3.666f)
quadToRelative(-1.541f, 0f, -2.583f, -1.041f)
quadToRelative(-1.042f, -1.042f, -1.042f, -2.625f)
verticalLineTo(12.208f)
quadToRelative(0f, -1.583f, 1.042f, -2.625f)
quadToRelative(1.042f, -1.041f, 2.583f, -1.041f)
horizontalLineToRelative(8.459f)
quadToRelative(1.541f, 0f, 2.583f, 1.041f)
quadToRelative(1.042f, 1.042f, 1.042f, 2.625f)
verticalLineToRelative(15.584f)
quadToRelative(0f, 1.583f, -1.042f, 2.625f)
quadToRelative(-1.042f, 1.041f, -2.583f, 1.041f)
close()
}
}.build()
}
}
@Composable
fun videoGame(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary
return remember {
ImageVector.Builder(
name = "videogame_asset",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
stroke = SolidColor(primaryColor),
fillAlpha = 1f,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(6.25f, 29.792f)
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
quadToRelative(-0.771f, -0.792f, -0.771f, -1.833f)
verticalLineTo(12.833f)
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
horizontalLineToRelative(27.5f)
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
verticalLineToRelative(14.334f)
quadToRelative(0f, 1.041f, -0.771f, 1.833f)
reflectiveQuadToRelative(-1.854f, 0.792f)
close()
moveToRelative(0f, -2.625f)
horizontalLineToRelative(27.5f)
verticalLineTo(12.833f)
horizontalLineTo(6.25f)
verticalLineToRelative(14.334f)
close()
moveToRelative(7.167f, -1.792f)
quadToRelative(0.541f, 0f, 0.916f, -0.375f)
reflectiveQuadToRelative(0.375f, -0.917f)
verticalLineToRelative(-2.791f)
horizontalLineToRelative(2.75f)
quadToRelative(0.584f, 0f, 0.959f, -0.375f)
reflectiveQuadToRelative(0.375f, -0.917f)
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
quadToRelative(-0.375f, -0.395f, -0.959f, -0.395f)
horizontalLineToRelative(-2.75f)
verticalLineToRelative(-2.75f)
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
quadToRelative(-0.375f, -0.396f, -0.916f, -0.396f)
quadToRelative(-0.584f, 0f, -0.959f, 0.396f)
reflectiveQuadToRelative(-0.375f, 0.938f)
verticalLineToRelative(2.75f)
horizontalLineToRelative(-2.75f)
quadToRelative(-0.541f, 0f, -0.937f, 0.395f)
quadTo(8f, 19.458f, 8f, 20f)
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
reflectiveQuadToRelative(0.937f, 0.375f)
horizontalLineToRelative(2.75f)
verticalLineToRelative(2.791f)
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
reflectiveQuadToRelative(0.938f, 0.375f)
close()
moveToRelative(11.125f, -0.5f)
quadToRelative(0.791f, 0f, 1.396f, -0.583f)
quadToRelative(0.604f, -0.584f, 0.604f, -1.375f)
quadToRelative(0f, -0.834f, -0.604f, -1.417f)
quadToRelative(-0.605f, -0.583f, -1.396f, -0.583f)
quadToRelative(-0.834f, 0f, -1.417f, 0.583f)
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
quadToRelative(0f, 0.833f, 0.583f, 1.417f)
quadToRelative(0.583f, 0.583f, 1.417f, 0.583f)
close()
moveToRelative(3.916f, -5.833f)
quadToRelative(0.834f, 0f, 1.417f, -0.584f)
quadToRelative(0.583f, -0.583f, 0.583f, -1.416f)
quadToRelative(0f, -0.792f, -0.583f, -1.375f)
quadToRelative(-0.583f, -0.584f, -1.417f, -0.584f)
quadToRelative(-0.791f, 0f, -1.375f, 0.584f)
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
quadToRelative(0f, 0.833f, 0.583f, 1.416f)
quadToRelative(0.584f, 0.584f, 1.375f, 0.584f)
close()
moveTo(6.25f, 27.167f)
verticalLineTo(12.833f)
verticalLineToRelative(14.334f)
close()
}
}.build()
}
}
}
}
@Preview
@Composable
fun Preview() {
IconButton(modifier = Modifier.padding(4.dp), onClick = {
}) {
Icon(
imageVector = CssGgIcons.Games,
contentDescription = "Open Panel"
)
}
}

View File

@ -0,0 +1,63 @@
package org.ryujinx.android
import android.content.Intent
import androidx.core.content.FileProvider
import net.lingala.zip4j.ZipFile
import org.ryujinx.android.viewmodels.MainViewModel
import java.io.File
import java.net.URLConnection
class Logging(private var viewModel: MainViewModel) {
val logPath = MainActivity.AppPath + "/Logs"
init {
File(logPath).mkdirs()
}
fun requestExport() {
val files = File(logPath).listFiles()
files?.apply {
val zipExportPath = MainActivity.AppPath + "/log.zip"
File(zipExportPath).delete()
var count = 0
if (files.isNotEmpty()) {
val zipFile = ZipFile(zipExportPath)
for (file in files) {
if (file.isFile) {
zipFile.addFile(file)
count++
}
}
zipFile.close()
}
if (count > 0) {
val zip = File(zipExportPath)
val uri = FileProvider.getUriForFile(
viewModel.activity,
viewModel.activity.packageName + ".fileprovider",
zip
)
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.setDataAndType(uri, URLConnection.guessContentTypeFromName(zip.name))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, "Share logs")
viewModel.activity.startActivity(chooser)
} else {
File(zipExportPath).delete()
}
}
}
fun clearLogs() {
if (File(logPath).exists()) {
File(logPath).deleteRecursively()
}
File(logPath).mkdirs()
}
}
internal enum class LogLevel {
Debug, Stub, Info, Warning, Error, Guest, AccessLog, Notice, Trace
}

View File

@ -0,0 +1,227 @@
package org.ryujinx.android
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.os.Environment
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.anggrayudi.storage.SimpleStorageHelper
import com.sun.jna.JNIEnv
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings
import org.ryujinx.android.views.MainView
class MainActivity : BaseActivity() {
private var physicalControllerManager: PhysicalControllerManager =
PhysicalControllerManager(this)
private lateinit var motionSensorManager: MotionSensorManager
private var _isInit: Boolean = false
var isGameRunning = false
var isActive = false
var storageHelper: SimpleStorageHelper? = null
lateinit var uiHandler: UiHandler
companion object {
var mainViewModel: MainViewModel? = null
var AppPath: String = ""
var StorageHelper: SimpleStorageHelper? = null
val performanceMonitor = PerformanceMonitor()
@JvmStatic
fun frameEnded() {
mainViewModel?.activity?.apply {
if (isActive && QuickSettings(this).enablePerformanceMode) {
mainViewModel?.performanceManager?.setTurboMode(true)
}
}
mainViewModel?.gameHost?.hideProgressIndicator()
}
}
init {
storageHelper = SimpleStorageHelper(this)
StorageHelper = storageHelper
System.loadLibrary("ryujinxjni")
initVm()
}
private external fun initVm()
private fun initialize() {
if (_isInit)
return
val appPath: String = AppPath
var quickSettings = QuickSettings(this)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Debug.ordinal,
quickSettings.enableDebugLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Info.ordinal,
quickSettings.enableInfoLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Stub.ordinal,
quickSettings.enableStubLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Warning.ordinal,
quickSettings.enableWarningLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Error.ordinal,
quickSettings.enableErrorLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.AccessLog.ordinal,
quickSettings.enableAccessLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Guest.ordinal,
quickSettings.enableGuestLogs
)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Trace.ordinal,
quickSettings.enableTraceLogs
)
RyujinxNative.jnaInstance.loggingEnabledGraphicsLog(
quickSettings.enableTraceLogs
)
val success =
RyujinxNative.jnaInstance.javaInitialize(appPath, JNIEnv.CURRENT)
uiHandler = UiHandler()
_isInit = success
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
motionSensorManager = MotionSensorManager(this)
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
if (
!Environment.isExternalStorageManager()
) {
storageHelper?.storage?.requestFullStorageAccess()
}
AppPath = this.getExternalFilesDir(null)!!.absolutePath
initialize()
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
WindowCompat.setDecorFitsSystemWindows(window, false)
mainViewModel = MainViewModel(this)
mainViewModel!!.physicalControllerManager = physicalControllerManager
mainViewModel!!.motionSensorManager = motionSensorManager
mainViewModel!!.refreshFirmwareVersion()
mainViewModel?.apply {
setContent {
RyujinxAndroidTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainView.Main(mainViewModel = this)
}
}
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
storageHelper?.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storageHelper?.onRestoreInstanceState(savedInstanceState)
}
fun setFullScreen(fullscreen: Boolean) {
requestedOrientation =
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
val insets = WindowCompat.getInsetsController(window, window.decorView)
insets.apply {
if (fullscreen) {
insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
insets.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} else {
insets.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
insets.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
}
}
}
@SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
event.apply {
if (physicalControllerManager.onKeyEvent(this))
return true
}
return super.dispatchKeyEvent(event)
}
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
ev?.apply {
physicalControllerManager.onMotionEvent(this)
}
return super.dispatchGenericMotionEvent(ev)
}
override fun onStop() {
super.onStop()
isActive = false
if (isGameRunning) {
mainViewModel?.performanceManager?.setTurboMode(false)
}
}
override fun onResume() {
super.onResume()
isActive = true
if (isGameRunning) {
setFullScreen(true)
if (QuickSettings(this).enableMotion)
motionSensorManager.register()
}
}
override fun onPause() {
super.onPause()
isActive = true
if (isGameRunning) {
mainViewModel?.performanceManager?.setTurboMode(false)
}
motionSensorManager.unregister()
}
}

View File

@ -0,0 +1,137 @@
package org.ryujinx.android
import android.app.Activity
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener2
import android.hardware.SensorManager
import android.view.OrientationEventListener
class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
private var isRegistered: Boolean = false
private var gyro: Sensor?
private var accelerometer: Sensor?
private var sensorManager: SensorManager =
activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager
private var controllerId: Int = -1
private val motionGyroOrientation: FloatArray = FloatArray(3)
private val motionAcelOrientation: FloatArray = FloatArray(3)
init {
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
setOrientation90()
var orientationListener = object : OrientationEventListener(activity) {
override fun onOrientationChanged(orientation: Int) {
when {
isWithinOrientationRange(orientation, 270) -> {
setOrientation270()
}
isWithinOrientationRange(orientation, 90) -> {
setOrientation90()
}
}
}
private fun isWithinOrientationRange(
currentOrientation: Int, targetOrientation: Int, epsilon: Int = 90
): Boolean {
return currentOrientation > targetOrientation - epsilon
&& currentOrientation < targetOrientation + epsilon
}
}
}
fun setOrientation270() {
motionGyroOrientation[0] = -1.0f
motionGyroOrientation[1] = 1.0f
motionGyroOrientation[2] = 1.0f
motionAcelOrientation[0] = 1.0f
motionAcelOrientation[1] = -1.0f
motionAcelOrientation[2] = -1.0f
}
fun setOrientation90() {
motionGyroOrientation[0] = 1.0f
motionGyroOrientation[1] = -1.0f
motionGyroOrientation[2] = 1.0f
motionAcelOrientation[0] = -1.0f
motionAcelOrientation[1] = 1.0f
motionAcelOrientation[2] = -1.0f
}
fun setControllerId(id: Int) {
controllerId = id
}
fun register() {
if (isRegistered)
return
gyro?.apply {
sensorManager.registerListener(
this@MotionSensorManager,
gyro,
SensorManager.SENSOR_DELAY_GAME
)
}
accelerometer?.apply {
sensorManager.registerListener(
this@MotionSensorManager,
accelerometer,
SensorManager.SENSOR_DELAY_GAME
)
}
isRegistered = true
}
fun unregister() {
sensorManager.unregisterListener(this)
isRegistered = false
if (controllerId != -1) {
RyujinxNative.jnaInstance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.jnaInstance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
}
}
override fun onSensorChanged(event: SensorEvent?) {
if (controllerId != -1)
if (isRegistered)
event?.apply {
when (sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
val x = motionAcelOrientation[0] * event.values[1]
val y = motionAcelOrientation[1] * event.values[0]
val z = motionAcelOrientation[2] * event.values[2]
RyujinxNative.jnaInstance.inputSetAccelerometerData(
x,
y,
z,
controllerId
)
}
Sensor.TYPE_GYROSCOPE -> {
val x = motionGyroOrientation[0] * event.values[1]
val y = motionGyroOrientation[1] * event.values[0]
val z = motionGyroOrientation[2] * event.values[2]
RyujinxNative.jnaInstance.inputSetGyroData(x, y, z, controllerId)
}
}
}
else {
RyujinxNative.jnaInstance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.jnaInstance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
override fun onFlushCompleted(sensor: Sensor?) {
}
}

View File

@ -0,0 +1,7 @@
package org.ryujinx.android
class NativeGraphicsInterop {
var VkCreateSurface: Long = 0
var SurfaceHandle: Long = 0
var VkRequiredExtensions: Array<String>? = null
}

View File

@ -0,0 +1,31 @@
package org.ryujinx.android
import android.view.Surface
class NativeHelpers {
companion object {
val instance = NativeHelpers()
init {
System.loadLibrary("ryujinxjni")
}
}
external fun releaseNativeWindow(window: Long)
external fun getCreateSurfacePtr(): Long
external fun getNativeWindow(surface: Surface): Long
external fun loadDriver(
nativeLibPath: String,
privateAppsPath: String,
driverName: String
): Long
external fun setTurboMode(enable: Boolean)
external fun getMaxSwapInterval(nativeWindow: Long): Int
external fun getMinSwapInterval(nativeWindow: Long): Int
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
external fun getStringJava(ptr: Long): String
external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
}

View File

@ -0,0 +1,42 @@
package org.ryujinx.android
import android.view.SurfaceView
class NativeWindow(val surface: SurfaceView) {
var nativePointer: Long
private val nativeHelpers: NativeHelpers = NativeHelpers.instance
private var _swapInterval: Int = 0
var maxSwapInterval: Int = 0
get() {
return if (nativePointer == -1L) 0 else nativeHelpers.getMaxSwapInterval(nativePointer)
}
var minSwapInterval: Int = 0
get() {
return if (nativePointer == -1L) 0 else nativeHelpers.getMinSwapInterval(nativePointer)
}
var swapInterval: Int
get() {
return _swapInterval
}
set(value) {
if (nativePointer == -1L || nativeHelpers.setSwapInterval(nativePointer, value) == 0)
_swapInterval = value
}
init {
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
swapInterval = maxOf(1, minSwapInterval)
}
fun requeryWindowHandle(): Long {
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
swapInterval = swapInterval
return nativePointer
}
}

View File

@ -0,0 +1,31 @@
package org.ryujinx.android
import android.content.Intent
import kotlin.math.abs
class PerformanceManager(private val activity: MainActivity) {
companion object {
fun force60HzRefreshRate(enable: Boolean, activity: MainActivity) {
// Hack for MIUI devices since they don't support the standard Android APIs
try {
val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS")
setFpsIntent.putExtra("package_name", "org.ryujinx.android")
setFpsIntent.putExtra("isEnter", enable)
activity.sendBroadcast(setFpsIntent)
} catch (_: Exception) {
}
if (enable)
activity.display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }
?.let { activity.window.attributes.preferredDisplayModeId = it.modeId }
else
activity.display?.supportedModes?.maxByOrNull { it.refreshRate }
?.let { activity.window.attributes.preferredDisplayModeId = it.modeId }
}
}
fun setTurboMode(enable: Boolean) {
NativeHelpers.instance.setTurboMode(enable)
force60HzRefreshRate(enable, activity)
}
}

View File

@ -0,0 +1,45 @@
package org.ryujinx.android
import android.app.ActivityManager
import android.content.Context.ACTIVITY_SERVICE
import androidx.compose.runtime.MutableState
import java.io.RandomAccessFile
class PerformanceMonitor {
val numberOfCores = Runtime.getRuntime().availableProcessors()
fun getFrequencies(frequencies: MutableList<Double>){
frequencies.clear()
for (i in 0..<numberOfCores) {
var freq = 0.0
try {
val reader = RandomAccessFile(
"/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_cur_freq",
"r"
)
val f = reader.readLine()
reader.close()
freq = f.toDouble() / 1000.0
} catch (e: Exception) {
}
frequencies.add(freq)
}
}
fun getMemoryUsage(
usedMem: MutableState<Int>,
totalMem: MutableState<Int>) {
MainActivity.mainViewModel?.activity?.apply {
val actManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
actManager.getMemoryInfo(memInfo)
val availMemory = memInfo.availMem.toDouble() / (1024 * 1024)
val totalMemory = memInfo.totalMem.toDouble() / (1024 * 1024)
usedMem.value = (totalMemory - availMemory).toInt()
totalMem.value = totalMemory.toInt()
}
}
}

View File

@ -0,0 +1,158 @@
package org.ryujinx.android
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import org.ryujinx.android.viewmodels.QuickSettings
class PhysicalControllerManager(val activity: MainActivity) {
private var controllerId: Int = -1
fun onKeyEvent(event: KeyEvent): Boolean {
val id = getGamePadButtonInputId(event.keyCode)
if (id != GamePadButtonInputId.None) {
val isNotFallback = (event.flags and KeyEvent.FLAG_FALLBACK) == 0
if (/*controllerId != -1 &&*/ isNotFallback) {
when (event.action) {
KeyEvent.ACTION_UP -> {
RyujinxNative.jnaInstance.inputSetButtonReleased(id.ordinal, controllerId)
}
KeyEvent.ACTION_DOWN -> {
RyujinxNative.jnaInstance.inputSetButtonPressed(id.ordinal, controllerId)
}
}
return true
} else if (!isNotFallback) {
return true
}
}
return false
}
fun onMotionEvent(ev: MotionEvent) {
if (true) {
if (ev.action == MotionEvent.ACTION_MOVE) {
val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X)
val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y)
val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z)
val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ)
RyujinxNative.jnaInstance.inputSetStickAxis(
1,
leftStickX,
-leftStickY,
controllerId
)
RyujinxNative.jnaInstance.inputSetStickAxis(
2,
rightStickX,
-rightStickY,
controllerId
)
ev.device?.apply {
if (sources and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD) {
// Controller uses HAT
val dPadHor = ev.getAxisValue(MotionEvent.AXIS_HAT_X)
val dPadVert = ev.getAxisValue(MotionEvent.AXIS_HAT_Y)
if (dPadVert == 0.0f) {
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
controllerId
)
}
if (dPadHor == 0.0f) {
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
controllerId
)
}
if (dPadVert < 0.0f) {
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadUp.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
controllerId
)
}
if (dPadHor < 0.0f) {
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadLeft.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
controllerId
)
}
if (dPadVert > 0.0f) {
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadDown.ordinal,
controllerId
)
}
if (dPadHor > 0.0f) {
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadRight.ordinal,
controllerId
)
}
}
}
}
}
}
fun connect(): Int {
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
return controllerId
}
fun disconnect() {
controllerId = -1
}
private fun getGamePadButtonInputId(keycode: Int): GamePadButtonInputId {
val quickSettings = QuickSettings(activity)
return when (keycode) {
KeyEvent.KEYCODE_BUTTON_A -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.A else GamePadButtonInputId.B
KeyEvent.KEYCODE_BUTTON_B -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.B else GamePadButtonInputId.A
KeyEvent.KEYCODE_BUTTON_X -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.X else GamePadButtonInputId.Y
KeyEvent.KEYCODE_BUTTON_Y -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.Y else GamePadButtonInputId.X
KeyEvent.KEYCODE_BUTTON_L1 -> GamePadButtonInputId.LeftShoulder
KeyEvent.KEYCODE_BUTTON_L2 -> GamePadButtonInputId.LeftTrigger
KeyEvent.KEYCODE_BUTTON_R1 -> GamePadButtonInputId.RightShoulder
KeyEvent.KEYCODE_BUTTON_R2 -> GamePadButtonInputId.RightTrigger
KeyEvent.KEYCODE_BUTTON_THUMBL -> GamePadButtonInputId.LeftStick
KeyEvent.KEYCODE_BUTTON_THUMBR -> GamePadButtonInputId.RightStick
KeyEvent.KEYCODE_DPAD_UP -> GamePadButtonInputId.DpadUp
KeyEvent.KEYCODE_DPAD_DOWN -> GamePadButtonInputId.DpadDown
KeyEvent.KEYCODE_DPAD_LEFT -> GamePadButtonInputId.DpadLeft
KeyEvent.KEYCODE_DPAD_RIGHT -> GamePadButtonInputId.DpadRight
KeyEvent.KEYCODE_BUTTON_START -> GamePadButtonInputId.Plus
KeyEvent.KEYCODE_BUTTON_SELECT -> GamePadButtonInputId.Minus
else -> GamePadButtonInputId.None
}
}
}

View File

@ -0,0 +1,11 @@
package org.ryujinx.android
enum class RegionCode {
Japan,
USA,
Europe,
Australia,
China,
Korea,
Taiwan,
}

View File

@ -0,0 +1,20 @@
package org.ryujinx.android
import android.app.Application
import android.content.Context
import java.io.File
class RyujinxApplication : Application() {
init {
instance = this
}
fun getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
companion object {
lateinit var instance: RyujinxApplication
private set
val context: Context get() = instance.applicationContext
}
}

View File

@ -0,0 +1,158 @@
package org.ryujinx.android
import com.sun.jna.JNIEnv
import com.sun.jna.Library
import com.sun.jna.Native
import org.ryujinx.android.viewmodels.GameInfo
import java.util.Collections
interface RyujinxNativeJna : Library {
fun deviceInitialize(
isHostMapped: Boolean, useNce: Boolean,
systemLanguage: Int,
regionCode: Int,
enableVsync: Boolean,
enableDockedMode: Boolean,
enablePtc: Boolean,
enableInternetAccess: Boolean,
timeZone: String,
ignoreMissingServices: Boolean
): Boolean
fun graphicsInitialize(
rescale: Float = 1f,
maxAnisotropy: Float = 1f,
fastGpuTime: Boolean = true,
fast2DCopy: Boolean = true,
enableMacroJit: Boolean = false,
enableMacroHLE: Boolean = true,
enableShaderCache: Boolean = true,
enableTextureRecompression: Boolean = false,
backendThreading: Int = BackendThreading.Auto.ordinal
): Boolean
fun graphicsInitializeRenderer(
extensions: Array<String>,
extensionsLength: Int,
driver: Long
): Boolean
fun javaInitialize(appPath: String, env: JNIEnv): Boolean
fun deviceLaunchMiiEditor(): Boolean
fun deviceGetGameFrameRate(): Double
fun deviceGetGameFrameTime(): Double
fun deviceGetGameFifo(): Double
fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
fun graphicsRendererSetSize(width: Int, height: Int)
fun graphicsRendererSetVsync(enabled: Boolean)
fun graphicsRendererRunLoop()
fun deviceReloadFilesystem()
fun inputInitialize(width: Int, height: Int)
fun inputSetClientSize(width: Int, height: Int)
fun inputSetTouchPoint(x: Int, y: Int)
fun inputReleaseTouchPoint()
fun inputUpdate()
fun inputSetButtonPressed(button: Int, id: Int)
fun inputSetButtonReleased(button: Int, id: Int)
fun inputConnectGamepad(index: Int): Int
fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
fun deviceCloseEmulation()
fun deviceSignalEmulationClose()
fun userGetOpenedUser(): String
fun userGetUserPicture(userId: String): String
fun userSetUserPicture(userId: String, picture: String)
fun userGetUserName(userId: String): String
fun userSetUserName(userId: String, userName: String)
fun userAddUser(username: String, picture: String)
fun userDeleteUser(userId: String)
fun userOpenUser(userId: String)
fun userCloseUser(userId: String)
fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): String
fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
fun deviceGetInstalledFirmwareVersion(): String
fun uiHandlerSetup()
fun uiHandlerSetResponse(isOkPressed: Boolean, input: String)
fun deviceGetDlcTitleId(path: String, ncaPath: String): String
fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
fun userGetAllUsers(): Array<String>
fun deviceGetDlcContentList(path: String, titleId: Long): Array<String>
fun loggingEnabledGraphicsLog(enabled: Boolean)
}
class RyujinxNative {
companion object {
val jnaInstance: RyujinxNativeJna = Native.load(
"ryujinx",
RyujinxNativeJna::class.java,
Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true)
)
@JvmStatic
fun test()
{
val i = 0
}
@JvmStatic
fun frameEnded()
{
MainActivity.frameEnded()
}
@JvmStatic
fun getSurfacePtr() : Long
{
return MainActivity.mainViewModel?.gameHost?.currentSurface ?: -1
}
@JvmStatic
fun getWindowHandle() : Long
{
return MainActivity.mainViewModel?.gameHost?.currentWindowhandle ?: -1
}
@JvmStatic
fun updateProgress(infoPtr : Long, progress: Float)
{
val info = NativeHelpers.instance.getStringJava(infoPtr);
MainActivity.mainViewModel?.gameHost?.setProgress(info, progress)
}
@JvmStatic
fun updateUiHandler(
newTitlePointer: Long,
newMessagePointer: Long,
newWatermarkPointer: Long,
newType: Int,
min: Int,
max: Int,
nMode: Int,
newSubtitlePointer: Long,
newInitialTextPointer: Long
)
{
var uiHandler = MainActivity.mainViewModel?.activity?.uiHandler
uiHandler?.apply {
val newTitle = NativeHelpers.instance.getStringJava(newTitlePointer)
val newMessage = NativeHelpers.instance.getStringJava(newMessagePointer)
val newWatermark = NativeHelpers.instance.getStringJava(newWatermarkPointer)
val newSubtitle = NativeHelpers.instance.getStringJava(newSubtitlePointer)
val newInitialText = NativeHelpers.instance.getStringJava(newInitialTextPointer)
val newMode = KeyboardMode.entries[nMode]
update(newTitle,
newMessage,
newWatermark,
newType,
min,
max,
newMode,
newSubtitle,
newInitialText);
}
}
}
}

View File

@ -0,0 +1,22 @@
package org.ryujinx.android
enum class SystemLanguage {
Japanese,
AmericanEnglish,
French,
German,
Italian,
Spanish,
Chinese,
Korean,
Dutch,
Portuguese,
Russian,
Taiwanese,
BritishEnglish,
CanadianFrench,
LatinAmericanSpanish,
SimplifiedChinese,
TraditionalChinese,
BrazilianPortuguese
}

View File

@ -0,0 +1,211 @@
package org.ryujinx.android
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.material3.RichText
enum class KeyboardMode {
Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin
}
class UiHandler {
private var initialText: String = ""
private var subtitle: String = ""
private var maxLength: Int = 0
private var minLength: Int = 0
private var watermark: String = ""
private var type: Int = -1
private var mode: KeyboardMode = KeyboardMode.Default
val showMessage = mutableStateOf(false)
val inputText = mutableStateOf("")
var title: String = ""
var message: String = ""
init {
RyujinxNative.jnaInstance.uiHandlerSetup()
}
fun update(
newTitle: String,
newMessage: String,
newWatermark: String,
newType: Int,
min: Int,
max: Int,
newMode: KeyboardMode,
newSubtitle: String,
newInitialText: String
)
{
title = newTitle
message = newMessage
watermark = newWatermark
type = newType
minLength = min
maxLength = max
mode = newMode
subtitle = newSubtitle
initialText = newInitialText
inputText.value = initialText
showMessage.value = type > 0
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Compose() {
val showMessageListener = remember {
showMessage
}
val inputListener = remember {
inputText
}
val validation = remember {
mutableStateOf("")
}
fun validate(): Boolean {
if (inputText.value.isEmpty()) {
validation.value = "Must be between ${minLength} and ${maxLength} characters"
} else {
return inputText.value.length < minLength || inputText.value.length > maxLength
}
return false
}
fun getInputType(): KeyboardType {
return when (mode) {
KeyboardMode.Default -> KeyboardType.Text
KeyboardMode.Numeric -> KeyboardType.Decimal
KeyboardMode.ASCII -> KeyboardType.Ascii
else -> {
KeyboardType.Text
}
}
}
fun submit() {
if (type == 2) {
if (inputListener.value.length < minLength || inputListener.value.length > maxLength)
return
}
RyujinxNative.jnaInstance.uiHandlerSetResponse(
true,
if (type == 2) inputListener.value else ""
)
showMessageListener.value = false
}
if (showMessageListener.value) {
BasicAlertDialog(
onDismissRequest = { },
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
properties = DialogProperties(dismissOnBackPress = false, false)
) {
Column {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(text = title)
Column(
modifier = Modifier
.height(128.dp)
.verticalScroll(rememberScrollState())
.padding(8.dp),
verticalArrangement = Arrangement.Center
) {
RichText {
Markdown(content = message)
}
if (type == 2) {
validate()
if (watermark.isNotEmpty())
TextField(
value = inputListener.value,
onValueChange = { inputListener.value = it },
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
label = {
Text(text = watermark)
},
keyboardOptions = KeyboardOptions(keyboardType = getInputType()),
isError = validate()
)
else
TextField(
value = inputListener.value,
onValueChange = { inputListener.value = it },
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
keyboardOptions = KeyboardOptions(
keyboardType = getInputType(),
imeAction = ImeAction.Done
),
isError = validate(),
singleLine = true,
keyboardActions = KeyboardActions(onDone = { submit() })
)
if (subtitle.isNotEmpty())
Text(text = subtitle)
Text(text = validation.value)
}
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
) {
Button(onClick = {
submit()
}) {
Text(text = "OK")
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,300 @@
package org.ryujinx.android.providers
import android.database.Cursor
import android.database.MatrixCursor
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract
import android.provider.DocumentsProvider
import android.webkit.MimeTypeMap
import org.ryujinx.android.BuildConfig
import org.ryujinx.android.R
import org.ryujinx.android.RyujinxApplication
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
class DocumentProvider : DocumentsProvider() {
private val baseDirectory = File(RyujinxApplication.instance.getPublicFilesDir().canonicalPath)
private val applicationName = "Ryujinx"
companion object {
private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf(
DocumentsContract.Root.COLUMN_ROOT_ID,
DocumentsContract.Root.COLUMN_MIME_TYPES,
DocumentsContract.Root.COLUMN_FLAGS,
DocumentsContract.Root.COLUMN_ICON,
DocumentsContract.Root.COLUMN_TITLE,
DocumentsContract.Root.COLUMN_SUMMARY,
DocumentsContract.Root.COLUMN_DOCUMENT_ID,
DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
)
private val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
DocumentsContract.Document.COLUMN_FLAGS,
DocumentsContract.Document.COLUMN_SIZE
)
const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".providers"
const val ROOT_ID: String = "root"
}
override fun onCreate(): Boolean {
return true
}
/**
* @return The [File] that corresponds to the document ID supplied by [getDocumentId]
*/
private fun getFile(documentId: String): File {
if (documentId.startsWith(ROOT_ID)) {
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
return file
} else {
throw FileNotFoundException("'$documentId' is not in any known root")
}
}
/**
* @return A unique ID for the provided [File]
*/
private fun getDocumentId(file: File): String {
return "$ROOT_ID/${file.toRelativeString(baseDirectory)}"
}
override fun queryRoots(projection: Array<out String>?): Cursor {
val cursor = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
cursor.newRow().apply {
add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID)
add(DocumentsContract.Root.COLUMN_SUMMARY, null)
add(
DocumentsContract.Root.COLUMN_FLAGS,
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
)
add(DocumentsContract.Root.COLUMN_TITLE, applicationName)
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*")
add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, baseDirectory.freeSpace)
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_launcher_foreground)
}
return cursor
}
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
return includeFile(cursor, documentId, null)
}
override fun isChildDocument(parentDocumentId: String?, documentId: String?): Boolean {
return documentId?.startsWith(parentDocumentId!!) ?: false
}
/**
* @return A new [File] with a unique name based off the supplied [name], not conflicting with any existing file
*/
fun File.resolveWithoutConflict(name: String): File {
var file = resolve(name)
if (file.exists()) {
var noConflictId =
1 // Makes sure two files don't have the same name by adding a number to the end
val extension = name.substringAfterLast('.')
val baseName = name.substringBeforeLast('.')
while (file.exists())
file = resolve("$baseName (${noConflictId++}).$extension")
}
return file
}
override fun createDocument(
parentDocumentId: String?,
mimeType: String?,
displayName: String
): String {
val parentFile = getFile(parentDocumentId!!)
val newFile = parentFile.resolveWithoutConflict(displayName)
try {
if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
if (!newFile.mkdir())
throw IOException("Failed to create directory")
} else {
if (!newFile.createNewFile())
throw IOException("Failed to create file")
}
} catch (e: IOException) {
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
}
return getDocumentId(newFile)
}
override fun deleteDocument(documentId: String?) {
val file = getFile(documentId!!)
if (!file.delete())
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
}
override fun removeDocument(documentId: String, parentDocumentId: String?) {
val parent = getFile(parentDocumentId!!)
val file = getFile(documentId)
if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
if (!file.delete())
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
} else {
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
}
}
override fun renameDocument(documentId: String?, displayName: String?): String {
if (displayName == null)
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
val sourceFile = getFile(documentId!!)
val sourceParentFile = sourceFile.parentFile
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
val destFile = sourceParentFile.resolve(displayName)
try {
if (!sourceFile.renameTo(destFile))
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
} catch (e: Exception) {
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
}
return getDocumentId(destFile)
}
private fun copyDocument(
sourceDocumentId: String, sourceParentDocumentId: String,
targetParentDocumentId: String?
): String? {
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
return copyDocument(sourceDocumentId, targetParentDocumentId)
}
override fun copyDocument(sourceDocumentId: String, targetParentDocumentId: String?): String {
val parent = getFile(targetParentDocumentId!!)
val oldFile = getFile(sourceDocumentId)
val newFile = parent.resolveWithoutConflict(oldFile.name)
try {
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true)))
throw IOException("Couldn't create new file")
FileInputStream(oldFile).use { inStream ->
FileOutputStream(newFile).use { outStream ->
inStream.copyTo(outStream)
}
}
} catch (e: IOException) {
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}")
}
return getDocumentId(newFile)
}
override fun moveDocument(
sourceDocumentId: String, sourceParentDocumentId: String?,
targetParentDocumentId: String?
): String? {
try {
val newDocumentId = copyDocument(
sourceDocumentId, sourceParentDocumentId!!,
targetParentDocumentId
)
removeDocument(sourceDocumentId, sourceParentDocumentId)
return newDocumentId
} catch (e: FileNotFoundException) {
throw FileNotFoundException("Couldn't move document '$sourceDocumentId'")
}
}
private fun includeFile(cursor: MatrixCursor, documentId: String?, file: File?): MatrixCursor {
val localDocumentId = documentId ?: file?.let { getDocumentId(it) }
val localFile = file ?: getFile(documentId!!)
var flags = 0
if (localFile.isDirectory && localFile.canWrite()) {
flags = DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE
} else if (localFile.canWrite()) {
flags = DocumentsContract.Document.FLAG_SUPPORTS_WRITE
flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_DELETE
flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_REMOVE
flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_MOVE
flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_COPY
flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_RENAME
}
cursor.newRow().apply {
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
add(
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
if (localFile == baseDirectory) applicationName else localFile.name
)
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
add(DocumentsContract.Document.COLUMN_FLAGS, flags)
if (localFile == baseDirectory)
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_launcher_foreground)
}
return cursor
}
private fun getTypeForFile(file: File): Any? {
return if (file.isDirectory)
DocumentsContract.Document.MIME_TYPE_DIR
else
getTypeForName(file.name)
}
private fun getTypeForName(name: String): Any {
val lastDot = name.lastIndexOf('.')
if (lastDot >= 0) {
val extension = name.substring(lastDot + 1)
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
if (mime != null)
return mime
}
return "application/octect-stream"
}
override fun queryChildDocuments(
parentDocumentId: String?,
projection: Array<out String>?,
sortOrder: String?
): Cursor {
var cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
val parent = getFile(parentDocumentId!!)
for (file in parent.listFiles()!!)
cursor = includeFile(cursor, null, file)
return cursor
}
override fun openDocument(
documentId: String?,
mode: String?,
signal: CancellationSignal?
): ParcelFileDescriptor {
val file = documentId?.let { getFile(it) }
val accessMode = ParcelFileDescriptor.parseMode(mode)
return ParcelFileDescriptor.open(file, accessMode)
}
}

View File

@ -0,0 +1,11 @@
package org.ryujinx.android.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,79 @@
package org.ryujinx.android.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40,
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
)
@Composable
fun RyujinxAndroidTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val shapes = Shapes(
extraSmall = RoundedCornerShape(4.dp),
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(12.dp),
large = RoundedCornerShape(16.dp),
extraLarge = RoundedCornerShape(24.dp)
)
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content,
shapes = shapes
)
}

View File

@ -0,0 +1,34 @@
package org.ryujinx.android.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,157 @@
package org.ryujinx.android.viewmodels
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import com.anggrayudi.storage.SimpleStorageHelper
import com.anggrayudi.storage.file.getAbsolutePath
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.ryujinx.android.MainActivity
import org.ryujinx.android.RyujinxNative
import java.io.File
class DlcViewModel(val titleId: String) {
private var storageHelper: SimpleStorageHelper
companion object {
const val UpdateRequestCode = 1002
}
fun remove(item: DlcItem) {
data?.apply {
this.removeAll { it.path == item.containerPath }
}
}
fun add(refresh: MutableState<Boolean>) {
val callBack = storageHelper.onFileSelected
storageHelper.onFileSelected = { requestCode, files ->
run {
storageHelper.onFileSelected = callBack
if (requestCode == UpdateRequestCode) {
val file = files.firstOrNull()
file?.apply {
val path = file.getAbsolutePath(storageHelper.storage.context)
if (path.isNotEmpty()) {
data?.apply {
val contents = RyujinxNative.jnaInstance.deviceGetDlcContentList(
path,
titleId.toLong(16)
)
if (contents.isNotEmpty()) {
val contentPath = path
val container = DlcContainerList(contentPath)
for (content in contents)
container.dlc_nca_list.add(
DlcContainer(
true,
titleId,
content
)
)
this.add(container)
}
}
}
}
refresh.value = true
}
}
}
storageHelper.openFilePicker(UpdateRequestCode)
}
fun save(items: List<DlcItem>) {
data?.apply {
val gson = Gson()
val json = gson.toJson(this)
jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
File(jsonPath).mkdirs()
File("$jsonPath/dlc.json").writeText(json)
}
}
@Composable
fun getDlc(): List<DlcItem> {
var items = mutableListOf<DlcItem>()
data?.apply {
for (container in this) {
val containerPath = container.path
if (!File(containerPath).exists())
continue
for (dlc in container.dlc_nca_list) {
val enabled = remember {
mutableStateOf(dlc.enabled)
}
items.add(
DlcItem(
File(containerPath).name,
enabled,
containerPath,
dlc.fullPath,
RyujinxNative.jnaInstance.deviceGetDlcTitleId(
containerPath,
dlc.fullPath
)
)
)
}
}
}
return items.toList()
}
var data: MutableList<DlcContainerList>? = null
private var jsonPath: String
init {
jsonPath =
MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/dlc.json"
storageHelper = MainActivity.StorageHelper!!
reloadFromDisk()
}
private fun reloadFromDisk() {
data = mutableListOf()
if (File(jsonPath).exists()) {
val gson = Gson()
val typeToken = object : TypeToken<MutableList<DlcContainerList>>() {}.type
data =
gson.fromJson<MutableList<DlcContainerList>>(File(jsonPath).readText(), typeToken)
}
}
}
data class DlcContainerList(
var path: String = "",
var dlc_nca_list: MutableList<DlcContainer> = mutableListOf()
)
data class DlcContainer(
var enabled: Boolean = false,
var titleId: String = "",
var fullPath: String = ""
)
data class DlcItem(
var name: String = "",
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
var containerPath: String = "",
var fullPath: String = "",
var titleId: String = ""
)

View File

@ -0,0 +1,20 @@
package org.ryujinx.android.viewmodels;
import com.sun.jna.Structure;
import java.util.List;
public class GameInfo extends Structure {
public double FileSize = 0.0;
public String TitleName;
public String TitleId;
public String Developer;
public String Version;
public String Icon;
@Override
protected List<String> getFieldOrder() {
return List.of("FileSize", "TitleName", "TitleId", "Developer", "Version", "Icon");
}
}

View File

@ -0,0 +1,90 @@
package org.ryujinx.android.viewmodels
import android.content.Context
import android.net.Uri
import android.os.ParcelFileDescriptor
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.file.extension
import org.ryujinx.android.RyujinxNative
class GameModel(var file: DocumentFile, val context: Context) {
private var updateDescriptor: ParcelFileDescriptor? = null
var type: FileType
var descriptor: ParcelFileDescriptor? = null
var fileName: String?
var fileSize = 0.0
var titleName: String? = null
var titleId: String? = null
var developer: String? = null
var version: String? = null
var icon: String? = null
init {
fileName = file.name
val pid = open()
val gameInfo = GameInfo()
RyujinxNative.jnaInstance.deviceGetGameInfo(pid, file.extension, gameInfo)
close()
fileSize = gameInfo.FileSize
titleId = gameInfo.TitleId
titleName = gameInfo.TitleName
developer = gameInfo.Developer
version = gameInfo.Version
icon = gameInfo.Icon
type = when {
(file.extension == "xci") -> FileType.Xci
(file.extension == "nsp") -> FileType.Nsp
(file.extension == "nro") -> FileType.Nro
else -> FileType.None
}
if (type == FileType.Nro && (titleName.isNullOrEmpty() || titleName == "Unknown")) {
titleName = file.name
}
}
fun open(): Int {
descriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
return descriptor?.fd ?: 0
}
fun openUpdate(): Int {
if (titleId?.isNotEmpty() == true) {
val vm = TitleUpdateViewModel(titleId ?: "")
if (vm.data?.selected?.isNotEmpty() == true) {
val uri = Uri.parse(vm.data?.selected)
val file = DocumentFile.fromSingleUri(context, uri)
if (file?.exists() == true) {
try {
updateDescriptor =
context.contentResolver.openFileDescriptor(file.uri, "rw")
return updateDescriptor?.fd ?: -1
} catch (e: Exception) {
return -2
}
}
}
}
return -1
}
fun close() {
descriptor?.close()
descriptor = null
updateDescriptor?.close()
updateDescriptor = null
}
}
enum class FileType {
None,
Nsp,
Xci,
Nro
}

View File

@ -0,0 +1,101 @@
package org.ryujinx.android.viewmodels
import android.content.SharedPreferences
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import com.anggrayudi.storage.file.DocumentFileCompat
import com.anggrayudi.storage.file.DocumentFileType
import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.search
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.ryujinx.android.MainActivity
import java.util.Locale
import kotlin.concurrent.thread
class HomeViewModel(
val activity: MainActivity? = null,
val mainViewModel: MainViewModel? = null
) {
private var shouldReload: Boolean = false
private var savedFolder: String = ""
private var loadedCache: MutableList<GameModel> = mutableListOf()
private var gameFolderPath: DocumentFile? = null
private var sharedPref: SharedPreferences? = null
val gameList: SnapshotStateList<GameModel> = SnapshotStateList()
val isLoading: MutableState<Boolean> = mutableStateOf(false)
init {
if (activity != null) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(activity)
}
}
fun ensureReloadIfNecessary() {
val oldFolder = savedFolder
savedFolder = sharedPref?.getString("gameFolder", "") ?: ""
if (savedFolder.isNotEmpty() && (shouldReload || savedFolder != oldFolder)) {
gameFolderPath = DocumentFileCompat.fromFullPath(
mainViewModel?.activity!!,
savedFolder,
documentType = DocumentFileType.FOLDER,
requiresWriteAccess = true
)
reloadGameList()
}
}
fun filter(query: String) {
gameList.clear()
gameList.addAll(loadedCache.filter {
it.titleName != null && it.titleName!!.isNotEmpty() && (query.trim()
.isEmpty() || it.titleName!!.lowercase(Locale.getDefault())
.contains(query))
})
}
fun requestReload() {
shouldReload = true
}
@OptIn(DelicateCoroutinesApi::class)
private fun reloadGameList() {
activity?.storageHelper ?: return
val folder = gameFolderPath ?: return
shouldReload = false
if (isLoading.value)
return
gameList.clear()
loadedCache.clear()
isLoading.value = true
thread {
try {
for (file in folder.search(false, DocumentFileType.FILE)) {
if (file.extension == "xci" || file.extension == "nsp" || file.extension == "nro")
activity.let {
val item = GameModel(file, it)
if (item.titleId?.isNotEmpty() == true && item.titleName?.isNotEmpty() == true && item.titleName != "Unknown") {
loadedCache.add(item)
}
}
}
} finally {
isLoading.value = false
GlobalScope.launch(Dispatchers.Main){
filter("")
}
}
}
}
}

View File

@ -0,0 +1,414 @@
package org.ryujinx.android.viewmodels
import android.annotation.SuppressLint
import androidx.compose.runtime.MutableState
import androidx.navigation.NavHostController
import com.anggrayudi.storage.extension.launchOnUiThread
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import org.ryujinx.android.GameController
import org.ryujinx.android.GameHost
import org.ryujinx.android.Logging
import org.ryujinx.android.MainActivity
import org.ryujinx.android.MotionSensorManager
import org.ryujinx.android.NativeGraphicsInterop
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.PerformanceManager
import org.ryujinx.android.PhysicalControllerManager
import org.ryujinx.android.RegionCode
import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.SystemLanguage
import java.io.File
@SuppressLint("WrongConstant")
class MainViewModel(val activity: MainActivity) {
var physicalControllerManager: PhysicalControllerManager? = null
var motionSensorManager: MotionSensorManager? = null
var gameModel: GameModel? = null
var controller: GameController? = null
var performanceManager: PerformanceManager? = null
var selected: GameModel? = null
var isMiiEditorLaunched = false
val userViewModel = UserViewModel()
val logging = Logging(this)
var firmwareVersion = ""
private var gameTimeState: MutableState<Double>? = null
private var gameFpsState: MutableState<Double>? = null
private var fifoState: MutableState<Double>? = null
private var usedMemState: MutableState<Int>? = null
private var totalMemState: MutableState<Int>? = null
private var frequenciesState: MutableList<Double>? = null
private var progress: MutableState<String>? = null
private var progressValue: MutableState<Float>? = null
private var showLoading: MutableState<Boolean>? = null
private var refreshUser: MutableState<Boolean>? = null
var gameHost: GameHost? = null
set(value) {
field = value
field?.setProgressStates(showLoading, progressValue, progress)
}
var navController: NavHostController? = null
var homeViewModel: HomeViewModel = HomeViewModel(activity, this)
init {
performanceManager = PerformanceManager(activity)
}
fun closeGame() {
RyujinxNative.jnaInstance.deviceSignalEmulationClose()
gameHost?.close()
RyujinxNative.jnaInstance.deviceCloseEmulation()
motionSensorManager?.unregister()
physicalControllerManager?.disconnect()
motionSensorManager?.setControllerId(-1)
}
fun refreshFirmwareVersion() {
firmwareVersion = RyujinxNative.jnaInstance.deviceGetInstalledFirmwareVersion()
}
fun loadGame(game: GameModel): Int {
val descriptor = game.open()
if (descriptor == 0)
return 0
val update = game.openUpdate()
if(update == -2)
{
return -2
}
gameModel = game
isMiiEditorLaunched = false
val settings = QuickSettings(activity)
var success = RyujinxNative.jnaInstance.graphicsInitialize(
enableShaderCache = settings.enableShaderCache,
enableTextureRecompression = settings.enableTextureRecompression,
rescale = settings.resScale,
backendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
)
if (!success)
return 0
val nativeHelpers = NativeHelpers.instance
val nativeInterop = NativeGraphicsInterop()
nativeInterop.VkRequiredExtensions = arrayOf(
"VK_KHR_surface", "VK_KHR_android_surface"
)
nativeInterop.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
nativeInterop.SurfaceHandle = 0
val driverViewModel = VulkanDriverViewModel(activity)
val drivers = driverViewModel.getAvailableDrivers()
var driverHandle = 0L
if (driverViewModel.selected.isNotEmpty()) {
val metaData = drivers.find { it.driverPath == driverViewModel.selected }
metaData?.apply {
val privatePath = activity.filesDir
val privateDriverPath = privatePath.canonicalPath + "/driver/"
val pD = File(privateDriverPath)
if (pD.exists())
pD.deleteRecursively()
pD.mkdirs()
val driver = File(driverViewModel.selected)
val parent = driver.parentFile
if (parent != null) {
for (file in parent.walkTopDown()) {
if (file.absolutePath == parent.absolutePath)
continue
file.copyTo(File(privateDriverPath + file.name), true)
}
}
driverHandle = NativeHelpers.instance.loadDriver(
activity.applicationInfo.nativeLibraryDir!! + "/",
privateDriverPath,
this.libraryName
)
}
}
val extensions = nativeInterop.VkRequiredExtensions
success = RyujinxNative.jnaInstance.graphicsInitializeRenderer(
extensions!!,
extensions.size,
driverHandle
)
if (!success)
return 0
val semaphore = Semaphore(1, 0)
runBlocking {
semaphore.acquire()
launchOnUiThread {
// We are only able to initialize the emulation context on the main thread
success = RyujinxNative.jnaInstance.deviceInitialize(
settings.isHostMapped,
settings.useNce,
SystemLanguage.AmericanEnglish.ordinal,
RegionCode.USA.ordinal,
settings.enableVsync,
settings.enableDocked,
settings.enablePtc,
false,
"UTC",
settings.ignoreMissingServices
)
semaphore.release()
}
semaphore.acquire()
semaphore.release()
}
if (!success)
return 0
success =
RyujinxNative.jnaInstance.deviceLoadDescriptor(descriptor, game.type.ordinal, update)
return if (success) 1 else 0
}
fun loadMiiEditor(): Boolean {
gameModel = null
isMiiEditorLaunched = true
val settings = QuickSettings(activity)
var success = RyujinxNative.jnaInstance.graphicsInitialize(
enableShaderCache = settings.enableShaderCache,
enableTextureRecompression = settings.enableTextureRecompression,
rescale = settings.resScale,
backendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
)
if (!success)
return false
val nativeHelpers = NativeHelpers.instance
val nativeInterop = NativeGraphicsInterop()
nativeInterop.VkRequiredExtensions = arrayOf(
"VK_KHR_surface", "VK_KHR_android_surface"
)
nativeInterop.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
nativeInterop.SurfaceHandle = 0
val driverViewModel = VulkanDriverViewModel(activity)
val drivers = driverViewModel.getAvailableDrivers()
var driverHandle = 0L
if (driverViewModel.selected.isNotEmpty()) {
val metaData = drivers.find { it.driverPath == driverViewModel.selected }
metaData?.apply {
val privatePath = activity.filesDir
val privateDriverPath = privatePath.canonicalPath + "/driver/"
val pD = File(privateDriverPath)
if (pD.exists())
pD.deleteRecursively()
pD.mkdirs()
val driver = File(driverViewModel.selected)
val parent = driver.parentFile
if (parent != null) {
for (file in parent.walkTopDown()) {
if (file.absolutePath == parent.absolutePath)
continue
file.copyTo(File(privateDriverPath + file.name), true)
}
}
driverHandle = NativeHelpers.instance.loadDriver(
activity.applicationInfo.nativeLibraryDir!! + "/",
privateDriverPath,
this.libraryName
)
}
}
val extensions = nativeInterop.VkRequiredExtensions
success = RyujinxNative.jnaInstance.graphicsInitializeRenderer(
extensions!!,
extensions.size,
driverHandle
)
if (!success)
return false
val semaphore = Semaphore(1, 0)
runBlocking {
semaphore.acquire()
launchOnUiThread {
// We are only able to initialize the emulation context on the main thread
success = RyujinxNative.jnaInstance.deviceInitialize(
settings.isHostMapped,
settings.useNce,
SystemLanguage.AmericanEnglish.ordinal,
RegionCode.USA.ordinal,
settings.enableVsync,
settings.enableDocked,
settings.enablePtc,
false,
"UTC",
settings.ignoreMissingServices
)
semaphore.release()
}
semaphore.acquire()
semaphore.release()
}
if (!success)
return false
success = RyujinxNative.jnaInstance.deviceLaunchMiiEditor()
return success
}
fun clearPptcCache(titleId: String) {
if (titleId.isNotEmpty()) {
val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu"
if (File(basePath).exists()) {
var caches = mutableListOf<String>()
val mainCache = basePath + "${File.separator}0"
File(mainCache).listFiles()?.forEach {
if (it.isFile && it.name.endsWith(".cache"))
caches.add(it.absolutePath)
}
val backupCache = basePath + "${File.separator}1"
File(backupCache).listFiles()?.forEach {
if (it.isFile && it.name.endsWith(".cache"))
caches.add(it.absolutePath)
}
for (path in caches)
File(path).delete()
}
}
}
fun purgeShaderCache(titleId: String) {
if (titleId.isNotEmpty()) {
val basePath = MainActivity.AppPath + "/games/$titleId/cache/shader"
if (File(basePath).exists()) {
var caches = mutableListOf<String>()
File(basePath).listFiles()?.forEach {
if (!it.isFile)
it.delete()
else {
if (it.name.endsWith(".toc") || it.name.endsWith(".data"))
caches.add(it.absolutePath)
}
}
for (path in caches)
File(path).delete()
}
}
}
fun deleteCache(titleId: String) {
fun deleteDirectory(directory: File) {
if (directory.exists() && directory.isDirectory) {
directory.listFiles()?.forEach { file ->
if (file.isDirectory) {
deleteDirectory(file)
} else {
file.delete()
}
}
directory.delete()
}
}
if (titleId.isNotEmpty()) {
val basePath = MainActivity.AppPath + "/games/$titleId/cache"
if (File(basePath).exists()) {
deleteDirectory(File(basePath))
}
}
}
fun setStatStates(
fifo: MutableState<Double>,
gameFps: MutableState<Double>,
gameTime: MutableState<Double>,
usedMem: MutableState<Int>,
totalMem: MutableState<Int>,
frequencies: MutableList<Double>
) {
fifoState = fifo
gameFpsState = gameFps
gameTimeState = gameTime
usedMemState = usedMem
totalMemState = totalMem
frequenciesState = frequencies
}
fun updateStats(
fifo: Double,
gameFps: Double,
gameTime: Double
) {
fifoState?.apply {
this.value = fifo
}
gameFpsState?.apply {
this.value = gameFps
}
gameTimeState?.apply {
this.value = gameTime
}
usedMemState?.let { usedMem ->
totalMemState?.let { totalMem ->
MainActivity.performanceMonitor.getMemoryUsage(
usedMem,
totalMem
)
}
}
frequenciesState?.let { MainActivity.performanceMonitor.getFrequencies(it) }
}
fun setGameController(controller: GameController) {
this.controller = controller
}
fun navigateToGame() {
activity.setFullScreen(true)
navController?.navigate("game")
activity.isGameRunning = true
if (QuickSettings(activity).enableMotion)
motionSensorManager?.register()
}
fun setProgressStates(
showLoading: MutableState<Boolean>,
progressValue: MutableState<Float>,
progress: MutableState<String>
) {
this.showLoading = showLoading
this.progressValue = progressValue
this.progress = progress
gameHost?.setProgressStates(showLoading, progressValue, progress)
}
}

View File

@ -0,0 +1,97 @@
package org.ryujinx.android.viewmodels
import android.app.Activity
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
class QuickSettings(val activity: Activity) {
var ignoreMissingServices: Boolean
var enablePtc: Boolean
var enableDocked: Boolean
var enableVsync: Boolean
var useNce: Boolean
var useVirtualController: Boolean
var isHostMapped: Boolean
var enableShaderCache: Boolean
var enableTextureRecompression: Boolean
var resScale: Float
var isGrid: Boolean
var useSwitchLayout: Boolean
var enableMotion: Boolean
var enablePerformanceMode: Boolean
var controllerStickSensitivity: Float
// Logs
var enableDebugLogs: Boolean
var enableStubLogs: Boolean
var enableInfoLogs: Boolean
var enableWarningLogs: Boolean
var enableErrorLogs: Boolean
var enableGuestLogs: Boolean
var enableAccessLogs: Boolean
var enableTraceLogs: Boolean
var enableGraphicsLogs: Boolean
private var sharedPref: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity)
init {
isHostMapped = sharedPref.getBoolean("isHostMapped", true)
useNce = sharedPref.getBoolean("useNce", true)
enableVsync = sharedPref.getBoolean("enableVsync", true)
enableDocked = sharedPref.getBoolean("enableDocked", true)
enablePtc = sharedPref.getBoolean("enablePtc", true)
ignoreMissingServices = sharedPref.getBoolean("ignoreMissingServices", false)
enableShaderCache = sharedPref.getBoolean("enableShaderCache", true)
enableTextureRecompression = sharedPref.getBoolean("enableTextureRecompression", false)
resScale = sharedPref.getFloat("resScale", 1f)
useVirtualController = sharedPref.getBoolean("useVirtualController", true)
isGrid = sharedPref.getBoolean("isGrid", true)
useSwitchLayout = sharedPref.getBoolean("useSwitchLayout", true)
enableMotion = sharedPref.getBoolean("enableMotion", true)
enablePerformanceMode = sharedPref.getBoolean("enablePerformanceMode", true)
controllerStickSensitivity = sharedPref.getFloat("controllerStickSensitivity", 1.0f)
enableDebugLogs = sharedPref.getBoolean("enableDebugLogs", false)
enableStubLogs = sharedPref.getBoolean("enableStubLogs", false)
enableInfoLogs = sharedPref.getBoolean("enableInfoLogs", true)
enableWarningLogs = sharedPref.getBoolean("enableWarningLogs", true)
enableErrorLogs = sharedPref.getBoolean("enableErrorLogs", true)
enableGuestLogs = sharedPref.getBoolean("enableGuestLogs", true)
enableAccessLogs = sharedPref.getBoolean("enableAccessLogs", false)
enableTraceLogs = sharedPref.getBoolean("enableStubLogs", false)
enableGraphicsLogs = sharedPref.getBoolean("enableGraphicsLogs", false)
}
fun save() {
val editor = sharedPref.edit()
editor.putBoolean("isHostMapped", isHostMapped)
editor.putBoolean("useNce", useNce)
editor.putBoolean("enableVsync", enableVsync)
editor.putBoolean("enableDocked", enableDocked)
editor.putBoolean("enablePtc", enablePtc)
editor.putBoolean("ignoreMissingServices", ignoreMissingServices)
editor.putBoolean("enableShaderCache", enableShaderCache)
editor.putBoolean("enableTextureRecompression", enableTextureRecompression)
editor.putFloat("resScale", resScale)
editor.putBoolean("useVirtualController", useVirtualController)
editor.putBoolean("isGrid", isGrid)
editor.putBoolean("useSwitchLayout", useSwitchLayout)
editor.putBoolean("enableMotion", enableMotion)
editor.putBoolean("enablePerformanceMode", enablePerformanceMode)
editor.putFloat("controllerStickSensitivity", controllerStickSensitivity)
editor.putBoolean("enableDebugLogs", enableDebugLogs)
editor.putBoolean("enableStubLogs", enableStubLogs)
editor.putBoolean("enableInfoLogs", enableInfoLogs)
editor.putBoolean("enableWarningLogs", enableWarningLogs)
editor.putBoolean("enableErrorLogs", enableErrorLogs)
editor.putBoolean("enableGuestLogs", enableGuestLogs)
editor.putBoolean("enableAccessLogs", enableAccessLogs)
editor.putBoolean("enableTraceLogs", enableTraceLogs)
editor.putBoolean("enableGraphicsLogs", enableGraphicsLogs)
editor.apply()
}
}

View File

@ -0,0 +1,287 @@
package org.ryujinx.android.viewmodels
import android.content.SharedPreferences
import androidx.compose.runtime.MutableState
import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavHostController
import androidx.preference.PreferenceManager
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.file.FileFullPath
import com.anggrayudi.storage.file.copyFileTo
import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.getAbsolutePath
import org.ryujinx.android.LogLevel
import org.ryujinx.android.MainActivity
import org.ryujinx.android.RyujinxNative
import java.io.File
import kotlin.concurrent.thread
class SettingsViewModel(var navController: NavHostController, val activity: MainActivity) {
var selectedFirmwareVersion: String = ""
private var previousFileCallback: ((requestCode: Int, files: List<DocumentFile>) -> Unit)?
private var previousFolderCallback: ((requestCode: Int, folder: DocumentFile) -> Unit)?
private var sharedPref: SharedPreferences
var selectedFirmwareFile: DocumentFile? = null
init {
sharedPref = getPreferences()
previousFolderCallback = activity.storageHelper!!.onFolderSelected
previousFileCallback = activity.storageHelper!!.onFileSelected
activity.storageHelper!!.onFolderSelected = { _, folder ->
run {
val p = folder.getAbsolutePath(activity)
val editor = sharedPref.edit()
editor?.putString("gameFolder", p)
editor?.apply()
}
}
}
private fun getPreferences(): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(activity)
}
fun initializeState(
isHostMapped: MutableState<Boolean>,
useNce: MutableState<Boolean>,
enableVsync: MutableState<Boolean>,
enableDocked: MutableState<Boolean>,
enablePtc: MutableState<Boolean>,
ignoreMissingServices: MutableState<Boolean>,
enableShaderCache: MutableState<Boolean>,
enableTextureRecompression: MutableState<Boolean>,
resScale: MutableState<Float>,
useVirtualController: MutableState<Boolean>,
isGrid: MutableState<Boolean>,
useSwitchLayout: MutableState<Boolean>,
enableMotion: MutableState<Boolean>,
enablePerformanceMode: MutableState<Boolean>,
controllerStickSensitivity: MutableState<Float>,
enableDebugLogs: MutableState<Boolean>,
enableStubLogs: MutableState<Boolean>,
enableInfoLogs: MutableState<Boolean>,
enableWarningLogs: MutableState<Boolean>,
enableErrorLogs: MutableState<Boolean>,
enableGuestLogs: MutableState<Boolean>,
enableAccessLogs: MutableState<Boolean>,
enableTraceLogs: MutableState<Boolean>,
enableGraphicsLogs: MutableState<Boolean>
) {
isHostMapped.value = sharedPref.getBoolean("isHostMapped", true)
useNce.value = sharedPref.getBoolean("useNce", true)
enableVsync.value = sharedPref.getBoolean("enableVsync", true)
enableDocked.value = sharedPref.getBoolean("enableDocked", true)
enablePtc.value = sharedPref.getBoolean("enablePtc", true)
ignoreMissingServices.value = sharedPref.getBoolean("ignoreMissingServices", false)
enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true)
enableTextureRecompression.value =
sharedPref.getBoolean("enableTextureRecompression", false)
resScale.value = sharedPref.getFloat("resScale", 1f)
useVirtualController.value = sharedPref.getBoolean("useVirtualController", true)
isGrid.value = sharedPref.getBoolean("isGrid", true)
useSwitchLayout.value = sharedPref.getBoolean("useSwitchLayout", true)
enableMotion.value = sharedPref.getBoolean("enableMotion", true)
enablePerformanceMode.value = sharedPref.getBoolean("enablePerformanceMode", false)
controllerStickSensitivity.value = sharedPref.getFloat("controllerStickSensitivity", 1.0f)
enableDebugLogs.value = sharedPref.getBoolean("enableDebugLogs", false)
enableStubLogs.value = sharedPref.getBoolean("enableStubLogs", false)
enableInfoLogs.value = sharedPref.getBoolean("enableInfoLogs", true)
enableWarningLogs.value = sharedPref.getBoolean("enableWarningLogs", true)
enableErrorLogs.value = sharedPref.getBoolean("enableErrorLogs", true)
enableGuestLogs.value = sharedPref.getBoolean("enableGuestLogs", true)
enableAccessLogs.value = sharedPref.getBoolean("enableAccessLogs", false)
enableTraceLogs.value = sharedPref.getBoolean("enableStubLogs", false)
enableGraphicsLogs.value = sharedPref.getBoolean("enableGraphicsLogs", false)
}
fun save(
isHostMapped: MutableState<Boolean>,
useNce: MutableState<Boolean>,
enableVsync: MutableState<Boolean>,
enableDocked: MutableState<Boolean>,
enablePtc: MutableState<Boolean>,
ignoreMissingServices: MutableState<Boolean>,
enableShaderCache: MutableState<Boolean>,
enableTextureRecompression: MutableState<Boolean>,
resScale: MutableState<Float>,
useVirtualController: MutableState<Boolean>,
isGrid: MutableState<Boolean>,
useSwitchLayout: MutableState<Boolean>,
enableMotion: MutableState<Boolean>,
enablePerformanceMode: MutableState<Boolean>,
controllerStickSensitivity: MutableState<Float>,
enableDebugLogs: MutableState<Boolean>,
enableStubLogs: MutableState<Boolean>,
enableInfoLogs: MutableState<Boolean>,
enableWarningLogs: MutableState<Boolean>,
enableErrorLogs: MutableState<Boolean>,
enableGuestLogs: MutableState<Boolean>,
enableAccessLogs: MutableState<Boolean>,
enableTraceLogs: MutableState<Boolean>,
enableGraphicsLogs: MutableState<Boolean>
) {
val editor = sharedPref.edit()
editor.putBoolean("isHostMapped", isHostMapped.value)
editor.putBoolean("useNce", useNce.value)
editor.putBoolean("enableVsync", enableVsync.value)
editor.putBoolean("enableDocked", enableDocked.value)
editor.putBoolean("enablePtc", enablePtc.value)
editor.putBoolean("ignoreMissingServices", ignoreMissingServices.value)
editor.putBoolean("enableShaderCache", enableShaderCache.value)
editor.putBoolean("enableTextureRecompression", enableTextureRecompression.value)
editor.putFloat("resScale", resScale.value)
editor.putBoolean("useVirtualController", useVirtualController.value)
editor.putBoolean("isGrid", isGrid.value)
editor.putBoolean("useSwitchLayout", useSwitchLayout.value)
editor.putBoolean("enableMotion", enableMotion.value)
editor.putBoolean("enablePerformanceMode", enablePerformanceMode.value)
editor.putFloat("controllerStickSensitivity", controllerStickSensitivity.value)
editor.putBoolean("enableDebugLogs", enableDebugLogs.value)
editor.putBoolean("enableStubLogs", enableStubLogs.value)
editor.putBoolean("enableInfoLogs", enableInfoLogs.value)
editor.putBoolean("enableWarningLogs", enableWarningLogs.value)
editor.putBoolean("enableErrorLogs", enableErrorLogs.value)
editor.putBoolean("enableGuestLogs", enableGuestLogs.value)
editor.putBoolean("enableAccessLogs", enableAccessLogs.value)
editor.putBoolean("enableTraceLogs", enableTraceLogs.value)
editor.putBoolean("enableGraphicsLogs", enableGraphicsLogs.value)
editor.apply()
activity.storageHelper!!.onFolderSelected = previousFolderCallback
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Stub.ordinal, enableStubLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Warning.ordinal,
enableWarningLogs.value
)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Error.ordinal, enableErrorLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.AccessLog.ordinal,
enableAccessLogs.value
)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Guest.ordinal, enableGuestLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value)
RyujinxNative.jnaInstance.loggingEnabledGraphicsLog(enableGraphicsLogs.value)
}
fun openGameFolder() {
val path = sharedPref.getString("gameFolder", "") ?: ""
if (path.isEmpty())
activity.storageHelper?.storage?.openFolderPicker()
else
activity.storageHelper?.storage?.openFolderPicker(
activity.storageHelper!!.storage.requestCodeFolderPicker,
FileFullPath(activity, path)
)
}
fun importProdKeys() {
activity.storageHelper!!.onFileSelected = { _, files ->
run {
activity.storageHelper!!.onFileSelected = previousFileCallback
val file = files.firstOrNull()
file?.apply {
if (name == "prod.keys") {
val outputFile = File(MainActivity.AppPath + "/system")
outputFile.delete()
thread {
file.copyFileTo(
activity,
outputFile,
callback = object : FileCallback() {
})
}
}
}
}
}
activity.storageHelper?.storage?.openFilePicker()
}
fun selectFirmware(installState: MutableState<FirmwareInstallState>) {
if (installState.value != FirmwareInstallState.None)
return
activity.storageHelper!!.onFileSelected = { _, files ->
run {
activity.storageHelper!!.onFileSelected = previousFileCallback
val file = files.firstOrNull()
file?.apply {
if (extension == "xci" || extension == "zip") {
installState.value = FirmwareInstallState.Verifying
thread {
val descriptor =
activity.contentResolver.openFileDescriptor(file.uri, "rw")
descriptor?.use { d ->
selectedFirmwareVersion =
RyujinxNative.jnaInstance.deviceVerifyFirmware(
d.fd,
extension == "xci"
)
selectedFirmwareFile = file
if (selectedFirmwareVersion.isEmpty()) {
installState.value = FirmwareInstallState.Query
} else {
installState.value = FirmwareInstallState.Cancelled
}
}
}
} else {
installState.value = FirmwareInstallState.Cancelled
}
}
}
}
activity.storageHelper?.storage?.openFilePicker()
}
fun installFirmware(installState: MutableState<FirmwareInstallState>) {
if (installState.value != FirmwareInstallState.Query)
return
if (selectedFirmwareFile == null) {
installState.value = FirmwareInstallState.None
return
}
selectedFirmwareFile?.apply {
val descriptor =
activity.contentResolver.openFileDescriptor(uri, "rw")
descriptor?.use { d ->
installState.value = FirmwareInstallState.Install
thread {
try {
RyujinxNative.jnaInstance.deviceInstallFirmware(
d.fd,
extension == "xci"
)
} finally {
MainActivity.mainViewModel?.refreshFirmwareVersion()
installState.value = FirmwareInstallState.Done
}
}
}
}
}
fun clearFirmwareSelection(installState: MutableState<FirmwareInstallState>) {
selectedFirmwareFile = null
selectedFirmwareVersion = ""
installState.value = FirmwareInstallState.None
}
}
enum class FirmwareInstallState {
None,
Cancelled,
Verifying,
Query,
Install,
Done
}

View File

@ -0,0 +1,171 @@
package org.ryujinx.android.viewmodels
import android.content.Intent
import android.net.Uri
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.SimpleStorageHelper
import com.anggrayudi.storage.file.extension
import com.google.gson.Gson
import org.ryujinx.android.MainActivity
import java.io.File
import kotlin.math.max
class TitleUpdateViewModel(val titleId: String) {
private var canClose: MutableState<Boolean>? = null
private var basePath: String
private var updateJsonName = "updates.json"
private var storageHelper: SimpleStorageHelper
private var currentPaths: MutableList<String> = mutableListOf()
private var pathsState: SnapshotStateList<String>? = null
companion object {
const val UpdateRequestCode = 1002
}
fun remove(index: Int) {
if (index <= 0)
return
data?.paths?.apply {
val str = removeAt(index - 1)
Uri.parse(str)?.apply {
storageHelper.storage.context.contentResolver.releasePersistableUriPermission(
this,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
pathsState?.clear()
pathsState?.addAll(this)
currentPaths = this
}
}
fun add() {
val callBack = storageHelper.onFileSelected
storageHelper.onFileSelected = { requestCode, files ->
run {
storageHelper.onFileSelected = callBack
if (requestCode == UpdateRequestCode) {
val file = files.firstOrNull()
file?.apply {
if (file.extension == "nsp") {
storageHelper.storage.context.contentResolver.takePersistableUriPermission(
file.uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
currentPaths.add(file.uri.toString())
}
}
refreshPaths()
}
}
}
storageHelper.openFilePicker(UpdateRequestCode)
}
private fun refreshPaths() {
data?.apply {
val existingPaths = mutableListOf<String>()
currentPaths.forEach {
val uri = Uri.parse(it)
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
if (file?.exists() == true) {
existingPaths.add(it)
}
}
if (!existingPaths.contains(selected)) {
selected = ""
}
pathsState?.clear()
pathsState?.addAll(existingPaths)
paths = existingPaths
canClose?.apply {
value = true
}
}
}
fun save(
index: Int,
openDialog: MutableState<Boolean>
) {
data?.apply {
this.selected = ""
if (paths.isNotEmpty() && index > 0) {
val ind = max(index - 1, paths.count() - 1)
this.selected = paths[ind]
}
val gson = Gson()
File(basePath).mkdirs()
val metadata = TitleUpdateMetadata()
val savedUpdates = mutableListOf<String>()
currentPaths.forEach {
val uri = Uri.parse(it)
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
if (file?.exists() == true) {
savedUpdates.add(it)
}
}
metadata.paths = savedUpdates
if (selected.isNotEmpty()) {
val uri = Uri.parse(selected)
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
if (file?.exists() == true) {
metadata.selected = selected
}
} else {
metadata.selected = selected
}
val json = gson.toJson(metadata)
File("$basePath/$updateJsonName").writeText(json)
openDialog.value = false
}
}
fun setPaths(paths: SnapshotStateList<String>, canClose: MutableState<Boolean>) {
pathsState = paths
this.canClose = canClose
data?.apply {
pathsState?.clear()
pathsState?.addAll(this.paths)
}
}
var data: TitleUpdateMetadata? = null
private var jsonPath: String
init {
basePath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
jsonPath = "${basePath}/${updateJsonName}"
data = TitleUpdateMetadata()
if (File(jsonPath).exists()) {
val gson = Gson()
data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
}
currentPaths = data?.paths ?: mutableListOf()
storageHelper = MainActivity.StorageHelper!!
refreshPaths()
File("$basePath/update").deleteRecursively()
}
}
data class TitleUpdateMetadata(
var selected: String = "",
var paths: MutableList<String> = mutableListOf()
)

View File

@ -0,0 +1,77 @@
package org.ryujinx.android.viewmodels
import org.ryujinx.android.RyujinxNative
import java.util.Base64
class UserViewModel {
var openedUser = UserModel()
val userList = mutableListOf<UserModel>()
init {
refreshUsers()
}
fun refreshUsers() {
userList.clear()
val decoder = Base64.getDecoder()
openedUser = UserModel()
openedUser.id = RyujinxNative.jnaInstance.userGetOpenedUser()
if (openedUser.id.isNotEmpty()) {
openedUser.username = RyujinxNative.jnaInstance.userGetUserName(openedUser.id)
openedUser.userPicture = decoder.decode(
RyujinxNative.jnaInstance.userGetUserPicture(
openedUser.id
)
)
}
val users = RyujinxNative.jnaInstance.userGetAllUsers()
for (user in users) {
userList.add(
UserModel(
user,
RyujinxNative.jnaInstance.userGetUserName(user),
decoder.decode(
RyujinxNative.jnaInstance.userGetUserPicture(user)
)
)
)
}
}
fun openUser(userModel: UserModel) {
RyujinxNative.jnaInstance.userOpenUser(userModel.id)
refreshUsers()
}
}
data class UserModel(
var id: String = "",
var username: String = "",
var userPicture: ByteArray? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UserModel
if (id != other.id) return false
if (username != other.username) return false
if (userPicture != null) {
if (other.userPicture == null) return false
if (!userPicture.contentEquals(other.userPicture)) return false
} else if (other.userPicture != null) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + username.hashCode()
result = 31 * result + (userPicture?.contentHashCode() ?: 0)
return result
}
}

View File

@ -0,0 +1,167 @@
package org.ryujinx.android.viewmodels
import androidx.compose.runtime.MutableState
import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.openInputStream
import com.google.gson.Gson
import org.ryujinx.android.MainActivity
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipInputStream
class VulkanDriverViewModel(val activity: MainActivity) {
var selected: String = ""
companion object {
const val DriverRequestCode: Int = 1003
const val DriverFolder: String = "drivers"
}
private fun getAppPath(): String {
var appPath =
MainActivity.AppPath
appPath += "/"
return appPath
}
fun ensureDriverPath(): File {
val driverPath = getAppPath() + DriverFolder
val driverFolder = File(driverPath)
if (!driverFolder.exists())
driverFolder.mkdirs()
return driverFolder
}
fun getAvailableDrivers(): MutableList<DriverMetadata> {
val driverFolder = ensureDriverPath()
val folders = driverFolder.walkTopDown()
val drivers = mutableListOf<DriverMetadata>()
val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
if (selectedDriverFile.exists()) {
selected = selectedDriverFile.readText()
if (!File(selected).exists()) {
selected = ""
saveSelected()
}
}
val gson = Gson()
for (folder in folders) {
if (folder.isDirectory && folder.parent == driverFolder.absolutePath) {
val meta = File(folder.absolutePath + "/meta.json")
if (meta.exists()) {
val metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java)
if (metadata.name.isNotEmpty()) {
val driver = folder.absolutePath + "/${metadata.libraryName}"
metadata.driverPath = driver
if (File(driver).exists())
drivers.add(metadata)
}
}
}
}
return drivers
}
fun saveSelected() {
val driverFolder = ensureDriverPath()
val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
selectedDriverFile.writeText(selected)
}
fun removeSelected() {
if (selected.isNotEmpty()) {
val sel = File(selected)
if (sel.exists()) {
sel.parentFile?.deleteRecursively()
}
selected = ""
saveSelected()
}
}
fun add(refresh: MutableState<Boolean>) {
activity.storageHelper?.apply {
val callBack = this.onFileSelected
onFileSelected = { requestCode, files ->
run {
onFileSelected = callBack
if (requestCode == DriverRequestCode) {
val file = files.firstOrNull()
file?.apply {
val stream = file.openInputStream(storage.context)
stream?.apply {
val name = file.name?.removeSuffix("." + file.extension) ?: ""
val driverFolder = ensureDriverPath()
val extractionFolder = File(driverFolder.absolutePath + "/${name}")
extractionFolder.deleteRecursively()
extractionFolder.mkdirs()
ZipInputStream(stream).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
val filePath =
extractionFolder.absolutePath + File.separator + entry.name
if (!entry.isDirectory) {
File(filePath).delete()
val bos =
BufferedOutputStream(FileOutputStream(filePath))
val bytesIn = ByteArray(4096)
var read: Int
while (zip.read(bytesIn)
.also { read = it } != -1
) {
bos.write(bytesIn, 0, read)
}
bos.close()
} else {
val dir = File(filePath)
dir.mkdir()
}
entry = zip.nextEntry
}
}
}
}
refresh.value = true
}
}
}
openFilePicker(
DriverRequestCode,
filterMimeTypes = arrayOf("application/zip")
)
}
}
}
data class DriverMetadata(
var schemaVersion: Int = 0,
var name: String = "",
var description: String = "",
var author: String = "",
var packageVersion: String = "",
var vendor: String = "",
var driverVersion: String = "",
var minApi: Int = 0,
var libraryName: String = "",
var driverPath: String = ""
)

View File

@ -0,0 +1,142 @@
package org.ryujinx.android.views
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import org.ryujinx.android.viewmodels.DlcItem
import org.ryujinx.android.viewmodels.DlcViewModel
class DlcViews {
companion object {
@Composable
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>) {
val viewModel = DlcViewModel(titleId)
var dlcList = remember {
mutableListOf<DlcItem>()
}
viewModel.data?.apply {
dlcList.clear()
}
var refresh = remember {
mutableStateOf(true)
}
Column(modifier = Modifier.padding(16.dp)) {
Column {
Row(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "DLC for ${name}",
textAlign = TextAlign.Center,
modifier = Modifier.align(
Alignment.CenterVertically
)
)
}
Surface(
modifier = Modifier
.padding(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
shape = MaterialTheme.shapes.medium
) {
if (refresh.value) {
dlcList.clear()
dlcList.addAll(viewModel.getDlc())
refresh.value = false
}
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
) {
items(dlcList) { dlcItem ->
dlcItem.apply {
Row(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Checkbox(
checked = (dlcItem.isEnabled.value),
onCheckedChange = { dlcItem.isEnabled.value = it })
Text(
text = dlcItem.name,
modifier = Modifier
.align(Alignment.CenterVertically)
.wrapContentWidth(Alignment.Start)
.fillMaxWidth(0.9f)
)
IconButton(
onClick = {
viewModel.remove(dlcItem)
refresh.value = true
}) {
Icon(
Icons.Filled.Delete,
contentDescription = "remove"
)
}
}
}
}
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Row(modifier = Modifier.align(Alignment.End)) {
TextButton(
modifier = Modifier.padding(4.dp),
onClick = {
viewModel.add(refresh)
}
) {
Text("Add")
}
TextButton(
modifier = Modifier.padding(4.dp),
onClick = {
openDialog.value = false
viewModel.save(dlcList)
},
) {
Text("Save")
}
}
}
}
}
}

View File

@ -0,0 +1,403 @@
package org.ryujinx.android.views
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import compose.icons.CssGgIcons
import compose.icons.cssggicons.ToolbarBottom
import org.ryujinx.android.GameController
import org.ryujinx.android.GameHost
import org.ryujinx.android.Icons
import org.ryujinx.android.MainActivity
import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings
import kotlin.math.roundToInt
class GameViews {
companion object {
@Composable
fun Main() {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
GameView(mainViewModel = MainActivity.mainViewModel!!)
}
}
@Composable
fun GameView(mainViewModel: MainViewModel) {
Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
GameHost(context, mainViewModel)
}
)
GameOverlay(mainViewModel)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GameOverlay(mainViewModel: MainViewModel) {
Box(modifier = Modifier.fillMaxSize()) {
GameStats(mainViewModel)
val showController = remember {
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
}
val enableVsync = remember {
mutableStateOf(QuickSettings(mainViewModel.activity).enableVsync)
}
val enableMotion = remember {
mutableStateOf(QuickSettings(mainViewModel.activity).enableMotion)
}
val showMore = remember {
mutableStateOf(false)
}
val showLoading = remember {
mutableStateOf(true)
}
val progressValue = remember {
mutableStateOf(0.0f)
}
val progress = remember {
mutableStateOf("Loading")
}
mainViewModel.setProgressStates(showLoading, progressValue, progress)
// touch surface
Surface(color = Color.Transparent, modifier = Modifier
.fillMaxSize()
.padding(0.dp)
.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
if (showController.value)
continue
val change = event
.component1()
.firstOrNull()
change?.apply {
val position = this.position
when (event.type) {
PointerEventType.Press -> {
RyujinxNative.jnaInstance.inputSetTouchPoint(
position.x.roundToInt(),
position.y.roundToInt()
)
}
PointerEventType.Release -> {
RyujinxNative.jnaInstance.inputReleaseTouchPoint()
}
PointerEventType.Move -> {
RyujinxNative.jnaInstance.inputSetTouchPoint(
position.x.roundToInt(),
position.y.roundToInt()
)
}
}
}
}
}
}) {
}
if (!showLoading.value) {
GameController.Compose(mainViewModel)
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(8.dp)
) {
IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = true
}) {
Icon(
imageVector = CssGgIcons.ToolbarBottom,
contentDescription = "Open Panel"
)
}
}
if (showMore.value) {
Popup(
alignment = Alignment.BottomCenter,
onDismissRequest = { showMore.value = false }) {
Surface(
modifier = Modifier.padding(16.dp),
shape = MaterialTheme.shapes.medium
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable Motion",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(end = 16.dp)
)
Switch(checked = enableMotion.value, onCheckedChange = {
showMore.value = false
enableMotion.value = !enableMotion.value
val settings = QuickSettings(mainViewModel.activity)
settings.enableMotion = enableMotion.value
settings.save()
if (enableMotion.value)
mainViewModel.motionSensorManager?.register()
else
mainViewModel.motionSensorManager?.unregister()
})
}
Row(
modifier = Modifier.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false
showController.value = !showController.value
RyujinxNative.jnaInstance.inputReleaseTouchPoint()
mainViewModel.controller?.setVisible(showController.value)
}) {
Icon(
imageVector = Icons.videoGame(),
contentDescription = "Toggle Virtual Pad"
)
}
IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false
enableVsync.value = !enableVsync.value
RyujinxNative.jnaInstance.graphicsRendererSetVsync(
enableVsync.value
)
}) {
Icon(
imageVector = Icons.vSync(),
tint = if (enableVsync.value) Color.Green else Color.Red,
contentDescription = "Toggle VSync"
)
}
}
}
}
}
}
}
val showBackNotice = remember {
mutableStateOf(false)
}
BackHandler {
showBackNotice.value = true
}
if (showLoading.value) {
Card(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(0.5f)
.align(Alignment.Center),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = progress.value)
if (progressValue.value > -1)
LinearProgressIndicator(
progress = {
progressValue.value
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
)
else
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
)
}
}
}
if (showBackNotice.value) {
BasicAlertDialog(onDismissRequest = { showBackNotice.value = false }) {
Column {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
Column {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Are you sure you want to exit the game?")
Text(text = "All unsaved data will be lost!")
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Button(onClick = {
showBackNotice.value = false
mainViewModel.closeGame()
mainViewModel.activity.setFullScreen(false)
mainViewModel.navController?.popBackStack()
mainViewModel.activity.isGameRunning = false
}, modifier = Modifier.padding(16.dp)) {
Text(text = "Exit Game")
}
Button(onClick = {
showBackNotice.value = false
}, modifier = Modifier.padding(16.dp)) {
Text(text = "Dismiss")
}
}
}
}
}
}
}
mainViewModel.activity.uiHandler.Compose()
}
}
@Composable
fun GameStats(mainViewModel: MainViewModel) {
val fifo = remember {
mutableDoubleStateOf(0.0)
}
val gameFps = remember {
mutableDoubleStateOf(0.0)
}
val gameTime = remember {
mutableDoubleStateOf(0.0)
}
val usedMem = remember {
mutableIntStateOf(0)
}
val totalMem = remember {
mutableIntStateOf(0)
}
val frequencies = remember {
mutableListOf<Double>()
}
Surface(
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.background.copy(0.4f)
) {
CompositionLocalProvider(LocalTextStyle provides TextStyle(fontSize = 10.sp)) {
Column {
var gameTimeVal = 0.0
if (!gameTime.value.isInfinite())
gameTimeVal = gameTime.value
Text(text = "${String.format("%.3f", fifo.value)} %")
Text(text = "${String.format("%.3f", gameFps.value)} FPS")
Text(text = "${String.format("%.3f", gameTimeVal)} ms")
Box(modifier = Modifier.width(96.dp)) {
Column {
LazyColumn {
itemsIndexed(frequencies) { i, t ->
Row {
Text(
modifier = Modifier.padding(2.dp),
text = "CPU $i"
)
Spacer(Modifier.weight(1f))
Text(text = "$t MHz")
}
}
}
Row {
Text(modifier = Modifier.padding(2.dp), text = "Used")
Spacer(Modifier.weight(1f))
Text(text = "${usedMem.value} MB")
}
Row {
Text(modifier = Modifier.padding(2.dp), text = "Total")
Spacer(Modifier.weight(1f))
Text(text = "${totalMem.value} MB")
}
}
}
}
}
}
mainViewModel.setStatStates(fifo, gameFps, gameTime, usedMem, totalMem, frequencies)
}
}
}

View File

@ -0,0 +1,803 @@
package org.ryujinx.android.views
import android.content.res.Resources
import android.graphics.BitmapFactory
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBar
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.anggrayudi.storage.extension.launchOnUiThread
import org.ryujinx.android.R
import org.ryujinx.android.viewmodels.FileType
import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.HomeViewModel
import org.ryujinx.android.viewmodels.QuickSettings
import java.util.Base64
import java.util.Locale
import kotlin.concurrent.thread
import kotlin.math.roundToInt
class HomeViews {
companion object {
const val ListImageSize = 150
const val GridImageSize = 300
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun Home(
viewModel: HomeViewModel = HomeViewModel(),
navController: NavHostController? = null,
isPreview: Boolean = false
) {
viewModel.ensureReloadIfNecessary()
val showAppActions = remember { mutableStateOf(false) }
val showLoading = remember { mutableStateOf(false) }
val openTitleUpdateDialog = remember { mutableStateOf(false) }
val canClose = remember { mutableStateOf(true) }
val openDlcDialog = remember { mutableStateOf(false) }
var openAppBarExtra by remember { mutableStateOf(false) }
val showError = remember {
mutableStateOf("")
}
val selectedModel = remember {
mutableStateOf(viewModel.mainViewModel?.selected)
}
val query = remember {
mutableStateOf("")
}
var refreshUser by remember {
mutableStateOf(true)
}
var isFabVisible by remember {
mutableStateOf(true)
}
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (available.y < -1) {
isFabVisible = false
}
if (available.y > 1) {
isFabVisible = true
}
return Offset.Zero
}
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
SearchBar(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
shape = SearchBarDefaults.inputFieldShape,
query = query.value,
onQueryChange = {
query.value = it
},
onSearch = {},
active = false,
onActiveChange = {},
leadingIcon = {
Icon(
Icons.Filled.Search,
contentDescription = "Search Games"
)
},
placeholder = {
Text(text = "Ryujinx")
},
trailingIcon = {
IconButton(onClick = {
openAppBarExtra = !openAppBarExtra
}) {
if (!refreshUser) {
refreshUser = true
}
if (refreshUser)
if (viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) {
val pic =
viewModel.mainViewModel.userViewModel.openedUser.userPicture
Image(
bitmap = BitmapFactory.decodeByteArray(
pic,
0,
pic?.size ?: 0
)
.asImageBitmap(),
contentDescription = "user image",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(4.dp)
.size(52.dp)
.clip(CircleShape)
)
} else {
Icon(
Icons.Filled.Person,
contentDescription = "user"
)
}
}
}
) {
}
},
floatingActionButton = {
AnimatedVisibility(visible = isFabVisible,
enter = slideInVertically(initialOffsetY = { it * 2 }),
exit = slideOutVertically(targetOffsetY = { it * 2 })) {
FloatingActionButton(
onClick = {
viewModel.requestReload()
viewModel.ensureReloadIfNecessary()
},
shape = MaterialTheme.shapes.small
) {
Icon(Icons.Default.Refresh, contentDescription = "refresh")
}
}
}
) { contentPadding ->
Column(modifier = Modifier.padding(contentPadding)) {
val iconSize = 52.dp
AnimatedVisibility(
visible = openAppBarExtra,
)
{
Card(
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.medium
) {
Column(modifier = Modifier.padding(8.dp)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (refreshUser) {
Box(
modifier = Modifier
.border(
width = 2.dp,
color = Color(0xFF14bf00),
shape = CircleShape
)
.size(iconSize)
.padding(2.dp),
contentAlignment = Alignment.Center
) {
if (viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) {
val pic =
viewModel.mainViewModel.userViewModel.openedUser.userPicture
Image(
bitmap = BitmapFactory.decodeByteArray(
pic,
0,
pic?.size ?: 0
)
.asImageBitmap(),
contentDescription = "user image",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(4.dp)
.size(iconSize)
.clip(CircleShape)
)
} else {
Icon(
Icons.Filled.Person,
contentDescription = "user"
)
}
}
Card(
modifier = Modifier
.padding(horizontal = 4.dp)
.fillMaxWidth(0.7f),
shape = MaterialTheme.shapes.small,
) {
LazyRow {
if (viewModel.mainViewModel?.userViewModel?.userList?.isNotEmpty() == true) {
items(viewModel.mainViewModel.userViewModel.userList) { user ->
if (user.id != viewModel.mainViewModel.userViewModel.openedUser.id) {
Image(
bitmap = BitmapFactory.decodeByteArray(
user.userPicture,
0,
user.userPicture?.size ?: 0
)
.asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(4.dp)
.size(iconSize)
.clip(CircleShape)
.combinedClickable(
onClick = {
viewModel.mainViewModel.userViewModel.openUser(
user
)
refreshUser =
false
})
)
}
}
}
}
}
Box(
modifier = Modifier
.size(iconSize)
) {
IconButton(
modifier = Modifier.fillMaxSize(),
onClick = {
openAppBarExtra = false
navController?.navigate("user")
}) {
Icon(
Icons.Filled.Add,
contentDescription = "N/A"
)
}
}
}
}
TextButton(modifier = Modifier.fillMaxWidth(),
onClick = {
navController?.navigate("settings")
}
) {
Row(modifier = Modifier.fillMaxWidth()) {
Icon(
Icons.Filled.Settings,
contentDescription = "Settings"
)
Text(
text = "Settings",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 8.dp)
)
}
}
}
}
}
Box {
val list = remember {
viewModel.gameList
}
val isLoading = remember {
viewModel.isLoading
}
viewModel.filter(query.value)
if (!isPreview) {
var settings = QuickSettings(viewModel.activity!!)
if (isLoading.value) {
Box(modifier = Modifier.fillMaxSize())
{
CircularProgressIndicator(
modifier = Modifier
.width(64.dp)
.align(Alignment.Center),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant
)
}
} else {
if (settings.isGrid) {
val size =
GridImageSize / Resources.getSystem().displayMetrics.density
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = (size + 4).dp),
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
.nestedScroll(nestedScrollConnection),
horizontalArrangement = Arrangement.SpaceEvenly
) {
items(list) {
it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(Locale.getDefault())
.contains(query.value))
)
GridGameItem(
it,
viewModel,
showAppActions,
showLoading,
selectedModel,
showError
)
}
}
}
} else {
LazyColumn(Modifier.fillMaxSize()) {
items(list) {
it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(
Locale.getDefault()
)
.contains(query.value))
)
Box(modifier = Modifier.animateItemPlacement()) {
ListGameItem(
it,
viewModel,
showAppActions,
showLoading,
selectedModel,
showError
)
}
}
}
}
}
}
}
}
}
if (showLoading.value) {
BasicAlertDialog(onDismissRequest = { }) {
Card(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = "Loading")
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
)
}
}
}
}
if (openTitleUpdateDialog.value) {
BasicAlertDialog(onDismissRequest = {
openTitleUpdateDialog.value = false
}) {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog, canClose)
}
}
}
if (openDlcDialog.value) {
BasicAlertDialog(onDismissRequest = {
openDlcDialog.value = false
}) {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
DlcViews.Main(titleId, name, openDlcDialog)
}
}
}
}
if (showAppActions.value)
ModalBottomSheet(
content = {
Row(
modifier = Modifier.padding(8.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
if (showAppActions.value) {
IconButton(onClick = {
if (viewModel.mainViewModel?.selected != null) {
thread {
showLoading.value = true
val success =
viewModel.mainViewModel.loadGame(viewModel.mainViewModel.selected!!)
if (success == 1) {
launchOnUiThread {
viewModel.mainViewModel.navigateToGame()
}
} else {
if (success == -2)
showError.value =
"Error loading update. Please re-add update file"
viewModel.mainViewModel.selected!!.close()
}
showLoading.value = false
}
}
}) {
Icon(
org.ryujinx.android.Icons.playArrow(MaterialTheme.colorScheme.onSurface),
contentDescription = "Run"
)
}
val showAppMenu = remember { mutableStateOf(false) }
Box {
IconButton(onClick = {
showAppMenu.value = true
}) {
Icon(
Icons.Filled.Menu,
contentDescription = "Menu"
)
}
DropdownMenu(
expanded = showAppMenu.value,
onDismissRequest = { showAppMenu.value = false }) {
DropdownMenuItem(text = {
Text(text = "Clear PPTC Cache")
}, onClick = {
showAppMenu.value = false
viewModel.mainViewModel?.clearPptcCache(
viewModel.mainViewModel.selected?.titleId ?: ""
)
})
DropdownMenuItem(text = {
Text(text = "Purge Shader Cache")
}, onClick = {
showAppMenu.value = false
viewModel.mainViewModel?.purgeShaderCache(
viewModel.mainViewModel.selected?.titleId ?: ""
)
})
DropdownMenuItem(text = {
Text(text = "Delete All Cache")
}, onClick = {
showAppMenu.value = false
viewModel.mainViewModel?.deleteCache(
viewModel.mainViewModel.selected?.titleId ?: ""
)
})
DropdownMenuItem(text = {
Text(text = "Manage Updates")
}, onClick = {
showAppMenu.value = false
openTitleUpdateDialog.value = true
})
DropdownMenuItem(text = {
Text(text = "Manage DLC")
}, onClick = {
showAppMenu.value = false
openDlcDialog.value = true
})
}
}
}
}
},
onDismissRequest = {
showAppActions.value = false
selectedModel.value = null
}
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListGameItem(
gameModel: GameModel,
viewModel: HomeViewModel,
showAppActions: MutableState<Boolean>,
showLoading: MutableState<Boolean>,
selectedModel: MutableState<GameModel?>,
showError: MutableState<String>
) {
remember {
selectedModel
}
val color =
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
val decoder = Base64.getDecoder()
Surface(
shape = MaterialTheme.shapes.medium,
color = color,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.combinedClickable(
onClick = {
if (viewModel.mainViewModel?.selected != null) {
showAppActions.value = false
viewModel.mainViewModel.apply {
selected = null
}
selectedModel.value = null
} else if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro) {
thread {
showLoading.value = true
val success =
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success == 1) {
launchOnUiThread {
viewModel.mainViewModel?.navigateToGame()
}
} else {
if (success == -2)
showError.value =
"Error loading update. Please re-add update file"
gameModel.close()
}
showLoading.value = false
}
}
},
onLongClick = {
viewModel.mainViewModel?.selected = gameModel
showAppActions.value = true
selectedModel.value = gameModel
})
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row {
if (!gameModel.titleId.isNullOrEmpty() && (gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro)) {
if (gameModel.icon?.isNotEmpty() == true) {
val pic = decoder.decode(gameModel.icon)
val size =
ListImageSize / Resources.getSystem().displayMetrics.density
Image(
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
.asImageBitmap(),
contentDescription = gameModel.titleName + " icon",
modifier = Modifier
.padding(end = 8.dp)
.width(size.roundToInt().dp)
.height(size.roundToInt().dp)
)
} else if (gameModel.type == FileType.Nro)
NROIcon()
else NotAvailableIcon()
} else NotAvailableIcon()
Column {
Text(text = gameModel.titleName ?: "")
Text(text = gameModel.developer ?: "")
Text(text = gameModel.titleId ?: "")
}
}
Column {
Text(text = gameModel.version ?: "")
Text(text = String.format("%.3f", gameModel.fileSize))
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GridGameItem(
gameModel: GameModel,
viewModel: HomeViewModel,
showAppActions: MutableState<Boolean>,
showLoading: MutableState<Boolean>,
selectedModel: MutableState<GameModel?>,
showError: MutableState<String>
) {
remember {
selectedModel
}
val color =
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
val decoder = Base64.getDecoder()
Surface(
shape = MaterialTheme.shapes.medium,
color = color,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.combinedClickable(
onClick = {
if (viewModel.mainViewModel?.selected != null) {
showAppActions.value = false
viewModel.mainViewModel.apply {
selected = null
}
selectedModel.value = null
} else if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro) {
thread {
showLoading.value = true
val success =
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success == 1) {
launchOnUiThread {
viewModel.mainViewModel?.navigateToGame()
}
} else {
if (success == -2)
showError.value =
"Error loading update. Please re-add update file"
gameModel.close()
}
showLoading.value = false
}
}
},
onLongClick = {
viewModel.mainViewModel?.selected = gameModel
showAppActions.value = true
selectedModel.value = gameModel
})
) {
Column(modifier = Modifier.padding(4.dp)) {
if (!gameModel.titleId.isNullOrEmpty() && (gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro)) {
if (gameModel.icon?.isNotEmpty() == true) {
val pic = decoder.decode(gameModel.icon)
val size = GridImageSize / Resources.getSystem().displayMetrics.density
Image(
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
.asImageBitmap(),
contentDescription = gameModel.titleName + " icon",
modifier = Modifier
.padding(0.dp)
.clip(RoundedCornerShape(16.dp))
.align(Alignment.CenterHorizontally)
)
} else if (gameModel.type == FileType.Nro)
NROIcon()
else NotAvailableIcon()
} else NotAvailableIcon()
Text(
text = gameModel.titleName ?: "N/A",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.padding(vertical = 4.dp)
.basicMarquee()
)
}
}
}
@Composable
fun NotAvailableIcon() {
val size = ListImageSize / Resources.getSystem().displayMetrics.density
Icon(
Icons.Filled.Add,
contentDescription = "N/A",
modifier = Modifier
.padding(end = 8.dp)
.width(size.roundToInt().dp)
.height(size.roundToInt().dp)
)
}
@Composable
fun NROIcon() {
val size = ListImageSize / Resources.getSystem().displayMetrics.density
Image(
painter = painterResource(id = R.drawable.icon_nro),
contentDescription = "NRO",
modifier = Modifier
.padding(end = 8.dp)
.width(size.roundToInt().dp)
.height(size.roundToInt().dp)
)
}
}
@Preview
@Composable
fun HomePreview() {
Home(isPreview = true)
}
}

View File

@ -0,0 +1,32 @@
package org.ryujinx.android.views
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.SettingsViewModel
class MainView {
companion object {
@Composable
fun Main(mainViewModel: MainViewModel) {
val navController = rememberNavController()
mainViewModel.navController = navController
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
composable("user") { UserViews.Main(mainViewModel) }
composable("game") { GameViews.Main() }
composable("settings") {
SettingViews.Main(
SettingsViewModel(
navController,
mainViewModel.activity
), mainViewModel
)
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
package org.ryujinx.android.views
import android.net.Uri
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import org.ryujinx.android.MainActivity
import org.ryujinx.android.viewmodels.TitleUpdateViewModel
class TitleUpdateViews {
companion object {
@Composable
fun Main(
titleId: String,
name: String,
openDialog: MutableState<Boolean>,
canClose: MutableState<Boolean>
) {
val viewModel = TitleUpdateViewModel(titleId)
val selected = remember { mutableStateOf(0) }
viewModel.data?.apply {
selected.value = paths.indexOf(this.selected) + 1
}
Column(modifier = Modifier.padding(16.dp)) {
Column {
Text(text = "Updates for ${name}", textAlign = TextAlign.Center)
Surface(
modifier = Modifier
.padding(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier
.height(250.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
Row(modifier = Modifier.padding(8.dp)) {
RadioButton(
selected = (selected.value == 0),
onClick = {
selected.value = 0
})
Text(
text = "None",
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically)
)
}
val paths = remember {
mutableStateListOf<String>()
}
viewModel.setPaths(paths, canClose)
var index = 1
for (path in paths) {
val i = index
val uri = Uri.parse(path)
val file = DocumentFile.fromSingleUri(
MainActivity.mainViewModel!!.activity,
uri
)
file?.apply {
Row(modifier = Modifier.padding(8.dp)) {
RadioButton(
selected = (selected.value == i),
onClick = { selected.value = i })
Text(
text = file.name ?: "",
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically)
)
}
}
index++
}
}
}
Row(modifier = Modifier.align(Alignment.End)) {
IconButton(
onClick = {
viewModel.remove(selected.value)
}
) {
Icon(
Icons.Filled.Delete,
contentDescription = "Remove"
)
}
IconButton(
onClick = {
viewModel.add()
}
) {
Icon(
Icons.Filled.Add,
contentDescription = "Add"
)
}
}
}
Spacer(modifier = Modifier.height(18.dp))
TextButton(
modifier = Modifier.align(Alignment.End),
onClick = {
canClose.value = true
viewModel.save(selected.value, openDialog)
},
) {
Text("Save")
}
}
}
}
}

View File

@ -0,0 +1,175 @@
package org.ryujinx.android.views
import android.graphics.BitmapFactory
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import org.ryujinx.android.viewmodels.MainViewModel
class UserViews {
companion object {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun Main(viewModel: MainViewModel? = null) {
val reload = remember {
mutableStateOf(true)
}
fun refresh() {
viewModel?.userViewModel?.refreshUsers()
reload.value = true
}
LaunchedEffect(reload.value) {
reload.value = false
}
Scaffold(modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(title = {
Text(text = "Users")
},
navigationIcon = {
IconButton(onClick = {
viewModel?.navController?.popBackStack()
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
})
}) { contentPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(text = "Selected user")
Row(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (viewModel?.userViewModel?.openedUser?.id?.isNotEmpty() == true) {
val openUser = viewModel.userViewModel.openedUser
Image(
bitmap = BitmapFactory.decodeByteArray(
openUser.userPicture,
0,
openUser.userPicture?.size ?: 0
).asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(4.dp)
.size(96.dp)
.clip(CircleShape)
)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = openUser.username)
Text(text = openUser.id)
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = "Available Users")
IconButton(onClick = {
refresh()
}) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = "refresh users"
)
}
}
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 96.dp),
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
) {
if (viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
items(viewModel.userViewModel.userList) { user ->
Image(
bitmap = BitmapFactory.decodeByteArray(
user.userPicture,
0,
user.userPicture?.size ?: 0
)
.asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
.clip(CircleShape)
.align(Alignment.CenterHorizontally)
.combinedClickable(
onClick = {
viewModel.userViewModel.openUser(user)
reload.value = true
})
)
}
}
}
}
}
}
}
}
@Preview
@Composable
fun Preview() {
Main()
}
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M10.5,3A2.5,2.5 0,0 1,13 5.5L13,11h5.5a2.5,2.5 0,0 1,2.5 2.5v5a2.5,2.5 0,0 1,-2.5 2.5h-13A2.5,2.5 0,0 1,3 18.5v-13A2.5,2.5 0,0 1,5.5 3h5ZM11,13L5,13v5.5a0.5,0.5 0,0 0,0.5 0.5L11,19v-6ZM18.5,13L13,13v6h5.5a0.5,0.5 0,0 0,0.5 -0.5v-5a0.5,0.5 0,0 0,-0.5 -0.5ZM10.5,5h-5a0.5,0.5 0,0 0,-0.5 0.5L5,11h6L11,5.5a0.5,0.5 0,0 0,-0.5 -0.5ZM17.883,2.007L18,2a1,1 0,0 1,0.993 0.883L19,3v2h2a1,1 0,0 1,0.993 0.883L22,6a1,1 0,0 1,-0.883 0.993L21,7h-2v2a1,1 0,0 1,-0.883 0.993L18,10a1,1 0,0 1,-0.993 -0.883L17,9L17,7h-2a1,1 0,0 1,-0.993 -0.883L14,6a1,1 0,0 1,0.883 -0.993L15,5h2L17,3a1,1 0,0 1,0.883 -0.993L18,2l-0.117,0.007Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="255.76"
android:viewportHeight="255.76">
<group android:scaleX="0.44"
android:scaleY="0.44"
android:translateX="71.6128"
android:translateY="71.6128">
<path
android:pathData="M80.63,0V220.39H44.37c-14,0 -35.74,-20.74 -35.74,-39.13V40.13C8.63,19.19 31.36,0 49.06,0Z"
android:fillColor="#02c5e5"/>
<path
android:pathData="M175.13,35.37V255.76h36.26c14,0 35.74,-20.74 35.74,-39.13V75.5c0,-20.94 -22.73,-40.13 -40.43,-40.13Z"
android:fillColor="#ff5f55"/>
<path
android:pathData="M124.34,137.96l-1.76,7.61l-31.94,0l2.25,-7.61l31.45,0z"
android:fillColor="#02c5e5"/>
<path
android:pathData="M160.29,137.96l-2.45,7.61l-35.26,0l1.76,-7.61l35.95,0z"
android:fillColor="#ff5f55"/>
<path
android:pathData="M130.39,111.86l-1.77,7.61l-33.48,0l2.25,-7.61l33,0z"
android:fillColor="#02c5e5"/>
<path
android:pathData="M164.79,111.86l-2.45,7.61l-33.72,0l1.77,-7.61l34.4,0z"
android:fillColor="#ff5f55"/>
<path
android:pathData="M104.24,167.99l18.59,-80.22l6.95,0l-18.59,80.22l-6.95,0z"
android:fillColor="#02c5e5"/>
<path
android:pathData="M128.18,167.99l18.59,-80.22l7.12,0l-18.59,80.22l-7.12,0z"
android:fillColor="#ff5f55"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/constraint"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/leftcontainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/rightcontainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5"
app:layout_constraintGuide_begin="20dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">RyujinxAndroid</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.RyujinxAndroid" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external"
path="/" />
<external-files-path
name="external_files"
path="/" />
<files-path
name="files"
path="/" />
</paths>

View File

@ -0,0 +1,17 @@
package org.ryujinx.android
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -0,0 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.2.1' apply false
id 'com.android.library' version '8.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
}

View File

@ -0,0 +1,36 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
# Build configuration
# It needs to be set to either "debug" or "release" and can also be overriden on a per build basis
# by adding -Dorg.ryujinx.config=NAME to the command line.
org.ryujinx.config=release
# Controls stripping of symbols from libryujinx
# Setting this property to auto causes symbols to be stripped for release builds,
# but not for debug builds.
# Valid values are: ["auto", "-1", "true", "1", "false", "0"]
# Default: auto
org.ryujinx.symbols.strip=auto
# Output path of libryujinx.so
org.ryujinx.publish.path=app/src/main/jniLibs/arm64-v8a
org.ryujinx.llvm.toolchain.path=C\:\\Android\\android-sdk\\ndk\\25.1.8937393\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Fri Jun 30 08:45:05 UTC 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
src/RyujinxAndroid/gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
src/RyujinxAndroid/gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,12 @@
# LibRyujinx Gradle project
## Libraries
The following native libraries will be compiled for this project.
### OpenSSL
Version: `3.2.1`
Make sure all the prerequisites are available on your system.
You can read more about them [here](https://github.com/openssl/openssl/blob/openssl-3.2.1/INSTALL.md#prerequisites).

View File

@ -0,0 +1,139 @@
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
plugins {
id 'base'
}
// Configurable properties
// Path to the LLVM toolchain to use. This should be configured in your global gradle.properties
// See: https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home
def toolchainPath = providers.gradleProperty("org.ryujinx.llvm.toolchain.path").getOrNull()
// Path to the dotnet executable This should be configured in your global gradle.properties
// See: https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home
def dotnetExecutable = providers.gradleProperty("org.ryujinx.dotnet.bin").getOrElse("dotnet")
// Build configuration
def configuration = providers.gradleProperty("org.ryujinx.config").getOrElse("debug").toLowerCase()
// Publish directory
def publishDirectory = providers.gradleProperty("org.ryujinx.publish.path").getOrNull()
// Should the symbols be stripped from the published library?
// Per default the symbols will be stripped for release builds, but not for debug builds.
// This can be overridden using this property.
// Valid values are: ["auto", "-1", "true", "1", "false", "0"]
def stripSymbols = providers.gradleProperty("org.ryujinx.symbols.strip").getOrElse("")
//noinspection GroovyFallthrough
switch (stripSymbols) {
case "true":
case "1":
stripSymbols = true
break
case "false":
case "0":
stripSymbols = false
break
default:
stripSymbols = configuration == "release"
break
}
// Additional arguments for the dotnet publish command.
def additionalArgs = project.hasProperty("org.ryujinx.args") ? project.property("org.ryujinx.args") : ""
configuration = configuration.substring(0, 1).toUpperCase() + configuration.substring(1)
if (publishDirectory != null) {
publishDirectory = "${rootProject.projectDir}/${publishDirectory}"
}
else {
publishDirectory = libsDirectory.get().toString()
}
// Trees
ext.outputTree = fileTree("${buildDir}/publish") {
include "*/${configuration.toLowerCase()}_*/*"
builtBy 'compileLibRyujinx'
}
ext.publishTree = fileTree(publishDirectory) {
include ext.outputTree.getFiles().collect { it.getName().toLowerCase() }.findAll { it.endsWith(".so") }
builtBy 'compileLibRyujinx'
}
// Tasks
tasks.register('compileLibRyujinx', Exec) {
def projectName = "LibRyujinx"
workingDir "../../${projectName}"
def solutionFiles = fileTree("../../") {
include '**/*.cs'
include '**/*.csproj'
exclude '**/bin/**'
exclude '**/obj/**'
exclude '**/RyujinxAndroid/**'
}
inputs.files(solutionFiles)
.withPropertyName('sourceFiles')
.withPathSensitivity(PathSensitivity.RELATIVE)
.ignoreEmptyDirectories()
outputs.file("${publishDirectory}/${projectName.toLowerCase()}.so")
OperatingSystem os = DefaultNativePlatform.currentOperatingSystem
if (toolchainPath != null) {
if (os.isWindows()) {
// NOTE: This is not a typo. dotnet.exe actually uses Path instead of PATH.
environment "Path", "${toolchainPath};${providers.environmentVariable("PATH").get()}"
}
else {
environment "PATH", "${toolchainPath}:${providers.environmentVariable("PATH").get()}"
}
}
doFirst {
println "Building ${projectName} in ${configuration} mode."
println "Configuration:"
println "\tusing: ${dotnetExecutable}"
println "\tStripSymbols: ${stripSymbols}"
println "\tadditional args: ${additionalArgs.split(" ")}"
println "\tcustom LLVM toolchain path: ${toolchainPath}"
}
executable dotnetExecutable
args 'publish',
'-r', 'linux-bionic-arm64',
'-c', configuration,
"-p:DisableUnsupportedError=true",
"-p:PublishAotUsingRuntimePack=true",
"-p:StripSymbols=${stripSymbols}",
"--artifacts-path", buildDir
args additionalArgs.split(" ")
doLast {
project.sync {
from project.ext.outputTree.getFiles()
include '*.so'
into publishDirectory
rename (String originalName) -> originalName.toLowerCase()
duplicatesStrategy 'fail'
preserve {
include '.gitkeep'
include '*.so'
exclude {
project.ext.publishTree
}
}
}
}
}
tasks.register("cleanLibRyujinx", Delete) {
delete project.ext.publishTree.getFiles()
}
// Register tasks as standard lifecycle tasks
assemble.dependsOn("compileLibRyujinx")
clean.dependsOn("cleanLibRyujinx")

View File

@ -0,0 +1,54 @@
include(ExternalProject)
find_package(Perl 5 REQUIRED)
set(PROJECT_ENV "ANDROID_NDK_ROOT=${CMAKE_ANDROID_NDK}")
if (CMAKE_HOST_WIN32)
set(ProgramFiles_x86 "$ENV{ProgramFiles\(x86\)}")
# https://github.com/microsoft/vswhere/wiki/Find-MSBuild
cmake_path(APPEND VSWHERE_BIN "${ProgramFiles_x86}" "Microsoft Visual Studio" "Installer" "vswhere.exe")
# FIXME: Hardcoded architecture, no way to specify the MSVC version
execute_process(
COMMAND ${VSWHERE_BIN} "-latest" "-find" "VC\\Tools\\MSVC\\*\\bin\\Hostx64\\x64\\nmake.exe"
OUTPUT_VARIABLE NMAKE_PATHS_OUTPUT
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
string(REPLACE "\n" ";" NMAKE_PATH_LIST "${NMAKE_PATHS_OUTPUT}")
list(GET NMAKE_PATH_LIST 0 NMAKE_PATH)
cmake_path(NATIVE_PATH NMAKE_PATH NORMALIZE MAKE_COMMAND)
set(PROJECT_CFG_PREFIX ${PERL_EXECUTABLE})
# Deal with semicolon-separated lists
set(PROJECT_PATH_LIST $ENV{Path})
cmake_path(CONVERT "${ANDROID_TOOLCHAIN_ROOT}\\bin" TO_NATIVE_PATH_LIST ANDROID_TOOLCHAIN_BIN NORMALIZE)
list(PREPEND PROJECT_PATH_LIST "${ANDROID_TOOLCHAIN_BIN}")
# Replace semicolons with "|"
list(JOIN PROJECT_PATH_LIST "|" PROJECT_PATH_STRING)
# Add the modified PATH string to PROJECT_ENV
list(APPEND PROJECT_ENV "Path=${PROJECT_PATH_STRING}")
elseif (CMAKE_HOST_UNIX)
find_program(MAKE_COMMAND NAMES make REQUIRED)
list(APPEND PROJECT_ENV "PATH=${ANDROID_TOOLCHAIN_ROOT}/bin:$ENV{PATH}")
else ()
message(WARNING "Host system (${CMAKE_HOST_SYSTEM_NAME}) not supported. Treating as unix.")
find_program(MAKE_COMMAND NAMES make REQUIRED)
list(APPEND PROJECT_ENV "PATH=${ANDROID_TOOLCHAIN_ROOT}/bin:$ENV{PATH}")
endif ()
ExternalProject_Add(
openssl
GIT_REPOSITORY https://github.com/openssl/openssl.git
GIT_TAG a7e992847de83aa36be0c399c89db3fb827b0be2 # openssl-3.2.1
LIST_SEPARATOR "|"
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${PROJECT_ENV}
${PROJECT_CFG_PREFIX} <SOURCE_DIR>/Configure
android-${CMAKE_ANDROID_ARCH}
-D__ANDROID_API_=${CMAKE_SYSTEM_VERSION}
--prefix=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
--libdir=""
BUILD_COMMAND ${CMAKE_COMMAND} -E env ${PROJECT_ENV}
${MAKE_COMMAND}
INSTALL_COMMAND ${MAKE_COMMAND} install_runtime_libs
)

View File

@ -0,0 +1,21 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url 'https://jitpack.io' }
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
}
}
rootProject.name = "RyujinxAndroid"
include ':app'
include ':libryujinx'