mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-22 21:09:16 +01:00
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:
parent
2e0014ba7c
commit
c005d7df74
@ -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>
|
||||
|
3
.idea/scopes/SkylineJava.xml
Normal file
3
.idea/scopes/SkylineJava.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="SkylineJava" pattern="file[app]:src/main/java//*" />
|
||||
</component>
|
@ -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">
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
Queue = nullptr;
|
||||
gameThread.join();
|
||||
Halt = false;
|
||||
ANativeActivity_finish(Activity);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "common.h"
|
||||
#include "nce.h"
|
||||
#include "gpu.h"
|
||||
#include <tinyxml2.h>
|
||||
|
||||
namespace skyline {
|
||||
@ -10,37 +12,46 @@ 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();
|
||||
}
|
||||
|
||||
std::string Settings::GetString(const std::string& key) {
|
||||
std::string Settings::GetString(const std::string &key) {
|
||||
return stringMap.at(key);
|
||||
}
|
||||
|
||||
bool Settings::GetBool(const std::string& key) {
|
||||
bool Settings::GetBool(const std::string &key) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
163
app/src/main/cpp/skyline/gpu.cpp
Normal file
163
app/src/main/cpp/skyline/gpu.cpp
Normal 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;
|
||||
}
|
||||
}
|
112
app/src/main/cpp/skyline/gpu.h
Normal file
112
app/src/main/cpp/skyline/gpu.h
Normal 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();
|
||||
};
|
||||
}
|
227
app/src/main/cpp/skyline/gpu/devices/nvdevice.h
Normal file
227
app/src/main/cpp/skyline/gpu/devices/nvdevice.h
Normal 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());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
5
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.cpp
Normal file
5
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.cpp
Normal 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, {}) {}
|
||||
}
|
13
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.h
Normal file
13
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.h
Normal 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);
|
||||
};
|
||||
}
|
42
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.cpp
Normal file
42
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.cpp
Normal 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) {}
|
||||
|
||||
}
|
57
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.h
Normal file
57
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.h
Normal 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);
|
||||
};
|
||||
}
|
5
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.cpp
Normal file
5
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include "nvhost_ctrl.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl, {}) {}
|
||||
}
|
13
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.h
Normal file
13
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.h
Normal 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);
|
||||
};
|
||||
}
|
138
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.cpp
Normal file
138
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.cpp
Normal 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);
|
||||
}
|
||||
}
|
38
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.h
Normal file
38
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.h
Normal 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);
|
||||
};
|
||||
}
|
140
app/src/main/cpp/skyline/gpu/devices/nvmap.cpp
Normal file
140
app/src/main/cpp/skyline/gpu/devices/nvmap.cpp
Normal 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);
|
||||
}
|
||||
}
|
71
app/src/main/cpp/skyline/gpu/devices/nvmap.h
Normal file
71
app/src/main/cpp/skyline/gpu/devices/nvmap.h
Normal 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);
|
||||
};
|
||||
}
|
138
app/src/main/cpp/skyline/gpu/display.cpp
Normal file
138
app/src/main/cpp/skyline/gpu/display.cpp
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
218
app/src/main/cpp/skyline/gpu/display.h
Normal file
218
app/src/main/cpp/skyline/gpu/display.h
Normal 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);
|
||||
};
|
||||
}
|
50
app/src/main/cpp/skyline/gpu/parcel.cpp
Normal file
50
app/src/main/cpp/skyline/gpu/parcel.cpp
Normal 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;
|
||||
}
|
||||
}
|
112
app/src/main/cpp/skyline/gpu/parcel.h
Normal file
112
app/src/main/cpp/skyline/gpu/parcel.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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) {
|
||||
@ -134,15 +158,21 @@ namespace skyline::kernel::ipc {
|
||||
memcpy(currPtr, argVec.data(), argVec.size());
|
||||
currPtr += argVec.size();
|
||||
|
||||
if(isDomain) {
|
||||
for (auto& domainObject : domainObjects) {
|
||||
if (isDomain) {
|
||||
for (auto &domainObject : domainObjects) {
|
||||
*reinterpret_cast<handle_t *>(currPtr) = domainObject;
|
||||
currPtr += sizeof(handle_t);
|
||||
}
|
||||
}
|
||||
|
||||
state.logger->Write(Logger::Debug, "Output: Raw Size: {}, Command ID: 0x{:X}, Copy Handles: {}, Move Handles: {}", u32(header->raw_sz), u32(payload->value), copyHandles.size(), moveHandles.size());
|
||||
state.logger->Debug("Output: Raw Size: {}, Command ID: 0x{:X}, Copy Handles: {}, Move Handles: {}", u32(header->raw_sz), u32(payload->value), copyHandles.size(), moveHandles.size());
|
||||
|
||||
state.thisProcess->WriteMemory(tls.data(), state.thisThread->tls, constant::TlsIpcSize);
|
||||
}
|
||||
|
||||
std::vector<u8> BufferDescriptorABW::Read(const DeviceState &state) {
|
||||
std::vector<u8> vec(Size());
|
||||
state.thisProcess->ReadMemory(vec.data(), Address(), Size());
|
||||
return std::move(vec);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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, {
|
||||
}) {}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
@ -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;
|
||||
for(const auto& [address, region] : state.thisProcess->memoryMap) {
|
||||
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) {
|
||||
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),
|
||||
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 = 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) {
|
||||
@ -53,14 +114,14 @@ namespace skyline::kernel::svc {
|
||||
|
||||
void SleepThread(DeviceState &state) {
|
||||
auto in = state.nce->GetRegister(Xreg::X0);
|
||||
switch(in) {
|
||||
switch (in) {
|
||||
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,9 +184,11 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
std::vector<handle_t> waitHandles(numHandles);
|
||||
state.thisProcess->ReadMemory(waitHandles.data(), state.nce->GetRegister(Xreg::X1), numHandles * sizeof(handle_t));
|
||||
for (const auto& handle : waitHandles) {
|
||||
std::string handleStr;
|
||||
for (const auto &handle : waitHandles) {
|
||||
handleStr += fmt::format("* 0x{:X}\n", handle);
|
||||
auto object = state.thisProcess->handleTable.at(handle);
|
||||
switch(object->objectType) {
|
||||
switch (object->objectType) {
|
||||
case type::KType::KProcess:
|
||||
case type::KType::KThread:
|
||||
case type::KType::KEvent:
|
||||
@ -118,15 +199,34 @@ namespace skyline::kernel::svc {
|
||||
return;
|
||||
}
|
||||
auto syncObject = std::static_pointer_cast<type::KSyncObject>(object);
|
||||
if(syncObject->signalled) {
|
||||
if (syncObject->signalled) {
|
||||
state.logger->Debug("Found signalled handle: 0x{:X}", handle);
|
||||
state.nce->SetRegister(Wreg::W0, constant::status::Success);
|
||||
state.nce->SetRegister(Wreg::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.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3);
|
||||
state.thisThread->status = type::KThread::ThreadStatus::WaitSync;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -17,8 +17,10 @@ namespace skyline::kernel::type {
|
||||
* @brief Signals all threads waiting on this object
|
||||
*/
|
||||
virtual inline void Signal() {
|
||||
KSyncObject::Signal();
|
||||
signalled = true;
|
||||
if (!signalled) {
|
||||
KSyncObject::Signal();
|
||||
signalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 ®ion : 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;
|
||||
@ -73,10 +80,11 @@ namespace skyline::kernel::type {
|
||||
|
||||
KPrivateMemory::~KPrivateMemory() {
|
||||
try {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, owner);
|
||||
} catch (const std::exception&) {}
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, owner);
|
||||
} catch (const std::exception &) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -24,10 +24,10 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
u64 KProcess::GetTlsSlot() {
|
||||
for (auto &tlsPage: tlsPages) {
|
||||
if (!tlsPage->Full())
|
||||
return tlsPage->ReserveSlot();
|
||||
}
|
||||
for (auto &tlsPage: tlsPages) {
|
||||
if (!tlsPage->Full())
|
||||
return tlsPage->ReserveSlot();
|
||||
}
|
||||
auto tlsMem = NewHandle<KPrivateMemory>(mainThread, 0, 0, PAGE_SIZE, memory::Permission(true, true, false), memory::Type::ThreadLocal).item;
|
||||
memoryMap[tlsMem->address] = tlsMem;
|
||||
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
|
||||
@ -39,11 +39,11 @@ namespace skyline::kernel::type {
|
||||
|
||||
KProcess::KProcess(const DeviceState &state, pid_t pid, u64 entryPoint, u64 stackBase, u64 stackSize) : mainThread(pid), mainThreadStackSz(stackSize), KSyncObject(state, KType::KProcess) {
|
||||
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 ®ion : 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;
|
||||
}
|
||||
}
|
||||
|
@ -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,26 +166,33 @@ namespace skyline::kernel::type {
|
||||
*/
|
||||
HandleOut<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region);
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
size_t GetProgramSize();
|
||||
|
||||
/**
|
||||
* @brief Creates a new handle to a KObject and adds it to the process handle_table
|
||||
* @tparam objectClass The class of the kernel object to create
|
||||
* @param args The arguments for the kernel object except handle, pid and state
|
||||
* @return A shared pointer to the corresponding object
|
||||
*/
|
||||
/**
|
||||
* @brief Creates a new handle to a KObject and adds it to the process handle_table
|
||||
* @tparam objectClass The class of the kernel object to create
|
||||
* @param args The arguments for the kernel object except handle, pid and state
|
||||
* @return A shared pointer to the corresponding object
|
||||
*/
|
||||
template<typename objectClass, typename ...objectArgs>
|
||||
HandleOut<objectClass> NewHandle(objectArgs... args) {
|
||||
std::shared_ptr<objectClass> item;
|
||||
if constexpr (std::is_same<objectClass, KThread>())
|
||||
item = std::make_shared<objectClass>(state, handleIndex, args...);
|
||||
else
|
||||
item = std::make_shared<objectClass>(state, args...);
|
||||
handleTable[handleIndex] = std::static_pointer_cast<KObject>(item);
|
||||
return {item, handleIndex++};
|
||||
HandleOut<objectClass> NewHandle(objectArgs... args) {
|
||||
std::shared_ptr<objectClass> item;
|
||||
if constexpr (std::is_same<objectClass, KThread>())
|
||||
item = std::make_shared<objectClass>(state, handleIndex, args...);
|
||||
else
|
||||
item = std::make_shared<objectClass>(state, args...);
|
||||
handleTable[handleIndex] = std::static_pointer_cast<KObject>(item);
|
||||
return {item, handleIndex++};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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++;
|
||||
}
|
||||
@ -210,13 +218,17 @@ namespace skyline::kernel::type {
|
||||
if constexpr(std::is_same<objectClass, KThread>())
|
||||
objectType = KType::KThread;
|
||||
else if constexpr(std::is_same<objectClass, KProcess>())
|
||||
objectType = KType::KProcess;
|
||||
objectType = KType::KProcess;
|
||||
else if constexpr(std::is_same<objectClass, KSharedMemory>())
|
||||
objectType = KType::KSharedMemory;
|
||||
objectType = KType::KSharedMemory;
|
||||
else if constexpr(std::is_same<objectClass, KTransferMemory>())
|
||||
objectType = KType::KTransferMemory;
|
||||
else if constexpr(std::is_same<objectClass, KPrivateMemory>())
|
||||
objectType = KType::KPrivateMemory;
|
||||
objectType = KType::KPrivateMemory;
|
||||
else if constexpr(std::is_same<objectClass, KSession>())
|
||||
objectType = KType::KSession;
|
||||
objectType = KType::KSession;
|
||||
else if constexpr(std::is_same<objectClass, KEvent>())
|
||||
objectType = KType::KEvent;
|
||||
else
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
constexpr const char* ASHMEM_NAME_DEF = "dev/ashmem";
|
||||
constexpr const char *ASHMEM_NAME_DEF = "dev/ashmem";
|
||||
constexpr int ASHMEM_SET_SIZE = 0x40087703;
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
@ -14,12 +14,12 @@ namespace skyline::kernel::type {
|
||||
KSharedMemory::KSharedMemory(const DeviceState &state, pid_t pid, u64 kaddress, size_t ksize, const memory::Permission localPermission, const memory::Permission remotePermission, memory::Type type) : kaddress(kaddress), ksize(ksize), localPermission(localPermission), remotePermission(remotePermission), type(type), owner(pid), KObject(state, KType::KSharedMemory) {
|
||||
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) {
|
||||
@ -43,13 +43,14 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
KSharedMemory::~KSharedMemory() {
|
||||
for (auto [process, procInf] : procInfMap) {
|
||||
for (auto[process, procInf] : procInfMap) {
|
||||
try {
|
||||
user_pt_regs fregs = {0};
|
||||
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);
|
||||
@ -60,7 +61,7 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
void KSharedMemory::Resize(size_t newSize) {
|
||||
for (auto& [process, procInf] : procInfMap) {
|
||||
for (auto&[process, procInf] : procInfMap) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = procInf.address;
|
||||
fregs.regs[1] = procInf.size;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -80,7 +81,7 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
void KSharedMemory::UpdatePermission(bool local, memory::Permission newPerms) {
|
||||
for (auto& [process, procInf] : procInfMap) {
|
||||
for (auto&[process, procInf] : procInfMap) {
|
||||
if ((local && process == owner) || (!local && process != owner)) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = procInf.address;
|
||||
@ -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);
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,22 @@
|
||||
#include "KObject.h"
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief KSyncObject holds the state of a waitable object
|
||||
*/
|
||||
/**
|
||||
* @brief KSyncObject holds the state of a waitable object
|
||||
*/
|
||||
class KSyncObject : public KObject {
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
@ -96,7 +91,7 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
KTransferMemory::~KTransferMemory() {
|
||||
if(owner) {
|
||||
if (owner) {
|
||||
try {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = cAddress;
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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:
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -7,39 +7,39 @@ 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;
|
||||
} header {};
|
||||
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
|
||||
NroSegmentHeader dynstr; //!< The .dynstr segment header
|
||||
NroSegmentHeader dynsym; //!< The .dynsym segment header
|
||||
} header{};
|
||||
|
||||
public:
|
||||
/**
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -10,84 +10,86 @@ namespace skyline {
|
||||
iovec iov = {®isters, 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 ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, 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)];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
@ -36,27 +35,27 @@ namespace skyline::kernel {
|
||||
munmap(stack, stackSize);
|
||||
throw exception("Failed to create guard pages");
|
||||
}
|
||||
pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FILES | CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
pid_t pid = clone(&ExecuteChild, stack + stackSize, CLONE_FILES | CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
if (pid == -1)
|
||||
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) {
|
||||
|
@ -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.
|
||||
|
80
app/src/main/cpp/skyline/services/am/applet.cpp
Normal file
80
app/src/main/cpp/skyline/services/am/applet.cpp
Normal 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)}
|
||||
}) {}
|
||||
}
|
101
app/src/main/cpp/skyline/services/am/applet.h
Normal file
101
app/src/main/cpp/skyline/services/am/applet.h
Normal 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);
|
||||
};
|
||||
}
|
102
app/src/main/cpp/skyline/services/am/appletController.cpp
Normal file
102
app/src/main/cpp/skyline/services/am/appletController.cpp
Normal 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, {
|
||||
}) {}
|
||||
}
|
@ -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:
|
@ -1,17 +1,17 @@
|
||||
#include "apm.h"
|
||||
|
||||
namespace skyline::kernel::service::apm {
|
||||
apm::apm(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::apm, {
|
||||
{0x0, SFunc(apm::OpenSession)}
|
||||
apm::apm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::apm, {
|
||||
{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)}
|
||||
ISession::ISession(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::apm_ISession, {
|
||||
{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) {
|
@ -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 {
|
||||
/**
|
||||
@ -9,7 +9,7 @@ namespace skyline::kernel::service::apm {
|
||||
*/
|
||||
class apm : public BaseService {
|
||||
public:
|
||||
apm(const DeviceState &state, ServiceManager& manager);
|
||||
apm(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This returns an handle to ISession
|
||||
@ -25,7 +25,7 @@ namespace skyline::kernel::service::apm {
|
||||
u32 performanceConfig[2] = {0x00010000, 0x00020001}; //!< This holds the performance config for both handheld(0) and docked(1) mode
|
||||
|
||||
public:
|
||||
ISession(const DeviceState &state, ServiceManager& manager);
|
||||
ISession(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This sets performanceConfig to the given arguments, it doesn't affect anything else (https://switchbrew.org/wiki/PPC_services#SetPerformanceConfiguration)
|
@ -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;
|
||||
@ -75,11 +95,11 @@ namespace skyline::kernel::service {
|
||||
class BaseService {
|
||||
protected:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
ServiceManager& manager; //!< A pointer to the service manager
|
||||
ServiceManager &manager; //!< A pointer to the service manager
|
||||
std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> vTable; //!< This holds the mapping from an object's CmdId to the actual function
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
@ -89,12 +109,17 @@ namespace skyline::kernel::service {
|
||||
* @param serviceName The name of the service
|
||||
* @param vTable The functions of the service
|
||||
*/
|
||||
BaseService(const DeviceState &state, ServiceManager& manager, bool hasLoop, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : state(state), manager(manager), hasLoop(hasLoop), serviceType(serviceType), vTable(vTable) {}
|
||||
BaseService(const DeviceState &state, ServiceManager &manager, bool hasLoop, Service serviceType, const std::unordered_map<u32, std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)>> &vTable) : state(state), manager(manager), hasLoop(hasLoop), serviceType(serviceType), vTable(vTable) {}
|
||||
|
||||
/**
|
||||
* @brief This returns the name of the current service
|
||||
* @note It may not return the exact name the service was initialized with if there are multiple entries in ServiceString
|
||||
* @return The name of the service
|
||||
*/
|
||||
std::string getName() {
|
||||
std::string serviceName = "";
|
||||
for (const auto& [name, type] : ServiceString)
|
||||
if(type == serviceType)
|
||||
std::string serviceName;
|
||||
for (const auto&[name, type] : ServiceString)
|
||||
if (type == serviceType)
|
||||
serviceName = name;
|
||||
return serviceName;
|
||||
}
|
||||
@ -108,14 +133,14 @@ namespace skyline::kernel::service {
|
||||
std::function<void(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &)> function;
|
||||
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));
|
||||
} catch (std::out_of_range &) {
|
||||
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());
|
||||
} catch (std::exception &e) {
|
||||
throw exception("{} (Service: {})", e.what(), getName());
|
||||
}
|
||||
};
|
||||
|
13
app/src/main/cpp/skyline/services/fatal/fatal.cpp
Normal file
13
app/src/main/cpp/skyline/services/fatal/fatal.cpp
Normal 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));
|
||||
}
|
||||
}
|
@ -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 {
|
||||
/**
|
||||
@ -9,7 +9,7 @@ namespace skyline::kernel::service::fatal {
|
||||
*/
|
||||
class fatalU : public BaseService {
|
||||
public:
|
||||
fatalU(const DeviceState &state, ServiceManager& manager);
|
||||
fatalU(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This throws an exception so that emulation will quit
|
@ -1,13 +1,13 @@
|
||||
#include "fs.h"
|
||||
|
||||
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)}
|
||||
fsp::fsp(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::fs_fsp, {
|
||||
{0x1, SFUNC(fsp::SetCurrentProcess)},
|
||||
{0x12, SFUNC(fsp::OpenSdCardFileSystem)}
|
||||
}) {}
|
||||
|
||||
void fsp::SetCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
process = *reinterpret_cast<pid_t*>(request.cmdArg);
|
||||
process = *reinterpret_cast<pid_t *>(request.cmdArg);
|
||||
}
|
||||
|
||||
void fsp::OpenSdCardFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
@ -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
|
||||
@ -20,7 +20,7 @@ namespace skyline::kernel::service::fs {
|
||||
public:
|
||||
pid_t process{}; //!< This holds the PID set by SetCurrentProcess
|
||||
|
||||
fsp(const DeviceState &state, ServiceManager& manager);
|
||||
fsp(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This sets the PID of the process using FS currently (https://switchbrew.org/wiki/Filesystem_services#SetCurrentProcess)
|
||||
@ -40,6 +40,6 @@ namespace skyline::kernel::service::fs {
|
||||
public:
|
||||
FsType type;
|
||||
|
||||
IFileSystem(FsType type, const DeviceState &state, ServiceManager& manager);
|
||||
IFileSystem(FsType type, const DeviceState &state, ServiceManager &manager);
|
||||
};
|
||||
}
|
@ -2,24 +2,26 @@
|
||||
#include <os.h>
|
||||
|
||||
namespace skyline::kernel::service::hid {
|
||||
IAppletResource::IAppletResource(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::hid_IAppletResource, {
|
||||
{0x0, SFunc(IAppletResource::GetSharedMemoryHandle)}
|
||||
IAppletResource::IAppletResource(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::hid_IAppletResource, {
|
||||
{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) {
|
@ -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 {
|
||||
@ -14,7 +14,7 @@ namespace skyline::kernel::service::hid {
|
||||
*/
|
||||
class IAppletResource : public BaseService {
|
||||
public:
|
||||
IAppletResource(const DeviceState &state, ServiceManager& manager);
|
||||
IAppletResource(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
std::shared_ptr<type::KSharedMemory> hidSharedMemory;
|
||||
|
||||
@ -33,15 +33,15 @@ namespace skyline::kernel::service::hid {
|
||||
* @brief This holds the controller styles supported by an application
|
||||
*/
|
||||
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;
|
||||
};
|
||||
@ -111,7 +111,7 @@ namespace skyline::kernel::service::hid {
|
||||
JoyConOrientation orientation{JoyConOrientation::Unset}; //!< The Orientation of the Joy-Con(s)
|
||||
|
||||
public:
|
||||
hid(const DeviceState &state, ServiceManager& manager);
|
||||
hid(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This returns an IAppletResource (https://switchbrew.org/wiki/HID_services#CreateAppletResource)
|
53
app/src/main/cpp/skyline/services/nvdrv/nvdrv.cpp
Normal file
53
app/src/main/cpp/skyline/services/nvdrv/nvdrv.cpp
Normal 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);
|
||||
}
|
||||
}
|
46
app/src/main/cpp/skyline/services/nvdrv/nvdrv.h
Normal file
46
app/src/main/cpp/skyline/services/nvdrv/nvdrv.h
Normal 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);
|
||||
};
|
||||
}
|
89
app/src/main/cpp/skyline/services/nvnflinger/dispdrv.cpp
Normal file
89
app/src/main/cpp/skyline/services/nvnflinger/dispdrv.cpp
Normal 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");
|
||||
}
|
||||
}
|
63
app/src/main/cpp/skyline/services/nvnflinger/dispdrv.h
Normal file
63
app/src/main/cpp/skyline/services/nvnflinger/dispdrv.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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);
|
||||
@ -154,17 +182,18 @@ namespace skyline::kernel::service {
|
||||
session->domainTable.erase(request.domain->object_id);
|
||||
break;
|
||||
}
|
||||
} catch (std::out_of_range&) {
|
||||
} catch (std::out_of_range &) {
|
||||
throw exception("Invalid object ID was used with domain request");
|
||||
}
|
||||
} else
|
||||
session->serviceObject->HandleRequest(*session, request, response);
|
||||
response.WriteTls();
|
||||
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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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,22 +13,22 @@ namespace skyline::kernel::service::set {
|
||||
* @brief Encapsulates the system version, this is sent to the application in GetFirmwareVersion (https://switchbrew.org/wiki/System_Version_Title)
|
||||
*/
|
||||
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);
|
||||
|
||||
public:
|
||||
sys(const DeviceState &state, ServiceManager& manager);
|
||||
sys(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief Writes the Firmware version to a 0xA buffer (https://switchbrew.org/wiki/Settings_services#GetFirmwareVersion)
|
@ -1,9 +1,9 @@
|
||||
#include "sm.h"
|
||||
|
||||
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)}
|
||||
sm::sm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::sm, {
|
||||
{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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
/**
|
||||
@ -9,7 +9,7 @@ namespace skyline::kernel::service::sm {
|
||||
*/
|
||||
class sm : public BaseService {
|
||||
public:
|
||||
sm(const DeviceState &state, ServiceManager& manager);
|
||||
sm(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This initializes the sm: service. It doesn't actually do anything. (https://switchbrew.org/wiki/Services_API#Initialize)
|
@ -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,13 +33,13 @@ 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) {
|
||||
time_t curTime = std::time(nullptr);
|
||||
tm calender = *std::gmtime(&curTime);
|
||||
CalendarTime calendarTime {
|
||||
CalendarTime calendarTime{
|
||||
.year = static_cast<u16>(calender.tm_year),
|
||||
.month = static_cast<u8>(calender.tm_mon),
|
||||
.day = static_cast<u8>(calender.tm_hour),
|
||||
@ -47,10 +47,10 @@ namespace skyline::kernel::service::time {
|
||||
.second = static_cast<u8>(calender.tm_sec)
|
||||
};
|
||||
response.WriteValue(calendarTime);
|
||||
CalendarAdditionalInfo calendarInfo {
|
||||
CalendarAdditionalInfo calendarInfo{
|
||||
.day_week = static_cast<u32>(calender.tm_wday),
|
||||
.day_month = static_cast<u32>(calender.tm_mday),
|
||||
.name = *reinterpret_cast<const u64*>(calender.tm_zone),
|
||||
.name = *reinterpret_cast<const u64 *>(calender.tm_zone),
|
||||
.dst = static_cast<i32>(calender.tm_isdst),
|
||||
.utc_rel = static_cast<u32>(calender.tm_gmtoff)
|
||||
};
|
@ -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
|
||||
@ -18,7 +18,7 @@ namespace skyline::kernel::service::time {
|
||||
*/
|
||||
class time : public BaseService {
|
||||
public:
|
||||
time(const DeviceState &state, ServiceManager& manager);
|
||||
time(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This returns a handle to a ISystemClock for user time (https://switchbrew.org/wiki/Services_API#GetStandardUserSystemClock)
|
||||
@ -48,7 +48,7 @@ namespace skyline::kernel::service::time {
|
||||
public:
|
||||
SystemClockType type; //!< The type of the system clock
|
||||
|
||||
ISystemClock(SystemClockType clockType, const DeviceState &state, ServiceManager& manager);
|
||||
ISystemClock(SystemClockType clockType, const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This returns the amount of seconds since epoch
|
||||
@ -73,7 +73,7 @@ namespace skyline::kernel::service::time {
|
||||
u8 second;
|
||||
u8 : 8;
|
||||
};
|
||||
static_assert(sizeof(CalendarTime)==8);
|
||||
static_assert(sizeof(CalendarTime) == 8);
|
||||
|
||||
/**
|
||||
* @brief This is passed in addition to CalendarTime
|
||||
@ -85,9 +85,9 @@ namespace skyline::kernel::service::time {
|
||||
i32 dst;
|
||||
u32 utc_rel;
|
||||
};
|
||||
static_assert(sizeof(CalendarAdditionalInfo)==24);
|
||||
static_assert(sizeof(CalendarAdditionalInfo) == 24);
|
||||
|
||||
ITimeZoneService(const DeviceState &state, ServiceManager& manager);
|
||||
ITimeZoneService(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief This receives a u64 #PosixTime (https://switchbrew.org/wiki/PSC_services#PosixTime), and returns a #CalendarTime (https://switchbrew.org/wiki/PSC_services#CalendarTime), #CalendarAdditionalInfo (https://switchbrew.org/wiki/PSC_services#CalendarAdditionalInfo)
|
155
app/src/main/cpp/skyline/services/vi/vi_m.cpp
Normal file
155
app/src/main/cpp/skyline/services/vi/vi_m.cpp
Normal 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);
|
||||
}
|
||||
}
|
142
app/src/main/cpp/skyline/services/vi/vi_m.h
Normal file
142
app/src/main/cpp/skyline/services/vi/vi_m.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
try {
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
final String line = reader.readLine();
|
||||
done = (line == null);
|
||||
if (!done) {
|
||||
runOnUiThread(() -> adapter.add(line));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
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) {
|
||||
String line = reader.readLine();
|
||||
if (!(done = (line == null))) {
|
||||
adapter.add(line);
|
||||
}
|
||||
};
|
||||
observer.onEvent(FileObserver.MODIFY, log_file.getPath());
|
||||
observer.startWatching();
|
||||
while (!interrupted()) ;
|
||||
observer.stopWatching();
|
||||
});
|
||||
thread.start();
|
||||
} catch (FileNotFoundException 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();
|
||||
}
|
||||
} 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
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);
|
||||
} else {
|
||||
super.add(log_meta[1], ContentType.Header);
|
||||
}
|
||||
void add(String log_line) {
|
||||
try {
|
||||
final String[] log_meta = log_line.split("\\|", 3);
|
||||
if (log_meta[0].startsWith("1")) {
|
||||
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 {
|
||||
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,32 +71,35 @@ 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);
|
||||
convertView = inflater.inflate(R.layout.log_item, parent, false);
|
||||
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);
|
||||
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
|
||||
viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle);
|
||||
}
|
||||
convertView.setOnLongClickListener(this);
|
||||
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);
|
||||
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());
|
||||
viewHolder.txtSub.setText(data.getLevel());
|
||||
if (!compact)
|
||||
viewHolder.txtSub.setText(data.getLevel());
|
||||
} else {
|
||||
viewHolder.txtTitle.setText((String) getItem(position));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
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">
|
||||
<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: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>
|
||||
|
@ -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" />
|
||||
|
||||
|
17
app/src/main/res/layout/log_item_compact.xml
Normal file
17
app/src/main/res/layout/log_item_compact.xml
Normal 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>
|
@ -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
|
||||
|
@ -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" />
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user