diff options
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | src/common/android/android_common.cpp | 65 | ||||
| -rw-r--r-- | src/common/android/android_common.h | 26 | ||||
| -rw-r--r-- | src/common/android/applets/software_keyboard.cpp | 277 | ||||
| -rw-r--r-- | src/common/android/applets/software_keyboard.h | 78 | ||||
| -rw-r--r-- | src/common/android/id_cache.cpp | 428 | ||||
| -rw-r--r-- | src/common/android/id_cache.h | 88 | ||||
| -rw-r--r-- | src/common/fs/fs_android.cpp | 167 | ||||
| -rw-r--r-- | src/common/fs/fs_android.h | 58 | ||||
| -rw-r--r-- | src/common/range_sets.h | 73 | ||||
| -rw-r--r-- | src/common/range_sets.inc | 304 | ||||
| -rw-r--r-- | src/common/settings.cpp | 3 | ||||
| -rw-r--r-- | src/common/settings.h | 32 | ||||
| -rw-r--r-- | src/common/settings_common.h | 1 | ||||
| -rw-r--r-- | src/common/settings_enums.h | 2 | ||||
| -rw-r--r-- | src/common/slot_vector.h | 227 |
16 files changed, 1705 insertions, 135 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 85926fc8f..779be211e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -107,6 +107,8 @@ add_library(common STATIC | |||
| 107 | quaternion.h | 107 | quaternion.h |
| 108 | range_map.h | 108 | range_map.h |
| 109 | range_mutex.h | 109 | range_mutex.h |
| 110 | range_sets.h | ||
| 111 | range_sets.inc | ||
| 110 | reader_writer_queue.h | 112 | reader_writer_queue.h |
| 111 | ring_buffer.h | 113 | ring_buffer.h |
| 112 | ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp | 114 | ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp |
| @@ -121,6 +123,7 @@ add_library(common STATIC | |||
| 121 | settings_input.cpp | 123 | settings_input.cpp |
| 122 | settings_input.h | 124 | settings_input.h |
| 123 | settings_setting.h | 125 | settings_setting.h |
| 126 | slot_vector.h | ||
| 124 | socket_types.h | 127 | socket_types.h |
| 125 | spin_lock.cpp | 128 | spin_lock.cpp |
| 126 | spin_lock.h | 129 | spin_lock.h |
| @@ -179,9 +182,15 @@ endif() | |||
| 179 | 182 | ||
| 180 | if(ANDROID) | 183 | if(ANDROID) |
| 181 | target_sources(common | 184 | target_sources(common |
| 182 | PRIVATE | 185 | PUBLIC |
| 183 | fs/fs_android.cpp | 186 | fs/fs_android.cpp |
| 184 | fs/fs_android.h | 187 | fs/fs_android.h |
| 188 | android/android_common.cpp | ||
| 189 | android/android_common.h | ||
| 190 | android/id_cache.cpp | ||
| 191 | android/id_cache.h | ||
| 192 | android/applets/software_keyboard.cpp | ||
| 193 | android/applets/software_keyboard.h | ||
| 185 | ) | 194 | ) |
| 186 | endif() | 195 | endif() |
| 187 | 196 | ||
diff --git a/src/common/android/android_common.cpp b/src/common/android/android_common.cpp new file mode 100644 index 000000000..e79005658 --- /dev/null +++ b/src/common/android/android_common.cpp | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "android_common.h" | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <string_view> | ||
| 8 | |||
| 9 | #include <jni.h> | ||
| 10 | |||
| 11 | #include "common/android/id_cache.h" | ||
| 12 | #include "common/string_util.h" | ||
| 13 | |||
| 14 | namespace Common::Android { | ||
| 15 | |||
| 16 | std::string GetJString(JNIEnv* env, jstring jstr) { | ||
| 17 | if (!jstr) { | ||
| 18 | return {}; | ||
| 19 | } | ||
| 20 | |||
| 21 | const jchar* jchars = env->GetStringChars(jstr, nullptr); | ||
| 22 | const jsize length = env->GetStringLength(jstr); | ||
| 23 | const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), | ||
| 24 | static_cast<u32>(length)); | ||
| 25 | const std::string converted_string = Common::UTF16ToUTF8(string_view); | ||
| 26 | env->ReleaseStringChars(jstr, jchars); | ||
| 27 | |||
| 28 | return converted_string; | ||
| 29 | } | ||
| 30 | |||
| 31 | jstring ToJString(JNIEnv* env, std::string_view str) { | ||
| 32 | const std::u16string converted_string = Common::UTF8ToUTF16(str); | ||
| 33 | return env->NewString(reinterpret_cast<const jchar*>(converted_string.data()), | ||
| 34 | static_cast<jint>(converted_string.size())); | ||
| 35 | } | ||
| 36 | |||
| 37 | jstring ToJString(JNIEnv* env, std::u16string_view str) { | ||
| 38 | return ToJString(env, Common::UTF16ToUTF8(str)); | ||
| 39 | } | ||
| 40 | |||
| 41 | double GetJDouble(JNIEnv* env, jobject jdouble) { | ||
| 42 | return env->GetDoubleField(jdouble, GetDoubleValueField()); | ||
| 43 | } | ||
| 44 | |||
| 45 | jobject ToJDouble(JNIEnv* env, double value) { | ||
| 46 | return env->NewObject(GetDoubleClass(), GetDoubleConstructor(), value); | ||
| 47 | } | ||
| 48 | |||
| 49 | s32 GetJInteger(JNIEnv* env, jobject jinteger) { | ||
| 50 | return env->GetIntField(jinteger, GetIntegerValueField()); | ||
| 51 | } | ||
| 52 | |||
| 53 | jobject ToJInteger(JNIEnv* env, s32 value) { | ||
| 54 | return env->NewObject(GetIntegerClass(), GetIntegerConstructor(), value); | ||
| 55 | } | ||
| 56 | |||
| 57 | bool GetJBoolean(JNIEnv* env, jobject jboolean) { | ||
| 58 | return env->GetBooleanField(jboolean, GetBooleanValueField()); | ||
| 59 | } | ||
| 60 | |||
| 61 | jobject ToJBoolean(JNIEnv* env, bool value) { | ||
| 62 | return env->NewObject(GetBooleanClass(), GetBooleanConstructor(), value); | ||
| 63 | } | ||
| 64 | |||
| 65 | } // namespace Common::Android | ||
diff --git a/src/common/android/android_common.h b/src/common/android/android_common.h new file mode 100644 index 000000000..d0ccb4ec2 --- /dev/null +++ b/src/common/android/android_common.h | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include <jni.h> | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace Common::Android { | ||
| 12 | |||
| 13 | std::string GetJString(JNIEnv* env, jstring jstr); | ||
| 14 | jstring ToJString(JNIEnv* env, std::string_view str); | ||
| 15 | jstring ToJString(JNIEnv* env, std::u16string_view str); | ||
| 16 | |||
| 17 | double GetJDouble(JNIEnv* env, jobject jdouble); | ||
| 18 | jobject ToJDouble(JNIEnv* env, double value); | ||
| 19 | |||
| 20 | s32 GetJInteger(JNIEnv* env, jobject jinteger); | ||
| 21 | jobject ToJInteger(JNIEnv* env, s32 value); | ||
| 22 | |||
| 23 | bool GetJBoolean(JNIEnv* env, jobject jboolean); | ||
| 24 | jobject ToJBoolean(JNIEnv* env, bool value); | ||
| 25 | |||
| 26 | } // namespace Common::Android | ||
diff --git a/src/common/android/applets/software_keyboard.cpp b/src/common/android/applets/software_keyboard.cpp new file mode 100644 index 000000000..477e62b16 --- /dev/null +++ b/src/common/android/applets/software_keyboard.cpp | |||
| @@ -0,0 +1,277 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <map> | ||
| 5 | #include <thread> | ||
| 6 | |||
| 7 | #include <jni.h> | ||
| 8 | |||
| 9 | #include "common/android/android_common.h" | ||
| 10 | #include "common/android/applets/software_keyboard.h" | ||
| 11 | #include "common/android/id_cache.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | #include "common/string_util.h" | ||
| 14 | #include "core/core.h" | ||
| 15 | |||
| 16 | static jclass s_software_keyboard_class; | ||
| 17 | static jclass s_keyboard_config_class; | ||
| 18 | static jclass s_keyboard_data_class; | ||
| 19 | static jmethodID s_swkbd_execute_normal; | ||
| 20 | static jmethodID s_swkbd_execute_inline; | ||
| 21 | |||
| 22 | namespace Common::Android::SoftwareKeyboard { | ||
| 23 | |||
| 24 | static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) { | ||
| 25 | JNIEnv* env = GetEnvForThread(); | ||
| 26 | jobject object = env->AllocObject(s_keyboard_config_class); | ||
| 27 | |||
| 28 | env->SetObjectField(object, | ||
| 29 | env->GetFieldID(s_keyboard_config_class, "ok_text", "Ljava/lang/String;"), | ||
| 30 | ToJString(env, config.ok_text)); | ||
| 31 | env->SetObjectField( | ||
| 32 | object, env->GetFieldID(s_keyboard_config_class, "header_text", "Ljava/lang/String;"), | ||
| 33 | ToJString(env, config.header_text)); | ||
| 34 | env->SetObjectField(object, | ||
| 35 | env->GetFieldID(s_keyboard_config_class, "sub_text", "Ljava/lang/String;"), | ||
| 36 | ToJString(env, config.sub_text)); | ||
| 37 | env->SetObjectField( | ||
| 38 | object, env->GetFieldID(s_keyboard_config_class, "guide_text", "Ljava/lang/String;"), | ||
| 39 | ToJString(env, config.guide_text)); | ||
| 40 | env->SetObjectField( | ||
| 41 | object, env->GetFieldID(s_keyboard_config_class, "initial_text", "Ljava/lang/String;"), | ||
| 42 | ToJString(env, config.initial_text)); | ||
| 43 | env->SetShortField(object, | ||
| 44 | env->GetFieldID(s_keyboard_config_class, "left_optional_symbol_key", "S"), | ||
| 45 | static_cast<jshort>(config.left_optional_symbol_key)); | ||
| 46 | env->SetShortField(object, | ||
| 47 | env->GetFieldID(s_keyboard_config_class, "right_optional_symbol_key", "S"), | ||
| 48 | static_cast<jshort>(config.right_optional_symbol_key)); | ||
| 49 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"), | ||
| 50 | static_cast<jint>(config.max_text_length)); | ||
| 51 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "min_text_length", "I"), | ||
| 52 | static_cast<jint>(config.min_text_length)); | ||
| 53 | env->SetIntField(object, | ||
| 54 | env->GetFieldID(s_keyboard_config_class, "initial_cursor_position", "I"), | ||
| 55 | static_cast<jint>(config.initial_cursor_position)); | ||
| 56 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "type", "I"), | ||
| 57 | static_cast<jint>(config.type)); | ||
| 58 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "password_mode", "I"), | ||
| 59 | static_cast<jint>(config.password_mode)); | ||
| 60 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "text_draw_type", "I"), | ||
| 61 | static_cast<jint>(config.text_draw_type)); | ||
| 62 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "key_disable_flags", "I"), | ||
| 63 | static_cast<jint>(config.key_disable_flags.raw)); | ||
| 64 | env->SetBooleanField(object, | ||
| 65 | env->GetFieldID(s_keyboard_config_class, "use_blur_background", "Z"), | ||
| 66 | static_cast<jboolean>(config.use_blur_background)); | ||
| 67 | env->SetBooleanField(object, | ||
| 68 | env->GetFieldID(s_keyboard_config_class, "enable_backspace_button", "Z"), | ||
| 69 | static_cast<jboolean>(config.enable_backspace_button)); | ||
| 70 | env->SetBooleanField(object, | ||
| 71 | env->GetFieldID(s_keyboard_config_class, "enable_return_button", "Z"), | ||
| 72 | static_cast<jboolean>(config.enable_return_button)); | ||
| 73 | env->SetBooleanField(object, | ||
| 74 | env->GetFieldID(s_keyboard_config_class, "disable_cancel_button", "Z"), | ||
| 75 | static_cast<jboolean>(config.disable_cancel_button)); | ||
| 76 | |||
| 77 | return object; | ||
| 78 | } | ||
| 79 | |||
| 80 | AndroidKeyboard::ResultData AndroidKeyboard::ResultData::CreateFromFrontend(jobject object) { | ||
| 81 | JNIEnv* env = GetEnvForThread(); | ||
| 82 | const jstring string = reinterpret_cast<jstring>(env->GetObjectField( | ||
| 83 | object, env->GetFieldID(s_keyboard_data_class, "text", "Ljava/lang/String;"))); | ||
| 84 | return ResultData{GetJString(env, string), | ||
| 85 | static_cast<Service::AM::Frontend::SwkbdResult>(env->GetIntField( | ||
| 86 | object, env->GetFieldID(s_keyboard_data_class, "result", "I")))}; | ||
| 87 | } | ||
| 88 | |||
| 89 | AndroidKeyboard::~AndroidKeyboard() = default; | ||
| 90 | |||
| 91 | void AndroidKeyboard::InitializeKeyboard( | ||
| 92 | bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, | ||
| 93 | SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) { | ||
| 94 | if (is_inline) { | ||
| 95 | LOG_WARNING( | ||
| 96 | Frontend, | ||
| 97 | "(STUBBED) called, backend requested to initialize the inline software keyboard."); | ||
| 98 | |||
| 99 | submit_inline_callback = std::move(submit_inline_callback_); | ||
| 100 | } else { | ||
| 101 | LOG_WARNING( | ||
| 102 | Frontend, | ||
| 103 | "(STUBBED) called, backend requested to initialize the normal software keyboard."); | ||
| 104 | |||
| 105 | submit_normal_callback = std::move(submit_normal_callback_); | ||
| 106 | } | ||
| 107 | |||
| 108 | parameters = std::move(initialize_parameters); | ||
| 109 | |||
| 110 | LOG_INFO(Frontend, | ||
| 111 | "\nKeyboardInitializeParameters:" | ||
| 112 | "\nok_text={}" | ||
| 113 | "\nheader_text={}" | ||
| 114 | "\nsub_text={}" | ||
| 115 | "\nguide_text={}" | ||
| 116 | "\ninitial_text={}" | ||
| 117 | "\nmax_text_length={}" | ||
| 118 | "\nmin_text_length={}" | ||
| 119 | "\ninitial_cursor_position={}" | ||
| 120 | "\ntype={}" | ||
| 121 | "\npassword_mode={}" | ||
| 122 | "\ntext_draw_type={}" | ||
| 123 | "\nkey_disable_flags={}" | ||
| 124 | "\nuse_blur_background={}" | ||
| 125 | "\nenable_backspace_button={}" | ||
| 126 | "\nenable_return_button={}" | ||
| 127 | "\ndisable_cancel_button={}", | ||
| 128 | Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text), | ||
| 129 | Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text), | ||
| 130 | Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length, | ||
| 131 | parameters.min_text_length, parameters.initial_cursor_position, parameters.type, | ||
| 132 | parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw, | ||
| 133 | parameters.use_blur_background, parameters.enable_backspace_button, | ||
| 134 | parameters.enable_return_button, parameters.disable_cancel_button); | ||
| 135 | } | ||
| 136 | |||
| 137 | void AndroidKeyboard::ShowNormalKeyboard() const { | ||
| 138 | LOG_DEBUG(Frontend, "called, backend requested to show the normal software keyboard."); | ||
| 139 | |||
| 140 | ResultData data{}; | ||
| 141 | |||
| 142 | // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. | ||
| 143 | std::thread([&] { | ||
| 144 | data = ResultData::CreateFromFrontend(GetEnvForThread()->CallStaticObjectMethod( | ||
| 145 | s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters))); | ||
| 146 | }).join(); | ||
| 147 | |||
| 148 | SubmitNormalText(data); | ||
| 149 | } | ||
| 150 | |||
| 151 | void AndroidKeyboard::ShowTextCheckDialog( | ||
| 152 | Service::AM::Frontend::SwkbdTextCheckResult text_check_result, | ||
| 153 | std::u16string text_check_message) const { | ||
| 154 | LOG_WARNING(Frontend, "(STUBBED) called, backend requested to show the text check dialog."); | ||
| 155 | } | ||
| 156 | |||
| 157 | void AndroidKeyboard::ShowInlineKeyboard( | ||
| 158 | Core::Frontend::InlineAppearParameters appear_parameters) const { | ||
| 159 | LOG_WARNING(Frontend, | ||
| 160 | "(STUBBED) called, backend requested to show the inline software keyboard."); | ||
| 161 | |||
| 162 | LOG_INFO(Frontend, | ||
| 163 | "\nInlineAppearParameters:" | ||
| 164 | "\nmax_text_length={}" | ||
| 165 | "\nmin_text_length={}" | ||
| 166 | "\nkey_top_scale_x={}" | ||
| 167 | "\nkey_top_scale_y={}" | ||
| 168 | "\nkey_top_translate_x={}" | ||
| 169 | "\nkey_top_translate_y={}" | ||
| 170 | "\ntype={}" | ||
| 171 | "\nkey_disable_flags={}" | ||
| 172 | "\nkey_top_as_floating={}" | ||
| 173 | "\nenable_backspace_button={}" | ||
| 174 | "\nenable_return_button={}" | ||
| 175 | "\ndisable_cancel_button={}", | ||
| 176 | appear_parameters.max_text_length, appear_parameters.min_text_length, | ||
| 177 | appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, | ||
| 178 | appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, | ||
| 179 | appear_parameters.type, appear_parameters.key_disable_flags.raw, | ||
| 180 | appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, | ||
| 181 | appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); | ||
| 182 | |||
| 183 | // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. | ||
| 184 | m_is_inline_active = true; | ||
| 185 | std::thread([&] { | ||
| 186 | GetEnvForThread()->CallStaticVoidMethod(s_software_keyboard_class, s_swkbd_execute_inline, | ||
| 187 | ToJKeyboardParams(parameters)); | ||
| 188 | }).join(); | ||
| 189 | } | ||
| 190 | |||
| 191 | void AndroidKeyboard::HideInlineKeyboard() const { | ||
| 192 | LOG_WARNING(Frontend, | ||
| 193 | "(STUBBED) called, backend requested to hide the inline software keyboard."); | ||
| 194 | } | ||
| 195 | |||
| 196 | void AndroidKeyboard::InlineTextChanged( | ||
| 197 | Core::Frontend::InlineTextParameters text_parameters) const { | ||
| 198 | LOG_WARNING(Frontend, | ||
| 199 | "(STUBBED) called, backend requested to change the inline keyboard text."); | ||
| 200 | |||
| 201 | LOG_INFO(Frontend, | ||
| 202 | "\nInlineTextParameters:" | ||
| 203 | "\ninput_text={}" | ||
| 204 | "\ncursor_position={}", | ||
| 205 | Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); | ||
| 206 | |||
| 207 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, | ||
| 208 | text_parameters.input_text, text_parameters.cursor_position); | ||
| 209 | } | ||
| 210 | |||
| 211 | void AndroidKeyboard::ExitKeyboard() const { | ||
| 212 | LOG_WARNING(Frontend, "(STUBBED) called, backend requested to exit the software keyboard."); | ||
| 213 | } | ||
| 214 | |||
| 215 | void AndroidKeyboard::SubmitInlineKeyboardText(std::u16string submitted_text) { | ||
| 216 | if (!m_is_inline_active) { | ||
| 217 | return; | ||
| 218 | } | ||
| 219 | |||
| 220 | m_current_text += submitted_text; | ||
| 221 | |||
| 222 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, | ||
| 223 | static_cast<int>(m_current_text.size())); | ||
| 224 | } | ||
| 225 | |||
| 226 | void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) { | ||
| 227 | static constexpr int KEYCODE_BACK = 4; | ||
| 228 | static constexpr int KEYCODE_ENTER = 66; | ||
| 229 | static constexpr int KEYCODE_DEL = 67; | ||
| 230 | |||
| 231 | if (!m_is_inline_active) { | ||
| 232 | return; | ||
| 233 | } | ||
| 234 | |||
| 235 | switch (key_code) { | ||
| 236 | case KEYCODE_BACK: | ||
| 237 | case KEYCODE_ENTER: | ||
| 238 | m_is_inline_active = false; | ||
| 239 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::DecidedEnter, m_current_text, | ||
| 240 | static_cast<s32>(m_current_text.size())); | ||
| 241 | break; | ||
| 242 | case KEYCODE_DEL: | ||
| 243 | m_current_text.pop_back(); | ||
| 244 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, | ||
| 245 | static_cast<int>(m_current_text.size())); | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | void AndroidKeyboard::SubmitNormalText(const ResultData& data) const { | ||
| 251 | submit_normal_callback(data.result, Common::UTF8ToUTF16(data.text), true); | ||
| 252 | } | ||
| 253 | |||
| 254 | void InitJNI(JNIEnv* env) { | ||
| 255 | s_software_keyboard_class = reinterpret_cast<jclass>( | ||
| 256 | env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard"))); | ||
| 257 | s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef( | ||
| 258 | env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig"))); | ||
| 259 | s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef( | ||
| 260 | env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData"))); | ||
| 261 | |||
| 262 | s_swkbd_execute_normal = env->GetStaticMethodID( | ||
| 263 | s_software_keyboard_class, "executeNormal", | ||
| 264 | "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" | ||
| 265 | "applets/keyboard/SoftwareKeyboard$KeyboardData;"); | ||
| 266 | s_swkbd_execute_inline = env->GetStaticMethodID( | ||
| 267 | s_software_keyboard_class, "executeInline", | ||
| 268 | "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V"); | ||
| 269 | } | ||
| 270 | |||
| 271 | void CleanupJNI(JNIEnv* env) { | ||
| 272 | env->DeleteGlobalRef(s_software_keyboard_class); | ||
| 273 | env->DeleteGlobalRef(s_keyboard_config_class); | ||
| 274 | env->DeleteGlobalRef(s_keyboard_data_class); | ||
| 275 | } | ||
| 276 | |||
| 277 | } // namespace Common::Android::SoftwareKeyboard | ||
diff --git a/src/common/android/applets/software_keyboard.h b/src/common/android/applets/software_keyboard.h new file mode 100644 index 000000000..9fd09d27c --- /dev/null +++ b/src/common/android/applets/software_keyboard.h | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <jni.h> | ||
| 7 | |||
| 8 | #include "core/frontend/applets/software_keyboard.h" | ||
| 9 | |||
| 10 | namespace Common::Android::SoftwareKeyboard { | ||
| 11 | |||
| 12 | class AndroidKeyboard final : public Core::Frontend::SoftwareKeyboardApplet { | ||
| 13 | public: | ||
| 14 | ~AndroidKeyboard() override; | ||
| 15 | |||
| 16 | void Close() const override { | ||
| 17 | ExitKeyboard(); | ||
| 18 | } | ||
| 19 | |||
| 20 | void InitializeKeyboard(bool is_inline, | ||
| 21 | Core::Frontend::KeyboardInitializeParameters initialize_parameters, | ||
| 22 | SubmitNormalCallback submit_normal_callback_, | ||
| 23 | SubmitInlineCallback submit_inline_callback_) override; | ||
| 24 | |||
| 25 | void ShowNormalKeyboard() const override; | ||
| 26 | |||
| 27 | void ShowTextCheckDialog(Service::AM::Frontend::SwkbdTextCheckResult text_check_result, | ||
| 28 | std::u16string text_check_message) const override; | ||
| 29 | |||
| 30 | void ShowInlineKeyboard( | ||
| 31 | Core::Frontend::InlineAppearParameters appear_parameters) const override; | ||
| 32 | |||
| 33 | void HideInlineKeyboard() const override; | ||
| 34 | |||
| 35 | void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; | ||
| 36 | |||
| 37 | void ExitKeyboard() const override; | ||
| 38 | |||
| 39 | void SubmitInlineKeyboardText(std::u16string submitted_text); | ||
| 40 | |||
| 41 | void SubmitInlineKeyboardInput(int key_code); | ||
| 42 | |||
| 43 | private: | ||
| 44 | struct ResultData { | ||
| 45 | static ResultData CreateFromFrontend(jobject object); | ||
| 46 | |||
| 47 | std::string text; | ||
| 48 | Service::AM::Frontend::SwkbdResult result{}; | ||
| 49 | }; | ||
| 50 | |||
| 51 | void SubmitNormalText(const ResultData& result) const; | ||
| 52 | |||
| 53 | Core::Frontend::KeyboardInitializeParameters parameters{}; | ||
| 54 | |||
| 55 | mutable SubmitNormalCallback submit_normal_callback; | ||
| 56 | mutable SubmitInlineCallback submit_inline_callback; | ||
| 57 | |||
| 58 | private: | ||
| 59 | mutable bool m_is_inline_active{}; | ||
| 60 | std::u16string m_current_text; | ||
| 61 | }; | ||
| 62 | |||
| 63 | // Should be called in JNI_Load | ||
| 64 | void InitJNI(JNIEnv* env); | ||
| 65 | |||
| 66 | // Should be called in JNI_Unload | ||
| 67 | void CleanupJNI(JNIEnv* env); | ||
| 68 | |||
| 69 | } // namespace Common::Android::SoftwareKeyboard | ||
| 70 | |||
| 71 | // Native function calls | ||
| 72 | extern "C" { | ||
| 73 | JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateFilters( | ||
| 74 | JNIEnv* env, jclass clazz, jstring text); | ||
| 75 | |||
| 76 | JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateInput( | ||
| 77 | JNIEnv* env, jclass clazz, jstring text); | ||
| 78 | } | ||
diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp new file mode 100644 index 000000000..f39262db9 --- /dev/null +++ b/src/common/android/id_cache.cpp | |||
| @@ -0,0 +1,428 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <jni.h> | ||
| 5 | |||
| 6 | #include "applets/software_keyboard.h" | ||
| 7 | #include "common/android/id_cache.h" | ||
| 8 | #include "common/assert.h" | ||
| 9 | #include "common/fs/fs_android.h" | ||
| 10 | #include "video_core/rasterizer_interface.h" | ||
| 11 | |||
| 12 | static JavaVM* s_java_vm; | ||
| 13 | static jclass s_native_library_class; | ||
| 14 | static jclass s_disk_cache_progress_class; | ||
| 15 | static jclass s_load_callback_stage_class; | ||
| 16 | static jclass s_game_dir_class; | ||
| 17 | static jmethodID s_game_dir_constructor; | ||
| 18 | static jmethodID s_exit_emulation_activity; | ||
| 19 | static jmethodID s_disk_cache_load_progress; | ||
| 20 | static jmethodID s_on_emulation_started; | ||
| 21 | static jmethodID s_on_emulation_stopped; | ||
| 22 | static jmethodID s_on_program_changed; | ||
| 23 | |||
| 24 | static jclass s_game_class; | ||
| 25 | static jmethodID s_game_constructor; | ||
| 26 | static jfieldID s_game_title_field; | ||
| 27 | static jfieldID s_game_path_field; | ||
| 28 | static jfieldID s_game_program_id_field; | ||
| 29 | static jfieldID s_game_developer_field; | ||
| 30 | static jfieldID s_game_version_field; | ||
| 31 | static jfieldID s_game_is_homebrew_field; | ||
| 32 | |||
| 33 | static jclass s_string_class; | ||
| 34 | static jclass s_pair_class; | ||
| 35 | static jmethodID s_pair_constructor; | ||
| 36 | static jfieldID s_pair_first_field; | ||
| 37 | static jfieldID s_pair_second_field; | ||
| 38 | |||
| 39 | static jclass s_overlay_control_data_class; | ||
| 40 | static jmethodID s_overlay_control_data_constructor; | ||
| 41 | static jfieldID s_overlay_control_data_id_field; | ||
| 42 | static jfieldID s_overlay_control_data_enabled_field; | ||
| 43 | static jfieldID s_overlay_control_data_landscape_position_field; | ||
| 44 | static jfieldID s_overlay_control_data_portrait_position_field; | ||
| 45 | static jfieldID s_overlay_control_data_foldable_position_field; | ||
| 46 | |||
| 47 | static jclass s_patch_class; | ||
| 48 | static jmethodID s_patch_constructor; | ||
| 49 | static jfieldID s_patch_enabled_field; | ||
| 50 | static jfieldID s_patch_name_field; | ||
| 51 | static jfieldID s_patch_version_field; | ||
| 52 | static jfieldID s_patch_type_field; | ||
| 53 | static jfieldID s_patch_program_id_field; | ||
| 54 | static jfieldID s_patch_title_id_field; | ||
| 55 | |||
| 56 | static jclass s_double_class; | ||
| 57 | static jmethodID s_double_constructor; | ||
| 58 | static jfieldID s_double_value_field; | ||
| 59 | |||
| 60 | static jclass s_integer_class; | ||
| 61 | static jmethodID s_integer_constructor; | ||
| 62 | static jfieldID s_integer_value_field; | ||
| 63 | |||
| 64 | static jclass s_boolean_class; | ||
| 65 | static jmethodID s_boolean_constructor; | ||
| 66 | static jfieldID s_boolean_value_field; | ||
| 67 | |||
| 68 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | ||
| 69 | |||
| 70 | namespace Common::Android { | ||
| 71 | |||
| 72 | JNIEnv* GetEnvForThread() { | ||
| 73 | thread_local static struct OwnedEnv { | ||
| 74 | OwnedEnv() { | ||
| 75 | status = s_java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); | ||
| 76 | if (status == JNI_EDETACHED) | ||
| 77 | s_java_vm->AttachCurrentThread(&env, nullptr); | ||
| 78 | } | ||
| 79 | |||
| 80 | ~OwnedEnv() { | ||
| 81 | if (status == JNI_EDETACHED) | ||
| 82 | s_java_vm->DetachCurrentThread(); | ||
| 83 | } | ||
| 84 | |||
| 85 | int status; | ||
| 86 | JNIEnv* env = nullptr; | ||
| 87 | } owned; | ||
| 88 | return owned.env; | ||
| 89 | } | ||
| 90 | |||
| 91 | jclass GetNativeLibraryClass() { | ||
| 92 | return s_native_library_class; | ||
| 93 | } | ||
| 94 | |||
| 95 | jclass GetDiskCacheProgressClass() { | ||
| 96 | return s_disk_cache_progress_class; | ||
| 97 | } | ||
| 98 | |||
| 99 | jclass GetDiskCacheLoadCallbackStageClass() { | ||
| 100 | return s_load_callback_stage_class; | ||
| 101 | } | ||
| 102 | |||
| 103 | jclass GetGameDirClass() { | ||
| 104 | return s_game_dir_class; | ||
| 105 | } | ||
| 106 | |||
| 107 | jmethodID GetGameDirConstructor() { | ||
| 108 | return s_game_dir_constructor; | ||
| 109 | } | ||
| 110 | |||
| 111 | jmethodID GetExitEmulationActivity() { | ||
| 112 | return s_exit_emulation_activity; | ||
| 113 | } | ||
| 114 | |||
| 115 | jmethodID GetDiskCacheLoadProgress() { | ||
| 116 | return s_disk_cache_load_progress; | ||
| 117 | } | ||
| 118 | |||
| 119 | jmethodID GetOnEmulationStarted() { | ||
| 120 | return s_on_emulation_started; | ||
| 121 | } | ||
| 122 | |||
| 123 | jmethodID GetOnEmulationStopped() { | ||
| 124 | return s_on_emulation_stopped; | ||
| 125 | } | ||
| 126 | |||
| 127 | jmethodID GetOnProgramChanged() { | ||
| 128 | return s_on_program_changed; | ||
| 129 | } | ||
| 130 | |||
| 131 | jclass GetGameClass() { | ||
| 132 | return s_game_class; | ||
| 133 | } | ||
| 134 | |||
| 135 | jmethodID GetGameConstructor() { | ||
| 136 | return s_game_constructor; | ||
| 137 | } | ||
| 138 | |||
| 139 | jfieldID GetGameTitleField() { | ||
| 140 | return s_game_title_field; | ||
| 141 | } | ||
| 142 | |||
| 143 | jfieldID GetGamePathField() { | ||
| 144 | return s_game_path_field; | ||
| 145 | } | ||
| 146 | |||
| 147 | jfieldID GetGameProgramIdField() { | ||
| 148 | return s_game_program_id_field; | ||
| 149 | } | ||
| 150 | |||
| 151 | jfieldID GetGameDeveloperField() { | ||
| 152 | return s_game_developer_field; | ||
| 153 | } | ||
| 154 | |||
| 155 | jfieldID GetGameVersionField() { | ||
| 156 | return s_game_version_field; | ||
| 157 | } | ||
| 158 | |||
| 159 | jfieldID GetGameIsHomebrewField() { | ||
| 160 | return s_game_is_homebrew_field; | ||
| 161 | } | ||
| 162 | |||
| 163 | jclass GetStringClass() { | ||
| 164 | return s_string_class; | ||
| 165 | } | ||
| 166 | |||
| 167 | jclass GetPairClass() { | ||
| 168 | return s_pair_class; | ||
| 169 | } | ||
| 170 | |||
| 171 | jmethodID GetPairConstructor() { | ||
| 172 | return s_pair_constructor; | ||
| 173 | } | ||
| 174 | |||
| 175 | jfieldID GetPairFirstField() { | ||
| 176 | return s_pair_first_field; | ||
| 177 | } | ||
| 178 | |||
| 179 | jfieldID GetPairSecondField() { | ||
| 180 | return s_pair_second_field; | ||
| 181 | } | ||
| 182 | |||
| 183 | jclass GetOverlayControlDataClass() { | ||
| 184 | return s_overlay_control_data_class; | ||
| 185 | } | ||
| 186 | |||
| 187 | jmethodID GetOverlayControlDataConstructor() { | ||
| 188 | return s_overlay_control_data_constructor; | ||
| 189 | } | ||
| 190 | |||
| 191 | jfieldID GetOverlayControlDataIdField() { | ||
| 192 | return s_overlay_control_data_id_field; | ||
| 193 | } | ||
| 194 | |||
| 195 | jfieldID GetOverlayControlDataEnabledField() { | ||
| 196 | return s_overlay_control_data_enabled_field; | ||
| 197 | } | ||
| 198 | |||
| 199 | jfieldID GetOverlayControlDataLandscapePositionField() { | ||
| 200 | return s_overlay_control_data_landscape_position_field; | ||
| 201 | } | ||
| 202 | |||
| 203 | jfieldID GetOverlayControlDataPortraitPositionField() { | ||
| 204 | return s_overlay_control_data_portrait_position_field; | ||
| 205 | } | ||
| 206 | |||
| 207 | jfieldID GetOverlayControlDataFoldablePositionField() { | ||
| 208 | return s_overlay_control_data_foldable_position_field; | ||
| 209 | } | ||
| 210 | |||
| 211 | jclass GetPatchClass() { | ||
| 212 | return s_patch_class; | ||
| 213 | } | ||
| 214 | |||
| 215 | jmethodID GetPatchConstructor() { | ||
| 216 | return s_patch_constructor; | ||
| 217 | } | ||
| 218 | |||
| 219 | jfieldID GetPatchEnabledField() { | ||
| 220 | return s_patch_enabled_field; | ||
| 221 | } | ||
| 222 | |||
| 223 | jfieldID GetPatchNameField() { | ||
| 224 | return s_patch_name_field; | ||
| 225 | } | ||
| 226 | |||
| 227 | jfieldID GetPatchVersionField() { | ||
| 228 | return s_patch_version_field; | ||
| 229 | } | ||
| 230 | |||
| 231 | jfieldID GetPatchTypeField() { | ||
| 232 | return s_patch_type_field; | ||
| 233 | } | ||
| 234 | |||
| 235 | jfieldID GetPatchProgramIdField() { | ||
| 236 | return s_patch_program_id_field; | ||
| 237 | } | ||
| 238 | |||
| 239 | jfieldID GetPatchTitleIdField() { | ||
| 240 | return s_patch_title_id_field; | ||
| 241 | } | ||
| 242 | |||
| 243 | jclass GetDoubleClass() { | ||
| 244 | return s_double_class; | ||
| 245 | } | ||
| 246 | |||
| 247 | jmethodID GetDoubleConstructor() { | ||
| 248 | return s_double_constructor; | ||
| 249 | } | ||
| 250 | |||
| 251 | jfieldID GetDoubleValueField() { | ||
| 252 | return s_double_value_field; | ||
| 253 | } | ||
| 254 | |||
| 255 | jclass GetIntegerClass() { | ||
| 256 | return s_integer_class; | ||
| 257 | } | ||
| 258 | |||
| 259 | jmethodID GetIntegerConstructor() { | ||
| 260 | return s_integer_constructor; | ||
| 261 | } | ||
| 262 | |||
| 263 | jfieldID GetIntegerValueField() { | ||
| 264 | return s_integer_value_field; | ||
| 265 | } | ||
| 266 | |||
| 267 | jclass GetBooleanClass() { | ||
| 268 | return s_boolean_class; | ||
| 269 | } | ||
| 270 | |||
| 271 | jmethodID GetBooleanConstructor() { | ||
| 272 | return s_boolean_constructor; | ||
| 273 | } | ||
| 274 | |||
| 275 | jfieldID GetBooleanValueField() { | ||
| 276 | return s_boolean_value_field; | ||
| 277 | } | ||
| 278 | |||
| 279 | #ifdef __cplusplus | ||
| 280 | extern "C" { | ||
| 281 | #endif | ||
| 282 | |||
| 283 | jint JNI_OnLoad(JavaVM* vm, void* reserved) { | ||
| 284 | s_java_vm = vm; | ||
| 285 | |||
| 286 | JNIEnv* env; | ||
| 287 | if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK) | ||
| 288 | return JNI_ERR; | ||
| 289 | |||
| 290 | // Initialize Java classes | ||
| 291 | const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); | ||
| 292 | s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class)); | ||
| 293 | s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef( | ||
| 294 | env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); | ||
| 295 | s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass( | ||
| 296 | "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); | ||
| 297 | |||
| 298 | const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); | ||
| 299 | s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class)); | ||
| 300 | s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", "(Ljava/lang/String;Z)V"); | ||
| 301 | env->DeleteLocalRef(game_dir_class); | ||
| 302 | |||
| 303 | // Initialize methods | ||
| 304 | s_exit_emulation_activity = | ||
| 305 | env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); | ||
| 306 | s_disk_cache_load_progress = | ||
| 307 | env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); | ||
| 308 | s_on_emulation_started = | ||
| 309 | env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); | ||
| 310 | s_on_emulation_stopped = | ||
| 311 | env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); | ||
| 312 | s_on_program_changed = | ||
| 313 | env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); | ||
| 314 | |||
| 315 | const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); | ||
| 316 | s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class)); | ||
| 317 | s_game_constructor = env->GetMethodID(game_class, "<init>", | ||
| 318 | "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" | ||
| 319 | "String;Ljava/lang/String;Ljava/lang/String;Z)V"); | ||
| 320 | s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); | ||
| 321 | s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); | ||
| 322 | s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); | ||
| 323 | s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); | ||
| 324 | s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); | ||
| 325 | s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); | ||
| 326 | env->DeleteLocalRef(game_class); | ||
| 327 | |||
| 328 | const jclass string_class = env->FindClass("java/lang/String"); | ||
| 329 | s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class)); | ||
| 330 | env->DeleteLocalRef(string_class); | ||
| 331 | |||
| 332 | const jclass pair_class = env->FindClass("kotlin/Pair"); | ||
| 333 | s_pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class)); | ||
| 334 | s_pair_constructor = | ||
| 335 | env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V"); | ||
| 336 | s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); | ||
| 337 | s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); | ||
| 338 | env->DeleteLocalRef(pair_class); | ||
| 339 | |||
| 340 | const jclass overlay_control_data_class = | ||
| 341 | env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); | ||
| 342 | s_overlay_control_data_class = | ||
| 343 | reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class)); | ||
| 344 | s_overlay_control_data_constructor = | ||
| 345 | env->GetMethodID(overlay_control_data_class, "<init>", | ||
| 346 | "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); | ||
| 347 | s_overlay_control_data_id_field = | ||
| 348 | env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); | ||
| 349 | s_overlay_control_data_enabled_field = | ||
| 350 | env->GetFieldID(overlay_control_data_class, "enabled", "Z"); | ||
| 351 | s_overlay_control_data_landscape_position_field = | ||
| 352 | env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); | ||
| 353 | s_overlay_control_data_portrait_position_field = | ||
| 354 | env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); | ||
| 355 | s_overlay_control_data_foldable_position_field = | ||
| 356 | env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); | ||
| 357 | env->DeleteLocalRef(overlay_control_data_class); | ||
| 358 | |||
| 359 | const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); | ||
| 360 | s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class)); | ||
| 361 | s_patch_constructor = env->GetMethodID( | ||
| 362 | patch_class, "<init>", | ||
| 363 | "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); | ||
| 364 | s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); | ||
| 365 | s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); | ||
| 366 | s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); | ||
| 367 | s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); | ||
| 368 | s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); | ||
| 369 | s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); | ||
| 370 | env->DeleteLocalRef(patch_class); | ||
| 371 | |||
| 372 | const jclass double_class = env->FindClass("java/lang/Double"); | ||
| 373 | s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class)); | ||
| 374 | s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V"); | ||
| 375 | s_double_value_field = env->GetFieldID(double_class, "value", "D"); | ||
| 376 | env->DeleteLocalRef(double_class); | ||
| 377 | |||
| 378 | const jclass int_class = env->FindClass("java/lang/Integer"); | ||
| 379 | s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class)); | ||
| 380 | s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V"); | ||
| 381 | s_integer_value_field = env->GetFieldID(int_class, "value", "I"); | ||
| 382 | env->DeleteLocalRef(int_class); | ||
| 383 | |||
| 384 | const jclass boolean_class = env->FindClass("java/lang/Boolean"); | ||
| 385 | s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class)); | ||
| 386 | s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V"); | ||
| 387 | s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); | ||
| 388 | env->DeleteLocalRef(boolean_class); | ||
| 389 | |||
| 390 | // Initialize Android Storage | ||
| 391 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | ||
| 392 | |||
| 393 | // Initialize applets | ||
| 394 | Common::Android::SoftwareKeyboard::InitJNI(env); | ||
| 395 | |||
| 396 | return JNI_VERSION; | ||
| 397 | } | ||
| 398 | |||
| 399 | void JNI_OnUnload(JavaVM* vm, void* reserved) { | ||
| 400 | JNIEnv* env; | ||
| 401 | if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK) { | ||
| 402 | return; | ||
| 403 | } | ||
| 404 | |||
| 405 | // UnInitialize Android Storage | ||
| 406 | Common::FS::Android::UnRegisterCallbacks(); | ||
| 407 | env->DeleteGlobalRef(s_native_library_class); | ||
| 408 | env->DeleteGlobalRef(s_disk_cache_progress_class); | ||
| 409 | env->DeleteGlobalRef(s_load_callback_stage_class); | ||
| 410 | env->DeleteGlobalRef(s_game_dir_class); | ||
| 411 | env->DeleteGlobalRef(s_game_class); | ||
| 412 | env->DeleteGlobalRef(s_string_class); | ||
| 413 | env->DeleteGlobalRef(s_pair_class); | ||
| 414 | env->DeleteGlobalRef(s_overlay_control_data_class); | ||
| 415 | env->DeleteGlobalRef(s_patch_class); | ||
| 416 | env->DeleteGlobalRef(s_double_class); | ||
| 417 | env->DeleteGlobalRef(s_integer_class); | ||
| 418 | env->DeleteGlobalRef(s_boolean_class); | ||
| 419 | |||
| 420 | // UnInitialize applets | ||
| 421 | SoftwareKeyboard::CleanupJNI(env); | ||
| 422 | } | ||
| 423 | |||
| 424 | #ifdef __cplusplus | ||
| 425 | } | ||
| 426 | #endif | ||
| 427 | |||
| 428 | } // namespace Common::Android | ||
diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h new file mode 100644 index 000000000..47802f96c --- /dev/null +++ b/src/common/android/id_cache.h | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <future> | ||
| 7 | #include <jni.h> | ||
| 8 | |||
| 9 | #include "video_core/rasterizer_interface.h" | ||
| 10 | |||
| 11 | namespace Common::Android { | ||
| 12 | |||
| 13 | JNIEnv* GetEnvForThread(); | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Starts a new thread to run JNI. Intended to be used when you must run JNI from a fiber. | ||
| 17 | * @tparam T Typename of the return value for the work param | ||
| 18 | * @param work Lambda that runs JNI code. This function will take care of attaching this thread to | ||
| 19 | * the JVM | ||
| 20 | * @return The result from the work lambda param | ||
| 21 | */ | ||
| 22 | template <typename T = void> | ||
| 23 | T RunJNIOnFiber(const std::function<T(JNIEnv*)>& work) { | ||
| 24 | std::future<T> j_result = std::async(std::launch::async, [&] { | ||
| 25 | auto env = GetEnvForThread(); | ||
| 26 | return work(env); | ||
| 27 | }); | ||
| 28 | return j_result.get(); | ||
| 29 | } | ||
| 30 | |||
| 31 | jclass GetNativeLibraryClass(); | ||
| 32 | |||
| 33 | jclass GetDiskCacheProgressClass(); | ||
| 34 | jclass GetDiskCacheLoadCallbackStageClass(); | ||
| 35 | jclass GetGameDirClass(); | ||
| 36 | jmethodID GetGameDirConstructor(); | ||
| 37 | jmethodID GetDiskCacheLoadProgress(); | ||
| 38 | |||
| 39 | jmethodID GetExitEmulationActivity(); | ||
| 40 | jmethodID GetOnEmulationStarted(); | ||
| 41 | jmethodID GetOnEmulationStopped(); | ||
| 42 | jmethodID GetOnProgramChanged(); | ||
| 43 | |||
| 44 | jclass GetGameClass(); | ||
| 45 | jmethodID GetGameConstructor(); | ||
| 46 | jfieldID GetGameTitleField(); | ||
| 47 | jfieldID GetGamePathField(); | ||
| 48 | jfieldID GetGameProgramIdField(); | ||
| 49 | jfieldID GetGameDeveloperField(); | ||
| 50 | jfieldID GetGameVersionField(); | ||
| 51 | jfieldID GetGameIsHomebrewField(); | ||
| 52 | |||
| 53 | jclass GetStringClass(); | ||
| 54 | jclass GetPairClass(); | ||
| 55 | jmethodID GetPairConstructor(); | ||
| 56 | jfieldID GetPairFirstField(); | ||
| 57 | jfieldID GetPairSecondField(); | ||
| 58 | |||
| 59 | jclass GetOverlayControlDataClass(); | ||
| 60 | jmethodID GetOverlayControlDataConstructor(); | ||
| 61 | jfieldID GetOverlayControlDataIdField(); | ||
| 62 | jfieldID GetOverlayControlDataEnabledField(); | ||
| 63 | jfieldID GetOverlayControlDataLandscapePositionField(); | ||
| 64 | jfieldID GetOverlayControlDataPortraitPositionField(); | ||
| 65 | jfieldID GetOverlayControlDataFoldablePositionField(); | ||
| 66 | |||
| 67 | jclass GetPatchClass(); | ||
| 68 | jmethodID GetPatchConstructor(); | ||
| 69 | jfieldID GetPatchEnabledField(); | ||
| 70 | jfieldID GetPatchNameField(); | ||
| 71 | jfieldID GetPatchVersionField(); | ||
| 72 | jfieldID GetPatchTypeField(); | ||
| 73 | jfieldID GetPatchProgramIdField(); | ||
| 74 | jfieldID GetPatchTitleIdField(); | ||
| 75 | |||
| 76 | jclass GetDoubleClass(); | ||
| 77 | jmethodID GetDoubleConstructor(); | ||
| 78 | jfieldID GetDoubleValueField(); | ||
| 79 | |||
| 80 | jclass GetIntegerClass(); | ||
| 81 | jmethodID GetIntegerConstructor(); | ||
| 82 | jfieldID GetIntegerValueField(); | ||
| 83 | |||
| 84 | jclass GetBooleanClass(); | ||
| 85 | jmethodID GetBooleanConstructor(); | ||
| 86 | jfieldID GetBooleanValueField(); | ||
| 87 | |||
| 88 | } // namespace Common::Android | ||
diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp index 1dd826a4a..9a8053222 100644 --- a/src/common/fs/fs_android.cpp +++ b/src/common/fs/fs_android.cpp | |||
| @@ -1,63 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "common/android/android_common.h" | ||
| 5 | #include "common/android/id_cache.h" | ||
| 6 | #include "common/assert.h" | ||
| 4 | #include "common/fs/fs_android.h" | 7 | #include "common/fs/fs_android.h" |
| 5 | #include "common/string_util.h" | 8 | #include "common/string_util.h" |
| 6 | 9 | ||
| 7 | namespace Common::FS::Android { | 10 | namespace Common::FS::Android { |
| 8 | 11 | ||
| 9 | JNIEnv* GetEnvForThread() { | ||
| 10 | thread_local static struct OwnedEnv { | ||
| 11 | OwnedEnv() { | ||
| 12 | status = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); | ||
| 13 | if (status == JNI_EDETACHED) | ||
| 14 | g_jvm->AttachCurrentThread(&env, nullptr); | ||
| 15 | } | ||
| 16 | |||
| 17 | ~OwnedEnv() { | ||
| 18 | if (status == JNI_EDETACHED) | ||
| 19 | g_jvm->DetachCurrentThread(); | ||
| 20 | } | ||
| 21 | |||
| 22 | int status; | ||
| 23 | JNIEnv* env = nullptr; | ||
| 24 | } owned; | ||
| 25 | return owned.env; | ||
| 26 | } | ||
| 27 | |||
| 28 | void RegisterCallbacks(JNIEnv* env, jclass clazz) { | 12 | void RegisterCallbacks(JNIEnv* env, jclass clazz) { |
| 29 | env->GetJavaVM(&g_jvm); | 13 | env->GetJavaVM(&g_jvm); |
| 30 | native_library = clazz; | 14 | native_library = clazz; |
| 31 | 15 | ||
| 32 | #define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \ | 16 | s_get_parent_directory = env->GetStaticMethodID(native_library, "getParentDirectory", |
| 33 | F(JMethodID, JMethodName, Signature) | 17 | "(Ljava/lang/String;)Ljava/lang/String;"); |
| 34 | #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ | 18 | s_get_filename = env->GetStaticMethodID(native_library, "getFilename", |
| 35 | F(JMethodID, JMethodName, Signature) | 19 | "(Ljava/lang/String;)Ljava/lang/String;"); |
| 36 | #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ | 20 | s_get_size = env->GetStaticMethodID(native_library, "getSize", "(Ljava/lang/String;)J"); |
| 37 | F(JMethodID, JMethodName, Signature) | 21 | s_is_directory = env->GetStaticMethodID(native_library, "isDirectory", "(Ljava/lang/String;)Z"); |
| 38 | #define F(JMethodID, JMethodName, Signature) \ | 22 | s_file_exists = env->GetStaticMethodID(native_library, "exists", "(Ljava/lang/String;)Z"); |
| 39 | JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); | 23 | s_open_content_uri = env->GetStaticMethodID(native_library, "openContentUri", |
| 40 | ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) | 24 | "(Ljava/lang/String;Ljava/lang/String;)I"); |
| 41 | ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) | ||
| 42 | ANDROID_STORAGE_FUNCTIONS(FS) | ||
| 43 | #undef F | ||
| 44 | #undef FS | ||
| 45 | #undef FR | ||
| 46 | #undef FH | ||
| 47 | } | 25 | } |
| 48 | 26 | ||
| 49 | void UnRegisterCallbacks() { | 27 | void UnRegisterCallbacks() { |
| 50 | #define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID) | 28 | s_get_parent_directory = nullptr; |
| 51 | #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) | 29 | s_get_filename = nullptr; |
| 52 | #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) | 30 | |
| 53 | #define F(JMethodID) JMethodID = nullptr; | 31 | s_get_size = nullptr; |
| 54 | ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) | 32 | s_is_directory = nullptr; |
| 55 | ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) | 33 | s_file_exists = nullptr; |
| 56 | ANDROID_STORAGE_FUNCTIONS(FS) | 34 | |
| 57 | #undef F | 35 | s_open_content_uri = nullptr; |
| 58 | #undef FS | ||
| 59 | #undef FR | ||
| 60 | #undef FH | ||
| 61 | } | 36 | } |
| 62 | 37 | ||
| 63 | bool IsContentUri(const std::string& path) { | 38 | bool IsContentUri(const std::string& path) { |
| @@ -69,8 +44,8 @@ bool IsContentUri(const std::string& path) { | |||
| 69 | return path.find(prefix) == 0; | 44 | return path.find(prefix) == 0; |
| 70 | } | 45 | } |
| 71 | 46 | ||
| 72 | int OpenContentUri(const std::string& filepath, OpenMode openmode) { | 47 | s32 OpenContentUri(const std::string& filepath, OpenMode openmode) { |
| 73 | if (open_content_uri == nullptr) | 48 | if (s_open_content_uri == nullptr) |
| 74 | return -1; | 49 | return -1; |
| 75 | 50 | ||
| 76 | const char* mode = ""; | 51 | const char* mode = ""; |
| @@ -82,50 +57,66 @@ int OpenContentUri(const std::string& filepath, OpenMode openmode) { | |||
| 82 | UNIMPLEMENTED(); | 57 | UNIMPLEMENTED(); |
| 83 | return -1; | 58 | return -1; |
| 84 | } | 59 | } |
| 85 | auto env = GetEnvForThread(); | 60 | auto env = Common::Android::GetEnvForThread(); |
| 86 | jstring j_filepath = env->NewStringUTF(filepath.c_str()); | 61 | jstring j_filepath = Common::Android::ToJString(env, filepath); |
| 87 | jstring j_mode = env->NewStringUTF(mode); | 62 | jstring j_mode = Common::Android::ToJString(env, mode); |
| 88 | return env->CallStaticIntMethod(native_library, open_content_uri, j_filepath, j_mode); | 63 | return env->CallStaticIntMethod(native_library, s_open_content_uri, j_filepath, j_mode); |
| 64 | } | ||
| 65 | |||
| 66 | u64 GetSize(const std::string& filepath) { | ||
| 67 | if (s_get_size == nullptr) { | ||
| 68 | return 0; | ||
| 69 | } | ||
| 70 | auto env = Common::Android::GetEnvForThread(); | ||
| 71 | return static_cast<u64>(env->CallStaticLongMethod( | ||
| 72 | native_library, s_get_size, | ||
| 73 | Common::Android::ToJString(Common::Android::GetEnvForThread(), filepath))); | ||
| 74 | } | ||
| 75 | |||
| 76 | bool IsDirectory(const std::string& filepath) { | ||
| 77 | if (s_is_directory == nullptr) { | ||
| 78 | return 0; | ||
| 79 | } | ||
| 80 | auto env = Common::Android::GetEnvForThread(); | ||
| 81 | return env->CallStaticBooleanMethod( | ||
| 82 | native_library, s_is_directory, | ||
| 83 | Common::Android::ToJString(Common::Android::GetEnvForThread(), filepath)); | ||
| 89 | } | 84 | } |
| 90 | 85 | ||
| 91 | #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ | 86 | bool Exists(const std::string& filepath) { |
| 92 | F(FunctionName, ReturnValue, JMethodID, Caller) | 87 | if (s_file_exists == nullptr) { |
| 93 | #define F(FunctionName, ReturnValue, JMethodID, Caller) \ | 88 | return 0; |
| 94 | ReturnValue FunctionName(const std::string& filepath) { \ | ||
| 95 | if (JMethodID == nullptr) { \ | ||
| 96 | return 0; \ | ||
| 97 | } \ | ||
| 98 | auto env = GetEnvForThread(); \ | ||
| 99 | jstring j_filepath = env->NewStringUTF(filepath.c_str()); \ | ||
| 100 | return env->Caller(native_library, JMethodID, j_filepath); \ | ||
| 101 | } | 89 | } |
| 102 | ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) | 90 | auto env = Common::Android::GetEnvForThread(); |
| 103 | #undef F | 91 | return env->CallStaticBooleanMethod( |
| 104 | #undef FR | 92 | native_library, s_file_exists, |
| 105 | 93 | Common::Android::ToJString(Common::Android::GetEnvForThread(), filepath)); | |
| 106 | #define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \ | 94 | } |
| 107 | F(FunctionName, JMethodID, Caller) | 95 | |
| 108 | #define F(FunctionName, JMethodID, Caller) \ | 96 | std::string GetParentDirectory(const std::string& filepath) { |
| 109 | std::string FunctionName(const std::string& filepath) { \ | 97 | if (s_get_parent_directory == nullptr) { |
| 110 | if (JMethodID == nullptr) { \ | 98 | return 0; |
| 111 | return 0; \ | ||
| 112 | } \ | ||
| 113 | auto env = GetEnvForThread(); \ | ||
| 114 | jstring j_filepath = env->NewStringUTF(filepath.c_str()); \ | ||
| 115 | jstring j_return = \ | ||
| 116 | static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \ | ||
| 117 | if (!j_return) { \ | ||
| 118 | return {}; \ | ||
| 119 | } \ | ||
| 120 | const jchar* jchars = env->GetStringChars(j_return, nullptr); \ | ||
| 121 | const jsize length = env->GetStringLength(j_return); \ | ||
| 122 | const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \ | ||
| 123 | const std::string converted_string = Common::UTF16ToUTF8(string_view); \ | ||
| 124 | env->ReleaseStringChars(j_return, jchars); \ | ||
| 125 | return converted_string; \ | ||
| 126 | } | 99 | } |
| 127 | ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) | 100 | auto env = Common::Android::GetEnvForThread(); |
| 128 | #undef F | 101 | jstring j_return = static_cast<jstring>(env->CallStaticObjectMethod( |
| 129 | #undef FH | 102 | native_library, s_get_parent_directory, Common::Android::ToJString(env, filepath))); |
| 103 | if (!j_return) { | ||
| 104 | return {}; | ||
| 105 | } | ||
| 106 | return Common::Android::GetJString(env, j_return); | ||
| 107 | } | ||
| 108 | |||
| 109 | std::string GetFilename(const std::string& filepath) { | ||
| 110 | if (s_get_filename == nullptr) { | ||
| 111 | return 0; | ||
| 112 | } | ||
| 113 | auto env = Common::Android::GetEnvForThread(); | ||
| 114 | jstring j_return = static_cast<jstring>(env->CallStaticObjectMethod( | ||
| 115 | native_library, s_get_filename, Common::Android::ToJString(env, filepath))); | ||
| 116 | if (!j_return) { | ||
| 117 | return {}; | ||
| 118 | } | ||
| 119 | return Common::Android::GetJString(env, j_return); | ||
| 120 | } | ||
| 130 | 121 | ||
| 131 | } // namespace Common::FS::Android | 122 | } // namespace Common::FS::Android |
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h index 2c9234313..b33f4beb8 100644 --- a/src/common/fs/fs_android.h +++ b/src/common/fs/fs_android.h | |||
| @@ -7,38 +7,17 @@ | |||
| 7 | #include <vector> | 7 | #include <vector> |
| 8 | #include <jni.h> | 8 | #include <jni.h> |
| 9 | 9 | ||
| 10 | #define ANDROID_STORAGE_FUNCTIONS(V) \ | ||
| 11 | V(OpenContentUri, int, (const std::string& filepath, OpenMode openmode), open_content_uri, \ | ||
| 12 | "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") | ||
| 13 | |||
| 14 | #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ | ||
| 15 | V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \ | ||
| 16 | V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \ | ||
| 17 | "(Ljava/lang/String;)Z") \ | ||
| 18 | V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") | ||
| 19 | |||
| 20 | #define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \ | ||
| 21 | V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \ | ||
| 22 | "(Ljava/lang/String;)Ljava/lang/String;") \ | ||
| 23 | V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \ | ||
| 24 | "(Ljava/lang/String;)Ljava/lang/String;") | ||
| 25 | |||
| 26 | namespace Common::FS::Android { | 10 | namespace Common::FS::Android { |
| 27 | 11 | ||
| 28 | static JavaVM* g_jvm = nullptr; | 12 | static JavaVM* g_jvm = nullptr; |
| 29 | static jclass native_library = nullptr; | 13 | static jclass native_library = nullptr; |
| 30 | 14 | ||
| 31 | #define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID) | 15 | static jmethodID s_get_parent_directory; |
| 32 | #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) | 16 | static jmethodID s_get_filename; |
| 33 | #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) | 17 | static jmethodID s_get_size; |
| 34 | #define F(JMethodID) static jmethodID JMethodID = nullptr; | 18 | static jmethodID s_is_directory; |
| 35 | ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) | 19 | static jmethodID s_file_exists; |
| 36 | ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) | 20 | static jmethodID s_open_content_uri; |
| 37 | ANDROID_STORAGE_FUNCTIONS(FS) | ||
| 38 | #undef F | ||
| 39 | #undef FS | ||
| 40 | #undef FR | ||
| 41 | #undef FH | ||
| 42 | 21 | ||
| 43 | enum class OpenMode { | 22 | enum class OpenMode { |
| 44 | Read, | 23 | Read, |
| @@ -57,24 +36,11 @@ void UnRegisterCallbacks(); | |||
| 57 | 36 | ||
| 58 | bool IsContentUri(const std::string& path); | 37 | bool IsContentUri(const std::string& path); |
| 59 | 38 | ||
| 60 | #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ | 39 | int OpenContentUri(const std::string& filepath, OpenMode openmode); |
| 61 | F(FunctionName, Parameters, ReturnValue) | 40 | std::uint64_t GetSize(const std::string& filepath); |
| 62 | #define F(FunctionName, Parameters, ReturnValue) ReturnValue FunctionName Parameters; | 41 | bool IsDirectory(const std::string& filepath); |
| 63 | ANDROID_STORAGE_FUNCTIONS(FS) | 42 | bool Exists(const std::string& filepath); |
| 64 | #undef F | 43 | std::string GetParentDirectory(const std::string& filepath); |
| 65 | #undef FS | 44 | std::string GetFilename(const std::string& filepath); |
| 66 | |||
| 67 | #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ | ||
| 68 | F(FunctionName, ReturnValue) | ||
| 69 | #define F(FunctionName, ReturnValue) ReturnValue FunctionName(const std::string& filepath); | ||
| 70 | ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) | ||
| 71 | #undef F | ||
| 72 | #undef FR | ||
| 73 | |||
| 74 | #define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName) | ||
| 75 | #define F(FunctionName) std::string FunctionName(const std::string& filepath); | ||
| 76 | ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) | ||
| 77 | #undef F | ||
| 78 | #undef FH | ||
| 79 | 45 | ||
| 80 | } // namespace Common::FS::Android | 46 | } // namespace Common::FS::Android |
diff --git a/src/common/range_sets.h b/src/common/range_sets.h new file mode 100644 index 000000000..f8fcee483 --- /dev/null +++ b/src/common/range_sets.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace Common { | ||
| 11 | |||
| 12 | template <typename AddressType> | ||
| 13 | class RangeSet { | ||
| 14 | public: | ||
| 15 | RangeSet(); | ||
| 16 | ~RangeSet(); | ||
| 17 | |||
| 18 | RangeSet(RangeSet const&) = delete; | ||
| 19 | RangeSet& operator=(RangeSet const&) = delete; | ||
| 20 | |||
| 21 | RangeSet(RangeSet&& other); | ||
| 22 | RangeSet& operator=(RangeSet&& other); | ||
| 23 | |||
| 24 | void Add(AddressType base_address, size_t size); | ||
| 25 | void Subtract(AddressType base_address, size_t size); | ||
| 26 | void Clear(); | ||
| 27 | bool Empty() const; | ||
| 28 | |||
| 29 | template <typename Func> | ||
| 30 | void ForEach(Func&& func) const; | ||
| 31 | |||
| 32 | template <typename Func> | ||
| 33 | void ForEachInRange(AddressType device_addr, size_t size, Func&& func) const; | ||
| 34 | |||
| 35 | private: | ||
| 36 | struct RangeSetImpl; | ||
| 37 | std::unique_ptr<RangeSetImpl> m_impl; | ||
| 38 | }; | ||
| 39 | |||
| 40 | template <typename AddressType> | ||
| 41 | class OverlapRangeSet { | ||
| 42 | public: | ||
| 43 | OverlapRangeSet(); | ||
| 44 | ~OverlapRangeSet(); | ||
| 45 | |||
| 46 | OverlapRangeSet(OverlapRangeSet const&) = delete; | ||
| 47 | OverlapRangeSet& operator=(OverlapRangeSet const&) = delete; | ||
| 48 | |||
| 49 | OverlapRangeSet(OverlapRangeSet&& other); | ||
| 50 | OverlapRangeSet& operator=(OverlapRangeSet&& other); | ||
| 51 | |||
| 52 | void Add(AddressType base_address, size_t size); | ||
| 53 | void Subtract(AddressType base_address, size_t size); | ||
| 54 | |||
| 55 | template <typename Func> | ||
| 56 | void Subtract(AddressType base_address, size_t size, Func&& on_delete); | ||
| 57 | |||
| 58 | void DeleteAll(AddressType base_address, size_t size); | ||
| 59 | void Clear(); | ||
| 60 | bool Empty() const; | ||
| 61 | |||
| 62 | template <typename Func> | ||
| 63 | void ForEach(Func&& func) const; | ||
| 64 | |||
| 65 | template <typename Func> | ||
| 66 | void ForEachInRange(AddressType device_addr, size_t size, Func&& func) const; | ||
| 67 | |||
| 68 | private: | ||
| 69 | struct OverlapRangeSetImpl; | ||
| 70 | std::unique_ptr<OverlapRangeSetImpl> m_impl; | ||
| 71 | }; | ||
| 72 | |||
| 73 | } // namespace Common | ||
diff --git a/src/common/range_sets.inc b/src/common/range_sets.inc new file mode 100644 index 000000000..b83eceb7b --- /dev/null +++ b/src/common/range_sets.inc | |||
| @@ -0,0 +1,304 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <limits> | ||
| 7 | #include <utility> | ||
| 8 | |||
| 9 | #include <boost/icl/interval.hpp> | ||
| 10 | #include <boost/icl/interval_base_set.hpp> | ||
| 11 | #include <boost/icl/interval_map.hpp> | ||
| 12 | #include <boost/icl/interval_set.hpp> | ||
| 13 | #include <boost/icl/split_interval_map.hpp> | ||
| 14 | #include <boost/pool/pool.hpp> | ||
| 15 | #include <boost/pool/pool_alloc.hpp> | ||
| 16 | #include <boost/pool/poolfwd.hpp> | ||
| 17 | |||
| 18 | #include "common/range_sets.h" | ||
| 19 | |||
| 20 | namespace Common { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | template <class T> | ||
| 24 | using RangeSetsAllocator = | ||
| 25 | boost::fast_pool_allocator<T, boost::default_user_allocator_new_delete, | ||
| 26 | boost::details::pool::default_mutex, 1024, 2048>; | ||
| 27 | } | ||
| 28 | |||
| 29 | template <typename AddressType> | ||
| 30 | struct RangeSet<AddressType>::RangeSetImpl { | ||
| 31 | using IntervalSet = boost::icl::interval_set< | ||
| 32 | AddressType, std::less, ICL_INTERVAL_INSTANCE(ICL_INTERVAL_DEFAULT, AddressType, std::less), | ||
| 33 | RangeSetsAllocator>; | ||
| 34 | using IntervalType = typename IntervalSet::interval_type; | ||
| 35 | |||
| 36 | RangeSetImpl() = default; | ||
| 37 | ~RangeSetImpl() = default; | ||
| 38 | |||
| 39 | void Add(AddressType base_address, size_t size) { | ||
| 40 | AddressType end_address = base_address + static_cast<AddressType>(size); | ||
| 41 | IntervalType interval{base_address, end_address}; | ||
| 42 | m_ranges_set.add(interval); | ||
| 43 | } | ||
| 44 | |||
| 45 | void Subtract(AddressType base_address, size_t size) { | ||
| 46 | AddressType end_address = base_address + static_cast<AddressType>(size); | ||
| 47 | IntervalType interval{base_address, end_address}; | ||
| 48 | m_ranges_set.subtract(interval); | ||
| 49 | } | ||
| 50 | |||
| 51 | template <typename Func> | ||
| 52 | void ForEach(Func&& func) const { | ||
| 53 | if (m_ranges_set.empty()) { | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | auto it = m_ranges_set.begin(); | ||
| 57 | auto end_it = m_ranges_set.end(); | ||
| 58 | for (; it != end_it; it++) { | ||
| 59 | const AddressType inter_addr_end = it->upper(); | ||
| 60 | const AddressType inter_addr = it->lower(); | ||
| 61 | func(inter_addr, inter_addr_end); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | template <typename Func> | ||
| 66 | void ForEachInRange(AddressType base_addr, size_t size, Func&& func) const { | ||
| 67 | if (m_ranges_set.empty()) { | ||
| 68 | return; | ||
| 69 | } | ||
| 70 | const AddressType start_address = base_addr; | ||
| 71 | const AddressType end_address = start_address + size; | ||
| 72 | const RangeSetImpl::IntervalType search_interval{start_address, end_address}; | ||
| 73 | auto it = m_ranges_set.lower_bound(search_interval); | ||
| 74 | if (it == m_ranges_set.end()) { | ||
| 75 | return; | ||
| 76 | } | ||
| 77 | auto end_it = m_ranges_set.upper_bound(search_interval); | ||
| 78 | for (; it != end_it; it++) { | ||
| 79 | AddressType inter_addr_end = it->upper(); | ||
| 80 | AddressType inter_addr = it->lower(); | ||
| 81 | if (inter_addr_end > end_address) { | ||
| 82 | inter_addr_end = end_address; | ||
| 83 | } | ||
| 84 | if (inter_addr < start_address) { | ||
| 85 | inter_addr = start_address; | ||
| 86 | } | ||
| 87 | func(inter_addr, inter_addr_end); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | IntervalSet m_ranges_set; | ||
| 92 | }; | ||
| 93 | |||
| 94 | template <typename AddressType> | ||
| 95 | struct OverlapRangeSet<AddressType>::OverlapRangeSetImpl { | ||
| 96 | using IntervalSet = boost::icl::split_interval_map< | ||
| 97 | AddressType, s32, boost::icl::partial_enricher, std::less, boost::icl::inplace_plus, | ||
| 98 | boost::icl::inter_section, | ||
| 99 | ICL_INTERVAL_INSTANCE(ICL_INTERVAL_DEFAULT, AddressType, std::less), RangeSetsAllocator>; | ||
| 100 | using IntervalType = typename IntervalSet::interval_type; | ||
| 101 | |||
| 102 | OverlapRangeSetImpl() = default; | ||
| 103 | ~OverlapRangeSetImpl() = default; | ||
| 104 | |||
| 105 | void Add(AddressType base_address, size_t size) { | ||
| 106 | AddressType end_address = base_address + static_cast<AddressType>(size); | ||
| 107 | IntervalType interval{base_address, end_address}; | ||
| 108 | m_split_ranges_set += std::make_pair(interval, 1); | ||
| 109 | } | ||
| 110 | |||
| 111 | template <bool has_on_delete, typename Func> | ||
| 112 | void Subtract(AddressType base_address, size_t size, s32 amount, | ||
| 113 | [[maybe_unused]] Func&& on_delete) { | ||
| 114 | if (m_split_ranges_set.empty()) { | ||
| 115 | return; | ||
| 116 | } | ||
| 117 | AddressType end_address = base_address + static_cast<AddressType>(size); | ||
| 118 | IntervalType interval{base_address, end_address}; | ||
| 119 | bool any_removals = false; | ||
| 120 | m_split_ranges_set += std::make_pair(interval, -amount); | ||
| 121 | do { | ||
| 122 | any_removals = false; | ||
| 123 | auto it = m_split_ranges_set.lower_bound(interval); | ||
| 124 | if (it == m_split_ranges_set.end()) { | ||
| 125 | return; | ||
| 126 | } | ||
| 127 | auto end_it = m_split_ranges_set.upper_bound(interval); | ||
| 128 | for (; it != end_it; it++) { | ||
| 129 | if (it->second <= 0) { | ||
| 130 | if constexpr (has_on_delete) { | ||
| 131 | if (it->second == 0) { | ||
| 132 | on_delete(it->first.lower(), it->first.upper()); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | any_removals = true; | ||
| 136 | m_split_ranges_set.erase(it); | ||
| 137 | break; | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } while (any_removals); | ||
| 141 | } | ||
| 142 | |||
| 143 | template <typename Func> | ||
| 144 | void ForEach(Func&& func) const { | ||
| 145 | if (m_split_ranges_set.empty()) { | ||
| 146 | return; | ||
| 147 | } | ||
| 148 | auto it = m_split_ranges_set.begin(); | ||
| 149 | auto end_it = m_split_ranges_set.end(); | ||
| 150 | for (; it != end_it; it++) { | ||
| 151 | const AddressType inter_addr_end = it->first.upper(); | ||
| 152 | const AddressType inter_addr = it->first.lower(); | ||
| 153 | func(inter_addr, inter_addr_end, it->second); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | template <typename Func> | ||
| 158 | void ForEachInRange(AddressType base_address, size_t size, Func&& func) const { | ||
| 159 | if (m_split_ranges_set.empty()) { | ||
| 160 | return; | ||
| 161 | } | ||
| 162 | const AddressType start_address = base_address; | ||
| 163 | const AddressType end_address = start_address + size; | ||
| 164 | const OverlapRangeSetImpl::IntervalType search_interval{start_address, end_address}; | ||
| 165 | auto it = m_split_ranges_set.lower_bound(search_interval); | ||
| 166 | if (it == m_split_ranges_set.end()) { | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | auto end_it = m_split_ranges_set.upper_bound(search_interval); | ||
| 170 | for (; it != end_it; it++) { | ||
| 171 | auto& inter = it->first; | ||
| 172 | AddressType inter_addr_end = inter.upper(); | ||
| 173 | AddressType inter_addr = inter.lower(); | ||
| 174 | if (inter_addr_end > end_address) { | ||
| 175 | inter_addr_end = end_address; | ||
| 176 | } | ||
| 177 | if (inter_addr < start_address) { | ||
| 178 | inter_addr = start_address; | ||
| 179 | } | ||
| 180 | func(inter_addr, inter_addr_end, it->second); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | IntervalSet m_split_ranges_set; | ||
| 185 | }; | ||
| 186 | |||
| 187 | template <typename AddressType> | ||
| 188 | RangeSet<AddressType>::RangeSet() { | ||
| 189 | m_impl = std::make_unique<RangeSet<AddressType>::RangeSetImpl>(); | ||
| 190 | } | ||
| 191 | |||
| 192 | template <typename AddressType> | ||
| 193 | RangeSet<AddressType>::~RangeSet() = default; | ||
| 194 | |||
| 195 | template <typename AddressType> | ||
| 196 | RangeSet<AddressType>::RangeSet(RangeSet&& other) { | ||
| 197 | m_impl = std::make_unique<RangeSet<AddressType>::RangeSetImpl>(); | ||
| 198 | m_impl->m_ranges_set = std::move(other.m_impl->m_ranges_set); | ||
| 199 | } | ||
| 200 | |||
| 201 | template <typename AddressType> | ||
| 202 | RangeSet<AddressType>& RangeSet<AddressType>::operator=(RangeSet&& other) { | ||
| 203 | m_impl->m_ranges_set = std::move(other.m_impl->m_ranges_set); | ||
| 204 | } | ||
| 205 | |||
| 206 | template <typename AddressType> | ||
| 207 | void RangeSet<AddressType>::Add(AddressType base_address, size_t size) { | ||
| 208 | m_impl->Add(base_address, size); | ||
| 209 | } | ||
| 210 | |||
| 211 | template <typename AddressType> | ||
| 212 | void RangeSet<AddressType>::Subtract(AddressType base_address, size_t size) { | ||
| 213 | m_impl->Subtract(base_address, size); | ||
| 214 | } | ||
| 215 | |||
| 216 | template <typename AddressType> | ||
| 217 | void RangeSet<AddressType>::Clear() { | ||
| 218 | m_impl->m_ranges_set.clear(); | ||
| 219 | } | ||
| 220 | |||
| 221 | template <typename AddressType> | ||
| 222 | bool RangeSet<AddressType>::Empty() const { | ||
| 223 | return m_impl->m_ranges_set.empty(); | ||
| 224 | } | ||
| 225 | |||
| 226 | template <typename AddressType> | ||
| 227 | template <typename Func> | ||
| 228 | void RangeSet<AddressType>::ForEach(Func&& func) const { | ||
| 229 | m_impl->ForEach(std::move(func)); | ||
| 230 | } | ||
| 231 | |||
| 232 | template <typename AddressType> | ||
| 233 | template <typename Func> | ||
| 234 | void RangeSet<AddressType>::ForEachInRange(AddressType base_address, size_t size, | ||
| 235 | Func&& func) const { | ||
| 236 | m_impl->ForEachInRange(base_address, size, std::move(func)); | ||
| 237 | } | ||
| 238 | |||
| 239 | template <typename AddressType> | ||
| 240 | OverlapRangeSet<AddressType>::OverlapRangeSet() { | ||
| 241 | m_impl = std::make_unique<OverlapRangeSet<AddressType>::OverlapRangeSetImpl>(); | ||
| 242 | } | ||
| 243 | |||
| 244 | template <typename AddressType> | ||
| 245 | OverlapRangeSet<AddressType>::~OverlapRangeSet() = default; | ||
| 246 | |||
| 247 | template <typename AddressType> | ||
| 248 | OverlapRangeSet<AddressType>::OverlapRangeSet(OverlapRangeSet&& other) { | ||
| 249 | m_impl = std::make_unique<OverlapRangeSet<AddressType>::OverlapRangeSetImpl>(); | ||
| 250 | m_impl->m_split_ranges_set = std::move(other.m_impl->m_split_ranges_set); | ||
| 251 | } | ||
| 252 | |||
| 253 | template <typename AddressType> | ||
| 254 | OverlapRangeSet<AddressType>& OverlapRangeSet<AddressType>::operator=(OverlapRangeSet&& other) { | ||
| 255 | m_impl->m_split_ranges_set = std::move(other.m_impl->m_split_ranges_set); | ||
| 256 | } | ||
| 257 | |||
| 258 | template <typename AddressType> | ||
| 259 | void OverlapRangeSet<AddressType>::Add(AddressType base_address, size_t size) { | ||
| 260 | m_impl->Add(base_address, size); | ||
| 261 | } | ||
| 262 | |||
| 263 | template <typename AddressType> | ||
| 264 | void OverlapRangeSet<AddressType>::Subtract(AddressType base_address, size_t size) { | ||
| 265 | m_impl->template Subtract<false>(base_address, size, 1, [](AddressType, AddressType) {}); | ||
| 266 | } | ||
| 267 | |||
| 268 | template <typename AddressType> | ||
| 269 | template <typename Func> | ||
| 270 | void OverlapRangeSet<AddressType>::Subtract(AddressType base_address, size_t size, | ||
| 271 | Func&& on_delete) { | ||
| 272 | m_impl->template Subtract<true, Func>(base_address, size, 1, std::move(on_delete)); | ||
| 273 | } | ||
| 274 | |||
| 275 | template <typename AddressType> | ||
| 276 | void OverlapRangeSet<AddressType>::DeleteAll(AddressType base_address, size_t size) { | ||
| 277 | m_impl->template Subtract<false>(base_address, size, std::numeric_limits<s32>::max(), | ||
| 278 | [](AddressType, AddressType) {}); | ||
| 279 | } | ||
| 280 | |||
| 281 | template <typename AddressType> | ||
| 282 | void OverlapRangeSet<AddressType>::Clear() { | ||
| 283 | m_impl->m_split_ranges_set.clear(); | ||
| 284 | } | ||
| 285 | |||
| 286 | template <typename AddressType> | ||
| 287 | bool OverlapRangeSet<AddressType>::Empty() const { | ||
| 288 | return m_impl->m_split_ranges_set.empty(); | ||
| 289 | } | ||
| 290 | |||
| 291 | template <typename AddressType> | ||
| 292 | template <typename Func> | ||
| 293 | void OverlapRangeSet<AddressType>::ForEach(Func&& func) const { | ||
| 294 | m_impl->ForEach(func); | ||
| 295 | } | ||
| 296 | |||
| 297 | template <typename AddressType> | ||
| 298 | template <typename Func> | ||
| 299 | void OverlapRangeSet<AddressType>::ForEachInRange(AddressType base_address, size_t size, | ||
| 300 | Func&& func) const { | ||
| 301 | m_impl->ForEachInRange(base_address, size, std::move(func)); | ||
| 302 | } | ||
| 303 | |||
| 304 | } // namespace Common | ||
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 07709d4e5..80d388fe8 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -30,6 +30,7 @@ namespace Settings { | |||
| 30 | #define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED> | 30 | #define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED> |
| 31 | #define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED> | 31 | #define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED> |
| 32 | 32 | ||
| 33 | SETTING(AppletMode, false); | ||
| 33 | SETTING(AudioEngine, false); | 34 | SETTING(AudioEngine, false); |
| 34 | SETTING(bool, false); | 35 | SETTING(bool, false); |
| 35 | SETTING(int, false); | 36 | SETTING(int, false); |
| @@ -215,6 +216,8 @@ const char* TranslateCategory(Category category) { | |||
| 215 | return "Debugging"; | 216 | return "Debugging"; |
| 216 | case Category::GpuDriver: | 217 | case Category::GpuDriver: |
| 217 | return "GpuDriver"; | 218 | return "GpuDriver"; |
| 219 | case Category::LibraryApplet: | ||
| 220 | return "LibraryApplet"; | ||
| 218 | case Category::Miscellaneous: | 221 | case Category::Miscellaneous: |
| 219 | return "Miscellaneous"; | 222 | return "Miscellaneous"; |
| 220 | case Category::Network: | 223 | case Category::Network: |
diff --git a/src/common/settings.h b/src/common/settings.h index f1b1add56..aa054dc24 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -133,6 +133,38 @@ struct TouchFromButtonMap { | |||
| 133 | struct Values { | 133 | struct Values { |
| 134 | Linkage linkage{}; | 134 | Linkage linkage{}; |
| 135 | 135 | ||
| 136 | // Applet | ||
| 137 | Setting<AppletMode> cabinet_applet_mode{linkage, AppletMode::LLE, "cabinet_applet_mode", | ||
| 138 | Category::LibraryApplet}; | ||
| 139 | Setting<AppletMode> controller_applet_mode{linkage, AppletMode::HLE, "controller_applet_mode", | ||
| 140 | Category::LibraryApplet}; | ||
| 141 | Setting<AppletMode> data_erase_applet_mode{linkage, AppletMode::HLE, "data_erase_applet_mode", | ||
| 142 | Category::LibraryApplet}; | ||
| 143 | Setting<AppletMode> error_applet_mode{linkage, AppletMode::HLE, "error_applet_mode", | ||
| 144 | Category::LibraryApplet}; | ||
| 145 | Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode", | ||
| 146 | Category::LibraryApplet}; | ||
| 147 | Setting<AppletMode> player_select_applet_mode{ | ||
| 148 | linkage, AppletMode::HLE, "player_select_applet_mode", Category::LibraryApplet}; | ||
| 149 | Setting<AppletMode> swkbd_applet_mode{linkage, AppletMode::LLE, "swkbd_applet_mode", | ||
| 150 | Category::LibraryApplet}; | ||
| 151 | Setting<AppletMode> mii_edit_applet_mode{linkage, AppletMode::LLE, "mii_edit_applet_mode", | ||
| 152 | Category::LibraryApplet}; | ||
| 153 | Setting<AppletMode> web_applet_mode{linkage, AppletMode::HLE, "web_applet_mode", | ||
| 154 | Category::LibraryApplet}; | ||
| 155 | Setting<AppletMode> shop_applet_mode{linkage, AppletMode::HLE, "shop_applet_mode", | ||
| 156 | Category::LibraryApplet}; | ||
| 157 | Setting<AppletMode> photo_viewer_applet_mode{ | ||
| 158 | linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet}; | ||
| 159 | Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode", | ||
| 160 | Category::LibraryApplet}; | ||
| 161 | Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode", | ||
| 162 | Category::LibraryApplet}; | ||
| 163 | Setting<AppletMode> wifi_web_auth_applet_mode{ | ||
| 164 | linkage, AppletMode::HLE, "wifi_web_auth_applet_mode", Category::LibraryApplet}; | ||
| 165 | Setting<AppletMode> my_page_applet_mode{linkage, AppletMode::LLE, "my_page_applet_mode", | ||
| 166 | Category::LibraryApplet}; | ||
| 167 | |||
| 136 | // Audio | 168 | // Audio |
| 137 | SwitchableSetting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine", | 169 | SwitchableSetting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine", |
| 138 | Category::Audio, Specialization::RuntimeList}; | 170 | Category::Audio, Specialization::RuntimeList}; |
diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 987489e8a..2df3f0809 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h | |||
| @@ -44,6 +44,7 @@ enum class Category : u32 { | |||
| 44 | Services, | 44 | Services, |
| 45 | Paths, | 45 | Paths, |
| 46 | Linux, | 46 | Linux, |
| 47 | LibraryApplet, | ||
| 47 | MaxEnum, | 48 | MaxEnum, |
| 48 | }; | 49 | }; |
| 49 | 50 | ||
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 617036588..f42367e67 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h | |||
| @@ -151,6 +151,8 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); | |||
| 151 | 151 | ||
| 152 | ENUM(ConsoleMode, Handheld, Docked); | 152 | ENUM(ConsoleMode, Handheld, Docked); |
| 153 | 153 | ||
| 154 | ENUM(AppletMode, HLE, LLE); | ||
| 155 | |||
| 154 | template <typename Type> | 156 | template <typename Type> |
| 155 | inline std::string CanonicalizeEnum(Type id) { | 157 | inline std::string CanonicalizeEnum(Type id) { |
| 156 | const auto group = EnumMetadata<Type>::Canonicalizations(); | 158 | const auto group = EnumMetadata<Type>::Canonicalizations(); |
diff --git a/src/common/slot_vector.h b/src/common/slot_vector.h new file mode 100644 index 000000000..34ff7de94 --- /dev/null +++ b/src/common/slot_vector.h | |||
| @@ -0,0 +1,227 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <bit> | ||
| 8 | #include <numeric> | ||
| 9 | #include <type_traits> | ||
| 10 | #include <utility> | ||
| 11 | #include <vector> | ||
| 12 | |||
| 13 | #include "common/assert.h" | ||
| 14 | #include "common/common_types.h" | ||
| 15 | #include "common/polyfill_ranges.h" | ||
| 16 | |||
| 17 | namespace Common { | ||
| 18 | |||
| 19 | struct SlotId { | ||
| 20 | static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::max(); | ||
| 21 | |||
| 22 | constexpr auto operator<=>(const SlotId&) const noexcept = default; | ||
| 23 | |||
| 24 | constexpr explicit operator bool() const noexcept { | ||
| 25 | return index != INVALID_INDEX; | ||
| 26 | } | ||
| 27 | |||
| 28 | u32 index = INVALID_INDEX; | ||
| 29 | }; | ||
| 30 | |||
| 31 | template <class T> | ||
| 32 | requires std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T> | ||
| 33 | class SlotVector { | ||
| 34 | public: | ||
| 35 | class Iterator { | ||
| 36 | friend SlotVector<T>; | ||
| 37 | |||
| 38 | public: | ||
| 39 | constexpr Iterator() = default; | ||
| 40 | |||
| 41 | Iterator& operator++() noexcept { | ||
| 42 | const u64* const bitset = slot_vector->stored_bitset.data(); | ||
| 43 | const u32 size = static_cast<u32>(slot_vector->stored_bitset.size()) * 64; | ||
| 44 | if (id.index < size) { | ||
| 45 | do { | ||
| 46 | ++id.index; | ||
| 47 | } while (id.index < size && !IsValid(bitset)); | ||
| 48 | if (id.index == size) { | ||
| 49 | id.index = SlotId::INVALID_INDEX; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | return *this; | ||
| 53 | } | ||
| 54 | |||
| 55 | Iterator operator++(int) noexcept { | ||
| 56 | const Iterator copy{*this}; | ||
| 57 | ++*this; | ||
| 58 | return copy; | ||
| 59 | } | ||
| 60 | |||
| 61 | bool operator==(const Iterator& other) const noexcept { | ||
| 62 | return id.index == other.id.index; | ||
| 63 | } | ||
| 64 | |||
| 65 | bool operator!=(const Iterator& other) const noexcept { | ||
| 66 | return id.index != other.id.index; | ||
| 67 | } | ||
| 68 | |||
| 69 | std::pair<SlotId, T*> operator*() const noexcept { | ||
| 70 | return {id, std::addressof((*slot_vector)[id])}; | ||
| 71 | } | ||
| 72 | |||
| 73 | T* operator->() const noexcept { | ||
| 74 | return std::addressof((*slot_vector)[id]); | ||
| 75 | } | ||
| 76 | |||
| 77 | private: | ||
| 78 | Iterator(SlotVector<T>* slot_vector_, SlotId id_) noexcept | ||
| 79 | : slot_vector{slot_vector_}, id{id_} {} | ||
| 80 | |||
| 81 | bool IsValid(const u64* bitset) const noexcept { | ||
| 82 | return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | SlotVector<T>* slot_vector; | ||
| 86 | SlotId id; | ||
| 87 | }; | ||
| 88 | |||
| 89 | ~SlotVector() noexcept { | ||
| 90 | size_t index = 0; | ||
| 91 | for (u64 bits : stored_bitset) { | ||
| 92 | for (size_t bit = 0; bits; ++bit, bits >>= 1) { | ||
| 93 | if ((bits & 1) != 0) { | ||
| 94 | values[index + bit].object.~T(); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | index += 64; | ||
| 98 | } | ||
| 99 | delete[] values; | ||
| 100 | } | ||
| 101 | |||
| 102 | [[nodiscard]] T& operator[](SlotId id) noexcept { | ||
| 103 | ValidateIndex(id); | ||
| 104 | return values[id.index].object; | ||
| 105 | } | ||
| 106 | |||
| 107 | [[nodiscard]] const T& operator[](SlotId id) const noexcept { | ||
| 108 | ValidateIndex(id); | ||
| 109 | return values[id.index].object; | ||
| 110 | } | ||
| 111 | |||
| 112 | template <typename... Args> | ||
| 113 | [[nodiscard]] SlotId insert(Args&&... args) noexcept { | ||
| 114 | const u32 index = FreeValueIndex(); | ||
| 115 | new (&values[index].object) T(std::forward<Args>(args)...); | ||
| 116 | SetStorageBit(index); | ||
| 117 | |||
| 118 | return SlotId{index}; | ||
| 119 | } | ||
| 120 | |||
| 121 | void erase(SlotId id) noexcept { | ||
| 122 | values[id.index].object.~T(); | ||
| 123 | free_list.push_back(id.index); | ||
| 124 | ResetStorageBit(id.index); | ||
| 125 | } | ||
| 126 | |||
| 127 | [[nodiscard]] Iterator begin() noexcept { | ||
| 128 | const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; }); | ||
| 129 | if (it == stored_bitset.end()) { | ||
| 130 | return end(); | ||
| 131 | } | ||
| 132 | const u32 word_index = static_cast<u32>(std::distance(it, stored_bitset.begin())); | ||
| 133 | const SlotId first_id{word_index * 64 + static_cast<u32>(std::countr_zero(*it))}; | ||
| 134 | return Iterator(this, first_id); | ||
| 135 | } | ||
| 136 | |||
| 137 | [[nodiscard]] Iterator end() noexcept { | ||
| 138 | return Iterator(this, SlotId{SlotId::INVALID_INDEX}); | ||
| 139 | } | ||
| 140 | |||
| 141 | [[nodiscard]] size_t size() const noexcept { | ||
| 142 | return values_capacity - free_list.size(); | ||
| 143 | } | ||
| 144 | |||
| 145 | private: | ||
| 146 | struct NonTrivialDummy { | ||
| 147 | NonTrivialDummy() noexcept {} | ||
| 148 | }; | ||
| 149 | |||
| 150 | union Entry { | ||
| 151 | Entry() noexcept : dummy{} {} | ||
| 152 | ~Entry() noexcept {} | ||
| 153 | |||
| 154 | NonTrivialDummy dummy; | ||
| 155 | T object; | ||
| 156 | }; | ||
| 157 | |||
| 158 | void SetStorageBit(u32 index) noexcept { | ||
| 159 | stored_bitset[index / 64] |= u64(1) << (index % 64); | ||
| 160 | } | ||
| 161 | |||
| 162 | void ResetStorageBit(u32 index) noexcept { | ||
| 163 | stored_bitset[index / 64] &= ~(u64(1) << (index % 64)); | ||
| 164 | } | ||
| 165 | |||
| 166 | bool ReadStorageBit(u32 index) noexcept { | ||
| 167 | return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | void ValidateIndex(SlotId id) const noexcept { | ||
| 171 | DEBUG_ASSERT(id); | ||
| 172 | DEBUG_ASSERT(id.index / 64 < stored_bitset.size()); | ||
| 173 | DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0); | ||
| 174 | } | ||
| 175 | |||
| 176 | [[nodiscard]] u32 FreeValueIndex() noexcept { | ||
| 177 | if (free_list.empty()) { | ||
| 178 | Reserve(values_capacity ? (values_capacity << 1) : 1); | ||
| 179 | } | ||
| 180 | const u32 free_index = free_list.back(); | ||
| 181 | free_list.pop_back(); | ||
| 182 | return free_index; | ||
| 183 | } | ||
| 184 | |||
| 185 | void Reserve(size_t new_capacity) noexcept { | ||
| 186 | Entry* const new_values = new Entry[new_capacity]; | ||
| 187 | size_t index = 0; | ||
| 188 | for (u64 bits : stored_bitset) { | ||
| 189 | for (size_t bit = 0; bits; ++bit, bits >>= 1) { | ||
| 190 | const size_t i = index + bit; | ||
| 191 | if ((bits & 1) == 0) { | ||
| 192 | continue; | ||
| 193 | } | ||
| 194 | T& old_value = values[i].object; | ||
| 195 | new (&new_values[i].object) T(std::move(old_value)); | ||
| 196 | old_value.~T(); | ||
| 197 | } | ||
| 198 | index += 64; | ||
| 199 | } | ||
| 200 | |||
| 201 | stored_bitset.resize((new_capacity + 63) / 64); | ||
| 202 | |||
| 203 | const size_t old_free_size = free_list.size(); | ||
| 204 | free_list.resize(old_free_size + (new_capacity - values_capacity)); | ||
| 205 | std::iota(free_list.begin() + old_free_size, free_list.end(), | ||
| 206 | static_cast<u32>(values_capacity)); | ||
| 207 | |||
| 208 | delete[] values; | ||
| 209 | values = new_values; | ||
| 210 | values_capacity = new_capacity; | ||
| 211 | } | ||
| 212 | |||
| 213 | Entry* values = nullptr; | ||
| 214 | size_t values_capacity = 0; | ||
| 215 | |||
| 216 | std::vector<u64> stored_bitset; | ||
| 217 | std::vector<u32> free_list; | ||
| 218 | }; | ||
| 219 | |||
| 220 | } // namespace Common | ||
| 221 | |||
| 222 | template <> | ||
| 223 | struct std::hash<Common::SlotId> { | ||
| 224 | size_t operator()(const Common::SlotId& id) const noexcept { | ||
| 225 | return std::hash<u32>{}(id.index); | ||
| 226 | } | ||
| 227 | }; | ||