diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index edc831d5..2f8c5ff7 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/kernel/services/set/sys.cpp ${source_DIR}/skyline/kernel/services/apm/apm.cpp ${source_DIR}/skyline/kernel/services/am/appletOE.cpp + ${source_DIR}/skyline/kernel/services/fatal/fatal.cpp ) target_link_libraries(skyline fmt tinyxml2) target_compile_options(skyline PRIVATE -Wno-c++17-extensions) diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index a8d0acbe..e223c6f5 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -12,7 +11,7 @@ bool Halt = false; void ThreadMain(const std::string romPath, const std::string prefPath, const std::string logPath) { auto logger = std::make_shared(logPath); auto settings = std::make_shared(prefPath); - //settings->List(log); // (Uncomment when you want to print out all settings strings) + //settings->List(logger); // (Uncomment when you want to print out all settings strings) auto start = std::chrono::steady_clock::now(); try { skyline::kernel::OS os(logger, settings); diff --git a/app/src/main/cpp/skyline/kernel/ipc.cpp b/app/src/main/cpp/skyline/kernel/ipc.cpp index 63d2d028..ff7038e2 100644 --- a/app/src/main/cpp/skyline/kernel/ipc.cpp +++ b/app/src/main/cpp/skyline/kernel/ipc.cpp @@ -71,7 +71,7 @@ namespace skyline::kernel::ipc { } if (payload->magic != constant::SfciMagic) - state.logger->Write(Logger::Warn, "Unexpected Magic in PayloadHeader: 0x{:X}", u32(payload->magic)); + state.logger->Write(Logger::Debug, "Unexpected Magic in PayloadHeader: 0x{:X}", u32(payload->magic)); if (header->c_flag == static_cast(BufferCFlag::SingleDescriptor)) { vecBufC.push_back(reinterpret_cast(currPtr)); diff --git a/app/src/main/cpp/skyline/kernel/services/am/appletOE.cpp b/app/src/main/cpp/skyline/kernel/services/am/appletOE.cpp index feb5e18c..92ae7f70 100644 --- a/app/src/main/cpp/skyline/kernel/services/am/appletOE.cpp +++ b/app/src/main/cpp/skyline/kernel/services/am/appletOE.cpp @@ -13,8 +13,11 @@ namespace skyline::kernel::service::am { {0x0, SFunc(IApplicationProxy::GetCommonStateGetter)}, {0x1, SFunc(IApplicationProxy::GetSelfController)}, {0x2, SFunc(IApplicationProxy::GetWindowController)}, + {0x3, SFunc(IApplicationProxy::GetAudioController)}, + {0x4, SFunc(IApplicationProxy::GetDisplayController)}, {0xB, SFunc(IApplicationProxy::GetLibraryAppletCreator)}, - {0x14, SFunc(IApplicationProxy::GetApplicationFunctions)} + {0x14, SFunc(IApplicationProxy::GetApplicationFunctions)}, + {0x3E8, SFunc(IApplicationProxy::GetDisplayController)} }) {} void IApplicationProxy::GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { @@ -29,6 +32,14 @@ namespace skyline::kernel::service::am { manager.NewService(Service::am_IWindowController, session, response); } + void IApplicationProxy::GetAudioController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + manager.NewService(Service::am_IAudioController, session, response); + } + + void IApplicationProxy::GetDisplayController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + manager.NewService(Service::am_IDisplayController, session, response); + } + void IApplicationProxy::GetLibraryAppletCreator(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { manager.NewService(Service::am_ILibraryAppletCreator, session, response); } @@ -37,18 +48,77 @@ namespace skyline::kernel::service::am { manager.NewService(Service::am_IApplicationFunctions, session, response); } + void IApplicationProxy::IDebugFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + manager.NewService(Service::am_IDebugFunctions, session, response); + } + ICommonStateGetter::ICommonStateGetter(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_ICommonStateGetter, { - }) {} + {0x0, SFunc(ICommonStateGetter::GetEventHandle)}, + {0x9, SFunc(ICommonStateGetter::GetCurrentFocusState)}, + {0x5, SFunc(ICommonStateGetter::GetOperationMode)}, + {0x6, SFunc(ICommonStateGetter::GetPerformanceMode)} + }) { + operationMode = static_cast(state.settings->GetBool("operation_mode")); + state.logger->Write(Logger::Info, "Switch on mode: {}", static_cast(operationMode) ? "Docked" : "Handheld"); + } + + void ICommonStateGetter::GetEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + messageEvent = state.thisProcess->NewHandle(); + response.copyHandles.push_back(messageEvent->handle); + } + + void ICommonStateGetter::GetCurrentFocusState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.WriteValue(static_cast(ApplicationStatus::InFocus)); + } + + void ICommonStateGetter::GetOperationMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.WriteValue(static_cast(operationMode)); + } + + void ICommonStateGetter::GetPerformanceMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.WriteValue(static_cast(operationMode)); + } ISelfController::ISelfController(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_ISelfController, { + {0xB, SFunc(ISelfController::SetOperationModeChangedNotification)}, + {0xC, SFunc(ISelfController::SetPerformanceModeChangedNotification)}, + {0xD, SFunc(ISelfController::SetFocusHandlingMode)} }) {} + void ISelfController::SetFocusHandlingMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} + + void ISelfController::SetOperationModeChangedNotification(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} + + void ISelfController::SetPerformanceModeChangedNotification(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} + IWindowController::IWindowController(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IWindowController, { + {0x1, SFunc(IWindowController::GetAppletResourceUserId)}, + {0xA, SFunc(IWindowController::AcquireForegroundRights)} + }) {} + + void IWindowController::GetAppletResourceUserId(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.WriteValue(static_cast(state.thisProcess->mainThread)); + } + + void IWindowController::AcquireForegroundRights(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} + + IAudioController::IAudioController(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IAudioController, { + }) {} + + IDisplayController::IDisplayController(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IDisplayController, { }) {} ILibraryAppletCreator::ILibraryAppletCreator(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_ILibraryAppletCreator, { }) {} IApplicationFunctions::IApplicationFunctions(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IApplicationFunctions, { + {0x28, SFunc(IApplicationFunctions::NotifyRunning)} + }) {} + + void IApplicationFunctions::NotifyRunning(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.WriteValue(1); + } + + IDebugFunctions::IDebugFunctions(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IDebugFunctions, { }) {} } diff --git a/app/src/main/cpp/skyline/kernel/services/am/appletOE.h b/app/src/main/cpp/skyline/kernel/services/am/appletOE.h index c80236f4..adbf6830 100644 --- a/app/src/main/cpp/skyline/kernel/services/am/appletOE.h +++ b/app/src/main/cpp/skyline/kernel/services/am/appletOE.h @@ -2,6 +2,8 @@ #include "../base_service.h" #include "../serviceman.h" +#include "../../types/KProcess.h" +#include "../../types/KEvent.h" namespace skyline::kernel::service::am { /** @@ -39,6 +41,16 @@ namespace skyline::kernel::service::am { */ void GetWindowController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** + * @brief This returns #IAudioController (https://switchbrew.org/wiki/Applet_Manager_services#IAudioController) + */ + void GetAudioController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This returns #IDisplayController (https://switchbrew.org/wiki/Applet_Manager_services#IDisplayController) + */ + void GetDisplayController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** * @brief This returns #ILibraryAppletCreator (https://switchbrew.org/wiki/Applet_Manager_services#ILibraryAppletCreator) */ @@ -48,30 +60,134 @@ namespace skyline::kernel::service::am { * @brief This returns #IApplicationFunctions (https://switchbrew.org/wiki/Applet_Manager_services#IApplicationFunctions) */ void GetApplicationFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This returns #IDebugFunctions (https://switchbrew.org/wiki/Applet_Manager_services#IDebugFunctions) + */ + void IDebugFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); }; + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#ICommonStateGetter + */ class ICommonStateGetter : public BaseService { + private: + std::shared_ptr messageEvent{}; + + enum class ApplicationStatus : u8 { + InFocus = 1, OutOfFocus = 2 + }; + + enum class OperationMode : u8 { + Handheld = 0, Docked = 1 + } operationMode; public: ICommonStateGetter(const DeviceState &state, ServiceManager &manager); + + /** + * @brief This returns the handle to a KEvent object that is signalled whenever RecieveMessage has a message (https://switchbrew.org/wiki/Applet_Manager_services#GetEventHandle) + */ + void GetEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This returns if an application is in focus or not. It always returns in focus on the emulator (https://switchbrew.org/wiki/Applet_Manager_services#GetCurrentFocusState) + */ + void GetCurrentFocusState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This returns the current OperationMode (https://switchbrew.org/wiki/Applet_Manager_services#GetOperationMode) + */ + void GetOperationMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This returns the current PerformanceMode (Same as operationMode but u32) (https://switchbrew.org/wiki/Applet_Manager_services#GetPerformanceMode) + */ + void GetPerformanceMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); }; + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#ISelfController + */ class ISelfController : public BaseService { public: ISelfController(const DeviceState &state, ServiceManager &manager); + + /** + * @brief This function inputs a u8 bool flag and no output (Stubbed) (https://switchbrew.org/wiki/Applet_Manager_services#SetOperationModeChangedNotification) + */ + void SetOperationModeChangedNotification(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This function inputs a u8 bool flag and no output (Stubbed) (https://switchbrew.org/wiki/Applet_Manager_services#SetPerformanceModeChangedNotification) + */ + void SetPerformanceModeChangedNotification(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This function inputs 3 unknown u8 values and has no output (Stubbed) (https://switchbrew.org/wiki/Applet_Manager_services#GetCurrentFocusState) + */ + void SetFocusHandlingMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); }; + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#IWindowController + */ class IWindowController : public BaseService { public: IWindowController(const DeviceState &state, ServiceManager &manager); + + /** + * @brief This returns the PID of the current application (https://switchbrew.org/wiki/Applet_Manager_services#GetAppletResourceUserId) + */ + void GetAppletResourceUserId(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief This function has mo inputs or outputs (Stubbed) (https://switchbrew.org/wiki/Applet_Manager_services#AcquireForegroundRights) + */ + void AcquireForegroundRights(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); }; + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#IAudioController + */ + class IAudioController : public BaseService { + public: + IAudioController(const DeviceState &state, ServiceManager &manager); + }; + + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#IDisplayController + */ + class IDisplayController : public BaseService { + public: + IDisplayController(const DeviceState &state, ServiceManager &manager); + }; + + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#ILibraryAppletCreator + */ class ILibraryAppletCreator : public BaseService { public: ILibraryAppletCreator(const DeviceState &state, ServiceManager &manager); }; + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#IApplicationFunctions + */ class IApplicationFunctions : public BaseService { public: IApplicationFunctions(const DeviceState &state, ServiceManager &manager); + + /** + * @brief This just returns 1 regardless of input (https://switchbrew.org/wiki/Applet_Manager_services#NotifyRunning) + */ + void NotifyRunning(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + }; + + /** + * @brief https://switchbrew.org/wiki/Applet_Manager_services#IDebugFunctions + */ + class IDebugFunctions : public BaseService { + public: + IDebugFunctions(const DeviceState &state, ServiceManager &manager); }; } diff --git a/app/src/main/cpp/skyline/kernel/services/base_service.h b/app/src/main/cpp/skyline/kernel/services/base_service.h index 22b163ab..0799296c 100644 --- a/app/src/main/cpp/skyline/kernel/services/base_service.h +++ b/app/src/main/cpp/skyline/kernel/services/base_service.h @@ -14,7 +14,21 @@ namespace skyline::kernel::service { * @brief This contains an enum for every service that's present */ enum class Service { - sm, set_sys, apm, apm_ISession, am_appletOE, am_IApplicationProxy, am_ICommonStateGetter, am_IApplicationFunctions, am_ISelfController, am_IWindowController, am_ILibraryAppletCreator + sm, + fatal_u, + set_sys, + apm, + apm_ISession, + am_appletOE, + am_IApplicationProxy, + am_ICommonStateGetter, + am_IApplicationFunctions, + am_ISelfController, + am_IWindowController, + am_IAudioController, + am_IDisplayController, + am_ILibraryAppletCreator, + am_IDebugFunctions, }; /** @@ -22,6 +36,7 @@ namespace skyline::kernel::service { */ const static std::unordered_map ServiceString = { {"sm:", Service::sm}, + {"fatal:u", Service::fatal_u}, {"set:sys", Service::set_sys}, {"apm", Service::apm}, {"apm:ISession", Service::apm_ISession}, @@ -30,8 +45,11 @@ namespace skyline::kernel::service { {"am:ICommonStateGetter", Service::am_ICommonStateGetter}, {"am:ISelfController", Service::am_ISelfController}, {"am:IWindowController", Service::am_IWindowController}, + {"am:IAudioController", Service::am_IAudioController}, + {"am:IDisplayController", Service::am_IDisplayController}, {"am:ILibraryAppletCreator", Service::am_ILibraryAppletCreator}, {"am:IApplicationFunctions", Service::am_IApplicationFunctions}, + {"am:IDebugFunctions", Service::am_IDebugFunctions}, }; class ServiceManager; @@ -73,10 +91,17 @@ namespace skyline::kernel::service { * @param response The corresponding IpcResponse object */ void HandleRequest(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + std::function function; try { - vTable.at(request.payload->value)(session, request, response); + function = vTable.at(request.payload->value); } catch (std::out_of_range&) { state.logger->Write(Logger::Warn, "Cannot find function in service '{0}' (Type: {1}): 0x{2:X} ({2})", getName(), serviceType, u32(request.payload->value)); + return; + } + try { + function(session, request, response); + } catch (std::exception& e) { + throw exception(e.what()); } }; diff --git a/app/src/main/cpp/skyline/kernel/services/fatal/fatal.cpp b/app/src/main/cpp/skyline/kernel/services/fatal/fatal.cpp new file mode 100644 index 00000000..5d1b3ca4 --- /dev/null +++ b/app/src/main/cpp/skyline/kernel/services/fatal/fatal.cpp @@ -0,0 +1,11 @@ +#include "fatal.h" + +namespace skyline::kernel::service::fatal { + fatalU::fatalU(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::fatal_u, { + {0x0, SFunc(fatalU::ThrowFatal)} + }) {} + + void fatalU::ThrowFatal(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + throw exception("A fatal error has caused emulation to stop"); + } +} diff --git a/app/src/main/cpp/skyline/kernel/services/fatal/fatal.h b/app/src/main/cpp/skyline/kernel/services/fatal/fatal.h new file mode 100644 index 00000000..2af9ae0c --- /dev/null +++ b/app/src/main/cpp/skyline/kernel/services/fatal/fatal.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../base_service.h" +#include "../serviceman.h" + +namespace skyline::kernel::service::fatal { + /** + * @brief fatal_u is used by applications to throw errors (https://switchbrew.org/wiki/Fatal_services#fatal:u) + */ + class fatalU : public BaseService { + public: + fatalU(const DeviceState &state, ServiceManager& manager); + + /** + * @brief This throws an exception so that emulation will quit + */ + void ThrowFatal(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + }; +} diff --git a/app/src/main/cpp/skyline/kernel/services/serviceman.cpp b/app/src/main/cpp/skyline/kernel/services/serviceman.cpp index ca64ef6e..e4cfe3df 100644 --- a/app/src/main/cpp/skyline/kernel/services/serviceman.cpp +++ b/app/src/main/cpp/skyline/kernel/services/serviceman.cpp @@ -4,6 +4,7 @@ #include "set/sys.h" #include "apm/apm.h" #include "am/appletOE.h" +#include "fatal/fatal.h" namespace skyline::kernel::service { ServiceManager::ServiceManager(const DeviceState &state) : state(state) {} @@ -15,6 +16,9 @@ namespace skyline::kernel::service { case Service::sm: serviceMap[serviceType] = std::make_shared(state, *this); break; + case Service::fatal_u: + serviceMap[serviceType] = std::make_shared(state, *this); + break; case Service::set_sys: serviceMap[serviceType] = std::make_shared(state, *this); break; @@ -36,6 +40,12 @@ namespace skyline::kernel::service { case Service::am_IWindowController: serviceMap[serviceType] = std::make_shared(state, *this); break; + case Service::am_IAudioController: + serviceMap[serviceType] = std::make_shared(state, *this); + break; + case Service::am_IDisplayController: + serviceMap[serviceType] = std::make_shared(state, *this); + break; case Service::am_ISelfController: serviceMap[serviceType] = std::make_shared(state, *this); break; @@ -45,6 +55,9 @@ namespace skyline::kernel::service { case Service::am_IApplicationFunctions: serviceMap[serviceType] = std::make_shared(state, *this); break; + case Service::am_IDebugFunctions: + serviceMap[serviceType] = std::make_shared(state, *this); + break; } serviceObj = serviceMap[serviceType]; } else diff --git a/app/src/main/cpp/skyline/kernel/types/KEvent.h b/app/src/main/cpp/skyline/kernel/types/KEvent.h new file mode 100644 index 00000000..9f73af9a --- /dev/null +++ b/app/src/main/cpp/skyline/kernel/types/KEvent.h @@ -0,0 +1,18 @@ +#pragma once + +#include "KSyncObject.h" + +namespace skyline::kernel::type { + /** + * @brief KEvent is an object that's signalled on an repeatable event occurring (https://switchbrew.org/wiki/Kernel_objects#KEvent) + */ + class KEvent : public KSyncObject { + public: + /** + * @param handle The handle of the object in the handle table + * @param pid The PID of the main thread + * @param state The state of the device + */ + KEvent(skyline::handle_t handle, pid_t pid, const DeviceState &state) : KSyncObject(handle, pid, state, KType::KEvent) {} + }; +} diff --git a/app/src/main/cpp/skyline/kernel/types/KObject.h b/app/src/main/cpp/skyline/kernel/types/KObject.h index 6178544b..38c5eeb7 100644 --- a/app/src/main/cpp/skyline/kernel/types/KObject.h +++ b/app/src/main/cpp/skyline/kernel/types/KObject.h @@ -7,7 +7,7 @@ namespace skyline::kernel::type { * @brief These types are used to perform runtime evaluation of a kernel object's type when converting from base class */ enum class KType { - KThread, KProcess, KSharedMemory, KPrivateMemory, KSession + KThread, KProcess, KSharedMemory, KPrivateMemory, KSession, KEvent }; /** diff --git a/app/src/main/cpp/skyline/kernel/types/KSyncObject.cpp b/app/src/main/cpp/skyline/kernel/types/KSyncObject.cpp index 27fbc729..f920f06a 100644 --- a/app/src/main/cpp/skyline/kernel/types/KSyncObject.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KSyncObject.cpp @@ -5,8 +5,7 @@ namespace skyline::kernel::type { KSyncObject::KSyncObject(skyline::handle_t handle, pid_t pid, const skyline::DeviceState &state, skyline::kernel::type::KType type) : KObject(handle, pid, state, type) {} void KSyncObject::Signal() { - for (auto&[tid, process] : state.os->threadMap) { - auto &thread = process->threadMap.at(tid); + for (auto&[tid, thread] : state.os->threadMap.at(ownerPid)->threadMap) { if (thread->status == type::KThread::ThreadStatus::Waiting) { for (auto &waitHandle : thread->waitHandles) { if (handle == waitHandle) { diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png deleted file mode 100644 index b89447a9..00000000 Binary files a/app/src/main/ic_launcher-web.png and /dev/null differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c175316d..401daad1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,12 +14,6 @@ Cannot find any ROMs NROs NSOs - - Search Location - Logging - Log Level - Localization - Language Clear Share @@ -28,4 +22,14 @@ An I/O error has occurred An error has occurred while sharing The logs have been cleared + + Search Location + Logging + Log Level + Localization + Language + System + Use Docked Mode + The system will emulate being in handheld mode + The system will emulate being in docked mode diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 50964da7..84740a1e 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -36,6 +36,16 @@ app:title="@string/log_level" app:useSimpleSummaryProvider="true" /> + + +