Framebuffer and NativeActivity

What was added:
* Framebuffer
* NativeActivity
* NV Services
* IOCTL Handler
* NV Devices:
* * /dev/nvmap - 0xC0080101, 0xC0080103, 0xC0200104, 0xC0180105, 0xC00C0109, 0xC008010E
* * /dev/nvhost-as-gpu
* * /dev/nvhost-channel - 0x40044801, 0xC0104809, 0xC010480B, 0xC018480C, 0x4004480D, 0xC020481A, 0x40084714
* * /dev/nvhost-ctrl
* * /dev/nvhost-ctrl-gpu - 0x80044701, 0x80284702, 0xC0184706, 0xC0B04705, 0x80084714
* SVCs:
* * SetMemoryAttribute
* * CreateTransferMemory
* * ResetSignal
* * GetSystemTick
* Addition of Compact Logger
What was fixed:
* SVCs:
* * SetHeapSize
* * SetMemoryAttribute
* * QueryMemory
* A release build would not set CMAKE_BUILD_TYPE to "RELEASE"
* The logger code was simplified
This commit is contained in:
◱ PixelyIon 2019-11-14 01:39:31 +05:30 committed by ◱ PixelyIon
parent 2e0014ba7c
commit c005d7df74
96 changed files with 3783 additions and 951 deletions

View File

@ -3,6 +3,9 @@
<option name="RIGHT_MARGIN" value="400" /> <option name="RIGHT_MARGIN" value="400" />
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" /> <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<option name="SOFT_MARGINS" value="80,140" /> <option name="SOFT_MARGINS" value="80,140" />
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<Objective-C> <Objective-C>
<option name="INDENT_VISIBILITY_KEYWORDS" value="2" /> <option name="INDENT_VISIBILITY_KEYWORDS" value="2" />
<option name="INDENT_PREPROCESSOR_DIRECTIVE" value="4" /> <option name="INDENT_PREPROCESSOR_DIRECTIVE" value="4" />
@ -48,6 +51,7 @@
<pair source="cpp" header="h" fileNamingConvention="SNAKE_CASE" /> <pair source="cpp" header="h" fileNamingConvention="SNAKE_CASE" />
<pair source="c" header="h" fileNamingConvention="SNAKE_CASE" /> <pair source="c" header="h" fileNamingConvention="SNAKE_CASE" />
<pair source="cpp" header="h" fileNamingConvention="PASCAL_CASE" /> <pair source="cpp" header="h" fileNamingConvention="PASCAL_CASE" />
<pair source="cpp" header="h" fileNamingConvention="NONE" />
</extensions> </extensions>
</Objective-C-extensions> </Objective-C-extensions>
<XML> <XML>

3
.idea/scopes/SkylineJava.xml generated Normal file
View File

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="SkylineJava" pattern="file[app]:src/main/java//*" />
</component>

View File

@ -1,4 +1,4 @@
<h1> <h1 align="center">
<img height="60%" width="60%" src="https://i.imgur.com/6PJ7Ml2.png"><br> <img height="60%" width="60%" src="https://i.imgur.com/6PJ7Ml2.png"><br>
<a href="https://discord.gg/XnbXNQM" target="_blank"> <a href="https://discord.gg/XnbXNQM" target="_blank">
<img src="https://img.shields.io/discord/545842171459272705?label=Discord&logo=Discord&logoColor=Violet"> <img src="https://img.shields.io/discord/545842171459272705?label=Discord&logo=Discord&logoColor=Violet">

View File

@ -1,21 +1,32 @@
cmake_minimum_required(VERSION 3.8) cmake_minimum_required(VERSION 3.8)
project(Skyline VERSION 0.3 LANGUAGES CXX) project(Skyline VERSION 0.3)
set(BUILD_TESTING OFF) set(BUILD_TESTING OFF)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -flto=full -Wno-unused-command-line-argument") set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -flto=full -Wno-unused-command-line-argument")
if (uppercase_CMAKE_BUILD_TYPE STREQUAL "RELEASE")
add_compile_definitions(NDEBUG)
endif()
add_subdirectory("libraries/tinyxml2") add_subdirectory("libraries/tinyxml2")
add_subdirectory("libraries/fmt") add_subdirectory("libraries/fmt")
set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
include_directories(${source_DIR}/skyline) include_directories(${source_DIR}/skyline)
add_library(skyline SHARED add_library(skyline SHARED
${source_DIR}/main.cpp ${source_DIR}/main.cpp
${source_DIR}/skyline/common.cpp ${source_DIR}/skyline/common.cpp
${source_DIR}/skyline/nce.cpp ${source_DIR}/skyline/nce.cpp
${source_DIR}/skyline/gpu.cpp
${source_DIR}/skyline/gpu/display.cpp
${source_DIR}/skyline/gpu/parcel.cpp
${source_DIR}/skyline/gpu/devices/nvhost_ctrl.cpp
${source_DIR}/skyline/gpu/devices/nvhost_ctrl_gpu.cpp
${source_DIR}/skyline/gpu/devices/nvhost_channel.cpp
${source_DIR}/skyline/gpu/devices/nvmap.cpp
${source_DIR}/skyline/gpu/devices/nvhost_as_gpu.cpp
${source_DIR}/skyline/os.cpp ${source_DIR}/skyline/os.cpp
${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/loader/nro.cpp
${source_DIR}/skyline/kernel/ipc.cpp ${source_DIR}/skyline/kernel/ipc.cpp
@ -26,15 +37,20 @@ add_library(skyline SHARED
${source_DIR}/skyline/kernel/types/KSharedMemory.cpp ${source_DIR}/skyline/kernel/types/KSharedMemory.cpp
${source_DIR}/skyline/kernel/types/KTransferMemory.cpp ${source_DIR}/skyline/kernel/types/KTransferMemory.cpp
${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp ${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp
${source_DIR}/skyline/kernel/services/serviceman.cpp ${source_DIR}/skyline/services/serviceman.cpp
${source_DIR}/skyline/kernel/services/sm/sm.cpp ${source_DIR}/skyline/services/sm/sm.cpp
${source_DIR}/skyline/kernel/services/fatal/fatal.cpp ${source_DIR}/skyline/services/fatal/fatal.cpp
${source_DIR}/skyline/kernel/services/set/sys.cpp ${source_DIR}/skyline/services/set/sys.cpp
${source_DIR}/skyline/kernel/services/apm/apm.cpp ${source_DIR}/skyline/services/apm/apm.cpp
${source_DIR}/skyline/kernel/services/am/appletOE.cpp ${source_DIR}/skyline/services/am/applet.cpp
${source_DIR}/skyline/kernel/services/hid/hid.cpp ${source_DIR}/skyline/services/am/appletController.cpp
${source_DIR}/skyline/kernel/services/time/time.cpp ${source_DIR}/skyline/services/hid/hid.cpp
${source_DIR}/skyline/kernel/services/fs/fs.cpp ${source_DIR}/skyline/services/time/time.cpp
${source_DIR}/skyline/services/fs/fs.cpp
${source_DIR}/skyline/services/nvdrv/nvdrv.cpp
${source_DIR}/skyline/services/nvnflinger/dispdrv.cpp
${source_DIR}/skyline/services/vi/vi_m.cpp
) )
target_link_libraries(skyline fmt tinyxml2)
target_link_libraries(skyline vulkan GLESv3 EGL android fmt tinyxml2)
target_compile_options(skyline PRIVATE -Wno-c++17-extensions) target_compile_options(skyline PRIVATE -Wno-c++17-extensions)

View File

@ -5,7 +5,7 @@ android {
buildToolsVersion '29.0.2' buildToolsVersion '29.0.2'
defaultConfig { defaultConfig {
applicationId "skyline.emu" applicationId "skyline.emu"
minSdkVersion 26 minSdkVersion 24
targetSdkVersion 29 targetSdkVersion 29
versionCode 3 versionCode 3
versionName "0.3" versionName "0.3"
@ -16,6 +16,11 @@ android {
buildTypes { buildTypes {
release { release {
debuggable true debuggable true
externalNativeBuild {
cmake {
arguments "-DCMAKE_BUILD_TYPE=RELEASE"
}
}
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@ -33,11 +38,6 @@ android {
path "CMakeLists.txt" path "CMakeLists.txt"
} }
} }
sourceSets {
main {
jni.srcDirs = ['src/main/cpp/unicorn/lib']
}
}
compileOptions { compileOptions {
sourceCompatibility = 1.8 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8

View File

@ -7,15 +7,19 @@
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-feature
android:glEsVersion="0x00030001"
android:required="true" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@drawable/logo_skyline" android:icon="@drawable/logo_skyline"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning" tools:ignore="GoogleAppIndexingWarning">
android:fullBackupContent="@xml/backup_descriptor"
android:requestLegacyExternalStorage="true">
<activity <activity
android:name="emu.skyline.LogActivity" android:name="emu.skyline.LogActivity"
android:label="@string/log" android:label="@string/log"
@ -32,10 +36,21 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="emu.skyline.MainActivity" /> android:value="emu.skyline.MainActivity" />
</activity> </activity>
<activity
android:name="android.app.NativeActivity"
android:configChanges="orientation|screenSize"
android:parentActivityName="emu.skyline.MainActivity"
android:screenOrientation="landscape">
<meta-data
android:name="android.app.lib_name"
android:value="skyline" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.skyline.MainActivity" />
</activity>
<activity android:name="emu.skyline.MainActivity"> <activity android:name="emu.skyline.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -1,47 +1,115 @@
#include <jni.h>
#include <csignal>
#include <string>
#include <thread>
#include "skyline/common.h" #include "skyline/common.h"
#include "skyline/os.h" #include "skyline/os.h"
#include <unistd.h>
#include <jni.h>
#include <android/native_window_jni.h>
#include <android/native_activity.h>
#include <csignal>
std::thread *EmuThread; bool Halt{};
bool Halt = false; uint faultCount{};
ANativeActivity *Activity{};
ANativeWindow *Window{};
AInputQueue *Queue{};
std::thread *uiThread{};
void ThreadMain(const std::string romPath, const std::string prefPath, const std::string logPath) { void GameThread(const std::string &prefPath, const std::string &logPath, const std::string &romPath) {
auto logger = std::make_shared<skyline::Logger>(logPath); while (!Window)
sched_yield();
setpriority(PRIO_PROCESS, static_cast<id_t>(getpid()), skyline::constant::PriorityAn.second);
auto settings = std::make_shared<skyline::Settings>(prefPath); auto settings = std::make_shared<skyline::Settings>(prefPath);
auto logger = std::make_shared<skyline::Logger>(logPath, static_cast<skyline::Logger::LogLevel>(std::stoi(settings->GetString("log_level"))));
//settings->List(logger); // (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(); auto start = std::chrono::steady_clock::now();
try { try {
skyline::kernel::OS os(logger, settings); skyline::kernel::OS os(logger, settings, Window);
logger->Write(skyline::Logger::Info, "Launching ROM {}", romPath); logger->Info("Launching ROM {}", romPath);
os.Execute(romPath); os.Execute(romPath);
logger->Write(skyline::Logger::Info, "Emulation has ended"); logger->Info("Emulation has ended");
} catch (std::exception &e) { } catch (std::exception &e) {
logger->Write(skyline::Logger::Error, e.what()); logger->Error(e.what());
} catch (...) { } catch (...) {
logger->Write(skyline::Logger::Error, "An unknown exception has occurred"); logger->Error("An unknown exception has occurred");
} }
auto end = std::chrono::steady_clock::now(); auto end = std::chrono::steady_clock::now();
logger->Write(skyline::Logger::Info, "Done in: {} ms", (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count())); logger->Info("Done in: {} ms", (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()));
Window = nullptr;
Halt = true;
} }
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_MainActivity_loadFile(JNIEnv *env, jobject instance, jstring romPathJni, jstring prefPathJni, jstring logPathJni) { void UIThread(const std::string &prefPath, const std::string &logPath, const std::string &romPath) {
const char *romPath = env->GetStringUTFChars(romPathJni, nullptr); while (!Queue)
const char *prefPath = env->GetStringUTFChars(prefPathJni, nullptr); sched_yield();
const char *logPath = env->GetStringUTFChars(logPathJni, nullptr); std::thread gameThread(GameThread, std::string(prefPath), std::string(logPath), std::string(romPath));
AInputEvent *event{};
if (EmuThread) { while (!Halt) {
Halt = true; // This'll cause execution to stop after the next breakpoint if (AInputQueue_getEvent(Queue, &event) >= -1) {
EmuThread->join(); if (AKeyEvent_getKeyCode(event) == AKEYCODE_BACK)
Halt = false; // Or the current instance will halt immediately Halt = true;
AInputQueue_finishEvent(Queue, event, true);
}
} }
Queue = nullptr;
// Running on UI thread is not a good idea as the UI will remain unresponsive gameThread.join();
EmuThread = new std::thread(ThreadMain, std::string(romPath, strlen(romPath)), std::string(prefPath, strlen(prefPath)), std::string(logPath, strlen(logPath))); Halt = false;
ANativeActivity_finish(Activity);
env->ReleaseStringUTFChars(romPathJni, romPath); }
env->ReleaseStringUTFChars(prefPathJni, prefPath);
env->ReleaseStringUTFChars(logPathJni, logPath); void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow *window) {
Window = window;
}
void onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) {
Queue = queue;
}
void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow *window) {
Halt = true;
while (Window)
sched_yield();
}
void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) {
Halt = true;
while (Queue)
sched_yield();
}
void signalHandler(int signal) {
syslog(LOG_ERR, "Halting program due to signal: %s", strsignal(signal));
if (faultCount > 2)
pthread_kill(uiThread->native_handle(), SIGKILL);
else
ANativeActivity_finish(Activity);
faultCount++;
}
JNIEXPORT void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize) {
Activity = activity;
Halt = false;
faultCount = 0;
JNIEnv *env = activity->env;
jobject intent = env->CallObjectMethod(activity->clazz, env->GetMethodID(env->GetObjectClass(activity->clazz), "getIntent", "()Landroid/content/Intent;"));
jclass icl = env->GetObjectClass(intent);
jmethodID gse = env->GetMethodID(icl, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
auto jsRom = reinterpret_cast<jstring>(env->CallObjectMethod(intent, gse, env->NewStringUTF("rom")));
auto jsPrefs = reinterpret_cast<jstring>(env->CallObjectMethod(intent, gse, env->NewStringUTF("prefs")));
auto jsLog = reinterpret_cast<jstring>(env->CallObjectMethod(intent, gse, env->NewStringUTF("log")));
const char *romPath = env->GetStringUTFChars(jsRom, nullptr);
const char *prefPath = env->GetStringUTFChars(jsPrefs, nullptr);
const char *logPath = env->GetStringUTFChars(jsLog, nullptr);
std::signal(SIGTERM, signalHandler);
std::signal(SIGSEGV, signalHandler);
std::signal(SIGINT, signalHandler);
std::signal(SIGILL, signalHandler);
std::signal(SIGABRT, signalHandler);
std::signal(SIGFPE, signalHandler);
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
uiThread = new std::thread(UIThread, std::string(prefPath), std::string(logPath), std::string(romPath));
env->ReleaseStringUTFChars(jsRom, romPath);
env->ReleaseStringUTFChars(jsPrefs, prefPath);
env->ReleaseStringUTFChars(jsLog, logPath);
} }

View File

@ -1,4 +1,6 @@
#include "common.h" #include "common.h"
#include "nce.h"
#include "gpu.h"
#include <tinyxml2.h> #include <tinyxml2.h>
namespace skyline { namespace skyline {
@ -10,37 +12,46 @@ namespace skyline {
while (elem) { while (elem) {
switch (elem->Value()[0]) { switch (elem->Value()[0]) {
case 's': case 's':
stringMap.insert( stringMap[elem->FindAttribute("name")->Value()] = elem->GetText();
std::pair<std::string, std::string>(elem->FindAttribute("name")->Value(), elem->GetText()));
break; break;
case 'b': case 'b':
boolMap.insert(std::pair<std::string, bool>(elem->FindAttribute("name")->Value(), elem->FindAttribute("value")->BoolValue())); boolMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute("value")->BoolValue();
break;
case 'i':
intMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute("value")->IntValue();
break;
default: default:
syslog(LOG_ALERT, "Settings type is missing: %s for %s", elem->Value(), elem->FindAttribute("name")->Value());
break; break;
}; };
if (elem->NextSibling()) if (elem->NextSibling())
elem = elem->NextSibling()->ToElement(); elem = elem->NextSibling()->ToElement();
else break; else
break;
} }
pref.Clear(); pref.Clear();
} }
std::string Settings::GetString(const std::string& key) { std::string Settings::GetString(const std::string &key) {
return stringMap.at(key); return stringMap.at(key);
} }
bool Settings::GetBool(const std::string& key) { bool Settings::GetBool(const std::string &key) {
return boolMap.at(key); return boolMap.at(key);
} }
void Settings::List(std::shared_ptr<Logger> logger) { int Settings::GetInt(const std::string &key) {
for (auto& iter : stringMap) return intMap.at(key);
logger->Write(Logger::Info, "Key: {}, Value: {}, Type: String", iter.first, GetString(iter.first));
for (auto& iter : boolMap)
logger->Write(Logger::Info, "Key: {}, Value: {}, Type: Bool", iter.first, GetBool(iter.first));
} }
Logger::Logger(const std::string &logPath) { void Settings::List(const std::shared_ptr<Logger> &logger) {
for (auto &iter : stringMap)
logger->Info("Key: {}, Value: {}, Type: String", iter.first, GetString(iter.first));
for (auto &iter : boolMap)
logger->Info("Key: {}, Value: {}, Type: Bool", iter.first, GetBool(iter.first));
}
Logger::Logger(const std::string &logPath, LogLevel configLevel) : configLevel(configLevel) {
logFile.open(logPath, std::ios::app); logFile.open(logPath, std::ios::app);
WriteHeader("Logging started"); WriteHeader("Logging started");
} }
@ -55,12 +66,18 @@ namespace skyline {
logFile.flush(); logFile.flush();
} }
void Logger::Write(const LogLevel level, const std::string& str) { void Logger::Write(const LogLevel level, std::string str) {
#ifdef NDEBUG syslog(levelSyslog[static_cast<u8>(level)], "%s", str.c_str());
if (level == Debug) return; for (auto &character : str)
#endif if (character == '\n')
syslog(levelSyslog[level], "%s", str.c_str()); character = '\\';
logFile << "1|" << levelStr[level] << "|" << str << "\n"; logFile << "1|" << levelStr[static_cast<u8>(level)] << "|" << str << "\n";
logFile.flush(); logFile.flush();
} }
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &thisProcess, std::shared_ptr<kernel::type::KThread> &thisThread, ANativeWindow *window, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger) : os(os), settings(std::move(settings)), logger(std::move(logger)), thisProcess(thisProcess), thisThread(thisThread) {
// We assign these later as they may use the state in their constructor and we don't want null pointers
nce = std::move(std::make_shared<NCE>(*this));
gpu = std::move(std::make_shared<gpu::GPU>(*this, window));
}
} }

View File

@ -14,6 +14,7 @@
#include <cstdint> #include <cstdint>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <android/native_window.h>
namespace skyline { namespace skyline {
// Global typedefs // Global typedefs
@ -27,13 +28,13 @@ namespace skyline {
typedef __int32_t i32; typedef __int32_t i32;
typedef __int16_t i16; typedef __int16_t i16;
typedef __int8_t i8; typedef __int8_t i8;
typedef std::runtime_error exception; //!< This is used as the default exception
typedef u32 handle_t; //!< The type of an handle typedef u32 handle_t; //!< The type of an handle
namespace constant { namespace constant {
// Memory // Memory
constexpr u64 BaseAddr = 0x8000000; //!< The address space base constexpr u64 BaseAddr = 0x8000000; //!< The address space base
constexpr u64 MapAddr = BaseAddr + 0x80000000; //!< The address of the map region constexpr u64 MapAddr = BaseAddr + 0x80000000; //!< The address of the map region
constexpr u64 HeapAddr = MapAddr + 0x1000000000; //!< The address of the heap region
constexpr u64 BaseSize = 0x7FF8000000; //!< The size of the address space constexpr u64 BaseSize = 0x7FF8000000; //!< The size of the address space
constexpr u64 BaseEnd = BaseAddr + BaseSize; //!< The end of the address space constexpr u64 BaseEnd = BaseAddr + BaseSize; //!< The end of the address space
constexpr u64 MapSize = 0x1000000000; //!< The size of the map region constexpr u64 MapSize = 0x1000000000; //!< The size of the map region
@ -54,17 +55,24 @@ namespace skyline {
// Kernel // Kernel
constexpr u64 MaxSyncHandles = 0x40; //!< The total amount of handles that can be passed to WaitSynchronization constexpr u64 MaxSyncHandles = 0x40; //!< The total amount of handles that can be passed to WaitSynchronization
constexpr handle_t BaseHandleIndex = 0xD000; // The index of the base handle constexpr handle_t BaseHandleIndex = 0xD000; // The index of the base handle
constexpr handle_t ThreadSelf = 0xFFFF8000; //!< This is the handle used by threads to refer to themselves
constexpr u8 DefaultPriority = 31; //!< The default priority of a process constexpr u8 DefaultPriority = 31; //!< The default priority of a process
constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1 constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch
constexpr u32 mtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex constexpr u32 MtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex
// IPC // IPC
constexpr size_t TlsIpcSize = 0x100; //!< The size of the IPC command buffer in a TLS slot constexpr size_t TlsIpcSize = 0x100; //!< The size of the IPC command buffer in a TLS slot
constexpr u8 PortSize = 0x8; //!< The size of a port name string constexpr u8 PortSize = 0x8; //!< The size of a port name string
constexpr u32 SfcoMagic = 0x4F434653; //!< SFCO in reverse, written to IPC messages constexpr u32 SfcoMagic = 0x4F434653; //!< SFCO in reverse, written to IPC messages
constexpr u32 SfciMagic = 0x49434653; //!< SFCI in reverse, present in received IPC messages constexpr u32 SfciMagic = 0x49434653; //!< SFCI in reverse, present in received IPC messages
constexpr u64 PaddingSum = 0x10; //!< The sum of the padding surrounding DataPayload constexpr u64 IpcPaddingSum = 0x10; //!< The sum of the padding surrounding DataPayload
constexpr handle_t BaseVirtualHandleIndex = 0x1; // The index of the base virtual handle constexpr handle_t BaseVirtualHandleIndex = 0x1; // The index of the base virtual handle
// GPU
constexpr u32 HandheldResolutionW = 1280; //!< The width component of the handheld resolution
constexpr u32 HandheldResolutionH = 720; //!< The height component of the handheld resolution
constexpr u32 DockedResolutionW = 1920; //!< The width component of the docked resolution
constexpr u32 DockedResolutionH = 1080; //!< The height component of the docked resolution
constexpr u32 TokenLength = 0x50; //!< The length of the token on BufferQueue parcels
// Status codes // Status codes
namespace status { namespace status {
constexpr u32 Success = 0x0; //!< "Success" constexpr u32 Success = 0x0; //!< "Success"
@ -75,6 +83,7 @@ namespace skyline {
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles" constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
constexpr u32 Timeout = 0xEA01; //!< "Timeout while svcWaitSynchronization" constexpr u32 Timeout = 0xEA01; //!< "Timeout while svcWaitSynchronization"
constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour" constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour"
constexpr u32 NoMessages = 0x680; //!< "No message available"
} }
}; };
@ -175,12 +184,14 @@ namespace skyline {
static constexpr int levelSyslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG}; //!< This corresponds to LogLevel and provides it's equivalent for syslog static constexpr int levelSyslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG}; //!< This corresponds to LogLevel and provides it's equivalent for syslog
public: public:
enum LogLevel { Error, Warn, Info, Debug }; //!< The level of a particular log enum class LogLevel { Error, Warn, Info, Debug }; //!< The level of a particular log
LogLevel configLevel; //!< The level of logs to write
/** /**
* @param logPath The path to the log file * @param logPath The path to the log file
* @param configLevel The minimum level of logs to write
*/ */
Logger(const std::string &logPath); Logger(const std::string &logPath, LogLevel configLevel);
/** /**
* Writes "Logging ended" as a header * Writes "Logging ended" as a header
@ -198,20 +209,54 @@ namespace skyline {
* @param level The level of the log * @param level The level of the log
* @param str The value to be written * @param str The value to be written
*/ */
void Write(const LogLevel level, const std::string &str); void Write(const LogLevel level, std::string str);
/** /**
* @brief Write a log to the log file with libfmt formatting * @brief Write an error log with libfmt formatting
* @param level The level of the log
* @param formatStr The value to be written, with libfmt formatting * @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str * @param args The arguments based on format_str
*/ */
template<typename S, typename... Args> template<typename S, typename... Args>
void Write(Logger::LogLevel level, const S &formatStr, Args &&... args) { inline void Error(const S &formatStr, Args &&... args) {
#ifdef NDEBUG if (LogLevel::Error <= configLevel) {
if (level == Debug) return; Write(LogLevel::Error, fmt::format(formatStr, args...));
#endif }
Write(level, fmt::format(formatStr, args...)); }
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Warn(const S &formatStr, Args &&... args) {
if (LogLevel::Warn <= configLevel) {
Write(LogLevel::Warn, fmt::format(formatStr, args...));
}
}
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Info(const S &formatStr, Args &&... args) {
if (LogLevel::Info <= configLevel) {
Write(LogLevel::Info, fmt::format(formatStr, args...));
}
}
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Debug(const S &formatStr, Args &&... args) {
if (LogLevel::Debug <= configLevel) {
Write(LogLevel::Debug, fmt::format(formatStr, args...));
}
} }
}; };
@ -222,6 +267,7 @@ namespace skyline {
private: private:
std::map<std::string, std::string> stringMap; //!< A mapping from all keys to their corresponding string value std::map<std::string, std::string> stringMap; //!< A mapping from all keys to their corresponding string value
std::map<std::string, bool> boolMap; //!< A mapping from all keys to their corresponding boolean value std::map<std::string, bool> boolMap; //!< A mapping from all keys to their corresponding boolean value
std::map<std::string, int> intMap; //!< A mapping from all keys to their corresponding integer value
public: public:
/** /**
@ -243,22 +289,45 @@ namespace skyline {
*/ */
bool GetBool(const std::string &key); bool GetBool(const std::string &key);
/**
* @brief Retrieves a particular setting as a integer
* @param key The key of the setting
* @return The integer value of the setting
*/
int GetInt(const std::string &key);
/** /**
* @brief Writes all settings keys and values to syslog. This function is for development purposes. * @brief Writes all settings keys and values to syslog. This function is for development purposes.
*/ */
void List(std::shared_ptr<Logger> logger); void List(const std::shared_ptr<Logger> &logger);
};
/**
* @brief This is a std::runtime_error with libfmt formatting
*/
class exception : public std::runtime_error {
public:
/**
* @param formatStr The exception string to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline exception(const S &formatStr, Args &&... args) : runtime_error(fmt::format(formatStr, args...)) {}
}; };
/** /**
* @brief Returns the current time in nanoseconds * @brief Returns the current time in nanoseconds
* @return The current time in nanoseconds * @return The current time in nanoseconds
*/ */
inline long long int GetCurrTimeNs() { inline u64 GetCurrTimeNs() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); return static_cast<u64>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
} }
// Predeclare some classes here as we use them in DeviceState // Predeclare some classes here as we use them in DeviceState
class NCE; class NCE;
namespace gpu {
class GPU;
}
namespace kernel { namespace kernel {
namespace type { namespace type {
class KProcess; class KProcess;
@ -271,12 +340,13 @@ namespace skyline {
* @brief This struct is used to hold the state of a device * @brief This struct is used to hold the state of a device
*/ */
struct DeviceState { struct DeviceState {
DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &thisProcess, std::shared_ptr<kernel::type::KThread> &thisThread, std::shared_ptr<NCE> nce, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger) : os(os), nce(nce), settings(settings), logger(logger), thisProcess(thisProcess), thisThread(thisThread) {} DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &thisProcess, std::shared_ptr<kernel::type::KThread> &thisThread, ANativeWindow *window, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger);
kernel::OS *os; //!< This holds a reference to the OS class kernel::OS *os; //!< This holds a reference to the OS class
std::shared_ptr<kernel::type::KProcess> &thisProcess; //!< This holds a reference to the current process object std::shared_ptr<kernel::type::KProcess> &thisProcess; //!< This holds a reference to the current process object
std::shared_ptr<kernel::type::KThread> &thisThread; //!< This holds a reference to the current thread object std::shared_ptr<kernel::type::KThread> &thisThread; //!< This holds a reference to the current thread object
std::shared_ptr<NCE> nce; //!< This holds a reference to the NCE class std::shared_ptr<NCE> nce; //!< This holds a reference to the NCE class
std::shared_ptr<gpu::GPU> gpu; //!< This holds a reference to the GPU class
std::shared_ptr<Settings> settings; //!< This holds a reference to the Settings class std::shared_ptr<Settings> settings; //!< This holds a reference to the Settings class
std::shared_ptr<Logger> logger; //!< This holds a reference to the Logger class std::shared_ptr<Logger> logger; //!< This holds a reference to the Logger class
}; };

View File

@ -0,0 +1,163 @@
#include "gpu.h"
#include "gpu/devices/nvhost_ctrl.h"
#include "gpu/devices/nvhost_ctrl_gpu.h"
#include "gpu/devices/nvhost_channel.h"
#include "gpu/devices/nvhost_as_gpu.h"
#include <kernel/types/KProcess.h>
extern bool Halt;
namespace skyline::gpu {
GPU::GPU(const DeviceState &state, ANativeWindow *window) : state(state), window(window), bufferQueue(state), vsyncEvent(std::make_shared<kernel::type::KEvent>(state)), bufferEvent(std::make_shared<kernel::type::KEvent>(state)) {
ANativeWindow_acquire(window);
resolution.width = static_cast<u32>(ANativeWindow_getWidth(window));
resolution.height = static_cast<u32>(ANativeWindow_getHeight(window));
}
GPU::~GPU() {
ANativeWindow_release(window);
}
void GPU::Loop() {
if (!bufferQueue.displayQueue.empty()) {
auto &buffer = bufferQueue.displayQueue.front();
bufferQueue.displayQueue.pop();
if (resolution != buffer->resolution || buffer->gbpBuffer.format != format) {
if (resolution != buffer->resolution && buffer->gbpBuffer.format != format) {
ANativeWindow_setBuffersGeometry(window, buffer->resolution.width, buffer->resolution.height, buffer->gbpBuffer.format);
resolution = buffer->resolution;
format = buffer->gbpBuffer.format;
} else if (resolution != buffer->resolution) {
ANativeWindow_setBuffersGeometry(window, buffer->resolution.width, buffer->resolution.height, format);
resolution = buffer->resolution;
} else if (buffer->gbpBuffer.format != format) {
ANativeWindow_setBuffersGeometry(window, resolution.width, resolution.height, buffer->gbpBuffer.format);
format = buffer->gbpBuffer.format;
}
}
buffer->UpdateBuffer();
auto bufferData = buffer->dataBuffer.data();
//madvise(bufferData, buffer->gbpBuffer.size, MADV_SEQUENTIAL); (Uncomment this after deswizzling while reading sequentially instead of writing sequentially)
ANativeWindow_Buffer windowBuffer;
ARect rect;
ANativeWindow_lock(window, &windowBuffer, &rect);
u32 *address = reinterpret_cast<u32 *>(windowBuffer.bits);
for (u32 y = 0; y < buffer->resolution.height; y++) {
for (u32 x = 0; x < buffer->resolution.width; x += 4, address += 4) {
u32 position = (y & 0x7f) >> 4U;
position += (x >> 4U) << 3U;
position += (y >> 7U) * ((resolution.width >> 4U) << 3U);
position *= 1024;
position += ((y & 0xf) >> 3U) << 9U;
position += ((x & 0xf) >> 3U) << 8U;
position += ((y & 0x7) >> 1U) << 6U;
position += ((x & 0x7) >> 2U) << 5U;
position += (y & 0x1) << 4U;
position += (x & 0x3) << 2U;
std::memcpy(address, bufferData + position, sizeof(u32) * 4);
}
}
ANativeWindow_unlockAndPost(window);
bufferQueue.FreeBuffer(buffer->slot);
vsyncEvent->Signal();
if (prevTime != 0) {
auto now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
state.logger->Error("{} ms, {} FPS", (now - prevTime) / 1000, 1000000 / (now - prevTime));
}
prevTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}
}
u32 GPU::OpenDevice(const std::string &path) {
state.logger->Debug("Opening NVDRV device ({}): {}", fdIndex, path);
auto type = device::nvDeviceMap.at(path);
for (const auto &device : fdMap) {
if (device.second->deviceType == type) {
device.second->refCount++;
fdMap[fdIndex] = device.second;
return fdIndex++;
}
}
std::shared_ptr<device::NvDevice> object;
switch (type) {
case (device::NvDeviceType::nvhost_ctrl):
object = std::make_shared<device::NvHostCtrl>(state);
break;
case (device::NvDeviceType::nvhost_gpu):
case (device::NvDeviceType::nvhost_vic):
case (device::NvDeviceType::nvhost_nvdec):
object = std::make_shared<device::NvHostChannel>(state, type);
break;
case (device::NvDeviceType::nvhost_ctrl_gpu):
object = std::make_shared<device::NvHostCtrlGpu>(state);
break;
case (device::NvDeviceType::nvmap):
object = std::make_shared<device::NvMap>(state);
break;
case (device::NvDeviceType::nvhost_as_gpu):
object = std::make_shared<device::NvHostAsGpu>(state);
break;
default:
throw exception("Cannot find NVDRV device");
}
deviceMap[type] = object;
fdMap[fdIndex] = object;
return fdIndex++;
}
void GPU::CloseDevice(u32 fd) {
state.logger->Debug("Closing NVDRV device ({})", fd);
try {
auto device = fdMap.at(fd);
if (!--device->refCount)
deviceMap.erase(device->deviceType);
fdMap.erase(fd);
} catch (const std::out_of_range &) {
state.logger->Warn("Trying to close non-existent FD");
}
}
void GPU::Ioctl(u32 fd, u32 cmd, kernel::ipc::IpcRequest &request, kernel::ipc::IpcResponse &response) {
state.logger->Debug("IOCTL on device: 0x{:X}, cmd: 0x{:X}", fd, cmd);
try {
if (!request.vecBufA.empty() && !request.vecBufB.empty()) {
device::IoctlBuffers input(device::InputBuffer(request.vecBufA[0]), device::OutputBuffer(request.vecBufB[0]));
fdMap.at(fd)->HandleIoctl(cmd, input);
response.WriteValue<u32>(input.status);
} else if (!request.vecBufX.empty() && !request.vecBufC.empty()) {
device::IoctlBuffers input(device::InputBuffer(request.vecBufX[0]), device::OutputBuffer(request.vecBufC[0]));
fdMap.at(fd)->HandleIoctl(cmd, input);
response.WriteValue<u32>(input.status);
} else if (!request.vecBufX.empty()) {
device::IoctlBuffers input(device::InputBuffer(request.vecBufX[0]));
fdMap.at(fd)->HandleIoctl(cmd, input);
response.WriteValue<u32>(input.status);
} else if (!request.vecBufC.empty()) {
device::IoctlBuffers input(device::OutputBuffer(request.vecBufC[0]));
fdMap.at(fd)->HandleIoctl(cmd, input);
response.WriteValue<u32>(input.status);
} else
throw exception("Unknown IOCTL buffer configuration");
} catch (const std::out_of_range &) {
throw exception("IOCTL was requested on an invalid file descriptor");
}
}
void GPU::SetDisplay(const std::string &name) {
try {
const auto type = displayTypeMap.at(name);
if (displayId == DisplayId::Null)
displayId = type;
else
throw exception("Trying to change display type from non-null type");
} catch (const std::out_of_range &) {
throw exception("The display with name: '{}' doesn't exist", name);
}
}
void GPU::CloseDisplay() {
if (displayId == DisplayId::Null)
state.logger->Warn("Trying to close uninitiated display");
displayId = DisplayId::Null;
}
}

View File

@ -0,0 +1,112 @@
#pragma once
#include "kernel/ipc.h"
#include "kernel/types/KEvent.h"
#include "gpu/display.h"
#include "gpu/devices/nvdevice.h"
namespace skyline::gpu {
/**
* @brief This is used to converge all of the interfaces to the GPU and send the results to a GPU API
* @note We opted for just supporting a single layer and display as it's what basically all games use and wasting cycles on it is pointless
*/
class GPU {
private:
ANativeWindow *window; //!< The ANativeWindow to render to
const DeviceState &state; //!< The state of the device
u32 fdIndex{}; //!< Holds the index of a file descriptor
std::unordered_map<device::NvDeviceType, std::shared_ptr<device::NvDevice>> deviceMap; //!< A map from a NvDeviceType to the NvDevice object
std::unordered_map<u32, std::shared_ptr<device::NvDevice>> fdMap; //!< A map from an FD to a shared pointer to it's NvDevice object
double prevTime{};
public:
DisplayId displayId{DisplayId::Null}; //!< The ID of this display
LayerStatus layerStatus{LayerStatus::Uninitialized}; //!< This is the status of the single layer the display has
BufferQueue bufferQueue; //!< This holds all of the buffers to be pushed onto the display
Resolution resolution{}; //!< The resolution of the display window
i32 format{}; //!< The format of the display window
std::shared_ptr<kernel::type::KEvent> vsyncEvent; //!< This KEvent is triggered every time a frame is drawn
std::shared_ptr<kernel::type::KEvent> bufferEvent; //!< This KEvent is triggered every time a buffer is freed
/**
* @param window The ANativeWindow to render to
*/
GPU(const DeviceState &state, ANativeWindow *window);
/**
* @brief The destructor for the GPU class
*/
~GPU();
/**
* @brief The loop that executes routine GPU functions
*/
void Loop();
/**
* @brief Open a specific device and return a FD
* @param path The path of the device to open an FD to
* @return The file descriptor to the device
*/
u32 OpenDevice(const std::string &path);
/**
* @brief Close the specified FD
* @param fd The file descriptor to close
*/
void CloseDevice(u32 fd);
/**
* @brief Returns a particular device with a specific FD
* @tparam objectClass The class of the device to return
* @param fd The file descriptor to retrieve
* @return A shared pointer to the device
*/
template<typename objectClass>
std::shared_ptr<objectClass> GetDevice(u32 fd) {
try {
auto item = fdMap.at(fd);
return std::static_pointer_cast<objectClass>(item);
} catch (std::out_of_range) {
throw exception("GetDevice was called with invalid file descriptor: 0x{:X}", fd);
}
}
/**
* @brief Returns a particular device with a specific type
* @tparam objectClass The class of the device to return
* @param type The type of the device to return
* @return A shared pointer to the device
*/
template<typename objectClass>
std::shared_ptr<objectClass> GetDevice(device::NvDeviceType type) {
try {
auto item = deviceMap.at(type);
return std::static_pointer_cast<objectClass>(item);
} catch (std::out_of_range) {
throw exception("GetDevice was called with invalid type: 0x{:X}", type);
}
}
/**
* @brief Process an Ioctl request to a device
* @param fd The file descriptor the Ioctl was issued to
* @param cmd The command of the Ioctl
* @param request The IPC request object
* @param response The IPC response object
*/
void Ioctl(u32 fd, u32 cmd, kernel::ipc::IpcRequest &request, kernel::ipc::IpcResponse &response);
/**
* @brief This sets displayId to a specific display type
* @param name The name of the display
* @note displayId has to be DisplayId::Null or this will throw an exception
*/
void SetDisplay(const std::string &name);
/**
* @brief This closes the display by setting displayId to DisplayId::Null
*/
void CloseDisplay();
};
}

View File

@ -0,0 +1,227 @@
#pragma once
#include <common.h>
#include <kernel/ipc.h>
#define NFUNC(function) std::bind(&function, this, std::placeholders::_1)
namespace skyline::gpu::device {
/**
* @brief An enumeration of all the devices that can be opened by nvdrv
*/
enum class NvDeviceType {
nvhost_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl
nvhost_gpu, //!< https://switchbrew.org/wiki/NV_services#Channels
nvhost_nvdec, //!< https://switchbrew.org/wiki/NV_services#Channels
nvhost_vic, //!< https://switchbrew.org/wiki/NV_services#Channels
nvmap, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvmap
nvdisp_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-ctrl
nvdisp_disp0, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-disp0.2C_.2Fdev.2Fnvdisp-disp1
nvdisp_disp1, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-disp0.2C_.2Fdev.2Fnvdisp-disp1
nvcec_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvcec-ctrl
nvhdcp_up_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhdcp_up-ctrl
nvdcutil_disp0, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdcutil-disp0.2C_.2Fdev.2Fnvdcutil-disp1
nvdcutil_disp1, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdcutil-disp0.2C_.2Fdev.2Fnvdcutil-disp1
nvsched_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvsched-ctrl
nverpt_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnverpt-ctrl
nvhost_as_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu
nvhost_dbg_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-dbg-gpu
nvhost_prof_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-prof-gpu
nvhost_ctrl_gpu //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu
};
/**
* @brief A mapping from a device's path to it's nvDevice entry
*/
const static std::unordered_map<std::string, NvDeviceType> nvDeviceMap{
{"/dev/nvhost-ctrl", NvDeviceType::nvhost_ctrl},
{"/dev/nvhost-gpu", NvDeviceType::nvhost_gpu},
{"/dev/nvhost-nvdec", NvDeviceType::nvhost_nvdec},
{"/dev/nvhost-vic", NvDeviceType::nvhost_vic},
{"/dev/nvmap", NvDeviceType::nvmap},
{"/dev/nvdisp-ctrl", NvDeviceType::nvdisp_ctrl},
{"/dev/nvdisp-disp0", NvDeviceType::nvdisp_disp0},
{"/dev/nvdisp-disp1", NvDeviceType::nvdisp_disp1},
{"/dev/nvcec-ctrl", NvDeviceType::nvcec_ctrl},
{"/dev/nvhdcp_up-ctrl", NvDeviceType::nvhdcp_up_ctrl},
{"/dev/nvdcutil-disp0", NvDeviceType::nvdcutil_disp0},
{"/dev/nvdcutil-disp1", NvDeviceType::nvdcutil_disp1},
{"/dev/nvsched-ctrl", NvDeviceType::nvsched_ctrl},
{"/dev/nverpt-ctrl", NvDeviceType::nverpt_ctrl},
{"/dev/nvhost-as-gpu", NvDeviceType::nvhost_as_gpu},
{"/dev/nvhost-dbg-gpu", NvDeviceType::nvhost_dbg_gpu},
{"/dev/nvhost-prof-gpu", NvDeviceType::nvhost_prof_gpu},
{"/dev/nvhost-ctrl-gpu", NvDeviceType::nvhost_ctrl_gpu}
};
/**
* @brief Describes a buffer by holding the address and size
*/
struct IoctlBuffer {
u64 address; //!< The address of the buffer
size_t size; //!< The size of the buffer
/**
* @param address The address of the buffer
* @param size The size of the buffer
*/
IoctlBuffer(u64 address, size_t size) : address(address), size(size) {}
};
/**
* @brief Wrapper around IoctlBuffer that loads in the address from a A Buffer Descriptor
*/
struct InputBuffer : public IoctlBuffer {
/**
* @param aBuf The A Buffer Descriptor that has contains the input data
*/
InputBuffer(kernel::ipc::BufferDescriptorABW *aBuf) : IoctlBuffer(aBuf->Address(), aBuf->Size()) {}
/**
* @param aBuf The X Buffer Descriptor that has contains the input data
*/
InputBuffer(kernel::ipc::BufferDescriptorX *xBuf) : IoctlBuffer(xBuf->Address(), xBuf->size) {}
};
/**
* @brief Wrapper around IoctlBuffer that loads in the address from a B Buffer Descriptor
*/
struct OutputBuffer : public IoctlBuffer {
/**
* @param aBuf The B Buffer Descriptor that has to be outputted to
*/
OutputBuffer(kernel::ipc::BufferDescriptorABW *bBuf) : IoctlBuffer(bBuf->Address(), bBuf->Size()) {}
/**
* @param xBuf The C Buffer Descriptor that has to be outputted to
*/
OutputBuffer(kernel::ipc::BufferDescriptorC *cBuf) : IoctlBuffer(cBuf->address, cBuf->size) {}
};
/**
* @brief This enumerates all the possible error codes returned by the Nvidia driver (https://switchbrew.org/wiki/NV_services#Errors)
*/
enum NvStatus : u32 {
Success = 0x0, //!< The operation has succeeded
NotImplemented = 0x1, //!< The operation is not implemented
NotSupported = 0x2, //!< The operation is not supported
NotInitialized = 0x3, //!< The operation uses an uninitialized object
BadParameter = 0x4, //!< The operation was provided a bad parameter
Timeout = 0x5, //!< The operation has timed out
InsufficientMemory = 0x6, //!< The device ran out of memory during the operation
ReadOnlyAttribute = 0x7, //!< The mutating operation was performed on a read only section
InvalidState = 0x8, //!< The state of the device was invalid
InvalidAddress = 0x9, //!< The provided address is invalid
InvalidSize = 0xA, //!< The provided size is invalid
BadValue = 0xB, //!< The operation was provided a bad value
AlreadyAllocated = 0xD, //!< An object was tried to be reallocated
Busy = 0xE, //!< The device is busy
ResourceError = 0xF, //!< There was an error accessing the resource
CountMismatch = 0x10, //!< ?
SharedMemoryTooSmall = 0x1000, //!< The shared memory segment is too small
FileOperationFailed = 0x30003, //!< The file operation has failed
DirOperationFailed = 0x30004, //!< The directory operation has failed
IoctlFailed = 0x3000F, //!< The IOCTL operation has failed
AccessDenied = 0x30010, //!< The access to a resource was denied
FileNotFound = 0x30013, //!< A file was not found
ModuleNotPresent = 0xA000E, //!< A module was not present
};
/**
* @brief This holds all the IoctlBuffer objects in a coherent container
*/
struct IoctlBuffers {
std::vector<InputBuffer> input; //!< A vector of all input IOCTL buffers
std::vector<OutputBuffer> output; //!< A vector of all output IOCTL buffers
NvStatus status{NvStatus::Success}; //!< The error code that is returned to the application
/**
* @brief This constructor takes 1 input buffer and 1 output buffer, it's used for Ioctl
* @param input An input buffer
* @param output An output buffer
*/
IoctlBuffers(InputBuffer input, OutputBuffer output) : input({input}), output({output}) {}
/**
* @brief This constructor takes 1 input buffer, it's used for Ioctl sometimes
* @param output An output buffer
*/
IoctlBuffers(InputBuffer input) : input({input}) {}
/**
* @brief This constructor takes 1 output buffer, it's used for Ioctl sometimes
* @param output An output buffer
*/
IoctlBuffers(OutputBuffer output) : output({output}) {}
/**
* @brief This constructor takes 2 input buffers and 1 output buffer, it's used for Ioctl1
* @param input1 The first input buffer
* @param input2 The second input buffer
* @param output An output buffer
*/
IoctlBuffers(InputBuffer input1, InputBuffer input2, OutputBuffer output) : input({input1, input2}), output({output}) {}
/**
* @brief This constructor takes 1 input buffer and 2 output buffers, it's used for Ioctl2
* @param input An input buffer
* @param output1 The first output buffer
* @param output2 The second output buffer
*/
IoctlBuffers(InputBuffer input, OutputBuffer output1, OutputBuffer output2) : input({input}), output({output1, output2}) {}
};
/**
* @brief NvDevice is the base class all /dev/nv* devices inherit from
*/
class NvDevice {
protected:
const DeviceState &state; //!< The state of the device
std::unordered_map<u32, std::function<void(IoctlBuffers &)>> vTable; //!< This holds the mapping from an Ioctl to the actual function
public:
u16 refCount{1}; //!< The amount of handles to the device
NvDeviceType deviceType; //!< The type of the device
/**
* @param state The state of the device
* @param deviceType The type of the device
* @param vTable The functions in this device
*/
NvDevice(const DeviceState &state, NvDeviceType deviceType, std::unordered_map<u32, std::function<void(IoctlBuffers &)>> vTable) : state(state), deviceType(deviceType), vTable(vTable) {}
/**
* @brief This returns the name of the current service
* @note It may not return the exact name the service was initialized with if there are multiple entries in ServiceString
* @return The name of the service
*/
std::string getName() {
std::string serviceName;
for (const auto&[name, type] : nvDeviceMap)
if (type == deviceType)
serviceName = name;
return serviceName;
}
/**
* @brief This handles IOCTL calls for devices
* @param cmd The IOCTL command that was called
* @param input The input to the IOCTL call
*/
void HandleIoctl(u32 cmd, IoctlBuffers &input) {
std::function<void(IoctlBuffers &)> function;
try {
function = vTable.at(cmd);
} catch (std::out_of_range &) {
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", getName(), deviceType, cmd);
input.status = NvStatus::NotImplemented;
return;
}
try {
function(input);
} catch (std::exception &e) {
throw exception("{} (Device: {})", e.what(), getName());
}
}
};
}

View File

@ -0,0 +1,5 @@
#include "nvhost_as_gpu.h"
namespace skyline::gpu::device {
NvHostAsGpu::NvHostAsGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_as_gpu, {}) {}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostAsGpu (/dev/nvhost-as-gpu) is used to access GPU virtual address spaces (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu)
*/
class NvHostAsGpu : public NvDevice {
public:
NvHostAsGpu(const DeviceState &state);
};
}

View File

@ -0,0 +1,42 @@
#include "nvhost_channel.h"
#include <kernel/types/KProcess.h>
namespace skyline::gpu::device {
NvHostChannel::NvHostChannel(const DeviceState &state, NvDeviceType type) : NvDevice(state, type, {
{0x40044801, NFUNC(NvHostChannel::SetNvmapFd)},
{0xC0104809, NFUNC(NvHostChannel::AllocObjCtx)},
{0xC010480B, NFUNC(NvHostChannel::ZcullBind)},
{0xC018480C, NFUNC(NvHostChannel::SetErrorNotifier)},
{0x4004480D, NFUNC(NvHostChannel::SetPriority)},
{0xC020481A, NFUNC(NvHostChannel::AllocGpfifoEx2)},
{0x40084714, NFUNC(NvHostChannel::SetUserData)}
}) {}
void NvHostChannel::SetNvmapFd(skyline::gpu::device::IoctlBuffers &buffer) {}
void NvHostChannel::AllocObjCtx(skyline::gpu::device::IoctlBuffers &buffer) {}
void NvHostChannel::ZcullBind(IoctlBuffers &buffer) {}
void NvHostChannel::SetErrorNotifier(skyline::gpu::device::IoctlBuffers &buffer) {}
void NvHostChannel::SetPriority(skyline::gpu::device::IoctlBuffers &buffer) {
auto priority = state.thisProcess->ReadMemory<NvChannelPriority>(buffer.input[0].address);
switch (priority) {
case NvChannelPriority::Low:
timeslice = 1300;
break;
case NvChannelPriority::Medium:
timeslice = 2600;
break;
case NvChannelPriority::High:
timeslice = 5200;
break;
}
}
void NvHostChannel::AllocGpfifoEx2(skyline::gpu::device::IoctlBuffers &buffer) {}
void NvHostChannel::SetUserData(skyline::gpu::device::IoctlBuffers &buffer) {}
}

View File

@ -0,0 +1,57 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostChannel is used as a common interface for all Channel devices (https://switchbrew.org/wiki/NV_services#Channels)
*/
class NvHostChannel : public NvDevice {
private:
enum class NvChannelPriority : u32 {
Low = 0x32,
Medium = 0x64,
High = 0x94
};
u32 timeslice{};
public:
NvHostChannel(const DeviceState &state, NvDeviceType type);
/**
* @brief This sets the nvmap file descriptor (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_NVMAP_FD)
*/
void SetNvmapFd(IoctlBuffers &buffer);
/**
* @brief This allocates a graphic context object (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_OBJ_CTX)
*/
void AllocObjCtx(IoctlBuffers &buffer);
/**
* @brief This initializes the error notifier for this channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ZCULL_BIND)
*/
void ZcullBind(IoctlBuffers &buffer);
/**
* @brief This initializes the error notifier for this channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_ERROR_NOTIFIER)
*/
void SetErrorNotifier(IoctlBuffers &buffer);
/**
* @brief This sets the priority of the channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_PRIORITY)
*/
void SetPriority(IoctlBuffers &buffer);
/**
* @brief This allocates a GPFIFO entry (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_GPFIFO_EX2)
*/
void AllocGpfifoEx2(IoctlBuffers &buffer);
/**
* @brief This sets the user specific data (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA)
*/
void SetUserData(IoctlBuffers &buffer);
};
}

View File

@ -0,0 +1,5 @@
#include "nvhost_ctrl.h"
namespace skyline::gpu::device {
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl, {}) {}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl)
*/
class NvHostCtrl : public NvDevice {
public:
NvHostCtrl(const DeviceState &state);
};
}

View File

@ -0,0 +1,138 @@
#include "nvhost_ctrl_gpu.h"
#include <kernel/types/KProcess.h>
namespace skyline::gpu::device {
NvHostCtrlGpu::NvHostCtrlGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl_gpu, {
{0x80044701, NFUNC(NvHostCtrlGpu::ZCullGetCtxSize)},
{0x80284702, NFUNC(NvHostCtrlGpu::ZCullGetInfo)},
{0xC0184706, NFUNC(NvHostCtrlGpu::GetTpcMasks)},
{0xC0B04705, NFUNC(NvHostCtrlGpu::GetCharacteristics)},
{0x80084714, NFUNC(NvHostCtrlGpu::GetActiveSlotMask)}
}) {}
void NvHostCtrlGpu::ZCullGetCtxSize(IoctlBuffers &buffer) {
u32 size = 0x1;
state.thisProcess->WriteMemory(size, buffer.output[0].address);
}
void NvHostCtrlGpu::ZCullGetInfo(skyline::gpu::device::IoctlBuffers &buffer) {
struct {
u32 widthAlignPixels{0x20};
u32 heightAlignPixels{0x20};
u32 pixelSquaresByAliquots{0x400};
u32 aliquotTotal{0x800};
u32 regionByteMultiplier{0x20};
u32 regionHeaderSize{0x20};
u32 subregionHeaderSize{0xC0};
u32 subregionWidthAlignPixels{0x20};
u32 subregionHeightAlignPixels{0x40};
u32 subregionCount{0x10};
} zCullInfo;
state.thisProcess->WriteMemory(zCullInfo, buffer.output[0].address);
}
void NvHostCtrlGpu::GetCharacteristics(IoctlBuffers &buffer) {
struct GpuCharacteristics {
u32 arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
u32 impl; // 0xB (NVGPU_GPU_IMPL_GM20B) or 0xE (NVGPU_GPU_IMPL_GM20B_B)
u32 rev; // 0xA1 (Revision A1)
u32 numGpc; // 0x1
u64 l2CacheSize; // 0x40000
u64 onBoardVideoMemorySize; // 0x0 (not used)
u32 numTpcPerGpc; // 0x2
u32 busType; // 0x20 (NVGPU_GPU_BUS_TYPE_AXI)
u32 bigPageSize; // 0x20000
u32 compressionPageSize; // 0x20000
u32 pdeCoverageBitCount; // 0x1B
u32 availableBigPageSizes; // 0x30000
u32 gpcMask; // 0x1
u32 smArchSmVersion; // 0x503 (Maxwell Generation 5.0.3)
u32 smArchSpaVersion; // 0x503 (Maxwell Generation 5.0.3)
u32 smArchWarpCount; // 0x80
u32 gpuVaBitCount; // 0x28
u32 reserved; // NULL
u64 flags; // 0x55 (HAS_SYNCPOINTS | SUPPORT_SPARSE_ALLOCS | SUPPORT_CYCLE_STATS | SUPPORT_CYCLE_STATS_SNAPSHOT)
u32 twodClass; // 0x902D (FERMI_TWOD_A)
u32 threedClass; // 0xB197 (MAXWELL_B)
u32 computeClass; // 0xB1C0 (MAXWELL_COMPUTE_B)
u32 gpfifoClass; // 0xB06F (MAXWELL_CHANNEL_GPFIFO_A)
u32 inlineToMemoryClass; // 0xA140 (KEPLER_INLINE_TO_MEMORY_B)
u32 dmaCopyClass; // 0xB0B5 (MAXWELL_DMA_COPY_A)
u32 maxFbpsCount; // 0x1
u32 fbpEnMask; // 0x0 (disabled)
u32 maxLtcPerFbp; // 0x2
u32 maxLtsPerLtc; // 0x1
u32 maxTexPerTpc; // 0x0 (not supported)
u32 maxGpcCount; // 0x1
u32 ropL2EnMask0; // 0x21D70 (fuse_status_opt_rop_l2_fbp_r)
u32 ropL2EnMask1; // 0x0
u64 chipName; // 0x6230326D67 ("gm20b")
u64 grCompbitStoreBaseHw; // 0x0 (not supported)
};
struct Data {
u64 gpuCharacteristicsBufSize; // InOut
u64 gpuCharacteristicsBufAddr; // In
GpuCharacteristics gpuCharacteristics; // Out
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
data.gpuCharacteristics = {
.arch = 0x120,
.impl = 0xB,
.rev = 0xA1,
.numGpc = 0x1,
.l2CacheSize = 0x40000,
.onBoardVideoMemorySize = 0x0,
.numTpcPerGpc = 0x2,
.busType = 0x20,
.bigPageSize = 0x20000,
.compressionPageSize = 0x20000,
.pdeCoverageBitCount = 0x1B,
.availableBigPageSizes = 0x30000,
.gpcMask = 0x1,
.smArchSmVersion = 0x503,
.smArchSpaVersion = 0x503,
.smArchWarpCount = 0x80,
.gpuVaBitCount = 0x2,
.flags = 0x55,
.twodClass = 0x902D,
.threedClass = 0xB197,
.computeClass = 0xB1C0,
.gpfifoClass = 0xB06F,
.inlineToMemoryClass = 0xA140,
.dmaCopyClass = 0xB0B5,
.maxFbpsCount = 0x1,
.fbpEnMask = 0x0,
.maxLtcPerFbp = 0x2,
.maxLtsPerLtc = 0x1,
.maxTexPerTpc = 0x0,
.maxGpcCount = 0x1,
.ropL2EnMask0 = 0x21D70,
.ropL2EnMask1 = 0x0,
.chipName = 0x6230326D67,
.grCompbitStoreBaseHw = 0x0
};
data.gpuCharacteristicsBufSize = 0xA0;
state.thisProcess->WriteMemory(data, buffer.output[0].address);
}
void NvHostCtrlGpu::GetTpcMasks(IoctlBuffers &buffer) {
struct Data {
u32 maskBufSize; // In
u32 reserved[3]; // In
u64 maskBuf; // Out
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
if (data.maskBufSize)
data.maskBuf = 0x3;
state.thisProcess->WriteMemory(data, buffer.output[0].address);
}
void NvHostCtrlGpu::GetActiveSlotMask(IoctlBuffers &buffer) {
struct Data {
u32 slot; // Out
u32 mask; // Out
} data = {
.slot = 0x07,
.mask = 0x01
};
state.thisProcess->WriteMemory(data, buffer.output[0].address);
}
}

View File

@ -0,0 +1,38 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostCtrlGpu (/dev/nvhost-ctrl-gpu) is used for context independent operations on the underlying GPU (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu)
*/
class NvHostCtrlGpu : public NvDevice {
public:
NvHostCtrlGpu(const DeviceState &state);
/**
* @brief This returns a u32 GPU ZCULL Context Size (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_CTX_SIZE)
*/
void ZCullGetCtxSize(IoctlBuffers &buffer);
/**
* @brief This returns a the GPU ZCULL Information (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_INFO)
*/
void ZCullGetInfo(IoctlBuffers &buffer);
/**
* @brief This returns a struct with certain GPU characteristics (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_CHARACTERISTICS)
*/
void GetCharacteristics(IoctlBuffers &buffer);
/**
* @brief This returns the TPC mask value for each GPC (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_TPC_MASKS)
*/
void GetTpcMasks(IoctlBuffers &buffer);
/**
* @brief This returns the mask value for a ZBC slot (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZBC_GET_ACTIVE_SLOT_MASK)
*/
void GetActiveSlotMask(IoctlBuffers &buffer);
};
}

View File

@ -0,0 +1,140 @@
#include "nvmap.h"
#include <kernel/types/KProcess.h>
namespace skyline::gpu::device {
NvMap::NvMapObject::NvMapObject(u32 id, u32 size) : id(id), size(size) {}
NvMap::NvMap(const DeviceState &state) : NvDevice(state, NvDeviceType::nvmap, {
{0xC0080101, NFUNC(NvMap::Create)},
{0xC0080103, NFUNC(NvMap::FromId)},
{0xC0200104, NFUNC(NvMap::Alloc)},
{0xC0180105, NFUNC(NvMap::Free)},
{0xC00C0109, NFUNC(NvMap::Param)},
{0xC008010E, NFUNC(NvMap::GetId)}
}) {}
void NvMap::Create(IoctlBuffers &buffer) {
struct Data {
u32 size; // In
u32 handle; // Out
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
handleTable[handleIndex] = std::make_shared<NvMapObject>(idIndex++, data.size);
data.handle = handleIndex++;
state.thisProcess->WriteMemory(data, buffer.output[0].address);
state.logger->Info("Create: Input: Size: 0x{:X}, Output: Handle: 0x{:X}, Status: {}", data.size, data.handle, buffer.status);
}
void NvMap::FromId(skyline::gpu::device::IoctlBuffers &buffer) {
struct Data {
u32 id; // In
u32 handle; // Out
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
bool found{};
for (const auto &object : handleTable) {
if (object.second->id == data.id) {
data.handle = object.first;
found = true;
break;
}
}
if (found)
state.thisProcess->WriteMemory(data, buffer.output[0].address);
else
buffer.status = NvStatus::BadValue;
state.logger->Info("FromId: Input: Handle: 0x{:X}, Output: ID: 0x{:X}, Status: {}", data.handle, data.id, buffer.status);
}
void NvMap::Alloc(IoctlBuffers &buffer) {
struct Data {
u32 handle; // In
u32 heapMask; // In
u32 flags; // In
u32 align; // In
u8 kind; // In
u8 _pad0_[7];
u64 address; // InOut
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
auto &object = handleTable.at(data.handle);
object->heapMask = data.heapMask;
object->flags = data.flags;
object->align = data.align;
object->kind = data.kind;
object->address = data.address;
object->status = NvMapObject::Status::Allocated;
state.logger->Info("Alloc: Input: Handle: 0x{:X}, HeapMask: 0x{:X}, Flags: {}, Align: 0x{:X}, Kind: {}, Address: 0x{:X}, Output: Status: {}", data.handle, data.heapMask, data.flags, data.align, data.kind, data.address, buffer.status);
}
void NvMap::Free(skyline::gpu::device::IoctlBuffers &buffer) {
struct Data {
u32 handle; // In
u32 _pad0_;
u32 address; // Out
u32 size; // Out
u64 flags; // Out
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
const auto &object = handleTable.at(data.handle);
if (object.use_count() > 1) {
data.address = static_cast<u32>(object->address);
data.flags = 0x0;
} else {
data.address = 0x0;
data.flags = 0x1; // Not free yet
}
data.size = object->size;
handleTable.erase(data.handle);
state.thisProcess->WriteMemory(data, buffer.output[0].address);
}
void NvMap::Param(IoctlBuffers &buffer) {
enum class Parameter : u32 { Size = 1, Alignment = 2, Base = 3, HeapMask = 4, Kind = 5, Compr = 6 }; // https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102
struct Data {
u32 handle; // In
Parameter parameter; // In
u32 result; // Out
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
auto &object = handleTable.at(data.handle);
switch (data.parameter) {
case Parameter::Size:
data.result = object->size;
break;
case Parameter::Alignment:
case Parameter::HeapMask:
case Parameter::Kind: {
if (object->status != NvMapObject::Status::Allocated)
data.result = NvStatus::BadParameter;
switch (data.parameter) {
case Parameter::Alignment:
data.result = object->align;
break;
case Parameter::HeapMask:
data.result = object->heapMask;
break;
case Parameter::Kind:
data.result = object->kind;
break;
default:
break;
}
break;
}
case Parameter::Base:
buffer.status = NvStatus::NotImplemented;
break;
case Parameter::Compr:
buffer.status = NvStatus::NotImplemented;
break;
}
state.thisProcess->WriteMemory(data, buffer.output[0].address);
state.logger->Info("Param: Input: Handle: 0x{:X}, Parameter: {}, Output: Result: 0x{:X}, Status: {}", data.handle, data.parameter, data.result, buffer.status);
}
void NvMap::GetId(skyline::gpu::device::IoctlBuffers &buffer) {
struct Data {
u32 id; // Out
u32 handle; // In
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
data.id = handleTable.at(data.handle)->id;
state.thisProcess->WriteMemory(data, buffer.output[0].address);
state.logger->Info("GetId: Input: Handle: 0x{:X}, Output: ID: 0x{:X}, Status: {}", data.handle, data.id, buffer.status);
}
}

View File

@ -0,0 +1,71 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvMap (/dev/nvmap) is used to map certain CPU memory as GPU memory (https://switchbrew.org/wiki/NV_services) (https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h)
*/
class NvMap : public NvDevice {
public:
/**
* @brief NvMapObject is used to hold the state of held objects
*/
struct NvMapObject {
u32 id; //!< The ID of this object
u32 size; //!< The size of this object
u64 address{}; //!< The address of the allocation
u32 flags{}; //!< The flag of the memory (0 = Read Only, 1 = Read-Write)
u32 align{}; //!< The alignment of the allocation
u32 heapMask{}; //!< This is set during Alloc and returned during Param
u8 kind{}; //!< This is same as heapMask
enum class Status {
Created, //!< The object has been created but memory has not been allocated
Allocated //!< The object has been allocated
} status{Status::Created}; //!< This holds the status of the object
/**
* @param handle The ID of this object
* @param size The size of the object in bytes
*/
NvMapObject(u32 id, u32 size);
};
std::unordered_map<handle_t, std::shared_ptr<NvMapObject>> handleTable; //!< A mapping from a handle to it's corresponding NvMapObject
handle_t handleIndex{1}; //!< This is used to keep track of the next handle to allocate
u32 idIndex{1}; //!< This is used to keep track of the next ID to allocate
NvMap(const DeviceState &state);
/**
* @brief This creates an NvMapObject and returns an handle to it (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_CREATE)
*/
void Create(IoctlBuffers &buffer);
/**
* @brief This returns the handle of an NvMapObject from it's ID (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID)
*/
void FromId(IoctlBuffers &buffer);
/**
* @brief This allocates memory for an NvMapObject (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_ALLOC)
*/
void Alloc(IoctlBuffers &buffer);
/**
* @brief This frees previously allocated memory (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FREE)
*/
void Free(IoctlBuffers &buffer);
/**
* @brief This returns a particular parameter from an NvMapObject (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_PARAM)
*/
void Param(IoctlBuffers &buffer);
/**
* @brief This returns the ID of an NvMapObject from it's handle (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID)
*/
void GetId(IoctlBuffers &buffer);
};
}

View File

@ -0,0 +1,138 @@
#include "display.h"
#include <kernel/types/KProcess.h>
#include <gpu.h>
namespace skyline::gpu {
Buffer::Buffer(const DeviceState &state, u32 slot, GbpBuffer &gbpBuffer) : state(state), slot(slot), gbpBuffer(gbpBuffer), resolution{gbpBuffer.width, gbpBuffer.height}, dataBuffer(gbpBuffer.size) {
if (gbpBuffer.nvmapHandle)
nvBuffer = state.gpu->GetDevice<device::NvMap>(device::NvDeviceType::nvmap)->handleTable.at(gbpBuffer.nvmapHandle);
else {
auto nvmap = state.gpu->GetDevice<device::NvMap>(device::NvDeviceType::nvmap);
for (const auto &object : nvmap->handleTable) {
if (object.second->id == gbpBuffer.nvmapId) {
nvBuffer = object.second;
break;
}
}
if (!nvBuffer)
throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId);
}
}
void Buffer::UpdateBuffer() {
state.thisProcess->ReadMemory(dataBuffer.data(), nvBuffer->address + gbpBuffer.offset, gbpBuffer.size);
}
BufferQueue::WaitContext::WaitContext(std::shared_ptr<kernel::type::KThread> thread, DequeueIn input, u64 address, u64 size) : thread(std::move(thread)), input(input), address(address), size(size) {}
BufferQueue::DequeueOut::DequeueOut(u32 slot) : slot(slot), _unk0_(0x1), _unk1_(0x24) {}
BufferQueue::BufferQueue(const DeviceState &state) : state(state) {}
void BufferQueue::RequestBuffer(Parcel &in, Parcel &out) {
u32 slot = *reinterpret_cast<u32 *>(in.data.data() + constant::TokenLength);
auto buffer = queue.at(slot);
out.WriteData<u32>(1);
out.WriteData<u32>(sizeof(GbpBuffer));
out.WriteData<u32>(0);
out.WriteData(buffer->gbpBuffer);
state.logger->Debug("RequestBuffer: Slot: {}, Size: {}", slot, sizeof(GbpBuffer));
}
bool BufferQueue::DequeueBuffer(Parcel &in, Parcel &out, u64 address, u64 size) {
auto *data = reinterpret_cast<DequeueIn *>(in.data.data() + constant::TokenLength);
i64 slot{-1};
for (auto &buffer : queue) {
if (buffer.second->status == BufferStatus::Free && buffer.second->resolution.width == data->width && buffer.second->resolution.height == data->height && buffer.second->gbpBuffer.format == data->format && buffer.second->gbpBuffer.usage == data->usage) {
slot = buffer.first;
buffer.second->status = BufferStatus::Dequeued;
}
}
if (slot == -1) {
state.thisThread->Sleep();
waitVec.emplace_back(state.thisThread, *data, address, size);
state.logger->Debug("DequeueBuffer: No Free Buffers");
return true;
}
DequeueOut output(static_cast<u32>(slot));
out.WriteData(output);
state.logger->Debug("DequeueBuffer: Width: {}, Height: {}, Format: {}, Usage: {}, Timestamps: {}, Slot: {}", data->width, data->height, data->format, data->usage, data->timestamps, slot);
return false;
}
void BufferQueue::QueueBuffer(Parcel &in, Parcel &out) {
struct Data {
u32 slot;
u64 timestamp;
u32 autoTimestamp;
ARect crop;
u32 scalingMode;
u32 transform;
u32 stickyTransform;
u64 _unk0_;
u32 swapInterval;
Fence fence[4];
} *data = reinterpret_cast<Data *>(in.data.data() + constant::TokenLength);
auto buffer = queue.at(data->slot);
buffer->status = BufferStatus::Queued;
displayQueue.emplace(buffer);
state.gpu->bufferEvent->Signal();
struct {
u32 width;
u32 height;
u32 _pad0_[3];
} output{
.width = buffer->gbpBuffer.width,
.height = buffer->gbpBuffer.height
};
out.WriteData(output);
state.logger->Debug("QueueBuffer: Timestamp: {}, Auto Timestamp: {}, Crop: [T: {}, B: {}, L: {}, R: {}], Scaling Mode: {}, Transform: {}, Sticky Transform: {}, Swap Interval: {}, Slot: {}", data->timestamp, data->autoTimestamp, data->crop.top, data->crop.bottom, data->crop.left, data->crop.right, data->scalingMode, data->transform, data->stickyTransform, data->swapInterval,
data->slot);
}
void BufferQueue::CancelBuffer(Parcel &parcel) {
struct Data {
u32 slot;
Fence fence[4];
} *data = reinterpret_cast<Data *>(parcel.data.data() + constant::TokenLength);
FreeBuffer(data->slot);
}
void BufferQueue::SetPreallocatedBuffer(Parcel &parcel) {
auto pointer = parcel.data.data() + constant::TokenLength;
struct Data {
u32 slot;
u32 _unk0_;
u32 length;
u32 _pad0_;
} *data = reinterpret_cast<Data *>(pointer);
pointer += sizeof(Data);
auto gbpBuffer = reinterpret_cast<GbpBuffer *>(pointer);
queue[data->slot] = std::make_shared<Buffer>(state, data->slot, *gbpBuffer);
state.gpu->bufferEvent->Signal();
state.logger->Debug("SetPreallocatedBuffer: Slot: {}, Length: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}", data->slot, data->length, gbpBuffer->magic, gbpBuffer->width, gbpBuffer->height, gbpBuffer->stride, gbpBuffer->format, gbpBuffer->usage, gbpBuffer->index, gbpBuffer->nvmapId,
gbpBuffer->nvmapHandle, gbpBuffer->offset, (1U << gbpBuffer->blockHeightLog2));
}
void BufferQueue::FreeBuffer(u32 slotNo) {
auto &slot = queue.at(slotNo);
if (waitVec.empty())
slot->status = BufferStatus::Free;
else {
auto context = waitVec.begin();
while (context != waitVec.end()) {
if (slot->resolution.width == context->input.width && slot->resolution.height == context->input.height && slot->gbpBuffer.format == context->input.format && slot->gbpBuffer.usage == context->input.usage) {
context->thread->WakeUp();
gpu::Parcel out(state);
DequeueOut output(slotNo);
out.WriteData(output);
out.WriteParcel(context->address, context->size, context->thread->pid);
slot->status = BufferStatus::Dequeued;
waitVec.erase(context);
break;
}
context++;
}
}
}
}

View File

@ -0,0 +1,218 @@
#pragma once
#include <common.h>
#include <queue>
#include <gpu/devices/nvmap.h>
#include "parcel.h"
namespace skyline::gpu {
/**
* @brief A struct that encapsulates a resolution
*/
struct Resolution {
u32 width; //!< The width component of the resolution
u32 height; //!< The height component of the resolution
bool operator==(const Resolution &r) {
return (width == r.width) && (height == r.height);
}
bool operator!=(const Resolution &r) {
return !operator==(r);
}
};
/**
* @brief An enumeration of all the possible display IDs (https://switchbrew.org/wiki/Display_services#DisplayName)
*/
enum class DisplayId : u64 {
Default,
External,
Edid,
Internal,
Null
};
/**
* @brief A mapping from a display's name to it's displayType entry
*/
static const std::unordered_map<std::string, DisplayId> displayTypeMap{
{"Default", DisplayId::Default},
{"External", DisplayId::External},
{"Edid", DisplayId::Edid},
{"Internal", DisplayId::Internal},
{"Null", DisplayId::Null},
};
/**
* @brief The status of a specific layer
*/
enum class LayerStatus {
Uninitialized,
Initialized
};
/**
* @brief The status of a specific buffer
*/
enum class BufferStatus {
Free,
Dequeued,
Queued,
Acquired
};
/**
* @brief This struct holds information about the graphics buffer (https://github.com/reswitched/libtransistor/blob/0f0c36227842c344d163922fc98ee76229e9f0ee/lib/display/graphic_buffer_queue.c#L66)
*/
struct GbpBuffer {
u32 magic; //!< The magic of the graphics buffer: 0x47424652
u32 width; //!< The width of the buffer
u32 height; //!< The height of the buffer
u32 stride; //!< The stride of the buffer
u32 format; //!< The format of the buffer, this corresponds to AHardwareBuffer_Format
u32 usage; //!< The usage flags for the buffer
u32 _pad0_;
u32 index; //!< The index of the buffer
u32 _pad1_[3];
u32 nvmapId; //!< The ID of the buffer in regards to /dev/nvmap
u32 _pad2_[8];
u32 size; //!< The size of the buffer
u32 _pad3_[8];
u32 nvmapHandle; //!< The handle of the buffer in regards to /dev/nvmap
u32 offset; //!< This is the offset of the pixel data in the GPU Buffer
u32 _pad4_;
u32 blockHeightLog2; //!< The log2 of the block height
u32 _pad5_[58];
};
/**
* @brief This represents conditions for the completion of an asynchronous graphics operation
*/
struct Fence {
u32 syncptId;
u32 syncptValue;
};
/**
* @brief This holds the state and describes a single Buffer
*/
class Buffer {
public:
const DeviceState &state; //!< The state of the device
u32 slot; //!< The slot the buffer is in
Resolution resolution; //!< The resolution of this buffer
GbpBuffer gbpBuffer; //!< The information about the underlying buffer
BufferStatus status{BufferStatus::Free}; //!< The status of this buffer
std::vector<u8> dataBuffer; //!< The vector holding the actual pixel data
std::shared_ptr<device::NvMap::NvMapObject> nvBuffer{}; //!< A shared pointer to the buffer's nvmap object
/**
* @param state The state of the device
* @param slot The slot this buffer is in
* @param gpBuffer The GbpBuffer object for this buffer
*/
Buffer(const DeviceState &state, u32 slot, GbpBuffer &gbpBuffer);
/**
* @brief This reads the buffer from the process into the dataBuffer vector
*/
void UpdateBuffer();
};
/**
* @brief This holds the state of all the buffers used by the guest application
*/
class BufferQueue {
private:
const DeviceState &state; //!< The state of the device
/**
* @brief This is the input struct for DequeueBuffer
*/
struct DequeueIn {
u32 format;
u32 width;
u32 height;
u32 timestamps;
u32 usage;
};
/**
* @brief This is the output struct for DequeueBuffer
*/
struct DequeueOut {
u32 slot; //!< The slot of the dequeued buffer
u32 _unk0_;
u32 _unk1_;
u32 _unk2_[11]{};
/**
* @param slot The slot of the dequeued buffer
*/
DequeueOut(u32 slot);
};
/**
* @brief This holds the context of a thread waiting on a buffer
*/
struct WaitContext {
std::shared_ptr<kernel::type::KThread> thread; //!< The thread that is waiting on a buffer
DequeueIn input; //!< The input of DequeueBuffer
u64 address; //!< The address of the parcel buffer
u64 size; //!< The size of the parcel buffer
/**
* @param thread The thread that is waiting on a buffer
* @param input The input of DequeueBuffer
* @param address The address of the parcel buffer
* @param size The size of the parcel buffer
*/
WaitContext(std::shared_ptr<kernel::type::KThread> thread, DequeueIn input, u64 address, u64 size);
};
std::vector<WaitContext> waitVec; //!< A vector of shared pointers to threads waiting on a buffer
public:
std::unordered_map<u32, std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers
std::queue<std::shared_ptr<Buffer>> displayQueue; //!< A queue of all the buffers to be posted to the display
/**
* @param state The state of the device
*/
BufferQueue(const DeviceState &state);
/**
* @brief This the GbpBuffer struct of the specified buffer
*/
void RequestBuffer(Parcel &in, Parcel &out);
/**
* @brief This returns the slot of a free buffer
* @param address The address of the parcel buffer
* @param size The size of the parcel buffer
* @return If the process is waiting for a buffer or not
*/
bool DequeueBuffer(Parcel &in, Parcel &out, u64 address, u64 size);
/**
* @brief This queues a buffer to be displayed
*/
void QueueBuffer(Parcel &in, Parcel &out);
/**
* @brief This removes a previously queued buffer
*/
void CancelBuffer(Parcel &parcel);
/**
* @brief This adds a pre-existing buffer to the queue
*/
void SetPreallocatedBuffer(Parcel &parcel);
/**
* @brief This frees a buffer which is currently queued
* @param slotNo The slot of the buffer
*/
void FreeBuffer(u32 slotNo);
};
}

View File

@ -0,0 +1,50 @@
#include "parcel.h"
#include <os.h>
#include <kernel/types/KProcess.h>
namespace skyline::gpu {
Parcel::Parcel(kernel::ipc::BufferDescriptorABW *buffer, const DeviceState &state) : Parcel(buffer->Address(), buffer->Size(), state) {}
Parcel::Parcel(kernel::ipc::BufferDescriptorX *buffer, const DeviceState &state) : Parcel(buffer->Address(), buffer->size, state) {}
Parcel::Parcel(u64 address, u64 size, const DeviceState &state) : state(state) {
state.thisProcess->ReadMemory(&header, address, sizeof(ParcelHeader));
if (size < (sizeof(ParcelHeader) + header.dataSize + header.objectsSize))
throw exception("The size of the parcel according to the header exceeds the specified size");
data.resize(header.dataSize);
state.thisProcess->ReadMemory(data.data(), address + header.dataOffset, header.dataSize);
objects.resize(header.objectsSize);
state.thisProcess->ReadMemory(objects.data(), address + header.objectsOffset, header.objectsSize);
}
Parcel::Parcel(const DeviceState &state) : state(state) {}
u64 Parcel::WriteParcel(kernel::ipc::BufferDescriptorABW *buffer, pid_t process) {
return WriteParcel(buffer->Address(), buffer->Size(), process);
}
u64 Parcel::WriteParcel(kernel::ipc::BufferDescriptorC *buffer, pid_t process) {
return WriteParcel(buffer->address, buffer->size, process);
}
u64 Parcel::WriteParcel(u64 address, u64 maxSize, pid_t process) {
header.dataSize = static_cast<u32>(data.size());
header.dataOffset = sizeof(ParcelHeader);
header.objectsSize = static_cast<u32>(objects.size());
header.objectsOffset = sizeof(ParcelHeader) + data.size();
u64 totalSize = sizeof(ParcelHeader) + header.dataSize + header.objectsSize;
if (maxSize < totalSize)
throw exception("The size of the parcel exceeds maxSize");
if (process) {
auto &object = state.os->processMap.at(process);
object->WriteMemory(header, address);
object->WriteMemory(data.data(), address + header.dataOffset, data.size());
object->WriteMemory(objects.data(), address + header.objectsOffset, objects.size());
} else {
state.thisProcess->WriteMemory(header, address);
state.thisProcess->WriteMemory(data.data(), address + header.dataOffset, data.size());
state.thisProcess->WriteMemory(objects.data(), address + header.objectsOffset, objects.size());
}
return totalSize;
}
}

View File

@ -0,0 +1,112 @@
#pragma once
#include <common.h>
#include <kernel/ipc.h>
namespace skyline::gpu {
/**
* @brief This class encapsulates a Parcel object (https://switchbrew.org/wiki/Display_services#Parcel)
*/
class Parcel {
private:
/**
* @brief This holds the header of a parcel
*/
struct ParcelHeader {
u32 dataSize;
u32 dataOffset;
u32 objectsSize;
u32 objectsOffset;
} header{};
static_assert(sizeof(ParcelHeader) == 0x10);
const DeviceState &state; //!< The state of the device
public:
std::vector<u8> data; //!< A vector filled with data in the parcel
std::vector<u8> objects; //!< A vector filled with objects in the parcel
/**
* @brief This constructor fills in the Parcel object with data from a IPC buffer
* @param buffer The buffer that contains the parcel
* @param state The state of the device
*/
Parcel(kernel::ipc::BufferDescriptorABW *buffer, const DeviceState &state);
/**
* @brief This constructor fills in the Parcel object with data from a IPC buffer
* @param buffer The buffer that contains the parcel
* @param state The state of the device
*/
Parcel(kernel::ipc::BufferDescriptorX *buffer, const DeviceState &state);
/**
* @brief This constructor fills in the Parcel object with data from a Parcel on a remote process
* @param address The remote address of the parcel
* @param size The size of the parcel
* @param state The state of the device
*/
Parcel(u64 address, u64 size, const DeviceState &state);
/**
* @brief This constructor is used to create an empty parcel then write to a process
* @param state The state of the device
*/
Parcel(const DeviceState &state);
/**
* @brief Writes some data to the Parcel
* @tparam ValueType The type of the object to write
* @param value The object to be written
*/
template<typename ValueType>
void WriteData(const ValueType &value) {
data.reserve(data.size() + sizeof(ValueType));
auto item = reinterpret_cast<const u8 *>(&value);
for (uint index = 0; sizeof(ValueType) > index; index++) {
data.push_back(*item);
item++;
}
}
/**
* @brief Writes an object to the Parcel
* @tparam ValueType The type of the object to write
* @param value The object to be written
*/
template<typename ValueType>
void WriteObject(const ValueType &value) {
objects.reserve(objects.size() + sizeof(ValueType));
auto item = reinterpret_cast<const u8 *>(&value);
for (uint index = 0; sizeof(ValueType) > index; index++) {
objects.push_back(*item);
item++;
}
}
/**
* @brief Writes the Parcel object into a particular B buffer on a process
* @param buffer The buffer to write into
* @param process The process to write the Parcel to
* @return The total size of the message
*/
u64 WriteParcel(kernel::ipc::BufferDescriptorABW *buffer, pid_t process = 0);
/**
* @brief Writes the Parcel object into a particular C buffer on a process
* @param buffer The buffer to write into
* @param process The process to write the Parcel to
* @return The total size of the message
*/
u64 WriteParcel(kernel::ipc::BufferDescriptorC *buffer, pid_t process = 0);
/**
* @brief Writes the Parcel object into the process's memory
* @param address The address to write the Parcel object to
* @param maxSize The maximum size of the Parcel
* @param process The process to write the Parcel to
* @return The total size of the message
*/
u64 WriteParcel(u64 address, u64 maxSize, pid_t process = 0);
};
}

View File

@ -23,26 +23,43 @@ namespace skyline::kernel::ipc {
} }
for (uint index = 0; header->x_no > index; index++) { for (uint index = 0; header->x_no > index; index++) {
vecBufX.push_back(reinterpret_cast<BufferDescriptorX *>(currPtr)); auto bufX = reinterpret_cast<BufferDescriptorX *>(currPtr);
if (bufX->Address()) {
vecBufX.push_back(bufX);
state.logger->Debug("Buf X #{} AD: 0x{:X} SZ: 0x{:X} CTR: {}", index, u64(bufX->Address()), u16(bufX->size), u16(bufX->Counter()));
}
currPtr += sizeof(BufferDescriptorX); currPtr += sizeof(BufferDescriptorX);
} }
for (uint index = 0; header->a_no > index; index++) { for (uint index = 0; header->a_no > index; index++) {
vecBufA.push_back(reinterpret_cast<BufferDescriptorABW *>(currPtr)); auto bufA = reinterpret_cast<BufferDescriptorABW *>(currPtr);
if (bufA->Address()) {
vecBufA.push_back(bufA);
state.logger->Debug("Buf A #{} AD: 0x{:X} SZ: 0x{:X}", index, u64(bufA->Address()), u64(bufA->Size()));
}
currPtr += sizeof(BufferDescriptorABW); currPtr += sizeof(BufferDescriptorABW);
} }
for (uint index = 0; header->b_no > index; index++) { for (uint index = 0; header->b_no > index; index++) {
vecBufB.push_back(reinterpret_cast<BufferDescriptorABW *>(currPtr)); auto bufB = reinterpret_cast<BufferDescriptorABW *>(currPtr);
if (bufB->Address()) {
vecBufB.push_back(bufB);
state.logger->Debug("Buf B #{} AD: 0x{:X} SZ: 0x{:X}", index, u64(bufB->Address()), u64(bufB->Size()));
}
currPtr += sizeof(BufferDescriptorABW); currPtr += sizeof(BufferDescriptorABW);
} }
for (uint index = 0; header->w_no > index; index++) { for (uint index = 0; header->w_no > index; index++) {
vecBufW.push_back(reinterpret_cast<BufferDescriptorABW *>(currPtr)); auto bufW = reinterpret_cast<BufferDescriptorABW *>(currPtr);
if (bufW->Address()) {
vecBufW.push_back(bufW);
state.logger->Debug("Buf W #{} AD: 0x{:X} SZ: 0x{:X}", index, u64(bufW->Address()), u16(bufW->Size()));
}
currPtr += sizeof(BufferDescriptorABW); currPtr += sizeof(BufferDescriptorABW);
} }
currPtr = reinterpret_cast<u8 *>((((reinterpret_cast<u64>(currPtr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::PaddingSum - 1U)) + constant::PaddingSum + reinterpret_cast<u64>(tls.data())); // Align to 16 bytes relative to start of TLS u64 padding = ((((reinterpret_cast<u64>(currPtr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::IpcPaddingSum - 1U)) + constant::IpcPaddingSum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(currPtr))); // Calculate the amount of padding at the front
currPtr += padding;
if (isDomain) { if (isDomain) {
domain = reinterpret_cast<DomainHeaderRequest *>(currPtr); domain = reinterpret_cast<DomainHeaderRequest *>(currPtr);
@ -64,29 +81,36 @@ namespace skyline::kernel::ipc {
currPtr += sizeof(PayloadHeader); currPtr += sizeof(PayloadHeader);
cmdArg = currPtr; cmdArg = currPtr;
cmdArgSz = (header->raw_sz * sizeof(u32)) - (constant::PaddingSum + sizeof(PayloadHeader)); cmdArgSz = (header->raw_sz * sizeof(u32)) - (constant::IpcPaddingSum + sizeof(PayloadHeader));
currPtr += cmdArgSz; currPtr += cmdArgSz;
} }
if (payload->magic != constant::SfciMagic) if (payload->magic != constant::SfciMagic && header->type != static_cast<u16>(CommandType::Control))
state.logger->Write(Logger::Debug, "Unexpected Magic in PayloadHeader: 0x{:X}", u32(payload->magic)); state.logger->Debug("Unexpected Magic in PayloadHeader: 0x{:X}", u32(payload->magic));
currPtr += constant::IpcPaddingSum - padding;
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) { if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
vecBufC.push_back(reinterpret_cast<BufferDescriptorC *>(currPtr)); auto bufC = reinterpret_cast<BufferDescriptorC *>(currPtr);
vecBufC.push_back(bufC);
state.logger->Debug("Buf C: AD: 0x{:X} SZ: 0x{:X}", u64(bufC->address), u16(bufC->size));
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) { } else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
for (uint index = 0; (header->c_flag - 2) > index; index++) { // (c_flag - 2) C descriptors are present for (uint index = 0; (header->c_flag - 2) > index; index++) { // (c_flag - 2) C descriptors are present
vecBufC.push_back(reinterpret_cast<BufferDescriptorC *>(currPtr)); auto bufC = reinterpret_cast<BufferDescriptorC *>(currPtr);
state.logger->Write(Logger::Debug, "Buf C #{} AD: 0x{:X} SZ: 0x{:X}", index, u64(vecBufC[index]->address), u16(vecBufC[index]->size)); if (bufC->address) {
vecBufC.push_back(bufC);
state.logger->Debug("Buf C #{} AD: 0x{:X} SZ: 0x{:X}", index, u64(bufC->address), u16(bufC->size));
}
currPtr += sizeof(BufferDescriptorC); currPtr += sizeof(BufferDescriptorC);
} }
} }
state.logger->Write(Logger::Debug, "Header: X No: {}, A No: {}, B No: {}, W No: {}, C No: {}, Raw Size: {}", u8(header->x_no), u8(header->a_no), u8(header->b_no), u8(header->w_no), u8(vecBufC.size()), u64(cmdArgSz)); state.logger->Debug("Header: X No: {}, A No: {}, B No: {}, W No: {}, C No: {}, Raw Size: {}", u8(header->x_no), u8(header->a_no), u8(header->b_no), u8(header->w_no), u8(vecBufC.size()), u64(cmdArgSz));
if (header->handle_desc) if (header->handle_desc)
state.logger->Write(Logger::Debug, "Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", bool(handleDesc->send_pid), u32(handleDesc->copy_count), u32(handleDesc->move_count)); state.logger->Debug("Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", bool(handleDesc->send_pid), u32(handleDesc->copy_count), u32(handleDesc->move_count));
if (isDomain) if (isDomain)
state.logger->Write(Logger::Debug, "Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->input_count, domain->object_id); state.logger->Debug("Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->input_count, domain->object_id);
state.logger->Write(Logger::Debug, "Data Payload: Command ID: 0x{:X}", u32(payload->value)); state.logger->Debug("Data Payload: Command ID: 0x{:X}", u32(payload->value));
} }
IpcResponse::IpcResponse(bool isDomain, const DeviceState &state) : isDomain(isDomain), state(state) {} IpcResponse::IpcResponse(bool isDomain, const DeviceState &state) : isDomain(isDomain), state(state) {}
@ -95,7 +119,7 @@ namespace skyline::kernel::ipc {
std::array<u8, constant::TlsIpcSize> tls{}; std::array<u8, constant::TlsIpcSize> tls{};
u8 *currPtr = tls.data(); u8 *currPtr = tls.data();
auto header = reinterpret_cast<CommandHeader *>(currPtr); auto header = reinterpret_cast<CommandHeader *>(currPtr);
header->raw_sz = static_cast<u32>((sizeof(PayloadHeader) + argVec.size() + (domainObjects.size() * sizeof(handle_t)) + constant::PaddingSum + (isDomain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo header->raw_sz = static_cast<u32>((sizeof(PayloadHeader) + argVec.size() + (domainObjects.size() * sizeof(handle_t)) + constant::IpcPaddingSum + (isDomain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo
header->handle_desc = (!copyHandles.empty() || !moveHandles.empty()); header->handle_desc = (!copyHandles.empty() || !moveHandles.empty());
currPtr += sizeof(CommandHeader); currPtr += sizeof(CommandHeader);
@ -116,7 +140,7 @@ namespace skyline::kernel::ipc {
} }
} }
u64 padding = ((((reinterpret_cast<u64>(currPtr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::PaddingSum - 1U)) + constant::PaddingSum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(currPtr))); // Calculate the amount of padding at the front u64 padding = ((((reinterpret_cast<u64>(currPtr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::IpcPaddingSum - 1U)) + constant::IpcPaddingSum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(currPtr))); // Calculate the amount of padding at the front
currPtr += padding; currPtr += padding;
if (isDomain) { if (isDomain) {
@ -134,15 +158,21 @@ namespace skyline::kernel::ipc {
memcpy(currPtr, argVec.data(), argVec.size()); memcpy(currPtr, argVec.data(), argVec.size());
currPtr += argVec.size(); currPtr += argVec.size();
if(isDomain) { if (isDomain) {
for (auto& domainObject : domainObjects) { for (auto &domainObject : domainObjects) {
*reinterpret_cast<handle_t *>(currPtr) = domainObject; *reinterpret_cast<handle_t *>(currPtr) = domainObject;
currPtr += sizeof(handle_t); currPtr += sizeof(handle_t);
} }
} }
state.logger->Write(Logger::Debug, "Output: Raw Size: {}, Command ID: 0x{:X}, Copy Handles: {}, Move Handles: {}", u32(header->raw_sz), u32(payload->value), copyHandles.size(), moveHandles.size()); state.logger->Debug("Output: Raw Size: {}, Command ID: 0x{:X}, Copy Handles: {}, Move Handles: {}", u32(header->raw_sz), u32(payload->value), copyHandles.size(), moveHandles.size());
state.thisProcess->WriteMemory(tls.data(), state.thisThread->tls, constant::TlsIpcSize); state.thisProcess->WriteMemory(tls.data(), state.thisThread->tls, constant::TlsIpcSize);
} }
std::vector<u8> BufferDescriptorABW::Read(const DeviceState &state) {
std::vector<u8> vec(Size());
state.thisProcess->ReadMemory(vec.data(), Address(), Size());
return std::move(vec);
}
} }

View File

@ -144,6 +144,8 @@ namespace skyline::kernel::ipc {
size_32_35 = static_cast<u8>(size & 0x78000000); size_32_35 = static_cast<u8>(size & 0x78000000);
} }
std::vector<u8> Read(const DeviceState &state);
inline u64 Address() const { inline u64 Address() const {
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36; return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
} }
@ -160,7 +162,7 @@ namespace skyline::kernel::ipc {
*/ */
struct BufferDescriptorC { struct BufferDescriptorC {
u64 address : 48; u64 address : 48;
u16 size : 16; u32 size : 16;
BufferDescriptorC(u64 address, u16 size) : address(address), size(size) {} BufferDescriptorC(u64 address, u16 size) : address(address), size(size) {}
}; };
@ -208,6 +210,7 @@ namespace skyline::kernel::ipc {
const DeviceState &state; //!< The state of the device const DeviceState &state; //!< The state of the device
public: public:
bool nWrite{}; //!< This is to signal the IPC handler to not write this, as it will be manually written
bool isDomain{}; //!< If this is a domain request bool isDomain{}; //!< If this is a domain request
u32 errorCode{}; //!< The error code to respond with, it is 0 (Success) by default u32 errorCode{}; //!< The error code to respond with, it is 0 (Success) by default
std::vector<handle_t> copyHandles; //!< A vector of handles to copy std::vector<handle_t> copyHandles; //!< A vector of handles to copy

View File

@ -1,125 +0,0 @@
#include "appletOE.h"
namespace skyline::kernel::service::am {
appletOE::appletOE(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::am_appletOE, {
{0x0, SFunc(appletOE::OpenApplicationProxy)}
}) {}
void appletOE::OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.NewService(Service::am_IApplicationProxy, session, response);
}
IApplicationProxy::IApplicationProxy(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::am_IApplicationProxy, {
{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)},
{0x3E8, SFunc(IApplicationProxy::GetDisplayController)}
}) {}
void IApplicationProxy::GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.NewService(Service::am_ICommonStateGetter, session, response);
}
void IApplicationProxy::GetSelfController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.NewService(Service::am_ISelfController, session, response);
}
void IApplicationProxy::GetWindowController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
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);
}
void IApplicationProxy::GetApplicationFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
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<OperationMode>(state.settings->GetBool("operation_mode"));
state.logger->Write(Logger::Info, "Switch on mode: {}", static_cast<bool>(operationMode) ? "Docked" : "Handheld");
}
void ICommonStateGetter::GetEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto event = state.thisProcess->NewHandle<type::KEvent>();
messageEvent = event.item;
response.copyHandles.push_back(event.handle);
}
void ICommonStateGetter::GetCurrentFocusState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u8>(static_cast<u8>(ApplicationStatus::InFocus));
}
void ICommonStateGetter::GetOperationMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u8>(static_cast<u8>(operationMode));
}
void ICommonStateGetter::GetPerformanceMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u32>(static_cast<u32>(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<u64>(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<u8>(1);
}
IDebugFunctions::IDebugFunctions(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IDebugFunctions, {
}) {}
}

View File

@ -1,13 +0,0 @@
#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)},
{0x1, SFunc(fatalU::ThrowFatal)},
{0x2, SFunc(fatalU::ThrowFatal)}
}) {}
void fatalU::ThrowFatal(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
throw exception(fmt::format("A fatal error with code: 0x{:X} has caused emulation to stop", *reinterpret_cast<u32*>(request.cmdArg)));
}
}

View File

@ -1,42 +1,103 @@
#include "svc.h" #include "svc.h"
#include <os.h> #include <os.h>
#include <kernel/types/KTransferMemory.h>
namespace skyline::kernel::svc { namespace skyline::kernel::svc {
void SetHeapSize(DeviceState &state) { void SetHeapSize(DeviceState &state) {
auto heap = state.thisProcess->MapPrivateRegion(0, state.nce->GetRegister(Wreg::W1), {true, true, false}, memory::Type::Heap, memory::Region::Heap); u32 size = state.nce->GetRegister(Wreg::W1);
std::shared_ptr<type::KPrivateMemory> heap;
try {
heap = state.thisProcess->memoryRegionMap.at(memory::Region::Heap);
heap->Resize(size, true); // This can fail due to not enough space to resize
} catch (const exception &) {
state.logger->Warn("svcSetHeapSize is falling back to recreating memory");
state.thisProcess->UnmapPrivateRegion(memory::Region::Heap);
heap = state.thisProcess->MapPrivateRegion(constant::HeapAddr, size, {true, true, false}, memory::Type::Heap, memory::Region::Heap).item;
}
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
state.nce->SetRegister(Xreg::X1, heap.item->address); state.nce->SetRegister(Xreg::X1, heap->address);
state.logger->Write(Logger::Debug, "Heap size was set to 0x{:X}", state.nce->GetRegister(Wreg::W1)); state.logger->Debug("svcSetHeapSize allocated at 0x{:X} for 0x{:X} bytes", heap->address, heap->size);
}
void SetMemoryAttribute(DeviceState &state) {
u64 addr = state.nce->GetRegister(Xreg::X0);
u64 size = state.nce->GetRegister(Xreg::X1);
bool isUncached = (state.nce->GetRegister(Wreg::W2) == 8) && (state.nce->GetRegister(Wreg::W3) == 8);
bool found = false;
for (const auto&[address, region] : state.thisProcess->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
bool subFound = false;
for (auto &subregion : region->regionInfoVec) {
if ((address >= subregion.address) && (address < (subregion.address + subregion.size)))
subregion.isUncached = isUncached;
subFound = true;
break;
}
if (!subFound)
region->regionInfoVec.push_back(memory::RegionInfo{.address=addr, .size=size, .isUncached=isUncached});
found = true;
break;
}
}
state.logger->Debug("svcSetMemoryAttribute set caching to {} at 0x{:X} for 0x{:X} bytes", !isUncached, addr, size);
state.nce->SetRegister(Wreg::W0, found ? constant::status::Success : constant::status::InvAddress);
} }
void QueryMemory(DeviceState &state) { void QueryMemory(DeviceState &state) {
memory::MemoryInfo memInf; memory::MemoryInfo memInf;
u64 addr = state.nce->GetRegister(Xreg::X2); u64 addr = state.nce->GetRegister(Xreg::X2);
bool memFree = true; bool found = false;
for(const auto& [address, region] : state.thisProcess->memoryMap) { for (const auto&[address, region] : state.thisProcess->memoryMap) {
if (addr >= address && addr < (address + region->size)) { if (addr >= address && addr < (address + region->size)) {
memInf = region->GetInfo(); memInf = region->GetInfo(addr);
memFree = false; found = true;
break; break;
} }
} }
if (memFree) { if (!found) {
memInf = { for (const auto &object : state.thisProcess->handleTable) {
.baseAddress = static_cast<u64>(static_cast<u64>(addr / PAGE_SIZE) * PAGE_SIZE), if (object.second->objectType == type::KType::KSharedMemory) {
.size = static_cast<u64>(-constant::BaseSize + 1), const auto &mem = state.thisProcess->GetHandle<type::KSharedMemory>(object.first);
.type = static_cast<u64>(memory::Type::Unmapped), if (mem->procInfMap.count(state.thisProcess->mainThread)) {
const auto &map = mem->procInfMap.at(state.thisProcess->mainThread);
if (addr >= map.address && addr < (map.address + map.size)) {
memInf = mem->GetInfo(state.thisProcess->mainThread);
found = true;
break;
}
}
} else if (object.second->objectType == type::KType::KTransferMemory) {
const auto &mem = state.thisProcess->GetHandle<type::KTransferMemory>(object.first);
if (addr >= mem->cAddress && addr < (mem->cAddress + mem->cSize)) {
memInf = mem->GetInfo();
found = true;
break;
}
}
}
if (!found) {
memInf = {
.baseAddress = constant::BaseEnd,
.size = static_cast<u64>(-constant::BaseEnd + 1),
.type = static_cast<u64>(memory::Type::Unmapped)
}; };
state.logger->Warn("Cannot find block of address: 0x{:X}", addr);
}
} }
state.thisProcess->WriteMemory<memory::MemoryInfo>(memInf, state.nce->GetRegister(Xreg::X0)); state.thisProcess->WriteMemory<memory::MemoryInfo>(memInf, state.nce->GetRegister(Xreg::X0));
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
} }
void ExitProcess(DeviceState &state) {
state.os->KillThread(state.thisProcess->mainThread);
}
void CreateThread(DeviceState &state) { void CreateThread(DeviceState &state) {
// TODO: Support Core Mask potentially // TODO: Support Core Mask potentially
auto thread = state.thisProcess->CreateThread(state.nce->GetRegister(Xreg::X1), state.nce->GetRegister(Xreg::X2), state.nce->GetRegister(Xreg::X3), static_cast<u8>(state.nce->GetRegister(Wreg::W4))); auto thread = state.thisProcess->CreateThread(state.nce->GetRegister(Xreg::X1), state.nce->GetRegister(Xreg::X2), state.nce->GetRegister(Xreg::X3), static_cast<u8>(state.nce->GetRegister(Wreg::W4)));
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
state.nce->SetRegister(Wreg::W1, thread->handle); state.nce->SetRegister(Wreg::W1, thread->handle);
state.logger->Write(Logger::Info, "Creating a thread: {}", thread->handle); state.logger->Info("Creating a thread: {}", thread->handle);
} }
void StartThread(DeviceState &state) { void StartThread(DeviceState &state) {
@ -53,14 +114,14 @@ namespace skyline::kernel::svc {
void SleepThread(DeviceState &state) { void SleepThread(DeviceState &state) {
auto in = state.nce->GetRegister(Xreg::X0); auto in = state.nce->GetRegister(Xreg::X0);
switch(in) { switch (in) {
case 0: case 0:
case 1: case 1:
case 2: case 2:
state.thisThread->status = type::KThread::ThreadStatus::Runnable; // Will cause the application to awaken on the next iteration of the main loop state.thisThread->status = type::KThread::Status::Runnable; // Will cause the application to awaken on the next iteration of the main loop
default: default:
state.thisThread->timeout = GetCurrTimeNs() + in; state.thisThread->timeout = GetCurrTimeNs() + in;
state.thisThread->status = type::KThread::ThreadStatus::Sleeping; state.thisThread->status = type::KThread::Status::Sleeping;
} }
} }
@ -82,7 +143,7 @@ namespace skyline::kernel::svc {
void CloseHandle(DeviceState &state) { void CloseHandle(DeviceState &state) {
auto handle = static_cast<handle_t>(state.nce->GetRegister(Wreg::W0)); auto handle = static_cast<handle_t>(state.nce->GetRegister(Wreg::W0));
state.logger->Write(Logger::Debug, "Closing handle: 0x{:X}", handle); state.logger->Debug("Closing handle: 0x{:X}", handle);
auto &object = state.thisProcess->handleTable.at(handle); auto &object = state.thisProcess->handleTable.at(handle);
switch (object->objectType) { switch (object->objectType) {
case (type::KType::KThread): case (type::KType::KThread):
@ -97,6 +158,24 @@ namespace skyline::kernel::svc {
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
} }
void CreateTransferMemory(DeviceState &state) {
u64 address = state.nce->GetRegister(Xreg::X1);
u64 size = state.nce->GetRegister(Xreg::X2);
u32 perms = state.nce->GetRegister(Wreg::W3);
auto shmem = state.thisProcess->NewHandle<type::KTransferMemory>(state.thisProcess->mainThread, address, size, *reinterpret_cast<memory::Permission *>(&perms));
state.nce->SetRegister(Wreg::W0, constant::status::Success);
state.nce->SetRegister(Wreg::W1, shmem.handle);
}
void ResetSignal(DeviceState &state) {
try {
state.thisProcess->GetHandle<type::KEvent>(state.nce->GetRegister(Wreg::W0))->ResetSignal();
} catch (const exception &) {
state.thisProcess->GetHandle<type::KProcess>(state.nce->GetRegister(Wreg::W0))->ResetSignal();
}
state.nce->SetRegister(Wreg::W0, constant::status::Success);
}
void WaitSynchronization(DeviceState &state) { void WaitSynchronization(DeviceState &state) {
auto numHandles = state.nce->GetRegister(Wreg::W2); auto numHandles = state.nce->GetRegister(Wreg::W2);
if (numHandles > constant::MaxSyncHandles) { if (numHandles > constant::MaxSyncHandles) {
@ -105,9 +184,11 @@ namespace skyline::kernel::svc {
} }
std::vector<handle_t> waitHandles(numHandles); std::vector<handle_t> waitHandles(numHandles);
state.thisProcess->ReadMemory(waitHandles.data(), state.nce->GetRegister(Xreg::X1), numHandles * sizeof(handle_t)); state.thisProcess->ReadMemory(waitHandles.data(), state.nce->GetRegister(Xreg::X1), numHandles * sizeof(handle_t));
for (const auto& handle : waitHandles) { std::string handleStr;
for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle);
auto object = state.thisProcess->handleTable.at(handle); auto object = state.thisProcess->handleTable.at(handle);
switch(object->objectType) { switch (object->objectType) {
case type::KType::KProcess: case type::KType::KProcess:
case type::KType::KThread: case type::KType::KThread:
case type::KType::KEvent: case type::KType::KEvent:
@ -118,15 +199,34 @@ namespace skyline::kernel::svc {
return; return;
} }
auto syncObject = std::static_pointer_cast<type::KSyncObject>(object); auto syncObject = std::static_pointer_cast<type::KSyncObject>(object);
if(syncObject->signalled) { if (syncObject->signalled) {
state.logger->Debug("Found signalled handle: 0x{:X}", handle);
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
state.nce->SetRegister(Wreg::W1, handle);
return; return;
} }
state.thisThread->waitObjects.push_back(syncObject); state.thisThread->waitObjects.push_back(syncObject);
syncObject->waitThreads.push_back(state.thisThread->pid); syncObject->waitThreads.emplace_back(state.thisThread->pid, handle);
} }
state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3); state.logger->Debug("Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, state.nce->GetRegister(Xreg::X3));
state.thisThread->status = type::KThread::ThreadStatus::WaitSync; if (state.nce->GetRegister(Xreg::X3) != std::numeric_limits<u64>::max())
state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3);
else
state.thisThread->timeout = 0;
state.thisThread->status = type::KThread::Status::WaitSync;
}
void GetSystemTick(DeviceState &state) {
u64 tick{};
asm("STR X1, [SP, #-16]!\n\t"
"MRS %0, CNTVCT_EL0\n\t"
"MOV X1, #0xF800\n\t"
"MOVK X1, #0x124, lsl #16\n\t"
"MUL %0, %0, X1\n\t"
"MRS X1, CNTFRQ_EL0\n\t"
"UDIV %0, %0, X1\n\t"
"LDR X1, [SP], #16" : "=r"(tick));
state.nce->SetRegister(Xreg::X0, tick);
} }
void ArbitrateLock(DeviceState &state) { void ArbitrateLock(DeviceState &state) {
@ -156,7 +256,7 @@ namespace skyline::kernel::svc {
break; break;
} }
} }
state.thisThread->status = type::KThread::ThreadStatus::WaitCondVar; state.thisThread->status = type::KThread::Status::WaitCondVar;
state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3); state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3);
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
} }
@ -170,7 +270,7 @@ namespace skyline::kernel::svc {
auto &cvarVec = state.thisProcess->condVarMap[address]; auto &cvarVec = state.thisProcess->condVarMap[address];
count = std::min(count, static_cast<u32>(cvarVec.size())); count = std::min(count, static_cast<u32>(cvarVec.size()));
for (uint index = 0; index < count; index++) for (uint index = 0; index < count; index++)
cvarVec[index]->status = type::KThread::ThreadStatus::Runnable; cvarVec[index]->status = type::KThread::Status::Runnable;
cvarVec.erase(cvarVec.begin(), cvarVec.begin() + count); cvarVec.erase(cvarVec.begin(), cvarVec.begin() + count);
if (cvarVec.empty()) if (cvarVec.empty())
state.thisProcess->condVarMap.erase(address); state.thisProcess->condVarMap.erase(address);
@ -182,7 +282,7 @@ namespace skyline::kernel::svc {
if (std::strcmp(port, "sm:") == 0) if (std::strcmp(port, "sm:") == 0)
state.nce->SetRegister(Wreg::W1, state.os->serviceManager.NewSession(service::Service::sm)); state.nce->SetRegister(Wreg::W1, state.os->serviceManager.NewSession(service::Service::sm));
else else
throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port)); throw exception("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port);
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
} }
@ -191,18 +291,26 @@ namespace skyline::kernel::svc {
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
} }
void GetThreadId(DeviceState &state) {
pid_t pid{};
if (state.nce->GetRegister(Wreg::W1) != constant::ThreadSelf) {
handle_t thread = state.nce->GetRegister(Wreg::W1);
pid = state.thisProcess->GetHandle<type::KThread>(thread)->pid;
} else
pid = state.thisThread->pid;
state.nce->SetRegister(Xreg::X1, static_cast<u64>(pid));
state.nce->SetRegister(Wreg::W0, constant::status::Success);
}
void OutputDebugString(DeviceState &state) { void OutputDebugString(DeviceState &state) {
std::string debug(state.nce->GetRegister(Xreg::X1), '\0'); std::string debug(state.nce->GetRegister(Xreg::X1), '\0');
state.os->thisProcess->ReadMemory((void *) debug.data(), state.nce->GetRegister(Xreg::X0), state.nce->GetRegister(Xreg::X1)); state.os->thisProcess->ReadMemory(debug.data(), state.nce->GetRegister(Xreg::X0), state.nce->GetRegister(Xreg::X1));
std::string::size_type pos = 0; state.logger->Info("Debug Output: {}", debug);
while ((pos = debug.find("\r\n", pos)) != std::string::npos) state.nce->SetRegister(Wreg::W0, constant::status::Success);
debug.erase(pos, 2);
state.logger->Write(Logger::Info, "Debug Output: {}", debug);
state.nce->SetRegister(Wreg::W0, 0);
} }
void GetInfo(DeviceState &state) { void GetInfo(DeviceState &state) {
state.logger->Write(Logger::Debug, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(Wreg::W1), state.nce->GetRegister(Xreg::X3)); state.logger->Debug("svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(Wreg::W1), state.nce->GetRegister(Xreg::X3));
switch (state.nce->GetRegister(Wreg::W1)) { switch (state.nce->GetRegister(Wreg::W1)) {
case constant::infoState::AllowedCpuIdBitmask: case constant::infoState::AllowedCpuIdBitmask:
case constant::infoState::AllowedThreadPriorityMask: case constant::infoState::AllowedThreadPriorityMask:
@ -257,14 +365,10 @@ namespace skyline::kernel::svc {
state.nce->SetRegister(Xreg::X1, state.thisProcess->tlsPages[0]->Get(0)); state.nce->SetRegister(Xreg::X1, state.thisProcess->tlsPages[0]->Get(0));
break; break;
default: default:
state.logger->Write(Logger::Warn, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(Wreg::W1), state.nce->GetRegister(Xreg::X3)); state.logger->Warn("Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(Wreg::W1), state.nce->GetRegister(Xreg::X3));
state.nce->SetRegister(Wreg::W0, constant::status::Unimpl); state.nce->SetRegister(Wreg::W0, constant::status::Unimpl);
return; return;
} }
state.nce->SetRegister(Wreg::W0, constant::status::Success); state.nce->SetRegister(Wreg::W0, constant::status::Success);
} }
void ExitProcess(DeviceState &state) {
state.os->KillThread(state.thisProcess->mainThread);
}
} }

View File

@ -42,7 +42,12 @@ namespace skyline {
void SetHeapSize(DeviceState &state); void SetHeapSize(DeviceState &state);
/** /**
* @brief Query information about an address. Will always fetch the lowest page-aligned mapping that contains the provided address (https://switchbrew.org/wiki/SVC#svcQueryMemory) * @brief Change attribute of page-aligned memory region. This is used to turn on/off caching for a given memory area. (https://switchbrew.org/wiki/SVC#svcSetMemoryAttribute)
*/
void SetMemoryAttribute(DeviceState &state);
/**
* @brief Query information about an address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
*/ */
void QueryMemory(DeviceState &state); void QueryMemory(DeviceState &state);
@ -91,11 +96,26 @@ namespace skyline {
*/ */
void CloseHandle(DeviceState &state); void CloseHandle(DeviceState &state);
/**
* @brief Returns a handle to a KSharedMemory object (https://switchbrew.org/wiki/SVC#svcCreateTransferMemory)
*/
void CreateTransferMemory(DeviceState &state);
/**
* @brief This resets a particular KEvent or KProcess which is signalled (https://switchbrew.org/wiki/SVC#svcResetSignal)
*/
void ResetSignal(DeviceState &state);
/** /**
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended (https://switchbrew.org/wiki/SVC#svcWaitSynchronization) * @brief Stalls a thread till a KSyncObject signals or the timeout has ended (https://switchbrew.org/wiki/SVC#svcWaitSynchronization)
*/ */
void WaitSynchronization(DeviceState &state); void WaitSynchronization(DeviceState &state);
/**
* @brief This returns the value of CNTPCT_EL0 on the Switch (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
*/
void GetSystemTick(DeviceState &state);
/** /**
* @brief Locks a specified mutex * @brief Locks a specified mutex
*/ */
@ -126,6 +146,11 @@ namespace skyline {
*/ */
void SendSyncRequest(DeviceState &state); void SendSyncRequest(DeviceState &state);
/**
* @brief Retrieves the PID of a specific thread
*/
void GetThreadId(DeviceState &state);
/** /**
* @brief Outputs a debug string * @brief Outputs a debug string
*/ */
@ -143,7 +168,7 @@ namespace skyline {
nullptr, // 0x00 (Does not exist) nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01 SetHeapSize, // 0x01
nullptr, // 0x02 nullptr, // 0x02
nullptr, // 0x03 SetMemoryAttribute, // 0x03
nullptr, // 0x04 nullptr, // 0x04
nullptr, // 0x05 nullptr, // 0x05
QueryMemory, // 0x06 QueryMemory, // 0x06
@ -161,23 +186,23 @@ namespace skyline {
nullptr, // 0x12 nullptr, // 0x12
MapSharedMemory, // 0x13 MapSharedMemory, // 0x13
nullptr, // 0x14 nullptr, // 0x14
nullptr, // 0x15 CreateTransferMemory, // 0x15
CloseHandle, // 0x16 CloseHandle, // 0x16
nullptr, // 0x17 ResetSignal, // 0x17
WaitSynchronization, // 0x18 WaitSynchronization, // 0x18
nullptr, // 0x19 nullptr, // 0x19
ArbitrateLock, // 0x1a ArbitrateLock, // 0x1a
ArbitrateUnlock, // 0x1b ArbitrateUnlock, // 0x1b
WaitProcessWideKeyAtomic, // 0x1c WaitProcessWideKeyAtomic, // 0x1c
SignalProcessWideKey, // 0x1d SignalProcessWideKey, // 0x1d
nullptr, // 0x1e GetSystemTick, // 0x1e
ConnectToNamedPort, // 0x1f ConnectToNamedPort, // 0x1f
nullptr, // 0x20 nullptr, // 0x20
SendSyncRequest, // 0x21 SendSyncRequest, // 0x21
nullptr, // 0x22 nullptr, // 0x22
nullptr, // 0x23 nullptr, // 0x23
nullptr, // 0x24 nullptr, // 0x24
nullptr, // 0x25 GetThreadId, // 0x25
nullptr, // 0x26 nullptr, // 0x26
OutputDebugString, // 0x27 OutputDebugString, // 0x27
nullptr, // 0x28 nullptr, // 0x28

View File

@ -17,8 +17,10 @@ namespace skyline::kernel::type {
* @brief Signals all threads waiting on this object * @brief Signals all threads waiting on this object
*/ */
virtual inline void Signal() { virtual inline void Signal() {
KSyncObject::Signal(); if (!signalled) {
signalled = true; KSyncObject::Signal();
signalled = true;
}
} }
/** /**

View File

@ -24,41 +24,48 @@ namespace skyline::kernel::type {
this->address = fregs.regs[0]; this->address = fregs.regs[0];
} }
u64 RemapPrivateFunc(u64 address, size_t oldSize, size_t size) { u64 RemapPrivateFunc(u64 address, size_t oldSize, size_t size, u64 perms) {
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), oldSize, size, 0)); return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), oldSize, size, 0));
} }
void KPrivateMemory::Resize(size_t newSize) { u64 KPrivateMemory::Resize(size_t newSize, bool canMove) {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = address; fregs.regs[0] = address;
fregs.regs[1] = size; fregs.regs[1] = size;
fregs.regs[2] = newSize; fregs.regs[2] = newSize;
fregs.regs[3] = static_cast<u64>(PROT_READ | PROT_WRITE);
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, owner); state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, owner);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED) if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping private region in child process"); throw exception("An error occurred while remapping private region in child process");
address = fregs.regs[0];
size = newSize; size = newSize;
return address;
} }
u64 UpdatePermissionPrivateFunc(u64 address, size_t size, u64 perms) { u64 UpdatePermissionPrivateFunc(u64 address, size_t size, u64 perms) {
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms))); return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
} }
void KPrivateMemory::UpdatePermission(memory::Permission newPerms) { void KPrivateMemory::UpdatePermission(memory::Permission permission) {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = address; fregs.regs[0] = address;
fregs.regs[1] = size; fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(newPerms.Get()); fregs.regs[2] = static_cast<u64>(permission.Get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, owner); state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, owner);
if (static_cast<int>(fregs.regs[0]) == -1) if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating private region's permissions in child process"); throw exception("An error occurred while updating private region's permissions in child process");
permission = newPerms; this->permission = permission;
} }
memory::MemoryInfo KPrivateMemory::GetInfo() { memory::MemoryInfo KPrivateMemory::GetInfo(u64 address) {
memory::MemoryInfo info{}; memory::MemoryInfo info{};
info.baseAddress = address; info.baseAddress = address;
info.size = size; info.size = size;
info.type = static_cast<u64>(type); info.type = static_cast<u64>(type);
for (const auto &region : regionInfoVec) {
if ((address >= region.address) && (address < (region.address + region.size)))
info.memoryAttribute.isUncached = region.isUncached;
}
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0); info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0); info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);
info.perms = permission; info.perms = permission;
@ -73,10 +80,11 @@ namespace skyline::kernel::type {
KPrivateMemory::~KPrivateMemory() { KPrivateMemory::~KPrivateMemory() {
try { try {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = address; fregs.regs[0] = address;
fregs.regs[1] = size; fregs.regs[1] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, owner); state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, owner);
} catch (const std::exception&) {} } catch (const std::exception &) {
}
} }
}; };

View File

@ -19,9 +19,9 @@ namespace skyline::kernel::type {
u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC u16 deviceRefCount{}; //!< The amount of reference to this memory for IPC
memory::Permission permission; //!< The permissions for the allocated memory memory::Permission permission; //!< The permissions for the allocated memory
const memory::Type type; //!< The type of this memory allocation const memory::Type type; //!< The type of this memory allocation
std::vector<memory::RegionInfo> regionInfoVec; //!< This holds information about specific memory regions
/** /**
* @brief Constructor of a private memory object
* @param state The state of the device * @param state The state of the device
* @param pid The PID of the main * @param pid The PID of the main
* @param dstAddress The address to map to (If NULL then an arbitrary address is picked) * @param dstAddress The address to map to (If NULL then an arbitrary address is picked)
@ -35,24 +35,26 @@ namespace skyline::kernel::type {
/** /**
* @brief Remap a chunk of memory as to change the size occupied by it * @brief Remap a chunk of memory as to change the size occupied by it
* @param newSize The new size of the memory * @param newSize The new size of the memory
* @param canMove If the memory can move if there is not enough space at the current address
* @return The address the memory was remapped to
*/ */
void Resize(size_t newSize); u64 Resize(size_t newSize, bool canMove);
/** /**
* @brief Updates the permissions of a chunk of mapped memory * @brief Updates the permissions of a chunk of mapped memory
* @param perms The new permissions to be set for the memory * @param permission The new permissions to be set for the memory
*/ */
void UpdatePermission(memory::Permission newPerms); void UpdatePermission(memory::Permission permission);
/** /**
* @brief Returns a MemoryInfo object * @brief Returns a MemoryInfo object
* @param pid The PID of the requesting process * @param address The specific address being queried (Used to fill MemoryAttribute)
* @return A Memory::MemoryInfo struct based on attributes of the memory * @return A Memory::MemoryInfo struct based on attributes of the memory
*/ */
memory::MemoryInfo GetInfo(); memory::MemoryInfo GetInfo(u64 address);
/** /**
* @brief Destructor of private memory, it deallocates the memory * @brief The destructor of private memory, it deallocates the memory
*/ */
~KPrivateMemory(); ~KPrivateMemory();
}; };

View File

@ -24,10 +24,10 @@ namespace skyline::kernel::type {
} }
u64 KProcess::GetTlsSlot() { u64 KProcess::GetTlsSlot() {
for (auto &tlsPage: tlsPages) { for (auto &tlsPage: tlsPages) {
if (!tlsPage->Full()) if (!tlsPage->Full())
return tlsPage->ReserveSlot(); return tlsPage->ReserveSlot();
} }
auto tlsMem = NewHandle<KPrivateMemory>(mainThread, 0, 0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal).item; auto tlsMem = NewHandle<KPrivateMemory>(mainThread, 0, 0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal).item;
memoryMap[tlsMem->address] = tlsMem; memoryMap[tlsMem->address] = tlsMem;
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address)); tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
@ -39,11 +39,11 @@ namespace skyline::kernel::type {
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize) : mainThread(pid), mainThreadStackSz(stackSize), KSyncObject(state, KType::KProcess) { KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize) : mainThread(pid), mainThreadStackSz(stackSize), KSyncObject(state, KType::KProcess) {
state.nce->WaitRdy(pid); state.nce->WaitRdy(pid);
threadMap[pid] = NewHandle<KThread>(pid, entryPoint, 0, stackBase + stackSize, GetTlsSlot(), constant::DefaultPriority, this).item; threadMap[pid] = NewHandle<KThread>(pid, entryPoint, 0x0, stackBase + stackSize, GetTlsSlot(), constant::DefaultPriority, this).item;
MapPrivateRegion(0, constant::DefHeapSize, {true, true, true}, memory::Type::Heap, memory::Region::Heap); MapPrivateRegion(constant::HeapAddr, constant::DefHeapSize, {true, true, false}, memory::Type::Heap, memory::Region::Heap);
memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise) memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
if (memFd == -1) if (memFd == -1)
throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem, \"{}\"", pid, strerror(errno))); throw exception("Cannot open file descriptor to /proc/{}/mem, \"{}\"", pid, strerror(errno));
} }
KProcess::~KProcess() { KProcess::~KProcess() {
@ -70,9 +70,9 @@ namespace skyline::kernel::type {
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, mainThread); state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, mainThread);
auto pid = static_cast<pid_t>(fregs.regs[0]); auto pid = static_cast<pid_t>(fregs.regs[0]);
if (pid == -1) if (pid == -1)
throw exception(fmt::format("Cannot create thread: Address: 0x{:X}, Stack Top: 0x{:X}", entryPoint, stackTop)); throw exception("Cannot create thread: Address: 0x{:X}, Stack Top: 0x{:X}", entryPoint, stackTop);
threadMap[pid] = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this).item; threadMap[pid] = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this).item;
state.logger->Write(Logger::Info, "EP: 0x{:X}, EA: 0x{:X}, STP: 0x{:X}, PR: 0x{:X}, TLS: {}", entryPoint, entryArg, stackTop, priority, threadMap[pid]->tls); state.logger->Debug("A new thread was created: EP: 0x{:X}, EA: 0x{:X}, STP: 0x{:X}, PR: 0x{:X}, TLS: {}", entryPoint, entryArg, stackTop, priority, threadMap[pid]->tls);
return threadMap[pid]; return threadMap[pid];
} }
@ -95,6 +95,14 @@ namespace skyline::kernel::type {
return mem; return mem;
} }
bool KProcess::UnmapPrivateRegion(const skyline::memory::Region region) {
if (!memoryRegionMap.count(region))
return false;
memoryMap.erase(memoryRegionMap.at(region)->address);
memoryRegionMap.erase(region);
return true;
}
size_t KProcess::GetProgramSize() { size_t KProcess::GetProgramSize() {
size_t sharedSize = 0; size_t sharedSize = 0;
for (auto &region : memoryRegionMap) for (auto &region : memoryRegionMap)
@ -106,7 +114,7 @@ namespace skyline::kernel::type {
auto mtxVec = state.thisProcess->mutexMap[address]; auto mtxVec = state.thisProcess->mutexMap[address];
u32 mtxVal = state.thisProcess->ReadMemory<u32>(address); u32 mtxVal = state.thisProcess->ReadMemory<u32>(address);
if (mtxVec.empty()) { if (mtxVec.empty()) {
mtxVal = (mtxVal & ~constant::mtxOwnerMask) | state.thisThread->handle; mtxVal = (mtxVal & ~constant::MtxOwnerMask) | state.thisThread->handle;
state.thisProcess->WriteMemory(mtxVal, address); state.thisProcess->WriteMemory(mtxVal, address);
} else { } else {
for (auto thread = mtxVec.begin();; thread++) { for (auto thread = mtxVec.begin();; thread++) {
@ -118,25 +126,29 @@ namespace skyline::kernel::type {
break; break;
} }
} }
state.thisThread->status = KThread::ThreadStatus::WaitMutex; state.thisThread->status = KThread::Status::WaitMutex;
} }
} }
void KProcess::MutexUnlock(u64 address) { void KProcess::MutexUnlock(u64 address) {
auto mtxVec = state.thisProcess->mutexMap[address]; auto mtxVec = state.thisProcess->mutexMap[address];
u32 mtxVal = state.thisProcess->ReadMemory<u32>(address); u32 mtxVal = state.thisProcess->ReadMemory<u32>(address);
if ((mtxVal & constant::mtxOwnerMask) != state.thisThread->pid) if ((mtxVal & constant::MtxOwnerMask) != state.thisThread->pid)
throw exception("A non-owner thread tried to release a mutex"); throw exception("A non-owner thread tried to release a mutex");
if (mtxVec.empty()) { if (mtxVec.empty()) {
mtxVal = 0; mtxVal = 0;
} else { } else {
auto &thread = mtxVec.front(); auto &thread = mtxVec.front();
mtxVal = (mtxVal & ~constant::mtxOwnerMask) | thread->handle; mtxVal = (mtxVal & ~constant::MtxOwnerMask) | thread->handle;
thread->status = KThread::ThreadStatus::Runnable; thread->status = KThread::Status::Runnable;
mtxVec.erase(mtxVec.begin()); mtxVec.erase(mtxVec.begin());
if (!mtxVec.empty()) if (!mtxVec.empty())
mtxVal |= (~constant::mtxOwnerMask); mtxVal |= (~constant::MtxOwnerMask);
} }
state.thisProcess->WriteMemory(mtxVal, address); state.thisProcess->WriteMemory(mtxVal, address);
} }
void KProcess::ResetSignal() {
signalled = false;
}
} }

View File

@ -2,8 +2,10 @@
#include "KThread.h" #include "KThread.h"
#include "KPrivateMemory.h" #include "KPrivateMemory.h"
#include "KTransferMemory.h"
#include "KSharedMemory.h" #include "KSharedMemory.h"
#include "KSession.h" #include "KSession.h"
#include "KEvent.h"
namespace skyline::kernel::type { namespace skyline::kernel::type {
/** /**
@ -54,17 +56,16 @@ namespace skyline::kernel::type {
*/ */
u64 GetTlsSlot(); u64 GetTlsSlot();
int memFd; //!< The file descriptor to the memory of the process
public: public:
enum class ProcessStatus { enum class Status {
Created, //!< The process was created but the main thread has not started yet Created, //!< The process was created but the main thread has not started yet
Started, //!< The process has been started Started, //!< The process has been started
Exiting //!< The process is exiting Exiting //!< The process is exiting
} status = ProcessStatus::Created; //!< The state of the process } status = Status::Created; //!< The state of the process
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
pid_t mainThread; //!< The PID of the main thread pid_t mainThread; //!< The PID of the main thread
size_t mainThreadStackSz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se) size_t mainThreadStackSz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
int memFd; //!< The file descriptor to the memory of the process
std::unordered_map<u64, std::shared_ptr<KPrivateMemory>> memoryMap; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory, used to keep track of KPrivateMemory instances std::unordered_map<u64, std::shared_ptr<KPrivateMemory>> memoryMap; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory, used to keep track of KPrivateMemory instances
std::unordered_map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory std::unordered_map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
std::unordered_map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object std::unordered_map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
@ -79,8 +80,8 @@ namespace skyline::kernel::type {
*/ */
template<typename objectClass> template<typename objectClass>
struct HandleOut { struct HandleOut {
std::shared_ptr<objectClass> item; std::shared_ptr<objectClass> item; //!< A shared pointer to the object
handle_t handle; handle_t handle; //!< The handle of the object in the process
}; };
/** /**
@ -115,7 +116,7 @@ namespace skyline::kernel::type {
* @return An object of type T with read data * @return An object of type T with read data
*/ */
template<typename Type> template<typename Type>
Type ReadMemory(u64 address) const { inline Type ReadMemory(u64 address) const {
Type item{}; Type item{};
ReadMemory(&item, address, sizeof(Type)); ReadMemory(&item, address, sizeof(Type));
return item; return item;
@ -128,7 +129,7 @@ namespace skyline::kernel::type {
* @param address The address of the object * @param address The address of the object
*/ */
template<typename Type> template<typename Type>
void WriteMemory(Type &item, u64 address) const { inline void WriteMemory(Type &item, u64 address) const {
WriteMemory(&item, address, sizeof(Type)); WriteMemory(&item, address, sizeof(Type));
} }
@ -165,26 +166,33 @@ namespace skyline::kernel::type {
*/ */
HandleOut<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region); HandleOut<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region);
/**
* @brief Unmap a chunk of process local memory (private memory)
* @param region The region of memory to unmap
* @return If the region was mapped at all
*/
bool UnmapPrivateRegion(const memory::Region region);
/** /**
* @brief Returns the total memory occupied by regions mapped for the process * @brief Returns the total memory occupied by regions mapped for the process
*/ */
size_t GetProgramSize(); size_t GetProgramSize();
/** /**
* @brief Creates a new handle to a KObject and adds it to the process handle_table * @brief Creates a new handle to a KObject and adds it to the process handle_table
* @tparam objectClass The class of the kernel object to create * @tparam objectClass The class of the kernel object to create
* @param args The arguments for the kernel object except handle, pid and state * @param args The arguments for the kernel object except handle, pid and state
* @return A shared pointer to the corresponding object * @return A shared pointer to the corresponding object
*/ */
template<typename objectClass, typename ...objectArgs> template<typename objectClass, typename ...objectArgs>
HandleOut<objectClass> NewHandle(objectArgs... args) { HandleOut<objectClass> NewHandle(objectArgs... args) {
std::shared_ptr<objectClass> item; std::shared_ptr<objectClass> item;
if constexpr (std::is_same<objectClass, KThread>()) if constexpr (std::is_same<objectClass, KThread>())
item = std::make_shared<objectClass>(state, handleIndex, args...); item = std::make_shared<objectClass>(state, handleIndex, args...);
else else
item = std::make_shared<objectClass>(state, args...); item = std::make_shared<objectClass>(state, args...);
handleTable[handleIndex] = std::static_pointer_cast<KObject>(item); handleTable[handleIndex] = std::static_pointer_cast<KObject>(item);
return {item, handleIndex++}; return {item, handleIndex++};
} }
/** /**
@ -193,7 +201,7 @@ namespace skyline::kernel::type {
* @return The handle of the corresponding item in the handle table * @return The handle of the corresponding item in the handle table
*/ */
template<typename objectClass> template<typename objectClass>
handle_t InsertItem(std::shared_ptr<objectClass> item) { handle_t InsertItem(std::shared_ptr<objectClass> &item) {
handleTable[handleIndex] = std::static_pointer_cast<KObject>(item); handleTable[handleIndex] = std::static_pointer_cast<KObject>(item);
return handleIndex++; return handleIndex++;
} }
@ -210,13 +218,17 @@ namespace skyline::kernel::type {
if constexpr(std::is_same<objectClass, KThread>()) if constexpr(std::is_same<objectClass, KThread>())
objectType = KType::KThread; objectType = KType::KThread;
else if constexpr(std::is_same<objectClass, KProcess>()) else if constexpr(std::is_same<objectClass, KProcess>())
objectType = KType::KProcess; objectType = KType::KProcess;
else if constexpr(std::is_same<objectClass, KSharedMemory>()) else if constexpr(std::is_same<objectClass, KSharedMemory>())
objectType = KType::KSharedMemory; objectType = KType::KSharedMemory;
else if constexpr(std::is_same<objectClass, KTransferMemory>())
objectType = KType::KTransferMemory;
else if constexpr(std::is_same<objectClass, KPrivateMemory>()) else if constexpr(std::is_same<objectClass, KPrivateMemory>())
objectType = KType::KPrivateMemory; objectType = KType::KPrivateMemory;
else if constexpr(std::is_same<objectClass, KSession>()) else if constexpr(std::is_same<objectClass, KSession>())
objectType = KType::KSession; objectType = KType::KSession;
else if constexpr(std::is_same<objectClass, KEvent>())
objectType = KType::KEvent;
else else
throw exception("KProcess::GetHandle couldn't determine object type"); throw exception("KProcess::GetHandle couldn't determine object type");
try { try {
@ -224,9 +236,9 @@ namespace skyline::kernel::type {
if (item->objectType == objectType) if (item->objectType == objectType)
return std::static_pointer_cast<objectClass>(item); return std::static_pointer_cast<objectClass>(item);
else else
throw exception(fmt::format("Tried to get kernel object (0x{:X}) with different type: {} when object is {}", handle, objectType, item->objectType)); throw exception("Tried to get kernel object (0x{:X}) with different type: {} when object is {}", handle, objectType, item->objectType);
} catch (std::out_of_range) { } catch (std::out_of_range) {
throw exception(fmt::format("GetHandle was called with invalid handle: 0x{:X}", handle)); throw exception("GetHandle was called with invalid handle: 0x{:X}", handle);
} }
} }
@ -241,5 +253,10 @@ namespace skyline::kernel::type {
* @param address The address of the mutex * @param address The address of the mutex
*/ */
void MutexUnlock(u64 address); void MutexUnlock(u64 address);
/**
* @brief This resets the object to an unsignalled state
*/
void ResetSignal();
}; };
} }

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <common.h> #include <common.h>
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include "KSyncObject.h" #include "KSyncObject.h"
namespace skyline::kernel::type { namespace skyline::kernel::type {
@ -14,7 +14,7 @@ namespace skyline::kernel::type {
std::unordered_map<handle_t, std::shared_ptr<service::BaseService>> domainTable; //!< This maps from a virtual handle to it's service std::unordered_map<handle_t, std::shared_ptr<service::BaseService>> domainTable; //!< This maps from a virtual handle to it's service
handle_t handleIndex = constant::BaseVirtualHandleIndex; handle_t handleIndex = constant::BaseVirtualHandleIndex;
const service::Service serviceType; //!< The type of the service const service::Service serviceType; //!< The type of the service
enum class ServiceStatus { Open, Closed } serviceStatus = ServiceStatus::Open; //!< If the session is open or closed enum class ServiceStatus { Open, Closed } serviceStatus{ServiceStatus::Open}; //!< If the session is open or closed
bool isDomain{}; //!< Holds if this is a domain session or not bool isDomain{}; //!< Holds if this is a domain session or not
/** /**

View File

@ -3,7 +3,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
constexpr const char* ASHMEM_NAME_DEF = "dev/ashmem"; constexpr const char *ASHMEM_NAME_DEF = "dev/ashmem";
constexpr int ASHMEM_SET_SIZE = 0x40087703; constexpr int ASHMEM_SET_SIZE = 0x40087703;
namespace skyline::kernel::type { namespace skyline::kernel::type {
@ -14,12 +14,12 @@ namespace skyline::kernel::type {
KSharedMemory::KSharedMemory(const DeviceState &state, pid_t pid, u64 kaddress, size_t ksize, const memory::Permission localPermission, const memory::Permission remotePermission, memory::Type type) : kaddress(kaddress), ksize(ksize), localPermission(localPermission), remotePermission(remotePermission), type(type), owner(pid), KObject(state, KType::KSharedMemory) { KSharedMemory::KSharedMemory(const DeviceState &state, pid_t pid, u64 kaddress, size_t ksize, const memory::Permission localPermission, const memory::Permission remotePermission, memory::Type type) : kaddress(kaddress), ksize(ksize), localPermission(localPermission), remotePermission(remotePermission), type(type), owner(pid), KObject(state, KType::KSharedMemory) {
fd = open(ASHMEM_NAME_DEF, O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise) fd = open(ASHMEM_NAME_DEF, O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
if (fd < 0) if (fd < 0)
throw exception(fmt::format("An error occurred while opening {}: {}", ASHMEM_NAME_DEF, fd)); throw exception("An error occurred while opening {}: {}", ASHMEM_NAME_DEF, fd);
if (ioctl(fd, ASHMEM_SET_SIZE, ksize) < 0) // NOLINT(hicpp-signed-bitwise) if (ioctl(fd, ASHMEM_SET_SIZE, ksize) < 0) // NOLINT(hicpp-signed-bitwise)
throw exception(fmt::format("An error occurred while setting shared memory size: {}", ksize)); throw exception("An error occurred while setting shared memory size: {}", ksize);
kaddress = MapSharedFunc(kaddress, ksize, static_cast<u64>(pid ? remotePermission.Get() : localPermission.Get()), static_cast<u64>(fd)); kaddress = MapSharedFunc(kaddress, ksize, static_cast<u64>(pid ? remotePermission.Get() : localPermission.Get()), static_cast<u64>(fd));
if (kaddress == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise) if (kaddress == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise)
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno))); throw exception("An occurred while mapping shared region: {}", strerror(errno));
} }
u64 KSharedMemory::Map(u64 address, u64 size, pid_t process) { u64 KSharedMemory::Map(u64 address, u64 size, pid_t process) {
@ -43,13 +43,14 @@ namespace skyline::kernel::type {
} }
KSharedMemory::~KSharedMemory() { KSharedMemory::~KSharedMemory() {
for (auto [process, procInf] : procInfMap) { for (auto[process, procInf] : procInfMap) {
try { try {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = procInf.address; fregs.regs[0] = procInf.address;
fregs.regs[1] = procInf.size; fregs.regs[1] = procInf.size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process); state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process);
} catch (const std::exception&) {} } catch (const std::exception &) {
}
} }
UnmapSharedFunc(kaddress, ksize); UnmapSharedFunc(kaddress, ksize);
close(fd); close(fd);
@ -60,7 +61,7 @@ namespace skyline::kernel::type {
} }
void KSharedMemory::Resize(size_t newSize) { void KSharedMemory::Resize(size_t newSize) {
for (auto& [process, procInf] : procInfMap) { for (auto&[process, procInf] : procInfMap) {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = procInf.address; fregs.regs[0] = procInf.address;
fregs.regs[1] = procInf.size; fregs.regs[1] = procInf.size;
@ -71,7 +72,7 @@ namespace skyline::kernel::type {
procInf.size = newSize; procInf.size = newSize;
} }
if (RemapSharedFunc(kaddress, ksize, newSize) == reinterpret_cast<u64>(MAP_FAILED)) if (RemapSharedFunc(kaddress, ksize, newSize) == reinterpret_cast<u64>(MAP_FAILED))
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno))); throw exception("An occurred while remapping shared region: {}", strerror(errno));
ksize = newSize; ksize = newSize;
} }
@ -80,7 +81,7 @@ namespace skyline::kernel::type {
} }
void KSharedMemory::UpdatePermission(bool local, memory::Permission newPerms) { void KSharedMemory::UpdatePermission(bool local, memory::Permission newPerms) {
for (auto& [process, procInf] : procInfMap) { for (auto&[process, procInf] : procInfMap) {
if ((local && process == owner) || (!local && process != owner)) { if ((local && process == owner) || (!local && process != owner)) {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = procInf.address; fregs.regs[0] = procInf.address;
@ -93,7 +94,7 @@ namespace skyline::kernel::type {
} }
if ((local && owner == 0) || (!local && owner != 0)) if ((local && owner == 0) || (!local && owner != 0))
if (mprotect(reinterpret_cast<void *>(kaddress), ksize, newPerms.Get()) == -1) if (mprotect(reinterpret_cast<void *>(kaddress), ksize, newPerms.Get()) == -1)
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno))); throw exception("An occurred while updating shared region's permissions: {}", strerror(errno));
if (local) if (local)
localPermission = newPerms; localPermission = newPerms;
else else
@ -102,8 +103,9 @@ namespace skyline::kernel::type {
memory::MemoryInfo KSharedMemory::GetInfo(pid_t process) { memory::MemoryInfo KSharedMemory::GetInfo(pid_t process) {
memory::MemoryInfo info{}; memory::MemoryInfo info{};
info.baseAddress = kaddress; const auto &procInf = procInfMap.at(process);
info.size = ksize; info.baseAddress = procInf.address;
info.size = procInf.size;
info.type = static_cast<u64>(type); info.type = static_cast<u64>(type);
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0); info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0); info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);

View File

@ -10,13 +10,16 @@ namespace skyline::kernel::type {
class KSharedMemory : public KObject { class KSharedMemory : public KObject {
private: private:
int fd; //!< A file descriptor to the underlying shared memory int fd; //!< A file descriptor to the underlying shared memory
struct ProcInf {
public:
/**
* @brief This holds the address and size of a process's mapping
*/
struct ProcessInfo {
u64 address; u64 address;
size_t size; size_t size;
}; };
std::unordered_map<pid_t, ProcInf> procInfMap; //!< Maps from a PID to where the memory was mapped to std::unordered_map<pid_t, ProcessInfo> procInfMap; //!< Maps from a PID to where the memory was mapped to
public:
pid_t owner; //!< The PID of the process owning this shared memory pid_t owner; //!< The PID of the process owning this shared memory
u64 kaddress; //!< The address of the allocated memory for the kernel u64 kaddress; //!< The address of the allocated memory for the kernel
size_t ksize; //!< The size of the allocated memory size_t ksize; //!< The size of the allocated memory
@ -66,7 +69,7 @@ namespace skyline::kernel::type {
memory::MemoryInfo GetInfo(pid_t process); memory::MemoryInfo GetInfo(pid_t process);
/** /**
* @brief Destructor of shared memory, it deallocates the memory from all processes * @brief The destructor of shared memory, it deallocates the memory from all processes
*/ */
~KSharedMemory(); ~KSharedMemory();
}; };

View File

@ -4,9 +4,12 @@
namespace skyline::kernel::type { namespace skyline::kernel::type {
KSyncObject::KSyncObject(const skyline::DeviceState &state, skyline::kernel::type::KType type) : KObject(state, type) {} KSyncObject::KSyncObject(const skyline::DeviceState &state, skyline::kernel::type::KType type) : KObject(state, type) {}
KSyncObject::threadInfo::threadInfo(pid_t process, handle_t handle) : process(process), handle(handle) {}
void KSyncObject::Signal() { void KSyncObject::Signal() {
for(const auto& pid : waitThreads) { for (const auto &info : waitThreads) {
state.os->processMap.at(pid)->threadMap.at(pid)->status = KThread::ThreadStatus::Runnable; state.nce->SetRegister(Wreg::W1, info.handle);
state.os->processMap.at(info.process)->threadMap.at(info.process)->status = KThread::Status::Runnable;
} }
} }
} }

View File

@ -4,13 +4,22 @@
#include "KObject.h" #include "KObject.h"
namespace skyline::kernel::type { namespace skyline::kernel::type {
/** /**
* @brief KSyncObject holds the state of a waitable object * @brief KSyncObject holds the state of a waitable object
*/ */
class KSyncObject : public KObject { class KSyncObject : public KObject {
public: public:
bool signalled = false; //!< If the current object is signalled (Used by KEvent as it stays signalled till svcClearEvent or svcClearSignal is called) bool signalled{false}; //!< If the current object is signalled (Used by KEvent as it stays signalled till svcClearEvent or svcClearSignal is called)
std::vector<pid_t> waitThreads; //!< A vector of threads waiting on this object /**
* @brief This holds information about a specific thread that's waiting on this object
*/
struct threadInfo {
pid_t process; //!< The PID of the waiting thread
handle_t handle; //!< The handle in the process's handle table
threadInfo(pid_t process, handle_t handle);
};
std::vector<threadInfo> waitThreads; //!< A vector of threads waiting on this object
/** /**
* @param state The state of the device * @param state The state of the device

View File

@ -14,8 +14,8 @@ namespace skyline::kernel::type {
void KThread::Start() { void KThread::Start() {
if (pid == parent->mainThread) if (pid == parent->mainThread)
parent->status = KProcess::ProcessStatus::Started; parent->status = KProcess::Status::Started;
status = ThreadStatus::Running; status = Status::Running;
state.nce->StartProcess(entryPoint, entryArg, stackTop, handle, pid); state.nce->StartProcess(entryPoint, entryArg, stackTop, handle, pid);
} }
@ -23,6 +23,18 @@ namespace skyline::kernel::type {
this->priority = priority; this->priority = priority;
auto liPriority = static_cast<int8_t>(constant::PriorityAn.first + ((static_cast<float>(constant::PriorityAn.second - constant::PriorityAn.first) / static_cast<float>(constant::PriorityNin.second - constant::PriorityNin.first)) * (static_cast<float>(priority) - constant::PriorityNin.first))); // Resize range PriorityNin (Nintendo Priority) to PriorityAn (Android Priority) auto liPriority = static_cast<int8_t>(constant::PriorityAn.first + ((static_cast<float>(constant::PriorityAn.second - constant::PriorityAn.first) / static_cast<float>(constant::PriorityNin.second - constant::PriorityNin.first)) * (static_cast<float>(priority) - constant::PriorityNin.first))); // Resize range PriorityNin (Nintendo Priority) to PriorityAn (Android Priority)
if (setpriority(PRIO_PROCESS, static_cast<id_t>(pid), liPriority) == -1) if (setpriority(PRIO_PROCESS, static_cast<id_t>(pid), liPriority) == -1)
throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", liPriority, pid)); throw exception("Couldn't set process priority to {} for PID: {}", liPriority, pid);
}
void KThread::Sleep() {
if (status == Status::Running) {
status = Status::Sleeping;
timeout = 0;
}
}
void KThread::WakeUp() {
if (status == Status::Sleeping)
status = Status::Runnable;
} }
} }

View File

@ -13,7 +13,7 @@ namespace skyline::kernel::type {
u64 entryArg; //!< An argument to pass to the process on entry u64 entryArg; //!< An argument to pass to the process on entry
public: public:
enum class ThreadStatus { enum class Status {
Created, //!< The thread has been created but has not been started yet Created, //!< The thread has been created but has not been started yet
Running, //!< The thread is running currently Running, //!< The thread is running currently
Sleeping, //!< The thread is sleeping due to svcSleepThread Sleeping, //!< The thread is sleeping due to svcSleepThread
@ -21,7 +21,7 @@ namespace skyline::kernel::type {
WaitMutex, //!< The thread is waiting on a Mutex WaitMutex, //!< The thread is waiting on a Mutex
WaitCondVar, //!< The thread is waiting on a Conditional Variable WaitCondVar, //!< The thread is waiting on a Conditional Variable
Runnable //!< The thread is ready to run after waiting Runnable //!< The thread is ready to run after waiting
} status = ThreadStatus::Created; //!< The state of the thread } status = Status::Created; //!< The state of the thread
std::vector<std::shared_ptr<KSyncObject>> waitObjects; //!< A vector holding handles this thread is waiting for std::vector<std::shared_ptr<KSyncObject>> waitObjects; //!< A vector holding handles this thread is waiting for
u64 timeout{}; //!< The end of a timeout for svcWaitSynchronization or the end of the sleep period for svcSleepThread u64 timeout{}; //!< The end of a timeout for svcWaitSynchronization or the end of the sleep period for svcSleepThread
handle_t handle; // The handle of the object in the handle table handle_t handle; // The handle of the object in the handle table
@ -49,10 +49,20 @@ namespace skyline::kernel::type {
~KThread(); ~KThread();
/** /**
* @brief Starts the current thread * @brief This starts this thread
*/ */
void Start(); void Start();
/**
* @brief This causes this thread to sleep indefinitely (no-op if thread is already sleeping)
*/
void Sleep();
/**
* @brief This wakes up the thread from it's sleep (no-op if thread is already awake)
*/
void WakeUp();
/** /**
* @brief Update the priority level for the process. * @brief Update the priority level for the process.
* @details Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority]. We rescale the priority from Nintendo scale to that of Android. * @details Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority]. We rescale the priority from Nintendo scale to that of Android.

View File

@ -47,11 +47,8 @@ namespace skyline::kernel::type {
if (reinterpret_cast<void *>(address) == MAP_FAILED) if (reinterpret_cast<void *>(address) == MAP_FAILED)
throw exception("An error occurred while mapping transfer memory in kernel"); throw exception("An error occurred while mapping transfer memory in kernel");
} }
size_t copySz = std::min(size, cSize); size_t copySz = std::min(size, cSize);
if (process && owner) { if (process && owner) {
// TODO: Update when copy_file_range (http://man7.org/linux/man-pages/man2/copy_file_range.2.html) is added to bionic
std::vector<u8> tempBuf(copySz); std::vector<u8> tempBuf(copySz);
state.os->processMap.at(process)->ReadMemory(tempBuf.data(), cAddress, copySz); state.os->processMap.at(process)->ReadMemory(tempBuf.data(), cAddress, copySz);
state.os->processMap.at(owner)->WriteMemory(tempBuf.data(), address, copySz); state.os->processMap.at(owner)->WriteMemory(tempBuf.data(), address, copySz);
@ -62,7 +59,6 @@ namespace skyline::kernel::type {
} else { } else {
throw exception("Transferring from kernel to kernel is not supported"); throw exception("Transferring from kernel to kernel is not supported");
} }
if (owner) { if (owner) {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = address; fregs.regs[0] = address;
@ -75,7 +71,6 @@ namespace skyline::kernel::type {
if (reinterpret_cast<void *>(UnmapTransferFunc(address, size)) == MAP_FAILED) if (reinterpret_cast<void *>(UnmapTransferFunc(address, size)) == MAP_FAILED)
throw exception("An error occurred while unmapping transfer memory in kernel"); throw exception("An error occurred while unmapping transfer memory in kernel");
} }
owner = process; owner = process;
cAddress = address; cAddress = address;
cSize = size; cSize = size;
@ -96,7 +91,7 @@ namespace skyline::kernel::type {
} }
KTransferMemory::~KTransferMemory() { KTransferMemory::~KTransferMemory() {
if(owner) { if (owner) {
try { try {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = cAddress; fregs.regs[0] = cAddress;

View File

@ -42,7 +42,7 @@ namespace skyline::kernel::type {
memory::MemoryInfo GetInfo(); memory::MemoryInfo GetInfo();
/** /**
* @brief Destructor of private memory, it deallocates the memory * @brief The destructor of private memory, it deallocates the memory
*/ */
~KTransferMemory(); ~KTransferMemory();
}; };

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <string>
#include <os.h> #include <os.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
@ -23,6 +22,31 @@ namespace skyline::loader {
file.read(reinterpret_cast<char *>(output), size); file.read(reinterpret_cast<char *>(output), size);
} }
/**
* @brief This patches specific parts of the code
* @param code A vector with the code to be patched
*/
inline void PatchCode(std::vector<u8> &code) {
u32 *address = reinterpret_cast<u32 *>(code.data());
u32 *end = address + (code.size() / sizeof(u32));
while (address < end) {
auto instrSvc = reinterpret_cast<instr::Svc *>(address);
auto instrMrs = reinterpret_cast<instr::Mrs *>(address);
if (instrSvc->Verify()) {
instr::Brk brk(static_cast<u16>(instrSvc->value));
*address = *reinterpret_cast<u32 *>(&brk);
} else if (instrMrs->Verify()) {
if (instrMrs->srcReg == constant::TpidrroEl0) {
instr::Brk brk(static_cast<u16>(constant::SvcLast + 1 + instrMrs->dstReg));
*address = *reinterpret_cast<u32 *>(&brk);
} else if (instrMrs->srcReg == constant::CntpctEl0) {
instr::Mrs mrs(constant::CntvctEl0, instrMrs->dstReg);
*address = *reinterpret_cast<u32 *>(&mrs);
}
}
address++;
}
}
public: public:
/** /**

View File

@ -5,22 +5,10 @@ namespace skyline::loader {
NroLoader::NroLoader(std::string filePath) : Loader(filePath) { NroLoader::NroLoader(std::string filePath) : Loader(filePath) {
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader)); ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
if (header.magic != constant::NroMagic) if (header.magic != constant::NroMagic)
throw exception(fmt::format("Invalid NRO magic! 0x{0:X}", header.magic)); throw exception("Invalid NRO magic! 0x{0:X}", header.magic);
} }
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
process->MapPrivateRegion(constant::BaseAddr, header.text.size, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // R-X
state.logger->Write(Logger::Debug, "Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr, header.text.size);
process->MapPrivateRegion(constant::BaseAddr + header.text.size, header.ro.size, {true, false, false}, memory::Type::CodeReadOnly, memory::Region::RoData); // R--
state.logger->Write(Logger::Debug, "Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + header.text.size, header.ro.size);
process->MapPrivateRegion(constant::BaseAddr + header.text.size + header.ro.size, header.data.size, {true, true, false}, memory::Type::CodeStatic, memory::Region::Data); // RW-
state.logger->Write(Logger::Debug, "Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + header.text.size + header.ro.size, header.data.size);
process->MapPrivateRegion(constant::BaseAddr + header.text.size + header.ro.size + header.data.size, header.bssSize, {true, true, true}, memory::Type::CodeMutable, memory::Region::Bss); // RWX
state.logger->Write(Logger::Debug, "Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + header.text.size + header.ro.size + header.data.size, header.bssSize);
std::vector<u8> text(header.text.size); std::vector<u8> text(header.text.size);
std::vector<u8> rodata(header.ro.size); std::vector<u8> rodata(header.ro.size);
std::vector<u8> data(header.data.size); std::vector<u8> data(header.data.size);
@ -29,27 +17,26 @@ namespace skyline::loader {
ReadOffset(rodata.data(), header.ro.offset, header.ro.size); ReadOffset(rodata.data(), header.ro.offset, header.ro.size);
ReadOffset(data.data(), header.data.offset, header.data.size); ReadOffset(data.data(), header.data.offset, header.data.size);
// Replace SVC & MRS with BRK PatchCode(text);
for (auto address = reinterpret_cast<u32*>(text.data()); address <= reinterpret_cast<u32*>(text.data() + header.text.size); address++) {
auto instrSvc = reinterpret_cast<instr::Svc *>(address);
auto instrMrs = reinterpret_cast<instr::Mrs *>(address);
if (instrSvc->Verify()) { u64 textSize = text.size();
instr::Brk brk(static_cast<u16>(instrSvc->value)); u64 rodataSize = rodata.size();
*address = *reinterpret_cast<u32 *>(&brk); u64 dataSize = data.size();
} else if (instrMrs->Verify()) {
if(instrMrs->srcReg == constant::TpidrroEl0) {
instr::Brk brk(static_cast<u16>(constant::SvcLast + 1 + instrMrs->dstReg));
*address = *reinterpret_cast<u32 *>(&brk);
} else if(instrMrs->srcReg == constant::CntpctEl0) {
instr::Mrs mrs(constant::CntvctEl0, instrMrs->dstReg);
*address = *reinterpret_cast<u32 *>(&mrs);
}
}
}
process->WriteMemory(text.data(), constant::BaseAddr, header.text.size); process->MapPrivateRegion(constant::BaseAddr, textSize, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // R-X
process->WriteMemory(rodata.data(), constant::BaseAddr + header.text.size, header.ro.size); state.logger->Debug("Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr, textSize);
process->WriteMemory(data.data(), constant::BaseAddr + header.text.size + header.ro.size, header.data.size);
process->MapPrivateRegion(constant::BaseAddr + textSize, rodataSize, {true, false, false}, memory::Type::CodeReadOnly, memory::Region::RoData); // R--
state.logger->Debug("Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize, rodataSize);
process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize, dataSize, {true, true, false}, memory::Type::CodeStatic, memory::Region::Data); // RW-
state.logger->Debug("Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize, dataSize);
process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize + dataSize, header.bssSize, {true, true, true}, memory::Type::CodeMutable, memory::Region::Bss); // RWX
state.logger->Debug("Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize + dataSize, header.bssSize);
process->WriteMemory(text.data(), constant::BaseAddr, textSize);
process->WriteMemory(rodata.data(), constant::BaseAddr + textSize, rodataSize);
process->WriteMemory(data.data(), constant::BaseAddr + textSize + rodataSize, dataSize);
} }
} }

View File

@ -7,39 +7,39 @@ namespace skyline::loader {
class NroLoader : public Loader { class NroLoader : public Loader {
private: private:
/** /**
* @brief The structure of a single Segment descriptor in the NRO's header * @brief This holds a single data segment's offset and size
*/ */
struct NroSegmentHeader { struct NroSegmentHeader {
u32 offset; u32 offset; //!< The offset of the region
u32 size; u32 size; //!< The size of the region
}; };
/** /**
* @brief A bit-field struct to read the header of an NRO directly * @brief This holds the header of an NRO file
*/ */
struct NroHeader { struct NroHeader {
u32 : 32; u32 : 32;
u32 mod_offset; u32 modOffset; //!< The offset of the MOD metadata
u64 : 64; u64 : 64;
u32 magic; u32 magic; //!< The NRO magic "NRO0"
u32 version; u32 version; //!< The version of the application
u32 size; u32 size; //!< The size of the NRO
u32 flags; u32 flags; //!< The flags used with the NRO
NroSegmentHeader text; NroSegmentHeader text; //!< The .text segment header
NroSegmentHeader ro; NroSegmentHeader ro; //!< The .ro segment header
NroSegmentHeader data; NroSegmentHeader data; //!< The .data segment header
u32 bssSize; u32 bssSize; //!< The size of the bss segment
u32 : 32; u32 : 32;
u64 build_id[4]; u64 buildId[4]; //!< The build ID of the NRO
u64 : 64; u64 : 64;
NroSegmentHeader api_info; NroSegmentHeader apiInfo; //!< The .apiInfo segment header
NroSegmentHeader dynstr; NroSegmentHeader dynstr; //!< The .dynstr segment header
NroSegmentHeader dynsym; NroSegmentHeader dynsym; //!< The .dynsym segment header
} header {}; } header{};
public: public:
/** /**

View File

@ -8,7 +8,7 @@ namespace skyline::memory {
*/ */
struct Permission { struct Permission {
/** /**
* @brief Constructor that initializes all permissions to false * @brief This constructor initializes all permissions to false
*/ */
Permission() { Permission() {
r = 0; r = 0;
@ -42,9 +42,12 @@ namespace skyline::memory {
*/ */
int Get() const { int Get() const {
int perm = 0; int perm = 0;
if (r) perm |= PROT_READ; if (r)
if (w) perm |= PROT_WRITE; perm |= PROT_READ;
if (x) perm |= PROT_EXEC; if (w)
perm |= PROT_WRITE;
if (x)
perm |= PROT_EXEC;
return perm; return perm;
}; };
@ -61,6 +64,15 @@ namespace skyline::memory {
bool isUncached : 1; bool isUncached : 1;
}; };
/**
* @brief This describes the properties of a region of the allocated memory
*/
struct RegionInfo {
u64 address; //!< The starting address of the chunk of memory
u64 size; //!< The size of the chunk of memory
bool isUncached{}; //!< If the following region is uncached
};
/** /**
* @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo * @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
*/ */

View File

@ -10,84 +10,86 @@ namespace skyline {
iovec iov = {&registers, sizeof(registers)}; iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov); long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
if (status == -1) if (status == -1)
throw exception(fmt::format("Cannot read registers, PID: {}, Error: {}", pid, strerror(errno))); throw exception("Cannot read registers, PID: {}, Error: {}", pid, strerror(errno));
} }
void NCE::WriteRegisters(user_pt_regs &registers, pid_t pid) const { void NCE::WriteRegisters(user_pt_regs &registers, pid_t pid) const {
iovec iov = {&registers, sizeof(registers)}; iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_SETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov); long status = ptrace(PTRACE_SETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
if (status == -1) if (status == -1)
throw exception(fmt::format("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno))); throw exception("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno));
} }
instr::Brk NCE::ReadBrk(u64 address, pid_t pid) const { instr::Brk NCE::ReadBrk(u64 address, pid_t pid) const {
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : currPid, address, NULL); long status = ptrace(PTRACE_PEEKDATA, pid ? pid : currPid, address, NULL);
if (status == -1) if (status == -1)
throw exception(fmt::format("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno))); throw exception("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno));
return *(reinterpret_cast<instr::Brk *>(&status)); return *(reinterpret_cast<instr::Brk *>(&status));
} }
void NCE::Initialize(const DeviceState &state) { NCE::NCE(const DeviceState &state) : state(state) {}
this->state = &state;
}
void NCE::Execute() { void NCE::Execute() {
int status = 0; int status = 0;
while (!Halt && !state->os->processMap.empty()) { while (!Halt && !state.os->processMap.empty()) {
for (const auto &process : state->os->processMap) { // NOLINT(performance-for-range-copy) for (const auto &process : state.os->processMap) { // NOLINT(performance-for-range-copy)
state->os->thisProcess = process.second; state.os->thisProcess = process.second;
state->os->thisThread = process.second->threadMap.at(process.first); state.os->thisThread = process.second->threadMap.at(process.first);
currPid = process.first; currPid = process.first;
auto &currRegs = registerMap[currPid]; auto &currRegs = registerMap[currPid];
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Running) { if (state.thisThread->status == kernel::type::KThread::Status::Running) {
if (waitpid(state->thisThread->pid, &status, WNOHANG) == state->thisThread->pid) { if (waitpid(state.thisThread->pid, &status, WNOHANG) == state.thisThread->pid) {
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise) if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
ReadRegisters(currRegs); ReadRegisters(currRegs);
auto instr = ReadBrk(currRegs.pc); auto instr = ReadBrk(currRegs.pc);
if (instr.Verify()) { if (instr.Verify()) {
// We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0. // We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
if (instr.value <= constant::SvcLast) { if (instr.value <= constant::SvcLast) {
state->os->SvcHandler(static_cast<u16>(instr.value)); state.os->SvcHandler(static_cast<u16>(instr.value));
if (state->thisThread->status != kernel::type::KThread::ThreadStatus::Running) if (state.thisThread->status != kernel::type::KThread::Status::Running)
continue; continue;
} else if (instr.value > constant::SvcLast && instr.value <= constant::SvcLast + constant::NumRegs) { } else if (instr.value > constant::SvcLast && instr.value <= constant::SvcLast + constant::NumRegs) {
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS) // Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
SetRegister(static_cast<Xreg>(instr.value - (constant::SvcLast + 1)), state->thisThread->tls); SetRegister(static_cast<Xreg>(instr.value - (constant::SvcLast + 1)), state.thisThread->tls);
} else if (instr.value == constant::BrkRdy) } else if (instr.value == constant::BrkRdy)
continue; continue;
else else
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value))); throw exception("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value));
} }
currRegs.pc += sizeof(u32); currRegs.pc += sizeof(u32);
WriteRegisters(currRegs); WriteRegisters(currRegs);
ResumeProcess(); ResumeProcess();
} else { } else {
try { try {
ReadRegisters(currRegs); state.logger->Warn("Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
u32 instr = static_cast<u32>(ptrace(PTRACE_PEEKDATA, currPid, currRegs.pc, NULL)); ProcessTrace();
state->logger->Write(Logger::Warn, "Thread threw unknown signal, PID: {}, Stop Signal: {}, Instruction: 0x{:X}, PC: 0x{:X}", currPid, strsignal(WSTOPSIG(status)), instr, currRegs.pc); // NOLINT(hicpp-signed-bitwise)
} catch (const exception &) { } catch (const exception &) {
state->logger->Write(Logger::Warn, "Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise) state.logger->Warn("Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
} }
state->os->KillThread(currPid); state.os->KillThread(currPid);
} }
} }
} else if (state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitSync || state->thisThread->status == kernel::type::KThread::ThreadStatus::Sleeping || state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitCondVar) { } else if ((state.thisThread->status == kernel::type::KThread::Status::WaitSync || state.thisThread->status == kernel::type::KThread::Status::Sleeping || state.thisThread->status == kernel::type::KThread::Status::WaitCondVar) && state.thisThread->timeout != 0) { // timeout == 0 means sleep forever
if (state->thisThread->timeout <= GetCurrTimeNs()) { if (state.thisThread->timeout <= GetCurrTimeNs()) {
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitSync || state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitCondVar) state.logger->Info("An event has timed out: {}", state.thisThread->status);
if (state.thisThread->status == kernel::type::KThread::Status::WaitSync || state.thisThread->status == kernel::type::KThread::Status::WaitCondVar)
SetRegister(Wreg::W0, constant::status::Timeout); SetRegister(Wreg::W0, constant::status::Timeout);
state->thisThread->status = kernel::type::KThread::ThreadStatus::Runnable; state.thisThread->status = kernel::type::KThread::Status::Runnable;
} }
} }
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Runnable) { if (state.thisThread->status == kernel::type::KThread::Status::Runnable) {
state->thisThread->waitObjects.clear(); state.thisThread->waitObjects.clear();
state->thisThread->status = kernel::type::KThread::ThreadStatus::Running; state.thisThread->status = kernel::type::KThread::Status::Running;
currRegs.pc += sizeof(u32); currRegs.pc += sizeof(u32);
WriteRegisters(currRegs); WriteRegisters(currRegs);
ResumeProcess(); ResumeProcess();
} }
} }
state->os->serviceManager.Loop(); state.os->serviceManager.Loop();
state.gpu->Loop();
}
for (const auto &process : state.os->processMap) {
state.os->KillThread(process.first);
} }
} }
@ -123,9 +125,9 @@ namespace skyline {
WriteRegisters(regs, pid); WriteRegisters(regs, pid);
return regs; return regs;
} else } else
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value))); throw exception("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value));
} else } else
throw exception(fmt::format("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status)))); // NOLINT(hicpp-signed-bitwise) throw exception("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
} }
bool NCE::PauseProcess(pid_t pid) const { bool NCE::PauseProcess(pid_t pid) const {
@ -134,10 +136,10 @@ namespace skyline {
waitpid(pid, &status, WNOHANG); waitpid(pid, &status, WNOHANG);
bool wasStopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise) bool wasStopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise)
if (wasStopped) { if (wasStopped) {
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, 0) != -1)) if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, WNOHANG) != -1))
return true; return true;
else else
throw exception(fmt::format("Cannot pause process: {}, Error: {}", pid, strerror(errno))); throw exception("Cannot pause process: {}, Error: {}", pid, strerror(errno));
} else } else
return false; return false;
} }
@ -145,7 +147,7 @@ namespace skyline {
void NCE::ResumeProcess(pid_t pid) const { void NCE::ResumeProcess(pid_t pid) const {
long status = ptrace(PTRACE_CONT, pid ? pid : currPid, NULL, NULL); long status = ptrace(PTRACE_CONT, pid ? pid : currPid, NULL, NULL);
if (status == -1) if (status == -1)
throw exception(fmt::format("Cannot resume process: {}, Error: {}", pid, strerror(errno))); throw exception("Cannot resume process: {}, Error: {}", pid, strerror(errno));
} }
void NCE::StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const { void NCE::StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const {
@ -158,6 +160,30 @@ namespace skyline {
ResumeProcess(pid); ResumeProcess(pid);
} }
void NCE::ProcessTrace(u16 numHist, pid_t pid) {
pid = pid ? pid : currPid;
user_pt_regs regs{};
ReadRegisters(regs, pid);
u64 offset = regs.pc - (sizeof(u32) * numHist);
std::string raw{};
state.logger->Debug("Process Trace:");
for (; offset <= (regs.pc + sizeof(u32)); offset += sizeof(u32)) {
u32 instr = __builtin_bswap32(static_cast<u32>(ptrace(PTRACE_PEEKDATA, pid, offset, NULL)));
if (offset == regs.pc)
state.logger->Debug("-> 0x{:X} : 0x{:08X}", offset, instr);
else
state.logger->Debug(" 0x{:X} : 0x{:08X}", offset, instr);
raw += fmt::format("{:08X}", instr);
}
state.logger->Debug("Raw Instructions: 0x{}", raw);
state.logger->Debug("CPU Context:");
state.logger->Debug("SP: 0x{:X}", regs.sp);
state.logger->Debug("PSTATE: 0x{:X}", regs.pstate);
for (u16 index = 0; index < constant::NumRegs - 2; index++) {
state.logger->Debug("X{}: 0x{:X}", index, regs.regs[index]);
}
}
u64 NCE::GetRegister(Xreg regId, pid_t pid) { u64 NCE::GetRegister(Xreg regId, pid_t pid) {
return registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)]; return registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)];
} }

View File

@ -16,7 +16,7 @@ namespace skyline {
private: private:
pid_t currPid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly pid_t currPid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly
std::unordered_map<pid_t, user_pt_regs> registerMap; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated) std::unordered_map<pid_t, user_pt_regs> registerMap; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
const DeviceState *state; //!< The state of the device const DeviceState &state; //!< The state of the device
/** /**
* @brief Reads process registers into the `registers` variable * @brief Reads process registers into the `registers` variable
@ -41,11 +41,7 @@ namespace skyline {
instr::Brk ReadBrk(u64 address, pid_t pid = 0) const; instr::Brk ReadBrk(u64 address, pid_t pid = 0) const;
public: public:
/** NCE(const DeviceState &state);
* @brief Initialize NCE by setting the device_state variable
* @param state The state of the device
*/
void Initialize(const DeviceState &state);
/** /**
* @brief Start the event loop of executing the program * @brief Start the event loop of executing the program
@ -90,6 +86,13 @@ namespace skyline {
*/ */
void StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const; void StartProcess(u64 entryPoint, u64 entryArg, u64 stackTop, u32 handle, pid_t pid) const;
/**
* @brief This prints out a trace and the CPU context
* @param numHist The amount of previous instructions to print
* @param pid The PID of the process (Defaults to currPid)
*/
void ProcessTrace(u16 numHist = 10, pid_t pid = 0);
/** /**
* @brief Get the value of a Xn register * @brief Get the value of a Xn register
* @param regId The ID of the register * @param regId The ID of the register

View File

@ -3,10 +3,9 @@
#include "loader/nro.h" #include "loader/nro.h"
namespace skyline::kernel { namespace skyline::kernel {
OS::OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, thisProcess, thisThread, std::make_shared<NCE>(), settings, logger), serviceManager(state) {} OS::OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, ANativeWindow *window) : state(this, thisProcess, thisThread, window, settings, logger), serviceManager(state) {}
void OS::Execute(const std::string &romFile) { void OS::Execute(const std::string &romFile) {
state.nce->Initialize(state);
std::string romExt = romFile.substr(romFile.find_last_of('.') + 1); std::string romExt = romFile.substr(romFile.find_last_of('.') + 1);
std::transform(romExt.begin(), romExt.end(), romExt.begin(), [](unsigned char c) { return std::tolower(c); }); std::transform(romExt.begin(), romExt.end(), romExt.begin(), [](unsigned char c) { return std::tolower(c); });
auto process = CreateProcess(constant::BaseAddr, constant::DefStackSize); auto process = CreateProcess(constant::BaseAddr, constant::DefStackSize);
@ -36,27 +35,27 @@ namespace skyline::kernel {
munmap(stack, stackSize); munmap(stack, stackSize);
throw exception("Failed to create guard pages"); throw exception("Failed to create guard pages");
} }
pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FILES | CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise) pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FILES | CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
if (pid == -1) if (pid == -1)
throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno))); throw exception("Call to clone() has failed: {}", strerror(errno));
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(state, pid, address, reinterpret_cast<u64>(stack), stackSize); std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(state, pid, address, reinterpret_cast<u64>(stack), stackSize);
processMap[pid] = process; processMap[pid] = process;
processVec.push_back(pid); processVec.push_back(pid);
state.logger->Write(Logger::Debug, "Successfully created process with PID: {}", pid); state.logger->Debug("Successfully created process with PID: {}", pid);
return process; return process;
} }
void OS::KillThread(pid_t pid) { void OS::KillThread(pid_t pid) {
auto process = processMap.at(pid); auto process = processMap.at(pid);
if (process->mainThread == pid) { if (process->mainThread == pid) {
state.logger->Write(Logger::Debug, "Exiting process with PID: {}", pid); state.logger->Debug("Exiting process with PID: {}", pid);
// Erasing all shared_ptr instances to the process will call the destructor // Erasing all shared_ptr instances to the process will call the destructor
// However, in the case these are not all instances of it we wouldn't want to call the destructor // However, in the case these are not all instances of it we wouldn't want to call the destructor
for (auto&[key, value]: process->threadMap) for (auto&[key, value]: process->threadMap)
processMap.erase(key); processMap.erase(key);
processVec.erase(std::remove(processVec.begin(), processVec.end(), pid), processVec.end()); processVec.erase(std::remove(processVec.begin(), processVec.end(), pid), processVec.end());
} else { } else {
state.logger->Write(Logger::Debug, "Exiting thread with TID: {}", pid); state.logger->Debug("Exiting thread with TID: {}", pid);
process->handleTable.erase(process->threadMap[pid]->handle); process->handleTable.erase(process->threadMap[pid]->handle);
process->threadMap.erase(pid); process->threadMap.erase(pid);
processMap.erase(pid); processMap.erase(pid);
@ -65,10 +64,10 @@ namespace skyline::kernel {
void OS::SvcHandler(u16 svc) { void OS::SvcHandler(u16 svc) {
if (svc::SvcTable[svc]) { if (svc::SvcTable[svc]) {
state.logger->Write(Logger::Debug, "SVC called 0x{:X}", svc); state.logger->Debug("SVC called 0x{:X}", svc);
(*svc::SvcTable[svc])(state); (*svc::SvcTable[svc])(state);
} else } else
throw exception(fmt::format("Unimplemented SVC 0x{:X}", svc)); throw exception("Unimplemented SVC 0x{:X}", svc);
} }
std::shared_ptr<kernel::type::KSharedMemory> OS::MapSharedKernel(const u64 address, const size_t size, const memory::Permission kernelPermission, const memory::Permission remotePermission, const memory::Type type) { std::shared_ptr<kernel::type::KSharedMemory> OS::MapSharedKernel(const u64 address, const size_t size, const memory::Permission kernelPermission, const memory::Permission remotePermission, const memory::Type type) {

View File

@ -6,8 +6,9 @@
#include "kernel/ipc.h" #include "kernel/ipc.h"
#include "kernel/types/KProcess.h" #include "kernel/types/KProcess.h"
#include "kernel/types/KThread.h" #include "kernel/types/KThread.h"
#include "kernel/services/serviceman.h" #include "services/serviceman.h"
#include "nce.h" #include "nce.h"
#include "gpu.h"
namespace skyline::kernel { namespace skyline::kernel {
/** /**
@ -28,7 +29,7 @@ namespace skyline::kernel {
* @param logger An instance of the Logger class * @param logger An instance of the Logger class
* @param settings An instance of the Settings class * @param settings An instance of the Settings class
*/ */
OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings); OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, ANativeWindow *window);
/** /**
* @brief Execute a particular ROM file. This launches a the main processes and calls the NCE class to handle execution. * @brief Execute a particular ROM file. This launches a the main processes and calls the NCE class to handle execution.

View File

@ -0,0 +1,80 @@
#include "applet.h"
#include "appletController.h"
namespace skyline::kernel::service::am {
appletOE::appletOE(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_appletOE, {
{0x0, SFUNC(appletOE::OpenApplicationProxy)}
}) {}
void appletOE::OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IApplicationProxy), session, response);
}
appletAE::appletAE(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_appletAE, {
{0xC8, SFUNC(appletAE::OpenLibraryAppletProxy)},
{0x15E, SFUNC(appletAE::OpenApplicationProxy)},
}) {}
void appletAE::OpenLibraryAppletProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(ILibraryAppletProxy), session, response);
}
void appletAE::OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IApplicationProxy), session, response);
}
BaseProxy::BaseProxy(const DeviceState &state, ServiceManager &manager, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : BaseService(state, manager, false, serviceType, vTable) {}
void BaseProxy::GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(ICommonStateGetter), session, response);
}
void BaseProxy::GetSelfController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(ISelfController), session, response);
}
void BaseProxy::GetWindowController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IWindowController), session, response);
}
void BaseProxy::GetAudioController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IAudioController), session, response);
}
void BaseProxy::GetDisplayController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IDisplayController), session, response);
}
void BaseProxy::GetLibraryAppletCreator(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(ILibraryAppletCreator), session, response);
}
void BaseProxy::GetDebugFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IDebugFunctions), session, response);
}
IApplicationProxy::IApplicationProxy(const DeviceState &state, ServiceManager &manager) : BaseProxy(state, manager, Service::am_IApplicationProxy, {
{0x0, SFUNC(BaseProxy::GetCommonStateGetter)},
{0x1, SFUNC(BaseProxy::GetSelfController)},
{0x2, SFUNC(BaseProxy::GetWindowController)},
{0x3, SFUNC(BaseProxy::GetAudioController)},
{0x4, SFUNC(BaseProxy::GetDisplayController)},
{0xB, SFUNC(BaseProxy::GetLibraryAppletCreator)},
{0x14, SFUNC(IApplicationProxy::GetApplicationFunctions)},
{0x3E8, SFUNC(BaseProxy::GetDebugFunctions)}
}) {}
void IApplicationProxy::GetApplicationFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IApplicationFunctions), session, response);
}
ILibraryAppletProxy::ILibraryAppletProxy(const DeviceState &state, ServiceManager &manager) : BaseProxy(state, manager, Service::am_ILibraryAppletProxy, {
{0x0, SFUNC(BaseProxy::GetCommonStateGetter)},
{0x1, SFUNC(BaseProxy::GetSelfController)},
{0x2, SFUNC(BaseProxy::GetWindowController)},
{0x3, SFUNC(BaseProxy::GetAudioController)},
{0x4, SFUNC(BaseProxy::GetDisplayController)},
{0xB, SFUNC(BaseProxy::GetLibraryAppletCreator)},
{0x3E8, SFUNC(BaseProxy::GetDebugFunctions)}
}) {}
}

View File

@ -0,0 +1,101 @@
#pragma once
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::kernel::service::am {
/**
* @brief appletOE is used to open an application proxy (https://switchbrew.org/wiki/Applet_Manager_services#appletOE)
*/
class appletOE : public BaseService {
public:
appletOE(const DeviceState &state, ServiceManager &manager);
/**
* @brief This returns #IApplicationProxy (https://switchbrew.org/wiki/Applet_Manager_services#OpenApplicationProxy)
*/
void OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief appletAE is used to open proxies (https://switchbrew.org/wiki/Applet_Manager_services#appletAE)
*/
class appletAE : public BaseService {
public:
appletAE(const DeviceState &state, ServiceManager &manager);
/**
* @brief This returns #ILibraryAppletProxy (https://switchbrew.org/wiki/Applet_Manager_services#OpenLibraryAppletProxy)
*/
void OpenLibraryAppletProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns #IApplicationProxy (https://switchbrew.org/wiki/Applet_Manager_services#OpenApplicationProxy)
*/
void OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief ILibraryAppletProxy returns handles to various services (https://switchbrew.org/wiki/Applet_Manager_services#ILibraryAppletProxy)
*/
class BaseProxy : public BaseService {
public:
BaseProxy(const DeviceState &state, ServiceManager &manager, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable);
/**
* @brief This returns #ICommonStateGetter (https://switchbrew.org/wiki/Applet_Manager_services#ICommonStateGetter)
*/
void GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns #ISelfController (https://switchbrew.org/wiki/Applet_Manager_services#ISelfController)
*/
void GetSelfController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns #IWindowController (https://switchbrew.org/wiki/Applet_Manager_services#IWindowController)
*/
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)
*/
void GetLibraryAppletCreator(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns #IDebugFunctions (https://switchbrew.org/wiki/Applet_Manager_services#IDebugFunctions)
*/
void GetDebugFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief IApplicationProxy returns handles to various services (https://switchbrew.org/wiki/Applet_Manager_services#IApplicationProxy)
*/
class IApplicationProxy : public BaseProxy {
public:
IApplicationProxy(const DeviceState &state, ServiceManager &manager);
/**
* @brief This returns #IApplicationFunctions (https://switchbrew.org/wiki/Applet_Manager_services#IApplicationFunctions)
*/
void GetApplicationFunctions(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief ILibraryAppletProxy returns handles to various services (https://switchbrew.org/wiki/Applet_Manager_services#ILibraryAppletProxy)
*/
class ILibraryAppletProxy : public BaseProxy {
public:
ILibraryAppletProxy(const DeviceState &state, ServiceManager &manager);
};
}

View File

@ -0,0 +1,102 @@
#include "appletController.h"
namespace skyline::kernel::service::am {
void ICommonStateGetter::QueueMessage(ICommonStateGetter::Message message) {
messageQueue.emplace(message);
messageEvent->Signal();
}
ICommonStateGetter::ICommonStateGetter(const DeviceState &state, ServiceManager &manager) : messageEvent(std::make_shared<type::KEvent>(state)), BaseService(state, manager, false, Service::am_ICommonStateGetter, {
{0x0, SFUNC(ICommonStateGetter::GetEventHandle)},
{0x1, SFUNC(ICommonStateGetter::ReceiveMessage)},
{0x9, SFUNC(ICommonStateGetter::GetCurrentFocusState)},
{0x5, SFUNC(ICommonStateGetter::GetOperationMode)},
{0x6, SFUNC(ICommonStateGetter::GetPerformanceMode)}
}) {
operationMode = static_cast<OperationMode>(state.settings->GetBool("operation_mode"));
state.logger->Info("Switch on mode: {}", static_cast<bool>(operationMode) ? "Docked" : "Handheld");
QueueMessage(Message::FocusStateChange);
}
void ICommonStateGetter::GetEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto handle = state.thisProcess->InsertItem(messageEvent);
state.logger->Info("Event Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle);
}
void ICommonStateGetter::ReceiveMessage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
if (messageQueue.empty()) {
response.errorCode = constant::status::NoMessages;
return;
}
response.WriteValue<u32>(static_cast<u32>(messageQueue.front()));
messageQueue.pop();
}
void ICommonStateGetter::GetCurrentFocusState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u8>(static_cast<u8>(focusState));
}
void ICommonStateGetter::GetOperationMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u8>(static_cast<u8>(operationMode));
}
void ICommonStateGetter::GetPerformanceMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u32>(static_cast<u32>(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)},
{0x10, SFUNC(ISelfController::SetOutOfFocusSuspendingEnabled)},
{0x28, SFUNC(ISelfController::CreateManagedDisplayLayer)}
}) {}
void ISelfController::SetOperationModeChangedNotification(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
void ISelfController::SetPerformanceModeChangedNotification(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
void ISelfController::SetFocusHandlingMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
void ISelfController::SetOutOfFocusSuspendingEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
void ISelfController::CreateManagedDisplayLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.logger->Debug("Creating Managed Layer");
if (state.gpu->layerStatus == gpu::LayerStatus::Initialized)
throw exception("The application is creating more than one layer");
state.gpu->layerStatus = gpu::LayerStatus::Initialized;
response.WriteValue<u64>(0);
}
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<u64>(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<u8>(1);
}
IDebugFunctions::IDebugFunctions(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_IDebugFunctions, {
}) {}
}

View File

@ -1,88 +1,50 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
#include <kernel/types/KEvent.h> #include <kernel/types/KEvent.h>
#include <gpu.h>
namespace skyline::kernel::service::am { namespace skyline::kernel::service::am {
/**
* @brief appletOE is used to open an application proxy (https://switchbrew.org/wiki/Applet_Manager_services#appletOE)
*/
class appletOE : public BaseService {
public:
appletOE(const DeviceState &state, ServiceManager& manager);
/**
* @brief This returns IApplicationProxy (https://switchbrew.org/wiki/Applet_Manager_services#OpenApplicationProxy)
*/
void OpenApplicationProxy(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief IApplicationProxy returns handles to various services (https://switchbrew.org/wiki/Applet_Manager_services#IApplicationProxy)
*/
class IApplicationProxy : public BaseService {
public:
IApplicationProxy(const DeviceState &state, ServiceManager& manager);
/**
* @brief This returns #ICommonStateGetter (https://switchbrew.org/wiki/Applet_Manager_services#ICommonStateGetter)
*/
void GetCommonStateGetter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns #ISelfController (https://switchbrew.org/wiki/Applet_Manager_services#ISelfController)
*/
void GetSelfController(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns #IWindowController (https://switchbrew.org/wiki/Applet_Manager_services#IWindowController)
*/
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)
*/
void GetLibraryAppletCreator(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @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 * @brief https://switchbrew.org/wiki/Applet_Manager_services#ICommonStateGetter
*/ */
class ICommonStateGetter : public BaseService { class ICommonStateGetter : public BaseService {
private: private:
std::shared_ptr<type::KEvent> messageEvent{}; /**
* @brief This enumerates all the possible contents of a #AppletMessage (https://switchbrew.org/wiki/Applet_Manager_services#AppletMessage)
*/
enum class Message : u32 {
ExitRequested = 0x4, //!< The applet has been requested to exit
FocusStateChange = 0xF, //!< There was a change in the focus state of the applet
ExecutionResumed = 0x10, //!< The execution of the applet has resumed
OperationModeChange = 0x1E, //!< There was a change in the operation mode
PerformanceModeChange = 0x1F, //!< There was a change in the performance mode
RequestToDisplay = 0x33, //!< This indicates that ApproveToDisplay should be used
CaptureButtonShortPressed = 0x5A, //!< The Capture button was short pressed
ScreenshotTaken = 0x5C //!< A screenshot was taken
};
enum class ApplicationStatus : u8 { std::shared_ptr<type::KEvent> messageEvent; //!< The event signalled when there is a message available
std::queue<Message> messageQueue;
enum class FocusState : u8 {
InFocus = 1, //!< The application is in foreground InFocus = 1, //!< The application is in foreground
OutOfFocus = 2 //!< The application is in the background OutOfFocus = 2 //!< The application is in the background
}; } focusState{FocusState::InFocus};
enum class OperationMode : u8 { enum class OperationMode : u8 {
Handheld = 0, //!< The device is in handheld mode Handheld = 0, //!< The device is in handheld mode
Docked = 1 //!< The device is in docked mode Docked = 1 //!< The device is in docked mode
} operationMode; } operationMode;
/**
* @brief This queues a message for the application to read via ReceiveMessage
* @param message The message to queue
*/
void QueueMessage(Message message);
public: public:
ICommonStateGetter(const DeviceState &state, ServiceManager &manager); ICommonStateGetter(const DeviceState &state, ServiceManager &manager);
@ -91,6 +53,11 @@ namespace skyline::kernel::service::am {
*/ */
void GetEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void GetEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns an #AppletMessage or 0x680 to indicate the lack of a message (https://switchbrew.org/wiki/Applet_Manager_services#ReceiveMessage)
*/
void ReceiveMessage(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) * @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)
*/ */
@ -108,30 +75,40 @@ namespace skyline::kernel::service::am {
}; };
/** /**
* @brief https://switchbrew.org/wiki/Applet_Manager_services#ISelfController * @brief This has functions relating to an application's own current status (https://switchbrew.org/wiki/Applet_Manager_services#ISelfController)
*/ */
class ISelfController : public BaseService { class ISelfController : public BaseService {
public: public:
ISelfController(const DeviceState &state, ServiceManager &manager); 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) * @brief This function takes 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); 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) * @brief This function takes 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); 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) * @brief This function takes 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); void SetFocusHandlingMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This function takes a u8 bool flag and has no output (Stubbed) (https://switchbrew.org/wiki/Applet_Manager_services#SetOutOfFocusSuspendingEnabled)
*/
void SetOutOfFocusSuspendingEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This function returns an output u64 LayerId (https://switchbrew.org/wiki/Applet_Manager_services#CreateManagedDisplayLayer)
*/
void CreateManagedDisplayLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
}; };
/** /**
* @brief https://switchbrew.org/wiki/Applet_Manager_services#IWindowController * @brief This has functions used to retrieve the status of the application's window (https://switchbrew.org/wiki/Applet_Manager_services#IWindowController)
*/ */
class IWindowController : public BaseService { class IWindowController : public BaseService {
public: public:
@ -149,7 +126,7 @@ namespace skyline::kernel::service::am {
}; };
/** /**
* @brief https://switchbrew.org/wiki/Applet_Manager_services#IAudioController * @brief This has functions relating to volume control (https://switchbrew.org/wiki/Applet_Manager_services#IAudioController)
*/ */
class IAudioController : public BaseService { class IAudioController : public BaseService {
public: public:
@ -157,7 +134,7 @@ namespace skyline::kernel::service::am {
}; };
/** /**
* @brief https://switchbrew.org/wiki/Applet_Manager_services#IDisplayController * @brief This has functions used to capture the contents of a display (https://switchbrew.org/wiki/Applet_Manager_services#IDisplayController)
*/ */
class IDisplayController : public BaseService { class IDisplayController : public BaseService {
public: public:
@ -173,20 +150,20 @@ namespace skyline::kernel::service::am {
}; };
/** /**
* @brief https://switchbrew.org/wiki/Applet_Manager_services#IApplicationFunctions * @brief This has functions that are used to notify an application about it's state (https://switchbrew.org/wiki/Applet_Manager_services#IApplicationFunctions)
*/ */
class IApplicationFunctions : public BaseService { class IApplicationFunctions : public BaseService {
public: public:
IApplicationFunctions(const DeviceState &state, ServiceManager &manager); IApplicationFunctions(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This just returns a boolean true (https://switchbrew.org/wiki/Applet_Manager_services#NotifyRunning) * @brief This returns if the application is running or not, always returns true (https://switchbrew.org/wiki/Applet_Manager_services#NotifyRunning)
*/ */
void NotifyRunning(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void NotifyRunning(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
}; };
/** /**
* @brief https://switchbrew.org/wiki/Applet_Manager_services#IDebugFunctions * @brief This has functions that are used for debugging purposes (https://switchbrew.org/wiki/Applet_Manager_services#IDebugFunctions)
*/ */
class IDebugFunctions : public BaseService { class IDebugFunctions : public BaseService {
public: public:

View File

@ -1,17 +1,17 @@
#include "apm.h" #include "apm.h"
namespace skyline::kernel::service::apm { namespace skyline::kernel::service::apm {
apm::apm(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::apm, { apm::apm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::apm, {
{0x0, SFunc(apm::OpenSession)} {0x0, SFUNC(apm::OpenSession)}
}) {} }) {}
void apm::OpenSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void apm::OpenSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.NewService(Service::apm_ISession, session, response); manager.RegisterService(std::make_shared<ISession>(state, manager), session, response);
} }
ISession::ISession(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::apm_ISession, { ISession::ISession(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::apm_ISession, {
{0x0, SFunc(ISession::SetPerformanceConfiguration)}, {0x0, SFUNC(ISession::SetPerformanceConfiguration)},
{0x1, SFunc(ISession::GetPerformanceConfiguration)} {0x1, SFUNC(ISession::GetPerformanceConfiguration)}
}) {} }) {}
void ISession::SetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void ISession::SetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -20,7 +20,7 @@ namespace skyline::kernel::service::apm {
u32 config; u32 config;
} *performance = reinterpret_cast<InputStruct *>(request.cmdArg); } *performance = reinterpret_cast<InputStruct *>(request.cmdArg);
performanceConfig[performance->mode] = performance->config; performanceConfig[performance->mode] = performance->config;
state.logger->Write(Logger::Info, "SetPerformanceConfiguration called with 0x{:X} ({})", performance->config, performance->mode ? "Docked" : "Handheld"); state.logger->Info("SetPerformanceConfiguration called with 0x{:X} ({})", performance->config, performance->mode ? "Docked" : "Handheld");
} }
void ISession::GetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void ISession::GetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
namespace skyline::kernel::service::apm { namespace skyline::kernel::service::apm {
/** /**
@ -9,7 +9,7 @@ namespace skyline::kernel::service::apm {
*/ */
class apm : public BaseService { class apm : public BaseService {
public: public:
apm(const DeviceState &state, ServiceManager& manager); apm(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This returns an handle to ISession * @brief This returns an handle to ISession
@ -25,7 +25,7 @@ namespace skyline::kernel::service::apm {
u32 performanceConfig[2] = {0x00010000, 0x00020001}; //!< This holds the performance config for both handheld(0) and docked(1) mode u32 performanceConfig[2] = {0x00010000, 0x00020001}; //!< This holds the performance config for both handheld(0) and docked(1) mode
public: public:
ISession(const DeviceState &state, ServiceManager& manager); ISession(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This sets performanceConfig to the given arguments, it doesn't affect anything else (https://switchbrew.org/wiki/PPC_services#SetPerformanceConfiguration) * @brief This sets performanceConfig to the given arguments, it doesn't affect anything else (https://switchbrew.org/wiki/PPC_services#SetPerformanceConfiguration)

View File

@ -4,7 +4,8 @@
#include <kernel/ipc.h> #include <kernel/ipc.h>
#include <functional> #include <functional>
#define SFunc(function) std::bind(&function, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) #define SFUNC(function) std::bind(&function, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
#define SRVREG(class) std::make_shared<class>(state, manager)
namespace skyline::kernel::type { namespace skyline::kernel::type {
class KSession; class KSession;
@ -20,7 +21,9 @@ namespace skyline::kernel::service {
apm, apm,
apm_ISession, apm_ISession,
am_appletOE, am_appletOE,
am_appletAE,
am_IApplicationProxy, am_IApplicationProxy,
am_ILibraryAppletProxy,
am_ICommonStateGetter, am_ICommonStateGetter,
am_IApplicationFunctions, am_IApplicationFunctions,
am_ISelfController, am_ISelfController,
@ -36,19 +39,27 @@ namespace skyline::kernel::service {
time_ITimeZoneService, time_ITimeZoneService,
fs_fsp, fs_fsp,
fs_IFileSystem, fs_IFileSystem,
nvdrv,
vi_m,
vi_IApplicationDisplayService,
vi_ISystemDisplayService,
vi_IManagerDisplayService,
nvnflinger_dispdrv,
}; };
/** /**
* @brief A map from every service's name as a std::string to the corresponding serviceEnum * @brief A map from every service's name as a std::string to the corresponding serviceEnum
*/ */
const static std::unordered_map<std::string, Service> ServiceString = { const static std::unordered_map<std::string, Service> ServiceString{
{"sm:", Service::sm}, {"sm:", Service::sm},
{"fatal:u", Service::fatal_u}, {"fatal:u", Service::fatal_u},
{"set:sys", Service::set_sys}, {"set:sys", Service::set_sys},
{"apm", Service::apm}, {"apm", Service::apm},
{"apm:ISession", Service::apm_ISession}, {"apm:ISession", Service::apm_ISession},
{"appletOE", Service::am_appletOE}, {"appletOE", Service::am_appletOE},
{"appletAE", Service::am_appletAE},
{"am:IApplicationProxy", Service::am_IApplicationProxy}, {"am:IApplicationProxy", Service::am_IApplicationProxy},
{"am:ILibraryAppletProxy", Service::am_ILibraryAppletProxy},
{"am:ICommonStateGetter", Service::am_ICommonStateGetter}, {"am:ICommonStateGetter", Service::am_ICommonStateGetter},
{"am:ISelfController", Service::am_ISelfController}, {"am:ISelfController", Service::am_ISelfController},
{"am:IWindowController", Service::am_IWindowController}, {"am:IWindowController", Service::am_IWindowController},
@ -65,6 +76,15 @@ namespace skyline::kernel::service {
{"time:ITimeZoneService", Service::time_ITimeZoneService}, {"time:ITimeZoneService", Service::time_ITimeZoneService},
{"fsp-srv", Service::fs_fsp}, {"fsp-srv", Service::fs_fsp},
{"fs:IFileSystem", Service::fs_IFileSystem}, {"fs:IFileSystem", Service::fs_IFileSystem},
{"nvdrv", Service::nvdrv},
{"nvdrv:a", Service::nvdrv},
{"nvdrv:s", Service::nvdrv},
{"nvdrv:t", Service::nvdrv},
{"vi:m", Service::vi_m},
{"vi:IApplicationDisplayService", Service::vi_IApplicationDisplayService},
{"vi:ISystemDisplayService", Service::vi_ISystemDisplayService},
{"vi:IManagerDisplayService", Service::vi_IManagerDisplayService},
{"nvnflinger:dispdrv", Service::nvnflinger_dispdrv},
}; };
class ServiceManager; class ServiceManager;
@ -75,11 +95,11 @@ namespace skyline::kernel::service {
class BaseService { class BaseService {
protected: protected:
const DeviceState &state; //!< The state of the device const DeviceState &state; //!< The state of the device
ServiceManager& manager; //!< A pointer to the service manager ServiceManager &manager; //!< A pointer to the service manager
std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> vTable; //!< This holds the mapping from an object's CmdId to the actual function std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> vTable; //!< This holds the mapping from an object's CmdId to the actual function
public: public:
Service serviceType; //!< Which service this is Service serviceType; //!< The type of the service this is
const bool hasLoop; //<! If the service has a loop or not const bool hasLoop; //<! If the service has a loop or not
/** /**
@ -89,12 +109,17 @@ namespace skyline::kernel::service {
* @param serviceName The name of the service * @param serviceName The name of the service
* @param vTable The functions of the service * @param vTable The functions of the service
*/ */
BaseService(const DeviceState &state, ServiceManager& manager, bool hasLoop, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : state(state), manager(manager), hasLoop(hasLoop), serviceType(serviceType), vTable(vTable) {} BaseService(const DeviceState &state, ServiceManager &manager, bool hasLoop, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : state(state), manager(manager), hasLoop(hasLoop), serviceType(serviceType), vTable(vTable) {}
/**
* @brief This returns the name of the current service
* @note It may not return the exact name the service was initialized with if there are multiple entries in ServiceString
* @return The name of the service
*/
std::string getName() { std::string getName() {
std::string serviceName = ""; std::string serviceName;
for (const auto& [name, type] : ServiceString) for (const auto&[name, type] : ServiceString)
if(type == serviceType) if (type == serviceType)
serviceName = name; serviceName = name;
return serviceName; return serviceName;
} }
@ -108,14 +133,14 @@ namespace skyline::kernel::service {
std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)> function; std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)> function;
try { try {
function = vTable.at(request.payload->value); function = vTable.at(request.payload->value);
} catch (std::out_of_range&) { } 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)); state.logger->Warn("Cannot find function in service '{0}' (Type: {1}): 0x{2:X} ({2})", getName(), serviceType, u32(request.payload->value));
return; return;
} }
try { try {
function(session, request, response); function(session, request, response);
} catch (std::exception& e) { } catch (std::exception &e) {
throw exception(e.what()); throw exception("{} (Service: {})", e.what(), getName());
} }
}; };

View File

@ -0,0 +1,13 @@
#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)},
{0x1, SFUNC(fatalU::ThrowFatal)},
{0x2, SFUNC(fatalU::ThrowFatal)}
}) {}
void fatalU::ThrowFatal(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
throw exception("A fatal error with code: 0x{:X} has caused emulation to stop", *reinterpret_cast<u32 *>(request.cmdArg));
}
}

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
namespace skyline::kernel::service::fatal { namespace skyline::kernel::service::fatal {
/** /**
@ -9,7 +9,7 @@ namespace skyline::kernel::service::fatal {
*/ */
class fatalU : public BaseService { class fatalU : public BaseService {
public: public:
fatalU(const DeviceState &state, ServiceManager& manager); fatalU(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This throws an exception so that emulation will quit * @brief This throws an exception so that emulation will quit

View File

@ -1,13 +1,13 @@
#include "fs.h" #include "fs.h"
namespace skyline::kernel::service::fs { namespace skyline::kernel::service::fs {
fsp::fsp(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::fs_fsp, { fsp::fsp(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::fs_fsp, {
{0x1, SFunc(fsp::SetCurrentProcess)}, {0x1, SFUNC(fsp::SetCurrentProcess)},
{0x12, SFunc(fsp::OpenSdCardFileSystem)} {0x12, SFUNC(fsp::OpenSdCardFileSystem)}
}) {} }) {}
void fsp::SetCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void fsp::SetCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
process = *reinterpret_cast<pid_t*>(request.cmdArg); process = *reinterpret_cast<pid_t *>(request.cmdArg);
} }
void fsp::OpenSdCardFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void fsp::OpenSdCardFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {

View File

@ -1,13 +1,13 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
namespace skyline::kernel::service::fs { namespace skyline::kernel::service::fs {
/** /**
* @brief These are the possible types of the filesystem * @brief These are the possible types of the filesystem
*/ */
enum FsType { enum class FsType {
Nand, Nand,
SdCard, SdCard,
GameCard GameCard
@ -20,7 +20,7 @@ namespace skyline::kernel::service::fs {
public: public:
pid_t process{}; //!< This holds the PID set by SetCurrentProcess pid_t process{}; //!< This holds the PID set by SetCurrentProcess
fsp(const DeviceState &state, ServiceManager& manager); fsp(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This sets the PID of the process using FS currently (https://switchbrew.org/wiki/Filesystem_services#SetCurrentProcess) * @brief This sets the PID of the process using FS currently (https://switchbrew.org/wiki/Filesystem_services#SetCurrentProcess)
@ -40,6 +40,6 @@ namespace skyline::kernel::service::fs {
public: public:
FsType type; FsType type;
IFileSystem(FsType type, const DeviceState &state, ServiceManager& manager); IFileSystem(FsType type, const DeviceState &state, ServiceManager &manager);
}; };
} }

View File

@ -2,24 +2,26 @@
#include <os.h> #include <os.h>
namespace skyline::kernel::service::hid { namespace skyline::kernel::service::hid {
IAppletResource::IAppletResource(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::hid_IAppletResource, { IAppletResource::IAppletResource(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::hid_IAppletResource, {
{0x0, SFunc(IAppletResource::GetSharedMemoryHandle)} {0x0, SFUNC(IAppletResource::GetSharedMemoryHandle)}
}) {} }) {}
void IAppletResource::GetSharedMemoryHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IAppletResource::GetSharedMemoryHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
hidSharedMemory = state.os->MapSharedKernel(0, constant::hidSharedMemSize, memory::Permission(true, false, false), memory::Permission(true, true, false), memory::Type::SharedMemory); hidSharedMemory = state.os->MapSharedKernel(0, constant::hidSharedMemSize, memory::Permission(true, false, false), memory::Permission(true, true, false), memory::Type::SharedMemory);
response.copyHandles.push_back(state.thisProcess->InsertItem<type::KSharedMemory>(hidSharedMemory)); auto handle = state.thisProcess->InsertItem<type::KSharedMemory>(hidSharedMemory);
state.logger->Info("Writing HID SHM: {}", handle);
response.copyHandles.push_back(handle);
} }
hid::hid(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::hid, { hid::hid(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::hid, {
{0x0, SFunc(hid::CreateAppletResource)}, {0x0, SFUNC(hid::CreateAppletResource)},
{0x64, SFunc(hid::SetSupportedNpadStyleSet)}, {0x64, SFUNC(hid::SetSupportedNpadStyleSet)},
{0x66, SFunc(hid::SetSupportedNpadIdType)}, {0x66, SFUNC(hid::SetSupportedNpadIdType)},
{0x67, SFunc(hid::ActivateNpad)}, {0x67, SFUNC(hid::ActivateNpad)},
{0x78, SFunc(hid::SetNpadJoyHoldType)}, {0x78, SFUNC(hid::SetNpadJoyHoldType)},
{0x7A, SFunc(hid::SetNpadJoyAssignmentModeSingleByDefault)}, {0x7A, SFUNC(hid::SetNpadJoyAssignmentModeSingleByDefault)},
{0x7B, SFunc(hid::SetNpadJoyAssignmentModeSingle)}, {0x7B, SFUNC(hid::SetNpadJoyAssignmentModeSingle)},
{0x7C, SFunc(hid::SetNpadJoyAssignmentModeDual)} {0x7C, SFUNC(hid::SetNpadJoyAssignmentModeDual)}
}) {} }) {}
void hid::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void hid::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -33,8 +35,8 @@ namespace skyline::kernel::service::hid {
u64 appletUserId; u64 appletUserId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg); } *input = reinterpret_cast<InputStruct *>(request.cmdArg);
styleSet = *reinterpret_cast<StyleSet *>(&input->styleSet); styleSet = *reinterpret_cast<StyleSet *>(&input->styleSet);
state.logger->Write(Logger::Debug, "Controller Support: Pro-Controller: {} Joy-Con: Handheld: {}, Dual: {}, L: {}, R: {} GameCube: {} PokeBall: {} NES: {} NES Handheld: {} SNES: {}", static_cast<bool>(styleSet->pro_controller), static_cast<bool>(styleSet->joycon_handheld), static_cast<bool>(styleSet->joycon_dual), static_cast<bool>(styleSet->joycon_left), static_cast<bool> state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast<bool>(styleSet->proController), static_cast<bool>(styleSet->joyconHandheld), static_cast<bool>(styleSet->joyconDual), static_cast<bool>(styleSet->joyconLeft), static_cast<bool>
(styleSet->joycon_right), static_cast<bool>(styleSet->gamecube), static_cast<bool>(styleSet->pokeball), static_cast<bool>(styleSet->nes), static_cast<bool>(styleSet->nes_handheld), static_cast<bool>(styleSet->snes)); (styleSet->joyconRight), static_cast<bool>(styleSet->gamecube), static_cast<bool>(styleSet->pokeball), static_cast<bool>(styleSet->nes), static_cast<bool>(styleSet->nesHandheld), static_cast<bool>(styleSet->snes));
} }
void hid::SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void hid::SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
namespace skyline::constant { namespace skyline::constant {
@ -14,7 +14,7 @@ namespace skyline::kernel::service::hid {
*/ */
class IAppletResource : public BaseService { class IAppletResource : public BaseService {
public: public:
IAppletResource(const DeviceState &state, ServiceManager& manager); IAppletResource(const DeviceState &state, ServiceManager &manager);
std::shared_ptr<type::KSharedMemory> hidSharedMemory; std::shared_ptr<type::KSharedMemory> hidSharedMemory;
@ -33,15 +33,15 @@ namespace skyline::kernel::service::hid {
* @brief This holds the controller styles supported by an application * @brief This holds the controller styles supported by an application
*/ */
struct StyleSet { struct StyleSet {
bool pro_controller : 1; //!< The Pro Controller bool proController : 1; //!< The Pro Controller
bool joycon_handheld : 1; //!< Joy-Cons in handheld mode bool joyconHandheld : 1; //!< Joy-Cons in handheld mode
bool joycon_dual : 1; //!< Joy-Cons in a pair bool joyconDual : 1; //!< Joy-Cons in a pair
bool joycon_left : 1; //!< Left Joy-Con only bool joyconLeft : 1; //!< Left Joy-Con only
bool joycon_right : 1; //!< Right Joy-Con only bool joyconRight : 1; //!< Right Joy-Con only
bool gamecube : 1; //!< GameCube controller bool gamecube : 1; //!< GameCube controller
bool pokeball : 1; //!< Poké Ball Plus controller bool pokeball : 1; //!< Poké Ball Plus controller
bool nes : 1; //!< NES controller bool nes : 1; //!< NES controller
bool nes_handheld : 1; //!< NES controller in handheld mode bool nesHandheld : 1; //!< NES controller in handheld mode
bool snes : 1; //!< SNES controller bool snes : 1; //!< SNES controller
u32 : 22; u32 : 22;
}; };
@ -111,7 +111,7 @@ namespace skyline::kernel::service::hid {
JoyConOrientation orientation{JoyConOrientation::Unset}; //!< The Orientation of the Joy-Con(s) JoyConOrientation orientation{JoyConOrientation::Unset}; //!< The Orientation of the Joy-Con(s)
public: public:
hid(const DeviceState &state, ServiceManager& manager); hid(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This returns an IAppletResource (https://switchbrew.org/wiki/HID_services#CreateAppletResource) * @brief This returns an IAppletResource (https://switchbrew.org/wiki/HID_services#CreateAppletResource)

View File

@ -0,0 +1,53 @@
#include "nvdrv.h"
#include <kernel/types/KProcess.h>
namespace skyline::kernel::service::nvdrv {
nvdrv::nvdrv(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::nvdrv, {
{0x0, SFUNC(nvdrv::Open)},
{0x1, SFUNC(nvdrv::Ioctl)},
{0x2, SFUNC(nvdrv::Close)},
{0x3, SFUNC(nvdrv::Initialize)},
{0x4, SFUNC(nvdrv::QueryEvent)},
{0x8, SFUNC(nvdrv::SetAruidByPID)}
}) {}
void nvdrv::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto aBuf = request.vecBufA[0];
std::string path(aBuf->Size(), '\0');
state.thisProcess->ReadMemory(path.data(), aBuf->Address(), aBuf->Size());
response.WriteValue<u32>(state.gpu->OpenDevice(path));
response.WriteValue<u32>(constant::status::Success);
}
void nvdrv::Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u32 fd;
u32 cmd;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
state.gpu->Ioctl(input->fd, input->cmd, request, response);
}
void nvdrv::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
u32 fd = *reinterpret_cast<u32 *>(request.cmdArg);
state.gpu->CloseDevice(fd);
response.WriteValue<u32>(constant::status::Success);
}
void nvdrv::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u32>(constant::status::Success);
}
void nvdrv::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u32 fd;
u32 eventId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
state.logger->Info("QueryEvent: FD: {}, Event ID: {}", input->fd, input->eventId);
auto event = std::make_shared<type::KEvent>(state);
response.copyHandles.push_back(state.thisProcess->InsertItem<type::KEvent>(event));
}
void nvdrv::SetAruidByPID(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.WriteValue<u32>(constant::status::Success);
}
}

View File

@ -0,0 +1,46 @@
#pragma once
#include <services/base_service.h>
#include <services/serviceman.h>
#include <kernel/types/KTransferMemory.h>
#include <gpu.h>
namespace skyline::kernel::service::nvdrv {
/**
* @brief nvdrv or INvDrvServices is used to access the Nvidia GPU inside the Switch (https://switchbrew.org/wiki/NV_services#nvdrv.2C_nvdrv:a.2C_nvdrv:s.2C_nvdrv:t)
*/
class nvdrv : public BaseService {
public:
nvdrv(const DeviceState &state, ServiceManager &manager);
/**
* @brief Open a specific device and return a FD (https://switchbrew.org/wiki/NV_services#Open)
*/
void Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Close the specified FD (https://switchbrew.org/wiki/NV_services#Close)
*/
void Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Close the specified FD (https://switchbrew.org/wiki/NV_services#Close)
*/
void Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This initializes the driver (https://switchbrew.org/wiki/NV_services#Initialize)
*/
void Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns a specific event from a device (https://switchbrew.org/wiki/NV_services#QueryEvent)
*/
void QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This sets the AppletResourceUserId which matches the PID (https://switchbrew.org/wiki/NV_services#SetAruidByPID)
*/
void SetAruidByPID(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
}

View File

@ -0,0 +1,89 @@
#include "dispdrv.h"
#include <kernel/types/KProcess.h>
#include <gpu.h>
#include <android/native_window.h>
namespace skyline::kernel::service::nvnflinger {
dispdrv::dispdrv(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::nvnflinger_dispdrv, {
{0x0, SFUNC(dispdrv::TransactParcel)},
{0x1, SFUNC(dispdrv::AdjustRefcount)},
{0x2, SFUNC(dispdrv::GetNativeHandle)},
{0x3, SFUNC(dispdrv::TransactParcel)}
}) {}
void dispdrv::TransactParcel(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u32 layerId;
TransactionCode code;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
std::shared_ptr<gpu::Parcel> in{};
if (request.vecBufA.empty())
in = std::make_shared<gpu::Parcel>(request.vecBufX[0], state);
else
in = std::make_shared<gpu::Parcel>(request.vecBufA[0], state);
gpu::Parcel out(state);
state.logger->Debug("TransactParcel: Layer ID: {}, Code: {}", input->layerId, input->code);
switch (input->code) {
case TransactionCode::RequestBuffer:
state.gpu->bufferQueue.RequestBuffer(*in, out);
break;
case TransactionCode::DequeueBuffer: {
if (request.vecBufA.empty()) {
if (state.gpu->bufferQueue.DequeueBuffer(*in, out, request.vecBufC[0]->address, request.vecBufC[0]->size))
return;
} else {
if (state.gpu->bufferQueue.DequeueBuffer(*in, out, request.vecBufB[0]->Address(), request.vecBufB[0]->Size()))
return;
}
break;
}
case TransactionCode::QueueBuffer:
state.gpu->bufferQueue.QueueBuffer(*in, out);
break;
case TransactionCode::CancelBuffer:
state.gpu->bufferQueue.CancelBuffer(*in);
break;
case TransactionCode::Query:
out.WriteData<u64>(0);
break;
case TransactionCode::Connect: {
ConnectParcel connect = {
.width = constant::HandheldResolutionW,
.height = constant::HandheldResolutionH,
.transformHint = 0,
.pendingBuffers = 0,
.status = constant::status::Success,
};
out.WriteData(connect);
break;
}
case TransactionCode::Disconnect:
break;
case TransactionCode::SetPreallocatedBuffer:
state.gpu->bufferQueue.SetPreallocatedBuffer(*in);
break;
default:
throw exception("An unimplemented transaction was called: {}", static_cast<u32>(input->code));
}
if (request.vecBufA.empty())
out.WriteParcel(request.vecBufC[0]);
else
out.WriteParcel(request.vecBufB[0]);
}
void dispdrv::GetNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
handle_t handle = state.thisProcess->InsertItem(state.gpu->bufferEvent);
state.logger->Debug("BufferEvent Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle);
response.WriteValue<u32>(constant::status::Success);
}
void dispdrv::AdjustRefcount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u32 layerId;
i32 addVal;
i32 type;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
state.logger->Debug("Reference Change: {} {} reference", input->addVal, input->type ? "strong" : "weak");
}
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "services/base_service.h"
#include "services/serviceman.h"
#include <gpu/parcel.h>
namespace skyline::kernel::service::nvnflinger {
/**
* @brief This enumerates the functions called by TransactParcel for android.gui.IGraphicBufferProducer
* @refitem https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#35
*/
enum class TransactionCode : u32 {
RequestBuffer = 1, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#281
SetBufferCount = 2, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#293
DequeueBuffer = 3, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#300
DetachBuffer = 4, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#318
DetachNextBuffer = 5, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#325
AttachBuffer = 6, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#343
QueueBuffer = 7, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#353
CancelBuffer = 8, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#364
Query = 9, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#372
Connect = 10, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#381
Disconnect = 11, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#396
SetSidebandStream = 12, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#403
AllocateBuffers = 13, //!< https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#413
SetPreallocatedBuffer = 14, //!< No source on this but it's used to set a existing buffer according to libtransistor and libNX
};
/**
* @brief nvnflinger:dispdrv or nns::hosbinder::IHOSBinderDriver is responsible for writing buffers to the display
*/
class dispdrv : public BaseService {
private:
/**
* @brief This is the structure of the parcel used for TransactionCode::Connect
*/
struct ConnectParcel {
u32 width; //!< The width of the display
u32 height; //!< The height of the display
u32 transformHint; //!< A hint of the transformation of the display
u32 pendingBuffers; //!< The number of pending buffers
u32 status; //!< The status of the buffer queue
};
public:
dispdrv(const DeviceState &state, ServiceManager &manager);
/**
* @brief This emulates the transaction of parcels between a IGraphicBufferProducer and the application (https://switchbrew.org/wiki/Nvnflinger_services#TransactParcel)
*/
void TransactParcel(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This adjusts the reference counts to the underlying binder, it is stubbed as we aren't using the real symbols (https://switchbrew.org/wiki/Nvnflinger_services#GetNativeHandle)
*/
void GetNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This adjusts the reference counts to the underlying binder, it is stubbed as we aren't using the real symbols (https://switchbrew.org/wiki/Nvnflinger_services#AdjustRefcount)
*/
void AdjustRefcount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
}

View File

@ -3,11 +3,15 @@
#include "sm/sm.h" #include "sm/sm.h"
#include "set/sys.h" #include "set/sys.h"
#include "apm/apm.h" #include "apm/apm.h"
#include "am/appletOE.h" #include "am/applet.h"
#include "am/appletController.h"
#include "fatal/fatal.h" #include "fatal/fatal.h"
#include "hid/hid.h" #include "hid/hid.h"
#include "time/timesrv.h" #include "time/timesrv.h"
#include "fs/fs.h" #include "fs/fs.h"
#include "nvdrv/nvdrv.h"
#include "vi/vi_m.h"
#include "nvnflinger/dispdrv.h"
namespace skyline::kernel::service { namespace skyline::kernel::service {
ServiceManager::ServiceManager(const DeviceState &state) : state(state) {} ServiceManager::ServiceManager(const DeviceState &state) : state(state) {}
@ -33,9 +37,15 @@ namespace skyline::kernel::service {
case Service::am_appletOE: case Service::am_appletOE:
serviceObj = std::make_shared<am::appletOE>(state, *this); serviceObj = std::make_shared<am::appletOE>(state, *this);
break; break;
case Service::am_appletAE:
serviceObj = std::make_shared<am::appletAE>(state, *this);
break;
case Service::am_IApplicationProxy: case Service::am_IApplicationProxy:
serviceObj = std::make_shared<am::IApplicationProxy>(state, *this); serviceObj = std::make_shared<am::IApplicationProxy>(state, *this);
break; break;
case Service::am_ILibraryAppletProxy:
serviceObj = std::make_shared<am::ILibraryAppletProxy>(state, *this);
break;
case Service::am_ICommonStateGetter: case Service::am_ICommonStateGetter:
serviceObj = std::make_shared<am::ICommonStateGetter>(state, *this); serviceObj = std::make_shared<am::ICommonStateGetter>(state, *this);
break; break;
@ -72,8 +82,26 @@ namespace skyline::kernel::service {
case Service::fs_fsp: case Service::fs_fsp:
serviceObj = std::make_shared<fs::fsp>(state, *this); serviceObj = std::make_shared<fs::fsp>(state, *this);
break; break;
case Service::nvdrv:
serviceObj = std::make_shared<nvdrv::nvdrv>(state, *this);
break;
case Service::vi_m:
serviceObj = std::make_shared<vi::vi_m>(state, *this);
break;
case Service::vi_IApplicationDisplayService:
serviceObj = std::make_shared<vi::IApplicationDisplayService>(state, *this);
break;
case Service::vi_ISystemDisplayService:
serviceObj = std::make_shared<vi::ISystemDisplayService>(state, *this);
break;
case Service::vi_IManagerDisplayService:
serviceObj = std::make_shared<vi::IManagerDisplayService>(state, *this);
break;
case Service::nvnflinger_dispdrv:
serviceObj = std::make_shared<nvnflinger::dispdrv>(state, *this);
break;
default: default:
throw exception("GetService called on missing object"); throw exception("GetService called on missing object, type: {}", serviceType);
} }
serviceVec.push_back(serviceObj); serviceVec.push_back(serviceObj);
return serviceObj; return serviceObj;
@ -83,8 +111,8 @@ namespace skyline::kernel::service {
return state.thisProcess->NewHandle<type::KSession>(GetService(serviceType)).handle; return state.thisProcess->NewHandle<type::KSession>(GetService(serviceType)).handle;
} }
std::shared_ptr<BaseService> ServiceManager::NewService(const Service serviceType, type::KSession &session, ipc::IpcResponse &response) { std::shared_ptr<BaseService> ServiceManager::NewService(const std::string &serviceName, type::KSession &session, ipc::IpcResponse &response) {
auto serviceObject = GetService(serviceType); auto serviceObject = GetService(ServiceString.at(serviceName));
handle_t handle{}; handle_t handle{};
if (response.isDomain) { if (response.isDomain) {
session.domainTable[++session.handleIndex] = serviceObject; session.domainTable[++session.handleIndex] = serviceObject;
@ -94,7 +122,7 @@ namespace skyline::kernel::service {
handle = state.thisProcess->NewHandle<type::KSession>(serviceObject).handle; handle = state.thisProcess->NewHandle<type::KSession>(serviceObject).handle;
response.moveHandles.push_back(handle); response.moveHandles.push_back(handle);
} }
state.logger->Write(Logger::Debug, "Service has been created: \"{}\" (0x{:X})", serviceObject->getName(), handle); state.logger->Debug("Service has been created: \"{}\" (0x{:X})", serviceName, handle);
return serviceObject; return serviceObject;
} }
@ -102,14 +130,14 @@ namespace skyline::kernel::service {
serviceVec.push_back(serviceObject); serviceVec.push_back(serviceObject);
handle_t handle{}; handle_t handle{};
if (response.isDomain) { if (response.isDomain) {
session.domainTable[++session.handleIndex] = serviceObject; session.domainTable[session.handleIndex] = serviceObject;
response.domainObjects.push_back(session.handleIndex); response.domainObjects.push_back(session.handleIndex);
handle = session.handleIndex; handle = session.handleIndex++;
} else { } else {
handle = state.thisProcess->NewHandle<type::KSession>(serviceObject).handle; handle = state.thisProcess->NewHandle<type::KSession>(serviceObject).handle;
response.moveHandles.push_back(handle); response.moveHandles.push_back(handle);
} }
state.logger->Write(Logger::Debug, "Service has been registered: \"{}\" (0x{:X})", serviceObject->getName(), handle); state.logger->Debug("Service has been registered: \"{}\" (0x{:X})", serviceObject->getName(), handle);
} }
void ServiceManager::CloseSession(const handle_t handle) { void ServiceManager::CloseSession(const handle_t handle) {
@ -132,8 +160,8 @@ namespace skyline::kernel::service {
void ServiceManager::SyncRequestHandler(const handle_t handle) { void ServiceManager::SyncRequestHandler(const handle_t handle) {
auto session = state.thisProcess->GetHandle<type::KSession>(handle); auto session = state.thisProcess->GetHandle<type::KSession>(handle);
state.logger->Write(Logger::Debug, "----Start----"); state.logger->Debug("----Start----");
state.logger->Write(Logger::Debug, "Handle is 0x{:X}", handle); state.logger->Debug("Handle is 0x{:X}", handle);
if (session->serviceStatus == type::KSession::ServiceStatus::Open) { if (session->serviceStatus == type::KSession::ServiceStatus::Open) {
ipc::IpcRequest request(session->isDomain, state); ipc::IpcRequest request(session->isDomain, state);
@ -154,17 +182,18 @@ namespace skyline::kernel::service {
session->domainTable.erase(request.domain->object_id); session->domainTable.erase(request.domain->object_id);
break; break;
} }
} catch (std::out_of_range&) { } catch (std::out_of_range &) {
throw exception("Invalid object ID was used with domain request"); throw exception("Invalid object ID was used with domain request");
} }
} else } else
session->serviceObject->HandleRequest(*session, request, response); session->serviceObject->HandleRequest(*session, request, response);
response.WriteTls(); if (!response.nWrite)
response.WriteTls();
break; break;
case ipc::CommandType::Control: case ipc::CommandType::Control:
case ipc::CommandType::ControlWithContext: case ipc::CommandType::ControlWithContext:
state.logger->Write(Logger::Debug, "Control IPC Message: {}", request.payload->value); state.logger->Debug("Control IPC Message: {}", request.payload->value);
switch (static_cast<ipc::ControlCommand>(request.payload->value)) { switch (static_cast<ipc::ControlCommand>(request.payload->value)) {
case ipc::ControlCommand::ConvertCurrentObjectToDomain: case ipc::ControlCommand::ConvertCurrentObjectToDomain:
response.WriteValue(session->ConvertDomain()); response.WriteValue(session->ConvertDomain());
@ -173,26 +202,29 @@ namespace skyline::kernel::service {
case ipc::ControlCommand::CloneCurrentObjectEx: case ipc::ControlCommand::CloneCurrentObjectEx:
CloneSession(*session, request, response); CloneSession(*session, request, response);
break; break;
case ipc::ControlCommand::QueryPointerBufferSize:
response.WriteValue<u32>(0x1000);
break;
default: default:
throw exception(fmt::format("Unknown Control Command: {}", request.payload->value)); throw exception("Unknown Control Command: {}", request.payload->value);
} }
response.WriteTls(); response.WriteTls();
break; break;
case ipc::CommandType::Close: case ipc::CommandType::Close:
state.logger->Write(Logger::Debug, "Closing Session"); state.logger->Debug("Closing Session");
CloseSession(handle); CloseSession(handle);
break; break;
default: default:
throw exception(fmt::format("Unimplemented IPC message type: {}", u16(request.header->type))); throw exception("Unimplemented IPC message type: {}", u16(request.header->type));
} }
} else } else
state.logger->Write(Logger::Warn, "svcSendSyncRequest called on closed handle: 0x{:X}", handle); state.logger->Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle);
state.logger->Write(Logger::Debug, "====End===="); state.logger->Debug("====End====");
} }
void ServiceManager::CloneSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void ServiceManager::CloneSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
NewService(session.serviceType, session, response); //NewService(session.serviceType, session, response);
} }
} }

View File

@ -34,11 +34,11 @@ namespace skyline::kernel::service {
/** /**
* @brief Creates a new service using it's type enum and writes it's handle or virtual handle (If it's a domain request) to IpcResponse * @brief Creates a new service using it's type enum and writes it's handle or virtual handle (If it's a domain request) to IpcResponse
* @param serviceType The type of the service * @param serviceName The name of the service
* @param session The session object of the command * @param session The session object of the command
* @param response The response object to write the handle or virtual handle to * @param response The response object to write the handle or virtual handle to
*/ */
std::shared_ptr<BaseService> NewService(const Service serviceType, type::KSession &session, ipc::IpcResponse &response); std::shared_ptr<BaseService> NewService(const std::string &serviceName, type::KSession &session, ipc::IpcResponse &response);
/** /**
* @brief Registers a service object in the manager and writes it's handle or virtual handle (If it's a domain request) to IpcResponse * @brief Registers a service object in the manager and writes it's handle or virtual handle (If it's a domain request) to IpcResponse
@ -48,7 +48,6 @@ namespace skyline::kernel::service {
*/ */
void RegisterService(std::shared_ptr<BaseService> serviceObject, type::KSession &session, ipc::IpcResponse &response); void RegisterService(std::shared_ptr<BaseService> serviceObject, type::KSession &session, ipc::IpcResponse &response);
/** /**
* @brief Closes an existing session to a service * @brief Closes an existing session to a service
* @param service The handle of the KService object * @param service The handle of the KService object

View File

@ -2,10 +2,10 @@
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
namespace skyline::kernel::service::set { namespace skyline::kernel::service::set {
sys::sys(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::set_sys, {{0x3, SFunc(sys::GetFirmwareVersion)}}) {} sys::sys(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::set_sys, {{0x3, SFUNC(sys::GetFirmwareVersion)}}) {}
void sys::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void sys::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
SysVerTitle title{.minor=9, .major=0, .micro=0, .rev_major=4, .platform="NX", .ver_hash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .disp_ver="9.0.0", .disp_title="NintendoSDK Firmware for NX 9.0.0-4.0"}; SysVerTitle title{.minor=9, .major=0, .micro=0, .revMajor=4, .platform="NX", .verHash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .dispVer="9.0.0", .dispTitle="NintendoSDK Firmware for NX 9.0.0-4.0"};
state.thisProcess->WriteMemory(title, request.vecBufC[0]->address); state.thisProcess->WriteMemory(title, request.vecBufC[0]->address);
} }
} }

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
namespace skyline::kernel::service::set { namespace skyline::kernel::service::set {
/** /**
@ -13,22 +13,22 @@ namespace skyline::kernel::service::set {
* @brief Encapsulates the system version, this is sent to the application in GetFirmwareVersion (https://switchbrew.org/wiki/System_Version_Title) * @brief Encapsulates the system version, this is sent to the application in GetFirmwareVersion (https://switchbrew.org/wiki/System_Version_Title)
*/ */
struct SysVerTitle { struct SysVerTitle {
u8 major; u8 major; //!< The major version
u8 minor; u8 minor; //!< The minor vision
u8 micro; u8 micro; //!< The micro vision
u8 : 8; u8 : 8;
u8 rev_major; u8 revMajor; //!< The major revision
u8 rev_minor; u8 revMinor; //!< The major revision
u16 : 16; u16 : 16;
u8 platform[0x20]; u8 platform[0x20]; //!< "NX"
u8 ver_hash[0x40]; u8 verHash[0x40]; //!< This is the hash of the version string
u8 disp_ver[0x18]; u8 dispVer[0x18]; //!< The version number string
u8 disp_title[0x80]; u8 dispTitle[0x80]; //!< The version title string
}; };
static_assert(sizeof(SysVerTitle) == 0x100); static_assert(sizeof(SysVerTitle) == 0x100);
public: public:
sys(const DeviceState &state, ServiceManager& manager); sys(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief Writes the Firmware version to a 0xA buffer (https://switchbrew.org/wiki/Settings_services#GetFirmwareVersion) * @brief Writes the Firmware version to a 0xA buffer (https://switchbrew.org/wiki/Settings_services#GetFirmwareVersion)

View File

@ -1,9 +1,9 @@
#include "sm.h" #include "sm.h"
namespace skyline::kernel::service::sm { namespace skyline::kernel::service::sm {
sm::sm(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::sm, { sm::sm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::sm, {
{0x0, SFunc(sm::Initialize)}, {0x0, SFUNC(sm::Initialize)},
{0x1, SFunc(sm::GetService)} {0x1, SFUNC(sm::GetService)}
}) {} }) {}
void sm::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} void sm::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
@ -14,10 +14,10 @@ namespace skyline::kernel::service::sm {
response.errorCode = constant::status::ServiceInvName; response.errorCode = constant::status::ServiceInvName;
else { else {
try { try {
manager.NewService(ServiceString.at(serviceName), session, response); manager.NewService(serviceName, session, response);
} catch (std::out_of_range &) { } catch (std::out_of_range &) {
response.errorCode = constant::status::ServiceNotReg; response.errorCode = constant::status::ServiceNotReg;
state.logger->Write(Logger::Error, "Service has not been implemented: \"{}\"", serviceName); state.logger->Warn("Service has not been implemented: \"{}\"", serviceName);
} }
} }
} }

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
namespace skyline::kernel::service::sm { namespace skyline::kernel::service::sm {
/** /**
@ -9,7 +9,7 @@ namespace skyline::kernel::service::sm {
*/ */
class sm : public BaseService { class sm : public BaseService {
public: public:
sm(const DeviceState &state, ServiceManager& manager); sm(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This initializes the sm: service. It doesn't actually do anything. (https://switchbrew.org/wiki/Services_API#Initialize) * @brief This initializes the sm: service. It doesn't actually do anything. (https://switchbrew.org/wiki/Services_API#Initialize)

View File

@ -2,10 +2,10 @@
namespace skyline::kernel::service::time { namespace skyline::kernel::service::time {
time::time(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::time, { time::time(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::time, {
{0x0, SFunc(time::GetStandardUserSystemClock)}, {0x0, SFUNC(time::GetStandardUserSystemClock)},
{0x1, SFunc(time::GetStandardNetworkSystemClock)}, {0x1, SFUNC(time::GetStandardNetworkSystemClock)},
{0x3, SFunc(time::GetTimeZoneService)}, {0x3, SFUNC(time::GetTimeZoneService)},
{0x4, SFunc(time::GetStandardLocalSystemClock)} {0x4, SFUNC(time::GetStandardLocalSystemClock)}
}) {} }) {}
void time::GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void time::GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -25,7 +25,7 @@ namespace skyline::kernel::service::time {
} }
ISystemClock::ISystemClock(SystemClockType clockType, const DeviceState &state, ServiceManager &manager) : type(clockType), BaseService(state, manager, false, Service::time_ISystemClock, { ISystemClock::ISystemClock(SystemClockType clockType, const DeviceState &state, ServiceManager &manager) : type(clockType), BaseService(state, manager, false, Service::time_ISystemClock, {
{0x0, SFunc(ISystemClock::GetCurrentTime)} {0x0, SFUNC(ISystemClock::GetCurrentTime)}
}) {} }) {}
void ISystemClock::GetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void ISystemClock::GetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -33,13 +33,13 @@ namespace skyline::kernel::service::time {
} }
ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::time_ITimeZoneService, { ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::time_ITimeZoneService, {
{0x65, SFunc(ITimeZoneService::ToCalendarTimeWithMyRule)} {0x65, SFUNC(ITimeZoneService::ToCalendarTimeWithMyRule)}
}) {} }) {}
void ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
time_t curTime = std::time(nullptr); time_t curTime = std::time(nullptr);
tm calender = *std::gmtime(&curTime); tm calender = *std::gmtime(&curTime);
CalendarTime calendarTime { CalendarTime calendarTime{
.year = static_cast<u16>(calender.tm_year), .year = static_cast<u16>(calender.tm_year),
.month = static_cast<u8>(calender.tm_mon), .month = static_cast<u8>(calender.tm_mon),
.day = static_cast<u8>(calender.tm_hour), .day = static_cast<u8>(calender.tm_hour),
@ -47,10 +47,10 @@ namespace skyline::kernel::service::time {
.second = static_cast<u8>(calender.tm_sec) .second = static_cast<u8>(calender.tm_sec)
}; };
response.WriteValue(calendarTime); response.WriteValue(calendarTime);
CalendarAdditionalInfo calendarInfo { CalendarAdditionalInfo calendarInfo{
.day_week = static_cast<u32>(calender.tm_wday), .day_week = static_cast<u32>(calender.tm_wday),
.day_month = static_cast<u32>(calender.tm_mday), .day_month = static_cast<u32>(calender.tm_mday),
.name = *reinterpret_cast<const u64*>(calender.tm_zone), .name = *reinterpret_cast<const u64 *>(calender.tm_zone),
.dst = static_cast<i32>(calender.tm_isdst), .dst = static_cast<i32>(calender.tm_isdst),
.utc_rel = static_cast<u32>(calender.tm_gmtoff) .utc_rel = static_cast<u32>(calender.tm_gmtoff)
}; };

View File

@ -1,13 +1,13 @@
#pragma once #pragma once
#include <kernel/services/base_service.h> #include <services/base_service.h>
#include <kernel/services/serviceman.h> #include <services/serviceman.h>
namespace skyline::kernel::service::time { namespace skyline::kernel::service::time {
/** /**
* @brief The type of a SystemClockType * @brief The type of a #SystemClockType
*/ */
enum SystemClockType { enum class SystemClockType {
User, //!< Use time provided by user User, //!< Use time provided by user
Network, //!< Use network time Network, //!< Use network time
Local, //!< Use local time Local, //!< Use local time
@ -18,7 +18,7 @@ namespace skyline::kernel::service::time {
*/ */
class time : public BaseService { class time : public BaseService {
public: public:
time(const DeviceState &state, ServiceManager& manager); time(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This returns a handle to a ISystemClock for user time (https://switchbrew.org/wiki/Services_API#GetStandardUserSystemClock) * @brief This returns a handle to a ISystemClock for user time (https://switchbrew.org/wiki/Services_API#GetStandardUserSystemClock)
@ -48,7 +48,7 @@ namespace skyline::kernel::service::time {
public: public:
SystemClockType type; //!< The type of the system clock SystemClockType type; //!< The type of the system clock
ISystemClock(SystemClockType clockType, const DeviceState &state, ServiceManager& manager); ISystemClock(SystemClockType clockType, const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This returns the amount of seconds since epoch * @brief This returns the amount of seconds since epoch
@ -73,7 +73,7 @@ namespace skyline::kernel::service::time {
u8 second; u8 second;
u8 : 8; u8 : 8;
}; };
static_assert(sizeof(CalendarTime)==8); static_assert(sizeof(CalendarTime) == 8);
/** /**
* @brief This is passed in addition to CalendarTime * @brief This is passed in addition to CalendarTime
@ -85,9 +85,9 @@ namespace skyline::kernel::service::time {
i32 dst; i32 dst;
u32 utc_rel; u32 utc_rel;
}; };
static_assert(sizeof(CalendarAdditionalInfo)==24); static_assert(sizeof(CalendarAdditionalInfo) == 24);
ITimeZoneService(const DeviceState &state, ServiceManager& manager); ITimeZoneService(const DeviceState &state, ServiceManager &manager);
/** /**
* @brief This receives a u64 #PosixTime (https://switchbrew.org/wiki/PSC_services#PosixTime), and returns a #CalendarTime (https://switchbrew.org/wiki/PSC_services#CalendarTime), #CalendarAdditionalInfo (https://switchbrew.org/wiki/PSC_services#CalendarAdditionalInfo) * @brief This receives a u64 #PosixTime (https://switchbrew.org/wiki/PSC_services#PosixTime), and returns a #CalendarTime (https://switchbrew.org/wiki/PSC_services#CalendarTime), #CalendarAdditionalInfo (https://switchbrew.org/wiki/PSC_services#CalendarAdditionalInfo)

View File

@ -0,0 +1,155 @@
#include "vi_m.h"
#include <kernel/types/KProcess.h>
#include <services/nvnflinger/dispdrv.h>
#include <gpu/display.h>
namespace skyline::kernel::service::vi {
vi_m::vi_m(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::nvdrv, {
{0x2, SFUNC(vi_m::GetDisplayService)}
}) {}
void vi_m::GetDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IApplicationDisplayService), session, response);
}
IDisplayService::IDisplayService(const DeviceState &state, ServiceManager &manager, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : BaseService(state, manager, false, serviceType, vTable) {}
void IDisplayService::CreateStrayLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.logger->Debug("Creating Stray Layer");
response.WriteValue<u64>(0); // There's only one layer
gpu::Parcel parcel(state);
LayerParcel data{
.type = 0x20,
.pid = 0,
.bufferId = 0, // As we only have one layer and buffer
.string = "dispdrv"
};
parcel.WriteData(data);
response.WriteValue<u64>(parcel.WriteParcel(request.vecBufB[0]));
}
IApplicationDisplayService::IApplicationDisplayService(const DeviceState &state, ServiceManager &manager) : IDisplayService(state, manager, Service::vi_IApplicationDisplayService, {
{0x64, SFUNC(IApplicationDisplayService::GetRelayService)},
{0x65, SFUNC(IApplicationDisplayService::GetSystemDisplayService)},
{0x66, SFUNC(IApplicationDisplayService::GetManagerDisplayService)},
{0x67, SFUNC(IApplicationDisplayService::GetIndirectDisplayTransactionService)},
{0x3F2, SFUNC(IApplicationDisplayService::OpenDisplay)},
{0x3FC, SFUNC(IApplicationDisplayService::CloseDisplay)},
{0x7E4, SFUNC(IApplicationDisplayService::OpenLayer)},
{0x7E5, SFUNC(IApplicationDisplayService::CloseLayer)},
{0x835, SFUNC(IApplicationDisplayService::SetLayerScalingMode)},
{0x1452, SFUNC(IApplicationDisplayService::GetDisplayVsyncEvent)},
}) {}
void IApplicationDisplayService::GetRelayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(nvnflinger::dispdrv), session, response);
}
void IApplicationDisplayService::GetIndirectDisplayTransactionService(skyline::kernel::type::KSession &session, skyline::kernel::ipc::IpcRequest &request, skyline::kernel::ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(nvnflinger::dispdrv), session, response);
}
void IApplicationDisplayService::GetSystemDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(ISystemDisplayService), session, response);
}
void IApplicationDisplayService::GetManagerDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(SRVREG(IManagerDisplayService), session, response);
}
void IApplicationDisplayService::OpenDisplay(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string displayName(reinterpret_cast<char *>(request.cmdArg));
state.logger->Debug("Setting display as: {}", displayName);
state.gpu->SetDisplay(displayName);
response.WriteValue<u64>(0); // There's only one display
}
void IApplicationDisplayService::CloseDisplay(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.logger->Debug("Closing the display");
state.gpu->CloseDisplay();
}
void IApplicationDisplayService::OpenLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
char displayName[0x40];
u64 layerId;
u64 userId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
state.logger->Debug("Opening Layer: Display Name: {}, Layer ID: {}, User ID: {}", input->displayName, input->layerId, input->userId);
std::string name(input->displayName);
gpu::Parcel parcel(state);
LayerParcel data{
.type = 0x20,
.pid = 0,
.bufferId = 0, // As we only have one layer and buffer
.string = "dispdrv"
};
parcel.WriteData(data);
parcel.objects.resize(4);
response.WriteValue(parcel.WriteParcel(request.vecBufB[0]));
}
void IApplicationDisplayService::CloseLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
u64 layerId = *reinterpret_cast<u64 *>(request.cmdArg);
state.logger->Debug("Closing Layer: {}", layerId);
if (state.gpu->layerStatus == gpu::LayerStatus::Uninitialized)
state.logger->Warn("The application is destroying an uninitialized layer");
state.gpu->layerStatus = gpu::LayerStatus::Uninitialized;
response.WriteValue<u32>(constant::status::Success);
}
void IApplicationDisplayService::SetLayerScalingMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u64 scalingMode;
u64 layerId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
state.logger->Debug("Setting Layer Scaling mode to '{}' for layer {}", input->scalingMode, input->layerId);
}
void IApplicationDisplayService::GetDisplayVsyncEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
handle_t handle = state.thisProcess->InsertItem(state.gpu->vsyncEvent);
state.logger->Info("VSyncEvent Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle);
}
ISystemDisplayService::ISystemDisplayService(const DeviceState &state, ServiceManager &manager) : IDisplayService(state, manager, Service::vi_ISystemDisplayService, {
{0x89D, SFUNC(ISystemDisplayService::SetLayerZ)},
{0x908, SFUNC(IDisplayService::CreateStrayLayer)}
}) {}
void ISystemDisplayService::SetLayerZ(skyline::kernel::type::KSession &session, skyline::kernel::ipc::IpcRequest &request, skyline::kernel::ipc::IpcResponse &response) {
response.WriteValue<u32>(constant::status::Success);
}
IManagerDisplayService::IManagerDisplayService(const DeviceState &state, ServiceManager &manager) : IDisplayService(state, manager, Service::vi_IManagerDisplayService, {
{0x7DA, SFUNC(IManagerDisplayService::CreateManagedLayer)},
{0x7DB, SFUNC(IManagerDisplayService::DestroyManagedLayer)},
{0x7DC, SFUNC(IDisplayService::CreateStrayLayer)},
{0x1770, SFUNC(IManagerDisplayService::AddToLayerStack)}
}) {}
void IManagerDisplayService::CreateManagedLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u32 _unk0_;
u64 displayId;
u64 userId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
state.logger->Debug("Creating Managed Layer: {}", input->displayId);
if (state.gpu->layerStatus == gpu::LayerStatus::Initialized)
throw exception("The application is creating more than one layer");
state.gpu->layerStatus = gpu::LayerStatus::Initialized;
response.WriteValue<u64>(0); // There's only one layer
}
void IManagerDisplayService::DestroyManagedLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.logger->Debug("Destroying managed layer");
if (state.gpu->layerStatus == gpu::LayerStatus::Uninitialized)
state.logger->Warn("The application is destroying an uninitialized layer");
state.gpu->layerStatus = gpu::LayerStatus::Uninitialized;
response.WriteValue<u32>(constant::status::Success);
}
void IManagerDisplayService::AddToLayerStack(skyline::kernel::type::KSession &session, skyline::kernel::ipc::IpcRequest &request, skyline::kernel::ipc::IpcResponse &response) {
response.WriteValue<u32>(constant::status::Success);
}
}

View File

@ -0,0 +1,142 @@
#pragma once
#include <services/base_service.h>
#include <services/serviceman.h>
#include <gpu.h>
#include <gpu/parcel.h>
namespace skyline::kernel::service::vi {
/**
* @brief This service is used to get an handle to #IApplicationDisplayService (https://switchbrew.org/wiki/Display_services#vi:m)
*/
class vi_m : public BaseService {
public:
vi_m(const DeviceState &state, ServiceManager &manager);
/**
* @brief This returns an handle to #IApplicationDisplayService (https://switchbrew.org/wiki/Display_services#GetDisplayService)
*/
void GetDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief This is the base class for all IDisplayService variants with common functions
*/
class IDisplayService : public BaseService {
protected:
/**
* @brief This is the format of the parcel used in OpenLayer/CreateStrayLayer
*/
struct LayerParcel {
u32 type; //!< The type of the layer
u32 pid; //!< The PID that the layer belongs to
u32 bufferId; //!< The buffer ID of the layer
u32 _pad0_[3];
u8 string[0x8]; //!< "dispdrv"
u64 _pad1_;
};
static_assert(sizeof(LayerParcel) == 0x28);
public:
IDisplayService(const DeviceState &state, ServiceManager &manager, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable);
/**
* @brief This takes a display's ID and returns a layer ID and the corresponding buffer ID
*/
void CreateStrayLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief This service is used to access the display (https://switchbrew.org/wiki/Display_services#IApplicationDisplayService)
*/
class IApplicationDisplayService : public IDisplayService {
public:
IApplicationDisplayService(const DeviceState &state, ServiceManager &manager);
/**
* @brief Returns an handle to the 'nvnflinger' service (https://switchbrew.org/wiki/Display_services#GetRelayService)
*/
void GetRelayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Returns an handle to the 'nvnflinger' service (https://switchbrew.org/wiki/Display_services#GetIndirectDisplayTransactionService)
*/
void GetIndirectDisplayTransactionService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Returns an handle to #ISystemDisplayService (https://switchbrew.org/wiki/Display_services#GetSystemDisplayService)
*/
void GetSystemDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Returns an handle to #IManagerDisplayService (https://switchbrew.org/wiki/Display_services#GetManagerDisplayService)
*/
void GetManagerDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Opens up a display using it's name as the input (https://switchbrew.org/wiki/Display_services#OpenDisplay)
*/
void OpenDisplay(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Closes an open display using it's ID (https://switchbrew.org/wiki/Display_services#CloseDisplay)
*/
void CloseDisplay(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Opens a specific layer on a display (https://switchbrew.org/wiki/Display_services#OpenLayer)
*/
void OpenLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Closes a specific layer on a display (https://switchbrew.org/wiki/Display_services#CloseLayer)
*/
void CloseLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Sets the scaling mode for a window, this is not required by emulators (https://switchbrew.org/wiki/Display_services#SetLayerScalingMode)
*/
void SetLayerScalingMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Returns a handle to a KEvent which is triggered every time a frame is drawn (https://switchbrew.org/wiki/Display_services#GetDisplayVsyncEvent)
*/
void GetDisplayVsyncEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief This service retrieves information about a display in context of the entire system (https://switchbrew.org/wiki/Display_services#ISystemDisplayService)
*/
class ISystemDisplayService : public IDisplayService {
public:
ISystemDisplayService(const DeviceState &state, ServiceManager &manager);
/**
* @brief Sets the Z index of a layer
*/
void SetLayerZ(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
/**
* @brief This service retrieves information about a display in context of the entire system (https://switchbrew.org/wiki/Display_services#IManagerDisplayService)
*/
class IManagerDisplayService : public IDisplayService {
public:
IManagerDisplayService(const DeviceState &state, ServiceManager &manager);
/**
* @brief Creates a managed layer on a specific display
*/
void CreateManagedLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Destroys a managed layer created on a specific display
*/
void DestroyManagedLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This takes a layer's ID and adds it to the layer stack
*/
void AddToLayerStack(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
}

View File

@ -21,20 +21,20 @@ import java.util.Objects;
class GameItem extends BaseItem { class GameItem extends BaseItem {
private final File file; private final File file;
transient private TitleEntry meta;
private final int index; private final int index;
private transient TitleEntry meta;
GameItem(File file) { GameItem(final File file) {
this.file = file; this.file = file;
index = file.getName().lastIndexOf("."); index = file.getName().lastIndexOf(".");
meta = NroLoader.getTitleEntry(getPath()); meta = NroLoader.getTitleEntry(getPath());
if (meta == null) { if (meta == null) {
meta = new TitleEntry(file.getName(), GameAdapter.mContext.getString(R.string.aset_missing), null); meta = new TitleEntry(file.getName(), HeaderAdapter.mContext.getString(R.string.aset_missing), null);
} }
} }
public boolean hasIcon() { public boolean hasIcon() {
return !getSubTitle().equals(GameAdapter.mContext.getString(R.string.aset_missing)); return !getSubTitle().equals(HeaderAdapter.mContext.getString(R.string.aset_missing));
} }
public Bitmap getIcon() { public Bitmap getIcon() {
@ -71,10 +71,10 @@ class GameItem extends BaseItem {
public class GameAdapter extends HeaderAdapter<GameItem> implements View.OnClickListener { public class GameAdapter extends HeaderAdapter<GameItem> implements View.OnClickListener {
GameAdapter(Context context) { super(context); } GameAdapter(final Context context) { super(context); }
@Override @Override
public void load(File file) throws IOException, ClassNotFoundException { public void load(final File file) throws IOException, ClassNotFoundException {
super.load(file); super.load(file);
for (int i = 0; i < item_array.size(); i++) for (int i = 0; i < item_array.size(); i++)
item_array.set(i, new GameItem(item_array.get(i).getFile())); item_array.set(i, new GameItem(item_array.get(i).getFile()));
@ -82,15 +82,15 @@ public class GameAdapter extends HeaderAdapter<GameItem> implements View.OnClick
} }
@Override @Override
public void onClick(View view) { public void onClick(final View view) {
int position = (int) view.getTag(); final int position = (int) view.getTag();
if (getItemViewType(position) == ContentType.Item) { if (getItemViewType(position) == ContentType.Item) {
GameItem item = (GameItem) getItem(position); final GameItem item = (GameItem) getItem(position);
if (view.getId() == R.id.icon) { if (view.getId() == R.id.icon) {
Dialog builder = new Dialog(mContext); final Dialog builder = new Dialog(HeaderAdapter.mContext);
builder.requestWindowFeature(Window.FEATURE_NO_TITLE); builder.requestWindowFeature(Window.FEATURE_NO_TITLE);
Objects.requireNonNull(builder.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); Objects.requireNonNull(builder.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
ImageView imageView = new ImageView(mContext); final ImageView imageView = new ImageView(HeaderAdapter.mContext);
assert item != null; assert item != null;
imageView.setImageBitmap(item.getIcon()); imageView.setImageBitmap(item.getIcon());
builder.addContentView(imageView, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); builder.addContentView(imageView, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
@ -101,39 +101,39 @@ public class GameAdapter extends HeaderAdapter<GameItem> implements View.OnClick
@NonNull @NonNull
@Override @Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) { public View getView(final int position, View convertView, @NonNull final ViewGroup parent) {
ViewHolder viewHolder; final GameAdapter.ViewHolder viewHolder;
int type = type_array.get(position).type; final int type = type_array.get(position).type;
if (convertView == null) { if (convertView == null) {
if (type == ContentType.Item) { if (type == ContentType.Item) {
viewHolder = new ViewHolder(); viewHolder = new GameAdapter.ViewHolder();
LayoutInflater inflater = LayoutInflater.from(mContext); final LayoutInflater inflater = LayoutInflater.from(HeaderAdapter.mContext);
convertView = inflater.inflate(R.layout.game_item, parent, false); convertView = inflater.inflate(R.layout.game_item, parent, false);
viewHolder.icon = convertView.findViewById(R.id.icon); viewHolder.icon = convertView.findViewById(R.id.icon);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title); viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle); viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle);
convertView.setTag(viewHolder); convertView.setTag(viewHolder);
} else { } else {
viewHolder = new ViewHolder(); viewHolder = new GameAdapter.ViewHolder();
LayoutInflater inflater = LayoutInflater.from(mContext); final LayoutInflater inflater = LayoutInflater.from(HeaderAdapter.mContext);
convertView = inflater.inflate(R.layout.section_item, parent, false); convertView = inflater.inflate(R.layout.section_item, parent, false);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title); viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
convertView.setTag(viewHolder); convertView.setTag(viewHolder);
} }
} else { } else {
viewHolder = (ViewHolder) convertView.getTag(); viewHolder = (GameAdapter.ViewHolder) convertView.getTag();
} }
if (type == ContentType.Item) { if (type == ContentType.Item) {
GameItem data = (GameItem) getItem(position); final GameItem data = (GameItem) getItem(position);
viewHolder.txtTitle.setText(data.getTitle()); viewHolder.txtTitle.setText(data.getTitle());
viewHolder.txtSub.setText(data.getSubTitle()); viewHolder.txtSub.setText(data.getSubTitle());
Bitmap icon = data.getIcon(); final Bitmap icon = data.getIcon();
if (icon != null) { if (icon != null) {
viewHolder.icon.setImageBitmap(icon); viewHolder.icon.setImageBitmap(icon);
viewHolder.icon.setOnClickListener(this); viewHolder.icon.setOnClickListener(this);
viewHolder.icon.setTag(position); viewHolder.icon.setTag(position);
} else { } else {
viewHolder.icon.setImageDrawable(mContext.getDrawable(R.drawable.ic_missing_icon)); viewHolder.icon.setImageDrawable(HeaderAdapter.mContext.getDrawable(R.drawable.ic_missing_icon));
viewHolder.icon.setOnClickListener(null); viewHolder.icon.setOnClickListener(null);
} }
} else { } else {

View File

@ -24,20 +24,20 @@ import me.xdrop.fuzzywuzzy.FuzzySearch;
import me.xdrop.fuzzywuzzy.model.ExtractedResult; import me.xdrop.fuzzywuzzy.model.ExtractedResult;
class ContentType implements Serializable { class ContentType implements Serializable {
transient static final int Header = 0; static final transient int Header = 0;
transient static final int Item = 1; static final transient int Item = 1;
public final int type; public final int type;
public int index; public int index;
ContentType(int index, int type) { ContentType(final int index, final int type) {
this(type); this(type);
this.index = index; this.index = index;
} }
private ContentType(int type) { private ContentType(final int type) {
switch (type) { switch (type) {
case Item: case ContentType.Item:
case Header: case ContentType.Header:
break; break;
default: default:
throw (new IllegalArgumentException()); throw (new IllegalArgumentException());
@ -59,15 +59,15 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
private ArrayList<String> header_array; private ArrayList<String> header_array;
private String search_term = ""; private String search_term = "";
HeaderAdapter(Context context) { HeaderAdapter(final Context context) {
mContext = context; HeaderAdapter.mContext = context;
this.item_array = new ArrayList<>(); item_array = new ArrayList<>();
this.header_array = new ArrayList<>(); header_array = new ArrayList<>();
this.type_array_uf = new ArrayList<>(); type_array_uf = new ArrayList<>();
this.type_array = new ArrayList<>(); type_array = new ArrayList<>();
} }
public void add(Object item, int type) { public void add(final Object item, final int type) {
if (type == ContentType.Item) { if (type == ContentType.Item) {
item_array.add((ItemType) item); item_array.add((ItemType) item);
type_array_uf.add(new ContentType(item_array.size() - 1, ContentType.Item)); type_array_uf.add(new ContentType(item_array.size() - 1, ContentType.Item));
@ -76,31 +76,31 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
type_array_uf.add(new ContentType(header_array.size() - 1, ContentType.Header)); type_array_uf.add(new ContentType(header_array.size() - 1, ContentType.Header));
} }
if (search_term.length() != 0) if (search_term.length() != 0)
this.getFilter().filter(search_term); getFilter().filter(search_term);
else else
type_array = type_array_uf; type_array = type_array_uf;
} }
public void save(File file) throws IOException { public void save(final File file) throws IOException {
State state = new State<>(item_array, header_array, type_array_uf); final HeaderAdapter.State state = new HeaderAdapter.State(item_array, header_array, type_array_uf);
FileOutputStream file_obj = new FileOutputStream(file); final FileOutputStream file_obj = new FileOutputStream(file);
ObjectOutputStream out = new ObjectOutputStream(file_obj); final ObjectOutputStream out = new ObjectOutputStream(file_obj);
out.writeObject(state); out.writeObject(state);
out.close(); out.close();
file_obj.close(); file_obj.close();
} }
void load(File file) throws IOException, ClassNotFoundException { void load(final File file) throws IOException, ClassNotFoundException {
FileInputStream file_obj = new FileInputStream(file); final FileInputStream file_obj = new FileInputStream(file);
ObjectInputStream in = new ObjectInputStream(file_obj); final ObjectInputStream in = new ObjectInputStream(file_obj);
State state = (State) in.readObject(); final HeaderAdapter.State state = (HeaderAdapter.State) in.readObject();
in.close(); in.close();
file_obj.close(); file_obj.close();
if (state != null) { if (state != null) {
this.item_array = state.item_array; item_array = state.item_array;
this.header_array = state.header_array; header_array = state.header_array;
this.type_array_uf = state.type_array; type_array_uf = state.type_array;
this.getFilter().filter(search_term); getFilter().filter(search_term);
} }
} }
@ -118,8 +118,8 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
} }
@Override @Override
public Object getItem(int i) { public Object getItem(final int i) {
ContentType type = type_array.get(i); final ContentType type = type_array.get(i);
if (type.type == ContentType.Item) if (type.type == ContentType.Item)
return item_array.get(type.index); return item_array.get(type.index);
else else
@ -127,12 +127,12 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
} }
@Override @Override
public long getItemId(int position) { public long getItemId(final int position) {
return position; return position;
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(final int position) {
return type_array.get(position).type; return type_array.get(position).type;
} }
@ -149,24 +149,24 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
public Filter getFilter() { public Filter getFilter() {
return new Filter() { return new Filter() {
@Override @Override
protected FilterResults performFiltering(CharSequence charSequence) { protected Filter.FilterResults performFiltering(final CharSequence charSequence) {
FilterResults results = new FilterResults(); final Filter.FilterResults results = new Filter.FilterResults();
search_term = ((String) charSequence).toLowerCase().replaceAll(" ", ""); search_term = ((String) charSequence).toLowerCase().replaceAll(" ", "");
if (charSequence.length() == 0) { if (charSequence.length() == 0) {
results.values = type_array_uf; results.values = type_array_uf;
results.count = type_array_uf.size(); results.count = type_array_uf.size();
} else { } else {
ArrayList<ContentType> filter_data = new ArrayList<>(); final ArrayList<ContentType> filter_data = new ArrayList<>();
ArrayList<String> key_arr = new ArrayList<>(); final ArrayList<String> key_arr = new ArrayList<>();
SparseIntArray key_ind = new SparseIntArray(); final SparseIntArray key_ind = new SparseIntArray();
for (int index = 0; index < type_array_uf.size(); index++) { for (int index = 0; index < type_array_uf.size(); index++) {
ContentType item = type_array_uf.get(index); final ContentType item = type_array_uf.get(index);
if (item.type == ContentType.Item) { if (item.type == ContentType.Item) {
key_arr.add(item_array.get(item.index).key().toLowerCase()); key_arr.add(item_array.get(item.index).key().toLowerCase());
key_ind.append(key_arr.size() - 1, index); key_ind.append(key_arr.size() - 1, index);
} }
} }
for (ExtractedResult result : FuzzySearch.extractTop(search_term, key_arr, Math.max(1, 10 - search_term.length()))) for (final ExtractedResult result : FuzzySearch.extractTop(search_term, key_arr, Math.max(1, 10 - search_term.length())))
if (result.getScore() >= 35) if (result.getScore() >= 35)
filter_data.add(type_array_uf.get(key_ind.get(result.getIndex()))); filter_data.add(type_array_uf.get(key_ind.get(result.getIndex())));
results.values = filter_data; results.values = filter_data;
@ -176,7 +176,7 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
} }
@Override @Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) { protected void publishResults(final CharSequence charSequence, final Filter.FilterResults filterResults) {
type_array = (ArrayList<ContentType>) filterResults.values; type_array = (ArrayList<ContentType>) filterResults.values;
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -188,7 +188,7 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
private final ArrayList<String> header_array; private final ArrayList<String> header_array;
private final ArrayList<ContentType> type_array; private final ArrayList<ContentType> type_array;
State(ArrayList<StateType> item_array, ArrayList<String> header_array, ArrayList<ContentType> type_array) { State(final ArrayList<StateType> item_array, final ArrayList<String> header_array, final ArrayList<ContentType> type_array) {
this.item_array = item_array; this.item_array = item_array;
this.header_array = header_array; this.header_array = header_array;
this.type_array = type_array; this.type_array = type_array;

View File

@ -3,7 +3,6 @@ package emu.skyline;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.FileObserver;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -38,59 +37,39 @@ import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import static java.lang.Thread.interrupted;
public class LogActivity extends AppCompatActivity { public class LogActivity extends AppCompatActivity {
private File log_file; private File log_file;
private BufferedReader reader;
private Thread thread;
private LogAdapter adapter; private LogAdapter adapter;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.log_activity); setContentView(R.layout.log_activity);
setSupportActionBar(findViewById(R.id.toolbar)); setSupportActionBar(findViewById(R.id.toolbar));
ActionBar actionBar = getSupportActionBar(); final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) if (actionBar != null)
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
final ListView log_list = this.findViewById(R.id.log_list); ListView log_list = findViewById(R.id.log_list);
adapter = new LogAdapter(this, Integer.parseInt(prefs.getString("log_level", "3")), getResources().getStringArray(R.array.log_level)); adapter = new LogAdapter(this, prefs.getBoolean("log_compact", false), Integer.parseInt(prefs.getString("log_level", "3")), getResources().getStringArray(R.array.log_level));
log_list.setAdapter(adapter); log_list.setAdapter(adapter);
log_file = new File(getApplicationInfo().dataDir + "/skyline.log");
try { try {
InputStream inputStream = new FileInputStream(log_file); log_file = new File(getApplicationInfo().dataDir + "/skyline.log");
reader = new BufferedReader(new InputStreamReader(inputStream)); final InputStream inputStream = new FileInputStream(log_file);
thread = new Thread(() -> { final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
// Required as FileObserver(File) is only on API level 29 also no AndroidX version present try {
FileObserver observer = new FileObserver(log_file.getPath()) { boolean done = false;
@Override while (!done) {
public void onEvent(int event, String path) { String line = reader.readLine();
if (event == FileObserver.MODIFY) { if (!(done = (line == null))) {
try { adapter.add(line);
boolean done = false;
while (!done) {
final String line = reader.readLine();
done = (line == null);
if (!done) {
runOnUiThread(() -> adapter.add(line));
}
}
} catch (IOException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
} }
}; }
observer.onEvent(FileObserver.MODIFY, log_file.getPath()); } catch (final IOException e) {
observer.startWatching(); Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
while (!interrupted()) ; Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
observer.stopWatching(); }
}); } catch (final FileNotFoundException e) {
thread.start();
} catch (FileNotFoundException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage()); Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show();
finish(); finish();
@ -98,19 +77,19 @@ public class LogActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_log, menu); getMenuInflater().inflate(R.menu.toolbar_log, menu);
MenuItem mSearch = menu.findItem(R.id.action_search_log); final MenuItem mSearch = menu.findItem(R.id.action_search_log);
final SearchView searchView = (SearchView) mSearch.getActionView(); SearchView searchView = (SearchView) mSearch.getActionView();
searchView.setSubmitButtonEnabled(false); searchView.setSubmitButtonEnabled(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(final String query) {
searchView.setIconified(false); searchView.setIconified(false);
return false; return false;
} }
@Override @Override
public boolean onQueryTextChange(String newText) { public boolean onQueryTextChange(final String newText) {
adapter.getFilter().filter(newText); adapter.getFilter().filter(newText);
return true; return true;
} }
@ -119,13 +98,13 @@ public class LogActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) { public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_clear: case R.id.action_clear:
try { try {
FileWriter fileWriter = new FileWriter(log_file, false); final FileWriter fileWriter = new FileWriter(log_file, false);
fileWriter.close(); fileWriter.close();
} catch (IOException e) { } catch (final IOException e) {
Log.w("Logger", "IO Error while clearing the log file: " + e.getMessage()); Log.w("Logger", "IO Error while clearing the log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
} }
@ -133,19 +112,19 @@ public class LogActivity extends AppCompatActivity {
finish(); finish();
return true; return true;
case R.id.action_share_log: case R.id.action_share_log:
Thread share_thread = new Thread(() -> { final Thread share_thread = new Thread(() -> {
HttpsURLConnection urlConnection = null; HttpsURLConnection urlConnection = null;
try { try {
URL url = new URL("https://hastebin.com/documents"); final URL url = new URL("https://hastebin.com/documents");
urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST"); urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Host", "hastebin.com"); urlConnection.setRequestProperty("Host", "hastebin.com");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
urlConnection.setRequestProperty("Referer", "https://hastebin.com/"); urlConnection.setRequestProperty("Referer", "https://hastebin.com/");
urlConnection.setRequestProperty("Connection", "keep-alive"); urlConnection.setRequestProperty("Connection", "keep-alive");
OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream()); final OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); final BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
FileReader fileReader = new FileReader(log_file); final FileReader fileReader = new FileReader(log_file);
int chr; int chr;
while ((chr = fileReader.read()) != -1) { while ((chr = fileReader.read()) != -1) {
bufferedWriter.write(chr); bufferedWriter.write(chr);
@ -157,15 +136,15 @@ public class LogActivity extends AppCompatActivity {
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.getResponseCode()); Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.getResponseCode());
throw new Exception(); throw new Exception();
} }
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); final InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key"); final String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
bufferedReader.close(); bufferedReader.close();
inputStream.close(); inputStream.close();
String result = "https://hastebin.com/" + key; final String result = "https://hastebin.com/" + key;
Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result); final Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
startActivity(Intent.createChooser(sharingIntent, "Share log url with:")); startActivity(Intent.createChooser(sharingIntent, "Share log url with:"));
} catch (Exception e) { } catch (final Exception e) {
runOnUiThread(() -> Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show()); runOnUiThread(() -> Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show());
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@ -176,7 +155,7 @@ public class LogActivity extends AppCompatActivity {
share_thread.start(); share_thread.start();
try { try {
share_thread.join(1000); share_thread.join(1000);
} catch (Exception e) { } catch (final Exception e) {
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
e.printStackTrace(); e.printStackTrace();
} }
@ -184,18 +163,4 @@ public class LogActivity extends AppCompatActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
@Override
protected void onDestroy() {
super.onDestroy();
try {
thread.interrupt();
thread.join();
reader.close();
} catch (IOException e) {
Log.w("Logger", "IO Error during closing BufferedReader: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
} catch (NullPointerException | InterruptedException ignored) {
}
}
} }

View File

@ -15,7 +15,7 @@ class LogItem extends BaseItem {
private final String content; private final String content;
private final String level; private final String level;
LogItem(String content, String level) { LogItem(final String content, final String level) {
this.content = content; this.content = content;
this.level = level; this.level = level;
} }
@ -38,28 +38,32 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
private final ClipboardManager clipboard; private final ClipboardManager clipboard;
private final int debug_level; private final int debug_level;
private final String[] level_str; private final String[] level_str;
private final boolean compact;
LogAdapter(Context context, int debug_level, String[] level_str) { LogAdapter(final Context context, final boolean compact, final int debug_level, final String[] level_str) {
super(context); super(context);
clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
this.debug_level = debug_level; this.debug_level = debug_level;
this.level_str = level_str; this.level_str = level_str;
this.compact = compact;
} }
void add(final String log_line) { void add(String log_line) {
String[] log_meta = log_line.split("\\|", 3); try {
if (log_meta[0].startsWith("1")) { final String[] log_meta = log_line.split("\\|", 3);
int level = Integer.parseInt(log_meta[1]); if (log_meta[0].startsWith("1")) {
if (level > this.debug_level) return; final int level = Integer.parseInt(log_meta[1]);
super.add(new LogItem(log_meta[2], level_str[level]), ContentType.Item); if (level > debug_level) return;
} else { add(new LogItem(log_meta[2].replace('\\', '\n'), level_str[level]), ContentType.Item);
super.add(log_meta[1], ContentType.Header); } else {
} add(log_meta[1], ContentType.Header);
}
} catch (final IndexOutOfBoundsException ignored) {}
} }
@Override @Override
public boolean onLongClick(View view) { public boolean onLongClick(final View view) {
LogItem item = (LogItem) getItem(((ViewHolder) view.getTag()).position); final LogItem item = (LogItem) getItem(((LogAdapter.ViewHolder) view.getTag()).position);
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getMessage() + " (" + item.getLevel() + ")")); clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getMessage() + " (" + item.getLevel() + ")"));
Toast.makeText(view.getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show(); Toast.makeText(view.getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show();
return false; return false;
@ -67,32 +71,35 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
@NonNull @NonNull
@Override @Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) { public View getView(final int position, View convertView, @NonNull final ViewGroup parent) {
ViewHolder viewHolder; final LogAdapter.ViewHolder viewHolder;
int type = type_array.get(position).type; final int type = type_array.get(position).type;
if (convertView == null) { if (convertView == null) {
viewHolder = new LogAdapter.ViewHolder();
final LayoutInflater inflater = LayoutInflater.from(HeaderAdapter.mContext);
if (type == ContentType.Item) { if (type == ContentType.Item) {
viewHolder = new ViewHolder(); if (compact) {
LayoutInflater inflater = LayoutInflater.from(mContext); convertView = inflater.inflate(R.layout.log_item_compact, parent, false);
convertView = inflater.inflate(R.layout.log_item, parent, false); viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
} else {
convertView = inflater.inflate(R.layout.log_item, parent, false);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle);
}
convertView.setOnLongClickListener(this); convertView.setOnLongClickListener(this);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle);
convertView.setTag(viewHolder);
} else { } else {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(mContext);
convertView = inflater.inflate(R.layout.section_item, parent, false); convertView = inflater.inflate(R.layout.section_item, parent, false);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title); viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
convertView.setTag(viewHolder);
} }
convertView.setTag(viewHolder);
} else { } else {
viewHolder = (ViewHolder) convertView.getTag(); viewHolder = (LogAdapter.ViewHolder) convertView.getTag();
} }
if (type == ContentType.Item) { if (type == ContentType.Item) {
LogItem data = (LogItem) getItem(position); final LogItem data = (LogItem) getItem(position);
viewHolder.txtTitle.setText(data.getMessage()); viewHolder.txtTitle.setText(data.getMessage());
viewHolder.txtSub.setText(data.getLevel()); if (!compact)
viewHolder.txtSub.setText(data.getLevel());
} else { } else {
viewHolder.txtTitle.setText((String) getItem(position)); viewHolder.txtTitle.setText((String) getItem(position));
} }

View File

@ -8,6 +8,7 @@ import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.Surface;
import android.view.View; import android.view.View;
import android.widget.ListView; import android.widget.ListView;
@ -33,30 +34,30 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
System.loadLibrary("skyline"); System.loadLibrary("skyline");
} }
private SharedPreferences sharedPreferences = null; private SharedPreferences sharedPreferences;
private GameAdapter adapter = null; private GameAdapter adapter;
private void notifyUser(String text) { private void notifyUser(final String text) {
Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show(); Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show();
} }
private List<File> findFile(String ext, File file, @Nullable List<File> files) { private List<File> findFile(final String ext, final File file, @Nullable List<File> files) {
if (files == null) if (files == null)
files = new ArrayList<>(); files = new ArrayList<>();
File[] list = file.listFiles(); final File[] list = file.listFiles();
if (list != null) { if (list != null) {
for (File file_i : list) { for (final File file_i : list) {
if (file_i.isDirectory()) { if (file_i.isDirectory()) {
files = findFile(ext, file_i, files); files = findFile(ext, file_i, files);
} else { } else {
try { try {
String file_str = file_i.getName(); final String file_str = file_i.getName();
if (ext.equalsIgnoreCase(file_str.substring(file_str.lastIndexOf(".") + 1))) { if (ext.equalsIgnoreCase(file_str.substring(file_str.lastIndexOf(".") + 1))) {
if (NroLoader.verifyFile(file_i.getAbsolutePath())) { if (NroLoader.verifyFile(file_i.getAbsolutePath())) {
files.add(file_i); files.add(file_i);
} }
} }
} catch (StringIndexOutOfBoundsException e) { } catch (final StringIndexOutOfBoundsException e) {
Log.w("findFile", Objects.requireNonNull(e.getMessage())); Log.w("findFile", Objects.requireNonNull(e.getMessage()));
} }
} }
@ -65,72 +66,75 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
return files; return files;
} }
private void RefreshFiles(boolean try_load) { private void RefreshFiles(final boolean try_load) {
if (try_load) { if (try_load) {
try { try {
adapter.load(new File(getApplicationInfo().dataDir + "/roms.bin")); adapter.load(new File(getApplicationInfo().dataDir + "/roms.bin"));
return; return;
} catch (Exception e) { } catch (final Exception e) {
Log.w("refreshFiles", "Ran into exception while loading: " + Objects.requireNonNull(e.getMessage())); Log.w("refreshFiles", "Ran into exception while loading: " + Objects.requireNonNull(e.getMessage()));
} }
} }
adapter.clear(); adapter.clear();
List<File> files = findFile("nro", new File(sharedPreferences.getString("search_location", "")), null); final List<File> files = findFile("nro", new File(sharedPreferences.getString("search_location", "")), null);
if (!files.isEmpty()) { if (!files.isEmpty()) {
adapter.add(getString(R.string.nro), ContentType.Header); adapter.add(getString(R.string.nro), ContentType.Header);
for (File file : files) for (final File file : files)
adapter.add(new GameItem(file), ContentType.Item); adapter.add(new GameItem(file), ContentType.Item);
} else { } else {
adapter.add(getString(R.string.no_rom), ContentType.Header); adapter.add(getString(R.string.no_rom), ContentType.Header);
} }
try { try {
adapter.save(new File(getApplicationInfo().dataDir + "/roms.bin")); adapter.save(new File(getApplicationInfo().dataDir + "/roms.bin"));
} catch (IOException e) { } catch (final IOException e) {
Log.w("refreshFiles", "Ran into exception while saving: " + Objects.requireNonNull(e.getMessage())); Log.w("refreshFiles", "Ran into exception while saving: " + Objects.requireNonNull(e.getMessage()));
} }
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1); ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)
System.exit(0); System.exit(0);
} }
setContentView(R.layout.main_activity); setContentView(R.layout.main_activity);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false); PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
setSupportActionBar(findViewById(R.id.toolbar)); setSupportActionBar(findViewById(R.id.toolbar));
FloatingActionButton log_fab = findViewById(R.id.log_fab); final FloatingActionButton log_fab = findViewById(R.id.log_fab);
log_fab.setOnClickListener(this); log_fab.setOnClickListener(this);
adapter = new GameAdapter(this); adapter = new GameAdapter(this);
ListView game_list = findViewById(R.id.game_list); final ListView game_list = findViewById(R.id.game_list);
game_list.setAdapter(adapter); game_list.setAdapter(adapter);
game_list.setOnItemClickListener((parent, view, position, id) -> { game_list.setOnItemClickListener((parent, view, position, id) -> {
if (adapter.getItemViewType(position) == ContentType.Item) { if (adapter.getItemViewType(position) == ContentType.Item) {
GameItem item = ((GameItem) parent.getItemAtPosition(position)); final GameItem item = ((GameItem) parent.getItemAtPosition(position));
notifyUser(getString(R.string.launching) + " " + item.getTitle()); final Intent intent = new Intent(this, android.app.NativeActivity.class);
loadFile(item.getPath(), getApplicationInfo().dataDir + "/shared_prefs/" + getApplicationInfo().packageName + "_preferences.xml", getApplicationInfo().dataDir + "/skyline.log"); intent.putExtra("rom", item.getPath());
intent.putExtra("prefs", getApplicationInfo().dataDir + "/shared_prefs/" + getApplicationInfo().packageName + "_preferences.xml");
intent.putExtra("log", getApplicationInfo().dataDir + "/skyline.log");
startActivity(intent);
} }
}); });
RefreshFiles(true); RefreshFiles(true);
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu); getMenuInflater().inflate(R.menu.toolbar_main, menu);
MenuItem mSearch = menu.findItem(R.id.action_search_main); final MenuItem mSearch = menu.findItem(R.id.action_search_main);
final SearchView searchView = (SearchView) mSearch.getActionView(); SearchView searchView = (SearchView) mSearch.getActionView();
searchView.setSubmitButtonEnabled(false); searchView.setSubmitButtonEnabled(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(final String query) {
searchView.clearFocus(); searchView.clearFocus();
return false; return false;
} }
@Override @Override
public boolean onQueryTextChange(String newText) { public boolean onQueryTextChange(final String newText) {
adapter.getFilter().filter(newText); adapter.getFilter().filter(newText);
return true; return true;
} }
@ -138,13 +142,13 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
public void onClick(View view) { public void onClick(final View view) {
if (view.getId() == R.id.log_fab) if (view.getId() == R.id.log_fab)
startActivity(new Intent(this, LogActivity.class)); startActivity(new Intent(this, LogActivity.class));
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_settings: case R.id.action_settings:
startActivity(new Intent(this, SettingsActivity.class)); startActivity(new Intent(this, SettingsActivity.class));
@ -159,5 +163,5 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
} }
public native void loadFile(String rom_path, String preference_path, String log_path); public native void loadFile(String rom_path, String preference_path, String log_path, Surface surface);
} }

View File

@ -12,7 +12,7 @@ final class TitleEntry {
private final String author; private final String author;
private final Bitmap icon; private final Bitmap icon;
TitleEntry(String name, String author, Bitmap icon) { TitleEntry(final String name, final String author, final Bitmap icon) {
this.name = name; this.name = name;
this.author = author; this.author = author;
this.icon = icon; this.icon = icon;
@ -32,54 +32,54 @@ final class TitleEntry {
} }
class NroLoader { class NroLoader {
static TitleEntry getTitleEntry(String file) { static TitleEntry getTitleEntry(final String file) {
try { try {
RandomAccessFile f = new RandomAccessFile(file, "r"); final RandomAccessFile f = new RandomAccessFile(file, "r");
f.seek(0x18); // Skip to NroHeader.size f.seek(0x18); // Skip to NroHeader.size
int asetOffset = Integer.reverseBytes(f.readInt()); final int asetOffset = Integer.reverseBytes(f.readInt());
f.seek(asetOffset); // Skip to the offset specified by NroHeader.size f.seek(asetOffset); // Skip to the offset specified by NroHeader.size
byte[] buffer = new byte[4]; final byte[] buffer = new byte[4];
f.read(buffer); f.read(buffer);
if (!(new String(buffer).equals("ASET"))) if (!(new String(buffer).equals("ASET")))
throw new IOException(); throw new IOException();
f.skipBytes(0x4); f.skipBytes(0x4);
long iconOffset = Long.reverseBytes(f.readLong()); final long iconOffset = Long.reverseBytes(f.readLong());
int iconSize = Integer.reverseBytes(f.readInt()); final int iconSize = Integer.reverseBytes(f.readInt());
if (iconOffset == 0 || iconSize == 0) if (iconOffset == 0 || iconSize == 0)
throw new IOException(); throw new IOException();
f.seek(asetOffset + iconOffset); f.seek(asetOffset + iconOffset);
byte[] iconData = new byte[iconSize]; final byte[] iconData = new byte[iconSize];
f.read(iconData); f.read(iconData);
Bitmap icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize); final Bitmap icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize);
f.seek(asetOffset + 0x18); f.seek(asetOffset + 0x18);
long nacpOffset = Long.reverseBytes(f.readLong()); final long nacpOffset = Long.reverseBytes(f.readLong());
long nacpSize = Long.reverseBytes(f.readLong()); final long nacpSize = Long.reverseBytes(f.readLong());
if (nacpOffset == 0 || nacpSize == 0) if (nacpOffset == 0 || nacpSize == 0)
throw new IOException(); throw new IOException();
f.seek(asetOffset + nacpOffset); f.seek(asetOffset + nacpOffset);
byte[] name = new byte[0x200]; final byte[] name = new byte[0x200];
f.read(name); f.read(name);
byte[] author = new byte[0x100]; final byte[] author = new byte[0x100];
f.read(author); f.read(author);
return new TitleEntry(new String(name).trim(), new String(author).trim(), icon); return new TitleEntry(new String(name).trim(), new String(author).trim(), icon);
} catch (IOException e) { } catch (final IOException e) {
Log.e("app_process64", "Error while loading ASET: " + e.getMessage()); Log.e("app_process64", "Error while loading ASET: " + e.getMessage());
return null; return null;
} }
} }
static boolean verifyFile(String file) { static boolean verifyFile(final String file) {
try { try {
RandomAccessFile f = new RandomAccessFile(file, "r"); final RandomAccessFile f = new RandomAccessFile(file, "r");
f.seek(0x10); // Skip to NroHeader.magic f.seek(0x10); // Skip to NroHeader.magic
byte[] buffer = new byte[4]; final byte[] buffer = new byte[4];
f.read(buffer); f.read(buffer);
if (!(new String(buffer).equals("NRO0"))) if (!(new String(buffer).equals("NRO0")))
return false; return false;
} catch (IOException e) { } catch (final IOException e) {
return false; return false;
} }
return true; return true;

View File

@ -9,15 +9,15 @@ import androidx.preference.PreferenceFragmentCompat;
public class SettingsActivity extends AppCompatActivity { public class SettingsActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity); setContentView(R.layout.settings_activity);
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
.replace(R.id.settings, new HeaderFragment()) .replace(R.id.settings, new SettingsActivity.HeaderFragment())
.commit(); .commit();
setSupportActionBar(findViewById(R.id.toolbar)); setSupportActionBar(findViewById(R.id.toolbar));
ActionBar actionBar = getSupportActionBar(); final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
} }
@ -25,7 +25,7 @@ public class SettingsActivity extends AppCompatActivity {
public static class HeaderFragment extends PreferenceFragmentCompat { public static class HeaderFragment extends PreferenceFragmentCompat {
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey); setPreferencesFromResource(R.xml.preferences, rootKey);
} }
} }

View File

@ -1,56 +1,114 @@
<vector android:height="200dp" android:viewportHeight="248" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="248" android:width="200dp" xmlns:aapt="http://schemas.android.com/aapt"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="200dp"
android:height="200dp"
android:viewportWidth="248"
android:viewportHeight="248">
<path android:pathData="M124,124m-124,0a124,124 0,1 1,248 0a124,124 0,1 1,-248 0"> <path android:pathData="M124,124m-124,0a124,124 0,1 1,248 0a124,124 0,1 1,-248 0">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="124" android:endY="-9.094947E-13" <gradient
android:startX="124" android:startY="248" android:type="linear"> android:endX="124"
<item android:color="#FF9E005D" android:offset="0"/> android:endY="-9.094947E-13"
<item android:color="#FF82045E" android:offset="0.1147"/> android:startX="124"
<item android:color="#FF5D0A60" android:offset="0.2951"/> android:startY="248"
<item android:color="#FF400E62" android:offset="0.4757"/> android:type="linear">
<item android:color="#FF2C1163" android:offset="0.6544"/> <item
<item android:color="#FF1F1364" android:offset="0.8303"/> android:color="#FF9E005D"
<item android:color="#FF1B1464" android:offset="1"/> android:offset="0" />
<item
android:color="#FF82045E"
android:offset="0.1147" />
<item
android:color="#FF5D0A60"
android:offset="0.2951" />
<item
android:color="#FF400E62"
android:offset="0.4757" />
<item
android:color="#FF2C1163"
android:offset="0.6544" />
<item
android:color="#FF1F1364"
android:offset="0.8303" />
<item
android:color="#FF1B1464"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:fillColor="#FFFFFF" android:pathData="M88.9,61.9l0.8,1.5c0.2,0.4 0.5,0.7 0.9,0.9l1.5,0.8c1.5,0.8 1.5,3 0,3.9l-1.5,0.8c-0.4,0.2 -0.7,0.5 -0.9,0.9l-0.8,1.5c-0.8,1.5 -3,1.5 -3.9,0l-0.8,-1.5c-0.2,-0.4 -0.5,-0.7 -0.9,-0.9l-1.5,-0.8c-1.5,-0.8 -1.5,-3 0,-3.9l1.5,-0.8c0.4,-0.2 0.7,-0.5 0.9,-0.9l0.8,-1.5C85.9,60.4 88.1,60.4 88.9,61.9z"/> <path
android:fillColor="#FFFFFF"
android:pathData="M88.9,61.9l0.8,1.5c0.2,0.4 0.5,0.7 0.9,0.9l1.5,0.8c1.5,0.8 1.5,3 0,3.9l-1.5,0.8c-0.4,0.2 -0.7,0.5 -0.9,0.9l-0.8,1.5c-0.8,1.5 -3,1.5 -3.9,0l-0.8,-1.5c-0.2,-0.4 -0.5,-0.7 -0.9,-0.9l-1.5,-0.8c-1.5,-0.8 -1.5,-3 0,-3.9l1.5,-0.8c0.4,-0.2 0.7,-0.5 0.9,-0.9l0.8,-1.5C85.9,60.4 88.1,60.4 88.9,61.9z" />
<path android:pathData="M41.7,78.1v84.4h0c-15.5,0 -28.1,-12.6 -28.1,-28.1v-28.1C13.6,90.7 26.2,78.1 41.7,78.1L41.7,78.1z"> <path android:pathData="M41.7,78.1v84.4h0c-15.5,0 -28.1,-12.6 -28.1,-28.1v-28.1C13.6,90.7 26.2,78.1 41.7,78.1L41.7,78.1z">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="59.927605" android:endY="144.2635" <gradient
android:startX="11.915204" android:startY="96.2512" android:type="linear"> android:endX="59.927605"
<item android:color="#FF9E005D" android:offset="0"/> android:endY="144.2635"
<item android:color="#00FFFFFF" android:offset="1"/> android:startX="11.915204"
android:startY="96.2512"
android:type="linear">
<item
android:color="#FF9E005D"
android:offset="0" />
<item
android:color="#00FFFFFF"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:pathData="M206.3,86.6v84.4h0c15.5,0 28.1,-12.6 28.1,-28.1v-28.1C234.4,99.1 221.8,86.6 206.3,86.6L206.3,86.6z"> <path android:pathData="M206.3,86.6v84.4h0c15.5,0 28.1,-12.6 28.1,-28.1v-28.1C234.4,99.1 221.8,86.6 206.3,86.6L206.3,86.6z">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="236.0847" android:endY="104.7364" <gradient
android:startX="188.07231" android:startY="152.7488" android:type="linear"> android:endX="236.0847"
<item android:color="#FF23F6FF" android:offset="0"/> android:endY="104.7364"
<item android:color="#00FFFFFF" android:offset="1"/> android:startX="188.07231"
android:startY="152.7488"
android:type="linear">
<item
android:color="#FF23F6FF"
android:offset="0" />
<item
android:color="#00FFFFFF"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:pathData="M49.2,95.6v68.2c0,4 3.2,7.2 7.2,7.2h135.7c4,0 7.2,-3.2 7.2,-7.2V95.6c0,-4 -3.2,-7.2 -7.2,-7.2H56.4C52.5,88.4 49.2,91.6 49.2,95.6z"> <path android:pathData="M49.2,95.6v68.2c0,4 3.2,7.2 7.2,7.2h135.7c4,0 7.2,-3.2 7.2,-7.2V95.6c0,-4 -3.2,-7.2 -7.2,-7.2H56.4C52.5,88.4 49.2,91.6 49.2,95.6z">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient android:endX="180.2715" android:endY="185.7053" <gradient
android:startX="68.22111" android:startY="73.6549" android:type="linear"> android:endX="180.2715"
<item android:color="#FF9E005D" android:offset="0"/> android:endY="185.7053"
<item android:color="#00FFFFFF" android:offset="1"/> android:startX="68.22111"
android:startY="73.6549"
android:type="linear">
<item
android:color="#FF9E005D"
android:offset="0" />
<item
android:color="#00FFFFFF"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path android:fillColor="#00000000" <path
android:pathData="M95,59c44.7,-31.9 88.7,-38 107,-20c3.7,3.7 8.7,10.6 10,24" android:fillColor="#00000000"
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="5"> android:pathData="M95,59c44.7,-31.9 88.7,-38 107,-20c3.7,3.7 8.7,10.6 10,24"
android:strokeWidth="5"
android:strokeLineCap="round"
android:strokeLineJoin="round">
<aapt:attr name="android:strokeColor"> <aapt:attr name="android:strokeColor">
<gradient android:endX="214.5" android:endY="46.0098" <gradient
android:startX="92.5" android:startY="46.0098" android:type="linear"> android:endX="214.5"
<item android:color="#FFFFFFFF" android:offset="0"/> android:endY="46.0098"
<item android:color="#00FFFFFF" android:offset="1"/> android:startX="92.5"
android:startY="46.0098"
android:type="linear">
<item
android:color="#FFFFFFFF"
android:offset="0" />
<item
android:color="#00FFFFFF"
android:offset="1" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>

View File

@ -12,6 +12,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:layout_marginEnd="5dp"
android:textAppearance="?android:attr/textAppearanceListItem" android:textAppearance="?android:attr/textAppearanceListItem"
android:textSize="15sp" /> android:textSize="15sp" />

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:padding="6dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -21,7 +21,10 @@
<ListView <ListView
android:id="@+id/game_list" android:id="@+id/game_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" /> app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton

View File

@ -12,6 +12,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:layout_marginEnd="5dp"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:textSize="13sp" /> android:textSize="13sp" />

View File

@ -26,6 +26,9 @@
<string name="search_location">Search Location</string> <string name="search_location">Search Location</string>
<string name="logging">Logging</string> <string name="logging">Logging</string>
<string name="log_level">Log Level</string> <string name="log_level">Log Level</string>
<string name="log_compact">Compact Logs</string>
<string name="log_compact_desc_on">Logs will be displayed in a compact form factor</string>
<string name="log_compact_desc_off">The logs will be displayed in a verbose form factor</string>
<string name="localization">Localization</string> <string name="localization">Localization</string>
<string name="localization_language">Language</string> <string name="localization_language">Language</string>
<string name="system">System</string> <string name="system">System</string>

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<full-backup-content> <full-backup-content>
<include domain="sharedpref" path="."/> <include
domain="sharedpref"
path="." />
</full-backup-content> </full-backup-content>

View File

@ -35,16 +35,22 @@
app:key="log_level" app:key="log_level"
app:title="@string/log_level" app:title="@string/log_level"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:defaultValue="false"
android:summaryOff="@string/log_compact_desc_off"
android:summaryOn="@string/log_compact_desc_on"
app:key="log_compact"
app:title="@string/log_compact" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="category_system" android:key="category_system"
android:title="@string/system"> android:title="@string/system">
<CheckBoxPreference <CheckBoxPreference
app:key="operation_mode"
app:title="@string/use_docked"
android:defaultValue="true" android:defaultValue="true"
android:summaryOff="@string/handheld_enabled"
android:summaryOn="@string/docked_enabled" android:summaryOn="@string/docked_enabled"
android:summaryOff="@string/handheld_enabled"/> app:key="operation_mode"
app:title="@string/use_docked" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="category_localization" android:key="category_localization"