diff --git a/.gitignore b/.gitignore
index 37b419d07..6b56addcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -173,3 +173,5 @@ PublishProfiles/
# Glade backup files
*.glade~
+/src/RyujinxAndroid/.idea
+/src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a
diff --git a/src/RyujinxAndroid/.gitignore b/src/RyujinxAndroid/.gitignore
new file mode 100644
index 000000000..aa724b770
--- /dev/null
+++ b/src/RyujinxAndroid/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/src/RyujinxAndroid/app/.gitignore b/src/RyujinxAndroid/app/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/src/RyujinxAndroid/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/build.gradle b/src/RyujinxAndroid/app/build.gradle
new file mode 100644
index 000000000..b790abf66
--- /dev/null
+++ b/src/RyujinxAndroid/app/build.gradle
@@ -0,0 +1,95 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'org.ryujinx.android'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "org.ryujinx.android"
+ minSdk 28
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary true
+ }
+
+ ndk {
+ 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'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ buildFeatures {
+ compose true
+ prefab true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.3.2'
+ }
+ packagingOptions {
+ resources {
+ excludes += '/META-INF/{AL2.0,LGPL2.1}'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path file('src/main/cpp/CMakeLists.txt')
+ version '3.22.1'
+ }
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.10.1'
+ implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
+ implementation "androidx.navigation:navigation-compose:2.6.0"
+ implementation 'androidx.activity:activity-compose:1.7.2'
+ implementation platform('androidx.compose:compose-bom:2023.06.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.google.oboe:oboe:1.7.0'
+ implementation "com.anggrayudi:storage:1.5.5"
+ implementation "androidx.preference:preference-ktx:1.2.0"
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
+ implementation 'com.google.code.gson:gson:2.10.1'
+ implementation "io.coil-kt:coil-compose:2.4.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:2023.06.00')
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/proguard-rules.pro b/src/RyujinxAndroid/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/src/RyujinxAndroid/app/proguard-rules.pro
@@ -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
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/androidTest/java/org/ryujinx/android/ExampleInstrumentedTest.kt b/src/RyujinxAndroid/app/src/androidTest/java/org/ryujinx/android/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..d6e1198c7
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/androidTest/java/org/ryujinx/android/ExampleInstrumentedTest.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/AndroidManifest.xml b/src/RyujinxAndroid/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..778370752
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/cpp/CMakeLists.txt b/src/RyujinxAndroid/app/src/main/cpp/CMakeLists.txt
new file mode 100644
index 000000000..3060d2fb4
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,55 @@
+
+# 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")
+
+# 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
+ oboe.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 )
+
+find_package (oboe REQUIRED CONFIG)
+
+# 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.
+ oboe::oboe
+ ${log-lib}
+ -lvulkan
+ -landroid)
diff --git a/src/RyujinxAndroid/app/src/main/cpp/oboe.cpp b/src/RyujinxAndroid/app/src/main/cpp/oboe.cpp
new file mode 100644
index 000000000..61b605ab5
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/oboe.cpp
@@ -0,0 +1,137 @@
+//
+// Created by Emmanuel Hansen on 6/27/2023.
+//
+
+#include "oboe.h"
+
+static int s_device_id = 0;
+
+void AudioSession::initialize() {
+}
+
+void AudioSession::destroy() {
+ stream->close();
+ delete stream;
+}
+
+void AudioSession::start() {
+ isStarted = true;
+
+ stream->requestStart();
+}
+
+void AudioSession::stop() {
+ isStarted = false;
+ stream->requestStop();
+}
+
+
+void AudioSession::read(uint64_t data, uint64_t samples) {
+ int timeout = INT32_MAX;
+
+ stream->write((void*)data, samples, timeout);
+}
+
+extern "C"
+{
+JNIEXPORT void JNICALL
+Java_org_ryujinx_android_NativeHelpers_setDeviceId(
+ JNIEnv *env,
+jobject instance,
+ jint device_id){
+ s_device_id = device_id;
+}
+
+ AudioSession* create_session(int sample_format,
+ uint sample_rate,
+ uint channel_count)
+ {
+ using namespace oboe;
+
+ AudioStreamBuilder builder;
+
+ AudioFormat format;
+
+ switch (sample_format) {
+ case 0:
+ format = AudioFormat::Invalid;
+ break;
+ case 1:
+ case 2:
+ format = AudioFormat::I16;
+ break;
+ case 3:
+ format = AudioFormat::I24;
+ break;
+ case 4:
+ format = AudioFormat::I32;
+ break;
+ case 5:
+ format = AudioFormat::Float;
+ break;
+ default:
+ std::ostringstream string;
+ string << "Invalid Format" << sample_format;
+
+ throw std::runtime_error(string.str());
+ }
+
+ auto session = new AudioSession();
+ session->initialize();
+
+ session->format = format;
+ session->channelCount = channel_count;
+
+ builder.setDirection(Direction::Output)
+ ->setPerformanceMode(PerformanceMode::LowLatency)
+ ->setSharingMode(SharingMode::Shared)
+ ->setFormat(format)
+ ->setChannelCount(channel_count)
+ ->setSampleRate(sample_rate);
+ AudioStream* stream;
+ if(builder.openStream(&stream) != oboe::Result::OK)
+ {
+ delete session;
+ return nullptr;
+ }
+ session->stream = stream;
+
+ return session;
+ }
+
+ void start_session(AudioSession* session)
+ {
+ session->start();
+ }
+
+ void stop_session(AudioSession* session)
+ {
+ session->stop();
+ }
+
+ void set_session_volume(AudioSession* session, float volume)
+ {
+ session->volume = volume;
+ }
+
+ float get_session_volume(AudioSession* session)
+ {
+ return session->volume;
+ }
+
+ void close_session(AudioSession* session)
+ {
+ session->destroy();
+
+ delete session;
+ }
+
+ bool is_playing(AudioSession* session) {
+ return session->isStarted;
+ }
+
+ void write_to_session(AudioSession* session, uint64_t data, uint64_t samples)
+ {
+ session->read(data, samples);
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/cpp/oboe.h b/src/RyujinxAndroid/app/src/main/cpp/oboe.h
new file mode 100644
index 000000000..0d6559417
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/oboe.h
@@ -0,0 +1,29 @@
+//
+// Created by Emmanuel Hansen on 6/27/2023.
+//
+
+#ifndef RYUJINXNATIVE_OBOE_H
+#define RYUJINXNATIVE_OBOE_H
+
+#include
+#include
+#include
+#include
+#include
+
+class AudioSession {
+public:
+ oboe::AudioStream* stream;
+ float volume = 1.0f;
+ bool isStarted;
+ oboe::AudioFormat format;
+ uint channelCount;
+
+ void initialize();
+ void destroy();
+ void start();
+ void stop();
+ void read(uint64_t data, uint64_t samples);
+};
+
+#endif //RYUJINXNATIVE_OBOE_H
diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
new file mode 100644
index 000000000..2077c6fef
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
@@ -0,0 +1,39 @@
+//
+// Created by Emmanuel Hansen on 6/19/2023.
+//
+
+#ifndef RYUJINXNATIVE_RYUIJNX_H
+#define RYUJINXNATIVE_RYUIJNX_H
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "vulkan_wrapper.h"
+#include
+#include
+
+// 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;
+
+#endif //RYUJINXNATIVE_RYUIJNX_H
diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
new file mode 100644
index 000000000..0c072f8f3
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
@@ -0,0 +1,123 @@
+// 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"
+
+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);
+ }
+
+JNIEXPORT jlong JNICALL
+Java_org_ryujinx_android_NativeHelpers_createSurface(
+ JNIEnv *env,
+ jobject instance,
+ jlong vulkanInstance,
+ jlong window) {
+ auto nativeWindow = (ANativeWindow *) window;
+
+ if (nativeWindow != NULL)
+ return -1;
+ VkSurfaceKHR surface;
+ auto vkInstance = VkInstance(vulkanInstance);
+ auto fpCreateAndroidSurfaceKHR =
+ reinterpret_cast(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 (jlong)surface;
+}
+
+JNIEXPORT void JNICALL
+Java_org_ryujinx_android_NativeHelpers_attachCurrentThread(
+ JNIEnv *env,
+ jobject instance) {
+ JavaVM* jvm = NULL;
+ env->GetJavaVM(&jvm);
+
+ if(jvm != NULL)
+ jvm->AttachCurrentThread(&env, NULL);
+}
+
+JNIEXPORT void JNICALL
+Java_org_ryujinx_android_NativeHelpers_detachCurrentThread(
+ JNIEnv *env,
+ jobject instance) {
+ JavaVM* jvm = NULL;
+ env->GetJavaVM(&jvm);
+
+ if(jvm != NULL)
+ jvm->DetachCurrentThread();
+}
+
+long createSurface(long native_surface, long instance)
+{
+ auto nativeWindow = (ANativeWindow *) native_surface;
+ VkSurfaceKHR surface;
+ auto vkInstance = (VkInstance)instance;
+ auto fpCreateAndroidSurfaceKHR =
+ reinterpret_cast(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;
+}
+
+
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/cpp/vulkan_wrapper.cpp b/src/RyujinxAndroid/app/src/main/cpp/vulkan_wrapper.cpp
new file mode 100644
index 000000000..f186c8504
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/vulkan_wrapper.cpp
@@ -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
+
+int InitVulkan(void) {
+ void* libvulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
+ if (!libvulkan)
+ return 0;
+
+ // Vulkan supported, set function addresses
+ vkCreateInstance = reinterpret_cast(dlsym(libvulkan, "vkCreateInstance"));
+ vkDestroyInstance = reinterpret_cast(dlsym(libvulkan, "vkDestroyInstance"));
+ vkEnumeratePhysicalDevices = reinterpret_cast(dlsym(libvulkan, "vkEnumeratePhysicalDevices"));
+ vkGetPhysicalDeviceFeatures = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceFeatures"));
+ vkGetPhysicalDeviceFormatProperties = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceFormatProperties"));
+ vkGetPhysicalDeviceImageFormatProperties = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceImageFormatProperties"));
+ vkGetPhysicalDeviceProperties = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceProperties"));
+ vkGetPhysicalDeviceQueueFamilyProperties = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceQueueFamilyProperties"));
+ vkGetPhysicalDeviceMemoryProperties = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceMemoryProperties"));
+ vkGetInstanceProcAddr = reinterpret_cast(dlsym(libvulkan, "vkGetInstanceProcAddr"));
+ vkGetDeviceProcAddr = reinterpret_cast(dlsym(libvulkan, "vkGetDeviceProcAddr"));
+ vkCreateDevice = reinterpret_cast(dlsym(libvulkan, "vkCreateDevice"));
+ vkDestroyDevice = reinterpret_cast(dlsym(libvulkan, "vkDestroyDevice"));
+ vkEnumerateInstanceExtensionProperties = reinterpret_cast(dlsym(libvulkan, "vkEnumerateInstanceExtensionProperties"));
+ vkEnumerateDeviceExtensionProperties = reinterpret_cast(dlsym(libvulkan, "vkEnumerateDeviceExtensionProperties"));
+ vkEnumerateInstanceLayerProperties = reinterpret_cast(dlsym(libvulkan, "vkEnumerateInstanceLayerProperties"));
+ vkEnumerateDeviceLayerProperties = reinterpret_cast(dlsym(libvulkan, "vkEnumerateDeviceLayerProperties"));
+ vkGetDeviceQueue = reinterpret_cast(dlsym(libvulkan, "vkGetDeviceQueue"));
+ vkQueueSubmit = reinterpret_cast(dlsym(libvulkan, "vkQueueSubmit"));
+ vkQueueWaitIdle = reinterpret_cast(dlsym(libvulkan, "vkQueueWaitIdle"));
+ vkDeviceWaitIdle = reinterpret_cast(dlsym(libvulkan, "vkDeviceWaitIdle"));
+ vkAllocateMemory = reinterpret_cast(dlsym(libvulkan, "vkAllocateMemory"));
+ vkFreeMemory = reinterpret_cast(dlsym(libvulkan, "vkFreeMemory"));
+ vkMapMemory = reinterpret_cast(dlsym(libvulkan, "vkMapMemory"));
+ vkUnmapMemory = reinterpret_cast(dlsym(libvulkan, "vkUnmapMemory"));
+ vkFlushMappedMemoryRanges = reinterpret_cast(dlsym(libvulkan, "vkFlushMappedMemoryRanges"));
+ vkInvalidateMappedMemoryRanges = reinterpret_cast(dlsym(libvulkan, "vkInvalidateMappedMemoryRanges"));
+ vkGetDeviceMemoryCommitment = reinterpret_cast(dlsym(libvulkan, "vkGetDeviceMemoryCommitment"));
+ vkBindBufferMemory = reinterpret_cast(dlsym(libvulkan, "vkBindBufferMemory"));
+ vkBindImageMemory = reinterpret_cast(dlsym(libvulkan, "vkBindImageMemory"));
+ vkGetBufferMemoryRequirements = reinterpret_cast(dlsym(libvulkan, "vkGetBufferMemoryRequirements"));
+ vkGetImageMemoryRequirements = reinterpret_cast(dlsym(libvulkan, "vkGetImageMemoryRequirements"));
+ vkGetImageSparseMemoryRequirements = reinterpret_cast(dlsym(libvulkan, "vkGetImageSparseMemoryRequirements"));
+ vkGetPhysicalDeviceSparseImageFormatProperties = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceSparseImageFormatProperties"));
+ vkQueueBindSparse = reinterpret_cast(dlsym(libvulkan, "vkQueueBindSparse"));
+ vkCreateFence = reinterpret_cast(dlsym(libvulkan, "vkCreateFence"));
+ vkDestroyFence = reinterpret_cast(dlsym(libvulkan, "vkDestroyFence"));
+ vkResetFences = reinterpret_cast(dlsym(libvulkan, "vkResetFences"));
+ vkGetFenceStatus = reinterpret_cast(dlsym(libvulkan, "vkGetFenceStatus"));
+ vkWaitForFences = reinterpret_cast(dlsym(libvulkan, "vkWaitForFences"));
+ vkCreateSemaphore = reinterpret_cast(dlsym(libvulkan, "vkCreateSemaphore"));
+ vkDestroySemaphore = reinterpret_cast(dlsym(libvulkan, "vkDestroySemaphore"));
+ vkCreateEvent = reinterpret_cast(dlsym(libvulkan, "vkCreateEvent"));
+ vkDestroyEvent = reinterpret_cast(dlsym(libvulkan, "vkDestroyEvent"));
+ vkGetEventStatus = reinterpret_cast(dlsym(libvulkan, "vkGetEventStatus"));
+ vkSetEvent = reinterpret_cast(dlsym(libvulkan, "vkSetEvent"));
+ vkResetEvent = reinterpret_cast(dlsym(libvulkan, "vkResetEvent"));
+ vkCreateQueryPool = reinterpret_cast(dlsym(libvulkan, "vkCreateQueryPool"));
+ vkDestroyQueryPool = reinterpret_cast(dlsym(libvulkan, "vkDestroyQueryPool"));
+ vkGetQueryPoolResults = reinterpret_cast(dlsym(libvulkan, "vkGetQueryPoolResults"));
+ vkCreateBuffer = reinterpret_cast(dlsym(libvulkan, "vkCreateBuffer"));
+ vkDestroyBuffer = reinterpret_cast(dlsym(libvulkan, "vkDestroyBuffer"));
+ vkCreateBufferView = reinterpret_cast(dlsym(libvulkan, "vkCreateBufferView"));
+ vkDestroyBufferView = reinterpret_cast(dlsym(libvulkan, "vkDestroyBufferView"));
+ vkCreateImage = reinterpret_cast(dlsym(libvulkan, "vkCreateImage"));
+ vkDestroyImage = reinterpret_cast(dlsym(libvulkan, "vkDestroyImage"));
+ vkGetImageSubresourceLayout = reinterpret_cast(dlsym(libvulkan, "vkGetImageSubresourceLayout"));
+ vkCreateImageView = reinterpret_cast(dlsym(libvulkan, "vkCreateImageView"));
+ vkDestroyImageView = reinterpret_cast(dlsym(libvulkan, "vkDestroyImageView"));
+ vkCreateShaderModule = reinterpret_cast(dlsym(libvulkan, "vkCreateShaderModule"));
+ vkDestroyShaderModule = reinterpret_cast(dlsym(libvulkan, "vkDestroyShaderModule"));
+ vkCreatePipelineCache = reinterpret_cast(dlsym(libvulkan, "vkCreatePipelineCache"));
+ vkDestroyPipelineCache = reinterpret_cast(dlsym(libvulkan, "vkDestroyPipelineCache"));
+ vkGetPipelineCacheData = reinterpret_cast(dlsym(libvulkan, "vkGetPipelineCacheData"));
+ vkMergePipelineCaches = reinterpret_cast(dlsym(libvulkan, "vkMergePipelineCaches"));
+ vkCreateGraphicsPipelines = reinterpret_cast(dlsym(libvulkan, "vkCreateGraphicsPipelines"));
+ vkCreateComputePipelines = reinterpret_cast(dlsym(libvulkan, "vkCreateComputePipelines"));
+ vkDestroyPipeline = reinterpret_cast(dlsym(libvulkan, "vkDestroyPipeline"));
+ vkCreatePipelineLayout = reinterpret_cast(dlsym(libvulkan, "vkCreatePipelineLayout"));
+ vkDestroyPipelineLayout = reinterpret_cast(dlsym(libvulkan, "vkDestroyPipelineLayout"));
+ vkCreateSampler = reinterpret_cast(dlsym(libvulkan, "vkCreateSampler"));
+ vkDestroySampler = reinterpret_cast(dlsym(libvulkan, "vkDestroySampler"));
+ vkCreateDescriptorSetLayout = reinterpret_cast(dlsym(libvulkan, "vkCreateDescriptorSetLayout"));
+ vkDestroyDescriptorSetLayout = reinterpret_cast(dlsym(libvulkan, "vkDestroyDescriptorSetLayout"));
+ vkCreateDescriptorPool = reinterpret_cast(dlsym(libvulkan, "vkCreateDescriptorPool"));
+ vkDestroyDescriptorPool = reinterpret_cast(dlsym(libvulkan, "vkDestroyDescriptorPool"));
+ vkResetDescriptorPool = reinterpret_cast(dlsym(libvulkan, "vkResetDescriptorPool"));
+ vkAllocateDescriptorSets = reinterpret_cast(dlsym(libvulkan, "vkAllocateDescriptorSets"));
+ vkFreeDescriptorSets = reinterpret_cast(dlsym(libvulkan, "vkFreeDescriptorSets"));
+ vkUpdateDescriptorSets = reinterpret_cast(dlsym(libvulkan, "vkUpdateDescriptorSets"));
+ vkCreateFramebuffer = reinterpret_cast(dlsym(libvulkan, "vkCreateFramebuffer"));
+ vkDestroyFramebuffer = reinterpret_cast(dlsym(libvulkan, "vkDestroyFramebuffer"));
+ vkCreateRenderPass = reinterpret_cast(dlsym(libvulkan, "vkCreateRenderPass"));
+ vkDestroyRenderPass = reinterpret_cast(dlsym(libvulkan, "vkDestroyRenderPass"));
+ vkGetRenderAreaGranularity = reinterpret_cast(dlsym(libvulkan, "vkGetRenderAreaGranularity"));
+ vkCreateCommandPool = reinterpret_cast(dlsym(libvulkan, "vkCreateCommandPool"));
+ vkDestroyCommandPool = reinterpret_cast(dlsym(libvulkan, "vkDestroyCommandPool"));
+ vkResetCommandPool = reinterpret_cast(dlsym(libvulkan, "vkResetCommandPool"));
+ vkAllocateCommandBuffers = reinterpret_cast(dlsym(libvulkan, "vkAllocateCommandBuffers"));
+ vkFreeCommandBuffers = reinterpret_cast(dlsym(libvulkan, "vkFreeCommandBuffers"));
+ vkBeginCommandBuffer = reinterpret_cast(dlsym(libvulkan, "vkBeginCommandBuffer"));
+ vkEndCommandBuffer = reinterpret_cast(dlsym(libvulkan, "vkEndCommandBuffer"));
+ vkResetCommandBuffer = reinterpret_cast(dlsym(libvulkan, "vkResetCommandBuffer"));
+ vkCmdBindPipeline = reinterpret_cast(dlsym(libvulkan, "vkCmdBindPipeline"));
+ vkCmdSetViewport = reinterpret_cast(dlsym(libvulkan, "vkCmdSetViewport"));
+ vkCmdSetScissor = reinterpret_cast(dlsym(libvulkan, "vkCmdSetScissor"));
+ vkCmdSetLineWidth = reinterpret_cast(dlsym(libvulkan, "vkCmdSetLineWidth"));
+ vkCmdSetDepthBias = reinterpret_cast(dlsym(libvulkan, "vkCmdSetDepthBias"));
+ vkCmdSetBlendConstants = reinterpret_cast(dlsym(libvulkan, "vkCmdSetBlendConstants"));
+ vkCmdSetDepthBounds = reinterpret_cast(dlsym(libvulkan, "vkCmdSetDepthBounds"));
+ vkCmdSetStencilCompareMask = reinterpret_cast(dlsym(libvulkan, "vkCmdSetStencilCompareMask"));
+ vkCmdSetStencilWriteMask = reinterpret_cast(dlsym(libvulkan, "vkCmdSetStencilWriteMask"));
+ vkCmdSetStencilReference = reinterpret_cast(dlsym(libvulkan, "vkCmdSetStencilReference"));
+ vkCmdBindDescriptorSets = reinterpret_cast(dlsym(libvulkan, "vkCmdBindDescriptorSets"));
+ vkCmdBindIndexBuffer = reinterpret_cast(dlsym(libvulkan, "vkCmdBindIndexBuffer"));
+ vkCmdBindVertexBuffers = reinterpret_cast(dlsym(libvulkan, "vkCmdBindVertexBuffers"));
+ vkCmdDraw = reinterpret_cast(dlsym(libvulkan, "vkCmdDraw"));
+ vkCmdDrawIndexed = reinterpret_cast(dlsym(libvulkan, "vkCmdDrawIndexed"));
+ vkCmdDrawIndirect = reinterpret_cast(dlsym(libvulkan, "vkCmdDrawIndirect"));
+ vkCmdDrawIndexedIndirect = reinterpret_cast(dlsym(libvulkan, "vkCmdDrawIndexedIndirect"));
+ vkCmdDispatch = reinterpret_cast(dlsym(libvulkan, "vkCmdDispatch"));
+ vkCmdDispatchIndirect = reinterpret_cast(dlsym(libvulkan, "vkCmdDispatchIndirect"));
+ vkCmdCopyBuffer = reinterpret_cast(dlsym(libvulkan, "vkCmdCopyBuffer"));
+ vkCmdCopyImage = reinterpret_cast(dlsym(libvulkan, "vkCmdCopyImage"));
+ vkCmdBlitImage = reinterpret_cast(dlsym(libvulkan, "vkCmdBlitImage"));
+ vkCmdCopyBufferToImage = reinterpret_cast(dlsym(libvulkan, "vkCmdCopyBufferToImage"));
+ vkCmdCopyImageToBuffer = reinterpret_cast(dlsym(libvulkan, "vkCmdCopyImageToBuffer"));
+ vkCmdUpdateBuffer = reinterpret_cast(dlsym(libvulkan, "vkCmdUpdateBuffer"));
+ vkCmdFillBuffer = reinterpret_cast(dlsym(libvulkan, "vkCmdFillBuffer"));
+ vkCmdClearColorImage = reinterpret_cast(dlsym(libvulkan, "vkCmdClearColorImage"));
+ vkCmdClearDepthStencilImage = reinterpret_cast(dlsym(libvulkan, "vkCmdClearDepthStencilImage"));
+ vkCmdClearAttachments = reinterpret_cast(dlsym(libvulkan, "vkCmdClearAttachments"));
+ vkCmdResolveImage = reinterpret_cast(dlsym(libvulkan, "vkCmdResolveImage"));
+ vkCmdSetEvent = reinterpret_cast(dlsym(libvulkan, "vkCmdSetEvent"));
+ vkCmdResetEvent = reinterpret_cast(dlsym(libvulkan, "vkCmdResetEvent"));
+ vkCmdWaitEvents = reinterpret_cast(dlsym(libvulkan, "vkCmdWaitEvents"));
+ vkCmdPipelineBarrier = reinterpret_cast(dlsym(libvulkan, "vkCmdPipelineBarrier"));
+ vkCmdBeginQuery = reinterpret_cast(dlsym(libvulkan, "vkCmdBeginQuery"));
+ vkCmdEndQuery = reinterpret_cast(dlsym(libvulkan, "vkCmdEndQuery"));
+ vkCmdResetQueryPool = reinterpret_cast(dlsym(libvulkan, "vkCmdResetQueryPool"));
+ vkCmdWriteTimestamp = reinterpret_cast(dlsym(libvulkan, "vkCmdWriteTimestamp"));
+ vkCmdCopyQueryPoolResults = reinterpret_cast(dlsym(libvulkan, "vkCmdCopyQueryPoolResults"));
+ vkCmdPushConstants = reinterpret_cast(dlsym(libvulkan, "vkCmdPushConstants"));
+ vkCmdBeginRenderPass = reinterpret_cast(dlsym(libvulkan, "vkCmdBeginRenderPass"));
+ vkCmdNextSubpass = reinterpret_cast(dlsym(libvulkan, "vkCmdNextSubpass"));
+ vkCmdEndRenderPass = reinterpret_cast(dlsym(libvulkan, "vkCmdEndRenderPass"));
+ vkCmdExecuteCommands = reinterpret_cast(dlsym(libvulkan, "vkCmdExecuteCommands"));
+ vkDestroySurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkDestroySurfaceKHR"));
+ vkGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceSupportKHR"));
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
+ vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceFormatsKHR"));
+ vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceSurfacePresentModesKHR"));
+ vkCreateSwapchainKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateSwapchainKHR"));
+ vkDestroySwapchainKHR = reinterpret_cast(dlsym(libvulkan, "vkDestroySwapchainKHR"));
+ vkGetSwapchainImagesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetSwapchainImagesKHR"));
+ vkAcquireNextImageKHR = reinterpret_cast(dlsym(libvulkan, "vkAcquireNextImageKHR"));
+ vkQueuePresentKHR = reinterpret_cast(dlsym(libvulkan, "vkQueuePresentKHR"));
+ vkGetPhysicalDeviceDisplayPropertiesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceDisplayPropertiesKHR"));
+ vkGetPhysicalDeviceDisplayPlanePropertiesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR"));
+ vkGetDisplayPlaneSupportedDisplaysKHR = reinterpret_cast(dlsym(libvulkan, "vkGetDisplayPlaneSupportedDisplaysKHR"));
+ vkGetDisplayModePropertiesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetDisplayModePropertiesKHR"));
+ vkCreateDisplayModeKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateDisplayModeKHR"));
+ vkGetDisplayPlaneCapabilitiesKHR = reinterpret_cast(dlsym(libvulkan, "vkGetDisplayPlaneCapabilitiesKHR"));
+ vkCreateDisplayPlaneSurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateDisplayPlaneSurfaceKHR"));
+ vkCreateSharedSwapchainsKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateSharedSwapchainsKHR"));
+
+#ifdef VK_USE_PLATFORM_XLIB_KHR
+ vkCreateXlibSurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateXlibSurfaceKHR"));
+ vkGetPhysicalDeviceXlibPresentationSupportKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceXlibPresentationSupportKHR"));
+#endif
+
+#ifdef VK_USE_PLATFORM_XCB_KHR
+ vkCreateXcbSurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateXcbSurfaceKHR"));
+ vkGetPhysicalDeviceXcbPresentationSupportKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceXcbPresentationSupportKHR"));
+#endif
+
+#ifdef VK_USE_PLATFORM_WAYLAND_KHR
+ vkCreateWaylandSurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateWaylandSurfaceKHR"));
+ vkGetPhysicalDeviceWaylandPresentationSupportKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceWaylandPresentationSupportKHR"));
+#endif
+
+#ifdef VK_USE_PLATFORM_MIR_KHR
+ vkCreateMirSurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateMirSurfaceKHR"));
+ vkGetPhysicalDeviceMirPresentationSupportKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceMirPresentationSupportKHR"));
+#endif
+
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+ vkCreateAndroidSurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateAndroidSurfaceKHR"));
+#endif
+
+#ifdef VK_USE_PLATFORM_WIN32_KHR
+ vkCreateWin32SurfaceKHR = reinterpret_cast(dlsym(libvulkan, "vkCreateWin32SurfaceKHR"));
+ vkGetPhysicalDeviceWin32PresentationSupportKHR = reinterpret_cast(dlsym(libvulkan, "vkGetPhysicalDeviceWin32PresentationSupportKHR"));
+#endif
+#ifdef USE_DEBUG_EXTENTIONS
+ vkCreateDebugReportCallbackEXT = reinterpret_cast(dlsym(libvulkan, "vkCreateDebugReportCallbackEXT"));
+ vkDestroyDebugReportCallbackEXT = reinterpret_cast(dlsym(libvulkan, "vkDestroyDebugReportCallbackEXT"));
+ vkDebugReportMessageEXT = reinterpret_cast(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;
+
diff --git a/src/RyujinxAndroid/app/src/main/cpp/vulkan_wrapper.h b/src/RyujinxAndroid/app/src/main/cpp/vulkan_wrapper.h
new file mode 100644
index 000000000..5d34c0c8d
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/vulkan_wrapper.h
@@ -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
+
+/* 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
+// VK_EXT_debug_report
+extern PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
+extern PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
+extern PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT;
+#endif
+
+
+#endif // VULKAN_WRAPPER_H
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt
new file mode 100644
index 000000000..08c3af72f
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt
@@ -0,0 +1,374 @@
+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.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.GraphicsLayerScope
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.flowWithLifecycle
+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.RadialGamePadTheme
+import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
+import com.swordfish.radialgamepad.library.event.Event
+import com.swordfish.radialgamepad.library.event.GestureType
+import com.swordfish.radialgamepad.library.haptics.HapticConfig
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.subscribe
+import kotlinx.coroutines.launch
+
+typealias GamePad = RadialGamePad
+typealias GamePadConfig = RadialGamePadConfig
+
+class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) {
+ var leftGamePad: GamePad
+ var rightGamePad: GamePad
+ var controllerId: String? = null
+
+ 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
+ }
+
+ @Composable
+ fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit
+ {
+
+ AndroidView(
+ modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)})
+
+ lifecycleScope.apply {
+ lifecycleScope.launch {
+ var events = merge(leftGamePad.events(),rightGamePad.events())
+ .shareIn(lifecycleScope, SharingStarted.Lazily)
+
+ events.safeCollect {
+ handleEvent(it)
+ };
+ }
+ }
+ }
+
+ private fun Create(context: Context) : View
+ {
+ var inflator = LayoutInflater.from(context);
+ var view = inflator.inflate(R.layout.game_layout, null)
+ view.findViewById(R.id.leftcontainer)!!.addView(leftGamePad);
+ view.findViewById(R.id.rightcontainer)!!.addView(rightGamePad);
+
+ return view as View
+ }
+
+ fun connect(){
+ if(controllerId.isNullOrEmpty())
+ controllerId = ryujinxNative.inputConnectGamepad(0)
+ }
+
+ private fun handleEvent(ev: Event) {
+ if(controllerId.isNullOrEmpty())
+ controllerId = ryujinxNative.inputConnectGamepad(0)
+
+ controllerId?.apply {
+ when (ev) {
+ is Event.Button -> {
+ var action = ev.action
+ when (action) {
+ KeyEvent.ACTION_UP -> {
+ ryujinxNative.inputSetButtonReleased(ev.id, this)
+ }
+ KeyEvent.ACTION_DOWN -> {
+ ryujinxNative.inputSetButtonPressed(ev.id, this)
+ }
+ }
+ }
+ is Event.Direction -> {
+ var direction = ev.id
+
+ when(direction) {
+ GamePadButtonInputId.DpadUp.ordinal -> {
+ if (ev.xAxis > 0)
+ {
+ ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
+ }
+ else if (ev.xAxis < 0)
+ {
+ ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
+ }
+ else
+ {
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
+ }
+ if (ev.yAxis < 0)
+ {
+ ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
+ }
+ else if (ev.yAxis > 0)
+ {
+ ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
+ }
+ else
+ {
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
+ ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
+ }
+ }
+
+ GamePadButtonInputId.LeftStick.ordinal -> {
+ ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this)
+ }
+ GamePadButtonInputId.RightStick.ordinal -> {
+ ryujinxNative.inputSetStickAxis(2, ev.xAxis, -ev.yAxis ,this)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+suspend fun Flow.safeCollect(
+ block: suspend (T) -> Unit
+) {
+ this.catch {}
+ .collect {
+ block(it)
+ }
+}
+
+private fun generateConfig(isLeft: Boolean): GamePadConfig {
+ var distance = 0.05f
+
+ if (isLeft) {
+ return GamePadConfig(
+ 12,
+ PrimaryDialConfig.Stick(
+ GamePadButtonInputId.LeftStick.ordinal,
+ GamePadButtonInputId.LeftStickButton.ordinal,
+ setOf(),
+ "LeftStick",
+ null
+ ),
+ listOf(
+ SecondaryDialConfig.Cross(
+ 9,
+ 3,
+ 1.8f,
+ distance,
+ CrossConfig(
+ GamePadButtonInputId.DpadUp.ordinal,
+ CrossConfig.Shape.STANDARD,
+ null,
+ setOf(),
+ CrossContentDescription(),
+ true,
+ null
+ ),
+ SecondaryDialConfig.RotationProcessor()
+ ),
+ SecondaryDialConfig.SingleButton(
+ 0,
+ 1f,
+ 0.05f,
+ ButtonConfig(
+ GamePadButtonInputId.Minus.ordinal,
+ "-",
+ true,
+ null,
+ "Minus",
+ setOf(),
+ true,
+ null
+ ),
+ null,
+ SecondaryDialConfig.RotationProcessor()
+ ),
+ SecondaryDialConfig.DoubleButton(
+ 2,
+ 0.05f,
+ ButtonConfig(
+ GamePadButtonInputId.LeftShoulder.ordinal,
+ "L",
+ true,
+ null,
+ "LeftBumper",
+ setOf(),
+ true,
+ null
+ ),
+ null,
+ SecondaryDialConfig.RotationProcessor()
+ ),
+ SecondaryDialConfig.SingleButton(
+ 8,
+ 1f,
+ 0.05f,
+ 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,
+ 3f,
+ 0.05f,
+ GamePadButtonInputId.RightStick.ordinal,
+ GamePadButtonInputId.RightStickButton.ordinal,
+ null,
+ setOf(),
+ "RightStick",
+ SecondaryDialConfig.RotationProcessor()
+ ),
+ SecondaryDialConfig.SingleButton(
+ 6,
+ 1f,
+ 0.05f,
+ ButtonConfig(
+ GamePadButtonInputId.Plus.ordinal,
+ "+",
+ true,
+ null,
+ "Plus",
+ setOf(),
+ true,
+ null
+ ),
+ null,
+ SecondaryDialConfig.RotationProcessor()
+ ),
+ SecondaryDialConfig.DoubleButton(
+ 3,
+ 0.05f,
+ ButtonConfig(
+ GamePadButtonInputId.RightShoulder.ordinal,
+ "R",
+ true,
+ null,
+ "RightBumper",
+ setOf(),
+ true,
+ null
+ ),
+ null,
+ SecondaryDialConfig.RotationProcessor()
+ ),
+ SecondaryDialConfig.SingleButton(
+ 9,
+ 1f,
+ 0.05f,
+ ButtonConfig(
+ GamePadButtonInputId.RightTrigger.ordinal,
+ "ZR",
+ true,
+ null,
+ "RightTrigger",
+ setOf(),
+ true,
+ null
+ ),
+ null,
+ SecondaryDialConfig.RotationProcessor()
+ )
+ )
+ )
+ }
+}
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt
new file mode 100644
index 000000000..d9a804dfe
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt
@@ -0,0 +1,160 @@
+package org.ryujinx.android
+
+import android.content.Context
+import android.os.ParcelFileDescriptor
+import android.view.MotionEvent
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.ryujinx.android.viewmodels.GameModel
+import org.ryujinx.android.viewmodels.MainViewModel
+import kotlin.concurrent.thread
+import kotlin.math.roundToInt
+
+class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
+ private var _height: Int = 0
+ private var _width: Int = 0
+ private var _updateThread: Thread? = null
+ private var nativeInterop: NativeGraphicsInterop? = null
+ private var _guestThread: Thread? = null
+ private var _isInit: Boolean = false
+ private var _isStarted: Boolean = false
+ private var _nativeWindow: Long = 0
+ private var _nativeHelper: NativeHelpers = NativeHelpers()
+
+ private var _nativeRyujinx: RyujinxNative = RyujinxNative()
+
+ companion object {
+ var gameModel: GameModel? = null
+ }
+
+ init {
+ holder.addCallback(this)
+ }
+
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ if (_isStarted)
+ return when (event!!.actionMasked) {
+ MotionEvent.ACTION_MOVE -> {
+ _nativeRyujinx.inputSetTouchPoint(event.x.roundToInt(), event.y.roundToInt())
+ true
+ }
+ MotionEvent.ACTION_DOWN -> {
+ _nativeRyujinx.inputSetTouchPoint(event.x.roundToInt(), event.y.roundToInt())
+ true
+ }
+ MotionEvent.ACTION_UP -> {
+ _nativeRyujinx.inputReleaseTouchPoint()
+ true
+ }
+ else -> super.onTouchEvent(event)
+ }
+ return super.onTouchEvent(event)
+ }
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ var isStarted = _isStarted;
+
+ start(holder)
+
+ if(isStarted && (_width != width || _height != height))
+ {
+ var nativeHelpers = NativeHelpers()
+ var window = nativeHelpers.getNativeWindow(holder.surface);
+ _nativeRyujinx.graphicsSetSurface(window);
+ }
+
+ _width = width;
+ _height = height;
+
+ if(_isStarted)
+ {
+ _nativeRyujinx.inputSetClientSize(width, height)
+ }
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+
+ }
+
+ private fun start(surfaceHolder: SurfaceHolder) : Unit {
+ var game = gameModel ?: return
+ var path = game.getPath() ?: return
+ if (_isStarted)
+ return
+
+ var surface = surfaceHolder.surface;
+
+ var success = _nativeRyujinx.graphicsInitialize(GraphicsConfiguration())
+
+
+ var nativeHelpers = NativeHelpers()
+ var window = nativeHelpers.getNativeWindow(surfaceHolder.surface);
+ nativeInterop = NativeGraphicsInterop()
+ nativeInterop!!.VkRequiredExtensions = arrayOf(
+ "VK_KHR_surface", "VK_KHR_android_surface"
+ );
+ nativeInterop!!.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
+ nativeInterop!!.SurfaceHandle = window;
+
+ success = _nativeRyujinx.graphicsInitializeRenderer(
+ nativeInterop!!.VkRequiredExtensions!!,
+ window
+ );
+ success = _nativeRyujinx.deviceInitialize(true, true,
+ SystemLanguage.AmericanEnglish.ordinal,
+ RegionCode.USA.ordinal,
+ true,
+ true,
+ true,
+ false,
+ "UTC",
+ false);
+
+ success = _nativeRyujinx.deviceLoad(path)
+
+ _nativeRyujinx.inputInitialize(width, height)
+
+ controller.connect()
+
+ _nativeRyujinx.graphicsRendererSetSize(
+ surfaceHolder.surfaceFrame.width(),
+ surfaceHolder.surfaceFrame.height()
+ );
+
+ _guestThread = thread(start = true) {
+ runBlocking {
+ this.launch {
+ delay(5000)
+ _nativeRyujinx.graphicsRendererSetVsync(false);
+ }
+ }
+ runGame()
+ }
+ _isStarted = success;
+
+ _updateThread = thread(start = true) {
+ var c = 0
+ while (_isStarted) {
+ _nativeRyujinx.inputUpdate()
+ Thread.sleep(1)
+ c++
+ if (c >= 1000) {
+ c = 0
+ var stats = _nativeRyujinx.deviceGetGameStats()
+ mainViewModel.updateStats(stats.Fifo, stats.GameFps, stats.GameTime)
+ }
+ }
+ }
+ }
+
+ private fun runGame() : Unit{
+ _nativeRyujinx.graphicsRendererRunLoop()
+ }
+
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GamePadButtonInputId.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GamePadButtonInputId.kt
new file mode 100644
index 000000000..05119474a
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GamePadButtonInputId.kt
@@ -0,0 +1,26 @@
+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
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GraphicsConfiguration.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GraphicsConfiguration.kt
new file mode 100644
index 000000000..a52048441
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GraphicsConfiguration.kt
@@ -0,0 +1,22 @@
+package org.ryujinx.android
+
+import android.R.bool
+
+class GraphicsConfiguration {
+ var ResScale = 1f
+ var MaxAnisotropy = -1f
+ var FastGpuTime: Boolean = true
+ var Fast2DCopy: Boolean = true
+ var EnableMacroJit: Boolean = false
+ var EnableMacroHLE: Boolean = true
+ var EnableShaderCache: Boolean = true
+ var EnableTextureRecompression: Boolean = false
+ var BackendThreading: Int = org.ryujinx.android.BackendThreading.Auto.ordinal
+}
+
+enum class BackendThreading
+{
+ Auto,
+ Off,
+ On
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt
new file mode 100644
index 000000000..f08e44d8d
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt
@@ -0,0 +1,84 @@
+package org.ryujinx.android
+
+import android.content.ContentUris
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+
+class Helpers {
+ companion object{
+ fun getPath(context: Context, uri: Uri): String? {
+ val isKitKatorAbove = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+
+ // DocumentProvider
+ if (isKitKatorAbove && 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
+ if ("image" == type) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ } else if ("video" == type) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ } else if ("audio" == type) {
+ 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 getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array?): String? {
+ var cursor: Cursor? = null
+ val column = "_data"
+ val projection = arrayOf(column)
+ try {
+ cursor = uri?.let { context.getContentResolver().query(it, projection, selection, selectionArgs,null) }
+ if (cursor != null && cursor.moveToFirst()) {
+ val column_index: Int = cursor.getColumnIndexOrThrow(column)
+ return cursor.getString(column_index)
+ }
+ } finally {
+ if (cursor != null) cursor.close()
+ }
+ return null
+ }
+
+ fun isExternalStorageDocument(uri: Uri): Boolean {
+ return "com.android.externalstorage.documents" == uri.authority
+ }
+
+ fun isDownloadsDocument(uri: Uri): Boolean {
+ return "com.android.providers.downloads.documents" == uri.authority
+ }
+
+ fun isMediaDocument(uri: Uri): Boolean {
+ return "com.android.providers.media.documents" == uri.authority
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt
new file mode 100644
index 000000000..422b05fed
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt
@@ -0,0 +1,153 @@
+package org.ryujinx.android
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.view.WindowManager
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import com.anggrayudi.storage.SimpleStorageHelper
+import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
+import org.ryujinx.android.viewmodels.HomeViewModel
+import org.ryujinx.android.viewmodels.MainViewModel
+import org.ryujinx.android.views.HomeViews
+import org.ryujinx.android.views.MainView
+
+
+class MainActivity : ComponentActivity() {
+ private var mainViewModel: MainViewModel? = null
+ private var _isInit: Boolean = false
+ var storageHelper: SimpleStorageHelper? = null
+ companion object {
+ var AppPath : String?
+ var StorageHelper: SimpleStorageHelper? = null
+ init {
+ AppPath = ""
+ }
+ }
+
+ init {
+ storageHelper = SimpleStorageHelper(this)
+ StorageHelper = storageHelper
+ }
+
+ fun setFullScreen() :Unit {
+ requestedOrientation =
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+
+ WindowCompat.setDecorFitsSystemWindows(window,false)
+
+ var insets = WindowCompat.getInsetsController(window, window.decorView)
+
+ insets?.apply {
+ insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
+ insets.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
+ }
+
+ private fun getAudioDevice () : Int {
+ var audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
+ var devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+
+
+ return if (devices.isEmpty())
+ 0
+ else {
+ var speaker = devices.find { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
+ var earPiece = devices.find { it.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || it.type == AudioDeviceInfo.TYPE_WIRED_HEADSET }
+ if(earPiece != null)
+ return earPiece.id
+ if(speaker != null)
+ return speaker.id
+ devices.first().id
+ }
+ }
+
+ private fun initialize() : Unit
+ {
+ if(_isInit)
+ return
+
+ var appPath: String = AppPath ?: return
+ var success = RyujinxNative().initialize(appPath, false)
+ _isInit = success
+ }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ AppPath = this.getExternalFilesDir(null)!!.absolutePath
+
+ initialize()
+
+ if(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ !Environment.isExternalStorageManager()
+ } else {
+ false
+ }
+ ) {
+ storageHelper?.storage?.requestFullStorageAccess()
+ }
+ mainViewModel = MainViewModel(this)
+
+ mainViewModel?.apply {
+ setContent {
+ RyujinxAndroidTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ /*Box {
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = { context ->
+ GameHost(context)
+ }
+ )
+ controller.Compose(lifecycleScope, lifecycle)
+ }*/
+ 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)
+ }
+}
+
+@Composable
+fun Greeting(name: String, modifier: Modifier = Modifier) {
+
+}
+
+@Preview(showBackground = true)
+@Composable
+fun GreetingPreview() {
+ RyujinxAndroidTheme {
+ HomeViews.Home()
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeGraphicsInterop.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeGraphicsInterop.kt
new file mode 100644
index 000000000..262b551c3
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeGraphicsInterop.kt
@@ -0,0 +1,9 @@
+package org.ryujinx.android
+
+import android.view.Surface
+
+class NativeGraphicsInterop {
+ var VkCreateSurface: Long = 0
+ var SurfaceHandle: Long = 0
+ var VkRequiredExtensions: Array? = null
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt
new file mode 100644
index 000000000..32ff5f1d3
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt
@@ -0,0 +1,18 @@
+package org.ryujinx.android
+
+import android.view.Surface
+
+class NativeHelpers {
+
+ companion object {
+ init {
+ System.loadLibrary("ryujinxjni")
+ }
+ }
+ external fun releaseNativeWindow(window:Long) : Unit
+ external fun createSurface(vkInstance:Long, window:Long) : Long
+ external fun getCreateSurfacePtr() : Long
+ external fun getNativeWindow(surface:Surface) : Long
+ external fun attachCurrentThread() : Unit
+ external fun detachCurrentThread() : Unit
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RegionCode.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RegionCode.kt
new file mode 100644
index 000000000..f9dfec477
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RegionCode.kt
@@ -0,0 +1,11 @@
+package org.ryujinx.android
+
+enum class RegionCode {
+ Japan,
+ USA,
+ Europe,
+ Australia,
+ China,
+ Korea,
+ Taiwan,
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt
new file mode 100644
index 000000000..437f16ed7
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt
@@ -0,0 +1,51 @@
+package org.ryujinx.android
+
+import android.view.Surface
+import org.ryujinx.android.viewmodels.GameInfo
+import org.ryujinx.android.viewmodels.GameStats
+import java.io.FileDescriptor
+
+class RyujinxNative {
+
+ external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean
+
+ companion object {
+ init {
+ System.loadLibrary("ryujinx")
+ }
+ }
+
+ external fun deviceInitialize(isHostMapped: Boolean, useNce: Boolean,
+ systemLanguage : Int,
+ regionCode : Int,
+ enableVsync : Boolean,
+ enableDockedMode : Boolean,
+ enablePtc : Boolean,
+ enableInternetAccess : Boolean,
+ timeZone : String,
+ ignoreMissingServices : Boolean): Boolean
+ external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
+ external fun graphicsInitializeRenderer(
+ extensions: Array,
+ surface: Long
+ ): Boolean
+
+ external fun deviceLoad(game: String): Boolean
+ external fun deviceGetGameStats(): GameStats
+ external fun deviceGetGameInfo(fileDescriptor: Int, isXci:Boolean): GameInfo
+ external fun deviceGetGameInfoFromPath(path: String): GameInfo
+ external fun deviceLoadDescriptor(fileDescriptor: Int, isXci:Boolean): Boolean
+ external fun graphicsRendererSetSize(width: Int, height: Int): Unit
+ external fun graphicsRendererSetVsync(enabled: Boolean): Unit
+ external fun graphicsRendererRunLoop(): Unit
+ external fun inputInitialize(width: Int, height: Int): Unit
+ external fun inputSetClientSize(width: Int, height: Int): Unit
+ external fun inputSetTouchPoint(x: Int, y: Int): Unit
+ external fun inputReleaseTouchPoint(): Unit
+ external fun inputUpdate(): Unit
+ external fun inputSetButtonPressed(button: Int, id: String): Unit
+ external fun inputSetButtonReleased(button: Int, id: String): Unit
+ external fun inputConnectGamepad(index: Int): String
+ external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: String): Unit
+ external fun graphicsSetSurface(surface: Long): String
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/SystemLanguage.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/SystemLanguage.kt
new file mode 100644
index 000000000..f1af8e151
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/SystemLanguage.kt
@@ -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
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Color.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Color.kt
new file mode 100644
index 000000000..243ab3667
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Color.kt
@@ -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)
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Theme.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Theme.kt
new file mode 100644
index 000000000..38fbf2513
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Theme.kt
@@ -0,0 +1,80 @@
+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.tooling.preview.Preview
+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
+ )
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Type.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Type.kt
new file mode 100644
index 000000000..c9097e38a
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/ui/theme/Type.kt
@@ -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
+ )
+ */
+)
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt
new file mode 100644
index 000000000..2853a1c12
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt
@@ -0,0 +1,55 @@
+package org.ryujinx.android.viewmodels
+
+import android.R.string
+import android.content.ContentResolver
+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.Helpers
+import org.ryujinx.android.RyujinxNative
+
+
+class GameModel(var file: DocumentFile, val context: Context) {
+ var fileName: String?
+ var fileSize = 0.0
+ var titleName: String? = null
+ var titleId: String? = null
+ var developer: String? = null
+ var version: String? = null
+ var iconCache: String? = null
+
+ init {
+ fileName = file.name
+ var absPath = getPath()
+ var gameInfo = RyujinxNative().deviceGetGameInfoFromPath(absPath ?: "")
+
+ fileSize = gameInfo.FileSize
+ titleId = gameInfo.TitleId
+ titleName = gameInfo.TitleName
+ developer = gameInfo.Developer
+ version = gameInfo.Version
+ iconCache = gameInfo.IconCache
+ }
+
+ fun getPath() : String? {
+ var uri = file.uri
+ if (uri.scheme != "file")
+ uri = Uri.parse("file://" + Helpers.getPath(context, file.uri))
+ return uri.path;
+ }
+
+ fun getIsXci() : Boolean {
+ return file.extension == "xci"
+ }
+}
+
+class GameInfo {
+ var FileSize = 0.0
+ var TitleName: String? = null
+ var TitleId: String? = null
+ var Developer: String? = null
+ var Version: String? = null
+ var IconCache: String? = null
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameStats.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameStats.kt
new file mode 100644
index 000000000..e4d954312
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameStats.kt
@@ -0,0 +1,7 @@
+package org.ryujinx.android.viewmodels
+
+class GameStats {
+ var Fifo = 0.0
+ var GameFps = 0.0
+ var GameTime = 0.0
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt
new file mode 100644
index 000000000..0fffb7081
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt
@@ -0,0 +1,97 @@
+package org.ryujinx.android.viewmodels
+
+import android.content.SharedPreferences
+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.FileFullPath
+import com.anggrayudi.storage.file.extension
+import com.anggrayudi.storage.file.fullName
+import com.anggrayudi.storage.file.getAbsolutePath
+import com.anggrayudi.storage.file.search
+import org.ryujinx.android.MainActivity
+
+class HomeViewModel(
+ val activity: MainActivity? = null,
+ val mainViewModel: MainViewModel? = null
+) {
+ private var gameList: SnapshotStateList? = null
+ private var loadedCache: List = listOf()
+ private var gameFolderPath: DocumentFile? = null
+ private var sharedPref: SharedPreferences? = null;
+
+ init {
+ if (activity != null) {
+ sharedPref = PreferenceManager.getDefaultSharedPreferences(activity)
+ activity.storageHelper!!.onFolderSelected = { requestCode, folder ->
+ run {
+ gameFolderPath = folder
+ var p = folder.getAbsolutePath(activity!!)
+ var editor = sharedPref?.edit()
+ editor?.putString("gameFolder", p);
+ editor?.apply()
+ reloadGameList()
+ }
+ }
+
+ var savedFolder = sharedPref?.getString("gameFolder", "") ?: ""
+
+ if (savedFolder.isNotEmpty()) {
+ try {
+ gameFolderPath = DocumentFileCompat.fromFullPath(
+ activity,
+ savedFolder,
+ documentType = DocumentFileType.FOLDER,
+ requiresWriteAccess = true
+ )
+
+ reloadGameList()
+ } catch (e: Exception) {
+
+ }
+ }
+ }
+ }
+
+ fun openGameFolder() {
+ var path = sharedPref?.getString("gameFolder", "") ?: ""
+
+ if (path.isNullOrEmpty())
+ activity?.storageHelper?.storage?.openFolderPicker();
+ else
+ activity?.storageHelper?.storage?.openFolderPicker(
+ activity!!.storageHelper!!.storage.requestCodeFolderPicker,
+ FileFullPath(activity, path)
+ )
+ }
+
+ fun reloadGameList() {
+ var storage = activity?.storageHelper ?: return
+ var folder = gameFolderPath ?: return
+
+ var files = mutableListOf()
+
+ for (file in folder.search(false, DocumentFileType.FILE)) {
+ if (file.extension == "xci" || file.extension == "nsp")
+ activity?.let {
+ files.add(GameModel(file, it))
+ }
+ }
+
+ loadedCache = files.toList()
+
+ applyFilter()
+ }
+
+ private fun applyFilter() {
+ gameList?.clear()
+ gameList?.addAll(loadedCache)
+ }
+
+ fun setViewList(list: SnapshotStateList) {
+ gameList = list;
+ applyFilter()
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt
new file mode 100644
index 000000000..c8d3664b2
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt
@@ -0,0 +1,53 @@
+package org.ryujinx.android.viewmodels
+
+import androidx.compose.runtime.MutableState
+import androidx.navigation.NavHostController
+import org.ryujinx.android.GameHost
+import org.ryujinx.android.MainActivity
+
+class MainViewModel(val activity: MainActivity) {
+ var selected: GameModel? = null
+ private var gameTimeState: MutableState? = null
+ private var gameFpsState: MutableState? = null
+ private var fifoState: MutableState? = null
+ private var navController : NavHostController? = null
+
+ var homeViewModel: HomeViewModel = HomeViewModel(activity, this,)
+
+ fun loadGame(game:GameModel) {
+ var controller = navController?: return;
+ activity.setFullScreen()
+ GameHost.gameModel = game
+ controller.navigate("game")
+ }
+
+ fun setNavController(controller: NavHostController) {
+ navController = controller
+ }
+
+ fun setStatStates(
+ fifo: MutableState,
+ gameFps: MutableState,
+ gameTime: MutableState
+ ) {
+ fifoState = fifo
+ gameFpsState = gameFps
+ gameTimeState = gameTime
+ }
+
+ fun updateStats(
+ fifo: Double,
+ gameFps: Double,
+ gameTime: Double
+ ){
+ fifoState?.apply {
+ this.value = fifo
+ }
+ gameFpsState?.apply {
+ this.value = gameFps
+ }
+ gameTimeState?.apply {
+ this.value = gameTime
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt
new file mode 100644
index 000000000..670871572
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt
@@ -0,0 +1,116 @@
+package org.ryujinx.android.viewmodels
+
+import androidx.appcompat.widget.ThemedSpinnerAdapter.Helper
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.toLowerCase
+import com.anggrayudi.storage.SimpleStorageHelper
+import com.google.gson.Gson
+import org.ryujinx.android.Helpers
+import org.ryujinx.android.MainActivity
+import java.io.File
+import kotlin.math.max
+
+class TitleUpdateViewModel(val titleId: String) {
+ private var storageHelper: SimpleStorageHelper
+ var pathsState: SnapshotStateList? = null
+
+ companion object {
+ const val UpdateRequestCode = 1002
+ }
+
+ fun Remove(index: Int) {
+ if (index <= 0)
+ return
+
+ data?.paths?.apply {
+ removeAt(index - 1);
+ pathsState?.clear()
+ pathsState?.addAll(this)
+ }
+ }
+
+ fun Add() {
+ storageHelper.openFilePicker(UpdateRequestCode)
+ }
+
+ fun save(index: Int) {
+ data?.apply {
+ this.selected = ""
+ if(paths.isNotEmpty() && index > 0)
+ {
+ var ind = max(index - 1, paths.count() - 1)
+ this.selected = paths[ind]
+ }
+ var gson = Gson()
+ var json = gson.toJson(this)
+ jsonPath = (MainActivity.AppPath
+ ?: "") + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json"
+ File(jsonPath).writeText(json)
+ }
+ }
+
+ fun setPaths(paths: SnapshotStateList) {
+ pathsState = paths;
+ data?.apply {
+ pathsState?.clear()
+ pathsState?.addAll(this.paths)
+ }
+ }
+
+ var data: TitleUpdateMetadata? = null
+ private var jsonPath: String
+
+ init {
+ jsonPath = (MainActivity.AppPath
+ ?: "") + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json"
+ if (File(jsonPath).exists()) {
+ var gson = Gson()
+ data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
+
+ data?.apply {
+ var existingPaths = mutableListOf()
+ for (path in paths) {
+ if (File(path).exists()) {
+ existingPaths.add(path)
+ }
+ }
+
+ if(!existingPaths.contains(selected)){
+ selected = ""
+ }
+ pathsState?.clear()
+ pathsState?.addAll(existingPaths)
+ paths = existingPaths
+ }
+ }
+
+ storageHelper = MainActivity.StorageHelper!!
+
+ storageHelper.onFileSelected = { requestCode, files ->
+ run {
+ if(requestCode == UpdateRequestCode)
+ {
+ var file = files.firstOrNull()
+ file?.apply {
+ var path = Helpers.getPath(storageHelper.storage.context, file.uri)
+ if(!path.isNullOrEmpty()){
+ data?.apply {
+ if(!paths.contains(path)) {
+ paths.add(path)
+ pathsState?.clear()
+ pathsState?.addAll(paths)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+data class TitleUpdateMetadata(
+ var selected: String = "",
+ var paths: MutableList = mutableListOf()
+)
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt
new file mode 100644
index 000000000..ea21ecd5f
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt
@@ -0,0 +1,274 @@
+package org.ryujinx.android.views
+
+import android.content.res.Resources
+import androidx.compose.foundation.ExperimentalFoundationApi
+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.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.AlertDialogDefaults
+import androidx.compose.material3.Card
+import androidx.compose.material3.DockedSearchBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FabPosition
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SearchBarDefaults
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.rememberModalBottomSheetState
+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.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import coil.compose.AsyncImage
+import org.ryujinx.android.MainActivity
+import org.ryujinx.android.R
+import org.ryujinx.android.viewmodels.GameModel
+import org.ryujinx.android.viewmodels.HomeViewModel
+import java.io.File
+import kotlin.math.roundToInt
+
+class HomeViews {
+ companion object {
+ const val ImageSize = 150
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun MainTopBar() {
+ TopAppBar(
+ modifier = Modifier
+ .zIndex(1f)
+ .padding(top = 10.dp),
+ title = {
+ DockedSearchBar(
+ shape = SearchBarDefaults.inputFieldShape,
+ query = "",
+ onQueryChange = {},
+ onSearch = {},
+ active = false,
+ onActiveChange = {},
+ leadingIcon = {
+ Icon(
+ Icons.Filled.Search,
+ contentDescription = "Search Games"
+ )
+ },
+ placeholder = {
+ Text(text = "Search Games")
+ }
+ ) {
+
+ }
+ },
+ actions = {
+ IconButton(
+ onClick = { }
+ ) {
+ Icon(
+ Icons.Filled.MoreVert,
+ contentDescription = "More"
+ )
+ }
+ }
+ )
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun Home(viewModel: HomeViewModel = HomeViewModel()) {
+ val sheetState = rememberModalBottomSheetState()
+ val scope = rememberCoroutineScope()
+ var showBottomSheet = remember { mutableStateOf(false) }
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ MainTopBar()
+ },
+ floatingActionButtonPosition = FabPosition.End,
+ floatingActionButton = {
+ FloatingActionButton(onClick = {
+ viewModel.openGameFolder()
+ },
+ shape = CircleShape) {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = "Options"
+ )
+ }
+ }
+
+ ) { contentPadding ->
+ Box(modifier = Modifier.padding(contentPadding)) {
+ var list = remember {
+ mutableStateListOf()
+ }
+ viewModel.setViewList(list)
+ LazyColumn(Modifier.fillMaxSize()) {
+ items(list) {
+ GameItem(it, viewModel, showBottomSheet)
+ }
+ }
+ }
+
+ if(showBottomSheet.value) {
+ ModalBottomSheet(onDismissRequest = {
+ showBottomSheet.value = false
+ },
+ sheetState = sheetState) {
+ val openDialog = remember { mutableStateOf(false) }
+
+ if(openDialog.value) {
+ AlertDialog(onDismissRequest = {
+ openDialog.value = false
+ }) {
+ Surface(
+ modifier = Modifier
+ .wrapContentWidth()
+ .wrapContentHeight(),
+ shape = MaterialTheme.shapes.large,
+ tonalElevation = AlertDialogDefaults.TonalElevation
+ ) {
+ var titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
+ var name = viewModel.mainViewModel?.selected?.titleName ?: ""
+ TitleUpdateViews.Main(titleId, name, openDialog)
+ }
+
+ }
+ }
+ Surface(color = MaterialTheme.colorScheme.surface,
+ modifier = Modifier.padding(10.dp)) {
+ Column(modifier = Modifier.fillMaxSize()) {
+ Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
+ Card(
+ onClick = {
+ openDialog.value = true
+ }
+ ) {
+ Column(modifier = Modifier.padding(10.dp)) {
+ Icon(
+ painter = painterResource(R.drawable.app_update),
+ contentDescription = "More",
+ tint = Color.Green,
+ modifier = Modifier
+ .width(48.dp)
+ .height(48.dp)
+ .align(Alignment.CenterHorizontally)
+ )
+ Text(text = "Game Updates",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ color = MaterialTheme.colorScheme.onSurface)
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+ @Composable
+ fun GameItem(gameModel: GameModel, viewModel: HomeViewModel, showSheet : MutableState) {
+ Card(shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(10.dp)
+ .combinedClickable(
+ onClick = {
+ if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
+ viewModel.mainViewModel?.loadGame(gameModel)
+ }
+ },
+ onLongClick = {
+ viewModel.mainViewModel?.selected = gameModel
+ showSheet.value = true
+ })) {
+ Row(modifier = Modifier
+ .fillMaxWidth()
+ .padding(10.dp),
+ horizontalArrangement = Arrangement.SpaceBetween) {
+ Row {
+ if(!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000")
+ {
+ var iconSource = MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
+ var imageFile = File(iconSource)
+ if(imageFile.exists()) {
+ val size = ImageSize / Resources.getSystem().displayMetrics.density
+ AsyncImage(model = imageFile,
+ contentDescription = gameModel.titleName + " icon",
+ modifier = Modifier
+ .padding(end = 5.dp)
+ .width(size.roundToInt().dp)
+ .height(size.roundToInt().dp))
+ }
+ 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))
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun NotAvailableIcon() {
+ var size = ImageSize / Resources.getSystem().displayMetrics.density
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = "Options",
+ modifier = Modifier
+ .padding(end = 5.dp)
+ .width(size.roundToInt().dp)
+ .height(size.roundToInt().dp)
+ )
+ }
+
+ }
+
+ @Preview
+ @Composable
+ fun HomePreview() {
+ Home()
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt
new file mode 100644
index 000000000..0392cac3f
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt
@@ -0,0 +1,79 @@
+package org.ryujinx.android.views
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import org.ryujinx.android.GameController
+import org.ryujinx.android.GameHost
+import org.ryujinx.android.viewmodels.MainViewModel
+
+class MainView {
+ companion object {
+ @Composable
+ fun Main(mainViewModel: MainViewModel){
+ val navController = rememberNavController()
+ mainViewModel.setNavController(navController)
+
+ NavHost(navController = navController, startDestination = "home") {
+ composable("home") {HomeViews.Home(mainViewModel.homeViewModel)}
+ composable("game") { GameView(mainViewModel)}
+ }
+ }
+
+ @Composable
+ fun GameView(mainViewModel: MainViewModel){
+ Box {
+ var controller = GameController(mainViewModel.activity)
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = { context ->
+ GameHost(context, controller, mainViewModel)
+ }
+ )
+ GameStats(mainViewModel)
+ controller.Compose(mainViewModel.activity.lifecycleScope, mainViewModel.activity.lifecycle)
+ }
+ }
+
+ @Composable
+ fun GameStats(mainViewModel: MainViewModel){
+ var fifo = remember {
+ mutableStateOf(0.0)
+ }
+ var gameFps = remember {
+ mutableStateOf(0.0)
+ }
+ var gameTime = remember {
+ mutableStateOf(0.0)
+ }
+
+ Surface(modifier = Modifier.padding(10.dp),
+ color = MaterialTheme.colorScheme.surface.copy(0.4f)) {
+ 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")
+ }
+ }
+
+ mainViewModel.setStatStates(fifo, gameFps, gameTime)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt
new file mode 100644
index 000000000..42b9d3ce5
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt
@@ -0,0 +1,131 @@
+package org.ryujinx.android.views
+
+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.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+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 org.ryujinx.android.viewmodels.TitleUpdateViewModel
+
+class TitleUpdateViews {
+ companion object {
+ @Composable
+ fun Main(titleId: String, name: String, openDialog: MutableState) {
+ val viewModel = TitleUpdateViewModel(titleId)
+
+ var 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(5.dp),
+ color = MaterialTheme.colorScheme.surfaceVariant,
+ shape = MaterialTheme.shapes.medium
+ ) {
+ Column(
+ modifier = Modifier
+ .height(300.dp)
+ .fillMaxWidth()
+ ) {
+ Row(modifier = Modifier.padding(5.dp)) {
+ RadioButton(
+ selected = (selected.value == 0),
+ onClick = { selected.value = 0
+ })
+ Text(
+ text = "None",
+ modifier = Modifier.fillMaxWidth()
+ .align(Alignment.CenterVertically)
+ )
+ }
+
+ var paths = remember {
+ mutableStateListOf()
+ }
+
+ viewModel.setPaths(paths)
+ var index = 1
+ for (path in paths) {
+ var i = index
+ Row(modifier = Modifier.padding(5.dp)) {
+ RadioButton(
+ selected = (selected.value == i),
+ onClick = { selected.value = i })
+ Text(
+ text = path,
+ 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 = "Remove"
+ )
+ }
+ }
+
+ }
+ Spacer(modifier = Modifier.height(15.dp))
+ TextButton(
+ modifier = Modifier.align(Alignment.End),
+ onClick = {
+ openDialog.value = false
+ viewModel.save(selected.value)
+ },
+ ) {
+ Text("Save")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/src/RyujinxAndroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..2b068d114
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/drawable/app_update.xml b/src/RyujinxAndroid/app/src/main/res/drawable/app_update.xml
new file mode 100644
index 000000000..e5de70f7a
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/drawable/app_update.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/RyujinxAndroid/app/src/main/res/drawable/ic_launcher_background.xml b/src/RyujinxAndroid/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RyujinxAndroid/app/src/main/res/layout/game_layout.xml b/src/RyujinxAndroid/app/src/main/res/layout/game_layout.xml
new file mode 100644
index 000000000..d30d7c303
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/layout/game_layout.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/RyujinxAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..6f3b755bf
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/RyujinxAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..6f3b755bf
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/src/RyujinxAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/src/RyujinxAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/src/RyujinxAndroid/app/src/main/res/values/colors.xml b/src/RyujinxAndroid/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..f8c6127d3
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/values/strings.xml b/src/RyujinxAndroid/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..ea3527878
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ RyujinxAndroid
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/values/themes.xml b/src/RyujinxAndroid/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..9b1784b45
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/xml/backup_rules.xml b/src/RyujinxAndroid/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..fa0f996d2
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/main/res/xml/data_extraction_rules.xml b/src/RyujinxAndroid/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..9ee9997b0
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RyujinxAndroid/app/src/test/java/org/ryujinx/android/ExampleUnitTest.kt b/src/RyujinxAndroid/app/src/test/java/org/ryujinx/android/ExampleUnitTest.kt
new file mode 100644
index 000000000..c31638740
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/test/java/org/ryujinx/android/ExampleUnitTest.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/build.gradle b/src/RyujinxAndroid/build.gradle
new file mode 100644
index 000000000..431431374
--- /dev/null
+++ b/src/RyujinxAndroid/build.gradle
@@ -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.0.2' apply false
+ id 'com.android.library' version '8.0.2' apply false
+ id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+}
\ No newline at end of file
diff --git a/src/RyujinxAndroid/gradle.properties b/src/RyujinxAndroid/gradle.properties
new file mode 100644
index 000000000..3c5031eb7
--- /dev/null
+++ b/src/RyujinxAndroid/gradle.properties
@@ -0,0 +1,23 @@
+# 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
\ No newline at end of file
diff --git a/src/RyujinxAndroid/gradle/wrapper/gradle-wrapper.jar b/src/RyujinxAndroid/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..e708b1c02
Binary files /dev/null and b/src/RyujinxAndroid/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/src/RyujinxAndroid/gradle/wrapper/gradle-wrapper.properties b/src/RyujinxAndroid/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..5d9d161a3
--- /dev/null
+++ b/src/RyujinxAndroid/gradle/wrapper/gradle-wrapper.properties
@@ -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.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/src/RyujinxAndroid/gradlew b/src/RyujinxAndroid/gradlew
new file mode 100644
index 000000000..4f906e0c8
--- /dev/null
+++ b/src/RyujinxAndroid/gradlew
@@ -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" "$@"
diff --git a/src/RyujinxAndroid/gradlew.bat b/src/RyujinxAndroid/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/src/RyujinxAndroid/gradlew.bat
@@ -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
diff --git a/src/RyujinxAndroid/settings.gradle b/src/RyujinxAndroid/settings.gradle
new file mode 100644
index 000000000..50231595d
--- /dev/null
+++ b/src/RyujinxAndroid/settings.gradle
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url 'https://jitpack.io' }
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://jitpack.io' }
+ }
+}
+rootProject.name = "RyujinxAndroid"
+include ':app'