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.
This commit is contained in:
lynxnb 2022-07-19 13:08:15 +02:00 committed by ◱ Mark
parent 4be8b4cf66
commit c5dde5953a
11 changed files with 103 additions and 81 deletions

View File

@ -3,7 +3,6 @@
#include <csignal> #include <csignal>
#include <pthread.h> #include <pthread.h>
#include <unistd.h>
#include <android/asset_manager_jni.h> #include <android/asset_manager_jni.h>
#include <sys/system_properties.h> #include <sys/system_properties.h>
#include "skyline/common.h" #include "skyline/common.h"
@ -28,6 +27,7 @@ std::weak_ptr<skyline::kernel::OS> OsWeak;
std::weak_ptr<skyline::gpu::GPU> GpuWeak; std::weak_ptr<skyline::gpu::GPU> GpuWeak;
std::weak_ptr<skyline::audio::Audio> AudioWeak; std::weak_ptr<skyline::audio::Audio> AudioWeak;
std::weak_ptr<skyline::input::Input> InputWeak; std::weak_ptr<skyline::input::Input> InputWeak;
std::weak_ptr<skyline::Settings> SettingsWeak;
// https://cs.android.com/android/platform/superproject/+/master:bionic/libc/tzcode/bionic.cpp;l=43;drc=master;bpv=1;bpt=1 // 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() { static std::string GetTimeZoneName() {
@ -54,15 +54,23 @@ static std::string GetTimeZoneName() {
return "GMT"; return "GMT";
} }
template<> void skyline::Settings::Update<skyline::KtSettings>(KtSettings newSettings) {
operationMode = newSettings.GetBool("operationMode");
usernameValue = newSettings.GetString("usernameValue");
systemLanguage = newSettings.GetInt<skyline::language::SystemLanguage>("systemLanguage");
forceTripleBuffering = newSettings.GetBool("forceTripleBuffering");
disableFrameThrottling = newSettings.GetBool("disableFrameThrottling");
}
extern "C" JNIEXPORT void Java_emu_skyline_SkylineApplication_initializeLog( extern "C" JNIEXPORT void Java_emu_skyline_SkylineApplication_initializeLog(
JNIEnv *env, JNIEnv *env,
jobject, jobject,
jstring appFilesPathJstring, jstring publicAppFilesPathJstring,
jint logLevel jint logLevel
) { ) {
std::string appFilesPath{env->GetStringUTFChars(appFilesPathJstring, nullptr)}; skyline::JniString publicAppFilesPath(env, publicAppFilesPathJstring);
skyline::Logger::configLevel = static_cast<skyline::Logger::LogLevel>(logLevel); skyline::Logger::configLevel = static_cast<skyline::Logger::LogLevel>(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( extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
@ -71,8 +79,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
jstring romUriJstring, jstring romUriJstring,
jint romType, jint romType,
jint romFd, jint romFd,
jint preferenceFd, jobject settingsInstance,
jint systemLanguage,
jstring publicAppFilesPathJstring, jstring publicAppFilesPathJstring,
jstring privateAppFilesPathJstring, jstring privateAppFilesPathJstring,
jstring nativeLibraryPathJstring, jstring nativeLibraryPathJstring,
@ -85,8 +92,9 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
pthread_setname_np(pthread_self(), "EmuMain"); pthread_setname_np(pthread_self(), "EmuMain");
auto jvmManager{std::make_shared<skyline::JvmManager>(env, instance)}; auto jvmManager{std::make_shared<skyline::JvmManager>(env, instance)};
auto settings{std::make_shared<skyline::Settings>(preferenceFd)};
close(preferenceFd); skyline::KtSettings ktSettings{env, settingsInstance};
auto settings{std::make_shared<skyline::Settings>(ktSettings)};
skyline::JniString publicAppFilesPath(env, publicAppFilesPathJstring); skyline::JniString publicAppFilesPath(env, publicAppFilesPathJstring);
skyline::Logger::EmulationContext.Initialize(publicAppFilesPath + "logs/emulation.sklog"); skyline::Logger::EmulationContext.Initialize(publicAppFilesPath + "logs/emulation.sklog");
@ -110,13 +118,13 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
privateAppFilesPath, privateAppFilesPath,
nativeLibraryPath, nativeLibraryPath,
GetTimeZoneName(), GetTimeZoneName(),
static_cast<skyline::language::SystemLanguage>(systemLanguage),
std::make_shared<skyline::vfs::AndroidAssetFileSystem>(AAssetManager_fromJava(env, assetManager)) std::make_shared<skyline::vfs::AndroidAssetFileSystem>(AAssetManager_fromJava(env, assetManager))
)}; )};
OsWeak = os; OsWeak = os;
GpuWeak = os->state.gpu; GpuWeak = os->state.gpu;
AudioWeak = os->state.audio; AudioWeak = os->state.audio;
InputWeak = os->state.input; InputWeak = os->state.input;
SettingsWeak = settings;
jvmManager->InitializeControllers(); jvmManager->InitializeControllers();
skyline::Logger::InfoNoPrefix("Launching ROM {}", skyline::JniString(env, romUriJstring)); skyline::Logger::InfoNoPrefix("Launching ROM {}", skyline::JniString(env, romUriJstring));

View File

@ -1,56 +1,12 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#define PUGIXML_HEADER_ONLY
#include <pugixml.hpp>
#include "settings.h" #include "settings.h"
namespace skyline { namespace skyline {
Settings::Settings(int fd) { /**
pugi::xml_document document; * @note This is a placeholder implementation, it must be overridden via template specialisation for platform-specific behavior
auto result{document.load_file(fmt::format("/proc/self/fd/{}", fd).c_str())}; */
if (!result) template<class T>
throw exception("PugiXML Error: {} at {}", result.description(), result.offset); void Settings::Update(T newSettings) {}
#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<Logger::LogLevel>(element.text().as_uint(static_cast<unsigned int>(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<std::tuple_size_v<decltype(preferences)>> 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);
}
}
} }

View File

@ -3,7 +3,7 @@
#pragma once #pragma once
#include <common.h> #include "language.h"
namespace skyline { namespace skyline {
/** /**
@ -11,15 +11,25 @@ namespace skyline {
*/ */
class Settings { class Settings {
public: public:
Logger::LogLevel logLevel; //!< The minimum level that logs need to be for them to be printed // System
std::string username; //!< The name set by the user to be supplied to the guest
bool operationMode; //!< If the emulated Switch should be handheld or docked 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 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 bool disableFrameThrottling; //!< Allow the guest to submit frames without any blocking calls
template<class T>
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<class T>
void Update(T newSettings);
}; };
} }

View File

@ -18,6 +18,43 @@ namespace skyline {
JniString(JNIEnv *env, jstring jString) : std::string(GetJString(env, jString)) {} 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<typename T>
requires std::is_integral_v<T> || std::is_enum_v<T>
T GetInt(const std::string_view &key) {
return static_cast<T>(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<jstring>(env->GetObjectField(settingsInstance, env->GetFieldID(settingsClass, key.data(), "Ljava/lang/String;")))};
}
};
/** /**
* @brief The JvmManager class is used to simplify transactions with the Java component * @brief The JvmManager class is used to simplify transactions with the Java component
*/ */

View File

@ -20,7 +20,6 @@ namespace skyline::kernel {
std::string privateAppFilesPath, std::string privateAppFilesPath,
std::string nativeLibraryPath, std::string nativeLibraryPath,
std::string deviceTimeZone, std::string deviceTimeZone,
language::SystemLanguage systemLanguage,
std::shared_ptr<vfs::FileSystem> assetFileSystem) std::shared_ptr<vfs::FileSystem> assetFileSystem)
: nativeLibraryPath(std::move(nativeLibraryPath)), : nativeLibraryPath(std::move(nativeLibraryPath)),
publicAppFilesPath(std::move(publicAppFilesPath)), publicAppFilesPath(std::move(publicAppFilesPath)),
@ -28,8 +27,7 @@ namespace skyline::kernel {
state(this, jvmManager, settings), state(this, jvmManager, settings),
deviceTimeZone(std::move(deviceTimeZone)), deviceTimeZone(std::move(deviceTimeZone)),
assetFileSystem(std::move(assetFileSystem)), assetFileSystem(std::move(assetFileSystem)),
serviceManager(state), serviceManager(state) {}
systemLanguage(systemLanguage) {}
void OS::Execute(int romFd, loader::RomFormat romType) { void OS::Execute(int romFd, loader::RomFormat romType) {
auto romFile{std::make_shared<vfs::OsBacking>(romFd)}; auto romFile{std::make_shared<vfs::OsBacking>(romFd)};

View File

@ -21,7 +21,6 @@ namespace skyline::kernel {
std::string deviceTimeZone; //!< The timezone name (e.g. Europe/London) std::string deviceTimeZone; //!< The timezone name (e.g. Europe/London)
std::shared_ptr<vfs::FileSystem> assetFileSystem; //!< A filesystem to be used for accessing emulator assets (like tzdata) std::shared_ptr<vfs::FileSystem> assetFileSystem; //!< A filesystem to be used for accessing emulator assets (like tzdata)
service::ServiceManager serviceManager; service::ServiceManager serviceManager;
language::SystemLanguage systemLanguage;
/** /**
* @param settings An instance of the Settings class * @param settings An instance of the Settings class
@ -34,7 +33,6 @@ namespace skyline::kernel {
std::string privateAppFilesPath, std::string privateAppFilesPath,
std::string deviceTimeZone, std::string deviceTimeZone,
std::string nativeLibraryPath, std::string nativeLibraryPath,
language::SystemLanguage systemLanguage,
std::shared_ptr<vfs::FileSystem> assetFileSystem std::shared_ptr<vfs::FileSystem> assetFileSystem
); );

View File

@ -31,8 +31,8 @@ namespace skyline::service::account {
.uid = userId, .uid = userId,
}; };
size_t usernameSize{std::min(accountProfileBase.nickname.size() - 1, state.settings->username.size())}; size_t usernameSize{std::min(accountProfileBase.nickname.size() - 1, state.settings->usernameValue.size())};
std::memcpy(accountProfileBase.nickname.data(), state.settings->username.c_str(), usernameSize); std::memcpy(accountProfileBase.nickname.data(), state.settings->usernameValue.c_str(), usernameSize);
response.Push(accountProfileBase); response.Push(accountProfileBase);

View File

@ -4,7 +4,7 @@
#include <common/uuid.h> #include <common/uuid.h>
#include <mbedtls/sha1.h> #include <mbedtls/sha1.h>
#include <loader/loader.h> #include <loader/loader.h>
#include <os.h> #include <common/settings.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
#include <services/account/IAccountServiceForApplication.h> #include <services/account/IAccountServiceForApplication.h>
#include <services/am/storage/VectorIStorage.h> #include <services/am/storage/VectorIStorage.h>
@ -58,9 +58,9 @@ namespace skyline::service::am {
} }
Result IApplicationFunctions::GetDesiredLanguage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { 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<u32>(desiredLanguage)) & state.loader->nacp->nacpContents.supportedLanguageFlag) == 0) if (((1 << static_cast<u32>(desiredLanguage)) & state.loader->nacp->nacpContents.supportedLanguageFlag) == 0)
desiredLanguage = state.loader->nacp->GetFirstSupportedLanguage(); desiredLanguage = state.loader->nacp->GetFirstSupportedLanguage();

View File

@ -30,7 +30,7 @@ import emu.skyline.input.*
import emu.skyline.loader.getRomFormat import emu.skyline.loader.getRomFormat
import emu.skyline.utils.ByteBufferSerializable import emu.skyline.utils.ByteBufferSerializable
import emu.skyline.utils.Settings import emu.skyline.utils.Settings
import java.io.File import emu.skyline.utils.SettingsValues
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.util.concurrent.FutureTask 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 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 romType The type of the ROM as an enum value
* @param romFd The file descriptor of the ROM object * @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 publicAppFilesPath The full path to the public app files directory
* @param privateAppFilesPath The full path to the private 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 nativeLibraryPath The full path to the app native library directory
* @param assetManager The asset manager used for accessing app assets * @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 * @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 rom = intent.data!!
val romType = getRomFormat(rom, contentResolver).ordinal val romType = getRomFormat(rom, contentResolver).ordinal
val romFd = contentResolver.openFileDescriptor(rom, "r")!! 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 { 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() returnFromEmulation()
} }

View File

@ -34,8 +34,8 @@ class SkylineApplication : Application() {
instance = this instance = this
System.loadLibrary("skyline") System.loadLibrary("skyline")
val appFilesPath = applicationContext.getPublicFilesDir().canonicalPath val publicAppFilesPath = applicationContext.getPublicFilesDir().canonicalPath
File("$appFilesPath/logs/").mkdirs() File("$publicAppFilesPath/logs/").mkdirs()
initializeLog("$appFilesPath/", getSettings().logLevel) initializeLog("$publicAppFilesPath/", getSettings().logLevel)
} }
} }

View File

@ -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
}