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="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<option name="SOFT_MARGINS" value="80,140" />
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<Objective-C>
<option name="INDENT_VISIBILITY_KEYWORDS" value="2" />
<option name="INDENT_PREPROCESSOR_DIRECTIVE" value="4" />
@ -48,6 +51,7 @@
<pair source="cpp" 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="NONE" />
</extensions>
</Objective-C-extensions>
<XML>

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>
<a href="https://discord.gg/XnbXNQM" target="_blank">
<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)
project(Skyline VERSION 0.3 LANGUAGES CXX)
project(Skyline VERSION 0.3)
set(BUILD_TESTING OFF)
set(CMAKE_CXX_STANDARD 17)
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")
if (uppercase_CMAKE_BUILD_TYPE STREQUAL "RELEASE")
add_compile_definitions(NDEBUG)
endif()
add_subdirectory("libraries/tinyxml2")
add_subdirectory("libraries/fmt")
set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
include_directories(${source_DIR}/skyline)
add_library(skyline SHARED
${source_DIR}/main.cpp
${source_DIR}/skyline/common.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/loader/nro.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/KTransferMemory.cpp
${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp
${source_DIR}/skyline/kernel/services/serviceman.cpp
${source_DIR}/skyline/kernel/services/sm/sm.cpp
${source_DIR}/skyline/kernel/services/fatal/fatal.cpp
${source_DIR}/skyline/kernel/services/set/sys.cpp
${source_DIR}/skyline/kernel/services/apm/apm.cpp
${source_DIR}/skyline/kernel/services/am/appletOE.cpp
${source_DIR}/skyline/kernel/services/hid/hid.cpp
${source_DIR}/skyline/kernel/services/time/time.cpp
${source_DIR}/skyline/kernel/services/fs/fs.cpp
${source_DIR}/skyline/services/serviceman.cpp
${source_DIR}/skyline/services/sm/sm.cpp
${source_DIR}/skyline/services/fatal/fatal.cpp
${source_DIR}/skyline/services/set/sys.cpp
${source_DIR}/skyline/services/apm/apm.cpp
${source_DIR}/skyline/services/am/applet.cpp
${source_DIR}/skyline/services/am/appletController.cpp
${source_DIR}/skyline/services/hid/hid.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)

View File

@ -5,7 +5,7 @@ android {
buildToolsVersion '29.0.2'
defaultConfig {
applicationId "skyline.emu"
minSdkVersion 26
minSdkVersion 24
targetSdkVersion 29
versionCode 3
versionName "0.3"
@ -16,6 +16,11 @@ android {
buildTypes {
release {
debuggable true
externalNativeBuild {
cmake {
arguments "-DCMAKE_BUILD_TYPE=RELEASE"
}
}
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@ -33,11 +38,6 @@ android {
path "CMakeLists.txt"
}
}
sourceSets {
main {
jni.srcDirs = ['src/main/cpp/unicorn/lib']
}
}
compileOptions {
sourceCompatibility = 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.INTERNET" />
<uses-feature
android:glEsVersion="0x00030001"
android:required="true" />
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@drawable/logo_skyline"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"
android:fullBackupContent="@xml/backup_descriptor"
android:requestLegacyExternalStorage="true">
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="emu.skyline.LogActivity"
android:label="@string/log"
@ -32,10 +36,21 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.skyline.MainActivity" />
</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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

View File

@ -1,47 +1,115 @@
#include <jni.h>
#include <csignal>
#include <string>
#include <thread>
#include "skyline/common.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 = false;
bool Halt{};
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) {
auto logger = std::make_shared<skyline::Logger>(logPath);
void GameThread(const std::string &prefPath, const std::string &logPath, const std::string &romPath) {
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 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)
auto start = std::chrono::steady_clock::now();
try {
skyline::kernel::OS os(logger, settings);
logger->Write(skyline::Logger::Info, "Launching ROM {}", romPath);
skyline::kernel::OS os(logger, settings, Window);
logger->Info("Launching ROM {}", romPath);
os.Execute(romPath);
logger->Write(skyline::Logger::Info, "Emulation has ended");
logger->Info("Emulation has ended");
} catch (std::exception &e) {
logger->Write(skyline::Logger::Error, e.what());
logger->Error(e.what());
} 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();
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) {
const char *romPath = env->GetStringUTFChars(romPathJni, nullptr);
const char *prefPath = env->GetStringUTFChars(prefPathJni, nullptr);
const char *logPath = env->GetStringUTFChars(logPathJni, nullptr);
if (EmuThread) {
Halt = true; // This'll cause execution to stop after the next breakpoint
EmuThread->join();
Halt = false; // Or the current instance will halt immediately
void UIThread(const std::string &prefPath, const std::string &logPath, const std::string &romPath) {
while (!Queue)
sched_yield();
std::thread gameThread(GameThread, std::string(prefPath), std::string(logPath), std::string(romPath));
AInputEvent *event{};
while (!Halt) {
if (AInputQueue_getEvent(Queue, &event) >= -1) {
if (AKeyEvent_getKeyCode(event) == AKEYCODE_BACK)
Halt = true;
AInputQueue_finishEvent(Queue, event, true);
}
}
Queue = nullptr;
gameThread.join();
Halt = false;
ANativeActivity_finish(Activity);
}
// Running on UI thread is not a good idea as the UI will remain unresponsive
EmuThread = new std::thread(ThreadMain, std::string(romPath, strlen(romPath)), std::string(prefPath, strlen(prefPath)), std::string(logPath, strlen(logPath)));
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 "nce.h"
#include "gpu.h"
#include <tinyxml2.h>
namespace skyline {
@ -10,17 +12,22 @@ namespace skyline {
while (elem) {
switch (elem->Value()[0]) {
case 's':
stringMap.insert(
std::pair<std::string, std::string>(elem->FindAttribute("name")->Value(), elem->GetText()));
stringMap[elem->FindAttribute("name")->Value()] = elem->GetText();
break;
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:
syslog(LOG_ALERT, "Settings type is missing: %s for %s", elem->Value(), elem->FindAttribute("name")->Value());
break;
};
if (elem->NextSibling())
elem = elem->NextSibling()->ToElement();
else break;
else
break;
}
pref.Clear();
}
@ -33,14 +40,18 @@ namespace skyline {
return boolMap.at(key);
}
void Settings::List(std::shared_ptr<Logger> logger) {
for (auto& iter : stringMap)
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));
int Settings::GetInt(const std::string &key) {
return intMap.at(key);
}
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);
WriteHeader("Logging started");
}
@ -55,12 +66,18 @@ namespace skyline {
logFile.flush();
}
void Logger::Write(const LogLevel level, const std::string& str) {
#ifdef NDEBUG
if (level == Debug) return;
#endif
syslog(levelSyslog[level], "%s", str.c_str());
logFile << "1|" << levelStr[level] << "|" << str << "\n";
void Logger::Write(const LogLevel level, std::string str) {
syslog(levelSyslog[static_cast<u8>(level)], "%s", str.c_str());
for (auto &character : str)
if (character == '\n')
character = '\\';
logFile << "1|" << levelStr[static_cast<u8>(level)] << "|" << str << "\n";
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 <stdexcept>
#include <string>
#include <android/native_window.h>
namespace skyline {
// Global typedefs
@ -27,13 +28,13 @@ namespace skyline {
typedef __int32_t i32;
typedef __int16_t i16;
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
namespace constant {
// Memory
constexpr u64 BaseAddr = 0x8000000; //!< The address space base
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 BaseEnd = BaseAddr + BaseSize; //!< The end of the address space
constexpr u64 MapSize = 0x1000000000; //!< The size of the map region
@ -54,17 +55,24 @@ namespace skyline {
// Kernel
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 ThreadSelf = 0xFFFF8000; //!< This is the handle used by threads to refer to themselves
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<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
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 u32 SfcoMagic = 0x4F434653; //!< SFCO in reverse, written to 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
// 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
namespace status {
constexpr u32 Success = 0x0; //!< "Success"
@ -75,6 +83,7 @@ namespace skyline {
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
constexpr u32 Timeout = 0xEA01; //!< "Timeout while svcWaitSynchronization"
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
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 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
@ -198,20 +209,54 @@ namespace skyline {
* @param level The level of the log
* @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
* @param level The level of the log
* @brief Write an error 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>
void Write(Logger::LogLevel level, const S &formatStr, Args &&... args) {
#ifdef NDEBUG
if (level == Debug) return;
#endif
Write(level, fmt::format(formatStr, args...));
inline void Error(const S &formatStr, Args &&... args) {
if (LogLevel::Error <= configLevel) {
Write(LogLevel::Error, 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:
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, int> intMap; //!< A mapping from all keys to their corresponding integer value
public:
/**
@ -243,22 +289,45 @@ namespace skyline {
*/
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.
*/
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
* @return The current time in nanoseconds
*/
inline long long int GetCurrTimeNs() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
inline u64 GetCurrTimeNs() {
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
class NCE;
namespace gpu {
class GPU;
}
namespace kernel {
namespace type {
class KProcess;
@ -271,12 +340,13 @@ namespace skyline {
* @brief This struct is used to hold the state of a device
*/
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
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<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<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++) {
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);
}
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);
}
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);
}
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 = 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) {
domain = reinterpret_cast<DomainHeaderRequest *>(currPtr);
@ -64,29 +81,36 @@ namespace skyline::kernel::ipc {
currPtr += sizeof(PayloadHeader);
cmdArg = currPtr;
cmdArgSz = (header->raw_sz * sizeof(u32)) - (constant::PaddingSum + sizeof(PayloadHeader));
cmdArgSz = (header->raw_sz * sizeof(u32)) - (constant::IpcPaddingSum + sizeof(PayloadHeader));
currPtr += cmdArgSz;
}
if (payload->magic != constant::SfciMagic)
state.logger->Write(Logger::Debug, "Unexpected Magic in PayloadHeader: 0x{:X}", u32(payload->magic));
if (payload->magic != constant::SfciMagic && header->type != static_cast<u16>(CommandType::Control))
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)) {
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)) {
for (uint index = 0; (header->c_flag - 2) > index; index++) { // (c_flag - 2) C descriptors are present
vecBufC.push_back(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));
auto bufC = reinterpret_cast<BufferDescriptorC *>(currPtr);
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);
}
}
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)
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)
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->Write(Logger::Debug, "Data Payload: Command ID: 0x{:X}", u32(payload->value));
state.logger->Debug("Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->input_count, domain->object_id);
state.logger->Debug("Data Payload: Command ID: 0x{:X}", u32(payload->value));
}
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{};
u8 *currPtr = tls.data();
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());
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;
if (isDomain) {
@ -141,8 +165,14 @@ namespace skyline::kernel::ipc {
}
}
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);
}
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);
}
std::vector<u8> Read(const DeviceState &state);
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;
}
@ -160,7 +162,7 @@ namespace skyline::kernel::ipc {
*/
struct BufferDescriptorC {
u64 address : 48;
u16 size : 16;
u32 size : 16;
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
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
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

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 <os.h>
#include <kernel/types/KTransferMemory.h>
namespace skyline::kernel::svc {
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(Xreg::X1, heap.item->address);
state.logger->Write(Logger::Debug, "Heap size was set to 0x{:X}", state.nce->GetRegister(Wreg::W1));
state.nce->SetRegister(Xreg::X1, heap->address);
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) {
memory::MemoryInfo memInf;
u64 addr = state.nce->GetRegister(Xreg::X2);
bool memFree = true;
bool found = false;
for (const auto&[address, region] : state.thisProcess->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
memInf = region->GetInfo();
memFree = false;
memInf = region->GetInfo(addr);
found = true;
break;
}
}
if (memFree) {
if (!found) {
for (const auto &object : state.thisProcess->handleTable) {
if (object.second->objectType == type::KType::KSharedMemory) {
const auto &mem = state.thisProcess->GetHandle<type::KSharedMemory>(object.first);
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 = static_cast<u64>(static_cast<u64>(addr / PAGE_SIZE) * PAGE_SIZE),
.size = static_cast<u64>(-constant::BaseSize + 1),
.type = static_cast<u64>(memory::Type::Unmapped),
.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.nce->SetRegister(Wreg::W0, constant::status::Success);
}
void ExitProcess(DeviceState &state) {
state.os->KillThread(state.thisProcess->mainThread);
}
void CreateThread(DeviceState &state) {
// 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)));
state.nce->SetRegister(Wreg::W0, constant::status::Success);
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) {
@ -57,10 +118,10 @@ namespace skyline::kernel::svc {
case 0:
case 1:
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:
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) {
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);
switch (object->objectType) {
case (type::KType::KThread):
@ -97,6 +158,24 @@ namespace skyline::kernel::svc {
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) {
auto numHandles = state.nce->GetRegister(Wreg::W2);
if (numHandles > constant::MaxSyncHandles) {
@ -105,7 +184,9 @@ namespace skyline::kernel::svc {
}
std::vector<handle_t> waitHandles(numHandles);
state.thisProcess->ReadMemory(waitHandles.data(), state.nce->GetRegister(Xreg::X1), numHandles * sizeof(handle_t));
std::string handleStr;
for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle);
auto object = state.thisProcess->handleTable.at(handle);
switch (object->objectType) {
case type::KType::KProcess:
@ -119,14 +200,33 @@ namespace skyline::kernel::svc {
}
auto syncObject = std::static_pointer_cast<type::KSyncObject>(object);
if (syncObject->signalled) {
state.logger->Debug("Found signalled handle: 0x{:X}", handle);
state.nce->SetRegister(Wreg::W0, constant::status::Success);
state.nce->SetRegister(Wreg::W1, handle);
return;
}
state.thisThread->waitObjects.push_back(syncObject);
syncObject->waitThreads.push_back(state.thisThread->pid);
syncObject->waitThreads.emplace_back(state.thisThread->pid, handle);
}
state.logger->Debug("Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, state.nce->GetRegister(Xreg::X3));
if (state.nce->GetRegister(Xreg::X3) != std::numeric_limits<u64>::max())
state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3);
state.thisThread->status = type::KThread::ThreadStatus::WaitSync;
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) {
@ -156,7 +256,7 @@ namespace skyline::kernel::svc {
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.nce->SetRegister(Wreg::W0, constant::status::Success);
}
@ -170,7 +270,7 @@ namespace skyline::kernel::svc {
auto &cvarVec = state.thisProcess->condVarMap[address];
count = std::min(count, static_cast<u32>(cvarVec.size()));
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);
if (cvarVec.empty())
state.thisProcess->condVarMap.erase(address);
@ -182,7 +282,7 @@ namespace skyline::kernel::svc {
if (std::strcmp(port, "sm:") == 0)
state.nce->SetRegister(Wreg::W1, state.os->serviceManager.NewSession(service::Service::sm));
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);
}
@ -191,18 +291,26 @@ namespace skyline::kernel::svc {
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) {
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));
std::string::size_type pos = 0;
while ((pos = debug.find("\r\n", pos)) != std::string::npos)
debug.erase(pos, 2);
state.logger->Write(Logger::Info, "Debug Output: {}", debug);
state.nce->SetRegister(Wreg::W0, 0);
state.os->thisProcess->ReadMemory(debug.data(), state.nce->GetRegister(Xreg::X0), state.nce->GetRegister(Xreg::X1));
state.logger->Info("Debug Output: {}", debug);
state.nce->SetRegister(Wreg::W0, constant::status::Success);
}
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)) {
case constant::infoState::AllowedCpuIdBitmask:
case constant::infoState::AllowedThreadPriorityMask:
@ -257,14 +365,10 @@ namespace skyline::kernel::svc {
state.nce->SetRegister(Xreg::X1, state.thisProcess->tlsPages[0]->Get(0));
break;
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);
return;
}
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);
/**
* @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);
@ -91,11 +96,26 @@ namespace skyline {
*/
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)
*/
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
*/
@ -126,6 +146,11 @@ namespace skyline {
*/
void SendSyncRequest(DeviceState &state);
/**
* @brief Retrieves the PID of a specific thread
*/
void GetThreadId(DeviceState &state);
/**
* @brief Outputs a debug string
*/
@ -143,7 +168,7 @@ namespace skyline {
nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01
nullptr, // 0x02
nullptr, // 0x03
SetMemoryAttribute, // 0x03
nullptr, // 0x04
nullptr, // 0x05
QueryMemory, // 0x06
@ -161,23 +186,23 @@ namespace skyline {
nullptr, // 0x12
MapSharedMemory, // 0x13
nullptr, // 0x14
nullptr, // 0x15
CreateTransferMemory, // 0x15
CloseHandle, // 0x16
nullptr, // 0x17
ResetSignal, // 0x17
WaitSynchronization, // 0x18
nullptr, // 0x19
ArbitrateLock, // 0x1a
ArbitrateUnlock, // 0x1b
WaitProcessWideKeyAtomic, // 0x1c
SignalProcessWideKey, // 0x1d
nullptr, // 0x1e
GetSystemTick, // 0x1e
ConnectToNamedPort, // 0x1f
nullptr, // 0x20
SendSyncRequest, // 0x21
nullptr, // 0x22
nullptr, // 0x23
nullptr, // 0x24
nullptr, // 0x25
GetThreadId, // 0x25
nullptr, // 0x26
OutputDebugString, // 0x27
nullptr, // 0x28

View File

@ -17,9 +17,11 @@ namespace skyline::kernel::type {
* @brief Signals all threads waiting on this object
*/
virtual inline void Signal() {
if (!signalled) {
KSyncObject::Signal();
signalled = true;
}
}
/**
* @brief Resets the KEvent to an unsignalled state

View File

@ -24,41 +24,48 @@ namespace skyline::kernel::type {
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));
}
void KPrivateMemory::Resize(size_t newSize) {
u64 KPrivateMemory::Resize(size_t newSize, bool canMove) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = newSize;
fregs.regs[3] = static_cast<u64>(PROT_READ | PROT_WRITE);
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, owner);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping private region in child process");
address = fregs.regs[0];
size = newSize;
return address;
}
u64 UpdatePermissionPrivateFunc(u64 address, size_t size, u64 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};
fregs.regs[0] = address;
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);
if (static_cast<int>(fregs.regs[0]) == -1)
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{};
info.baseAddress = address;
info.size = size;
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.isDeviceShared = (info.deviceRefCount > 0);
info.perms = permission;
@ -77,6 +84,7 @@ namespace skyline::kernel::type {
fregs.regs[0] = address;
fregs.regs[1] = size;
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
memory::Permission permission; //!< The permissions for the allocated memory
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 pid The PID of the main
* @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
* @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
* @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
* @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
*/
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();
};

View File

@ -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) {
state.nce->WaitRdy(pid);
threadMap[pid] = NewHandle<KThread>(pid, entryPoint, 0, stackBase + stackSize, GetTlsSlot(), constant::DefaultPriority, this).item;
MapPrivateRegion(0, constant::DefHeapSize, {true, true, true}, memory::Type::Heap, memory::Region::Heap);
threadMap[pid] = NewHandle<KThread>(pid, entryPoint, 0x0, stackBase + stackSize, GetTlsSlot(), constant::DefaultPriority, this).item;
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)
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() {
@ -70,9 +70,9 @@ namespace skyline::kernel::type {
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, mainThread);
auto pid = static_cast<pid_t>(fregs.regs[0]);
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;
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];
}
@ -95,6 +95,14 @@ namespace skyline::kernel::type {
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 sharedSize = 0;
for (auto &region : memoryRegionMap)
@ -106,7 +114,7 @@ namespace skyline::kernel::type {
auto mtxVec = state.thisProcess->mutexMap[address];
u32 mtxVal = state.thisProcess->ReadMemory<u32>(address);
if (mtxVec.empty()) {
mtxVal = (mtxVal & ~constant::mtxOwnerMask) | state.thisThread->handle;
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | state.thisThread->handle;
state.thisProcess->WriteMemory(mtxVal, address);
} else {
for (auto thread = mtxVec.begin();; thread++) {
@ -118,25 +126,29 @@ namespace skyline::kernel::type {
break;
}
}
state.thisThread->status = KThread::ThreadStatus::WaitMutex;
state.thisThread->status = KThread::Status::WaitMutex;
}
}
void KProcess::MutexUnlock(u64 address) {
auto mtxVec = state.thisProcess->mutexMap[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");
if (mtxVec.empty()) {
mtxVal = 0;
} else {
auto &thread = mtxVec.front();
mtxVal = (mtxVal & ~constant::mtxOwnerMask) | thread->handle;
thread->status = KThread::ThreadStatus::Runnable;
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | thread->handle;
thread->status = KThread::Status::Runnable;
mtxVec.erase(mtxVec.begin());
if (!mtxVec.empty())
mtxVal |= (~constant::mtxOwnerMask);
mtxVal |= (~constant::MtxOwnerMask);
}
state.thisProcess->WriteMemory(mtxVal, address);
}
void KProcess::ResetSignal() {
signalled = false;
}
}

View File

@ -2,8 +2,10 @@
#include "KThread.h"
#include "KPrivateMemory.h"
#include "KTransferMemory.h"
#include "KSharedMemory.h"
#include "KSession.h"
#include "KEvent.h"
namespace skyline::kernel::type {
/**
@ -54,17 +56,16 @@ namespace skyline::kernel::type {
*/
u64 GetTlsSlot();
int memFd; //!< The file descriptor to the memory of the process
public:
enum class ProcessStatus {
enum class Status {
Created, //!< The process was created but the main thread has not started yet
Started, //!< The process has been started
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
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)
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<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
@ -79,8 +80,8 @@ namespace skyline::kernel::type {
*/
template<typename objectClass>
struct HandleOut {
std::shared_ptr<objectClass> item;
handle_t handle;
std::shared_ptr<objectClass> item; //!< A shared pointer to the object
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
*/
template<typename Type>
Type ReadMemory(u64 address) const {
inline Type ReadMemory(u64 address) const {
Type item{};
ReadMemory(&item, address, sizeof(Type));
return item;
@ -128,7 +129,7 @@ namespace skyline::kernel::type {
* @param address The address of the object
*/
template<typename Type>
void WriteMemory(Type &item, u64 address) const {
inline void WriteMemory(Type &item, u64 address) const {
WriteMemory(&item, address, sizeof(Type));
}
@ -165,6 +166,13 @@ namespace skyline::kernel::type {
*/
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
*/
@ -193,7 +201,7 @@ namespace skyline::kernel::type {
* @return The handle of the corresponding item in the handle table
*/
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);
return handleIndex++;
}
@ -213,10 +221,14 @@ namespace skyline::kernel::type {
objectType = KType::KProcess;
else if constexpr(std::is_same<objectClass, KSharedMemory>())
objectType = KType::KSharedMemory;
else if constexpr(std::is_same<objectClass, KTransferMemory>())
objectType = KType::KTransferMemory;
else if constexpr(std::is_same<objectClass, KPrivateMemory>())
objectType = KType::KPrivateMemory;
else if constexpr(std::is_same<objectClass, KSession>())
objectType = KType::KSession;
else if constexpr(std::is_same<objectClass, KEvent>())
objectType = KType::KEvent;
else
throw exception("KProcess::GetHandle couldn't determine object type");
try {
@ -224,9 +236,9 @@ namespace skyline::kernel::type {
if (item->objectType == objectType)
return std::static_pointer_cast<objectClass>(item);
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) {
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
*/
void MutexUnlock(u64 address);
/**
* @brief This resets the object to an unsignalled state
*/
void ResetSignal();
};
}

View File

@ -1,7 +1,7 @@
#pragma once
#include <common.h>
#include <kernel/services/base_service.h>
#include <services/base_service.h>
#include "KSyncObject.h"
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
handle_t handleIndex = constant::BaseVirtualHandleIndex;
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
/**

View File

@ -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) {
fd = open(ASHMEM_NAME_DEF, O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
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)
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));
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) {
@ -49,7 +49,8 @@ namespace skyline::kernel::type {
fregs.regs[0] = procInf.address;
fregs.regs[1] = procInf.size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process);
} catch (const std::exception&) {}
} catch (const std::exception &) {
}
}
UnmapSharedFunc(kaddress, ksize);
close(fd);
@ -71,7 +72,7 @@ namespace skyline::kernel::type {
procInf.size = newSize;
}
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;
}
@ -93,7 +94,7 @@ namespace skyline::kernel::type {
}
if ((local && owner == 0) || (!local && owner != 0))
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)
localPermission = newPerms;
else
@ -102,8 +103,9 @@ namespace skyline::kernel::type {
memory::MemoryInfo KSharedMemory::GetInfo(pid_t process) {
memory::MemoryInfo info{};
info.baseAddress = kaddress;
info.size = ksize;
const auto &procInf = procInfMap.at(process);
info.baseAddress = procInf.address;
info.size = procInf.size;
info.type = static_cast<u64>(type);
info.memoryAttribute.isIpcLocked = (info.ipcRefCount > 0);
info.memoryAttribute.isDeviceShared = (info.deviceRefCount > 0);

View File

@ -10,13 +10,16 @@ namespace skyline::kernel::type {
class KSharedMemory : public KObject {
private:
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;
size_t size;
};
std::unordered_map<pid_t, ProcInf> procInfMap; //!< Maps from a PID to where the memory was mapped to
public:
std::unordered_map<pid_t, ProcessInfo> procInfMap; //!< Maps from a PID to where the memory was mapped to
pid_t owner; //!< The PID of the process owning this shared memory
u64 kaddress; //!< The address of the allocated memory for the kernel
size_t ksize; //!< The size of the allocated memory
@ -66,7 +69,7 @@ namespace skyline::kernel::type {
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();
};

View File

@ -4,9 +4,12 @@
namespace skyline::kernel::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() {
for(const auto& pid : waitThreads) {
state.os->processMap.at(pid)->threadMap.at(pid)->status = KThread::ThreadStatus::Runnable;
for (const auto &info : waitThreads) {
state.nce->SetRegister(Wreg::W1, info.handle);
state.os->processMap.at(info.process)->threadMap.at(info.process)->status = KThread::Status::Runnable;
}
}
}

View File

@ -9,8 +9,17 @@ namespace skyline::kernel::type {
*/
class KSyncObject : public KObject {
public:
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
bool signalled{false}; //!< If the current object is signalled (Used by KEvent as it stays signalled till svcClearEvent or svcClearSignal is called)
/**
* @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

View File

@ -14,8 +14,8 @@ namespace skyline::kernel::type {
void KThread::Start() {
if (pid == parent->mainThread)
parent->status = KProcess::ProcessStatus::Started;
status = ThreadStatus::Running;
parent->status = KProcess::Status::Started;
status = Status::Running;
state.nce->StartProcess(entryPoint, entryArg, stackTop, handle, pid);
}
@ -23,6 +23,18 @@ namespace skyline::kernel::type {
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)
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
public:
enum class ThreadStatus {
enum class Status {
Created, //!< The thread has been created but has not been started yet
Running, //!< The thread is running currently
Sleeping, //!< The thread is sleeping due to svcSleepThread
@ -21,7 +21,7 @@ namespace skyline::kernel::type {
WaitMutex, //!< The thread is waiting on a Mutex
WaitCondVar, //!< The thread is waiting on a Conditional Variable
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
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
@ -49,10 +49,20 @@ namespace skyline::kernel::type {
~KThread();
/**
* @brief Starts the current thread
* @brief This starts this thread
*/
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.
* @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)
throw exception("An error occurred while mapping transfer memory in kernel");
}
size_t copySz = std::min(size, cSize);
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);
state.os->processMap.at(process)->ReadMemory(tempBuf.data(), cAddress, copySz);
state.os->processMap.at(owner)->WriteMemory(tempBuf.data(), address, copySz);
@ -62,7 +59,6 @@ namespace skyline::kernel::type {
} else {
throw exception("Transferring from kernel to kernel is not supported");
}
if (owner) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
@ -75,7 +71,6 @@ namespace skyline::kernel::type {
if (reinterpret_cast<void *>(UnmapTransferFunc(address, size)) == MAP_FAILED)
throw exception("An error occurred while unmapping transfer memory in kernel");
}
owner = process;
cAddress = address;
cSize = size;

View File

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

View File

@ -1,6 +1,5 @@
#pragma once
#include <string>
#include <os.h>
#include <kernel/types/KProcess.h>
@ -23,6 +22,31 @@ namespace skyline::loader {
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:
/**

View File

@ -5,22 +5,10 @@ namespace skyline::loader {
NroLoader::NroLoader(std::string filePath) : Loader(filePath) {
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
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) {
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> rodata(header.ro.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(data.data(), header.data.offset, header.data.size);
// Replace SVC & MRS with BRK
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);
PatchCode(text);
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);
}
}
}
u64 textSize = text.size();
u64 rodataSize = rodata.size();
u64 dataSize = data.size();
process->WriteMemory(text.data(), constant::BaseAddr, header.text.size);
process->WriteMemory(rodata.data(), constant::BaseAddr + header.text.size, header.ro.size);
process->WriteMemory(data.data(), constant::BaseAddr + header.text.size + header.ro.size, header.data.size);
process->MapPrivateRegion(constant::BaseAddr, textSize, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // R-X
state.logger->Debug("Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr, textSize);
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,38 +7,38 @@ namespace skyline::loader {
class NroLoader : public Loader {
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 {
u32 offset;
u32 size;
u32 offset; //!< The offset of the region
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 {
u32 : 32;
u32 mod_offset;
u32 modOffset; //!< The offset of the MOD metadata
u64 : 64;
u32 magic;
u32 version;
u32 size;
u32 flags;
u32 magic; //!< The NRO magic "NRO0"
u32 version; //!< The version of the application
u32 size; //!< The size of the NRO
u32 flags; //!< The flags used with the NRO
NroSegmentHeader text;
NroSegmentHeader ro;
NroSegmentHeader data;
NroSegmentHeader text; //!< The .text segment header
NroSegmentHeader ro; //!< The .ro segment header
NroSegmentHeader data; //!< The .data segment header
u32 bssSize;
u32 bssSize; //!< The size of the bss segment
u32 : 32;
u64 build_id[4];
u64 buildId[4]; //!< The build ID of the NRO
u64 : 64;
NroSegmentHeader api_info;
NroSegmentHeader dynstr;
NroSegmentHeader dynsym;
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
NroSegmentHeader dynstr; //!< The .dynstr segment header
NroSegmentHeader dynsym; //!< The .dynsym segment header
} header{};
public:

View File

@ -8,7 +8,7 @@ namespace skyline::memory {
*/
struct Permission {
/**
* @brief Constructor that initializes all permissions to false
* @brief This constructor initializes all permissions to false
*/
Permission() {
r = 0;
@ -42,9 +42,12 @@ namespace skyline::memory {
*/
int Get() const {
int perm = 0;
if (r) perm |= PROT_READ;
if (w) perm |= PROT_WRITE;
if (x) perm |= PROT_EXEC;
if (r)
perm |= PROT_READ;
if (w)
perm |= PROT_WRITE;
if (x)
perm |= PROT_EXEC;
return perm;
};
@ -61,6 +64,15 @@ namespace skyline::memory {
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
*/

View File

@ -10,84 +10,86 @@ namespace skyline {
iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
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 {
iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_SETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
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 {
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : currPid, address, NULL);
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));
}
void NCE::Initialize(const DeviceState &state) {
this->state = &state;
}
NCE::NCE(const DeviceState &state) : state(state) {}
void NCE::Execute() {
int status = 0;
while (!Halt && !state->os->processMap.empty()) {
for (const auto &process : state->os->processMap) { // NOLINT(performance-for-range-copy)
state->os->thisProcess = process.second;
state->os->thisThread = process.second->threadMap.at(process.first);
while (!Halt && !state.os->processMap.empty()) {
for (const auto &process : state.os->processMap) { // NOLINT(performance-for-range-copy)
state.os->thisProcess = process.second;
state.os->thisThread = process.second->threadMap.at(process.first);
currPid = process.first;
auto &currRegs = registerMap[currPid];
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Running) {
if (waitpid(state->thisThread->pid, &status, WNOHANG) == state->thisThread->pid) {
if (state.thisThread->status == kernel::type::KThread::Status::Running) {
if (waitpid(state.thisThread->pid, &status, WNOHANG) == state.thisThread->pid) {
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
ReadRegisters(currRegs);
auto instr = ReadBrk(currRegs.pc);
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.
if (instr.value <= constant::SvcLast) {
state->os->SvcHandler(static_cast<u16>(instr.value));
if (state->thisThread->status != kernel::type::KThread::ThreadStatus::Running)
state.os->SvcHandler(static_cast<u16>(instr.value));
if (state.thisThread->status != kernel::type::KThread::Status::Running)
continue;
} else if (instr.value > constant::SvcLast && instr.value <= constant::SvcLast + constant::NumRegs) {
// 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)
continue;
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);
WriteRegisters(currRegs);
ResumeProcess();
} else {
try {
ReadRegisters(currRegs);
u32 instr = static_cast<u32>(ptrace(PTRACE_PEEKDATA, currPid, currRegs.pc, NULL));
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)
state.logger->Warn("Thread threw unknown signal, PID: {}, Stop Signal: {}", currPid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
ProcessTrace();
} 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) {
if (state->thisThread->timeout <= GetCurrTimeNs()) {
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitSync || 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()) {
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);
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) {
state->thisThread->waitObjects.clear();
state->thisThread->status = kernel::type::KThread::ThreadStatus::Running;
if (state.thisThread->status == kernel::type::KThread::Status::Runnable) {
state.thisThread->waitObjects.clear();
state.thisThread->status = kernel::type::KThread::Status::Running;
currRegs.pc += sizeof(u32);
WriteRegisters(currRegs);
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);
return regs;
} 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
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 {
@ -134,10 +136,10 @@ namespace skyline {
waitpid(pid, &status, WNOHANG);
bool wasStopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise)
if (wasStopped) {
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, 0) != -1))
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, WNOHANG) != -1))
return true;
else
throw exception(fmt::format("Cannot pause process: {}, Error: {}", pid, strerror(errno)));
throw exception("Cannot pause process: {}, Error: {}", pid, strerror(errno));
} else
return false;
}
@ -145,7 +147,7 @@ namespace skyline {
void NCE::ResumeProcess(pid_t pid) const {
long status = ptrace(PTRACE_CONT, pid ? pid : currPid, NULL, NULL);
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 {
@ -158,6 +160,30 @@ namespace skyline {
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) {
return registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)];
}

View File

@ -16,7 +16,7 @@ namespace skyline {
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
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
@ -41,11 +41,7 @@ namespace skyline {
instr::Brk ReadBrk(u64 address, pid_t pid = 0) const;
public:
/**
* @brief Initialize NCE by setting the device_state variable
* @param state The state of the device
*/
void Initialize(const DeviceState &state);
NCE(const DeviceState &state);
/**
* @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;
/**
* @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
* @param regId The ID of the register

View File

@ -3,10 +3,9 @@
#include "loader/nro.h"
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) {
state.nce->Initialize(state);
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); });
auto process = CreateProcess(constant::BaseAddr, constant::DefStackSize);
@ -38,25 +37,25 @@ namespace skyline::kernel {
}
pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FILES | CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
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);
processMap[pid] = process;
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;
}
void OS::KillThread(pid_t pid) {
auto process = processMap.at(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
// 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)
processMap.erase(key);
processVec.erase(std::remove(processVec.begin(), processVec.end(), pid), processVec.end());
} 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->threadMap.erase(pid);
processMap.erase(pid);
@ -65,10 +64,10 @@ namespace skyline::kernel {
void OS::SvcHandler(u16 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);
} 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) {

View File

@ -6,8 +6,9 @@
#include "kernel/ipc.h"
#include "kernel/types/KProcess.h"
#include "kernel/types/KThread.h"
#include "kernel/services/serviceman.h"
#include "services/serviceman.h"
#include "nce.h"
#include "gpu.h"
namespace skyline::kernel {
/**
@ -28,7 +29,7 @@ namespace skyline::kernel {
* @param logger An instance of the Logger 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.

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
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
#include <kernel/types/KProcess.h>
#include <kernel/types/KEvent.h>
#include <gpu.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 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
*/
class ICommonStateGetter : public BaseService {
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
OutOfFocus = 2 //!< The application is in the background
};
} focusState{FocusState::InFocus};
enum class OperationMode : u8 {
Handheld = 0, //!< The device is in handheld mode
Docked = 1 //!< The device is in docked mode
} operationMode;
/**
* @brief This queues a message for the application to read via ReceiveMessage
* @param message The message to queue
*/
void QueueMessage(Message message);
public:
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);
/**
* @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)
*/
@ -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 {
public:
ISelfController(const DeviceState &state, ServiceManager &manager);
/**
* @brief This function inputs a u8 bool flag and no output (Stubbed) (https://switchbrew.org/wiki/Applet_Manager_services#SetOperationModeChangedNotification)
* @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);
/**
* @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);
/**
* @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);
/**
* @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 {
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 {
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 {
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 {
public:
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);
};
/**
* @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 {
public:

View File

@ -2,16 +2,16 @@
namespace skyline::kernel::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) {
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, {
{0x0, SFunc(ISession::SetPerformanceConfiguration)},
{0x1, SFunc(ISession::GetPerformanceConfiguration)}
{0x0, SFUNC(ISession::SetPerformanceConfiguration)},
{0x1, SFUNC(ISession::GetPerformanceConfiguration)}
}) {}
void ISession::SetPerformanceConfiguration(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -20,7 +20,7 @@ namespace skyline::kernel::service::apm {
u32 config;
} *performance = reinterpret_cast<InputStruct *>(request.cmdArg);
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) {

View File

@ -1,7 +1,7 @@
#pragma once
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::kernel::service::apm {
/**

View File

@ -4,7 +4,8 @@
#include <kernel/ipc.h>
#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 {
class KSession;
@ -20,7 +21,9 @@ namespace skyline::kernel::service {
apm,
apm_ISession,
am_appletOE,
am_appletAE,
am_IApplicationProxy,
am_ILibraryAppletProxy,
am_ICommonStateGetter,
am_IApplicationFunctions,
am_ISelfController,
@ -36,19 +39,27 @@ namespace skyline::kernel::service {
time_ITimeZoneService,
fs_fsp,
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
*/
const static std::unordered_map<std::string, Service> ServiceString = {
const static std::unordered_map<std::string, Service> ServiceString{
{"sm:", Service::sm},
{"fatal:u", Service::fatal_u},
{"set:sys", Service::set_sys},
{"apm", Service::apm},
{"apm:ISession", Service::apm_ISession},
{"appletOE", Service::am_appletOE},
{"appletAE", Service::am_appletAE},
{"am:IApplicationProxy", Service::am_IApplicationProxy},
{"am:ILibraryAppletProxy", Service::am_ILibraryAppletProxy},
{"am:ICommonStateGetter", Service::am_ICommonStateGetter},
{"am:ISelfController", Service::am_ISelfController},
{"am:IWindowController", Service::am_IWindowController},
@ -65,6 +76,15 @@ namespace skyline::kernel::service {
{"time:ITimeZoneService", Service::time_ITimeZoneService},
{"fsp-srv", Service::fs_fsp},
{"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;
@ -79,7 +99,7 @@ namespace skyline::kernel::service {
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:
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
/**
@ -91,8 +111,13 @@ namespace skyline::kernel::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) {}
/**
* @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 = "";
std::string serviceName;
for (const auto&[name, type] : ServiceString)
if (type == serviceType)
serviceName = name;
@ -109,13 +134,13 @@ namespace skyline::kernel::service {
try {
function = vTable.at(request.payload->value);
} catch (std::out_of_range &) {
state.logger->Write(Logger::Warn, "Cannot find function in service '{0}' (Type: {1}): 0x{2:X} ({2})", getName(), serviceType, u32(request.payload->value));
state.logger->Warn("Cannot find function in service '{0}' (Type: {1}): 0x{2:X} ({2})", getName(), serviceType, u32(request.payload->value));
return;
}
try {
function(session, request, response);
} catch (std::exception &e) {
throw exception(e.what());
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
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::kernel::service::fatal {
/**

View File

@ -2,8 +2,8 @@
namespace skyline::kernel::service::fs {
fsp::fsp(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::fs_fsp, {
{0x1, SFunc(fsp::SetCurrentProcess)},
{0x12, SFunc(fsp::OpenSdCardFileSystem)}
{0x1, SFUNC(fsp::SetCurrentProcess)},
{0x12, SFUNC(fsp::OpenSdCardFileSystem)}
}) {}
void fsp::SetCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {

View File

@ -1,13 +1,13 @@
#pragma once
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::kernel::service::fs {
/**
* @brief These are the possible types of the filesystem
*/
enum FsType {
enum class FsType {
Nand,
SdCard,
GameCard

View File

@ -3,23 +3,25 @@
namespace skyline::kernel::service::hid {
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) {
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, {
{0x0, SFunc(hid::CreateAppletResource)},
{0x64, SFunc(hid::SetSupportedNpadStyleSet)},
{0x66, SFunc(hid::SetSupportedNpadIdType)},
{0x67, SFunc(hid::ActivateNpad)},
{0x78, SFunc(hid::SetNpadJoyHoldType)},
{0x7A, SFunc(hid::SetNpadJoyAssignmentModeSingleByDefault)},
{0x7B, SFunc(hid::SetNpadJoyAssignmentModeSingle)},
{0x7C, SFunc(hid::SetNpadJoyAssignmentModeDual)}
{0x0, SFUNC(hid::CreateAppletResource)},
{0x64, SFUNC(hid::SetSupportedNpadStyleSet)},
{0x66, SFUNC(hid::SetSupportedNpadIdType)},
{0x67, SFUNC(hid::ActivateNpad)},
{0x78, SFUNC(hid::SetNpadJoyHoldType)},
{0x7A, SFUNC(hid::SetNpadJoyAssignmentModeSingleByDefault)},
{0x7B, SFUNC(hid::SetNpadJoyAssignmentModeSingle)},
{0x7C, SFUNC(hid::SetNpadJoyAssignmentModeDual)}
}) {}
void hid::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -33,8 +35,8 @@ namespace skyline::kernel::service::hid {
u64 appletUserId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
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>
(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));
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->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) {

View File

@ -1,7 +1,7 @@
#pragma once
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
#include <kernel/types/KProcess.h>
namespace skyline::constant {
@ -33,15 +33,15 @@ namespace skyline::kernel::service::hid {
* @brief This holds the controller styles supported by an application
*/
struct StyleSet {
bool pro_controller : 1; //!< The Pro Controller
bool joycon_handheld : 1; //!< Joy-Cons in handheld mode
bool joycon_dual : 1; //!< Joy-Cons in a pair
bool joycon_left : 1; //!< Left Joy-Con only
bool joycon_right : 1; //!< Right Joy-Con only
bool proController : 1; //!< The Pro Controller
bool joyconHandheld : 1; //!< Joy-Cons in handheld mode
bool joyconDual : 1; //!< Joy-Cons in a pair
bool joyconLeft : 1; //!< Left Joy-Con only
bool joyconRight : 1; //!< Right Joy-Con only
bool gamecube : 1; //!< GameCube controller
bool pokeball : 1; //!< Poké Ball Plus 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
u32 : 22;
};

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 "set/sys.h"
#include "apm/apm.h"
#include "am/appletOE.h"
#include "am/applet.h"
#include "am/appletController.h"
#include "fatal/fatal.h"
#include "hid/hid.h"
#include "time/timesrv.h"
#include "fs/fs.h"
#include "nvdrv/nvdrv.h"
#include "vi/vi_m.h"
#include "nvnflinger/dispdrv.h"
namespace skyline::kernel::service {
ServiceManager::ServiceManager(const DeviceState &state) : state(state) {}
@ -33,9 +37,15 @@ namespace skyline::kernel::service {
case Service::am_appletOE:
serviceObj = std::make_shared<am::appletOE>(state, *this);
break;
case Service::am_appletAE:
serviceObj = std::make_shared<am::appletAE>(state, *this);
break;
case Service::am_IApplicationProxy:
serviceObj = std::make_shared<am::IApplicationProxy>(state, *this);
break;
case Service::am_ILibraryAppletProxy:
serviceObj = std::make_shared<am::ILibraryAppletProxy>(state, *this);
break;
case Service::am_ICommonStateGetter:
serviceObj = std::make_shared<am::ICommonStateGetter>(state, *this);
break;
@ -72,8 +82,26 @@ namespace skyline::kernel::service {
case Service::fs_fsp:
serviceObj = std::make_shared<fs::fsp>(state, *this);
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:
throw exception("GetService called on missing object");
throw exception("GetService called on missing object, type: {}", serviceType);
}
serviceVec.push_back(serviceObj);
return serviceObj;
@ -83,8 +111,8 @@ namespace skyline::kernel::service {
return state.thisProcess->NewHandle<type::KSession>(GetService(serviceType)).handle;
}
std::shared_ptr<BaseService> ServiceManager::NewService(const Service serviceType, type::KSession &session, ipc::IpcResponse &response) {
auto serviceObject = GetService(serviceType);
std::shared_ptr<BaseService> ServiceManager::NewService(const std::string &serviceName, type::KSession &session, ipc::IpcResponse &response) {
auto serviceObject = GetService(ServiceString.at(serviceName));
handle_t handle{};
if (response.isDomain) {
session.domainTable[++session.handleIndex] = serviceObject;
@ -94,7 +122,7 @@ namespace skyline::kernel::service {
handle = state.thisProcess->NewHandle<type::KSession>(serviceObject).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;
}
@ -102,14 +130,14 @@ namespace skyline::kernel::service {
serviceVec.push_back(serviceObject);
handle_t handle{};
if (response.isDomain) {
session.domainTable[++session.handleIndex] = serviceObject;
session.domainTable[session.handleIndex] = serviceObject;
response.domainObjects.push_back(session.handleIndex);
handle = session.handleIndex;
handle = session.handleIndex++;
} else {
handle = state.thisProcess->NewHandle<type::KSession>(serviceObject).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) {
@ -132,8 +160,8 @@ namespace skyline::kernel::service {
void ServiceManager::SyncRequestHandler(const handle_t handle) {
auto session = state.thisProcess->GetHandle<type::KSession>(handle);
state.logger->Write(Logger::Debug, "----Start----");
state.logger->Write(Logger::Debug, "Handle is 0x{:X}", handle);
state.logger->Debug("----Start----");
state.logger->Debug("Handle is 0x{:X}", handle);
if (session->serviceStatus == type::KSession::ServiceStatus::Open) {
ipc::IpcRequest request(session->isDomain, state);
@ -159,12 +187,13 @@ namespace skyline::kernel::service {
}
} else
session->serviceObject->HandleRequest(*session, request, response);
if (!response.nWrite)
response.WriteTls();
break;
case ipc::CommandType::Control:
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)) {
case ipc::ControlCommand::ConvertCurrentObjectToDomain:
response.WriteValue(session->ConvertDomain());
@ -173,26 +202,29 @@ namespace skyline::kernel::service {
case ipc::ControlCommand::CloneCurrentObjectEx:
CloneSession(*session, request, response);
break;
case ipc::ControlCommand::QueryPointerBufferSize:
response.WriteValue<u32>(0x1000);
break;
default:
throw exception(fmt::format("Unknown Control Command: {}", request.payload->value));
throw exception("Unknown Control Command: {}", request.payload->value);
}
response.WriteTls();
break;
case ipc::CommandType::Close:
state.logger->Write(Logger::Debug, "Closing Session");
state.logger->Debug("Closing Session");
CloseSession(handle);
break;
default:
throw exception(fmt::format("Unimplemented IPC message type: {}", u16(request.header->type)));
throw exception("Unimplemented IPC message type: {}", u16(request.header->type));
}
} else
state.logger->Write(Logger::Warn, "svcSendSyncRequest called on closed handle: 0x{:X}", handle);
state.logger->Write(Logger::Debug, "====End====");
state.logger->Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle);
state.logger->Debug("====End====");
}
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
* @param serviceType The type of the service
* @param serviceName The name of the service
* @param session The session object of the command
* @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
@ -48,7 +48,6 @@ namespace skyline::kernel::service {
*/
void RegisterService(std::shared_ptr<BaseService> serviceObject, type::KSession &session, ipc::IpcResponse &response);
/**
* @brief Closes an existing session to a service
* @param service The handle of the KService object

View File

@ -2,10 +2,10 @@
#include <kernel/types/KProcess.h>
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) {
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);
}
}

View File

@ -1,7 +1,7 @@
#pragma once
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::kernel::service::set {
/**
@ -13,17 +13,17 @@ 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)
*/
struct SysVerTitle {
u8 major;
u8 minor;
u8 micro;
u8 major; //!< The major version
u8 minor; //!< The minor vision
u8 micro; //!< The micro vision
u8 : 8;
u8 rev_major;
u8 rev_minor;
u8 revMajor; //!< The major revision
u8 revMinor; //!< The major revision
u16 : 16;
u8 platform[0x20];
u8 ver_hash[0x40];
u8 disp_ver[0x18];
u8 disp_title[0x80];
u8 platform[0x20]; //!< "NX"
u8 verHash[0x40]; //!< This is the hash of the version string
u8 dispVer[0x18]; //!< The version number string
u8 dispTitle[0x80]; //!< The version title string
};
static_assert(sizeof(SysVerTitle) == 0x100);

View File

@ -2,8 +2,8 @@
namespace skyline::kernel::service::sm {
sm::sm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::sm, {
{0x0, SFunc(sm::Initialize)},
{0x1, SFunc(sm::GetService)}
{0x0, SFUNC(sm::Initialize)},
{0x1, SFUNC(sm::GetService)}
}) {}
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;
else {
try {
manager.NewService(ServiceString.at(serviceName), session, response);
manager.NewService(serviceName, session, response);
} catch (std::out_of_range &) {
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
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::kernel::service::sm {
/**

View File

@ -2,10 +2,10 @@
namespace skyline::kernel::service::time {
time::time(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::time, {
{0x0, SFunc(time::GetStandardUserSystemClock)},
{0x1, SFunc(time::GetStandardNetworkSystemClock)},
{0x3, SFunc(time::GetTimeZoneService)},
{0x4, SFunc(time::GetStandardLocalSystemClock)}
{0x0, SFUNC(time::GetStandardUserSystemClock)},
{0x1, SFUNC(time::GetStandardNetworkSystemClock)},
{0x3, SFUNC(time::GetTimeZoneService)},
{0x4, SFUNC(time::GetStandardLocalSystemClock)}
}) {}
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, {
{0x0, SFunc(ISystemClock::GetCurrentTime)}
{0x0, SFUNC(ISystemClock::GetCurrentTime)}
}) {}
void ISystemClock::GetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -33,7 +33,7 @@ namespace skyline::kernel::service::time {
}
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) {

View File

@ -1,13 +1,13 @@
#pragma once
#include <kernel/services/base_service.h>
#include <kernel/services/serviceman.h>
#include <services/base_service.h>
#include <services/serviceman.h>
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
Network, //!< Use network time
Local, //!< Use local time

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

View File

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

View File

@ -3,7 +3,6 @@ package emu.skyline;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.FileObserver;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@ -38,59 +37,39 @@ import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
import static java.lang.Thread.interrupted;
public class LogActivity extends AppCompatActivity {
private File log_file;
private BufferedReader reader;
private Thread thread;
private LogAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.log_activity);
setSupportActionBar(findViewById(R.id.toolbar));
ActionBar actionBar = getSupportActionBar();
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null)
actionBar.setDisplayHomeAsUpEnabled(true);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
final ListView log_list = this.findViewById(R.id.log_list);
adapter = new LogAdapter(this, Integer.parseInt(prefs.getString("log_level", "3")), getResources().getStringArray(R.array.log_level));
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
ListView log_list = findViewById(R.id.log_list);
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_file = new File(getApplicationInfo().dataDir + "/skyline.log");
try {
InputStream inputStream = new FileInputStream(log_file);
reader = new BufferedReader(new InputStreamReader(inputStream));
thread = new Thread(() -> {
// Required as FileObserver(File) is only on API level 29 also no AndroidX version present
FileObserver observer = new FileObserver(log_file.getPath()) {
@Override
public void onEvent(int event, String path) {
if (event == FileObserver.MODIFY) {
log_file = new File(getApplicationInfo().dataDir + "/skyline.log");
final InputStream inputStream = new FileInputStream(log_file);
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean done = false;
while (!done) {
final String line = reader.readLine();
done = (line == null);
if (!done) {
runOnUiThread(() -> adapter.add(line));
String line = reader.readLine();
if (!(done = (line == null))) {
adapter.add(line);
}
}
} catch (IOException e) {
} catch (final 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());
observer.startWatching();
while (!interrupted()) ;
observer.stopWatching();
});
thread.start();
} catch (FileNotFoundException e) {
} catch (final FileNotFoundException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show();
finish();
@ -98,19 +77,19 @@ public class LogActivity extends AppCompatActivity {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_log, menu);
MenuItem mSearch = menu.findItem(R.id.action_search_log);
final SearchView searchView = (SearchView) mSearch.getActionView();
final MenuItem mSearch = menu.findItem(R.id.action_search_log);
SearchView searchView = (SearchView) mSearch.getActionView();
searchView.setSubmitButtonEnabled(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
public boolean onQueryTextSubmit(String query) {
public boolean onQueryTextSubmit(final String query) {
searchView.setIconified(false);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
public boolean onQueryTextChange(final String newText) {
adapter.getFilter().filter(newText);
return true;
}
@ -119,13 +98,13 @@ public class LogActivity extends AppCompatActivity {
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_clear:
try {
FileWriter fileWriter = new FileWriter(log_file, false);
final FileWriter fileWriter = new FileWriter(log_file, false);
fileWriter.close();
} catch (IOException e) {
} catch (final IOException e) {
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();
}
@ -133,19 +112,19 @@ public class LogActivity extends AppCompatActivity {
finish();
return true;
case R.id.action_share_log:
Thread share_thread = new Thread(() -> {
final Thread share_thread = new Thread(() -> {
HttpsURLConnection urlConnection = null;
try {
URL url = new URL("https://hastebin.com/documents");
final URL url = new URL("https://hastebin.com/documents");
urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Host", "hastebin.com");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
urlConnection.setRequestProperty("Referer", "https://hastebin.com/");
urlConnection.setRequestProperty("Connection", "keep-alive");
OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
FileReader fileReader = new FileReader(log_file);
final OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
final BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
final FileReader fileReader = new FileReader(log_file);
int chr;
while ((chr = fileReader.read()) != -1) {
bufferedWriter.write(chr);
@ -157,15 +136,15 @@ public class LogActivity extends AppCompatActivity {
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.getResponseCode());
throw new Exception();
}
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
final InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
final String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
bufferedReader.close();
inputStream.close();
String result = "https://hastebin.com/" + key;
Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
final String result = "https://hastebin.com/" + key;
final Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
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());
e.printStackTrace();
} finally {
@ -176,7 +155,7 @@ public class LogActivity extends AppCompatActivity {
share_thread.start();
try {
share_thread.join(1000);
} catch (Exception e) {
} catch (final Exception e) {
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
e.printStackTrace();
}
@ -184,18 +163,4 @@ public class LogActivity extends AppCompatActivity {
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 level;
LogItem(String content, String level) {
LogItem(final String content, final String level) {
this.content = content;
this.level = level;
}
@ -38,28 +38,32 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
private final ClipboardManager clipboard;
private final int debug_level;
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);
clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
this.debug_level = debug_level;
this.level_str = level_str;
this.compact = compact;
}
void add(final String log_line) {
String[] log_meta = log_line.split("\\|", 3);
void add(String log_line) {
try {
final String[] log_meta = log_line.split("\\|", 3);
if (log_meta[0].startsWith("1")) {
int level = Integer.parseInt(log_meta[1]);
if (level > this.debug_level) return;
super.add(new LogItem(log_meta[2], level_str[level]), ContentType.Item);
final int level = Integer.parseInt(log_meta[1]);
if (level > debug_level) return;
add(new LogItem(log_meta[2].replace('\\', '\n'), level_str[level]), ContentType.Item);
} else {
super.add(log_meta[1], ContentType.Header);
add(log_meta[1], ContentType.Header);
}
} catch (final IndexOutOfBoundsException ignored) {}
}
@Override
public boolean onLongClick(View view) {
LogItem item = (LogItem) getItem(((ViewHolder) view.getTag()).position);
public boolean onLongClick(final View view) {
final LogItem item = (LogItem) getItem(((LogAdapter.ViewHolder) view.getTag()).position);
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getMessage() + " (" + item.getLevel() + ")"));
Toast.makeText(view.getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show();
return false;
@ -67,31 +71,34 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder viewHolder;
int type = type_array.get(position).type;
public View getView(final int position, View convertView, @NonNull final ViewGroup parent) {
final LogAdapter.ViewHolder viewHolder;
final int type = type_array.get(position).type;
if (convertView == null) {
viewHolder = new LogAdapter.ViewHolder();
final LayoutInflater inflater = LayoutInflater.from(HeaderAdapter.mContext);
if (type == ContentType.Item) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(mContext);
if (compact) {
convertView = inflater.inflate(R.layout.log_item_compact, parent, false);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
} else {
convertView = inflater.inflate(R.layout.log_item, parent, false);
convertView.setOnLongClickListener(this);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle);
convertView.setTag(viewHolder);
}
convertView.setOnLongClickListener(this);
} else {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(mContext);
convertView = inflater.inflate(R.layout.section_item, parent, false);
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
convertView.setTag(viewHolder);
}
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
viewHolder = (LogAdapter.ViewHolder) convertView.getTag();
}
if (type == ContentType.Item) {
LogItem data = (LogItem) getItem(position);
final LogItem data = (LogItem) getItem(position);
viewHolder.txtTitle.setText(data.getMessage());
if (!compact)
viewHolder.txtSub.setText(data.getLevel());
} else {
viewHolder.txtTitle.setText((String) getItem(position));

View File

@ -8,6 +8,7 @@ import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.widget.ListView;
@ -33,30 +34,30 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
System.loadLibrary("skyline");
}
private SharedPreferences sharedPreferences = null;
private GameAdapter adapter = null;
private SharedPreferences sharedPreferences;
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();
}
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)
files = new ArrayList<>();
File[] list = file.listFiles();
final File[] list = file.listFiles();
if (list != null) {
for (File file_i : list) {
for (final File file_i : list) {
if (file_i.isDirectory()) {
files = findFile(ext, file_i, files);
} else {
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 (NroLoader.verifyFile(file_i.getAbsolutePath())) {
files.add(file_i);
}
}
} catch (StringIndexOutOfBoundsException e) {
} catch (final StringIndexOutOfBoundsException e) {
Log.w("findFile", Objects.requireNonNull(e.getMessage()));
}
}
@ -65,72 +66,75 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
return files;
}
private void RefreshFiles(boolean try_load) {
private void RefreshFiles(final boolean try_load) {
if (try_load) {
try {
adapter.load(new File(getApplicationInfo().dataDir + "/roms.bin"));
return;
} catch (Exception e) {
} catch (final Exception e) {
Log.w("refreshFiles", "Ran into exception while loading: " + Objects.requireNonNull(e.getMessage()));
}
}
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()) {
adapter.add(getString(R.string.nro), ContentType.Header);
for (File file : files)
for (final File file : files)
adapter.add(new GameItem(file), ContentType.Item);
} else {
adapter.add(getString(R.string.no_rom), ContentType.Header);
}
try {
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()));
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(MainActivity.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) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)
System.exit(0);
}
setContentView(R.layout.main_activity);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
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);
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.setOnItemClickListener((parent, view, position, id) -> {
if (adapter.getItemViewType(position) == ContentType.Item) {
GameItem item = ((GameItem) parent.getItemAtPosition(position));
notifyUser(getString(R.string.launching) + " " + item.getTitle());
loadFile(item.getPath(), getApplicationInfo().dataDir + "/shared_prefs/" + getApplicationInfo().packageName + "_preferences.xml", getApplicationInfo().dataDir + "/skyline.log");
final GameItem item = ((GameItem) parent.getItemAtPosition(position));
final Intent intent = new Intent(this, android.app.NativeActivity.class);
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);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
MenuItem mSearch = menu.findItem(R.id.action_search_main);
final SearchView searchView = (SearchView) mSearch.getActionView();
final MenuItem mSearch = menu.findItem(R.id.action_search_main);
SearchView searchView = (SearchView) mSearch.getActionView();
searchView.setSubmitButtonEnabled(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
public boolean onQueryTextSubmit(String query) {
public boolean onQueryTextSubmit(final String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
public boolean onQueryTextChange(final String newText) {
adapter.getFilter().filter(newText);
return true;
}
@ -138,13 +142,13 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
return super.onCreateOptionsMenu(menu);
}
public void onClick(View view) {
public void onClick(final View view) {
if (view.getId() == R.id.log_fab)
startActivity(new Intent(this, LogActivity.class));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
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 Bitmap icon;
TitleEntry(String name, String author, Bitmap icon) {
TitleEntry(final String name, final String author, final Bitmap icon) {
this.name = name;
this.author = author;
this.icon = icon;
@ -32,54 +32,54 @@ final class TitleEntry {
}
class NroLoader {
static TitleEntry getTitleEntry(String file) {
static TitleEntry getTitleEntry(final String file) {
try {
RandomAccessFile f = new RandomAccessFile(file, "r");
final RandomAccessFile f = new RandomAccessFile(file, "r");
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
byte[] buffer = new byte[4];
final byte[] buffer = new byte[4];
f.read(buffer);
if (!(new String(buffer).equals("ASET")))
throw new IOException();
f.skipBytes(0x4);
long iconOffset = Long.reverseBytes(f.readLong());
int iconSize = Integer.reverseBytes(f.readInt());
final long iconOffset = Long.reverseBytes(f.readLong());
final int iconSize = Integer.reverseBytes(f.readInt());
if (iconOffset == 0 || iconSize == 0)
throw new IOException();
f.seek(asetOffset + iconOffset);
byte[] iconData = new byte[iconSize];
final byte[] iconData = new byte[iconSize];
f.read(iconData);
Bitmap icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize);
final Bitmap icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize);
f.seek(asetOffset + 0x18);
long nacpOffset = Long.reverseBytes(f.readLong());
long nacpSize = Long.reverseBytes(f.readLong());
final long nacpOffset = Long.reverseBytes(f.readLong());
final long nacpSize = Long.reverseBytes(f.readLong());
if (nacpOffset == 0 || nacpSize == 0)
throw new IOException();
f.seek(asetOffset + nacpOffset);
byte[] name = new byte[0x200];
final byte[] name = new byte[0x200];
f.read(name);
byte[] author = new byte[0x100];
final byte[] author = new byte[0x100];
f.read(author);
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());
return null;
}
}
static boolean verifyFile(String file) {
static boolean verifyFile(final String file) {
try {
RandomAccessFile f = new RandomAccessFile(file, "r");
final RandomAccessFile f = new RandomAccessFile(file, "r");
f.seek(0x10); // Skip to NroHeader.magic
byte[] buffer = new byte[4];
final byte[] buffer = new byte[4];
f.read(buffer);
if (!(new String(buffer).equals("NRO0")))
return false;
} catch (IOException e) {
} catch (final IOException e) {
return false;
}
return true;

View File

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

View File

@ -1,56 +1,114 @@
<vector android:height="200dp" android:viewportHeight="248"
android:viewportWidth="248" android:width="200dp"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
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">
<aapt:attr name="android:fillColor">
<gradient android:endX="124" android:endY="-9.094947E-13"
android:startX="124" android:startY="248" android:type="linear">
<item android:color="#FF9E005D" 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
android:endX="124"
android:endY="-9.094947E-13"
android:startX="124"
android:startY="248"
android:type="linear">
<item
android:color="#FF9E005D"
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>
</aapt:attr>
</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">
<aapt:attr name="android:fillColor">
<gradient android:endX="59.927605" android:endY="144.2635"
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
android:endX="59.927605"
android:endY="144.2635"
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>
</aapt:attr>
</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">
<aapt:attr name="android:fillColor">
<gradient android:endX="236.0847" android:endY="104.7364"
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
android:endX="236.0847"
android:endY="104.7364"
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>
</aapt:attr>
</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">
<aapt:attr name="android:fillColor">
<gradient android:endX="180.2715" android:endY="185.7053"
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
android:endX="180.2715"
android:endY="185.7053"
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>
</aapt:attr>
</path>
<path android:fillColor="#00000000"
<path
android:fillColor="#00000000"
android:pathData="M95,59c44.7,-31.9 88.7,-38 107,-20c3.7,3.7 8.7,10.6 10,24"
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="5">
android:strokeWidth="5"
android:strokeLineCap="round"
android:strokeLineJoin="round">
<aapt:attr name="android:strokeColor">
<gradient android:endX="214.5" android:endY="46.0098"
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
android:endX="214.5"
android:endY="46.0098"
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>
</aapt:attr>
</path>

View File

@ -12,6 +12,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="5dp"
android:textAppearance="?android:attr/textAppearanceListItem"
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
android:id="@+id/game_list"
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" />
<com.google.android.material.floatingactionbutton.FloatingActionButton

View File

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

View File

@ -26,6 +26,9 @@
<string name="search_location">Search Location</string>
<string name="logging">Logging</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_language">Language</string>
<string name="system">System</string>

View File

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

View File

@ -35,16 +35,22 @@
app:key="log_level"
app:title="@string/log_level"
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
android:key="category_system"
android:title="@string/system">
<CheckBoxPreference
app:key="operation_mode"
app:title="@string/use_docked"
android:defaultValue="true"
android:summaryOff="@string/handheld_enabled"
android:summaryOn="@string/docked_enabled"
android:summaryOff="@string/handheld_enabled"/>
app:key="operation_mode"
app:title="@string/use_docked" />
</PreferenceCategory>
<PreferenceCategory
android:key="category_localization"