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 <pthread.h>
#include <unistd.h>
#include <android/asset_manager_jni.h>
#include <sys/system_properties.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::audio::Audio> AudioWeak;
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
static std::string GetTimeZoneName() {
@ -54,15 +54,23 @@ static std::string GetTimeZoneName() {
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(
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<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(
@ -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<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::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<skyline::language::SystemLanguage>(systemLanguage),
std::make_shared<skyline::vfs::AndroidAssetFileSystem>(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));

View File

@ -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 <pugixml.hpp>
#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<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);
}
}
/**
* @note This is a placeholder implementation, it must be overridden via template specialisation for platform-specific behavior
*/
template<class T>
void Settings::Update(T newSettings) {}
}

View File

@ -3,7 +3,7 @@
#pragma once
#include <common.h>
#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<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)) {}
};
/**
* @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
*/

View File

@ -20,7 +20,6 @@ namespace skyline::kernel {
std::string privateAppFilesPath,
std::string nativeLibraryPath,
std::string deviceTimeZone,
language::SystemLanguage systemLanguage,
std::shared_ptr<vfs::FileSystem> 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<vfs::OsBacking>(romFd)};

View File

@ -21,7 +21,6 @@ namespace skyline::kernel {
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)
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<vfs::FileSystem> assetFileSystem
);

View File

@ -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);

View File

@ -4,7 +4,7 @@
#include <common/uuid.h>
#include <mbedtls/sha1.h>
#include <loader/loader.h>
#include <os.h>
#include <common/settings.h>
#include <kernel/types/KProcess.h>
#include <services/account/IAccountServiceForApplication.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) {
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)
desiredLanguage = state.loader->nacp->GetFirstSupportedLanguage();

View File

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

View File

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

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
}