From f20941305117e3ed0923b3f16b2737066b40491c Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Sun, 27 Jul 2025 23:23:39 +0300 Subject: [PATCH] Add account settings --- CMakeLists.txt | 8 +- src/android/app/build.gradle.kts | 55 +- .../cemu/cemu/PlaceholderInstrumentedTest.kt | 14 + src/android/app/src/main/cpp/CMakeLists.txt | 1 + src/android/app/src/main/cpp/JNIUtils.h | 10 + .../app/src/main/cpp/NativeAccount.cpp | 256 ++++++++ .../app/src/main/cpp/NativeActiveSettings.cpp | 6 + .../app/src/main/cpp/NativeLocalization.cpp | 2 +- .../app/src/main/cpp/NativeSettings.cpp | 42 +- .../core/components/FilledSearchToolbar.kt | 2 - .../cemu/core/components/ModifierClickable.kt | 33 - .../cemu/core/components/SingleSelection.kt | 70 ++- .../core/nativeenummapper/GameTitlesEnums.kt | 20 +- .../cemu/core/nativeenummapper/InputEnums.kt | 12 +- .../java/info/cemu/cemu/core/theme/Theme.kt | 3 +- .../cemu/cemu/core/translation/Translation.kt | 2 + .../cemu/cemu/emulation/EmulationActivity.kt | 35 +- .../cemu/emulation/SurfaceChangedListener.kt | 23 + .../cemu/cemu/gamelist/GameDetailsScreen.kt | 14 +- .../cemu/gamelist/GameProfileEditScreen.kt | 14 +- .../info/cemu/cemu/input/EmulatedInputs.kt | 122 ++++ .../java/info/cemu/cemu/input/InputManager.kt | 38 +- .../inputoverlay/InputOverlaySurfaceView.kt | 212 ++++--- .../cemu/nativeinterface/NativeAccount.kt | 78 +++ .../nativeinterface/NativeActiveSettings.kt | 3 + .../cemu/nativeinterface/NativeEmulation.kt | 12 +- .../cemu/nativeinterface/NativeGameTitles.kt | 64 +- .../cemu/cemu/nativeinterface/NativeInput.kt | 353 ++++------- .../cemu/nativeinterface/NativeSettings.kt | 99 ++- .../cemu/cemu/provider/DocumentsProvider.kt | 21 +- .../cemu/cemu/settings/SettingsHomeScreen.kt | 5 + .../cemu/cemu/settings/SettingsNavigation.kt | 11 + .../cemu/settings/account/AccountsScreen.kt | 568 ++++++++++++++++++ .../settings/account/AccountsViewModel.kt | 147 +++++ .../cemu/settings/audio/AudoSettingsScreen.kt | 14 +- .../settings/general/GeneralSettingsScreen.kt | 50 +- .../graphics/GraphicsSettingsScreen.kt | 42 +- .../settings/input/ClassicControllerInputs.kt | 94 +-- .../input/ControllerInputSettingsScreen.kt | 20 +- .../settings/input/ControllersViewModel.kt | 15 +- .../settings/input/InputSettingsScreen.kt | 2 +- .../settings/input/ProControllerInputs.kt | 104 ++-- .../cemu/cemu/settings/input/VPADInputs.kt | 111 ++-- .../settings/input/WiimoteControllerInputs.kt | 72 +-- .../settings/overlay/OverlaySettingsScreen.kt | 32 +- .../info/cemu/cemu/titlemanager/TitleEntry.kt | 14 +- .../main/java/info/cemu/cemu/utils/String.kt | 9 + .../app/src/main/res/values/strings.xml | 1 + .../info/cemu/cemu/PlaceholderUnitTest.kt | 12 + src/android/gradle/libs.versions.toml | 16 +- 50 files changed, 2083 insertions(+), 880 deletions(-) create mode 100644 src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt create mode 100644 src/android/app/src/main/cpp/NativeAccount.cpp delete mode 100644 src/android/app/src/main/java/info/cemu/cemu/core/components/ModifierClickable.kt create mode 100644 src/android/app/src/main/java/info/cemu/cemu/emulation/SurfaceChangedListener.kt create mode 100644 src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt create mode 100644 src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeAccount.kt create mode 100644 src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt create mode 100644 src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsViewModel.kt create mode 100644 src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt diff --git a/CMakeLists.txt b/CMakeLists.txt index 23ef2a59..1ec6a57e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21.1) +cmake_minimum_required(VERSION 3.25) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) @@ -78,10 +78,8 @@ add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}) set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds -if(NOT ANDROID) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) -endif() +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) if (MSVC) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT CemuBin) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 7993939c..8c83dd18 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -47,7 +47,7 @@ val cemuDataFilesFolder = "../../../bin" android { namespace = "info.cemu.cemu" compileSdk = 35 - ndkVersion = "26.1.10909125" + ndkVersion = "26.3.11579264" defaultConfig { applicationId = "info.cemu.cemu" minSdk = 31 @@ -70,7 +70,9 @@ android { packaging { jniLibs.useLegacyPackaging = true } + val keystoreFilePath: String? = System.getenv("ANDROID_KEYSTORE_FILE") + signingConfigs { if (keystoreFilePath != null) { create("releaseSigningConfig") { @@ -117,33 +119,35 @@ android { } externalNativeBuild { cmake { - version = "3.22.1" + version = "3.25.0+" path = file("../../../CMakeLists.txt") } } - val versionArguments = if (versionMajor != null && versionMinor != null) - arrayOf( - "-DEMULATOR_VERSION_MAJOR=$versionMajor", - "-DEMULATOR_VERSION_MINOR=$versionMinor" - ) else emptyArray() - val cmakeArguments = arrayOf( - "-DANDROID_STL=c++_shared", - "-DENABLE_VCPKG=ON", - "-DVCPKG_TARGET_ANDROID=ON", - "-DENABLE_SDL=OFF", - "-DENABLE_WXWIDGETS=OFF", - "-DENABLE_OPENGL=OFF", - "-DENABLE_BLUEZ=OFF", - "-DBUNDLE_SPEEX=ON", - "-DENABLE_DISCORD_RPC=OFF", - "-DENABLE_NSYSHID_LIBUSB=OFF", - "-DENABLE_WAYLAND=OFF", - "-DENABLE_HIDAPI=OFF" - ) + versionArguments defaultConfig { externalNativeBuild { cmake { - arguments.addAll(cmakeArguments) + arguments( + "-DANDROID_STL=c++_shared", + "-DENABLE_VCPKG=ON", + "-DVCPKG_TARGET_ANDROID=ON", + "-DENABLE_SDL=OFF", + "-DENABLE_WXWIDGETS=OFF", + "-DENABLE_OPENGL=OFF", + "-DENABLE_BLUEZ=OFF", + "-DBUNDLE_SPEEX=ON", + "-DENABLE_DISCORD_RPC=OFF", + "-DENABLE_NSYSHID_LIBUSB=OFF", + "-DENABLE_WAYLAND=OFF", + "-DENABLE_HIDAPI=OFF" + ) + if (versionMajor != null && versionMinor != null) { + arguments.addAll( + arrayOf( + "-DEMULATOR_VERSION_MAJOR=$versionMajor", + "-DEMULATOR_VERSION_MINOR=$versionMinor" + ) + ) + } abiFilters("arm64-v8a") } } @@ -226,13 +230,18 @@ dependencies { implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.compose.material3) + testImplementation(libs.junit) + testImplementation(libs.kotlin.reflect) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) implementation(libs.okhttp) implementation(libs.okhttp.coroutines) implementation(libs.androidx.appcompat) - implementation(libs.material) + implementation(libs.google.android.material) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.core.ktx) } diff --git a/src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt b/src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt new file mode 100644 index 00000000..92e34f83 --- /dev/null +++ b/src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt @@ -0,0 +1,14 @@ +package info.cemu.cemu + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PlaceholderInstrumentedTest { + @Test + fun test() { + assertTrue(true) + } +} diff --git a/src/android/app/src/main/cpp/CMakeLists.txt b/src/android/app/src/main/cpp/CMakeLists.txt index 8de47f35..90da504d 100644 --- a/src/android/app/src/main/cpp/CMakeLists.txt +++ b/src/android/app/src/main/cpp/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(CemuAndroid SHARED Image.cpp JNIUtils.cpp NativeActiveSettings.cpp + NativeAccount.cpp NativeEmulation.cpp NativeGameTitles.cpp NativeGraphicPacks.cpp diff --git a/src/android/app/src/main/cpp/JNIUtils.h b/src/android/app/src/main/cpp/JNIUtils.h index d40e1129..9ee4d7fa 100644 --- a/src/android/app/src/main/cpp/JNIUtils.h +++ b/src/android/app/src/main/cpp/JNIUtils.h @@ -22,6 +22,16 @@ namespace JNIUtils return env->NewStringUTF(str.c_str()); } + inline jstring toJString(JNIEnv* env, std::string_view str) + { + return toJString(env, std::string(str)); + } + + inline jstring toJString(JNIEnv* env, std::wstring_view str) + { + return toJString(env, boost::nowide::narrow(str)); + } + jobject createJavaStringArrayList(JNIEnv* env, const std::vector& stringList); jobject createJavaStringArrayList(JNIEnv* env, const std::vector& stringList); diff --git a/src/android/app/src/main/cpp/NativeAccount.cpp b/src/android/app/src/main/cpp/NativeAccount.cpp new file mode 100644 index 00000000..c2d52251 --- /dev/null +++ b/src/android/app/src/main/cpp/NativeAccount.cpp @@ -0,0 +1,256 @@ +#include +#include "WindowSystem.h" +#include "JNIUtils.h" +#include "AndroidAudio.h" +#include "AndroidEmulatedController.h" +#include "AndroidFilesystemCallbacks.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/CafeSystem.h" +#include "GameTitleLoader.h" +#include "input/ControllerFactory.h" +#include "input/InputManager.h" +#include "input/api/Android/AndroidController.h" +#include "input/api/Android/AndroidControllerProvider.h" +#include "config/ActiveSettings.h" +#include "Cemu/ncrypto/ncrypto.h" + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_createAccount(JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id, jstring mii_name) +{ + uint32 persistentId = static_cast(persistent_id); + std::string miiName = JNIUtils::toString(env, mii_name); + Account account(persistentId, boost::nowide::widen(miiName)); + account.Save(); + Account::RefreshAccounts(); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_deleteAccount([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id) +{ + uint32 persistentId = static_cast(persistent_id); + const auto& account = Account::GetAccount(persistentId); + + if (account.GetPersistentId() != persistentId) + { + return; + } + + const fs::path path = account.GetFileName(); + + try + { + fs::remove_all(path.parent_path()); + Account::RefreshAccounts(); + } catch (const std::exception& ex) + { + cemuLog_log(LogType::Force, "Failed to delete account {} {}", path.c_str(), ex.what()); + } +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_saveAccount(JNIEnv* env, [[maybe_unused]] jclass clazz, jobject account_java) +{ + using namespace std::chrono; + + jclass accountClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$Account"); + auto getAccountField = [&](const char* fieldName, const char* sig, auto getFieldFn) -> auto { + auto getField = std::bind(getFieldFn, env, account_java, std::placeholders::_1); + return getField(env->GetFieldID(accountClass, fieldName, sig)); + }; + uint32 persistentId = getAccountField("persistentId", "I", &JNIEnv::GetIntField); + auto account = Account::GetAccount(persistentId); + + if (account.GetPersistentId() != persistentId) + { + return; + } + + jstring miiNameJava = static_cast(getAccountField("miiName", "Ljava/lang/String;", &JNIEnv::GetObjectField)); + account.SetMiiName(boost::nowide::widen(JNIUtils::toString(env, miiNameJava))); + account.SetCountry(getAccountField("country", "I", &JNIEnv::GetIntField)); + account.SetGender(getAccountField("gender", "B", &JNIEnv::GetByteField)); + jstring emailJava = static_cast(getAccountField("email", "Ljava/lang/String;", &JNIEnv::GetObjectField)); + account.SetEmail(JNIUtils::toString(env, emailJava)); + auto birthdayMillis = milliseconds(getAccountField("birthday", "J", &JNIEnv::GetLongField)); + system_clock::time_point birthdayTimePoint(birthdayMillis); + year_month_day birthdayYMD(floor(time_point(birthdayTimePoint))); + if (birthdayYMD.ok()) + { + account.SetBirthYear(static_cast(birthdayYMD.year())); + account.SetBirthMonth(static_cast(birthdayYMD.month())); + account.SetBirthDay(static_cast(birthdayYMD.day())); + } + account.Save(); + + Account::RefreshAccounts(); +} + +jlong toUnixTimestampMillis(std::chrono::year_month_day ymd) +{ + using namespace std::chrono; + if (!ymd.ok()) + return 0; + + auto millis = duration_cast(system_clock::time_point(sys_days(ymd)).time_since_epoch()); + return millis.count(); +} + +extern "C" [[maybe_unused]] JNIEXPORT jobjectArray JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_getAccounts(JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + using namespace std::chrono; + jclass accountClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$Account"); + jmethodID accountCtrId = env->GetMethodID(accountClass, "", "(ILjava/lang/String;JBLjava/lang/String;IZ)V"); + + const auto& accounts = Account::GetAccounts(); + jsize accountsCount = static_cast(accounts.size()); + auto accountsJArray = env->NewObjectArray(accountsCount, accountClass, nullptr); + + for (jint i = 0; i < accounts.size(); i++) + { + const auto& account = accounts[i]; + jint persistentId = static_cast(account.GetPersistentId()); + jstring miiName = JNIUtils::toJString(env, account.GetMiiName()); + jlong birthday = toUnixTimestampMillis(year_month_day(year(account.GetBirthYear()), month(account.GetBirthMonth()), day(account.GetBirthDay()))); + jbyte gender = static_cast(account.GetGender()); + jstring email = JNIUtils::toJString(env, account.GetEmail()); + jint country = static_cast(account.GetCountry()); + jboolean isValid = account.IsValidOnlineAccount(); + + jobject accountJObj = env->NewObject( + accountClass, + accountCtrId, + persistentId, + miiName, + birthday, + gender, + email, + country, + isValid); + env->SetObjectArrayElement(accountsJArray, i, accountJObj); + } + + return accountsJArray; +} + +extern "C" [[maybe_unused]] JNIEXPORT jobjectArray JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_getAccountCountries(JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + jclass countryClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$AccountCountry"); + jmethodID countryCtrId = env->GetMethodID(countryClass, "", "(ILjava/lang/String;)V"); + + struct Country + { + jint index; + const char* name; + }; + + std::vector countries; + for (int i = 0; i < NCrypto::GetCountryCount(); ++i) + { + const auto countryName = NCrypto::GetCountryAsString(i); + if (countryName && (i == 0 || !boost::equals(countryName, "NN"))) + { + countries.push_back({.index = i, .name = countryName}); + } + } + + jobjectArray countriesJava = env->NewObjectArray(countries.size(), countryClass, nullptr); + for (int i = 0; i < countries.size(); ++i) + { + const auto& country = countries[i]; + jobject countryJava = env->NewObject(countryClass, + countryCtrId, + country.index, + env->NewStringUTF(country.name)); + env->SetObjectArrayElement(countriesJava, i, countryJava); + } + + return countriesJava; +} + +extern "C" [[maybe_unused]] JNIEXPORT jobjectArray JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_getAccountValidationErrors(JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id) +{ + using ErrorType = std::pair; + auto getErrorType = [&](const char* className, const char* ctrSig = "()V") -> ErrorType { + using namespace std::placeholders; + jclass errorClass = env->FindClass(className); + jmethodID ctrMID = env->GetMethodID(errorClass, "", ctrSig); + return std::make_pair(errorClass, ctrMID); + }; + auto newError = [&](const ErrorType& error, auto... args) { + return env->NewObject(error.first, error.second, args...); + }; + auto missingOTPError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$MissingOTP"); + auto corruptedOTPError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$CorruptedOTP"); + auto missingSEEPROMError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$MissingSEEPROM"); + auto corruptedSEEPROMError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$CorruptedSEEPROM"); + auto missingFileError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$MissingFile", "(Ljava/lang/String;)V"); + auto accountError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$AccountError", "(I)V"); + auto baseErrorType = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$OnlineValidationError"); + + uint32 persistentId = persistent_id; + auto account = Account::GetAccount(persistentId); + const auto validator = account.ValidateOnlineFiles(); + + if (account.GetPersistentId() != persistentId || validator.IsValid()) + { + return env->NewObjectArray(0, baseErrorType, nullptr); + } + + std::vector errors; + + if (validator.otp == OnlineValidator::FileState::Missing) + errors.push_back(newError(missingOTPError)); + else if (validator.otp == OnlineValidator::FileState::Corrupted) + errors.push_back(newError(corruptedOTPError)); + + if (validator.seeprom == OnlineValidator::FileState::Missing) + errors.push_back(newError(missingSEEPROMError)); + else if (validator.seeprom == OnlineValidator::FileState::Corrupted) + errors.push_back(newError(corruptedSEEPROMError)); + + if (!validator.missing_files.empty()) + { + int counter = 0; + for (const auto& missingFile : validator.missing_files) + { + errors.push_back(newError(missingFileError, JNIUtils::toJString(env, missingFile))); + + ++counter; + if (counter > 10) + { + break; + } + } + } + + if (!validator.valid_account && validator.account_error != OnlineAccountError::kNone) + { + errors.push_back(newError(accountError, validator.account_error)); + } + + jobjectArray errorsJava = env->NewObjectArray(errors.size(), baseErrorType, nullptr); + + for (int i = 0; i < errors.size(); i++) + { + env->SetObjectArrayElement(errorsJava, i, errors[i]); + } + + return errorsJava; +} + +extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_isOTPPresent([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + return NCrypto::OTP_IsPresent(); +} + +extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL +Java_info_cemu_cemu_nativeinterface_NativeAccount_isSEEPROMPresent([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + return NCrypto::SEEPROM_IsPresent(); +} \ No newline at end of file diff --git a/src/android/app/src/main/cpp/NativeActiveSettings.cpp b/src/android/app/src/main/cpp/NativeActiveSettings.cpp index 274249de..2709c682 100644 --- a/src/android/app/src/main/cpp/NativeActiveSettings.cpp +++ b/src/android/app/src/main/cpp/NativeActiveSettings.cpp @@ -33,4 +33,10 @@ extern "C" [[maybe_unused]] JNIEXPORT void JNICALL Java_info_cemu_cemu_nativeinterface_NativeActiveSettings_setInternalDir(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring internal_dir) { ActiveSettings::SetInternalDir(JNIUtils::toString(env, internal_dir)); +} + +extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL +Java_info_cemu_cemu_nativeinterface_NativeActiveSettings_hasRequiredOnlineFiles(JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + return ActiveSettings::HasRequiredOnlineFiles(); } \ No newline at end of file diff --git a/src/android/app/src/main/cpp/NativeLocalization.cpp b/src/android/app/src/main/cpp/NativeLocalization.cpp index 2bf8d0cb..3067098f 100644 --- a/src/android/app/src/main/cpp/NativeLocalization.cpp +++ b/src/android/app/src/main/cpp/NativeLocalization.cpp @@ -15,7 +15,7 @@ namespace NativeLocalization } } // namespace NativeLocalization -extern "C" JNIEXPORT [[maybe_unused]] void JNICALL +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL Java_info_cemu_cemu_nativeinterface_NativeLocalization_setTranslations(JNIEnv* env, [[maybe_unused]] jclass clazz, jobject translations) { NativeLocalization::g_messages.clear(); diff --git a/src/android/app/src/main/cpp/NativeSettings.cpp b/src/android/app/src/main/cpp/NativeSettings.cpp index b0c124a8..64a62014 100644 --- a/src/android/app/src/main/cpp/NativeSettings.cpp +++ b/src/android/app/src/main/cpp/NativeSettings.cpp @@ -1,6 +1,7 @@ #include "JNIUtils.h" #include "audio/IAudioAPI.h" #include "config/CemuConfig.h" +#include "config/NetworkSettings.h" extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL Java_info_cemu_cemu_nativeinterface_NativeSettings_getOverlayPosition([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) @@ -175,7 +176,7 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_addGamesPath(JNIEnv* env, [[m { auto& gamePaths = GetConfig().game_paths; auto gamePath = JNIUtils::toString(env, uri); - if (std::any_of(gamePaths.begin(), gamePaths.end(), [&](auto path) { return path == gamePath; })) + if (std::any_of(gamePaths.begin(), gamePaths.end(), [&](const auto& path) { return path == gamePath; })) return; gamePaths.push_back(gamePath); } @@ -185,7 +186,7 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_removeGamesPath(JNIEnv* env, { auto gamePath = JNIUtils::toString(env, uri); auto& gamePaths = GetConfig().game_paths; - std::erase_if(gamePaths, [&](auto path) { return path == gamePath; }); + std::erase_if(gamePaths, [&](const auto& path) { return path == gamePath; }); } extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL @@ -320,8 +321,9 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_getAudioLatency([[maybe_unuse extern "C" [[maybe_unused]] JNIEXPORT void JNICALL Java_info_cemu_cemu_nativeinterface_NativeSettings_setAudioLatency([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint latency) { - GetConfig().audio_delay = latency / 12; -// IAudioAPI::SetAudioDelayOverride(GetConfig().audio_delay); + sint32 audioDelay = latency / 12; + GetConfig().audio_delay = audioDelay; + IAudioAPI::SetAudioDelay(audioDelay); } extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL @@ -351,8 +353,38 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_setCustomDriverPath(JNIEnv* e GetConfig().custom_driver_path = JNIUtils::toString(env, custom_driver_path); } +extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL +Java_info_cemu_cemu_nativeinterface_NativeSettings_getAccountNetworkService([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id) +{ + return static_cast(GetConfig().GetAccountNetworkService(persistent_id)); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +Java_info_cemu_cemu_nativeinterface_NativeSettings_setAccountNetworkService([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id, jint network_service) +{ + GetConfig().SetAccountSelectedService(persistent_id, static_cast(network_service)); +} + +extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL +Java_info_cemu_cemu_nativeinterface_NativeSettings_getAccountPersistentId([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + return static_cast(GetConfig().account.m_persistent_id); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +Java_info_cemu_cemu_nativeinterface_NativeSettings_setAccountPersistentId([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id) +{ + GetConfig().account.m_persistent_id = persistent_id; +} + +extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL +Java_info_cemu_cemu_nativeinterface_NativeSettings_hasCustomNetworkConfiguration([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + return NetworkConfig::XMLExists(); +} + extern "C" [[maybe_unused]] JNIEXPORT void JNICALL Java_info_cemu_cemu_nativeinterface_NativeSettings_saveSettings([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) { GetConfigHandle().Save(); -} \ No newline at end of file +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt b/src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt index 769ebecc..f0ef82c5 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt @@ -38,12 +38,10 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.PlatformImeOptions import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import info.cemu.cemu.R @OptIn(ExperimentalMaterial3Api::class) diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/ModifierClickable.kt b/src/android/app/src/main/java/info/cemu/cemu/core/components/ModifierClickable.kt deleted file mode 100644 index 2c6efaee..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/ModifierClickable.kt +++ /dev/null @@ -1,33 +0,0 @@ -package info.cemu.cemu.core.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.semantics.Role - -fun Modifier.clickableWithNoRipple( - enabled: Boolean = true, - onClickLabel: String? = null, - role: Role? = null, - onClick: () -> Unit -): Modifier = composed( - inspectorInfo = debugInspectorInfo { - name = "clickable" - properties["enabled"] = enabled - properties["onClickLabel"] = onClickLabel - properties["role"] = role - properties["onClick"] = onClick - } -) { - Modifier.clickable( - enabled = enabled, - onClickLabel = onClickLabel, - onClick = onClick, - role = role, - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) -} diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt b/src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt index 3a23b762..8119d3a2 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt @@ -19,27 +19,27 @@ import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog -import info.cemu.cemu.R import info.cemu.cemu.core.translation.tr @Composable fun SingleSelection( label: String, choice: String, - choices: List, + choices: Collection, isChoiceEnabled: (String) -> Boolean = { true }, - modifier: Modifier = Modifier, + enabled: Boolean = true, + modifier: Modifier = Modifier.fillMaxWidth(), onChoiceChanged: (String) -> Unit, ) { SingleSelection( @@ -48,6 +48,7 @@ fun SingleSelection( choices = choices, modifier = modifier, choiceToString = { it }, + enabled = enabled, isChoiceEnabled = isChoiceEnabled, onChoiceChanged = onChoiceChanged, ) @@ -58,10 +59,11 @@ fun SingleSelection( fun SingleSelection( label: String, initialChoice: () -> T, - choices: List, + choices: Collection, + modifier: Modifier = Modifier.fillMaxWidth(), choiceToString: @Composable (T) -> String, isChoiceEnabled: (T) -> Boolean = { true }, - modifier: Modifier = Modifier, + enabled: Boolean = true, onChoiceChanged: (T) -> Unit, ) { var choice by rememberSaveable { mutableStateOf(initialChoice()) } @@ -71,6 +73,7 @@ fun SingleSelection( choices = choices, modifier = modifier, isChoiceEnabled = isChoiceEnabled, + enabled = enabled, choiceToString = choiceToString, onChoiceChanged = { newChoice -> choice = newChoice @@ -83,32 +86,45 @@ fun SingleSelection( fun SingleSelection( label: String, choice: T, - choices: List, + choices: Collection, choiceToString: @Composable (T) -> String, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier.fillMaxWidth(), + enabled: Boolean = true, isChoiceEnabled: (T) -> Boolean = { true }, onChoiceChanged: (T) -> Unit, ) { var showSelectDialog by rememberSaveable { mutableStateOf(false) } - Column( - modifier = modifier - .clickable { showSelectDialog = true; } - .fillMaxWidth() - .padding(8.dp), - verticalArrangement = Arrangement.Center, - ) { - Text( - text = label, - modifier = Modifier.padding(vertical = 8.dp), - fontWeight = FontWeight.Medium, - fontSize = 20.sp, - ) - Text( - text = choiceToString(choice), - modifier = Modifier.padding(vertical = 8.dp), - fontSize = 16.sp, - ) + + val clickableModifier = if (enabled) { + Modifier.clickable { showSelectDialog = true } + } else { + Modifier } + CompositionLocalProvider( + LocalContentColor provides + MaterialTheme.colorScheme.onSurface.copy(alpha = if (enabled) 1f else 0.38f) + ) + { + Column( + modifier = modifier + .then(clickableModifier) + .padding(8.dp), + verticalArrangement = Arrangement.Center, + ) { + Text( + text = label, + modifier = Modifier.padding(vertical = 8.dp), + fontWeight = FontWeight.Medium, + fontSize = 20.sp, + ) + Text( + text = choiceToString(choice), + modifier = Modifier.padding(vertical = 8.dp), + fontSize = 16.sp, + ) + } + } + if (showSelectDialog) { SelectDialog( label = label, @@ -126,7 +142,7 @@ fun SingleSelection( private fun SelectDialog( label: String, currentChoice: T, - choices: List, + choices: Collection, isChoiceEnabled: (T) -> Boolean, choiceToString: @Composable (T) -> String, onDismissRequest: () -> Unit, diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt b/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt index 5a50dbbd..3d498c84 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt @@ -1,16 +1,16 @@ package info.cemu.cemu.core.nativeenummapper -import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.nativeinterface.NativeGameTitles +import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.nativeinterface.NativeGameTitles.ConsoleRegion fun regionToString(region: Int): String = when (region) { - NativeGameTitles.CONSOLE_REGION_JPN -> tr("Japan") - NativeGameTitles.CONSOLE_REGION_USA -> tr("USA") - NativeGameTitles.CONSOLE_REGION_EUR -> tr("Europe") - NativeGameTitles.CONSOLE_REGION_AUS_DEPR -> tr("Australia") - NativeGameTitles.CONSOLE_REGION_CHN -> tr("China") - NativeGameTitles.CONSOLE_REGION_KOR -> tr("Korea") - NativeGameTitles.CONSOLE_REGION_TWN -> tr("Taiwan") - NativeGameTitles.CONSOLE_REGION_AUTO -> tr("Auto") + ConsoleRegion.JPN -> tr("Japan") + ConsoleRegion.USA -> tr("USA") + ConsoleRegion.EUR -> tr("Europe") + ConsoleRegion.AUS_DEPR -> tr("Australia") + ConsoleRegion.CHN -> tr("China") + ConsoleRegion.KOR -> tr("Korea") + ConsoleRegion.TWN -> tr("Taiwan") + ConsoleRegion.AUTO -> tr("Auto") else -> tr("Many") } diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt b/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt index f635f8ae..d643ef0d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt @@ -1,13 +1,13 @@ package info.cemu.cemu.core.nativeenummapper import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.nativeinterface.NativeInput +import info.cemu.cemu.nativeinterface.NativeInput.EmulatedControllerType fun controllerTypeToString(type: Int) = when (type) { - NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED -> tr("Disabled") - NativeInput.EMULATED_CONTROLLER_TYPE_VPAD -> tr("Wii U GamePad") - NativeInput.EMULATED_CONTROLLER_TYPE_PRO -> tr("Wii U Pro Controller") - NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE -> tr("Wiimote") - NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC -> tr("Wii U Classic Controller") + EmulatedControllerType.DISABLED -> tr("Disabled") + EmulatedControllerType.VPAD -> tr("Wii U GamePad") + EmulatedControllerType.PRO -> tr("Wii U Pro Controller") + EmulatedControllerType.WIIMOTE -> tr("Wiimote") + EmulatedControllerType.CLASSIC -> tr("Wii U Classic Controller") else -> throw IllegalArgumentException("Invalid controller type: $type") } diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt b/src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt index 4f7f867c..55127365 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt @@ -1,6 +1,5 @@ package info.cemu.cemu.core.theme -import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme @@ -21,7 +20,7 @@ fun CemuTheme( content: @Composable () -> Unit, ) { val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + dynamicColor -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt b/src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt index a130d65e..f95a0bf2 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt @@ -60,6 +60,8 @@ fun setLanguage(languageCode: String, context: Context) { CurrentLanguage = translation.locale.language } +fun getCurrentLocale() = I18n.locale + fun setTranslations(context: Context) { val assetTranslations = context.assets.list(TRANSLATIONS_FOLDER)?.filter { language -> if (language == DEFAULT_LANGUAGE) diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt index 5524adda..80bb4fe1 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt @@ -65,7 +65,7 @@ class EmulationActivity : AppCompatActivity() { NativeEmulation.setSurface(surfaceHolder.surface, isMainCanvas) surfaceSet = true } catch (exception: NativeException) { - onEmulationError(tr(">Failed creating surface: {0}", exception.message!!)) + onEmulationError(tr("Failed creating surface: {0}", exception.message!!)) } } @@ -241,9 +241,8 @@ class EmulationActivity : AppCompatActivity() { setFullscreen() binding = ActivityEmulationBinding.inflate(layoutInflater) - inputOverlaySurfaceView = binding.inputOverlay - inputOverlaySurfaceView.setVisible(overlaySettings.isOverlayEnabled) + initializeInputOverlay() binding.sideMenu.configureSideMenu() @@ -294,16 +293,8 @@ class EmulationActivity : AppCompatActivity() { val mainCanvasHolder = mainCanvas.holder mainCanvasHolder.addCallback(CanvasSurfaceHolderCallback(isMainCanvas = true)) - mainCanvasHolder.addCallback(object : SurfaceHolder.Callback { - override fun surfaceCreated(holder: SurfaceHolder) { - } - - override fun surfaceChanged( - holder: SurfaceHolder, - format: Int, - width: Int, - height: Int, - ) { + mainCanvasHolder.addCallback(object : SurfaceChangedListener() { + override fun surfaceChanged() { if (hasEmulationError) { return } @@ -312,13 +303,16 @@ class EmulationActivity : AppCompatActivity() { startGame(launchPath) } } - - override fun surfaceDestroyed(holder: SurfaceHolder) { - } }) mainCanvas.setOnTouchListener(CanvasOnTouchListener(isTV = true)) } + private fun initializeInputOverlay() { + inputOverlaySurfaceView = binding.inputOverlay + + inputOverlaySurfaceView.setVisible(overlaySettings.isOverlayEnabled) + } + private fun toastMessage(text: String) { toast?.cancel() toast = Toast.makeText(this, text, Toast.LENGTH_SHORT) @@ -327,15 +321,16 @@ class EmulationActivity : AppCompatActivity() { private fun startGame(launchPath: String) { val result = NativeEmulation.startGame(launchPath) - if (result == NativeEmulation.START_GAME_SUCCESSFUL) { + if (result == NativeEmulation.StartGameStatusCode.SUCCESSFUL) { return } val errorMessage = when (result) { - NativeEmulation.START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND -> tr("Unable to launch game because the base files were not found.") - NativeEmulation.START_GAME_ERROR_NO_DISC_KEY -> tr("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.") - NativeEmulation.START_GAME_ERROR_NO_TITLE_TIK -> tr("Could not decrypt title because title.tik is missing.") + NativeEmulation.StartGameStatusCode.ERROR_GAME_BASE_FILES_NOT_FOUND -> tr("Unable to launch game because the base files were not found.") + NativeEmulation.StartGameStatusCode.ERROR_NO_DISC_KEY -> tr("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.") + NativeEmulation.StartGameStatusCode.ERROR_NO_TITLE_TIK -> tr("Could not decrypt title because title.tik is missing.") else -> tr("Unable to launch game\nPath: {0}", launchPath) } + onEmulationError(errorMessage) } diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/SurfaceChangedListener.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/SurfaceChangedListener.kt new file mode 100644 index 00000000..122f2988 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/SurfaceChangedListener.kt @@ -0,0 +1,23 @@ +package info.cemu.cemu.emulation + +import android.view.SurfaceHolder + +abstract class SurfaceChangedListener : SurfaceHolder.Callback { + override fun surfaceCreated(surfaceHolder: SurfaceHolder) { + + } + + abstract fun surfaceChanged() + + override fun surfaceChanged( + surfaceHolder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + surfaceChanged() + } + + override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) { + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt index 1c642a21..f53cfb22 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import info.cemu.cemu.core.components.ScreenContent +import info.cemu.cemu.core.nativeenummapper.regionToString import info.cemu.cemu.core.translation.tr import info.cemu.cemu.nativeinterface.NativeGameTitles import info.cemu.cemu.nativeinterface.NativeGameTitles.Game @@ -106,16 +107,3 @@ private fun TitleDetailsEntry(entryName: String, entryData: T?) { ) } } - -fun regionToString(region: Int): String = when (region) { - NativeGameTitles.CONSOLE_REGION_JPN -> tr("Japan") - NativeGameTitles.CONSOLE_REGION_USA -> tr("USA") - NativeGameTitles.CONSOLE_REGION_EUR -> tr("Europe") - NativeGameTitles.CONSOLE_REGION_AUS_DEPR -> tr("Australia") - NativeGameTitles.CONSOLE_REGION_CHN -> tr("China") - NativeGameTitles.CONSOLE_REGION_KOR -> tr("Korea") - NativeGameTitles.CONSOLE_REGION_TWN -> tr("Taiwan") - NativeGameTitles.CONSOLE_REGION_AUTO -> tr("Auto") - else -> tr("Many") -} - diff --git a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt index 0c69221a..0dc2f6b9 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt @@ -59,10 +59,10 @@ fun GameProfileEditScreen(game: NativeGameTitles.Game?, navigateBack: () -> Unit label = tr("CPU mode"), initialChoice = { NativeGameTitles.getCpuModeForTitle(titleId) }, choices = listOf( - NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER, - NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER, - NativeGameTitles.CPU_MODE_MULTICORERECOMPILER, - NativeGameTitles.CPU_MODE_AUTO + NativeGameTitles.CPUMode.SINGLECOREINTERPRETER, + NativeGameTitles.CPUMode.SINGLECORERECOMPILER, + NativeGameTitles.CPUMode.MULTICORERECOMPILER, + NativeGameTitles.CPUMode.AUTO ), choiceToString = { cpuMode -> cpuModeToString(cpuMode) }, onChoiceChanged = { cpuMode -> NativeGameTitles.setCpuModeForTitle(titleId, cpuMode) } @@ -80,8 +80,8 @@ fun GameProfileEditScreen(game: NativeGameTitles.Game?, navigateBack: () -> Unit } private fun cpuModeToString(cpuMode: Int): String = when (cpuMode) { - NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER -> tr("Single-core interpreter") - NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER -> tr("Single-core recompiler") - NativeGameTitles.CPU_MODE_MULTICORERECOMPILER -> tr("Multi-core recompiler") + NativeGameTitles.CPUMode.SINGLECOREINTERPRETER -> tr("Single-core interpreter") + NativeGameTitles.CPUMode.SINGLECORERECOMPILER -> tr("Single-core recompiler") + NativeGameTitles.CPUMode.MULTICORERECOMPILER -> tr("Multi-core recompiler") else -> tr("Auto (recommended)") } diff --git a/src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt new file mode 100644 index 00000000..fc744123 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt @@ -0,0 +1,122 @@ +package info.cemu.cemu.input + +import info.cemu.cemu.nativeinterface.NativeInput +import info.cemu.cemu.nativeinterface.NativeInput.EmulatedControllerType + +sealed interface NativeInputButton { + val nativeKeyCode: Int +} + +enum class VPadButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.VPADButton.A), + B(NativeInput.VPADButton.B), + X(NativeInput.VPADButton.X), + Y(NativeInput.VPADButton.Y), + L(NativeInput.VPADButton.L), + R(NativeInput.VPADButton.R), + ZL(NativeInput.VPADButton.ZL), + ZR(NativeInput.VPADButton.ZR), + PLUS(NativeInput.VPADButton.PLUS), + MINUS(NativeInput.VPADButton.MINUS), + UP(NativeInput.VPADButton.UP), + DOWN(NativeInput.VPADButton.DOWN), + LEFT(NativeInput.VPADButton.LEFT), + RIGHT(NativeInput.VPADButton.RIGHT), + STICKL(NativeInput.VPADButton.STICKL), + STICKR(NativeInput.VPADButton.STICKR), + STICKL_UP(NativeInput.VPADButton.STICKL_UP), + STICKL_DOWN(NativeInput.VPADButton.STICKL_DOWN), + STICKL_LEFT(NativeInput.VPADButton.STICKL_LEFT), + STICKL_RIGHT(NativeInput.VPADButton.STICKL_RIGHT), + STICKR_UP(NativeInput.VPADButton.STICKR_UP), + STICKR_DOWN(NativeInput.VPADButton.STICKR_DOWN), + STICKR_LEFT(NativeInput.VPADButton.STICKR_LEFT), + STICKR_RIGHT(NativeInput.VPADButton.STICKR_RIGHT), + MIC(NativeInput.VPADButton.MIC), + SCREEN(NativeInput.VPADButton.SCREEN), + HOME(NativeInput.VPADButton.HOME), +} + +enum class ProControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.ProButton.A), + B(NativeInput.ProButton.B), + X(NativeInput.ProButton.X), + Y(NativeInput.ProButton.Y), + L(NativeInput.ProButton.L), + R(NativeInput.ProButton.R), + ZL(NativeInput.ProButton.ZL), + ZR(NativeInput.ProButton.ZR), + PLUS(NativeInput.ProButton.PLUS), + MINUS(NativeInput.ProButton.MINUS), + HOME(NativeInput.ProButton.HOME), + UP(NativeInput.ProButton.UP), + DOWN(NativeInput.ProButton.DOWN), + LEFT(NativeInput.ProButton.LEFT), + RIGHT(NativeInput.ProButton.RIGHT), + STICKL(NativeInput.ProButton.STICKL), + STICKR(NativeInput.ProButton.STICKR), + STICKL_UP(NativeInput.ProButton.STICKL_UP), + STICKL_DOWN(NativeInput.ProButton.STICKL_DOWN), + STICKL_LEFT(NativeInput.ProButton.STICKL_LEFT), + STICKL_RIGHT(NativeInput.ProButton.STICKL_RIGHT), + STICKR_UP(NativeInput.ProButton.STICKR_UP), + STICKR_DOWN(NativeInput.ProButton.STICKR_DOWN), + STICKR_LEFT(NativeInput.ProButton.STICKR_LEFT), + STICKR_RIGHT(NativeInput.ProButton.STICKR_RIGHT), +} + +enum class ClassicControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.ClassicButton.A), + B(NativeInput.ClassicButton.B), + X(NativeInput.ClassicButton.X), + Y(NativeInput.ClassicButton.Y), + L(NativeInput.ClassicButton.L), + R(NativeInput.ClassicButton.R), + ZL(NativeInput.ClassicButton.ZL), + ZR(NativeInput.ClassicButton.ZR), + PLUS(NativeInput.ClassicButton.PLUS), + MINUS(NativeInput.ClassicButton.MINUS), + HOME(NativeInput.ClassicButton.HOME), + UP(NativeInput.ClassicButton.UP), + DOWN(NativeInput.ClassicButton.DOWN), + LEFT(NativeInput.ClassicButton.LEFT), + RIGHT(NativeInput.ClassicButton.RIGHT), + STICKL_UP(NativeInput.ClassicButton.STICKL_UP), + STICKL_DOWN(NativeInput.ClassicButton.STICKL_DOWN), + STICKL_LEFT(NativeInput.ClassicButton.STICKL_LEFT), + STICKL_RIGHT(NativeInput.ClassicButton.STICKL_RIGHT), + STICKR_UP(NativeInput.ClassicButton.STICKR_UP), + STICKR_DOWN(NativeInput.ClassicButton.STICKR_DOWN), + STICKR_LEFT(NativeInput.ClassicButton.STICKR_LEFT), + STICKR_RIGHT(NativeInput.ClassicButton.STICKR_RIGHT), +} + +enum class WiimoteButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.WiimoteButton.A), + B(NativeInput.WiimoteButton.B), + ONE(NativeInput.WiimoteButton.ONE), + TWO(NativeInput.WiimoteButton.TWO), + NUNCHUCK_Z(NativeInput.WiimoteButton.NUNCHUCK_Z), + NUNCHUCK_C(NativeInput.WiimoteButton.NUNCHUCK_C), + PLUS(NativeInput.WiimoteButton.PLUS), + MINUS(NativeInput.WiimoteButton.MINUS), + UP(NativeInput.WiimoteButton.UP), + DOWN(NativeInput.WiimoteButton.DOWN), + LEFT(NativeInput.WiimoteButton.LEFT), + RIGHT(NativeInput.WiimoteButton.RIGHT), + NUNCHUCK_UP(NativeInput.WiimoteButton.NUNCHUCK_UP), + NUNCHUCK_DOWN(NativeInput.WiimoteButton.NUNCHUCK_DOWN), + NUNCHUCK_LEFT(NativeInput.WiimoteButton.NUNCHUCK_LEFT), + NUNCHUCK_RIGHT(NativeInput.WiimoteButton.NUNCHUCK_RIGHT), + HOME(NativeInput.WiimoteButton.HOME), +} + +fun getNativeButtonsForControllerType(controllerType: Int): Array { + return when (controllerType) { + EmulatedControllerType.VPAD -> VPadButtons.entries.toTypedArray() + EmulatedControllerType.PRO -> ProControllerButtons.entries.toTypedArray() + EmulatedControllerType.CLASSIC -> ClassicControllerButtons.entries.toTypedArray() + EmulatedControllerType.WIIMOTE -> WiimoteButtons.entries.toTypedArray() + else -> arrayOf() + } +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt b/src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt index 5a7abf3c..b0b80ceb 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt @@ -4,10 +4,6 @@ import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent import info.cemu.cemu.nativeinterface.NativeInput -import info.cemu.cemu.nativeinterface.NativeInput.VPadButtons -import info.cemu.cemu.nativeinterface.NativeInput.ProControllerButtons -import info.cemu.cemu.nativeinterface.NativeInput.ClassicControllerButtons -import info.cemu.cemu.nativeinterface.NativeInput.WiimoteButtons import info.cemu.cemu.nativeinterface.NativeInput.onNativeAxis import info.cemu.cemu.nativeinterface.NativeInput.onNativeKey import info.cemu.cemu.nativeinterface.NativeInput.setControllerMapping @@ -21,24 +17,24 @@ class InputManager { private fun getNativeAxisKey(axis: Int, isPositive: Boolean): Int { return if (isPositive) { when (axis) { - MotionEvent.AXIS_X -> NativeInput.AXIS_X_POS - MotionEvent.AXIS_Y -> NativeInput.AXIS_Y_POS - MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.ROTATION_X_POS - MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.ROTATION_Y_POS - MotionEvent.AXIS_LTRIGGER -> NativeInput.TRIGGER_X_POS - MotionEvent.AXIS_RTRIGGER -> NativeInput.TRIGGER_Y_POS - MotionEvent.AXIS_HAT_X -> NativeInput.DPAD_RIGHT - MotionEvent.AXIS_HAT_Y -> NativeInput.DPAD_DOWN + MotionEvent.AXIS_X -> NativeInput.Axis.X_POS + MotionEvent.AXIS_Y -> NativeInput.Axis.Y_POS + MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.Axis.ROTATION_X_POS + MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.Axis.ROTATION_Y_POS + MotionEvent.AXIS_LTRIGGER -> NativeInput.Axis.TRIGGER_X_POS + MotionEvent.AXIS_RTRIGGER -> NativeInput.Axis.TRIGGER_Y_POS + MotionEvent.AXIS_HAT_X -> NativeInput.Axis.DPAD_RIGHT + MotionEvent.AXIS_HAT_Y -> NativeInput.Axis.DPAD_DOWN else -> throw InvalidAxisException(axis) } } else { when (axis) { - MotionEvent.AXIS_X -> NativeInput.AXIS_X_NEG - MotionEvent.AXIS_Y -> NativeInput.AXIS_Y_NEG - MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.ROTATION_X_NEG - MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.ROTATION_Y_NEG - MotionEvent.AXIS_HAT_X -> NativeInput.DPAD_LEFT - MotionEvent.AXIS_HAT_Y -> NativeInput.DPAD_UP + MotionEvent.AXIS_X -> NativeInput.Axis.X_NEG + MotionEvent.AXIS_Y -> NativeInput.Axis.Y_NEG + MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.Axis.ROTATION_X_NEG + MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.Axis.ROTATION_Y_NEG + MotionEvent.AXIS_HAT_X -> NativeInput.Axis.DPAD_LEFT + MotionEvent.AXIS_HAT_Y -> NativeInput.Axis.DPAD_UP else -> throw InvalidAxisException(axis) } } @@ -66,7 +62,7 @@ class InputManager { var axis: Int try { axis = getNativeAxisKey(motionRange.axis, axisValue > 0) - } catch (e: InvalidAxisException) { + } catch (_: InvalidAxisException) { continue } if (abs(axisValue.toDouble()) > maxAbsAxisValue) { @@ -226,7 +222,7 @@ class InputManager { } } - val buttons = NativeInput.getNativeButtonsForControllerType(controllerType) + val buttons = getNativeButtonsForControllerType(controllerType) for (button in buttons) { val mapping = getButtonMappings(button).firstOrNull { inputs[it] == true } @@ -261,7 +257,7 @@ class InputManager { } } - private fun getButtonMappings(button: NativeInput.NativeInputButton): Array { + private fun getButtonMappings(button: NativeInputButton): Array { return when (button) { VPadButtons.A, ProControllerButtons.A, diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt b/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt index 88fbf181..c5726d79 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt @@ -3,7 +3,6 @@ package info.cemu.cemu.inputoverlay import android.content.Context import android.graphics.Canvas import android.graphics.Rect -import android.os.Build import android.os.VibrationEffect import android.os.Vibrator import android.os.VibratorManager @@ -28,6 +27,7 @@ import info.cemu.cemu.nativeinterface.NativeInput.getControllerType import info.cemu.cemu.nativeinterface.NativeInput.isControllerDisabled import info.cemu.cemu.nativeinterface.NativeInput.onOverlayAxis import info.cemu.cemu.nativeinterface.NativeInput.onOverlayButton +import kotlin.math.roundToInt class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), OnTouchListener { @@ -58,7 +58,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : init { pixelDensity = context.resources.displayMetrics.densityDpi inputsMinWidthHeight = - Math.round(INPUTS_MIN_WIDTH_HEIGHT_DP * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)) + (INPUTS_MIN_WIDTH_HEIGHT_DP * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).roundToInt() vibrator = getVibrator(context) setOnTouchListener(this) settingsProvider = InputOverlaySettingsManager(context) @@ -104,94 +104,90 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : } private fun getVibrator(context: Context): Vibrator { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val vibratorManager = - context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager - return vibratorManager.defaultVibrator - } - @Suppress("DEPRECATION") - return context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + val vibratorManager = + context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + return vibratorManager.defaultVibrator } private fun overlayButtonToVPADButton(button: OverlayInput): Int { return when (button) { - OverlayButton.A -> NativeInput.VPAD_BUTTON_A - OverlayButton.B -> NativeInput.VPAD_BUTTON_B - OverlayButton.X -> NativeInput.VPAD_BUTTON_X - OverlayButton.Y -> NativeInput.VPAD_BUTTON_Y - OverlayButton.PLUS -> NativeInput.VPAD_BUTTON_PLUS - OverlayButton.MINUS -> NativeInput.VPAD_BUTTON_MINUS - OverlayDpad.DPAD_UP -> NativeInput.VPAD_BUTTON_UP - OverlayDpad.DPAD_DOWN -> NativeInput.VPAD_BUTTON_DOWN - OverlayDpad.DPAD_LEFT -> NativeInput.VPAD_BUTTON_LEFT - OverlayDpad.DPAD_RIGHT -> NativeInput.VPAD_BUTTON_RIGHT - OverlayButton.L_STICK_CLICK -> NativeInput.VPAD_BUTTON_STICKL - OverlayButton.R_STICK_CLICK -> NativeInput.VPAD_BUTTON_STICKR - OverlayButton.L -> NativeInput.VPAD_BUTTON_L - OverlayButton.R -> NativeInput.VPAD_BUTTON_R - OverlayButton.ZR -> NativeInput.VPAD_BUTTON_ZR - OverlayButton.ZL -> NativeInput.VPAD_BUTTON_ZL + OverlayButton.A -> NativeInput.VPADButton.A + OverlayButton.B -> NativeInput.VPADButton.B + OverlayButton.X -> NativeInput.VPADButton.X + OverlayButton.Y -> NativeInput.VPADButton.Y + OverlayButton.PLUS -> NativeInput.VPADButton.PLUS + OverlayButton.MINUS -> NativeInput.VPADButton.MINUS + OverlayDpad.DPAD_UP -> NativeInput.VPADButton.UP + OverlayDpad.DPAD_DOWN -> NativeInput.VPADButton.DOWN + OverlayDpad.DPAD_LEFT -> NativeInput.VPADButton.LEFT + OverlayDpad.DPAD_RIGHT -> NativeInput.VPADButton.RIGHT + OverlayButton.L_STICK_CLICK -> NativeInput.VPADButton.STICKL + OverlayButton.R_STICK_CLICK -> NativeInput.VPADButton.STICKR + OverlayButton.L -> NativeInput.VPADButton.L + OverlayButton.R -> NativeInput.VPADButton.R + OverlayButton.ZR -> NativeInput.VPADButton.ZR + OverlayButton.ZL -> NativeInput.VPADButton.ZL else -> -1 } } private fun overlayButtonToClassicButton(button: OverlayInput): Int { return when (button) { - OverlayButton.A -> NativeInput.CLASSIC_BUTTON_A - OverlayButton.B -> NativeInput.CLASSIC_BUTTON_B - OverlayButton.X -> NativeInput.CLASSIC_BUTTON_X - OverlayButton.Y -> NativeInput.CLASSIC_BUTTON_Y - OverlayButton.PLUS -> NativeInput.CLASSIC_BUTTON_PLUS - OverlayButton.MINUS -> NativeInput.CLASSIC_BUTTON_MINUS - OverlayDpad.DPAD_UP -> NativeInput.CLASSIC_BUTTON_UP - OverlayDpad.DPAD_DOWN -> NativeInput.CLASSIC_BUTTON_DOWN - OverlayDpad.DPAD_LEFT -> NativeInput.CLASSIC_BUTTON_LEFT - OverlayDpad.DPAD_RIGHT -> NativeInput.CLASSIC_BUTTON_RIGHT - OverlayButton.L -> NativeInput.CLASSIC_BUTTON_L - OverlayButton.R -> NativeInput.CLASSIC_BUTTON_R - OverlayButton.ZR -> NativeInput.CLASSIC_BUTTON_ZR - OverlayButton.ZL -> NativeInput.CLASSIC_BUTTON_ZL + OverlayButton.A -> NativeInput.ClassicButton.A + OverlayButton.B -> NativeInput.ClassicButton.B + OverlayButton.X -> NativeInput.ClassicButton.X + OverlayButton.Y -> NativeInput.ClassicButton.Y + OverlayButton.PLUS -> NativeInput.ClassicButton.PLUS + OverlayButton.MINUS -> NativeInput.ClassicButton.MINUS + OverlayDpad.DPAD_UP -> NativeInput.ClassicButton.UP + OverlayDpad.DPAD_DOWN -> NativeInput.ClassicButton.DOWN + OverlayDpad.DPAD_LEFT -> NativeInput.ClassicButton.LEFT + OverlayDpad.DPAD_RIGHT -> NativeInput.ClassicButton.RIGHT + OverlayButton.L -> NativeInput.ClassicButton.L + OverlayButton.R -> NativeInput.ClassicButton.R + OverlayButton.ZR -> NativeInput.ClassicButton.ZR + OverlayButton.ZL -> NativeInput.ClassicButton.ZL else -> -1 } } private fun overlayButtonToProButton(button: OverlayInput): Int { return when (button) { - OverlayButton.A -> NativeInput.PRO_BUTTON_A - OverlayButton.B -> NativeInput.PRO_BUTTON_B - OverlayButton.X -> NativeInput.PRO_BUTTON_X - OverlayButton.Y -> NativeInput.PRO_BUTTON_Y - OverlayButton.PLUS -> NativeInput.PRO_BUTTON_PLUS - OverlayButton.MINUS -> NativeInput.PRO_BUTTON_MINUS - OverlayDpad.DPAD_UP -> NativeInput.PRO_BUTTON_UP - OverlayDpad.DPAD_DOWN -> NativeInput.PRO_BUTTON_DOWN - OverlayDpad.DPAD_LEFT -> NativeInput.PRO_BUTTON_LEFT - OverlayDpad.DPAD_RIGHT -> NativeInput.PRO_BUTTON_RIGHT - OverlayButton.L_STICK_CLICK -> NativeInput.PRO_BUTTON_STICKL - OverlayButton.R_STICK_CLICK -> NativeInput.PRO_BUTTON_STICKR - OverlayButton.L -> NativeInput.PRO_BUTTON_L - OverlayButton.R -> NativeInput.PRO_BUTTON_R - OverlayButton.ZR -> NativeInput.PRO_BUTTON_ZR - OverlayButton.ZL -> NativeInput.PRO_BUTTON_ZL + OverlayButton.A -> NativeInput.ProButton.A + OverlayButton.B -> NativeInput.ProButton.B + OverlayButton.X -> NativeInput.ProButton.X + OverlayButton.Y -> NativeInput.ProButton.Y + OverlayButton.PLUS -> NativeInput.ProButton.PLUS + OverlayButton.MINUS -> NativeInput.ProButton.MINUS + OverlayDpad.DPAD_UP -> NativeInput.ProButton.UP + OverlayDpad.DPAD_DOWN -> NativeInput.ProButton.DOWN + OverlayDpad.DPAD_LEFT -> NativeInput.ProButton.LEFT + OverlayDpad.DPAD_RIGHT -> NativeInput.ProButton.RIGHT + OverlayButton.L_STICK_CLICK -> NativeInput.ProButton.STICKL + OverlayButton.R_STICK_CLICK -> NativeInput.ProButton.STICKR + OverlayButton.L -> NativeInput.ProButton.L + OverlayButton.R -> NativeInput.ProButton.R + OverlayButton.ZR -> NativeInput.ProButton.ZR + OverlayButton.ZL -> NativeInput.ProButton.ZL else -> -1 } } private fun overlayButtonToWiimoteButton(button: OverlayInput): Int { return when (button) { - OverlayButton.A -> NativeInput.WIIMOTE_BUTTON_A - OverlayButton.B -> NativeInput.WIIMOTE_BUTTON_B - OverlayButton.ONE -> NativeInput.WIIMOTE_BUTTON_1 - OverlayButton.TWO -> NativeInput.WIIMOTE_BUTTON_2 - OverlayButton.PLUS -> NativeInput.WIIMOTE_BUTTON_PLUS - OverlayButton.MINUS -> NativeInput.WIIMOTE_BUTTON_MINUS - OverlayButton.HOME -> NativeInput.WIIMOTE_BUTTON_HOME - OverlayDpad.DPAD_UP -> NativeInput.WIIMOTE_BUTTON_UP - OverlayDpad.DPAD_DOWN -> NativeInput.WIIMOTE_BUTTON_DOWN - OverlayDpad.DPAD_LEFT -> NativeInput.WIIMOTE_BUTTON_LEFT - OverlayDpad.DPAD_RIGHT -> NativeInput.WIIMOTE_BUTTON_RIGHT - OverlayButton.C -> NativeInput.WIIMOTE_BUTTON_NUNCHUCK_C - OverlayButton.Z -> NativeInput.WIIMOTE_BUTTON_NUNCHUCK_Z + OverlayButton.A -> NativeInput.WiimoteButton.A + OverlayButton.B -> NativeInput.WiimoteButton.B + OverlayButton.ONE -> NativeInput.WiimoteButton.ONE + OverlayButton.TWO -> NativeInput.WiimoteButton.TWO + OverlayButton.PLUS -> NativeInput.WiimoteButton.PLUS + OverlayButton.MINUS -> NativeInput.WiimoteButton.MINUS + OverlayButton.HOME -> NativeInput.WiimoteButton.HOME + OverlayDpad.DPAD_UP -> NativeInput.WiimoteButton.UP + OverlayDpad.DPAD_DOWN -> NativeInput.WiimoteButton.DOWN + OverlayDpad.DPAD_LEFT -> NativeInput.WiimoteButton.LEFT + OverlayDpad.DPAD_RIGHT -> NativeInput.WiimoteButton.RIGHT + OverlayButton.C -> NativeInput.WiimoteButton.NUNCHUCK_C + OverlayButton.Z -> NativeInput.WiimoteButton.NUNCHUCK_Z else -> -1 } } @@ -219,15 +215,15 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : right: Float, ) { if (joystick == OverlayJoystick.LEFT) { - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKL_UP, up) - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKL_DOWN, down) - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKL_LEFT, left) - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKL_RIGHT, right) + onOverlayAxis(NativeInput.VPADButton.STICKL_UP, up) + onOverlayAxis(NativeInput.VPADButton.STICKL_DOWN, down) + onOverlayAxis(NativeInput.VPADButton.STICKL_LEFT, left) + onOverlayAxis(NativeInput.VPADButton.STICKL_RIGHT, right) } else if (joystick == OverlayJoystick.RIGHT) { - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKR_UP, up) - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKR_DOWN, down) - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKR_LEFT, left) - onOverlayAxis(NativeInput.VPAD_BUTTON_STICKR_RIGHT, right) + onOverlayAxis(NativeInput.VPADButton.STICKR_UP, up) + onOverlayAxis(NativeInput.VPADButton.STICKR_DOWN, down) + onOverlayAxis(NativeInput.VPADButton.STICKR_LEFT, left) + onOverlayAxis(NativeInput.VPADButton.STICKR_RIGHT, right) } } @@ -239,15 +235,15 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : right: Float, ) { if (joystick == OverlayJoystick.LEFT) { - onOverlayAxis(NativeInput.PRO_BUTTON_STICKL_UP, up) - onOverlayAxis(NativeInput.PRO_BUTTON_STICKL_DOWN, down) - onOverlayAxis(NativeInput.PRO_BUTTON_STICKL_LEFT, left) - onOverlayAxis(NativeInput.PRO_BUTTON_STICKL_RIGHT, right) + onOverlayAxis(NativeInput.ProButton.STICKL_UP, up) + onOverlayAxis(NativeInput.ProButton.STICKL_DOWN, down) + onOverlayAxis(NativeInput.ProButton.STICKL_LEFT, left) + onOverlayAxis(NativeInput.ProButton.STICKL_RIGHT, right) } else if (joystick == OverlayJoystick.RIGHT) { - onOverlayAxis(NativeInput.PRO_BUTTON_STICKR_UP, up) - onOverlayAxis(NativeInput.PRO_BUTTON_STICKR_DOWN, down) - onOverlayAxis(NativeInput.PRO_BUTTON_STICKR_LEFT, left) - onOverlayAxis(NativeInput.PRO_BUTTON_STICKR_RIGHT, right) + onOverlayAxis(NativeInput.ProButton.STICKR_UP, up) + onOverlayAxis(NativeInput.ProButton.STICKR_DOWN, down) + onOverlayAxis(NativeInput.ProButton.STICKR_LEFT, left) + onOverlayAxis(NativeInput.ProButton.STICKR_RIGHT, right) } } @@ -259,15 +255,15 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : right: Float, ) { if (joystick == OverlayJoystick.LEFT) { - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKL_UP, up) - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKL_DOWN, down) - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKL_LEFT, left) - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKL_RIGHT, right) + onOverlayAxis(NativeInput.ClassicButton.STICKL_UP, up) + onOverlayAxis(NativeInput.ClassicButton.STICKL_DOWN, down) + onOverlayAxis(NativeInput.ClassicButton.STICKL_LEFT, left) + onOverlayAxis(NativeInput.ClassicButton.STICKL_RIGHT, right) } else if (joystick == OverlayJoystick.RIGHT) { - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKR_UP, up) - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKR_DOWN, down) - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKR_LEFT, left) - onOverlayAxis(NativeInput.CLASSIC_BUTTON_STICKR_RIGHT, right) + onOverlayAxis(NativeInput.ClassicButton.STICKR_UP, up) + onOverlayAxis(NativeInput.ClassicButton.STICKR_DOWN, down) + onOverlayAxis(NativeInput.ClassicButton.STICKR_LEFT, left) + onOverlayAxis(NativeInput.ClassicButton.STICKR_RIGHT, right) } } @@ -279,10 +275,10 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : right: Float, ) { if (joystick == OverlayJoystick.RIGHT) { - onOverlayAxis(NativeInput.WIIMOTE_BUTTON_NUNCHUCK_UP, up) - onOverlayAxis(NativeInput.WIIMOTE_BUTTON_NUNCHUCK_DOWN, down) - onOverlayAxis(NativeInput.WIIMOTE_BUTTON_NUNCHUCK_LEFT, left) - onOverlayAxis(NativeInput.WIIMOTE_BUTTON_NUNCHUCK_RIGHT, right) + onOverlayAxis(NativeInput.WiimoteButton.NUNCHUCK_UP, up) + onOverlayAxis(NativeInput.WiimoteButton.NUNCHUCK_DOWN, down) + onOverlayAxis(NativeInput.WiimoteButton.NUNCHUCK_LEFT, left) + onOverlayAxis(NativeInput.WiimoteButton.NUNCHUCK_RIGHT, right) } } @@ -361,17 +357,17 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : nativeControllerType = getControllerType(controllerIndex) overlyButtonToNativeButton = when (nativeControllerType) { - NativeInput.EMULATED_CONTROLLER_TYPE_VPAD -> ::overlayButtonToVPADButton - NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC -> ::overlayButtonToClassicButton - NativeInput.EMULATED_CONTROLLER_TYPE_PRO -> ::overlayButtonToProButton - NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE -> ::overlayButtonToWiimoteButton + NativeInput.EmulatedControllerType.VPAD -> ::overlayButtonToVPADButton + NativeInput.EmulatedControllerType.CLASSIC -> ::overlayButtonToClassicButton + NativeInput.EmulatedControllerType.PRO -> ::overlayButtonToProButton + NativeInput.EmulatedControllerType.WIIMOTE -> ::overlayButtonToWiimoteButton else -> { _ -> -1 } } onJoystickChange = when (nativeControllerType) { - NativeInput.EMULATED_CONTROLLER_TYPE_VPAD -> ::onVPADJoystickStateChange - NativeInput.EMULATED_CONTROLLER_TYPE_PRO -> ::onProJoystickStateChange - NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC -> ::onClassicJoystickStateChange - NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE -> ::onWiimoteJoystickStateChange + NativeInput.EmulatedControllerType.VPAD -> ::onVPADJoystickStateChange + NativeInput.EmulatedControllerType.PRO -> ::onProJoystickStateChange + NativeInput.EmulatedControllerType.CLASSIC -> ::onClassicJoystickStateChange + NativeInput.EmulatedControllerType.WIIMOTE -> ::onWiimoteJoystickStateChange else -> { _, _, _, _, _ -> } } inputs = mutableListOf>().apply { @@ -381,7 +377,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : addRoundButton(OverlayButton.A) addRoundButton(OverlayButton.B) addJoystick(OverlayJoystick.RIGHT) - if (nativeControllerType != NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE) { + if (nativeControllerType != NativeInput.EmulatedControllerType.WIIMOTE) { addRoundButton(OverlayButton.X) addRoundButton(OverlayButton.Y) addRectangleButton(OverlayButton.ZL) @@ -390,15 +386,15 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : addRectangleButton(OverlayButton.R) addJoystick(OverlayJoystick.LEFT) } - if (nativeControllerType == NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE) { + if (nativeControllerType == NativeInput.EmulatedControllerType.WIIMOTE) { addRoundButton(OverlayButton.ONE, "1") addRoundButton(OverlayButton.TWO, "2") addRoundButton(OverlayButton.C) addRectangleButton(OverlayButton.Z) addRoundButton(OverlayButton.HOME, HomeButtonInnerDrawing()) } - if (nativeControllerType != NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC - && nativeControllerType != NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE + if (nativeControllerType != NativeInput.EmulatedControllerType.CLASSIC + && nativeControllerType != NativeInput.EmulatedControllerType.WIIMOTE ) { addRoundButton(OverlayButton.L_STICK_CLICK, StickClickInnerDrawing()) addRoundButton(OverlayButton.R_STICK_CLICK, StickClickInnerDrawing()) diff --git a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeAccount.kt b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeAccount.kt new file mode 100644 index 00000000..1635ad1f --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeAccount.kt @@ -0,0 +1,78 @@ +package info.cemu.cemu.nativeinterface + +import androidx.annotation.Keep + +object NativeAccount { + const val MAX_ACCOUNT_COUNT = 12 + const val MIN_ACCOUNT_COUNT = 1 + const val MIN_PERSISTENT_ID: UInt = 2147483649u + const val DEFAULT_MII_NAME = "default" + + object AccountGender { + const val FEMALE: Byte = 0 + const val MALE: Byte = 1 + } + + @JvmStatic + external fun createAccount(persistentId: Int, miiName: String) + + @JvmStatic + external fun deleteAccount(persistentId: Int) + + @Keep + data class Account( + val persistentId: Int, + val miiName: String, + val birthday: Long, + val gender: Byte, + val email: String, + val country: Int, + val isValid: Boolean, + ) + + @JvmStatic + external fun getAccounts(): Array + + @JvmStatic + external fun saveAccount(account: Account) + + @Keep + data class AccountCountry( + val index: Int, + val name: String, + ) + + @JvmStatic + external fun getAccountCountries(): Array + + object OnlineAccountError { + const val NO_ACCOUNT_ID = 1 + const val NO_PASSWORD_CACHED = 2 + const val PASSWORD_CACHE_EMPTY = 3 + const val NO_PRINCIPAL_ID = 4 + } + + @Keep + sealed interface OnlineValidationError + @Keep + class MissingOTP : OnlineValidationError + @Keep + class CorruptedOTP : OnlineValidationError + @Keep + class MissingSEEPROM : OnlineValidationError + @Keep + class CorruptedSEEPROM : OnlineValidationError + @Keep + data class MissingFile(val file: String) : OnlineValidationError + @Keep + data class AccountError(val accountError: Int) : OnlineValidationError + + @JvmStatic + external fun getAccountValidationErrors(persistentId: Int): Array + + @JvmStatic + external fun isOTPPresent(): Boolean + + @JvmStatic + external fun isSEEPROMPresent(): Boolean +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeActiveSettings.kt b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeActiveSettings.kt index b92bc9e3..c6432364 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeActiveSettings.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeActiveSettings.kt @@ -15,4 +15,7 @@ object NativeActiveSettings { @JvmStatic external fun getUserDataPath(): String + + @JvmStatic + external fun hasRequiredOnlineFiles(): Boolean } diff --git a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeEmulation.kt b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeEmulation.kt index 867290a9..e31daef3 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeEmulation.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeEmulation.kt @@ -21,11 +21,13 @@ object NativeEmulation { @JvmStatic external fun initializeRenderer(surface: Surface?) - const val START_GAME_SUCCESSFUL: Int = 0 - const val START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND: Int = 1 - const val START_GAME_ERROR_NO_DISC_KEY: Int = 2 - const val START_GAME_ERROR_NO_TITLE_TIK: Int = 3 - const val START_GAME_ERROR_UNKNOWN: Int = 4 + object StartGameStatusCode { + const val SUCCESSFUL: Int = 0 + const val ERROR_GAME_BASE_FILES_NOT_FOUND: Int = 1 + const val ERROR_NO_DISC_KEY: Int = 2 + const val ERROR_NO_TITLE_TIK: Int = 3 + const val ERROR_UNKNOWN: Int = 4 + } @JvmStatic external fun startGame(launchPath: String?): Int diff --git a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeGameTitles.kt b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeGameTitles.kt index f51ede25..c51a8887 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeGameTitles.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeGameTitles.kt @@ -6,14 +6,17 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap object NativeGameTitles { - const val CONSOLE_REGION_JPN: Int = 0x1 - const val CONSOLE_REGION_USA: Int = 0x2 - const val CONSOLE_REGION_EUR: Int = 0x4 - const val CONSOLE_REGION_AUS_DEPR: Int = 0x8 - const val CONSOLE_REGION_CHN: Int = 0x10 - const val CONSOLE_REGION_KOR: Int = 0x20 - const val CONSOLE_REGION_TWN: Int = 0x40 - const val CONSOLE_REGION_AUTO: Int = 0xFF + object ConsoleRegion { + const val JPN: Int = 0x1 + const val USA: Int = 0x2 + const val EUR: Int = 0x4 + const val AUS_DEPR: Int = 0x8 + const val CHN: Int = 0x10 + const val KOR: Int = 0x20 + const val TWN: Int = 0x40 + const val AUTO: Int = 0xFF + } + @JvmStatic external fun isLoadingSharedLibrariesForTitleEnabled(gameTitleId: Long): Boolean @@ -21,10 +24,13 @@ object NativeGameTitles { @JvmStatic external fun setLoadingSharedLibrariesForTitleEnabled(gameTitleId: Long, enabled: Boolean) - const val CPU_MODE_SINGLECOREINTERPRETER: Int = 0 - const val CPU_MODE_SINGLECORERECOMPILER: Int = 1 - const val CPU_MODE_MULTICORERECOMPILER: Int = 3 - const val CPU_MODE_AUTO: Int = 4 + object CPUMode { + const val SINGLECOREINTERPRETER: Int = 0 + const val SINGLECORERECOMPILER: Int = 1 + const val MULTICORERECOMPILER: Int = 3 + const val AUTO: Int = 4 + } + @JvmStatic external fun getCpuModeForTitle(gameTitleId: Long): Int @@ -131,22 +137,26 @@ object NativeGameTitles { @JvmStatic external fun setSaveListCallback(saveListCallback: SaveListCallback?) - const val TITLE_TYPE_UNKNOWN: Int = 0xFF; - const val TITLE_TYPE_BASE_TITLE: Int = 0x00; - const val TITLE_TYPE_BASE_TITLE_DEMO: Int = 0x02; - const val TITLE_TYPE_BASE_TITLE_UPDATE: Int = 0x0E; - const val TITLE_TYPE_HOMEBREW: Int = 0x0F; - const val TITLE_TYPE_AOC: Int = 0x0C; - const val TITLE_TYPE_SYSTEM_TITLE: Int = 0x10; - const val TITLE_TYPE_SYSTEM_DATA: Int = 0x1B; - const val TITLE_TYPE_SYSTEM_OVERLAY_TITLE: Int = 0x30; + object TitleType { + const val UNKNOWN: Int = 0xFF + const val BASE_TITLE: Int = 0x00 + const val BASE_TITLE_DEMO: Int = 0x02 + const val BASE_TITLE_UPDATE: Int = 0x0E + const val HOMEBREW: Int = 0x0F + const val AOC: Int = 0x0C + const val SYSTEM_TITLE: Int = 0x10 + const val SYSTEM_DATA: Int = 0x1B + const val SYSTEM_OVERLAY_TITLE: Int = 0x30 + } - const val TITLE_DATA_FORMAT_HOST_FS: Int = 1; - const val TITLE_DATA_FORMAT_WUD: Int = 2; - const val TITLE_DATA_FORMAT_WIIU_ARCHIVE: Int = 3; - const val TITLE_DATA_FORMAT_NUS: Int = 4; - const val TITLE_DATA_FORMAT_WUHB: Int = 5; - const val TITLE_DATA_FORMAT_INVALID_STRUCTURE: Int = 0; + object TitleDataFormat { + const val HOST_FS: Int = 1 + const val WUD: Int = 2 + const val WIIU_ARCHIVE: Int = 3 + const val NUS: Int = 4 + const val WUHB: Int = 5 + const val INVALID_STRUCTURE: Int = 0 + } @Keep data class TitleData( diff --git a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeInput.kt b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeInput.kt index d3139a1f..df53384d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeInput.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeInput.kt @@ -1,249 +1,136 @@ package info.cemu.cemu.nativeinterface object NativeInput { - sealed interface NativeInputButton { - val nativeKeyCode: Int + object VPADButton { + const val A: Int = 1 + const val B: Int = 2 + const val X: Int = 3 + const val Y: Int = 4 + const val L: Int = 5 + const val R: Int = 6 + const val ZL: Int = 7 + const val ZR: Int = 8 + const val PLUS: Int = 9 + const val MINUS: Int = 10 + const val UP: Int = 11 + const val DOWN: Int = 12 + const val LEFT: Int = 13 + const val RIGHT: Int = 14 + const val STICKL: Int = 15 + const val STICKR: Int = 16 + const val STICKL_UP: Int = 17 + const val STICKL_DOWN: Int = 18 + const val STICKL_LEFT: Int = 19 + const val STICKL_RIGHT: Int = 20 + const val STICKR_UP: Int = 21 + const val STICKR_DOWN: Int = 22 + const val STICKR_LEFT: Int = 23 + const val STICKR_RIGHT: Int = 24 + const val MIC: Int = 25 + const val SCREEN: Int = 26 + const val HOME: Int = 27 } - const val VPAD_BUTTON_NONE: Int = 0 - const val VPAD_BUTTON_A: Int = 1 - const val VPAD_BUTTON_B: Int = 2 - const val VPAD_BUTTON_X: Int = 3 - const val VPAD_BUTTON_Y: Int = 4 - const val VPAD_BUTTON_L: Int = 5 - const val VPAD_BUTTON_R: Int = 6 - const val VPAD_BUTTON_ZL: Int = 7 - const val VPAD_BUTTON_ZR: Int = 8 - const val VPAD_BUTTON_PLUS: Int = 9 - const val VPAD_BUTTON_MINUS: Int = 10 - const val VPAD_BUTTON_UP: Int = 11 - const val VPAD_BUTTON_DOWN: Int = 12 - const val VPAD_BUTTON_LEFT: Int = 13 - const val VPAD_BUTTON_RIGHT: Int = 14 - const val VPAD_BUTTON_STICKL: Int = 15 - const val VPAD_BUTTON_STICKR: Int = 16 - const val VPAD_BUTTON_STICKL_UP: Int = 17 - const val VPAD_BUTTON_STICKL_DOWN: Int = 18 - const val VPAD_BUTTON_STICKL_LEFT: Int = 19 - const val VPAD_BUTTON_STICKL_RIGHT: Int = 20 - const val VPAD_BUTTON_STICKR_UP: Int = 21 - const val VPAD_BUTTON_STICKR_DOWN: Int = 22 - const val VPAD_BUTTON_STICKR_LEFT: Int = 23 - const val VPAD_BUTTON_STICKR_RIGHT: Int = 24 - const val VPAD_BUTTON_MIC: Int = 25 - const val VPAD_BUTTON_SCREEN: Int = 26 - const val VPAD_BUTTON_HOME: Int = 27 - const val VPAD_BUTTON_MAX: Int = 28 - - enum class VPadButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(VPAD_BUTTON_A), - B(VPAD_BUTTON_B), - X(VPAD_BUTTON_X), - Y(VPAD_BUTTON_Y), - L(VPAD_BUTTON_L), - R(VPAD_BUTTON_R), - ZL(VPAD_BUTTON_ZL), - ZR(VPAD_BUTTON_ZR), - PLUS(VPAD_BUTTON_PLUS), - MINUS(VPAD_BUTTON_MINUS), - UP(VPAD_BUTTON_UP), - DOWN(VPAD_BUTTON_DOWN), - LEFT(VPAD_BUTTON_LEFT), - RIGHT(VPAD_BUTTON_RIGHT), - STICKL(VPAD_BUTTON_STICKL), - STICKR(VPAD_BUTTON_STICKR), - STICKL_UP(VPAD_BUTTON_STICKL_UP), - STICKL_DOWN(VPAD_BUTTON_STICKL_DOWN), - STICKL_LEFT(VPAD_BUTTON_STICKL_LEFT), - STICKL_RIGHT(VPAD_BUTTON_STICKL_RIGHT), - STICKR_UP(VPAD_BUTTON_STICKR_UP), - STICKR_DOWN(VPAD_BUTTON_STICKR_DOWN), - STICKR_LEFT(VPAD_BUTTON_STICKR_LEFT), - STICKR_RIGHT(VPAD_BUTTON_STICKR_RIGHT), - MIC(VPAD_BUTTON_MIC), - SCREEN(VPAD_BUTTON_SCREEN), - HOME(VPAD_BUTTON_HOME), + object ProButton { + const val A: Int = 1 + const val B: Int = 2 + const val X: Int = 3 + const val Y: Int = 4 + const val L: Int = 5 + const val R: Int = 6 + const val ZL: Int = 7 + const val ZR: Int = 8 + const val PLUS: Int = 9 + const val MINUS: Int = 10 + const val HOME: Int = 11 + const val UP: Int = 12 + const val DOWN: Int = 13 + const val LEFT: Int = 14 + const val RIGHT: Int = 15 + const val STICKL: Int = 16 + const val STICKR: Int = 17 + const val STICKL_UP: Int = 18 + const val STICKL_DOWN: Int = 19 + const val STICKL_LEFT: Int = 20 + const val STICKL_RIGHT: Int = 21 + const val STICKR_UP: Int = 22 + const val STICKR_DOWN: Int = 23 + const val STICKR_LEFT: Int = 24 + const val STICKR_RIGHT: Int = 25 } - const val PRO_BUTTON_NONE: Int = 0 - const val PRO_BUTTON_A: Int = 1 - const val PRO_BUTTON_B: Int = 2 - const val PRO_BUTTON_X: Int = 3 - const val PRO_BUTTON_Y: Int = 4 - const val PRO_BUTTON_L: Int = 5 - const val PRO_BUTTON_R: Int = 6 - const val PRO_BUTTON_ZL: Int = 7 - const val PRO_BUTTON_ZR: Int = 8 - const val PRO_BUTTON_PLUS: Int = 9 - const val PRO_BUTTON_MINUS: Int = 10 - const val PRO_BUTTON_HOME: Int = 11 - const val PRO_BUTTON_UP: Int = 12 - const val PRO_BUTTON_DOWN: Int = 13 - const val PRO_BUTTON_LEFT: Int = 14 - const val PRO_BUTTON_RIGHT: Int = 15 - const val PRO_BUTTON_STICKL: Int = 16 - const val PRO_BUTTON_STICKR: Int = 17 - const val PRO_BUTTON_STICKL_UP: Int = 18 - const val PRO_BUTTON_STICKL_DOWN: Int = 19 - const val PRO_BUTTON_STICKL_LEFT: Int = 20 - const val PRO_BUTTON_STICKL_RIGHT: Int = 21 - const val PRO_BUTTON_STICKR_UP: Int = 22 - const val PRO_BUTTON_STICKR_DOWN: Int = 23 - const val PRO_BUTTON_STICKR_LEFT: Int = 24 - const val PRO_BUTTON_STICKR_RIGHT: Int = 25 - const val PRO_BUTTON_MAX: Int = 26 - - enum class ProControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(PRO_BUTTON_A), - B(PRO_BUTTON_B), - X(PRO_BUTTON_X), - Y(PRO_BUTTON_Y), - L(PRO_BUTTON_L), - R(PRO_BUTTON_R), - ZL(PRO_BUTTON_ZL), - ZR(PRO_BUTTON_ZR), - PLUS(PRO_BUTTON_PLUS), - MINUS(PRO_BUTTON_MINUS), - HOME(PRO_BUTTON_HOME), - UP(PRO_BUTTON_UP), - DOWN(PRO_BUTTON_DOWN), - LEFT(PRO_BUTTON_LEFT), - RIGHT(PRO_BUTTON_RIGHT), - STICKL(PRO_BUTTON_STICKL), - STICKR(PRO_BUTTON_STICKR), - STICKL_UP(PRO_BUTTON_STICKL_UP), - STICKL_DOWN(PRO_BUTTON_STICKL_DOWN), - STICKL_LEFT(PRO_BUTTON_STICKL_LEFT), - STICKL_RIGHT(PRO_BUTTON_STICKL_RIGHT), - STICKR_UP(PRO_BUTTON_STICKR_UP), - STICKR_DOWN(PRO_BUTTON_STICKR_DOWN), - STICKR_LEFT(PRO_BUTTON_STICKR_LEFT), - STICKR_RIGHT(PRO_BUTTON_STICKR_RIGHT), + object ClassicButton { + const val A: Int = 1 + const val B: Int = 2 + const val X: Int = 3 + const val Y: Int = 4 + const val L: Int = 5 + const val R: Int = 6 + const val ZL: Int = 7 + const val ZR: Int = 8 + const val PLUS: Int = 9 + const val MINUS: Int = 10 + const val HOME: Int = 11 + const val UP: Int = 12 + const val DOWN: Int = 13 + const val LEFT: Int = 14 + const val RIGHT: Int = 15 + const val STICKL_UP: Int = 16 + const val STICKL_DOWN: Int = 17 + const val STICKL_LEFT: Int = 18 + const val STICKL_RIGHT: Int = 19 + const val STICKR_UP: Int = 20 + const val STICKR_DOWN: Int = 21 + const val STICKR_LEFT: Int = 22 + const val STICKR_RIGHT: Int = 23 } - const val CLASSIC_BUTTON_NONE: Int = 0 - const val CLASSIC_BUTTON_A: Int = 1 - const val CLASSIC_BUTTON_B: Int = 2 - const val CLASSIC_BUTTON_X: Int = 3 - const val CLASSIC_BUTTON_Y: Int = 4 - const val CLASSIC_BUTTON_L: Int = 5 - const val CLASSIC_BUTTON_R: Int = 6 - const val CLASSIC_BUTTON_ZL: Int = 7 - const val CLASSIC_BUTTON_ZR: Int = 8 - const val CLASSIC_BUTTON_PLUS: Int = 9 - const val CLASSIC_BUTTON_MINUS: Int = 10 - const val CLASSIC_BUTTON_HOME: Int = 11 - const val CLASSIC_BUTTON_UP: Int = 12 - const val CLASSIC_BUTTON_DOWN: Int = 13 - const val CLASSIC_BUTTON_LEFT: Int = 14 - const val CLASSIC_BUTTON_RIGHT: Int = 15 - const val CLASSIC_BUTTON_STICKL_UP: Int = 16 - const val CLASSIC_BUTTON_STICKL_DOWN: Int = 17 - const val CLASSIC_BUTTON_STICKL_LEFT: Int = 18 - const val CLASSIC_BUTTON_STICKL_RIGHT: Int = 19 - const val CLASSIC_BUTTON_STICKR_UP: Int = 20 - const val CLASSIC_BUTTON_STICKR_DOWN: Int = 21 - const val CLASSIC_BUTTON_STICKR_LEFT: Int = 22 - const val CLASSIC_BUTTON_STICKR_RIGHT: Int = 23 - const val CLASSIC_BUTTON_MAX: Int = 24 - enum class ClassicControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(CLASSIC_BUTTON_A), - B(CLASSIC_BUTTON_B), - X(CLASSIC_BUTTON_X), - Y(CLASSIC_BUTTON_Y), - L(CLASSIC_BUTTON_L), - R(CLASSIC_BUTTON_R), - ZL(CLASSIC_BUTTON_ZL), - ZR(CLASSIC_BUTTON_ZR), - PLUS(CLASSIC_BUTTON_PLUS), - MINUS(CLASSIC_BUTTON_MINUS), - HOME(CLASSIC_BUTTON_HOME), - UP(CLASSIC_BUTTON_UP), - DOWN(CLASSIC_BUTTON_DOWN), - LEFT(CLASSIC_BUTTON_LEFT), - RIGHT(CLASSIC_BUTTON_RIGHT), - STICKL_UP(CLASSIC_BUTTON_STICKL_UP), - STICKL_DOWN(CLASSIC_BUTTON_STICKL_DOWN), - STICKL_LEFT(CLASSIC_BUTTON_STICKL_LEFT), - STICKL_RIGHT(CLASSIC_BUTTON_STICKL_RIGHT), - STICKR_UP(CLASSIC_BUTTON_STICKR_UP), - STICKR_DOWN(CLASSIC_BUTTON_STICKR_DOWN), - STICKR_LEFT(CLASSIC_BUTTON_STICKR_LEFT), - STICKR_RIGHT(CLASSIC_BUTTON_STICKR_RIGHT), + object WiimoteButton { + const val A: Int = 1 + const val B: Int = 2 + const val ONE: Int = 3 + const val TWO: Int = 4 + const val NUNCHUCK_Z: Int = 5 + const val NUNCHUCK_C: Int = 6 + const val PLUS: Int = 7 + const val MINUS: Int = 8 + const val UP: Int = 9 + const val DOWN: Int = 10 + const val LEFT: Int = 11 + const val RIGHT: Int = 12 + const val NUNCHUCK_UP: Int = 13 + const val NUNCHUCK_DOWN: Int = 14 + const val NUNCHUCK_LEFT: Int = 15 + const val NUNCHUCK_RIGHT: Int = 16 + const val HOME: Int = 17 } - const val WIIMOTE_BUTTON_NONE: Int = 0 - const val WIIMOTE_BUTTON_A: Int = 1 - const val WIIMOTE_BUTTON_B: Int = 2 - const val WIIMOTE_BUTTON_1: Int = 3 - const val WIIMOTE_BUTTON_2: Int = 4 - const val WIIMOTE_BUTTON_NUNCHUCK_Z: Int = 5 - const val WIIMOTE_BUTTON_NUNCHUCK_C: Int = 6 - const val WIIMOTE_BUTTON_PLUS: Int = 7 - const val WIIMOTE_BUTTON_MINUS: Int = 8 - const val WIIMOTE_BUTTON_UP: Int = 9 - const val WIIMOTE_BUTTON_DOWN: Int = 10 - const val WIIMOTE_BUTTON_LEFT: Int = 11 - const val WIIMOTE_BUTTON_RIGHT: Int = 12 - const val WIIMOTE_BUTTON_NUNCHUCK_UP: Int = 13 - const val WIIMOTE_BUTTON_NUNCHUCK_DOWN: Int = 14 - const val WIIMOTE_BUTTON_NUNCHUCK_LEFT: Int = 15 - const val WIIMOTE_BUTTON_NUNCHUCK_RIGHT: Int = 16 - const val WIIMOTE_BUTTON_HOME: Int = 17 - const val WIIMOTE_BUTTON_MAX: Int = 18 - - enum class WiimoteButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(WIIMOTE_BUTTON_A), - B(WIIMOTE_BUTTON_B), - ONE(WIIMOTE_BUTTON_1), - TWO(WIIMOTE_BUTTON_2), - NUNCHUCK_Z(WIIMOTE_BUTTON_NUNCHUCK_Z), - NUNCHUCK_C(WIIMOTE_BUTTON_NUNCHUCK_C), - PLUS(WIIMOTE_BUTTON_PLUS), - MINUS(WIIMOTE_BUTTON_MINUS), - UP(WIIMOTE_BUTTON_UP), - DOWN(WIIMOTE_BUTTON_DOWN), - LEFT(WIIMOTE_BUTTON_LEFT), - RIGHT(WIIMOTE_BUTTON_RIGHT), - NUNCHUCK_UP(WIIMOTE_BUTTON_NUNCHUCK_UP), - NUNCHUCK_DOWN(WIIMOTE_BUTTON_NUNCHUCK_DOWN), - NUNCHUCK_LEFT(WIIMOTE_BUTTON_NUNCHUCK_LEFT), - NUNCHUCK_RIGHT(WIIMOTE_BUTTON_NUNCHUCK_RIGHT), - HOME(WIIMOTE_BUTTON_HOME), + object EmulatedControllerType { + const val VPAD: Int = 0 + const val PRO: Int = 1 + const val CLASSIC: Int = 2 + const val WIIMOTE: Int = 3 + const val DISABLED: Int = -1 } - fun getNativeButtonsForControllerType(controllerType: Int): Array { - return when (controllerType) { - EMULATED_CONTROLLER_TYPE_VPAD -> VPadButtons.entries.toTypedArray() - EMULATED_CONTROLLER_TYPE_PRO -> ProControllerButtons.entries.toTypedArray() - EMULATED_CONTROLLER_TYPE_CLASSIC -> ClassicControllerButtons.entries.toTypedArray() - EMULATED_CONTROLLER_TYPE_WIIMOTE -> WiimoteButtons.entries.toTypedArray() - else -> arrayOf() - } + object Axis { + const val DPAD_UP: Int = 34 + const val DPAD_DOWN: Int = 35 + const val DPAD_LEFT: Int = 36 + const val DPAD_RIGHT: Int = 37 + const val X_POS: Int = 38 + const val Y_POS: Int = 39 + const val ROTATION_X_POS: Int = 40 + const val ROTATION_Y_POS: Int = 41 + const val TRIGGER_X_POS: Int = 42 + const val TRIGGER_Y_POS: Int = 43 + const val X_NEG: Int = 44 + const val Y_NEG: Int = 45 + const val ROTATION_X_NEG: Int = 46 + const val ROTATION_Y_NEG: Int = 47 } - const val EMULATED_CONTROLLER_TYPE_VPAD: Int = 0 - const val EMULATED_CONTROLLER_TYPE_PRO: Int = 1 - const val EMULATED_CONTROLLER_TYPE_CLASSIC: Int = 2 - const val EMULATED_CONTROLLER_TYPE_WIIMOTE: Int = 3 - const val EMULATED_CONTROLLER_TYPE_DISABLED: Int = -1 - - const val DPAD_UP: Int = 34 - const val DPAD_DOWN: Int = 35 - const val DPAD_LEFT: Int = 36 - const val DPAD_RIGHT: Int = 37 - const val AXIS_X_POS: Int = 38 - const val AXIS_Y_POS: Int = 39 - const val ROTATION_X_POS: Int = 40 - const val ROTATION_Y_POS: Int = 41 - const val TRIGGER_X_POS: Int = 42 - const val TRIGGER_Y_POS: Int = 43 - const val AXIS_X_NEG: Int = 44 - const val AXIS_Y_NEG: Int = 45 - const val ROTATION_X_NEG: Int = 46 - const val ROTATION_Y_NEG: Int = 47 - const val MAX_CONTROLLERS: Int = 8 const val MAX_VPAD_CONTROLLERS: Int = 2 const val MAX_WPAD_CONTROLLERS: Int = 7 @@ -299,15 +186,9 @@ object NativeInput { @JvmStatic external fun clearControllerMapping(index: Int, mappingId: Int) - fun clearControllerMapping(index: Int, button: NativeInputButton) = - clearControllerMapping(index, button.nativeKeyCode) - @JvmStatic external fun getControllerMapping(index: Int, mappingId: Int): String - fun getControllerMapping(index: Int, button: NativeInputButton): String = - getControllerMapping(index, button.nativeKeyCode) - @JvmStatic external fun getControllerMappings(index: Int): Map diff --git a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeSettings.kt b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeSettings.kt index a686d6e3..2cf78594 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeSettings.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/nativeinterface/NativeSettings.kt @@ -19,9 +19,11 @@ object NativeSettings { @JvmStatic external fun setAsyncShaderCompile(value: Boolean) - const val VSYNC_MODE_OFF: Int = 0 - const val VSYNC_MODE_DOUBLE_BUFFERING: Int = 1 - const val VSYNC_MODE_TRIPLE_BUFFERING: Int = 2 + object VSyncMode { + const val OFF: Int = 0 + const val DOUBLE_BUFFERING: Int = 1 + const val TRIPLE_BUFFERING: Int = 2 + } @JvmStatic external fun getVsyncMode(): Int @@ -29,8 +31,10 @@ object NativeSettings { @JvmStatic external fun setVsyncMode(value: Int) - const val FULLSCREEN_SCALING_KEEP_ASPECT_RATIO: Int = 0 - const val FULLSCREEN_SCALING_STRETCH: Int = 1 + object FullscreenScaling { + const val KEEP_ASPECT_RATIO: Int = 0 + const val STRETCH: Int = 1 + } @JvmStatic external fun getFullscreenScaling(): Int @@ -38,10 +42,12 @@ object NativeSettings { @JvmStatic external fun setFullscreenScaling(value: Int) - const val SCALING_FILTER_BILINEAR_FILTER: Int = 0 - const val SCALING_FILTER_BICUBIC_FILTER: Int = 1 - const val SCALING_FILTER_BICUBIC_HERMITE_FILTER: Int = 2 - const val SCALING_FILTER_NEAREST_NEIGHBOR_FILTER: Int = 3 + object ScalingFilter { + const val BILINEAR_FILTER: Int = 0 + const val BICUBIC_FILTER: Int = 1 + const val BICUBIC_HERMITE_FILTER: Int = 2 + const val NEAREST_NEIGHBOR_FILTER: Int = 3 + } @JvmStatic external fun getUpscalingFilter(): Int @@ -67,9 +73,11 @@ object NativeSettings { @JvmStatic external fun setAudioDeviceEnabled(enabled: Boolean, tv: Boolean) - const val AUDIO_CHANNELS_MONO: Int = 0 - const val AUDIO_CHANNELS_STEREO: Int = 1 - const val AUDIO_CHANNELS_SURROUND: Int = 2 + object AudioChannels { + const val MONO: Int = 0 + const val STEREO: Int = 1 + const val SURROUND: Int = 2 + } @JvmStatic external fun setAudioDeviceChannels(channels: Int, tv: Boolean) @@ -94,13 +102,15 @@ object NativeSettings { @JvmStatic external fun setAudioLatency(value: Int) - const val OVERLAY_SCREEN_POSITION_DISABLED: Int = 0 - const val OVERLAY_SCREEN_POSITION_TOP_LEFT: Int = 1 - const val OVERLAY_SCREEN_POSITION_TOP_CENTER: Int = 2 - const val OVERLAY_SCREEN_POSITION_TOP_RIGHT: Int = 3 - const val OVERLAY_SCREEN_POSITION_BOTTOM_LEFT: Int = 4 - const val OVERLAY_SCREEN_POSITION_BOTTOM_CENTER: Int = 5 - const val OVERLAY_SCREEN_POSITION_BOTTOM_RIGHT: Int = 6 + object OverlayScreenPosition { + const val DISABLED: Int = 0 + const val TOP_LEFT: Int = 1 + const val TOP_CENTER: Int = 2 + const val TOP_RIGHT: Int = 3 + const val BOTTOM_LEFT: Int = 4 + const val BOTTOM_CENTER: Int = 5 + const val BOTTOM_RIGHT: Int = 6 + } @JvmStatic external fun getOverlayPosition(): Int @@ -177,18 +187,20 @@ object NativeSettings { @JvmStatic external fun setNotificationFriendListEnabled(value: Boolean) - const val CONSOLE_LANGUAGE_JAPANESE: Int = 0 - const val CONSOLE_LANGUAGE_ENGLISH: Int = 1 - const val CONSOLE_LANGUAGE_FRENCH: Int = 2 - const val CONSOLE_LANGUAGE_GERMAN: Int = 3 - const val CONSOLE_LANGUAGE_ITALIAN: Int = 4 - const val CONSOLE_LANGUAGE_SPANISH: Int = 5 - const val CONSOLE_LANGUAGE_CHINESE: Int = 6 - const val CONSOLE_LANGUAGE_KOREAN: Int = 7 - const val CONSOLE_LANGUAGE_DUTCH: Int = 8 - const val CONSOLE_LANGUAGE_PORTUGUESE: Int = 9 - const val CONSOLE_LANGUAGE_RUSSIAN: Int = 10 - const val CONSOLE_LANGUAGE_TAIWANESE: Int = 11 + object ConsoleLanguage { + const val JAPANESE: Int = 0 + const val ENGLISH: Int = 1 + const val FRENCH: Int = 2 + const val GERMAN: Int = 3 + const val ITALIAN: Int = 4 + const val SPANISH: Int = 5 + const val CHINESE: Int = 6 + const val KOREAN: Int = 7 + const val DUTCH: Int = 8 + const val PORTUGUESE: Int = 9 + const val RUSSIAN: Int = 10 + const val TAIWANESE: Int = 11 + } @JvmStatic external fun getConsoleLanguage(): Int @@ -207,4 +219,29 @@ object NativeSettings { */ @JvmStatic external fun setCustomDriverPath(path: String?) + + object NetworkService { + const val OFFLINE = 0 + const val NINTENDO = 1 + const val PRETENDO = 2 + const val CUSTOM = 3 + } + + @JvmStatic + external fun getAccountNetworkService(persistentId: Int): Int + + + @JvmStatic + external fun setAccountNetworkService(persistentId: Int, networkService: Int) + + + @JvmStatic + external fun getAccountPersistentId(): Int + + + @JvmStatic + external fun setAccountPersistentId(persistentId: Int) + + @JvmStatic + external fun hasCustomNetworkConfiguration(): Boolean } diff --git a/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt b/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt index 3a5efe71..4bd89d68 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt @@ -77,7 +77,7 @@ class DocumentsProvider : DocumentsProvider() { override fun createDocument( parentDocumentId: String, mimeType: String, - displayName: String + displayName: String, ): String { val parentFile = getFile(parentDocumentId) val newFile = resolveWithoutConflict(parentFile, displayName) @@ -199,15 +199,15 @@ class DocumentsProvider : DocumentsProvider() { override fun moveDocument( sourceDocumentId: String, sourceParentDocumentId: String, - targetParentDocumentId: String + targetParentDocumentId: String, ): String { try { val newDocumentId = copyDocument(sourceDocumentId, sourceParentDocumentId, targetParentDocumentId) removeDocument(sourceDocumentId, sourceParentDocumentId) return newDocumentId - } catch (e: FileNotFoundException) { - throw FileNotFoundException("Couldn't move document '$sourceDocumentId'") + } catch (notFoundException: FileNotFoundException) { + throw FileNotFoundException("Couldn't move document '$sourceDocumentId' ${notFoundException.message}") } } @@ -215,7 +215,7 @@ class DocumentsProvider : DocumentsProvider() { override fun queryChildDocuments( parentDocumentId: String, projection: Array?, - sortOrder: String? + sortOrder: String?, ): Cursor { val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) val parent = getFile(parentDocumentId) @@ -230,7 +230,7 @@ class DocumentsProvider : DocumentsProvider() { override fun openDocument( documentId: String, mode: String, - signal: CancellationSignal? + signal: CancellationSignal?, ): ParcelFileDescriptor { val file = getFile(documentId) val accessMode = ParcelFileDescriptor.parseMode(mode) @@ -245,7 +245,7 @@ class DocumentsProvider : DocumentsProvider() { private fun copyDocument( sourceDocumentId: String, sourceParentDocumentId: String, - targetParentDocumentId: String + targetParentDocumentId: String, ): String { if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) { throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") @@ -260,7 +260,7 @@ class DocumentsProvider : DocumentsProvider() { } // Makes sure two files don't have the same name by adding a number to the end - val noConflictId = 1 + var noConflictId = 1 val periodIndex = name.lastIndexOf('.') var extension = "" var baseName = name @@ -270,6 +270,7 @@ class DocumentsProvider : DocumentsProvider() { } while (file.exists()) { val newFileName = "$baseName ($noConflictId)$extension" + noConflictId++ file = file.toPath().resolve(newFileName).toFile() } return file @@ -331,7 +332,7 @@ class DocumentsProvider : DocumentsProvider() { if (documentId!!.startsWith(ROOT_ID)) { val file = resolve(baseDirectory, documentId.substring(ROOT_ID.length + 1)) if (!file.exists()) { - throw FileNotFoundException(file.absolutePath + " " + documentId + " not found") + throw FileNotFoundException("${file.absolutePath} $documentId not found") } return file } else { @@ -345,7 +346,7 @@ class DocumentsProvider : DocumentsProvider() { companion object { const val ROOT_ID: String = "root" - const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".provider" + const val AUTHORITY: String = "${BuildConfig.APPLICATION_ID}.provider" private val DEFAULT_ROOT_PROJECTION = arrayOf( DocumentsContract.Root.COLUMN_ROOT_ID, DocumentsContract.Root.COLUMN_MIME_TYPES, diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt index a7fb625b..e2d70315 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt @@ -11,6 +11,7 @@ data class SettingsHomeScreenActions( val goToInputSettings: () -> Unit, val goToGraphicsSettings: () -> Unit, val goToAudioSettings: () -> Unit, + val goToAccountSettings: () -> Unit, val goToOverlaySettings: () -> Unit, ) @@ -40,5 +41,9 @@ fun SettingsHomeScreen(navigateBack: () -> Unit, actions: SettingsHomeScreenActi label = tr("Overlay settings"), onClick = dropUnlessResumed(block = actions.goToOverlaySettings) ) + Button( + label = tr("Account settings"), + onClick = dropUnlessResumed(block = actions.goToAccountSettings) + ) } } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsNavigation.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsNavigation.kt index 40fd64b1..cc91755a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsNavigation.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsNavigation.kt @@ -5,6 +5,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import androidx.navigation.toRoute +import info.cemu.cemu.settings.account.AccountSettingsScreen import info.cemu.cemu.settings.audio.AudioSettingsScreen import info.cemu.cemu.settings.customdrivers.CustomDriversScreen import info.cemu.cemu.settings.gamespath.GamePathsScreen @@ -56,6 +57,9 @@ private object SettingsRoutes { @Serializable object InputOverlaySettingsScreenRoute + + @Serializable + object AccountSettingsScreenRoute } fun NavGraphBuilder.settingsNavigation(navController: NavHostController) { @@ -69,6 +73,7 @@ fun NavGraphBuilder.settingsNavigation(navController: NavHostController) { goToGraphicsSettings = { navController.navigate(SettingsRoutes.GraphicsSettingsScreenRoute) }, goToAudioSettings = { navController.navigate(SettingsRoutes.AudioSettingsScreenRoute) }, goToOverlaySettings = { navController.navigate(SettingsRoutes.OverlaySettingsScreenRoute) }, + goToAccountSettings = { navController.navigate(SettingsRoutes.AccountSettingsScreenRoute) } ) ) } @@ -140,5 +145,11 @@ fun NavGraphBuilder.settingsNavigation(navController: NavHostController) { ) } } + + composable { + AccountSettingsScreen( + navigateBack = { navController.popBackStack() }, + ) + } } } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt new file mode 100644 index 00000000..45d1324f --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt @@ -0,0 +1,568 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package info.cemu.cemu.settings.account + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import info.cemu.cemu.R +import info.cemu.cemu.core.components.Header +import info.cemu.cemu.core.components.ScreenContent +import info.cemu.cemu.core.components.SingleSelection +import info.cemu.cemu.core.translation.getCurrentLocale +import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.nativeinterface.NativeAccount +import info.cemu.cemu.nativeinterface.NativeAccount.AccountGender +import info.cemu.cemu.nativeinterface.NativeAccount.DEFAULT_MII_NAME +import info.cemu.cemu.nativeinterface.NativeAccount.MAX_ACCOUNT_COUNT +import info.cemu.cemu.nativeinterface.NativeAccount.MIN_ACCOUNT_COUNT +import info.cemu.cemu.nativeinterface.NativeSettings +import info.cemu.cemu.nativeinterface.NativeSettings.NetworkService +import info.cemu.cemu.utils.parseHexOrNull +import java.text.SimpleDateFormat +import java.util.Date + +private val Countries = NativeAccount.getAccountCountries().toList() +private val CountriesIndices = Countries.map { it.index } +private val CountriesMap = Countries.associate { it.index to it.name } + +@Composable +fun AccountSettingsScreen( + navigateBack: () -> Unit, + accountsViewModel: AccountsViewModel = viewModel(), +) { + val accounts by accountsViewModel.accounts.collectAsState() + val activeAccountData by accountsViewModel.activeAccount.collectAsState() + val activeAccount by remember { + derivedStateOf { + accounts.firstOrNull { it.persistentId == activeAccountData.persistentId } + ?: accounts.first() + } + } + val onlineFullyValid = + activeAccount.isValid && accountsViewModel.onlineFilesStatus.hasRequiredOnlineFiles + val hasCustomNetworkConfiguration = remember { NativeSettings.hasCustomNetworkConfiguration() } + var showCreateAccountDialog by remember { mutableStateOf(false) } + var showDeleteAccountDialog by remember { mutableStateOf(false) } + + ScreenContent( + appBarText = tr("Account settings"), + navigateBack = navigateBack, + ) { + SingleSelection( + label = tr("Active account"), + choice = activeAccount, + choices = accounts, + choiceToString = { String.format("%s (%x)", it.miiName, it.persistentId) }, + onChoiceChanged = { accountsViewModel.setActiveAccount(it.persistentId) } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button( + enabled = accounts.size < MAX_ACCOUNT_COUNT, + onClick = { showCreateAccountDialog = true }) { + Text(tr("Create")) + } + + OutlinedButton( + enabled = accounts.size > MIN_ACCOUNT_COUNT, + onClick = { showDeleteAccountDialog = true } + ) { + Text(tr("Delete")) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + SingleSelection( + label = tr("Network service") + " (${activeAccount.miiName})", + enabled = onlineFullyValid, + choice = if (onlineFullyValid) activeAccountData.networkService else NetworkService.OFFLINE, + choiceToString = { networkServiceToString(it) }, + choices = listOf( + NetworkService.OFFLINE, + NetworkService.NINTENDO, + NetworkService.PRETENDO, + NetworkService.CUSTOM, + ), + isChoiceEnabled = { it != NetworkService.CUSTOM || hasCustomNetworkConfiguration }, + onChoiceChanged = accountsViewModel::setNetworkServiceForActiveAccount + ) + + OnlinePlayRequirements( + account = activeAccount, + onlineFilesStatus = accountsViewModel.onlineFilesStatus, + onlineFullyValid = onlineFullyValid, + onGetOnlineValidationErrors = accountsViewModel::getActiveAccountValidationErrors + ) + + AccountInformation( + account = activeAccount, + onDataChange = accountsViewModel::saveAccount, + ) + } + + if (showCreateAccountDialog) { + CreateAccountDialog( + onDismissRequest = { showCreateAccountDialog = false }, + onCreateAccount = { + accountsViewModel.createAccount(it) + showCreateAccountDialog = false + }, + onValidateCreateAccount = accountsViewModel::validateCreateAccount + ) + } + + if (showDeleteAccountDialog) { + DeleteActiveAccountConfirmationDialog( + onDismissRequest = { showDeleteAccountDialog = false }, + onConfirmation = { + accountsViewModel.deleteActiveAccount() + showDeleteAccountDialog = false + }, + account = activeAccount, + ) + } +} + +@Composable +private fun OnlinePlayRequirements( + account: NativeAccount.Account, + onlineFilesStatus: OnlineFilesStatus, + onlineFullyValid: Boolean, + onGetOnlineValidationErrors: () -> Array, +) { + var onlineValidationErrors by remember { + mutableStateOf?>( + null + ) + } + + Header(tr("Online play requirements")) + + Row( + modifier = Modifier.padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = if (onlineFullyValid) Icons.Outlined.CheckCircle else Icons.Outlined.Warning, + modifier = Modifier.padding(end = 2.dp), + contentDescription = null + ) + Text(getAccountStatus(account, onlineFilesStatus)) + } + + if (!onlineFullyValid) { + Button( + onClick = { + if (onlineValidationErrors != null) return@Button + onlineValidationErrors = onGetOnlineValidationErrors() + }, + modifier = Modifier.padding(top = 2.dp, bottom = 8.dp, start = 8.dp, end = 8.dp) + ) { + Text(tr("Show online status")) + } + } + + OnlineTutorial() + + onlineValidationErrors?.let { + OnlineErrorsDialog( + validationErrors = it, + onDismissRequest = { onlineValidationErrors = null }) + } +} + +@Composable +fun OnlineErrorsDialog( + validationErrors: Array, + onDismissRequest: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + title = { + Text(tr("Online status")) + }, + text = { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + ) { + validationErrors.forEach { + Text( + modifier = Modifier.padding(horizontal = 2.dp, vertical = 4.dp), + text = getErrorMessage(it) + ) + } + } + }, + confirmButton = { + TextButton(onClick = onDismissRequest) { + Text(tr("OK")) + } + }, + ) +} + +private fun getErrorMessage(error: NativeAccount.OnlineValidationError): String { + return when (error) { + is NativeAccount.AccountError -> getAccountErrorMessage(error.accountError) + is NativeAccount.CorruptedOTP -> tr("otp.bin is invalid") + is NativeAccount.CorruptedSEEPROM -> tr("seeprom.bin is invalid") + is NativeAccount.MissingFile -> tr("Missing certificate and key files:") + "\n${error.file}" + is NativeAccount.MissingOTP -> tr("otp.bin missing in Cemu directory") + is NativeAccount.MissingSEEPROM -> tr("seeprom.bin missing in Cemu directory") + } +} + +private fun getAccountErrorMessage(error: Int): String = when (error) { + NativeAccount.OnlineAccountError.NO_ACCOUNT_ID -> tr("AccountId missing (The account is not connected to a NNID/PNID)") + NativeAccount.OnlineAccountError.NO_PASSWORD_CACHED -> tr("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)") + NativeAccount.OnlineAccountError.PASSWORD_CACHE_EMPTY -> tr("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)") + NativeAccount.OnlineAccountError.NO_PRINCIPAL_ID -> tr("PrincipalId missing") + else -> "no error" +} + +private fun getAccountStatus( + account: NativeAccount.Account, + onlineFilesStatus: OnlineFilesStatus, +): String { + if (onlineFilesStatus.hasRequiredOnlineFiles) { + return if (account.isValid) tr("Selected account is a valid online account") + else tr("Selected account is not linked to a NNID or PNID") + } + + return when { + onlineFilesStatus.isOTPPresent && onlineFilesStatus.isSEEPREOMPresent -> tr("OTP and SEEPROM present but no certificate files were found") + onlineFilesStatus.isOTPPresent != onlineFilesStatus.isSEEPREOMPresent -> tr("OTP.bin or SEEPROM.bin is missing") + else -> tr("Online play is not set up. Follow the guide below to get started") + } +} + +@Composable +private fun OnlineTutorial() { + Text( + modifier = Modifier.padding(8.dp), + text = buildAnnotatedString { + withLink( + LinkAnnotation.Url( + stringResource(R.string.cemu_online_guide), + TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colorScheme.onSurfaceVariant, + textDecoration = TextDecoration.Underline, + ), + ) + ) + ) { + append(tr("Online play tutorial")) + } + } + ) +} + +@Composable +private fun AccountInformation( + account: NativeAccount.Account, + onDataChange: (NativeAccount.Account) -> Unit, +) { + Header(tr("Account information")) + + TextField( + value = account.persistentId.toUInt().toString(16), + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + singleLine = true, + onValueChange = {}, + readOnly = true, + label = { Text(tr("PersistentId")) }, + ) + + TextField( + value = account.miiName, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + singleLine = true, + onValueChange = { onDataChange(account.copy(miiName = it.ifBlank { DEFAULT_MII_NAME })) }, + label = { Text(tr("Mii name")) }, + ) + + AccountBirthday( + account = account, + onDataChange = onDataChange, + ) + + SingleSelection( + choice = account.gender, + choiceToString = { accountGenderToString(it) }, + choices = listOf( + AccountGender.FEMALE, + AccountGender.MALE, + ), + onChoiceChanged = { onDataChange(account.copy(gender = it)) }, + label = tr("Gender"), + ) + + TextField( + value = account.email, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + singleLine = true, + onValueChange = { onDataChange(account.copy(email = it)) }, + label = { Text(tr("Email")) }, + ) + + SingleSelection( + label = tr("Country"), + choice = account.country, + choiceToString = { CountriesMap[it] ?: it.toString() }, + choices = CountriesIndices, + onChoiceChanged = { onDataChange(account.copy(country = it)) } + ) +} + +@Composable +private fun AccountBirthday( + account: NativeAccount.Account, + onDataChange: (NativeAccount.Account) -> Unit, +) { + var showDatePicker by remember { mutableStateOf(false) } + Text( + text = tr("Birthday: {0}", convertMillisToDate(account.birthday)), + modifier = Modifier.padding(top = 8.dp, bottom = 2.dp, start = 8.dp, end = 8.dp), + ) + + Button( + onClick = { showDatePicker = true }, + modifier = Modifier.padding(top = 2.dp, bottom = 8.dp, start = 8.dp, end = 8.dp) + ) { + Text(tr("Pick a date")) + } + + if (showDatePicker) { + DatePickerModal( + initialDateMillis = account.birthday, + onDateSelected = { onDataChange(account.copy(birthday = it)) }, + onDismiss = { showDatePicker = false }, + ) + } +} + + +@Composable +private fun DatePickerModal( + initialDateMillis: Long, + onDateSelected: (Long) -> Unit, + onDismiss: () -> Unit, +) { + val datePickerState = rememberDatePickerState(initialSelectedDateMillis = initialDateMillis) + + DatePickerDialog( + onDismissRequest = onDismiss, + confirmButton = { + TextButton(onClick = { + onDateSelected(datePickerState.selectedDateMillis ?: 0) + onDismiss() + }) { + Text(tr("OK")) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(tr("Cancel")) + } + } + ) { + DatePicker(state = datePickerState) + } +} + + +private fun convertMillisToDate(millis: Long): String { + val formatter = SimpleDateFormat("yyyy-MM-dd", getCurrentLocale()) + return formatter.format(Date(millis)) +} + +@Composable +private fun DeleteActiveAccountConfirmationDialog( + account: NativeAccount.Account, + onDismissRequest: () -> Unit, + onConfirmation: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + title = { + Text(tr("Confirmation")) + }, + text = { + Text( + tr( + "Are you sure you want to delete the account {0} with id {1}?", + account.miiName, + account.persistentId.toUInt().toString(16) + ) + ) + }, + confirmButton = { + TextButton(onClick = onConfirmation) { + Text(tr("Yes")) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(tr("No")) + } + } + ) +} + +@Composable +private fun CreateAccountDialog( + onDismissRequest: () -> Unit, + onValidateCreateAccount: (CreateAccount) -> CreateAccountError?, + onCreateAccount: (CreateAccount) -> Unit, +) { + var createAccount by remember { + mutableStateOf( + CreateAccount( + persistentId = NativeAccount.MIN_PERSISTENT_ID.toInt(), + miiName = "", + ) + ) + } + val createError by remember { + derivedStateOf { + onValidateCreateAccount(createAccount) + } + } + + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(tr("Create new account")) }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + TextField( + value = createAccount.miiName, + singleLine = true, + isError = createError != null, + supportingText = { + if (createError is CreateAccountError.EmptyMiiName) { + Text(tr("Account name may not be empty!")) + } + }, + onValueChange = { createAccount = createAccount.copy(miiName = it) }, + label = { Text(tr("Mii name")) }, + ) + TextField( + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), + value = createAccount.persistentId?.toUInt()?.toString(16) ?: "", + singleLine = true, + isError = createError != null, + supportingText = { + val currentError = createError + val errorMessage = when (currentError) { + is CreateAccountError.ConflictingPersistentId -> tr( + "The persistent id {0} is already in use by account {1}!", + currentError.existingPersistentId.toUInt().toString(16), + currentError.existingMiiName, + ) + + CreateAccountError.EmptyPersistentId -> tr("No persistent id entered!") + CreateAccountError.InvalidPersistentId -> tr( + "The persistent id must be greater than {0}!", + NativeAccount.MIN_PERSISTENT_ID.toString(16) + ) + + else -> return@TextField + } + + Text(errorMessage) + }, + onValueChange = { + if (it.isEmpty()) { + createAccount = createAccount.copy(persistentId = null) + return@TextField + } + val hexValue = it.parseHexOrNull() ?: return@TextField + createAccount = createAccount.copy(persistentId = hexValue.toInt()) + }, + label = { Text(tr("PersistentId")) }, + ) + } + }, + confirmButton = { + TextButton(onClick = { + if (createError == null) { + onCreateAccount(createAccount) + } + }) { + Text(tr("OK")) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(tr("Cancel")) + } + } + ) +} + +private fun accountGenderToString(gender: Byte) = when (gender) { + AccountGender.FEMALE -> tr("Female") + AccountGender.MALE -> tr("Male") + else -> throw IllegalArgumentException("Invalid account gender: $gender") +} + +private fun networkServiceToString(networkService: Int) = when (networkService) { + NetworkService.OFFLINE -> tr("Offline") + NetworkService.NINTENDO -> tr("Nintendo") + NetworkService.PRETENDO -> tr("Pretendo") + NetworkService.CUSTOM -> tr("Custom") + else -> throw IllegalArgumentException("Invalid network service: $networkService") +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsViewModel.kt new file mode 100644 index 00000000..eafe5264 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsViewModel.kt @@ -0,0 +1,147 @@ +package info.cemu.cemu.settings.account + +import androidx.lifecycle.ViewModel +import info.cemu.cemu.nativeinterface.NativeAccount +import info.cemu.cemu.nativeinterface.NativeAccount.MAX_ACCOUNT_COUNT +import info.cemu.cemu.nativeinterface.NativeAccount.MIN_ACCOUNT_COUNT +import info.cemu.cemu.nativeinterface.NativeActiveSettings +import info.cemu.cemu.nativeinterface.NativeSettings +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +data class ActiveAccount( + val persistentId: Int, + val networkService: Int, +) + +data class CreateAccount( + val persistentId: Int?, + val miiName: String, +) + +data class OnlineFilesStatus( + val hasRequiredOnlineFiles: Boolean, + val isOTPPresent: Boolean, + val isSEEPREOMPresent: Boolean, +) + +sealed class CreateAccountError { + object InvalidPersistentId : CreateAccountError() + object EmptyPersistentId : CreateAccountError() + data class ConflictingPersistentId( + val existingPersistentId: Int, + val existingMiiName: String, + ) : CreateAccountError() + + object EmptyMiiName : CreateAccountError() +} + +class AccountsViewModel : ViewModel() { + private val _accounts = MutableStateFlow(NativeAccount.getAccounts().toList()) + val accounts = _accounts.asStateFlow() + + val onlineFilesStatus = OnlineFilesStatus( + hasRequiredOnlineFiles = NativeActiveSettings.hasRequiredOnlineFiles(), + isOTPPresent = NativeAccount.isOTPPresent(), + isSEEPREOMPresent = NativeAccount.isSEEPROMPresent(), + ) + + private fun getActiveAccount(persistentId: Int): ActiveAccount { + val networkService = NativeSettings.getAccountNetworkService(persistentId) + + return ActiveAccount( + persistentId = persistentId, + networkService = networkService, + ) + } + + private val _activeAccount = + MutableStateFlow(getActiveAccount(NativeSettings.getAccountPersistentId())) + val activeAccount = _activeAccount.asStateFlow() + + fun setActiveAccount(persistentId: Int) { + if (!accounts.value.any { it.persistentId == persistentId }) { + return + } + + NativeSettings.setAccountPersistentId(persistentId) + _activeAccount.value = getActiveAccount(persistentId) + } + + fun setNetworkServiceForActiveAccount(networkService: Int) { + val activeAccount = activeAccount.value + NativeSettings.setAccountNetworkService(activeAccount.persistentId, networkService) + _activeAccount.value = activeAccount.copy(networkService = networkService) + } + + fun deleteActiveAccount() { + if (accounts.value.size <= MIN_ACCOUNT_COUNT) { + return + } + + val activeAccountPersistentId = activeAccount.value.persistentId + + NativeAccount.deleteAccount(activeAccountPersistentId) + refreshAccountList() + _activeAccount.value = getActiveAccount(_accounts.value.first().persistentId) + } + + + fun validateCreateAccount(createAccountData: CreateAccount): CreateAccountError? { + if (createAccountData.persistentId == null) { + return CreateAccountError.EmptyPersistentId + } + + if (createAccountData.persistentId.toUInt() < NativeAccount.MIN_PERSISTENT_ID) { + return CreateAccountError.InvalidPersistentId + } + + val existingAccount = + accounts.value.firstOrNull { it.persistentId == createAccountData.persistentId } + if (existingAccount != null) { + return CreateAccountError.ConflictingPersistentId( + existingAccount.persistentId, + existingAccount.miiName + ) + } + + if (createAccountData.miiName.isBlank()) { + return CreateAccountError.EmptyMiiName + } + + return null + } + + fun saveAccount(account: NativeAccount.Account) { + NativeAccount.saveAccount(account) + refreshAccountList() + } + + fun createAccount(createAccountData: CreateAccount) { + if (validateCreateAccount(createAccountData) != null) { + return + } + + if (accounts.value.size >= MAX_ACCOUNT_COUNT) { + return + } + + NativeAccount.createAccount(createAccountData.persistentId!!, createAccountData.miiName) + refreshAccountList() + } + + fun getActiveAccountValidationErrors(): Array { + val persistentId = activeAccount.value.persistentId + val accounts = accounts.value + val activeAccount = + accounts.firstOrNull { it.persistentId == persistentId } + ?: accounts.firstOrNull() + ?: return arrayOf() + + return NativeAccount.getAccountValidationErrors(activeAccount.persistentId) + } + + fun refreshAccountList() { + _accounts.value = NativeAccount.getAccounts().toList() + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt index 996b0a87..cd0745ad 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt @@ -39,9 +39,9 @@ fun AudioSettingsScreen(navigateBack: () -> Unit) { onChoiceChanged = { NativeSettings.setAudioDeviceChannels(it, true) }, choiceToString = { channelsToString(it) }, choices = listOf( - NativeSettings.AUDIO_CHANNELS_MONO, - NativeSettings.AUDIO_CHANNELS_STEREO, - NativeSettings.AUDIO_CHANNELS_SURROUND, + NativeSettings.AudioChannels.MONO, + NativeSettings.AudioChannels.STEREO, + NativeSettings.AudioChannels.SURROUND, ), ) Slider( @@ -64,7 +64,7 @@ fun AudioSettingsScreen(navigateBack: () -> Unit) { onChoiceChanged = { NativeSettings.setAudioDeviceChannels(it, false) }, choiceToString = { channelsToString(it) }, choices = listOf( - NativeSettings.AUDIO_CHANNELS_STEREO, + NativeSettings.AudioChannels.STEREO, ), ) Slider( @@ -79,8 +79,8 @@ fun AudioSettingsScreen(navigateBack: () -> Unit) { } fun channelsToString(channels: Int) = when (channels) { - NativeSettings.AUDIO_CHANNELS_MONO -> tr("Mono") - NativeSettings.AUDIO_CHANNELS_STEREO -> tr("Stereo") - NativeSettings.AUDIO_CHANNELS_SURROUND -> tr("Surround") + NativeSettings.AudioChannels.MONO -> tr("Mono") + NativeSettings.AudioChannels.STEREO -> tr("Stereo") + NativeSettings.AudioChannels.SURROUND -> tr("Surround") else -> throw IllegalArgumentException("Invalid channels type: $channels") } diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt index 1e2c835c..4f44e0fc 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt @@ -24,7 +24,7 @@ fun GeneralSettingsScreen( navigateBack = navigateBack, ) { Button( - label = "Add game path", + label = tr("Add game path"), description = tr("Add the root directory of your game(s). It will scan all directories in it for games"), onClick = dropUnlessResumed { goToGamePathsSettings() }, ) @@ -41,18 +41,18 @@ fun GeneralSettingsScreen( onChoiceChanged = NativeSettings::setConsoleLanguage, choiceToString = { consoleLanguageToString(it) }, choices = listOf( - NativeSettings.CONSOLE_LANGUAGE_JAPANESE, - NativeSettings.CONSOLE_LANGUAGE_ENGLISH, - NativeSettings.CONSOLE_LANGUAGE_FRENCH, - NativeSettings.CONSOLE_LANGUAGE_GERMAN, - NativeSettings.CONSOLE_LANGUAGE_ITALIAN, - NativeSettings.CONSOLE_LANGUAGE_SPANISH, - NativeSettings.CONSOLE_LANGUAGE_CHINESE, - NativeSettings.CONSOLE_LANGUAGE_KOREAN, - NativeSettings.CONSOLE_LANGUAGE_DUTCH, - NativeSettings.CONSOLE_LANGUAGE_PORTUGUESE, - NativeSettings.CONSOLE_LANGUAGE_RUSSIAN, - NativeSettings.CONSOLE_LANGUAGE_TAIWANESE, + NativeSettings.ConsoleLanguage.JAPANESE, + NativeSettings.ConsoleLanguage.ENGLISH, + NativeSettings.ConsoleLanguage.FRENCH, + NativeSettings.ConsoleLanguage.GERMAN, + NativeSettings.ConsoleLanguage.ITALIAN, + NativeSettings.ConsoleLanguage.SPANISH, + NativeSettings.ConsoleLanguage.CHINESE, + NativeSettings.ConsoleLanguage.KOREAN, + NativeSettings.ConsoleLanguage.DUTCH, + NativeSettings.ConsoleLanguage.PORTUGUESE, + NativeSettings.ConsoleLanguage.RUSSIAN, + NativeSettings.ConsoleLanguage.TAIWANESE, ), ) Toggle( @@ -67,17 +67,17 @@ fun GeneralSettingsScreen( } private fun consoleLanguageToString(channels: Int): String = when (channels) { - NativeSettings.CONSOLE_LANGUAGE_JAPANESE -> tr("Japanese") - NativeSettings.CONSOLE_LANGUAGE_ENGLISH -> tr("English") - NativeSettings.CONSOLE_LANGUAGE_FRENCH -> tr("French") - NativeSettings.CONSOLE_LANGUAGE_GERMAN -> tr("German") - NativeSettings.CONSOLE_LANGUAGE_ITALIAN -> tr("Italian") - NativeSettings.CONSOLE_LANGUAGE_SPANISH -> tr("Spanish") - NativeSettings.CONSOLE_LANGUAGE_CHINESE -> tr("Chinese") - NativeSettings.CONSOLE_LANGUAGE_KOREAN -> tr("Korean") - NativeSettings.CONSOLE_LANGUAGE_DUTCH -> tr("Dutch") - NativeSettings.CONSOLE_LANGUAGE_PORTUGUESE -> tr("Portuguese") - NativeSettings.CONSOLE_LANGUAGE_RUSSIAN -> tr("Russian") - NativeSettings.CONSOLE_LANGUAGE_TAIWANESE -> tr("Taiwanese") + NativeSettings.ConsoleLanguage.JAPANESE -> tr("Japanese") + NativeSettings.ConsoleLanguage.ENGLISH -> tr("English") + NativeSettings.ConsoleLanguage.FRENCH -> tr("French") + NativeSettings.ConsoleLanguage.GERMAN -> tr("German") + NativeSettings.ConsoleLanguage.ITALIAN -> tr("Italian") + NativeSettings.ConsoleLanguage.SPANISH -> tr("Spanish") + NativeSettings.ConsoleLanguage.CHINESE -> tr("Chinese") + NativeSettings.ConsoleLanguage.KOREAN -> tr("Korean") + NativeSettings.ConsoleLanguage.DUTCH -> tr("Dutch") + NativeSettings.ConsoleLanguage.PORTUGUESE -> tr("Portuguese") + NativeSettings.ConsoleLanguage.RUSSIAN -> tr("Russian") + NativeSettings.ConsoleLanguage.TAIWANESE -> tr("Taiwanese") else -> throw IllegalArgumentException("Invalid console language: $channels") } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt index c15f5ece..a181f63a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt @@ -10,11 +10,11 @@ import info.cemu.cemu.core.translation.tr import info.cemu.cemu.nativeinterface.NativeEmulation import info.cemu.cemu.nativeinterface.NativeSettings -private val SCALING_FILTER_CHOICES = listOf( - NativeSettings.SCALING_FILTER_BILINEAR_FILTER, - NativeSettings.SCALING_FILTER_BICUBIC_FILTER, - NativeSettings.SCALING_FILTER_BICUBIC_HERMITE_FILTER, - NativeSettings.SCALING_FILTER_NEAREST_NEIGHBOR_FILTER +private val ScalingFilterChoices = listOf( + NativeSettings.ScalingFilter.BILINEAR_FILTER, + NativeSettings.ScalingFilter.BICUBIC_FILTER, + NativeSettings.ScalingFilter.BICUBIC_HERMITE_FILTER, + NativeSettings.ScalingFilter.NEAREST_NEIGHBOR_FILTER ) @Composable @@ -44,9 +44,9 @@ fun GraphicsSettingsScreen(navigateBack: () -> Unit, goToCustomDriversSettings: onChoiceChanged = NativeSettings::setVsyncMode, choiceToString = { vsyncModeToString(it) }, choices = listOf( - NativeSettings.VSYNC_MODE_OFF, - NativeSettings.VSYNC_MODE_DOUBLE_BUFFERING, - NativeSettings.VSYNC_MODE_TRIPLE_BUFFERING + NativeSettings.VSyncMode.OFF, + NativeSettings.VSyncMode.DOUBLE_BUFFERING, + NativeSettings.VSyncMode.TRIPLE_BUFFERING ), ) Toggle( @@ -61,8 +61,8 @@ fun GraphicsSettingsScreen(navigateBack: () -> Unit, goToCustomDriversSettings: onChoiceChanged = NativeSettings::setFullscreenScaling, choiceToString = { fullscreenScalingModeToString(it) }, choices = listOf( - NativeSettings.FULLSCREEN_SCALING_KEEP_ASPECT_RATIO, - NativeSettings.FULLSCREEN_SCALING_STRETCH + NativeSettings.FullscreenScaling.KEEP_ASPECT_RATIO, + NativeSettings.FullscreenScaling.STRETCH ), ) SingleSelection( @@ -70,35 +70,35 @@ fun GraphicsSettingsScreen(navigateBack: () -> Unit, goToCustomDriversSettings: initialChoice = NativeSettings::getUpscalingFilter, onChoiceChanged = NativeSettings::setUpscalingFilter, choiceToString = { scalingFilterToString(it) }, - choices = SCALING_FILTER_CHOICES, + choices = ScalingFilterChoices, ) SingleSelection( label = tr("Downscale filter"), initialChoice = NativeSettings::getDownscalingFilter, onChoiceChanged = NativeSettings::setDownscalingFilter, choiceToString = { scalingFilterToString(it) }, - choices = SCALING_FILTER_CHOICES, + choices = ScalingFilterChoices, ) } } private fun scalingFilterToString(scalingFilter: Int) = when (scalingFilter) { - NativeSettings.SCALING_FILTER_BILINEAR_FILTER -> tr("Bilinear") - NativeSettings.SCALING_FILTER_BICUBIC_FILTER -> tr("Bicubic") - NativeSettings.SCALING_FILTER_BICUBIC_HERMITE_FILTER -> tr("Hermite") - NativeSettings.SCALING_FILTER_NEAREST_NEIGHBOR_FILTER -> tr("Nearest neighbor") + NativeSettings.ScalingFilter.BILINEAR_FILTER -> tr("Bilinear") + NativeSettings.ScalingFilter.BICUBIC_FILTER -> tr("Bicubic") + NativeSettings.ScalingFilter.BICUBIC_HERMITE_FILTER -> tr("Hermite") + NativeSettings.ScalingFilter.NEAREST_NEIGHBOR_FILTER -> tr("Nearest neighbor") else -> throw IllegalArgumentException("Invalid scaling filter: $scalingFilter") } private fun vsyncModeToString(vsyncMode: Int) = when (vsyncMode) { - NativeSettings.VSYNC_MODE_OFF -> tr("Off") - NativeSettings.VSYNC_MODE_DOUBLE_BUFFERING -> tr("Double buffering") - NativeSettings.VSYNC_MODE_TRIPLE_BUFFERING -> tr("Triple buffering") + NativeSettings.VSyncMode.OFF -> tr("Off") + NativeSettings.VSyncMode.DOUBLE_BUFFERING -> tr("Double buffering") + NativeSettings.VSyncMode.TRIPLE_BUFFERING -> tr("Triple buffering") else -> throw IllegalArgumentException("Invalid vsync mode: $vsyncMode") } private fun fullscreenScalingModeToString(fullscreenScaling: Int) = when (fullscreenScaling) { - NativeSettings.FULLSCREEN_SCALING_KEEP_ASPECT_RATIO -> tr("Keep aspect ratio") - NativeSettings.FULLSCREEN_SCALING_STRETCH -> tr("Stretch") + NativeSettings.FullscreenScaling.KEEP_ASPECT_RATIO -> tr("Keep aspect ratio") + NativeSettings.FullscreenScaling.STRETCH -> tr("Stretch") else -> throw IllegalArgumentException("Invalid fullscreen scaling mode: $fullscreenScaling") } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt index 709c23b4..65d28c11 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt @@ -2,7 +2,7 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.nativeinterface.NativeInput +import info.cemu.cemu.nativeinterface.NativeInput.ClassicButton @Composable fun ClassicControllerInputs( @@ -25,71 +25,71 @@ fun ClassicControllerInputs( InputItemsGroup( groupName = tr("Buttons"), inputIds = listOf( - NativeInput.CLASSIC_BUTTON_A, - NativeInput.CLASSIC_BUTTON_B, - NativeInput.CLASSIC_BUTTON_X, - NativeInput.CLASSIC_BUTTON_Y, - NativeInput.CLASSIC_BUTTON_L, - NativeInput.CLASSIC_BUTTON_R, - NativeInput.CLASSIC_BUTTON_ZL, - NativeInput.CLASSIC_BUTTON_ZR, - NativeInput.CLASSIC_BUTTON_PLUS, - NativeInput.CLASSIC_BUTTON_MINUS, - NativeInput.CLASSIC_BUTTON_HOME + ClassicButton.A, + ClassicButton.B, + ClassicButton.X, + ClassicButton.Y, + ClassicButton.L, + ClassicButton.R, + ClassicButton.ZL, + ClassicButton.ZR, + ClassicButton.PLUS, + ClassicButton.MINUS, + ClassicButton.HOME ) ) InputItemsGroup( groupName = tr("D-pad"), inputIds = listOf( - NativeInput.CLASSIC_BUTTON_UP, - NativeInput.CLASSIC_BUTTON_DOWN, - NativeInput.CLASSIC_BUTTON_LEFT, - NativeInput.CLASSIC_BUTTON_RIGHT + ClassicButton.UP, + ClassicButton.DOWN, + ClassicButton.LEFT, + ClassicButton.RIGHT ) ) InputItemsGroup( groupName = tr("Left Axis"), inputIds = listOf( - NativeInput.CLASSIC_BUTTON_STICKL_UP, - NativeInput.CLASSIC_BUTTON_STICKL_DOWN, - NativeInput.CLASSIC_BUTTON_STICKL_LEFT, - NativeInput.CLASSIC_BUTTON_STICKL_RIGHT + ClassicButton.STICKL_UP, + ClassicButton.STICKL_DOWN, + ClassicButton.STICKL_LEFT, + ClassicButton.STICKL_RIGHT ) ) InputItemsGroup( groupName = tr("Right Axis"), inputIds = listOf( - NativeInput.CLASSIC_BUTTON_STICKR_UP, - NativeInput.CLASSIC_BUTTON_STICKR_DOWN, - NativeInput.CLASSIC_BUTTON_STICKR_LEFT, - NativeInput.CLASSIC_BUTTON_STICKR_RIGHT + ClassicButton.STICKR_UP, + ClassicButton.STICKR_DOWN, + ClassicButton.STICKR_LEFT, + ClassicButton.STICKR_RIGHT ) ) } private fun classicControllerButtonToString(buttonId: Int) = when (buttonId) { - NativeInput.CLASSIC_BUTTON_A -> "A" - NativeInput.CLASSIC_BUTTON_B -> "B" - NativeInput.CLASSIC_BUTTON_X -> "X" - NativeInput.CLASSIC_BUTTON_Y -> "Y" - NativeInput.CLASSIC_BUTTON_L -> "L" - NativeInput.CLASSIC_BUTTON_R -> "R" - NativeInput.CLASSIC_BUTTON_ZL -> "ZL" - NativeInput.CLASSIC_BUTTON_ZR -> "ZR" - NativeInput.CLASSIC_BUTTON_PLUS -> "+" - NativeInput.CLASSIC_BUTTON_MINUS -> "-" - NativeInput.CLASSIC_BUTTON_HOME -> tr("home") - NativeInput.CLASSIC_BUTTON_UP -> tr("up") - NativeInput.CLASSIC_BUTTON_DOWN -> tr("down") - NativeInput.CLASSIC_BUTTON_LEFT -> tr("left") - NativeInput.CLASSIC_BUTTON_RIGHT -> tr("right") - NativeInput.CLASSIC_BUTTON_STICKL_UP -> tr("up") - NativeInput.CLASSIC_BUTTON_STICKL_DOWN -> tr("down") - NativeInput.CLASSIC_BUTTON_STICKL_LEFT -> tr("left") - NativeInput.CLASSIC_BUTTON_STICKL_RIGHT -> tr("right") - NativeInput.CLASSIC_BUTTON_STICKR_UP -> tr("up") - NativeInput.CLASSIC_BUTTON_STICKR_DOWN -> tr("down") - NativeInput.CLASSIC_BUTTON_STICKR_LEFT -> tr("left") - NativeInput.CLASSIC_BUTTON_STICKR_RIGHT -> tr("right") + ClassicButton.A -> "A" + ClassicButton.B -> "B" + ClassicButton.X -> "X" + ClassicButton.Y -> "Y" + ClassicButton.L -> "L" + ClassicButton.R -> "R" + ClassicButton.ZL -> "ZL" + ClassicButton.ZR -> "ZR" + ClassicButton.PLUS -> "+" + ClassicButton.MINUS -> "-" + ClassicButton.HOME -> tr("home") + ClassicButton.UP -> tr("up") + ClassicButton.DOWN -> tr("down") + ClassicButton.LEFT -> tr("left") + ClassicButton.RIGHT -> tr("right") + ClassicButton.STICKL_UP -> tr("up") + ClassicButton.STICKL_DOWN -> tr("down") + ClassicButton.STICKL_LEFT -> tr("left") + ClassicButton.STICKL_RIGHT -> tr("right") + ClassicButton.STICKR_UP -> tr("up") + ClassicButton.STICKR_DOWN -> tr("down") + ClassicButton.STICKR_LEFT -> tr("left") + ClassicButton.STICKR_RIGHT -> tr("right") else -> throw IllegalArgumentException("Invalid buttonId $buttonId for Classic controller type") } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt index ca466a6e..81ae06e3 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt @@ -81,17 +81,17 @@ fun ControllerInputSettingsScreen( label = tr("Emulated controller"), initialChoice = { controllerType }, choices = listOf( - NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED, - NativeInput.EMULATED_CONTROLLER_TYPE_VPAD, - NativeInput.EMULATED_CONTROLLER_TYPE_PRO, - NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC, - NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE + NativeInput.EmulatedControllerType.DISABLED, + NativeInput.EmulatedControllerType.VPAD, + NativeInput.EmulatedControllerType.PRO, + NativeInput.EmulatedControllerType.CLASSIC, + NativeInput.EmulatedControllerType.WIIMOTE ), choiceToString = { controllerTypeToString(it) }, onChoiceChanged = controllersViewModel::setControllerType ) - if (controllerType != NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED) { + if (controllerType != NativeInput.EmulatedControllerType.DISABLED) { MaterialButton( modifier = Modifier.padding(8.dp), onClick = { @@ -119,23 +119,23 @@ fun ControllerInputSettingsScreen( } when (controllerType) { - NativeInput.EMULATED_CONTROLLER_TYPE_VPAD -> VPADInputs( + NativeInput.EmulatedControllerType.VPAD -> VPADInputs( controllerIndex = controllerIndex, onInputClick = ::onInputClick, controlsMapping = controls, ) - NativeInput.EMULATED_CONTROLLER_TYPE_PRO -> ProControllerInputs( + NativeInput.EmulatedControllerType.PRO -> ProControllerInputs( onInputClick = ::onInputClick, controlsMapping = controls, ) - NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC -> ClassicControllerInputs( + NativeInput.EmulatedControllerType.CLASSIC -> ClassicControllerInputs( onInputClick = ::onInputClick, controlsMapping = controls, ) - NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE -> WiimoteControllerInputs( + NativeInput.EmulatedControllerType.WIIMOTE -> WiimoteControllerInputs( onInputClick = ::onInputClick, controlsMapping = controls, ) diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt index f3c531ef..5ffe34e0 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import info.cemu.cemu.input.InputManager +import info.cemu.cemu.input.getNativeButtonsForControllerType import info.cemu.cemu.nativeinterface.NativeInput import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -15,7 +16,7 @@ import kotlinx.coroutines.flow.asStateFlow class ControllersViewModel(val controllerIndex: Int) : ViewModel() { private val inputManager = InputManager() private var _controllerType = MutableStateFlow( - if (NativeInput.isControllerDisabled(controllerIndex)) NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED + if (NativeInput.isControllerDisabled(controllerIndex)) NativeInput.EmulatedControllerType.DISABLED else NativeInput.getControllerType(controllerIndex) ) val controllerType = _controllerType.asStateFlow() @@ -66,7 +67,7 @@ class ControllersViewModel(val controllerIndex: Int) : ViewModel() { inputManager.mapAllInputs(deviceId, controllerIndex) - val buttons = NativeInput.getNativeButtonsForControllerType (controllerType.value) + val buttons = getNativeButtonsForControllerType (controllerType.value) buttons.forEach { _controls.value += getControllerMapping(it.nativeKeyCode) } } @@ -91,14 +92,14 @@ class ControllersViewModel(val controllerIndex: Int) : ViewModel() { fun isControllerTypeAllowed(controllerType: Int): Boolean { val currentControllerType = this.controllerType.value - if (controllerType == NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED) { + if (controllerType == NativeInput.EmulatedControllerType.DISABLED) { return true } - if (controllerType == NativeInput.EMULATED_CONTROLLER_TYPE_VPAD) { - return currentControllerType == NativeInput.EMULATED_CONTROLLER_TYPE_VPAD || vpadCount < NativeInput.MAX_VPAD_CONTROLLERS + if (controllerType == NativeInput.EmulatedControllerType.VPAD) { + return currentControllerType == NativeInput.EmulatedControllerType.VPAD || vpadCount < NativeInput.MAX_VPAD_CONTROLLERS } - val isWPAD = currentControllerType != NativeInput.EMULATED_CONTROLLER_TYPE_VPAD - && currentControllerType != NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED + val isWPAD = currentControllerType != NativeInput.EmulatedControllerType.VPAD + && currentControllerType != NativeInput.EmulatedControllerType.DISABLED return isWPAD || wpadCount < NativeInput.MAX_WPAD_CONTROLLERS } diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt index c21cd851..a5850a3b 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt @@ -45,5 +45,5 @@ fun InputSettingsScreen(navigateBack: () -> Unit, actions: InputSettingsScreenAc fun getControllerType(index: Int): Int = if (NativeInput.isControllerDisabled(index)) - NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED + NativeInput.EmulatedControllerType.DISABLED else NativeInput.getControllerType(index) diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt index 96f61db3..7045c4a7 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt @@ -2,7 +2,7 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.nativeinterface.NativeInput +import info.cemu.cemu.nativeinterface.NativeInput.ProButton @Composable fun ProControllerInputs( @@ -25,75 +25,75 @@ fun ProControllerInputs( InputItemsGroup( groupName = tr("Buttons"), inputIds = listOf( - NativeInput.PRO_BUTTON_A, - NativeInput.PRO_BUTTON_B, - NativeInput.PRO_BUTTON_X, - NativeInput.PRO_BUTTON_Y, - NativeInput.PRO_BUTTON_L, - NativeInput.PRO_BUTTON_R, - NativeInput.PRO_BUTTON_ZL, - NativeInput.PRO_BUTTON_ZR, - NativeInput.PRO_BUTTON_PLUS, - NativeInput.PRO_BUTTON_MINUS, - NativeInput.PRO_BUTTON_HOME + ProButton.A, + ProButton.B, + ProButton.X, + ProButton.Y, + ProButton.L, + ProButton.R, + ProButton.ZL, + ProButton.ZR, + ProButton.PLUS, + ProButton.MINUS, + ProButton.HOME ) ) InputItemsGroup( groupName = tr("D-pad"), inputIds = listOf( - NativeInput.PRO_BUTTON_UP, - NativeInput.PRO_BUTTON_DOWN, - NativeInput.PRO_BUTTON_LEFT, - NativeInput.PRO_BUTTON_RIGHT + ProButton.UP, + ProButton.DOWN, + ProButton.LEFT, + ProButton.RIGHT ) ) InputItemsGroup( groupName = tr("Left Axis"), inputIds = listOf( - NativeInput.PRO_BUTTON_STICKL, - NativeInput.PRO_BUTTON_STICKL_UP, - NativeInput.PRO_BUTTON_STICKL_DOWN, - NativeInput.PRO_BUTTON_STICKL_LEFT, - NativeInput.PRO_BUTTON_STICKL_RIGHT + ProButton.STICKL, + ProButton.STICKL_UP, + ProButton.STICKL_DOWN, + ProButton.STICKL_LEFT, + ProButton.STICKL_RIGHT ) ) InputItemsGroup( groupName = tr("Right Axis"), inputIds = listOf( - NativeInput.PRO_BUTTON_STICKR, - NativeInput.PRO_BUTTON_STICKR_UP, - NativeInput.PRO_BUTTON_STICKR_DOWN, - NativeInput.PRO_BUTTON_STICKR_LEFT, - NativeInput.PRO_BUTTON_STICKR_RIGHT + ProButton.STICKR, + ProButton.STICKR_UP, + ProButton.STICKR_DOWN, + ProButton.STICKR_LEFT, + ProButton.STICKR_RIGHT ) ) } -fun proControllerButtonToString(buttonId: Int) = when (buttonId) { - NativeInput.PRO_BUTTON_A -> "A" - NativeInput.PRO_BUTTON_B -> "B" - NativeInput.PRO_BUTTON_X -> "X" - NativeInput.PRO_BUTTON_Y -> "Y" - NativeInput.PRO_BUTTON_L -> "L" - NativeInput.PRO_BUTTON_R -> "R" - NativeInput.PRO_BUTTON_ZL -> "ZL" - NativeInput.PRO_BUTTON_ZR -> "ZR" - NativeInput.PRO_BUTTON_PLUS -> "+" - NativeInput.PRO_BUTTON_MINUS -> "-" - NativeInput.PRO_BUTTON_HOME -> "home" - NativeInput.PRO_BUTTON_UP -> tr("up") - NativeInput.PRO_BUTTON_DOWN -> tr("down") - NativeInput.PRO_BUTTON_LEFT -> tr("left") - NativeInput.PRO_BUTTON_RIGHT -> tr("right") - NativeInput.PRO_BUTTON_STICKL -> tr("click") - NativeInput.PRO_BUTTON_STICKR -> tr("click") - NativeInput.PRO_BUTTON_STICKL_UP -> tr("up") - NativeInput.PRO_BUTTON_STICKL_DOWN -> tr("down") - NativeInput.PRO_BUTTON_STICKL_LEFT -> tr("left") - NativeInput.PRO_BUTTON_STICKL_RIGHT -> tr("right") - NativeInput.PRO_BUTTON_STICKR_UP -> tr("up") - NativeInput.PRO_BUTTON_STICKR_DOWN -> tr("down") - NativeInput.PRO_BUTTON_STICKR_LEFT -> tr("left") - NativeInput.PRO_BUTTON_STICKR_RIGHT -> tr("right") +private fun proControllerButtonToString(buttonId: Int) = when (buttonId) { + ProButton.A -> "A" + ProButton.B -> "B" + ProButton.X -> "X" + ProButton.Y -> "Y" + ProButton.L -> "L" + ProButton.R -> "R" + ProButton.ZL -> "ZL" + ProButton.ZR -> "ZR" + ProButton.PLUS -> "+" + ProButton.MINUS -> "-" + ProButton.HOME -> tr("home") + ProButton.UP -> tr("up") + ProButton.DOWN -> tr("down") + ProButton.LEFT -> tr("left") + ProButton.RIGHT -> tr("right") + ProButton.STICKL -> tr("click") + ProButton.STICKR -> tr("click") + ProButton.STICKL_UP -> tr("up") + ProButton.STICKL_DOWN -> tr("down") + ProButton.STICKL_LEFT -> tr("left") + ProButton.STICKL_RIGHT -> tr("right") + ProButton.STICKR_UP -> tr("up") + ProButton.STICKR_DOWN -> tr("down") + ProButton.STICKR_LEFT -> tr("left") + ProButton.STICKR_RIGHT -> tr("right") else -> throw IllegalArgumentException("Invalid buttonId $buttonId for Pro controller type") } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt index 5a485d05..c924e60a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import info.cemu.cemu.core.components.Toggle import info.cemu.cemu.core.translation.tr import info.cemu.cemu.nativeinterface.NativeInput +import info.cemu.cemu.nativeinterface.NativeInput.VPADButton @Composable fun VPADInputs( @@ -27,53 +28,53 @@ fun VPADInputs( InputItemsGroup( groupName = tr("Buttons"), inputIds = listOf( - NativeInput.VPAD_BUTTON_A, - NativeInput.VPAD_BUTTON_B, - NativeInput.VPAD_BUTTON_X, - NativeInput.VPAD_BUTTON_Y, - NativeInput.VPAD_BUTTON_L, - NativeInput.VPAD_BUTTON_R, - NativeInput.VPAD_BUTTON_ZL, - NativeInput.VPAD_BUTTON_ZR, - NativeInput.VPAD_BUTTON_PLUS, - NativeInput.VPAD_BUTTON_MINUS + VPADButton.A, + VPADButton.B, + VPADButton.X, + VPADButton.Y, + VPADButton.L, + VPADButton.R, + VPADButton.ZL, + VPADButton.ZR, + VPADButton.PLUS, + VPADButton.MINUS ) ) InputItemsGroup( groupName = tr("D-pad"), inputIds = listOf( - NativeInput.VPAD_BUTTON_UP, - NativeInput.VPAD_BUTTON_DOWN, - NativeInput.VPAD_BUTTON_LEFT, - NativeInput.VPAD_BUTTON_RIGHT + VPADButton.UP, + VPADButton.DOWN, + VPADButton.LEFT, + VPADButton.RIGHT ) ) InputItemsGroup( groupName = tr("Left Axis"), inputIds = listOf( - NativeInput.VPAD_BUTTON_STICKL, - NativeInput.VPAD_BUTTON_STICKL_UP, - NativeInput.VPAD_BUTTON_STICKL_DOWN, - NativeInput.VPAD_BUTTON_STICKL_LEFT, - NativeInput.VPAD_BUTTON_STICKL_RIGHT + VPADButton.STICKL, + VPADButton.STICKL_UP, + VPADButton.STICKL_DOWN, + VPADButton.STICKL_LEFT, + VPADButton.STICKL_RIGHT ) ) InputItemsGroup( groupName = tr("Right Axis"), inputIds = listOf( - NativeInput.VPAD_BUTTON_STICKR, - NativeInput.VPAD_BUTTON_STICKR_UP, - NativeInput.VPAD_BUTTON_STICKR_DOWN, - NativeInput.VPAD_BUTTON_STICKR_LEFT, - NativeInput.VPAD_BUTTON_STICKR_RIGHT + VPADButton.STICKR, + VPADButton.STICKR_UP, + VPADButton.STICKR_DOWN, + VPADButton.STICKR_LEFT, + VPADButton.STICKR_RIGHT ) ) InputItemsGroup( groupName = tr("Extra"), inputIds = listOf( - NativeInput.VPAD_BUTTON_MIC, - NativeInput.VPAD_BUTTON_HOME, - NativeInput.VPAD_BUTTON_SCREEN + VPADButton.MIC, + VPADButton.HOME, + VPADButton.SCREEN ) ) Toggle( @@ -85,33 +86,33 @@ fun VPADInputs( } -fun vpadButtonToString(buttonId: Int) = when (buttonId) { - NativeInput.VPAD_BUTTON_A -> "A" - NativeInput.VPAD_BUTTON_B -> "B" - NativeInput.VPAD_BUTTON_X -> "X" - NativeInput.VPAD_BUTTON_Y -> "Y" - NativeInput.VPAD_BUTTON_L -> "L" - NativeInput.VPAD_BUTTON_R -> "R" - NativeInput.VPAD_BUTTON_ZL -> "ZL" - NativeInput.VPAD_BUTTON_ZR -> "ZR" - NativeInput.VPAD_BUTTON_PLUS -> "+" - NativeInput.VPAD_BUTTON_MINUS -> "-" - NativeInput.VPAD_BUTTON_UP -> tr("up") - NativeInput.VPAD_BUTTON_DOWN -> tr("down") - NativeInput.VPAD_BUTTON_LEFT -> tr("left") - NativeInput.VPAD_BUTTON_RIGHT -> tr("right") - NativeInput.VPAD_BUTTON_STICKL -> tr("click") - NativeInput.VPAD_BUTTON_STICKR -> tr("click") - NativeInput.VPAD_BUTTON_STICKL_UP -> tr("up") - NativeInput.VPAD_BUTTON_STICKL_DOWN -> tr("down") - NativeInput.VPAD_BUTTON_STICKL_LEFT -> tr("left") - NativeInput.VPAD_BUTTON_STICKL_RIGHT -> tr("right") - NativeInput.VPAD_BUTTON_STICKR_UP -> tr("up") - NativeInput.VPAD_BUTTON_STICKR_DOWN -> tr("down") - NativeInput.VPAD_BUTTON_STICKR_LEFT -> tr("left") - NativeInput.VPAD_BUTTON_STICKR_RIGHT -> tr("right") - NativeInput.VPAD_BUTTON_MIC -> tr("blow mic") - NativeInput.VPAD_BUTTON_SCREEN -> tr("show screen") - NativeInput.VPAD_BUTTON_HOME -> tr("home") +private fun vpadButtonToString(buttonId: Int) = when (buttonId) { + VPADButton.A -> "A" + VPADButton.B -> "B" + VPADButton.X -> "X" + VPADButton.Y -> "Y" + VPADButton.L -> "L" + VPADButton.R -> "R" + VPADButton.ZL -> "ZL" + VPADButton.ZR -> "ZR" + VPADButton.PLUS -> "+" + VPADButton.MINUS -> "-" + VPADButton.UP -> tr("up") + VPADButton.DOWN -> tr("down") + VPADButton.LEFT -> tr("left") + VPADButton.RIGHT -> tr("right") + VPADButton.STICKL -> tr("click") + VPADButton.STICKR -> tr("click") + VPADButton.STICKL_UP -> tr("up") + VPADButton.STICKL_DOWN -> tr("down") + VPADButton.STICKL_LEFT -> tr("left") + VPADButton.STICKL_RIGHT -> tr("right") + VPADButton.STICKR_UP -> tr("up") + VPADButton.STICKR_DOWN -> tr("down") + VPADButton.STICKR_LEFT -> tr("left") + VPADButton.STICKR_RIGHT -> tr("right") + VPADButton.MIC -> tr("blow mic") + VPADButton.SCREEN -> tr("show screen") + VPADButton.HOME -> tr("home") else -> throw IllegalArgumentException("Invalid buttonId $buttonId for VPAD controller type") } diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt index fedbba96..017bda4c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt @@ -2,7 +2,7 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.nativeinterface.NativeInput +import info.cemu.cemu.nativeinterface.NativeInput.WiimoteButton @Composable fun WiimoteControllerInputs( @@ -25,54 +25,54 @@ fun WiimoteControllerInputs( InputItemsGroup( groupName = tr("Buttons"), inputIds = listOf( - NativeInput.WIIMOTE_BUTTON_A, - NativeInput.WIIMOTE_BUTTON_B, - NativeInput.WIIMOTE_BUTTON_1, - NativeInput.WIIMOTE_BUTTON_2, - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_Z, - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_C, - NativeInput.WIIMOTE_BUTTON_PLUS, - NativeInput.WIIMOTE_BUTTON_MINUS, - NativeInput.WIIMOTE_BUTTON_HOME + WiimoteButton.A, + WiimoteButton.B, + WiimoteButton.ONE, + WiimoteButton.TWO, + WiimoteButton.NUNCHUCK_Z, + WiimoteButton.NUNCHUCK_C, + WiimoteButton.PLUS, + WiimoteButton.MINUS, + WiimoteButton.HOME ) ) InputItemsGroup( groupName = tr("Nunchuck"), inputIds = listOf( - NativeInput.WIIMOTE_BUTTON_UP, - NativeInput.WIIMOTE_BUTTON_DOWN, - NativeInput.WIIMOTE_BUTTON_LEFT, - NativeInput.WIIMOTE_BUTTON_RIGHT + WiimoteButton.UP, + WiimoteButton.DOWN, + WiimoteButton.LEFT, + WiimoteButton.RIGHT ) ) InputItemsGroup( groupName = tr("Right Axis"), inputIds = listOf( - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_UP, - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_DOWN, - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_LEFT, - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_RIGHT + WiimoteButton.NUNCHUCK_UP, + WiimoteButton.NUNCHUCK_DOWN, + WiimoteButton.NUNCHUCK_LEFT, + WiimoteButton.NUNCHUCK_RIGHT ) ) } -fun wiimoteButtonItToString(buttonId: Int) = when (buttonId) { - NativeInput.WIIMOTE_BUTTON_A -> "A" - NativeInput.WIIMOTE_BUTTON_B -> "B" - NativeInput.WIIMOTE_BUTTON_1 -> "1" - NativeInput.WIIMOTE_BUTTON_2 -> "2" - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_Z -> "Z" - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_C -> "C" - NativeInput.WIIMOTE_BUTTON_PLUS -> "+" - NativeInput.WIIMOTE_BUTTON_MINUS -> "-" - NativeInput.WIIMOTE_BUTTON_UP -> tr("up") - NativeInput.WIIMOTE_BUTTON_DOWN -> tr("down") - NativeInput.WIIMOTE_BUTTON_LEFT -> tr("left") - NativeInput.WIIMOTE_BUTTON_RIGHT -> tr("right") - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_UP -> tr("up") - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_DOWN -> tr("down") - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_LEFT -> tr("left") - NativeInput.WIIMOTE_BUTTON_NUNCHUCK_RIGHT -> tr("right") - NativeInput.WIIMOTE_BUTTON_HOME -> tr("home") +private fun wiimoteButtonItToString(buttonId: Int) = when (buttonId) { + WiimoteButton.A -> "A" + WiimoteButton.B -> "B" + WiimoteButton.ONE -> "1" + WiimoteButton.TWO -> "2" + WiimoteButton.NUNCHUCK_Z -> "Z" + WiimoteButton.NUNCHUCK_C -> "C" + WiimoteButton.PLUS -> "+" + WiimoteButton.MINUS -> "-" + WiimoteButton.UP -> tr("up") + WiimoteButton.DOWN -> tr("down") + WiimoteButton.LEFT -> tr("left") + WiimoteButton.RIGHT -> tr("right") + WiimoteButton.NUNCHUCK_UP -> tr("up") + WiimoteButton.NUNCHUCK_DOWN -> tr("down") + WiimoteButton.NUNCHUCK_LEFT -> tr("left") + WiimoteButton.NUNCHUCK_RIGHT -> tr("right") + WiimoteButton.HOME -> tr("home") else -> throw IllegalArgumentException("Invalid buttonId $buttonId for Wiimote controller type") } diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt index 8b4e1cf3..1f290f82 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt @@ -14,13 +14,13 @@ import info.cemu.cemu.core.translation.tr import info.cemu.cemu.nativeinterface.NativeSettings private val OverlayPositionChoices = listOf( - NativeSettings.OVERLAY_SCREEN_POSITION_DISABLED, - NativeSettings.OVERLAY_SCREEN_POSITION_TOP_LEFT, - NativeSettings.OVERLAY_SCREEN_POSITION_TOP_CENTER, - NativeSettings.OVERLAY_SCREEN_POSITION_TOP_RIGHT, - NativeSettings.OVERLAY_SCREEN_POSITION_BOTTOM_LEFT, - NativeSettings.OVERLAY_SCREEN_POSITION_BOTTOM_CENTER, - NativeSettings.OVERLAY_SCREEN_POSITION_BOTTOM_RIGHT + NativeSettings.OverlayScreenPosition.DISABLED, + NativeSettings.OverlayScreenPosition.TOP_LEFT, + NativeSettings.OverlayScreenPosition.TOP_CENTER, + NativeSettings.OverlayScreenPosition.TOP_RIGHT, + NativeSettings.OverlayScreenPosition.BOTTOM_LEFT, + NativeSettings.OverlayScreenPosition.BOTTOM_CENTER, + NativeSettings.OverlayScreenPosition.BOTTOM_RIGHT ) private const val OVERLAY_TEXT_SCALE_STEPS = 9 @@ -44,7 +44,7 @@ fun OverlaySettingsScreen(navigateBack: () -> Unit) { NativeSettings.setOverlayPosition(it) }, ) - if (overlayPosition != NativeSettings.OVERLAY_SCREEN_POSITION_DISABLED) + if (overlayPosition != NativeSettings.OverlayScreenPosition.DISABLED) OverlaySettings() Header(tr("Notifications")) SingleSelection( @@ -57,7 +57,7 @@ fun OverlaySettingsScreen(navigateBack: () -> Unit) { NativeSettings.setNotificationsPosition(it) }, ) - if (notificationsPosition != NativeSettings.OVERLAY_SCREEN_POSITION_DISABLED) + if (notificationsPosition != NativeSettings.OverlayScreenPosition.DISABLED) NotificationSettings() } } @@ -137,12 +137,12 @@ private fun NotificationSettings() { } private fun overlayScreenPositionToString(overlayScreenPosition: Int) = when (overlayScreenPosition) { - NativeSettings.OVERLAY_SCREEN_POSITION_DISABLED -> tr("Disabled") - NativeSettings.OVERLAY_SCREEN_POSITION_TOP_LEFT -> tr("Top left") - NativeSettings.OVERLAY_SCREEN_POSITION_TOP_CENTER -> tr("Top center") - NativeSettings.OVERLAY_SCREEN_POSITION_TOP_RIGHT -> tr("Top right") - NativeSettings.OVERLAY_SCREEN_POSITION_BOTTOM_LEFT -> tr("Bottom left") - NativeSettings.OVERLAY_SCREEN_POSITION_BOTTOM_CENTER -> tr("Bottom center") - NativeSettings.OVERLAY_SCREEN_POSITION_BOTTOM_RIGHT -> tr("Bottom right") + NativeSettings.OverlayScreenPosition.DISABLED -> tr("Disabled") + NativeSettings.OverlayScreenPosition.TOP_LEFT -> tr("Top left") + NativeSettings.OverlayScreenPosition.TOP_CENTER -> tr("Top center") + NativeSettings.OverlayScreenPosition.TOP_RIGHT -> tr("Top right") + NativeSettings.OverlayScreenPosition.BOTTOM_LEFT -> tr("Bottom left") + NativeSettings.OverlayScreenPosition.BOTTOM_CENTER -> tr("Bottom center") + NativeSettings.OverlayScreenPosition.BOTTOM_RIGHT -> tr("Bottom right") else -> throw IllegalArgumentException("Invalid overlay position: $overlayScreenPosition") } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleEntry.kt b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleEntry.kt index 93b10706..7c2802e3 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleEntry.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleEntry.kt @@ -29,9 +29,9 @@ enum class EntryType { } fun nativeTitleTypeToEnum(titleType: Int) = when (titleType) { - NativeGameTitles.TITLE_TYPE_BASE_TITLE_UPDATE -> EntryType.Update - NativeGameTitles.TITLE_TYPE_AOC -> EntryType.Dlc - NativeGameTitles.TITLE_TYPE_SYSTEM_OVERLAY_TITLE, NativeGameTitles.TITLE_TYPE_SYSTEM_DATA, NativeGameTitles.TITLE_TYPE_SYSTEM_TITLE -> EntryType.System + NativeGameTitles.TitleType.BASE_TITLE_UPDATE -> EntryType.Update + NativeGameTitles.TitleType.AOC -> EntryType.Dlc + NativeGameTitles.TitleType.SYSTEM_OVERLAY_TITLE, NativeGameTitles.TitleType.SYSTEM_DATA, NativeGameTitles.TitleType.SYSTEM_TITLE -> EntryType.System else -> EntryType.Base } @@ -45,9 +45,9 @@ enum class EntryFormat { } fun nativeTitleFormatToEnum(titleFormat: Int) = when (titleFormat) { - NativeGameTitles.TITLE_DATA_FORMAT_WUD -> EntryFormat.WUD - NativeGameTitles.TITLE_DATA_FORMAT_WIIU_ARCHIVE -> EntryFormat.WUA - NativeGameTitles.TITLE_DATA_FORMAT_NUS -> EntryFormat.NUS - NativeGameTitles.TITLE_DATA_FORMAT_WUHB -> EntryFormat.WUHB + NativeGameTitles.TitleDataFormat.WUD -> EntryFormat.WUD + NativeGameTitles.TitleDataFormat.WIIU_ARCHIVE -> EntryFormat.WUA + NativeGameTitles.TitleDataFormat.NUS -> EntryFormat.NUS + NativeGameTitles.TitleDataFormat.WUHB -> EntryFormat.WUHB else -> EntryFormat.Folder } diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/String.kt b/src/android/app/src/main/java/info/cemu/cemu/utils/String.kt index b7668c23..1116a547 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/String.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/utils/String.kt @@ -7,3 +7,12 @@ fun String.urlDecode(enc: String = "UTF-8"): String = URLDecoder.decode(this, en fun String.toIntOrZero() = toIntOrNull() ?: 0 fun String.isContentUri() = startsWith("content://") + + +fun String.parseHexOrNull(): UInt? { + return try { + toUInt(16) + } catch (_: NumberFormatException) { + null + } +} \ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0008947b..053d3be8 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -2,4 +2,5 @@ Cemu Exzap, Petergov https://cemu.info + https://cemu.info/online-guide \ No newline at end of file diff --git a/src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt b/src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt new file mode 100644 index 00000000..70b69334 --- /dev/null +++ b/src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt @@ -0,0 +1,12 @@ +package info.cemu.cemu + +import org.junit.Test + +import org.junit.Assert.* + +class PlaceholderUnitTest { + @Test + fun test() { + assertTrue(true) + } +} \ No newline at end of file diff --git a/src/android/gradle/libs.versions.toml b/src/android/gradle/libs.versions.toml index 21b3fab7..00cbeeb3 100644 --- a/src/android/gradle/libs.versions.toml +++ b/src/android/gradle/libs.versions.toml @@ -6,14 +6,21 @@ kotlin = "2.2.0" androidx-core-ktx = "1.16.0" androidx-activity-compose = "1.10.1" androidx-compose-bom = "2025.07.00" -material = "1.12.0" -androidx-navigation-compose = "2.9.2" +androidx-compose-material3 = "1.3.2" +google-android-material = "1.12.0" +androidx-navigation-compose = "2.9.3" +junit = "4.13.2" +androidx-junit = "1.3.0" +espresso-core = "3.7.0" okhttp = "5.1.0" kotlinx-serialization-json = "1.9.0" kotlinx-gettext = "0.7.0" [libraries] aboutlibraries-compose-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-junit" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation-compose" } @@ -23,8 +30,9 @@ androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } -material = { module = "com.google.android.material:material", version.ref = "material" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidx-compose-material3" } +google-android-material = { module = "com.google.android.material:material", version.ref = "google-android-material" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-coroutines = { module = "com.squareup.okhttp3:okhttp-coroutines", version.ref = "okhttp" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }