From c5dde5953aa570af3389602b3492a142f332c955 Mon Sep 17 00:00:00 2001 From: lynxnb Date: Tue, 19 Jul 2022 13:08:15 +0200 Subject: [PATCH] Rework how settings are shared between Kotlin and native side Settings are now shared to the native side by passing an instance of the Kotlin's `Settings` class. This way the C++ `Settings` class doesn't need to parse the SharedPreferences xml anymore. --- app/src/main/cpp/emu_jni.cpp | 26 +++++---- app/src/main/cpp/skyline/common/settings.cpp | 54 ++----------------- app/src/main/cpp/skyline/common/settings.h | 20 +++++-- app/src/main/cpp/skyline/jvm.h | 37 +++++++++++++ app/src/main/cpp/skyline/os.cpp | 4 +- app/src/main/cpp/skyline/os.h | 2 - .../cpp/skyline/services/account/IProfile.cpp | 4 +- .../am/controller/IApplicationFunctions.cpp | 6 +-- .../java/emu/skyline/EmulationActivity.kt | 9 ++-- .../java/emu/skyline/SkylineApplication.kt | 6 +-- .../java/emu/skyline/utils/SettingsValues.kt | 16 ++++++ 11 files changed, 103 insertions(+), 81 deletions(-) create mode 100644 app/src/main/java/emu/skyline/utils/SettingsValues.kt diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 75c56ee3..6b2d6da7 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include "skyline/common.h" @@ -28,6 +27,7 @@ std::weak_ptr OsWeak; std::weak_ptr GpuWeak; std::weak_ptr AudioWeak; std::weak_ptr InputWeak; +std::weak_ptr SettingsWeak; // https://cs.android.com/android/platform/superproject/+/master:bionic/libc/tzcode/bionic.cpp;l=43;drc=master;bpv=1;bpt=1 static std::string GetTimeZoneName() { @@ -54,15 +54,23 @@ static std::string GetTimeZoneName() { return "GMT"; } +template<> void skyline::Settings::Update(KtSettings newSettings) { + operationMode = newSettings.GetBool("operationMode"); + usernameValue = newSettings.GetString("usernameValue"); + systemLanguage = newSettings.GetInt("systemLanguage"); + forceTripleBuffering = newSettings.GetBool("forceTripleBuffering"); + disableFrameThrottling = newSettings.GetBool("disableFrameThrottling"); +} + extern "C" JNIEXPORT void Java_emu_skyline_SkylineApplication_initializeLog( JNIEnv *env, jobject, - jstring appFilesPathJstring, + jstring publicAppFilesPathJstring, jint logLevel ) { - std::string appFilesPath{env->GetStringUTFChars(appFilesPathJstring, nullptr)}; + skyline::JniString publicAppFilesPath(env, publicAppFilesPathJstring); skyline::Logger::configLevel = static_cast(logLevel); - skyline::Logger::LoaderContext.Initialize(appFilesPath + "logs/loader.sklog"); + skyline::Logger::LoaderContext.Initialize(publicAppFilesPath + "logs/loader.sklog"); } extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( @@ -71,8 +79,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( jstring romUriJstring, jint romType, jint romFd, - jint preferenceFd, - jint systemLanguage, + jobject settingsInstance, jstring publicAppFilesPathJstring, jstring privateAppFilesPathJstring, jstring nativeLibraryPathJstring, @@ -85,8 +92,9 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( pthread_setname_np(pthread_self(), "EmuMain"); auto jvmManager{std::make_shared(env, instance)}; - auto settings{std::make_shared(preferenceFd)}; - close(preferenceFd); + + skyline::KtSettings ktSettings{env, settingsInstance}; + auto settings{std::make_shared(ktSettings)}; skyline::JniString publicAppFilesPath(env, publicAppFilesPathJstring); skyline::Logger::EmulationContext.Initialize(publicAppFilesPath + "logs/emulation.sklog"); @@ -110,13 +118,13 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( privateAppFilesPath, nativeLibraryPath, GetTimeZoneName(), - static_cast(systemLanguage), std::make_shared(AAssetManager_fromJava(env, assetManager)) )}; OsWeak = os; GpuWeak = os->state.gpu; AudioWeak = os->state.audio; InputWeak = os->state.input; + SettingsWeak = settings; jvmManager->InitializeControllers(); skyline::Logger::InfoNoPrefix("Launching ROM {}", skyline::JniString(env, romUriJstring)); diff --git a/app/src/main/cpp/skyline/common/settings.cpp b/app/src/main/cpp/skyline/common/settings.cpp index 5d593009..bd33ff07 100644 --- a/app/src/main/cpp/skyline/common/settings.cpp +++ b/app/src/main/cpp/skyline/common/settings.cpp @@ -1,56 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#define PUGIXML_HEADER_ONLY - -#include #include "settings.h" namespace skyline { - Settings::Settings(int fd) { - pugi::xml_document document; - auto result{document.load_file(fmt::format("/proc/self/fd/{}", fd).c_str())}; - if (!result) - throw exception("PugiXML Error: {} at {}", result.description(), result.offset); - - #define PREF_ELEM(name, memberName, rhs) std::make_pair(std::string(name), [](Settings &settings, const pugi::xml_node &element) { settings.memberName = rhs; }) - - std::tuple preferences{ - PREF_ELEM("log_level", logLevel, static_cast(element.text().as_uint(static_cast(Logger::LogLevel::Info)))), - PREF_ELEM("username_value", username, element.text().as_string()), - PREF_ELEM("operation_mode", operationMode, element.attribute("value").as_bool()), - PREF_ELEM("force_triple_buffering", forceTripleBuffering, element.attribute("value").as_bool()), - PREF_ELEM("disable_frame_throttling", disableFrameThrottling, element.attribute("value").as_bool()), - }; - - #undef PREF_ELEM - - std::bitset> preferencesSet{}; // A bitfield to keep track of all the preferences we've set - for (auto element{document.last_child().first_child()}; element; element = element.next_sibling()) { - std::string_view name{element.attribute("name").value()}; - std::apply([&](auto... preferences) { - size_t index{}; - ([&](auto preference) { - if (name.size() == preference.first.size() && name.starts_with(preference.first)) { - preference.second(*this, element); - preferencesSet.set(index); - } - index++; - }(preferences), ...); - }, preferences); - } - - if (!preferencesSet.all()) { - std::string unsetPreferences; - std::apply([&](auto... preferences) { - size_t index{}; - ([&](auto preference) { - if (!preferencesSet.test(index)) - unsetPreferences += std::string("\n* ") + preference.first; - index++; - }(preferences), ...); - }, preferences); - throw exception("Cannot find the following preferences:{}", unsetPreferences); - } - } + /** + * @note This is a placeholder implementation, it must be overridden via template specialisation for platform-specific behavior + */ + template + void Settings::Update(T newSettings) {} } diff --git a/app/src/main/cpp/skyline/common/settings.h b/app/src/main/cpp/skyline/common/settings.h index 0c267d72..b47e043d 100644 --- a/app/src/main/cpp/skyline/common/settings.h +++ b/app/src/main/cpp/skyline/common/settings.h @@ -3,7 +3,7 @@ #pragma once -#include +#include "language.h" namespace skyline { /** @@ -11,15 +11,25 @@ namespace skyline { */ class Settings { public: - Logger::LogLevel logLevel; //!< The minimum level that logs need to be for them to be printed - std::string username; //!< The name set by the user to be supplied to the guest + // System bool operationMode; //!< If the emulated Switch should be handheld or docked + std::string usernameValue; //!< The name set by the user to be supplied to the guest + language::SystemLanguage systemLanguage; //!< The system language set by the user + + // Display bool forceTripleBuffering; //!< If the presentation engine should always triple buffer even if the swapchain supports double buffering bool disableFrameThrottling; //!< Allow the guest to submit frames without any blocking calls + template + Settings(T settings) { + Update(settings); + } + /** - * @param fd An FD to the preference XML file + * @brief Updates settings with the given values + * @param newSettings A platform-specific object containing the new settings' values */ - Settings(int fd); + template + void Update(T newSettings); }; } diff --git a/app/src/main/cpp/skyline/jvm.h b/app/src/main/cpp/skyline/jvm.h index 0f370882..0783054e 100644 --- a/app/src/main/cpp/skyline/jvm.h +++ b/app/src/main/cpp/skyline/jvm.h @@ -18,6 +18,43 @@ namespace skyline { JniString(JNIEnv *env, jstring jString) : std::string(GetJString(env, jString)) {} }; + /** + * @brief A wrapper over the `Settings` Kotlin class + * @note The lifetime of this class must not exceed that of the JNI environment + */ + class KtSettings { + private: + JNIEnv *env; //!< A pointer to the current jni environment + jclass settingsClass; //!< The settings class + jobject settingsInstance; //!< The settings instance + + public: + KtSettings(JNIEnv *env, jobject settingsInstance) : env(env), settingsInstance(settingsInstance), settingsClass(env->GetObjectClass(settingsInstance)) {} + + /** + * @param key A null terminated string containing the key of the setting to get + */ + template + requires std::is_integral_v || std::is_enum_v + T GetInt(const std::string_view &key) { + return static_cast(env->GetIntField(settingsInstance, env->GetFieldID(settingsClass, key.data(), "I"))); + } + + /** + * @param key A null terminated string containing the key of the setting to get + */ + bool GetBool(const std::string_view &key) { + return env->GetBooleanField(settingsInstance, env->GetFieldID(settingsClass, key.data(), "Z")) == JNI_TRUE; + } + + /** + * @param key A null terminated string containing the key of the setting to get + */ + JniString GetString(const std::string_view &key) { + return {env, static_cast(env->GetObjectField(settingsInstance, env->GetFieldID(settingsClass, key.data(), "Ljava/lang/String;")))}; + } + }; + /** * @brief The JvmManager class is used to simplify transactions with the Java component */ diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index 1c0d1ba5..3948e1c6 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -20,7 +20,6 @@ namespace skyline::kernel { std::string privateAppFilesPath, std::string nativeLibraryPath, std::string deviceTimeZone, - language::SystemLanguage systemLanguage, std::shared_ptr assetFileSystem) : nativeLibraryPath(std::move(nativeLibraryPath)), publicAppFilesPath(std::move(publicAppFilesPath)), @@ -28,8 +27,7 @@ namespace skyline::kernel { state(this, jvmManager, settings), deviceTimeZone(std::move(deviceTimeZone)), assetFileSystem(std::move(assetFileSystem)), - serviceManager(state), - systemLanguage(systemLanguage) {} + serviceManager(state) {} void OS::Execute(int romFd, loader::RomFormat romType) { auto romFile{std::make_shared(romFd)}; diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 0ed1ec65..041a5767 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -21,7 +21,6 @@ namespace skyline::kernel { std::string deviceTimeZone; //!< The timezone name (e.g. Europe/London) std::shared_ptr assetFileSystem; //!< A filesystem to be used for accessing emulator assets (like tzdata) service::ServiceManager serviceManager; - language::SystemLanguage systemLanguage; /** * @param settings An instance of the Settings class @@ -34,7 +33,6 @@ namespace skyline::kernel { std::string privateAppFilesPath, std::string deviceTimeZone, std::string nativeLibraryPath, - language::SystemLanguage systemLanguage, std::shared_ptr assetFileSystem ); diff --git a/app/src/main/cpp/skyline/services/account/IProfile.cpp b/app/src/main/cpp/skyline/services/account/IProfile.cpp index 9261c7bb..7cc00238 100644 --- a/app/src/main/cpp/skyline/services/account/IProfile.cpp +++ b/app/src/main/cpp/skyline/services/account/IProfile.cpp @@ -31,8 +31,8 @@ namespace skyline::service::account { .uid = userId, }; - size_t usernameSize{std::min(accountProfileBase.nickname.size() - 1, state.settings->username.size())}; - std::memcpy(accountProfileBase.nickname.data(), state.settings->username.c_str(), usernameSize); + size_t usernameSize{std::min(accountProfileBase.nickname.size() - 1, state.settings->usernameValue.size())}; + std::memcpy(accountProfileBase.nickname.data(), state.settings->usernameValue.c_str(), usernameSize); response.Push(accountProfileBase); diff --git a/app/src/main/cpp/skyline/services/am/controller/IApplicationFunctions.cpp b/app/src/main/cpp/skyline/services/am/controller/IApplicationFunctions.cpp index 6d23b523..cabb407d 100644 --- a/app/src/main/cpp/skyline/services/am/controller/IApplicationFunctions.cpp +++ b/app/src/main/cpp/skyline/services/am/controller/IApplicationFunctions.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -58,9 +58,9 @@ namespace skyline::service::am { } Result IApplicationFunctions::GetDesiredLanguage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto desiredLanguage{language::GetApplicationLanguage(state.os->systemLanguage)}; + auto desiredLanguage{language::GetApplicationLanguage(state.settings->systemLanguage)}; - // In the future we might want to trigger an UI dialog if the user selected languages is not available, for now it will use the first available + // In the future we might want to trigger an UI dialog if the user-selected language is not available, for now it will use the first one available if (((1 << static_cast(desiredLanguage)) & state.loader->nacp->nacpContents.supportedLanguageFlag) == 0) desiredLanguage = state.loader->nacp->GetFirstSupportedLanguage(); diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index 6ef84c49..458bb83e 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -30,7 +30,7 @@ import emu.skyline.input.* import emu.skyline.loader.getRomFormat import emu.skyline.utils.ByteBufferSerializable import emu.skyline.utils.Settings -import java.io.File +import emu.skyline.utils.SettingsValues import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.concurrent.FutureTask @@ -85,13 +85,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo * @param romUri The URI of the ROM as a string, used to print out in the logs * @param romType The type of the ROM as an enum value * @param romFd The file descriptor of the ROM object - * @param preferenceFd The file descriptor of the Preference XML + * @param settingsValues The SettingsValues instance * @param publicAppFilesPath The full path to the public app files directory * @param privateAppFilesPath The full path to the private app files directory * @param nativeLibraryPath The full path to the app native library directory * @param assetManager The asset manager used for accessing app assets */ - private external fun executeApplication(romUri : String, romType : Int, romFd : Int, preferenceFd : Int, language : Int, publicAppFilesPath : String, privateAppFilesPath : String, nativeLibraryPath : String, assetManager : AssetManager) + private external fun executeApplication(romUri : String, romType : Int, romFd : Int, settingsValues : SettingsValues, publicAppFilesPath : String, privateAppFilesPath : String, nativeLibraryPath : String, assetManager : AssetManager) /** * @param join If the function should only return after all the threads join or immediately @@ -247,10 +247,9 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo val rom = intent.data!! val romType = getRomFormat(rom, contentResolver).ordinal val romFd = contentResolver.openFileDescriptor(rom, "r")!! - val preferenceFd = ParcelFileDescriptor.open(File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml"), ParcelFileDescriptor.MODE_READ_WRITE) emulationThread = Thread { - executeApplication(rom.toString(), romType, romFd.detachFd(), preferenceFd.detachFd(), settings.systemLanguage, applicationContext.getPublicFilesDir().canonicalPath + "/", applicationContext.filesDir.canonicalPath + "/", applicationInfo.nativeLibraryDir + "/", assets) + executeApplication(rom.toString(), romType, romFd.detachFd(), SettingsValues(settings), applicationContext.getPublicFilesDir().canonicalPath + "/", applicationContext.filesDir.canonicalPath + "/", applicationInfo.nativeLibraryDir + "/", assets) returnFromEmulation() } diff --git a/app/src/main/java/emu/skyline/SkylineApplication.kt b/app/src/main/java/emu/skyline/SkylineApplication.kt index 33876eb8..cb2c7c65 100644 --- a/app/src/main/java/emu/skyline/SkylineApplication.kt +++ b/app/src/main/java/emu/skyline/SkylineApplication.kt @@ -34,8 +34,8 @@ class SkylineApplication : Application() { instance = this System.loadLibrary("skyline") - val appFilesPath = applicationContext.getPublicFilesDir().canonicalPath - File("$appFilesPath/logs/").mkdirs() - initializeLog("$appFilesPath/", getSettings().logLevel) + val publicAppFilesPath = applicationContext.getPublicFilesDir().canonicalPath + File("$publicAppFilesPath/logs/").mkdirs() + initializeLog("$publicAppFilesPath/", getSettings().logLevel) } } diff --git a/app/src/main/java/emu/skyline/utils/SettingsValues.kt b/app/src/main/java/emu/skyline/utils/SettingsValues.kt new file mode 100644 index 00000000..65eb1d74 --- /dev/null +++ b/app/src/main/java/emu/skyline/utils/SettingsValues.kt @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.utils + +import java.io.Serializable + +class SettingsValues(pref: Settings) : Serializable { + var isDocked : Boolean = pref.isDocked + var usernameValue : String = pref.usernameValue + var systemLanguage : Int = pref.systemLanguage + var forceTripleBuffering : Boolean = pref.forceTripleBuffering + var disableFrameThrottling : Boolean = pref.disableFrameThrottling +}