Milestone 2 - Memory & IPC Marshalling

This commit introduces a new memory model that supports true shared memory with separate permissions for remote and local processes, implements svcQueryMemory and completes svcGetInfo further, adds IPC support with the IpcRequest and IpcResponse classes.
This commit is contained in:
◱ PixelyIon 2019-09-14 18:11:00 +05:30
parent 9e1e06c64b
commit a54f5ff578
59 changed files with 1752 additions and 1256 deletions

8
.gitignore vendored
View File

@ -61,7 +61,8 @@ build/
local.properties
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.externalNativeBuild/
.cxx/
# IntelliJ
out/
@ -93,6 +94,5 @@ captures/
# VSCode
.vscode/
# Android Studio
.cxx/
# Discord plugin for IntelliJ IDEA
.idea\discord.xml

View File

@ -4,6 +4,8 @@
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<option name="SOFT_MARGINS" value="80,140" />
<Objective-C>
<option name="INDENT_VISIBILITY_KEYWORDS" value="2" />
<option name="INDENT_PREPROCESSOR_DIRECTIVE" value="4" />
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
<option name="FUNCTION_PARAMETERS_WRAP" value="0" />
@ -31,6 +33,12 @@
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
<option name="WRAP_LONG_LINES" value="true" />
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="LABEL_INDENT_SIZE" value="-1" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<arrangement>
<rules>

View File

@ -4,7 +4,4 @@
<component name="DiscordProjectSettings">
<option name="show" value="true" />
</component>
<component name="ProjectNotificationSettings">
<option name="askShowProject" value="false" />
</component>
</project>

View File

@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.8)
project(Lightswitch VERSION 1 LANGUAGES CXX)
set_property(GLOBAL PROPERTY CMAKE_CXX_STANDARD 17 PROPERTY CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(BUILD_TESTING OFF)
set(CMAKE_CXX_STANDARD 17)
@ -25,6 +24,8 @@ add_library(lightswitch SHARED
${source_DIR}/switch/kernel/service.cpp
${source_DIR}/switch/kernel/types/KProcess.cpp
${source_DIR}/switch/kernel/types/KThread.cpp
${source_DIR}/switch/kernel/types/KSharedMemory.cpp
${source_DIR}/switch/kernel/types/KPrivateMemory.cpp
)
target_link_libraries(lightswitch fmt tinyxml2 android)
target_compile_options(lightswitch PRIVATE -Wno-c++17-extensions)

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="emu.lightswitch">
xmlns:tools="http://schemas.android.com/tools"
package="emu.lightswitch">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
@ -15,12 +15,13 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".LogActivity"
android:label="@string/log"
android:parentActivityName=".MainActivity">
<activity
android:name=".LogActivity"
android:label="@string/log"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.lightswitch.MainActivity"/>
android:value="emu.lightswitch.MainActivity" />
</activity>
<activity
android:name=".SettingsActivity"
@ -28,13 +29,13 @@
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.lightswitch.MainActivity"/>
android:value="emu.lightswitch.MainActivity" />
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

View File

@ -32,6 +32,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_lightswitch_MainActivity_loadFile(JNI
if (emu_thread) {
halt = true; // This'll cause execution to stop after the next breakpoint
emu_thread->join();
halt = false; // Or the current instance will halt immediately
}
// Running on UI thread is not a good idea as the UI will remain unresponsive

View File

@ -3,33 +3,7 @@
#include <syslog.h>
namespace lightSwitch {
namespace memory {
Permission::Permission() {
r = 0;
w = 0;
x = 0;
}
Permission::Permission(bool read, bool write, bool execute) {
r = read;
w = write;
x = execute;
}
bool Permission::operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); }
bool Permission::operator!=(const Permission &rhs) const { return !operator==(rhs); }
int Permission::get() const {
int perm = 0;
if (r) perm |= PROT_READ;
if (w) perm |= PROT_WRITE;
if (x) perm |= PROT_EXEC;
return perm;
}
}
Settings::Settings(std::string pref_xml) {
Settings::Settings(std::string &pref_xml) {
tinyxml2::XMLDocument pref;
if (pref.LoadFile(pref_xml.c_str()))
throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr()));
@ -38,13 +12,10 @@ namespace lightSwitch {
switch (elem->Value()[0]) {
case 's':
string_map.insert(
std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(),
(char *) elem->GetText()));
std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(), (char *) elem->GetText()));
break;
case 'b':
bool_map.insert(
std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(),
elem->FindAttribute("value")->BoolValue()));
bool_map.insert(std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(), elem->FindAttribute("value")->BoolValue()));
default:
break;
};
@ -78,7 +49,7 @@ namespace lightSwitch {
}
}
Logger::Logger(const std::string & log_path) {
Logger::Logger(const std::string &log_path) {
log_file.open(log_path, std::ios::app);
WriteHeader("Logging started");
}
@ -87,13 +58,13 @@ namespace lightSwitch {
WriteHeader("Logging ended");
}
void Logger::WriteHeader(const std::string& str) {
void Logger::WriteHeader(const std::string &str) {
syslog(LOG_ALERT, "%s", str.c_str());
log_file << "0|" << str << "\n";
log_file.flush();
}
void Logger::Write(const LogLevel level, const std::string& str) {
void Logger::Write(const LogLevel level, const std::string &str) {
#ifdef NDEBUG
if (level == DEBUG) return;
#endif

View File

@ -18,49 +18,71 @@
namespace lightSwitch {
// Global typedefs
typedef __uint128_t u128;
typedef __uint64_t u64;
typedef __uint32_t u32;
typedef __uint16_t u16;
typedef __uint8_t u8;
typedef __int128_t i128;
typedef __int64_t i64;
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 uint32_t handle_t; //!< The type of an handle
typedef u32 handle_t; //!< The type of an handle
namespace constant {
// Memory
constexpr uint64_t base_addr = 0x8000000; //!< The address space base
constexpr uint64_t base_size = 0x7FF8000000; //!< The size of the address space
constexpr uint64_t total_phy_mem = 0xF8000000; // ~4 GB of RAM
constexpr u64 base_addr = 0x8000000; //!< The address space base
constexpr u64 map_addr = base_addr + 0x80000000; //!< The address of the map region
constexpr u64 base_size = 0x7FF8000000; //!< The size of the address space
constexpr u64 map_size = 0x1000000000; //!< The size of the map region
constexpr u64 total_phy_mem = 0xF8000000; // ~4 GB of RAM
constexpr size_t def_stack_size = 0x1E8480; //!< The default amount of stack: 2 MB
constexpr size_t def_heap_size = PAGE_SIZE; //!< The default amount of heap
constexpr size_t tls_slot_size = 0x200; //!< The size of a single TLS slot
constexpr uint8_t tls_slots = PAGE_SIZE / constant::tls_slot_size; //!< The amount of TLS slots in a single page
constexpr u8 tls_slots = PAGE_SIZE / tls_slot_size; //!< The amount of TLS slots in a single page
// Loader
constexpr uint32_t nro_magic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
constexpr u32 nro_magic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
// NCE
constexpr uint8_t num_regs = 31; //!< The amount of registers that ARMv8 has
constexpr uint16_t svc_last = 0x7F; //!< The index of the last SVC
constexpr uint16_t brk_rdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
constexpr uint32_t tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
constexpr u8 num_regs = 31; //!< The amount of registers that ARMv8 has
constexpr u16 svc_last = 0x7F; //!< The index of the last SVC
constexpr u16 brk_rdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
constexpr u32 tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
// IPC
constexpr size_t tls_ipc_size = 0x100; //!< The size of the IPC command buffer in a TLS slot
constexpr uint64_t sm_handle = 0xd000; //!< sm:'s handle
constexpr uint8_t port_size = 0x8; //!< The size of a port name string
constexpr uint32_t ipc_sfco = 0x4F434653; //!< SFCO in reverse
constexpr handle_t sm_handle = 0xD000; //!< sm:'s handle
constexpr u8 port_size = 0x8; //!< The size of a port name string
constexpr u32 sfco_magic = 0x4F434653; //!< SFCO in reverse, written to IPC messages
constexpr u32 sfci_magic = 0x49434653; //!< SFCI in reverse, present in received IPC messages
constexpr u64 padding_sum = 0x10; //!< The sum of the padding surrounding DataPayload
// Process
constexpr uint32_t base_handle_index = 0xD001; // The index of the base handle
constexpr uint8_t default_priority = 31; //!< The default priority of a process
constexpr handle_t base_handle_index = sm_handle + 1; // The index of the base handle
constexpr u8 default_priority = 31; //!< The default priority of a process
constexpr std::pair<int8_t, int8_t> priority_an = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<uint8_t, uint8_t> priority_nin = {0, 63}; //!< The range of priority for the Nintendo Switch
constexpr std::pair<u8, u8> priority_nin = {0, 63}; //!< The range of priority for the Nintendo Switch
// Status codes
namespace status {
constexpr u32 success = 0x0; //!< "Success"
constexpr u32 inv_address = 0xCC01; //!< "Invalid address"
constexpr u32 inv_handle = 0xE401; //!< "Invalid handle"
constexpr u32 unimpl = 0x177202; //!< "Unimplemented behaviour"
}
};
namespace instr {
/**
* A bitfield struct that encapsulates a BRK instruction. It can be used to generate as well as parse the instruction's opcode. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
* A bit-field struct that encapsulates a BRK instruction. It can be used to generate as well as parse the instruction's opcode. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
*/
struct brk {
/**
* Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
* @param val The immediate value of the instruction
*/
brk(uint16_t val) {
brk(u16 val) {
start = 0x0; // First 5 bits of an BRK instruction are 0
value = val;
end = 0x6A1; // Last 11 bits of an BRK instruction stored as uint16_t
end = 0x6A1; // Last 11 bits of an BRK instruction stored as u16
}
/**
@ -70,13 +92,15 @@ namespace lightSwitch {
return (start == 0x0 && end == 0x6A1);
}
uint8_t start : 5;
uint32_t value : 16;
uint16_t end : 11;
u8 start : 5;
u32 value : 16;
u16 end : 11;
};
static_assert(sizeof(brk) == sizeof(u32));
/**
* A bitfield struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
* A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
*/
struct svc {
/**
@ -86,13 +110,15 @@ namespace lightSwitch {
return (start == 0x1 && end == 0x6A0);
}
uint8_t start : 5;
uint32_t value : 16;
uint16_t end : 11;
u8 start : 5;
u32 value : 16;
u16 end : 11;
};
static_assert(sizeof(svc) == sizeof(u32));
/**
* A bitfield struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
* A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
*/
struct mrs {
/**
@ -102,79 +128,26 @@ namespace lightSwitch {
return (end == 0xD53);
}
uint8_t dst_reg : 5;
uint32_t src_reg : 15;
uint16_t end : 12;
u8 dst_reg : 5;
u32 src_reg : 15;
u16 end : 12;
};
static_assert(sizeof(mrs) == sizeof(u32));
};
/**
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
*/
namespace regs {
enum xreg { x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 };
enum wreg { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 };
enum sreg { sp, pc, pstate };
}
namespace memory {
/**
* The Permission struct holds the permission of a particular chunk of memory
*/
struct Permission {
/**
* Initializes all values to false
*/
Permission();
/**
* @param read If memory has read permission
* @param write If memory has write permission
* @param execute If memory has execute permission
*/
Permission(bool read, bool write, bool execute);
/**
* Equality operator between two Permission objects
*/
bool operator==(const Permission &rhs) const;
/**
* Inequality operator between two Permission objects
*/
bool operator!=(const Permission &rhs) const;
/**
* @return The value of the permission struct in mmap(2) format
*/
int get() const;
bool r, w, x;
};
/**
* Memory Regions that are mapped by the kernel
*/
enum class Region {
heap, tls, text, rodata, data, bss
};
/**
* The RegionData struct holds information about a corresponding Region of memory such as address and size
*/
struct RegionData {
uint64_t address;
size_t size;
Permission perms;
int fd;
};
}
enum class xreg { x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 };
enum class wreg { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 };
enum class sreg { sp, pc, pstate };
/**
* The Settings class is used to access the parameters set in the Java component of the application
*/
class Settings {
private:
private:
struct KeyCompare {
bool operator()(char const *a, char const *b) const {
return std::strcmp(a, b) < 0;
@ -184,11 +157,11 @@ namespace lightSwitch {
std::map<char *, char *, KeyCompare> string_map; //!< A mapping from all keys to their corresponding string value
std::map<char *, bool, KeyCompare> bool_map; //!< A mapping from all keys to their corresponding boolean value
public:
public:
/**
* @param pref_xml The path to the preference XML file
*/
Settings(std::string pref_xml);
Settings(std::string &pref_xml);
/**
* @param key The key of the setting
@ -212,13 +185,13 @@ namespace lightSwitch {
* The Logger class is to generate a log of the program
*/
class Logger {
private:
private:
std::ofstream log_file; //!< An output stream to the log file
const char *level_str[4] = {"0", "1", "2", "3"}; //!< This is used to denote the LogLevel when written out to a file
static constexpr int level_syslog[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
public:
enum LogLevel { ERROR, WARN, INFO, DEBUG }; //!< The level of a particular log
/**
* @param log_path The path to the log file
@ -263,6 +236,7 @@ namespace lightSwitch {
namespace kernel {
namespace type {
class KProcess;
class KThread;
}
class OS;
@ -275,8 +249,8 @@ namespace lightSwitch {
device_state(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &this_process, std::shared_ptr<kernel::type::KThread> &this_thread, std::shared_ptr<NCE> nce, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger) : os(os), nce(nce), settings(settings), logger(logger), this_process(this_process), this_thread(this_thread) {}
kernel::OS *os; // Because OS holds the device_state struct, it's destruction will accompany that of device_state
std::shared_ptr<kernel::type::KProcess>& this_process;
std::shared_ptr<kernel::type::KThread>& this_thread;
std::shared_ptr<kernel::type::KProcess> &this_process;
std::shared_ptr<kernel::type::KThread> &this_thread;
std::shared_ptr<NCE> nce;
std::shared_ptr<Settings> settings;
std::shared_ptr<Logger> logger;

View File

@ -4,99 +4,130 @@
#include "types/KProcess.h"
namespace lightSwitch::kernel::ipc {
IpcRequest::IpcRequest(uint8_t *tls_ptr, device_state &state) : req_info((CommandStruct *) tls_ptr) {
state.logger->Write(Logger::DEBUG, "Enable handle descriptor: {0}", (bool) req_info->handle_desc);
data_offset = 8;
if (req_info->handle_desc) {
HandleDescriptor handledesc = *(HandleDescriptor *) (&tls_ptr[8]);
state.logger->Write(Logger::DEBUG, "Moving {} handles and copying {} handles (0x{:X})", uint8_t(handledesc.move_count), uint8_t(handledesc.copy_count), tls_ptr[2]);
data_offset += 4 + handledesc.copy_count * 4 + handledesc.move_count * 4;
}
if (req_info->x_no || req_info->a_no || req_info->b_no || req_info->w_no)
state.logger->Write(Logger::ERROR, "IPC - Descriptors");
// Align to 16 bytes
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16
data_ptr = &tls_ptr[data_offset + sizeof(req_info)];
state.logger->Write(Logger::DEBUG, "Type: 0x{:X}", (uint8_t) req_info->type);
state.logger->Write(Logger::DEBUG, "X descriptors: {}", (uint8_t) req_info->x_no);
state.logger->Write(Logger::DEBUG, "A descriptors: {}", (uint8_t) req_info->a_no);
state.logger->Write(Logger::DEBUG, "B descriptors: {}", (uint8_t) req_info->b_no);
state.logger->Write(Logger::DEBUG, "W descriptors: {}", (uint8_t) req_info->w_no);
state.logger->Write(Logger::DEBUG, "Raw data offset: 0x{:X}", data_offset);
state.logger->Write(Logger::DEBUG, "Raw data size: {}", (uint16_t) req_info->data_sz);
state.logger->Write(Logger::DEBUG, "Payload Command ID: {0} (0x{0:X})", *((uint32_t *) &tls_ptr[data_offset + 8]));
}
template<typename T>
T IpcRequest::GetValue() {
data_offset += sizeof(T);
return *reinterpret_cast<T *>(&data_ptr[data_offset - sizeof(T)]);
}
IpcResponse::IpcResponse() : resp_info{0} {
tls_ptr = reinterpret_cast<uint32_t *>(new uint8_t[constant::tls_ipc_size]);
}
IpcResponse::~IpcResponse() {
delete[] tls_ptr;
}
void IpcResponse::Generate(device_state &state) {
state.logger->Write(Logger::DEBUG, "Moving {} handles and copying {} handles", moved_handles.size(), copied_handles.size());
resp_info.type = 0;
data_offset = 8;
if (!moved_handles.empty() || !copied_handles.empty()) {
resp_info.handle_desc = true;
HandleDescriptor handledesc{};
handledesc.copy_count = static_cast<uint8_t>(copied_handles.size());
handledesc.move_count = static_cast<uint8_t>(moved_handles.size());
*(HandleDescriptor *) &tls_ptr[2] = handledesc;
uint64_t handle_index = 0;
for (auto& copied_handle : copied_handles) {
state.logger->Write(Logger::DEBUG, "Copying handle to 0x{:X}", 12 + handle_index * 4);
tls_ptr[3 + handle_index] = copied_handle;
handle_index += 1;
IpcRequest::IpcRequest(bool is_domain, device_state &state) : is_domain(is_domain), state(state), tls() {
u8 *curr_ptr = tls.data();
state.this_process->ReadMemory(curr_ptr, state.this_thread->tls, constant::tls_ipc_size);
header = reinterpret_cast<CommandHeader *>(curr_ptr);
curr_ptr += sizeof(CommandHeader);
if (header->handle_desc) {
handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
curr_ptr += sizeof(HandleDescriptor) + (handle_desc->send_pid ? sizeof(u8) : 0);
for (uint index = 0; handle_desc->copy_count > index; index++) {
copy_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
curr_ptr += sizeof(handle_t);
}
for (auto& moved_handle : moved_handles) {
state.logger->Write(Logger::DEBUG, "Moving handle to 0x{:X}", 12 + handle_index * 4);
tls_ptr[3 + handle_index] = moved_handle;
handle_index += 1;
for (uint index = 0; handle_desc->move_count > index; index++) {
move_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
curr_ptr += sizeof(handle_t);
}
data_offset += 4 + copied_handles.size() * 4 + moved_handles.size() * 4;
}
state.logger->Write(Logger::DEBUG, "Data offset: 0x{:X}", data_offset);
// Align to 16 bytes
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16
for (uint index = 0; header->x_no > index; index++) {
vec_buf_x.push_back(reinterpret_cast<BufferDescriptorX *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorX);
}
for (uint index = 0; header->a_no > index; index++) {
vec_buf_a.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->b_no > index; index++) {
vec_buf_b.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->w_no > index; index++) {
vec_buf_w.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorABW);
}
auto raw_ptr = reinterpret_cast<u8 *>((((reinterpret_cast<u64>(curr_ptr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::padding_sum - 1U)) + constant::padding_sum + reinterpret_cast<u64>(tls.data())); // Align to 16 bytes relative to start of TLS
if (is_domain) {
tls_ptr[data_offset >> 2] = static_cast<uint32_t>(moved_handles.size());
data_offset += 0x10;
domain = reinterpret_cast<DomainHeaderRequest *>(raw_ptr);
payload = reinterpret_cast<PayloadHeader *>(raw_ptr + sizeof(DomainHeaderRequest));
cmd_arg_sz = domain->payload_sz - sizeof(PayloadHeader);
} else {
payload = reinterpret_cast<PayloadHeader *>(raw_ptr);
cmd_arg_sz = (header->raw_sz * sizeof(u32)) - (constant::padding_sum + sizeof(PayloadHeader));
}
if (payload->magic != constant::sfci_magic) throw exception(fmt::format("Unexpected Magic in PayloadHeader: 0x{:X}", reinterpret_cast<u32>(payload->magic)));
cmd_arg = reinterpret_cast<u8 *>(payload) + sizeof(PayloadHeader);
curr_ptr += header->raw_sz * sizeof(u32);
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
vec_buf_c.push_back(reinterpret_cast<BufferDescriptorC *>(curr_ptr));
} 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
vec_buf_c.push_back(reinterpret_cast<BufferDescriptorC *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorC);
}
}
data_offset >>= 2;
// TODO: Calculate data size
resp_info.data_sz = static_cast<uint16_t>(16 + (is_domain ? 4 : 0)); // + data_words;
tls_ptr[data_offset] = constant::ipc_sfco;
tls_ptr[data_offset + 2] = error_code;
}
void IpcResponse::SetError(uint32_t _error_code) { error_code = _error_code; }
template<typename T>
void IpcResponse::WriteValue() {
IpcResponse::IpcResponse(bool is_domain, device_state &state) : is_domain(is_domain), state(state) {}
void IpcResponse::WriteTls() {
std::array<u8, constant::tls_ipc_size> tls{};
u8 *curr_ptr = tls.data();
auto header = reinterpret_cast<CommandHeader *>(curr_ptr);
header->x_no = static_cast<u8>(vec_buf_x.size());
header->a_no = static_cast<u8>(vec_buf_a.size());
header->b_no = static_cast<u8>(vec_buf_b.size());
header->w_no = static_cast<u8>(vec_buf_w.size());
header->raw_sz = static_cast<u32>((sizeof(PayloadHeader) + arg_vec.size() + constant::padding_sum + (is_domain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo
if (!vec_buf_c.empty())
header->c_flag = (vec_buf_c.size() == 1) ? static_cast<u8>(BufferCFlag::SingleDescriptor) : static_cast<u8>(vec_buf_c.size() + static_cast<u8>(BufferCFlag::SingleDescriptor));
header->handle_desc = (!copy_handles.empty() || !move_handles.empty());
curr_ptr += sizeof(CommandHeader);
if (header->handle_desc) {
auto handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
handle_desc->send_pid = false; // TODO: Figure this out ?
handle_desc->copy_count = static_cast<u8>(copy_handles.size());
handle_desc->move_count = static_cast<u8>(move_handles.size());
curr_ptr += sizeof(HandleDescriptor);
for (uint index = 0; handle_desc->copy_count > index; index++) {
*reinterpret_cast<handle_t *>(curr_ptr) = copy_handles[index];
curr_ptr += sizeof(handle_t);
}
for (uint index = 0; handle_desc->move_count > index; index++) {
*reinterpret_cast<handle_t *>(curr_ptr) = move_handles[index];
curr_ptr += sizeof(handle_t);
}
}
for (uint index = 0; header->x_no > index; index++) {
*reinterpret_cast<BufferDescriptorX *>(curr_ptr) = vec_buf_x[index];
curr_ptr += sizeof(BufferDescriptorX);
}
for (uint index = 0; header->a_no > index; index++) {
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_a[index];
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->b_no > index; index++) {
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_b[index];
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->w_no > index; index++) {
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_w[index];
curr_ptr += sizeof(BufferDescriptorABW);
}
u64 padding = ((((reinterpret_cast<u64>(curr_ptr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::padding_sum - 1U)) + constant::padding_sum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(curr_ptr))); // Calculate the amount of padding at the front
curr_ptr += padding;
PayloadHeader *payload;
if (is_domain) {
auto domain = reinterpret_cast<DomainHeaderResponse *>(curr_ptr);
domain->output_count = 0; // TODO: Figure this out
payload = reinterpret_cast<PayloadHeader *>(curr_ptr + sizeof(DomainHeaderResponse));
} else {
payload = reinterpret_cast<PayloadHeader *>(curr_ptr);
}
payload->magic = constant::sfco_magic;
payload->version = 1;
payload->value = error_code;
curr_ptr += sizeof(PayloadHeader);
if (!arg_vec.empty()) memcpy(curr_ptr, arg_vec.data(), arg_vec.size());
curr_ptr += arg_vec.size() + (constant::padding_sum - padding);
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
*reinterpret_cast<BufferDescriptorC *>(curr_ptr) = vec_buf_c[0];
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
for (uint index = 0; (header->c_flag - 2) > index; index++) {
*reinterpret_cast<BufferDescriptorC *>(curr_ptr) = vec_buf_c[index];
curr_ptr += sizeof(BufferDescriptorC);
}
}
}
void IpcResponse::CopyHandle(uint32_t handle) { copied_handles.push_back(handle); }
void IpcResponse::MoveHandle(uint32_t handle) { moved_handles.push_back(handle); }
}

View File

@ -2,68 +2,224 @@
#include <cstdint>
#include <vector>
#include <array>
#include "switch/common.h"
namespace lightSwitch::kernel::ipc {
struct CommandStruct {
// https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
uint16_t type : 16;
uint8_t x_no : 4;
uint8_t a_no : 4;
uint8_t b_no : 4;
uint8_t w_no : 4;
uint16_t data_sz : 10;
uint8_t c_flags : 4;
uint32_t : 17;
/**
* This bit-field structure holds the header of an IPC command.
* https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
*/
struct CommandHeader {
u16 type : 16;
u8 x_no : 4;
u8 a_no : 4;
u8 b_no : 4;
u8 w_no : 4;
u32 raw_sz : 10;
u8 c_flag : 4;
u32 : 17;
bool handle_desc : 1;
};
static_assert(sizeof(CommandHeader) == 8);
struct HandleDescriptor {
bool send_pid : 1;
uint8_t copy_count : 4;
uint8_t move_count : 4;
/**
* This reflects the value in CommandStruct::type
*/
enum class CommandType : u16 {
Invalid = 0, LegacyRequest = 1, Close = 2, LegacyControl = 3, Request = 4, Control = 5, RequestWithContext = 6, ControlWithContext = 7
};
/**
* This reflects the value in CommandStruct::c_flags
*/
enum class BufferCFlag : u8 {
None = 0, InlineDescriptor = 1, SingleDescriptor = 2
};
/**
* This bit-field structure holds the handle descriptor of a recieved IPC command.
* https://switchbrew.org/wiki/IPC_Marshalling#Handle_descriptor
*/
struct HandleDescriptor {
bool send_pid : 1;
u32 copy_count : 4;
u32 move_count : 4;
u32 : 23;
};
static_assert(sizeof(HandleDescriptor) == 4);
/**
* This bit-field structure holds the domain's header of an IPC request command.
* https://switchbrew.org/wiki/IPC_Marshalling#Domains
*/
struct DomainHeaderRequest {
u8 command : 8;
u8 input_count : 8;
u16 payload_sz : 16;
u32 object_id : 32;
u32 : 32;
u32 token : 32;
};
static_assert(sizeof(DomainHeaderRequest) == 16);
/**
* This bit-field structure holds the domain's header of an IPC response command.
* https://switchbrew.org/wiki/IPC_Marshalling#Domains
*/
struct DomainHeaderResponse {
u64 output_count : 32;
u64 : 32;
u64 : 64;
};
static_assert(sizeof(DomainHeaderResponse) == 16);
/**
* This bit-field structure holds the data payload of an IPC command.
* https://switchbrew.org/wiki/IPC_Marshalling#Data_payload
*/
struct PayloadHeader {
u32 magic : 32;
u32 version : 32;
u32 value : 32;
u32 token : 32;
};
static_assert(sizeof(PayloadHeader) == 16);
/**
* This is a buffer descriptor for X buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_X_.22Pointer.22
*/
struct BufferDescriptorX {
u16 counter_0_5 : 6;
u16 address_36_38 : 3;
u16 counter_9_11 : 3;
u16 address_32_35 : 4;
u16 size : 16;
u32 address_0_31 : 32;
BufferDescriptorX(u64 address, u16 counter, u16 size) : size(size) {
// Test: The AND mask might be the other way around
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
address_32_35 = static_cast<u16>(address & 0x78000000);
address_36_38 = static_cast<u16>(address & 0x7000000);
counter_0_5 = static_cast<u16>(address & 0x7E00);
counter_9_11 = static_cast<u16>(address & 0x38);
}
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;
}
inline u16 Counter() const {
return static_cast<u16>(counter_0_5) | static_cast<u16>(counter_9_11) << 9;
}
};
static_assert(sizeof(BufferDescriptorX) == 8);
/**
* This is a buffer descriptor for A/B/W buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_A.2FB.2FW_.22Send.22.2F.22Receive.22.2F.22Exchange.22
*/
struct BufferDescriptorABW {
u32 size_0_31 : 32;
u32 address_0_31 : 32;
u8 flags : 2;
u8 address_36_38 : 3;
u32 : 19;
u8 size_32_35 : 4;
u8 address_32_35 : 4;
BufferDescriptorABW(u64 address, u64 size) {
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
address_32_35 = static_cast<u8>(address & 0x78000000);
address_36_38 = static_cast<u8>(address & 0x7000000);
size_0_31 = static_cast<u32>(size & 0x7FFFFFFF80000000);
size_32_35 = static_cast<u8>(size & 0x78000000);
}
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;
}
inline u64 Size() const {
return static_cast<u64>(size_0_31) | static_cast<u64>(size_32_35) << 32;
}
};
static_assert(sizeof(BufferDescriptorABW) == 12);
/**
* This is a buffer descriptor for C buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_C_.22ReceiveList.22
*/
struct BufferDescriptorC {
u32 address_0_31 : 32;
u16 address_32_48 : 16;
u16 size : 16;
BufferDescriptorC(u64 address, u16 size) : size(size) {
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
address_32_48 = static_cast<u16>(address & 0x7FFFC000);
}
inline u64 Address() const {
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_48) << 32;
}
};
static_assert(sizeof(BufferDescriptorC) == 8);
class IpcRequest {
private:
uint8_t *data_ptr;
uint32_t data_offset;
public:
CommandStruct *req_info;
private:
device_state &state;
IpcRequest(uint8_t *tlsPtr, device_state &state);
public:
std::array<u8, constant::tls_ipc_size> tls;
CommandHeader *header{};
HandleDescriptor *handle_desc{};
bool is_domain{};
DomainHeaderRequest *domain{};
PayloadHeader *payload{};
u8 *cmd_arg{};
u64 cmd_arg_sz{};
std::vector<handle_t> copy_handles;
std::vector<handle_t> move_handles;
std::vector<BufferDescriptorX *> vec_buf_x;
std::vector<BufferDescriptorABW *> vec_buf_a;
std::vector<BufferDescriptorABW *> vec_buf_b;
std::vector<BufferDescriptorABW *> vec_buf_w;
std::vector<BufferDescriptorC *> vec_buf_c;
template<typename T>
T GetValue();
IpcRequest(bool is_domain, device_state &state);
};
class IpcResponse {
private:
uint32_t *tls_ptr{};
uint32_t data_offset{}; // Offset to raw data relative to tls_ptr
private:
std::vector<u8> arg_vec;
device_state &state;
bool is_domain{}; // TODO
uint32_t error_code{};
public:
bool is_domain{};
u32 error_code{};
std::vector<handle_t> copy_handles;
std::vector<handle_t> move_handles;
std::vector<BufferDescriptorX> vec_buf_x;
std::vector<BufferDescriptorABW> vec_buf_a;
std::vector<BufferDescriptorABW> vec_buf_b;
std::vector<BufferDescriptorABW> vec_buf_w;
std::vector<BufferDescriptorC> vec_buf_c;
CommandStruct resp_info;
std::vector<uint32_t> copied_handles;
std::vector<uint32_t> moved_handles;
std::vector<uint8_t> data;
uint16_t data_pos{}; // Position in raw data relative to data_offset
public:
IpcResponse();
~IpcResponse();
void Generate(device_state &state);
void SetError(uint32_t _error_code);
IpcResponse(bool is_domain, device_state &state);
template<typename T>
void WriteValue(); // TODO
void WriteValue(const T &value) {
arg_vec.reserve(arg_vec.size() + sizeof(T));
auto item = reinterpret_cast<const u8 *>(&value);
for (uint index = 0; sizeof(T) > index; index++) {
arg_vec.push_back(*item);
item++;
}
}
void CopyHandle(uint32_t handle);
void MoveHandle(uint32_t handle);
void WriteTls();
};
}

View File

@ -1,6 +1,7 @@
#include "service.h"
namespace lightSwitch::kernel::service {
Service::Service() {
}

View File

@ -3,7 +3,7 @@
#include "../nce.h"
namespace lightSwitch::kernel::service {
class Service {
Service();
};
class Service {
Service();
};
}

View File

@ -1,8 +1,18 @@
#include "../../common.h"
#include "../ipc.h"
namespace lightSwitch::kernel::service {
class BaseService {
virtual const char* name() = 0;
BaseService() {}
protected:
device_state &state;
public:
BaseService(device_state &state) : state(state) {}
virtual const char *Name() = 0;
virtual ipc::IpcResponse HandleSyncRequest(ipc::IpcRequest &request) = 0;
};
}

View File

@ -6,21 +6,37 @@
namespace lightSwitch::kernel::svc {
void SetHeapSize(device_state &state) {
uint64_t addr = state.this_process->MapPrivate(0, state.nce->GetRegister(regs::w1), {true, true, false}, memory::Region::heap);
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(regs::x1, addr);
auto heap = state.this_process->MapPrivateRegion(0, state.nce->GetRegister(wreg::w1), {true, true, false}, Memory::Type::Heap, Memory::Region::heap);
state.nce->SetRegister(wreg::w0, constant::status::success);
state.nce->SetRegister(xreg::x1, heap->address);
state.logger->Write(Logger::DEBUG, "Heap size was set to 0x{:X}", state.nce->GetRegister(wreg::w1));
}
void QueryMemory(device_state &state) {
Memory::MemoryInfo mem_inf;
u64 addr = state.nce->GetRegister(xreg::x2);
if (state.nce->memory_map.count(addr)) {
mem_inf = state.nce->memory_map.at(addr)->GetInfo(state.this_process->main_thread);
} else if (state.this_process->memory_map.count(addr)) {
mem_inf = state.this_process->memory_map.at(addr)->GetInfo();
} else {
state.nce->SetRegister(wreg::w0, constant::status::inv_address);
return;
}
state.this_process->WriteMemory<Memory::MemoryInfo>(mem_inf, state.nce->GetRegister(xreg::x0));
state.nce->SetRegister(wreg::w0, constant::status::success);
}
void CreateThread(device_state &state) {
// TODO: Check if the values supplied by the process are actually valid & Support Core Mask potentially ?
auto thread = state.this_process->CreateThread(state.nce->GetRegister(regs::x1), state.nce->GetRegister(regs::x2), state.nce->GetRegister(regs::x3), static_cast<uint8_t>(state.nce->GetRegister(regs::w4)));
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(regs::w1, thread->handle);
auto thread = state.this_process->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);
}
void StartThread(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
if(object->type==type::KObjectType::KThread) {
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
if (object->type == type::KObjectType::KThread) {
std::static_pointer_cast<type::KThread>(object)->Start();
} else
throw exception("StartThread was called on a non-KThread object");
@ -31,103 +47,125 @@ namespace lightSwitch::kernel::svc {
}
void GetThreadPriority(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
if(object->type==type::KObjectType::KThread) {
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(regs::w1, std::static_pointer_cast<type::KThread>(object)->priority);
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
if (object->type == type::KObjectType::KThread) {
state.nce->SetRegister(wreg::w0, constant::status::success);
state.nce->SetRegister(wreg::w1, std::static_pointer_cast<type::KThread>(object)->priority);
} else
throw exception("GetThreadPriority was called on a non-KThread object");
}
void SetThreadPriority(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
if(object->type==type::KObjectType::KThread) {
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
if (object->type == type::KObjectType::KThread) {
std::static_pointer_cast<type::KThread>(object)->Start();
} else
throw exception("SetThreadPriority was called on a non-KThread object");
}
void CloseHandle(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
switch (object->type) {
case(type::KObjectType::KThread):
case (type::KObjectType::KThread):
state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid);
break;
case(type::KObjectType::KProcess):
case (type::KObjectType::KProcess):
state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->main_thread);
break;
default:
state.nce->SetRegister(wreg::w0, constant::status::inv_handle);
return;
}
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(wreg::w0, constant::status::success);
}
void ConnectToNamedPort(device_state &state) {
char port[constant::port_size]{0};
state.os->this_process->ReadMemory(port, state.nce->GetRegister(regs::x1), constant::port_size);
state.os->this_process->ReadMemory(port, state.nce->GetRegister(xreg::x1), constant::port_size);
if (std::strcmp(port, "sm:") == 0)
state.nce->SetRegister(regs::w1, constant::sm_handle);
state.nce->SetRegister(wreg::w1, constant::sm_handle);
else
throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port));
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(wreg::w0, constant::status::success);
}
void SendSyncRequest(device_state &state) {
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(regs::x0));
uint8_t tls[constant::tls_ipc_size];
state.os->this_process->ReadMemory(&tls, state.this_thread->tls, constant::tls_ipc_size);
ipc::IpcRequest request(tls, state);
state.os->IpcHandler(request);
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest Start-----------------------");
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(xreg::x0));
state.os->IpcHandler(static_cast<handle_t>(state.nce->GetRegister(xreg::x0)));
state.nce->SetRegister(wreg::w0, constant::status::success);
state.nce->SetRegister(wreg::w19, constant::status::success);
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest End-------------------------");
}
void OutputDebugString(device_state &state) {
std::string debug(state.nce->GetRegister(regs::x1), '\0');
state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(regs::x0), state.nce->GetRegister(regs::x1));
std::string debug(state.nce->GetRegister(xreg::x1), '\0');
state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(xreg::x0), state.nce->GetRegister(xreg::x1));
state.logger->Write(Logger::INFO, "svcOutputDebugString: {}", debug.c_str());
state.nce->SetRegister(regs::w0, 0);
state.nce->SetRegister(wreg::w0, 0);
}
void GetInfo(device_state &state) {
state.logger->Write(Logger::DEBUG, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3));
switch (state.nce->GetRegister(regs::w1)) {
state.logger->Write(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:
case constant::infoState::IsCurrentProcessBeingDebugged:
case constant::infoState::TitleId:
case constant::infoState::PrivilegedProcessId:
state.nce->SetRegister(regs::x1, 0);
state.nce->SetRegister(xreg::x1, 0);
break;
case constant::infoState::AliasRegionBaseAddr:
state.nce->SetRegister(xreg::x1, constant::map_addr);
break;
case constant::infoState::AliasRegionSize:
state.nce->SetRegister(xreg::x1, constant::map_size);
break;
case constant::infoState::HeapRegionBaseAddr:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address);
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address);
break;
case constant::infoState::HeapRegionSize:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).size);
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->size);
break;
case constant::infoState::TotalMemoryAvailable:
state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
break;
case constant::infoState::TotalMemoryUsage:
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz + state.nce->GetSharedSize());
break;
case constant::infoState::AddressSpaceBaseAddr:
state.nce->SetRegister(regs::x1, constant::base_addr);
state.nce->SetRegister(xreg::x1, constant::base_addr);
break;
case constant::infoState::AddressSpaceSize:
state.nce->SetRegister(regs::x1, constant::base_size);
state.nce->SetRegister(xreg::x1, constant::base_size);
break;
case constant::infoState::StackRegionBaseAddr:
state.nce->SetRegister(xreg::x1, state.this_thread->stack_top);
break;
case constant::infoState::StackRegionSize:
state.nce->SetRegister(xreg::x1, state.this_process->main_thread_stack_sz);
break;
case constant::infoState::PersonalMmHeapSize:
state.nce->SetRegister(regs::x1, constant::total_phy_mem);
state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
break;
case constant::infoState::PersonalMmHeapUsage:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address + state.this_process->main_thread_stack_sz);
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz);
break;
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
state.nce->SetRegister(regs::x1, constant::total_phy_mem); // TODO: NPDM specifies SystemResourceSize, subtract that from this
state.nce->SetRegister(xreg::x1, constant::total_phy_mem); // TODO: NPDM specifies SystemResourceSize, subtract that from this
break;
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address + state.this_process->main_thread_stack_sz); // TODO: Same as above
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz); // TODO: Same as above
break;
case constant::infoState::UserExceptionContextAddr:
state.nce->SetRegister(regs::x1, state.this_process->tls_pages[0]->Get(0));
state.nce->SetRegister(xreg::x1, state.this_process->tls_pages[0]->Get(0));
break;
default:
state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3));
state.nce->SetRegister(regs::w0, constant::status::unimpl);
state.logger->Write(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(regs::w0, constant::status::success);
state.nce->SetRegister(wreg::w0, constant::status::success);
}
void ExitProcess(device_state &state) {

View File

@ -4,116 +4,119 @@
#include "../common.h"
#include "switch/os.h"
namespace lightSwitch::constant {
namespace infoState {
namespace lightSwitch {
namespace constant::infoState {
// 1.0.0+
constexpr uint8_t AllowedCpuIdBitmask = 0x0;
constexpr uint8_t AllowedThreadPriorityMask = 0x1;
constexpr uint8_t AliasRegionBaseAddr = 0x2;
constexpr uint8_t AliasRegionSize = 0x3;
constexpr uint8_t HeapRegionBaseAddr = 0x4;
constexpr uint8_t HeapRegionSize = 0x5;
constexpr uint8_t TotalMemoryAvailable = 0x6;
constexpr uint8_t TotalMemoryUsage = 0x7;
constexpr uint8_t IsCurrentProcessBeingDebugged = 0x8;
constexpr uint8_t ResourceLimit = 0x9;
constexpr uint8_t IdleTickCount = 0xA;
constexpr uint8_t RandomEntropy = 0xB;
constexpr u8 AllowedCpuIdBitmask = 0x0;
constexpr u8 AllowedThreadPriorityMask = 0x1;
constexpr u8 AliasRegionBaseAddr = 0x2;
constexpr u8 AliasRegionSize = 0x3;
constexpr u8 HeapRegionBaseAddr = 0x4;
constexpr u8 HeapRegionSize = 0x5;
constexpr u8 TotalMemoryAvailable = 0x6;
constexpr u8 TotalMemoryUsage = 0x7;
constexpr u8 IsCurrentProcessBeingDebugged = 0x8;
constexpr u8 ResourceLimit = 0x9;
constexpr u8 IdleTickCount = 0xA;
constexpr u8 RandomEntropy = 0xB;
// 2.0.0+
constexpr uint8_t AddressSpaceBaseAddr = 0xC;
constexpr uint8_t AddressSpaceSize = 0xD;
constexpr uint8_t StackRegionBaseAddr = 0xE;
constexpr uint8_t StackRegionSize = 0xF;
constexpr u8 AddressSpaceBaseAddr = 0xC;
constexpr u8 AddressSpaceSize = 0xD;
constexpr u8 StackRegionBaseAddr = 0xE;
constexpr u8 StackRegionSize = 0xF;
// 3.0.0+
constexpr uint8_t PersonalMmHeapSize = 0x10;
constexpr uint8_t PersonalMmHeapUsage = 0x11;
constexpr uint8_t TitleId = 0x12;
constexpr u8 PersonalMmHeapSize = 0x10;
constexpr u8 PersonalMmHeapUsage = 0x11;
constexpr u8 TitleId = 0x12;
// 4.0.0+
constexpr uint8_t PrivilegedProcessId = 0x13;
constexpr u8 PrivilegedProcessId = 0x13;
// 5.0.0+
constexpr uint8_t UserExceptionContextAddr = 0x14;
constexpr u8 UserExceptionContextAddr = 0x14;
// 6.0.0+
constexpr uint8_t TotalMemoryAvailableWithoutMmHeap = 0x15;
constexpr uint8_t TotalMemoryUsedWithoutMmHeap = 0x16;
constexpr u8 TotalMemoryAvailableWithoutMmHeap = 0x15;
constexpr u8 TotalMemoryUsedWithoutMmHeap = 0x16;
};
namespace status {
constexpr uint32_t success = 0x0; //!< "Success"
constexpr uint32_t unimpl = 0x177202; //!< "Unimplemented behaviour"
}
};
namespace kernel::svc {
namespace structs {
namespace lightSwitch::kernel::svc {
/**
* Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
*/
void SetHeapSize(device_state &state);
}
/**
* Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
*/
void ExitProcess(device_state &state);
/**
* Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
*/
void SetHeapSize(device_state &state);
/**
* Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
*/
void CreateThread(device_state &state);
/**
* Query information about an address. Will always fetch the lowest page-aligned mapping that contains the provided address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
*/
void QueryMemory(device_state &state);
/**
* Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
*/
void StartThread(device_state &state);
/**
* Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
*/
void ExitProcess(device_state &state);
/**
* Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
*/
void ExitThread(device_state &state);
/**
* Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
*/
void CreateThread(device_state &state);
/**
* Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
*/
void GetThreadPriority(device_state &state);
/**
* Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
*/
void StartThread(device_state &state);
/**
* Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
*/
void SetThreadPriority(device_state &state);
/**
* Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
*/
void ExitThread(device_state &state);
/**
* Closes the specified handle
*/
void CloseHandle(device_state &state);
/**
* Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
*/
void GetThreadPriority(device_state &state);
/**
* Connects to a named IPC port
*/
void ConnectToNamedPort(device_state &state);
/**
* Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
*/
void SetThreadPriority(device_state &state);
/**
* Send a synchronous IPC request to a service
*/
void SendSyncRequest(device_state &state);
/**
* Closes the specified handle
*/
void CloseHandle(device_state &state);
/**
* Outputs a debug string
*/
void OutputDebugString(device_state &state);
/**
* Connects to a named IPC port
*/
void ConnectToNamedPort(device_state &state);
/**
* Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo)
*/
void GetInfo(device_state &state);
/**
* Send a synchronous IPC request to a service
*/
void SendSyncRequest(device_state &state);
/**
* The SVC Table maps all SVCs to their corresponding functions
*/
void static (*svcTable[0x80])(device_state &) = {
/**
* Outputs a debug string
*/
void OutputDebugString(device_state &state);
/**
* Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo)
*/
void GetInfo(device_state &state);
/**
* The SVC Table maps all SVCs to their corresponding functions
*/
void static (*svcTable[0x80])(device_state &) = {
nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01
nullptr, // 0x02
nullptr, // 0x03
nullptr, // 0x04
nullptr, // 0x05
nullptr, // 0x06
QueryMemory, // 0x06
ExitProcess, // 0x07
CreateThread, // 0x08
StartThread, // 0x09
@ -235,5 +238,6 @@ namespace lightSwitch::kernel::svc {
nullptr, // 0x7d
nullptr, // 0x7e
nullptr // 0x7f
};
};
}
}

View File

@ -4,12 +4,14 @@
namespace lightSwitch::kernel::type {
enum class KObjectType {
KThread, KProcess
KThread, KProcess, KSharedMemory
};
class KObject {
public:
uint32_t handle;
public:
u32 handle;
KObjectType type;
KObject(handle_t handle, KObjectType type) : handle(handle), type(type) {}
};
}

View File

@ -0,0 +1,76 @@
#include "KPrivateMemory.h"
#include "../../nce.h"
#include "../../os.h"
#include <android/sharedmem.h>
#include <fcntl.h>
#include <unistd.h>
namespace lightSwitch::kernel::type {
u64 MapPrivateFunc(u64 dst_address, u64 src_address, size_t size, u64 perms) {
dst_address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(dst_address), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((dst_address) ? MAP_FIXED : 0), -1, 0)); // NOLINT(hicpp-signed-bitwise)
if (src_address) {
memcpy(reinterpret_cast<void *>(dst_address), reinterpret_cast<const void *>(src_address), size);
mprotect(reinterpret_cast<void *>(src_address), size, PROT_NONE);
}
return dst_address;
}
KPrivateMemory::KPrivateMemory(const device_state &state, u64 dst_address, u64 src_address, size_t size, Memory::Permission permission, const Memory::Type type, pid_t owner_pid) : state(state), address(dst_address), size(size), permission(permission), type(type), owner_pid(owner_pid) {
user_pt_regs fregs = {0};
fregs.regs[0] = dst_address;
fregs.regs[1] = src_address;
fregs.regs[2] = size;
fregs.regs[3] = static_cast<u64>(permission.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, owner_pid);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping private region in child process");
if (!this->address) this->address = fregs.regs[0];
}
u64 UnmapPrivateFunc(u64 address, size_t size) {
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
}
u64 RemapPrivateFunc(u64 address, size_t old_size, size_t size) {
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void KPrivateMemory::Resize(size_t new_size) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = new_size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, owner_pid);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping private region in child process");
size = new_size;
}
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 new_perms) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(new_perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, owner_pid);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating private region's permissions in child process");
permission = new_perms;
}
Memory::MemoryInfo KPrivateMemory::GetInfo() {
Memory::MemoryInfo info{};
info.base_address = address;
info.size = size;
info.type = static_cast<u64>(type);
info.memory_attribute.IsIpcLocked = (info.ipc_ref_count > 0);
info.memory_attribute.IsDeviceShared = (info.device_ref_count > 0);
info.perms = permission;
info.ipc_ref_count = ipc_ref_count;
info.device_ref_count = device_ref_count;
return info;
}
};

View File

@ -0,0 +1,50 @@
#pragma once
#include "../../memory.h"
#include "KObject.h"
namespace lightSwitch::kernel::type {
class KPrivateMemory {
private:
const device_state &state; //!< The state of the device
public:
u64 address; //!< The address of the allocated memory
size_t size; //!< The size of the allocated memory
u16 ipc_ref_count{}; //!< The amount of reference to this memory for IPC
u16 device_ref_count{}; //!< The amount of reference to this memory for IPC
Memory::Permission permission; //!< The amount of reference to this memory for IPC
const Memory::Type type; //!< The type of this memory allocation
const pid_t owner_pid; //!< The PID of the owner process
/**
* Constructor of a private memory object
* @param dst_address The address to map to (If NULL then an arbitrary address is picked)
* @param src_address The address to map from (If NULL then no copy is performed)
* @param size The size of the allocation
* @param permission The permissions for the memory
* @param owner_pid The PID of the owner process
*/
KPrivateMemory(const device_state &state, u64 dst_address, u64 src_address, size_t size, Memory::Permission permission, const Memory::Type type, const pid_t owner_pid);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param address The address of the mapped memory
* @param old_size The current size of the memory
* @param size The new size of the memory
*/
void Resize(size_t new_size);
/**
* Updates the permissions of a chunk of mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermission(Memory::Permission new_perms);
/**
* @param pid The PID of the requesting process
* @return A Memory::MemoryInfo struct based on attributes of the memory
*/
Memory::MemoryInfo GetInfo();
};
}

View File

@ -5,17 +5,17 @@
#include <utility>
namespace lightSwitch::kernel::type {
KProcess::tls_page_t::tls_page_t(uint64_t address) : address(address) {}
KProcess::tls_page_t::tls_page_t(u64 address) : address(address) {}
uint64_t KProcess::tls_page_t::ReserveSlot() {
u64 KProcess::tls_page_t::ReserveSlot() {
if (Full())
throw exception("Trying to get TLS slot from full page");
slot[index] = true;
return Get(index++); // ++ on right will cause increment after evaluation of expression
}
uint64_t KProcess::tls_page_t::Get(uint8_t slot_no) {
if(slot_no>=constant::tls_slots)
u64 KProcess::tls_page_t::Get(u8 slot_no) {
if (slot_no >= constant::tls_slots)
throw exception("TLS slot is out of range");
return address + (constant::tls_slot_size * slot_no);
}
@ -24,26 +24,31 @@ namespace lightSwitch::kernel::type {
return slot[constant::tls_slots - 1];
}
uint64_t KProcess::GetTLSSlot(bool init) {
u64 KProcess::GetTLSSlot(bool init) {
if (!init)
for (auto &tls_page: tls_pages) {
if (!tls_page->Full())
return tls_page->ReserveSlot();
}
uint64_t address = MapPrivate(0, PAGE_SIZE, {true, true, false}, memory::Region::tls);
tls_pages.push_back(std::make_shared<tls_page_t>(address));
auto tls_mem = std::make_shared<KPrivateMemory>(KPrivateMemory(state, 0, 0, PAGE_SIZE, {true, true, false}, Memory::Type::ThreadLocal, main_thread));
memory_map[tls_mem->address] = tls_mem;
tls_pages.push_back(std::make_shared<tls_page_t>(tls_mem->address));
auto &tls_page = tls_pages.back();
if (init)
tls_page->ReserveSlot(); // User-mode exception handling
return tls_page->ReserveSlot();
}
KProcess::KProcess(pid_t pid, uint64_t entry_point, uint64_t stack_base, uint64_t stack_size, const device_state &state, handle_t handle) : state(state), handle(handle), main_thread_stack_sz(stack_size), KObject(handle, KObjectType::KProcess) {
KProcess::KProcess(pid_t pid, u64 entry_point, u64 stack_base, u64 stack_size, const device_state &state, handle_t handle) : state(state), handle(handle), main_thread_stack_sz(stack_size), KObject(handle, KObjectType::KProcess) {
process_state = process_state_t::Created;
main_thread = pid;
state.nce->WaitRdy(pid);
thread_map[main_thread] = std::make_shared<KThread>(handle_index, pid, entry_point, 0, stack_base + stack_size, GetTLSSlot(true), constant::default_priority, this, state);
NewHandle(std::static_pointer_cast<KObject>(thread_map[main_thread]));
MapPrivateRegion(0, constant::def_heap_size, {true, true, true}, Memory::Type::Heap, Memory::Region::heap);
for (auto &region : state.nce->memory_map) {
region.second->InitiateProcess(pid);
}
mem_fd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
if (mem_fd == -1) throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem", pid));
}
@ -61,128 +66,36 @@ namespace lightSwitch::kernel::type {
return 0;
}
uint64_t CreateThreadFunc(uint64_t stack_top) {
u64 CreateThreadFunc(u64 stack_top) {
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stack_top), CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM, nullptr); // NOLINT(hicpp-signed-bitwise)
return static_cast<uint64_t>(pid);
return static_cast<u64>(pid);
}
std::shared_ptr<KThread> KProcess::CreateThread(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint8_t priority) {
std::shared_ptr<KThread> KProcess::CreateThread(u64 entry_point, u64 entry_arg, u64 stack_top, u8 priority) {
user_pt_regs fregs = {0};
fregs.regs[0] = entry_point;
fregs.regs[1] = stack_top;
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, main_thread);
if (fregs.regs[0]==-1)
if (fregs.regs[0] == -1)
throw exception(fmt::format("Cannot create thread: Address: {}, Stack Top: {}", entry_point, stack_top));
auto thread = std::make_shared<kernel::type::KThread>(handle_index, static_cast<pid_t>(fregs.regs[0]), entry_point, entry_arg, stack_top, GetTLSSlot(false), priority, this, state);
NewHandle(std::static_pointer_cast<KObject>(thread));
return thread;
}
template<typename T>
T KProcess::ReadMemory(uint64_t address) const {
T item{};
ReadMemory(&item, address, sizeof(T));
return item;
}
template<typename T>
void KProcess::WriteMemory(T &item, uint64_t address) const {
WriteMemory(&item, address, sizeof(T));
}
void KProcess::ReadMemory(void *destination, uint64_t offset, size_t size) const {
state.logger->Write(Logger::DEBUG, "ReadMemory for DE: 0x{:X}, OF: 0x{:X}, SZ: 0x{:X}", (uint64_t) destination, offset, size);
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
pread64(mem_fd, destination, size, offset);
}
void KProcess::WriteMemory(void *source, uint64_t offset, size_t size) const {
state.logger->Write(Logger::DEBUG, "WriteMemory for SRC: 0x{:X}, OF: 0x{:X}, SZ: 0x{:X}", (uint64_t) source, offset, size);
void KProcess::WriteMemory(void *source, u64 offset, size_t size) const {
pwrite64(mem_fd, source, size, offset);
}
uint64_t MapPrivateFunc(uint64_t address, size_t size, uint64_t perms) {
int flags = MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise)
if (address) flags |= MAP_FIXED; // NOLINT(hicpp-signed-bitwise)
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), flags, -1, 0));
}
uint64_t KProcess::MapPrivate(uint64_t address, size_t size, const memory::Permission perms) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping private region");
state.logger->Write(Logger::DEBUG, "MapPrivate for ADR: 0x{:X}, SZ: 0x{:X}", fregs.regs[0], size);
return fregs.regs[0];
}
uint64_t KProcess::MapPrivate(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
uint64_t addr = MapPrivate(address,size, perms);
memory_map.insert(std::pair<memory::Region, memory::RegionData>(region, {addr, size, perms}));
return addr;
}
uint64_t RemapPrivateFunc(uint64_t address, size_t old_size, size_t size) {
return (uint64_t) mremap(reinterpret_cast<void *>(address), old_size, size, 0);
}
void KProcess::RemapPrivate(uint64_t address, size_t old_size, size_t size) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = old_size;
fregs.regs[2] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping private region");
}
void KProcess::RemapPrivate(const memory::Region region, size_t size) {
memory::RegionData region_data = memory_map.at(region);
RemapPrivate(region_data.address, region_data.size, size);
region_data.size = size;
}
int UpdatePermissionPrivateFunc(uint64_t address, size_t size, uint64_t perms) {
return mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms));
}
void KProcess::UpdatePermissionPrivate(uint64_t address, size_t size, const memory::Permission perms) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating private region's permissions");
}
void KProcess::UpdatePermissionPrivate(const memory::Region region, const memory::Permission perms) {
memory::RegionData region_data = memory_map.at(region);
if (region_data.perms != perms) {
UpdatePermissionPrivate(region_data.address, region_data.size, perms);
region_data.perms = perms;
}
}
uint64_t UnmapPrivateFunc(uint64_t address, size_t size) {
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
}
void KProcess::UnmapPrivate(uint64_t address, size_t size) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while unmapping private region");
}
void KProcess::UnmapPrivate(const memory::Region region) {
memory::RegionData region_data = memory_map.at(region);
UnmapPrivate(region_data.address, region_data.size);
memory_map.erase(region);
std::shared_ptr<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region) {
auto item = std::make_shared<KPrivateMemory>(state, address, 0, size, perms, type, main_thread);
memory_map[item->address] = item;
memory_region_map[region] = item;
return item;
}
handle_t KProcess::NewHandle(std::shared_ptr<KObject> obj) {

View File

@ -1,13 +1,14 @@
#pragma once
#include "KThread.h"
#include "KPrivateMemory.h"
namespace lightSwitch::kernel::type {
/**
* The KProcess class is responsible for holding the state of a process
*/
class KProcess : public KObject {
private:
private:
/**
* tls_page_t holds the status of a single TLS page (A page is 4096 bytes on ARMv8).
* Each TLS page has 8 slots, each 0x200 (512) bytes in size.
@ -15,46 +16,51 @@ namespace lightSwitch::kernel::type {
* Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage
*/
struct tls_page_t {
uint64_t address; //!< The address of the page allocated for TLS
uint8_t index = 0; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved
u64 address; //!< The address of the page allocated for TLS
u8 index = 0; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved
bool slot[constant::tls_slots]{0}; //!< An array of booleans denoting which TLS slots are reserved
/**
* @param address The address of the allocated page
*/
tls_page_t(uint64_t address);
tls_page_t(u64 address);
/**
* Reserves a single 0x200 byte TLS slot
* @return The address of the reserved slot
*/
uint64_t ReserveSlot();
u64 ReserveSlot();
/**
* Returns the address of a particular slot
* @param slot_no The number of the slot to be returned
* @return The address of the specified slot
*/
uint64_t Get(uint8_t slot_no);
u64 Get(u8 slot_no);
/**
* @return If the whole page is full or not
*/
bool Full();
};
/**
* @param init If this initializes the first page (As the first TLS slot is reserved)
* @return The address of a free TLS slot
*/
uint64_t GetTLSSlot(bool init);
u64 GetTLSSlot(bool init);
int mem_fd; //!< The file descriptor to the memory of the process
const device_state& state; //!< The state of the device
const device_state &state; //!< The state of the device
public:
public:
enum class process_state_t { Created, CreatedAttached, Started, Crashed, StartedAttached, Exiting, Exited, DebugSuspended } process_state; //!< The state of the process
handle_t handle; //!< The handle of the current process in it's parent process's handle table (Will be 0 if this is the main process)
handle_t handle_index = constant::base_handle_index; //!< This is used to keep track of what to map as an handle
pid_t main_thread; //!< The PID of the main thread
size_t main_thread_stack_sz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
std::map<memory::Region, memory::RegionData> memory_map; //!< A mapping from every memory::Region to it's corresponding memory::RegionData which holds it's address and size
std::map<u64, std::shared_ptr<KPrivateMemory>> memory_map; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory
std::map<Memory::Region, std::shared_ptr<KPrivateMemory>> memory_region_map; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
std::map<handle_t, std::shared_ptr<KObject>> handle_table; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::map<pid_t, std::shared_ptr<KThread>> thread_map; //!< A mapping from a PID to it's corresponding KThread object
std::vector<std::shared_ptr<tls_page_t>> tls_pages; //!< A vector of all allocated TLS pages
@ -68,7 +74,7 @@ namespace lightSwitch::kernel::type {
* @param state The state of the device
* @param handle A handle to the process, this isn't used if the kernel creates the process
*/
KProcess(pid_t pid, uint64_t entry_point, uint64_t stack_base, uint64_t stack_size, const device_state& state, handle_t handle=0);
KProcess(pid_t pid, u64 entry_point, u64 stack_base, u64 stack_size, const device_state &state, handle_t handle = 0);
/**
* Close the file descriptor to the process's memory
@ -83,7 +89,7 @@ namespace lightSwitch::kernel::type {
* @param priority The priority of the thread
* @return An instance of KThread class for the corresponding thread
*/
std::shared_ptr<KThread> CreateThread(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint8_t priority);
std::shared_ptr<KThread> CreateThread(u64 entry_point, u64 entry_arg, u64 stack_top, u8 priority);
/**
* Returns an object of type T from process memory
@ -91,8 +97,12 @@ namespace lightSwitch::kernel::type {
* @param address The address of the object
* @return An object of type T with read data
*/
template <typename T>
T ReadMemory(uint64_t address) const;
template<typename T>
T ReadMemory(u64 address) const {
T item{};
ReadMemory(&item, address, sizeof(T));
return item;
}
/**
* Writes an object of type T to process memory
@ -101,7 +111,9 @@ namespace lightSwitch::kernel::type {
* @param address The address of the object
*/
template<typename T>
void WriteMemory(T &item, uint64_t address) const;
void WriteMemory(T &item, u64 address) const {
WriteMemory(&item, address, sizeof(T));
}
/**
* Read a piece of process memory
@ -109,7 +121,7 @@ namespace lightSwitch::kernel::type {
* @param offset The address to read from in process memory
* @param size The amount of memory to be read
*/
void ReadMemory(void *destination, uint64_t offset, size_t size) const;
void ReadMemory(void *destination, u64 offset, size_t size) const;
/**
* Write a piece of process memory
@ -117,69 +129,18 @@ namespace lightSwitch::kernel::type {
* @param offset The address to write to in process memory
* @param size The amount of memory to be written
*/
void WriteMemory(void *source, uint64_t offset, size_t size) const;
/**
* Map a chunk of process local memory (private memory)
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapPrivate(uint64_t address, size_t size, const memory::Permission perms);
void WriteMemory(void *source, u64 offset, size_t size) const;
/**
* Map a chunk of process local memory (private memory)
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @param type The type of the memory
* @param region The specific region this memory is mapped for
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapPrivate(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param address The address of the mapped memory
* @param old_size The current size of the memory
* @param size The new size of the memory
*/
void RemapPrivate(uint64_t address, size_t old_size, size_t size);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param region The region of memory that was mapped
* @param size The new size of the memory
*/
void RemapPrivate(const memory::Region region, size_t size);
/**
* Updates the permissions of a chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionPrivate(uint64_t address, size_t size, const memory::Permission perms);
/**
* Updates the permissions of a chunk of mapped memory
* @param region The region of memory that was mapped
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionPrivate(const memory::Region region, const memory::Permission perms);
/**
* Unmap a particular chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
*/
void UnmapPrivate(uint64_t address, size_t size);
/**
* Unmap a particular chunk of mapped memory
* @param region The region of mapped memory
*/
void UnmapPrivate(const memory::Region region);
std::shared_ptr<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region);
/**
* Creates a new handle to a KObject and adds it to the process handle_table

View File

@ -0,0 +1,119 @@
#include "KSharedMemory.h"
#include "../../nce.h"
#include "../../os.h"
#include <android/sharedmem.h>
#include <fcntl.h>
#include <unistd.h>
namespace lightSwitch::kernel::type {
u64 MapFunc(u64 address, size_t size, u64 perms, u64 fd) {
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
}
KSharedMemory::KSharedMemory(const device_state &state, size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, Memory::Type type, handle_t handle, pid_t owner_pid) : state(state), size(size), local_permission(local_permission), remote_permission(remote_permission), type(type), owner_pid(owner_pid), KObject(handle, KObjectType::KSharedMemory) {
fd = ASharedMemory_create("", size);
}
void KSharedMemory::Map(u64 address) {
this->address = address;
for (auto process : state.os->process_vec) {
user_pt_regs fregs = {0};
fregs.regs[0] = this->address;
fregs.regs[1] = size;
if (process == owner_pid)
fregs.regs[2] = static_cast<u64 >(local_permission.get());
else
fregs.regs[2] = static_cast<u64>(remote_permission.get());
fregs.regs[3] = static_cast<u64>(fd);
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapFunc), fregs, process);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping shared region in child process");
if (!this->address) this->address = fregs.regs[0];
}
this->address = MapFunc(this->address, size, static_cast<u64>(owner_pid ? remote_permission.get() : local_permission.get()), static_cast<u64>(fd));
if (this->address == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise)
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
}
u64 UnmapFunc(u64 address, size_t size) {
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
}
KSharedMemory::~KSharedMemory() {
for (auto process : state.os->process_vec) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapFunc), fregs, process);
}
UnmapFunc(address, size);
close(fd);
}
u64 RemapFunc(u64 address, size_t old_size, size_t size) {
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void KSharedMemory::Resize(size_t new_size) {
for (auto process : state.os->process_vec) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = new_size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapFunc), fregs, process);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping shared region in child process");
}
if (RemapFunc(address, size, new_size) == reinterpret_cast<u64>(MAP_FAILED))
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
size = new_size;
}
u64 UpdatePermissionFunc(u64 address, size_t size, u64 perms) {
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
}
void KSharedMemory::UpdatePermission(bool local, Memory::Permission new_perms) {
for (auto process : state.os->process_vec) {
if ((local && process == owner_pid) || (!local && process != owner_pid)) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(new_perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionFunc), fregs, process);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating shared region's permissions in child process");
}
}
if ((local && owner_pid == 0) || (!local && owner_pid != 0))
if (mprotect(reinterpret_cast<void *>(address), size, new_perms.get()) == -1)
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
if (local)
local_permission = new_perms;
else
remote_permission = new_perms;
}
void KSharedMemory::InitiateProcess(pid_t pid) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(remote_permission.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionFunc), fregs, pid);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while setting shared region's permissions in child process");
}
Memory::MemoryInfo KSharedMemory::GetInfo(pid_t pid) {
Memory::MemoryInfo info{};
info.base_address = address;
info.size = size;
info.type = static_cast<u64>(type);
info.memory_attribute.IsIpcLocked = (info.ipc_ref_count > 0);
info.memory_attribute.IsDeviceShared = (info.device_ref_count > 0);
info.perms = (pid == owner_pid) ? local_permission : remote_permission;
info.ipc_ref_count = ipc_ref_count;
info.device_ref_count = device_ref_count;
return info;
}
};

View File

@ -0,0 +1,67 @@
#pragma once
#include "../../memory.h"
#include "KObject.h"
namespace lightSwitch::kernel::type {
class KSharedMemory : public KObject {
private:
const device_state &state; //!< The state of the device
int fd; //!< A file descriptor to the underlying shared memory
public:
u64 address; //!< The address of the allocated memory
size_t size; //!< The size of the allocated memory
u16 ipc_ref_count{}; //!< The amount of reference to this memory for IPC
u16 device_ref_count{}; //!< The amount of reference to this memory for IPC
Memory::Permission local_permission; //!< The amount of reference to this memory for IPC
Memory::Permission remote_permission; //!< The permission of any process except the owner process
Memory::Type type; //!< The type of this memory allocation
pid_t owner_pid; //!< The PID of the owner process, 0 means memory is owned by kernel process
/**
* Constructor of a shared memory object
* @param size The size of the allocation
* @param local_permission The permission of the owner process
* @param remote_permission The permission of any process except the owner process
* @param owner_pid The PID of the owner process, 0 means memory is owned by kernel process
*/
KSharedMemory(const device_state &state, size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, Memory::Type type, handle_t handle, pid_t owner_pid = 0);
/**
* Maps the shared memory at an address
* @param address The address to map to (If NULL an arbitrary address is picked)
*/
void Map(u64 address);
/**
* Destructor of shared memory, it deallocates the memory from all processes
*/
~KSharedMemory();
/**
* Resize a chunk of memory as to change the size occupied by it
* @param new_size The new size of the memory
*/
void Resize(size_t new_size);
/**
* Updates the permissions of a chunk of mapped memory
* @param local If true change local permissions else change remote permissions
* @param perms The new permissions to be set for the memory
*/
void UpdatePermission(bool local, Memory::Permission new_perms);
/**
* Initiates the instance of shared memory in a particular process
* @param pid The PID of the process
*/
void InitiateProcess(pid_t pid);
/**
* @param pid The PID of the requesting process
* @return A Memory::MemoryInfo struct based on attributes of the memory
*/
Memory::MemoryInfo GetInfo(pid_t pid);
};
}

View File

@ -4,7 +4,7 @@
#include "../../nce.h"
namespace lightSwitch::kernel::type {
KThread::KThread(handle_t handle, pid_t pid, uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint64_t tls, uint8_t priority, KProcess* parent, const device_state &state) : handle(handle), pid(pid), entry_point(entry_point), entry_arg(entry_arg), stack_top(stack_top), tls(tls), priority(priority), parent(parent), state(state), KObject(handle, KObjectType::KThread) {
KThread::KThread(handle_t handle, pid_t pid, u64 entry_point, u64 entry_arg, u64 stack_top, u64 tls, u8 priority, KProcess *parent, const device_state &state) : handle(handle), pid(pid), entry_point(entry_point), entry_arg(entry_arg), stack_top(stack_top), tls(tls), priority(priority), parent(parent), state(state), KObject(handle, KObjectType::KThread) {
UpdatePriority(priority);
}
@ -13,14 +13,14 @@ namespace lightSwitch::kernel::type {
}
void KThread::Start() {
if(pid==parent->main_thread) parent->process_state = KProcess::process_state_t::Started;
if (pid == parent->main_thread) parent->process_state = KProcess::process_state_t::Started;
state.nce->StartProcess(entry_point, entry_arg, stack_top, handle, pid);
}
void KThread::UpdatePriority(uint8_t priority) {
void KThread::UpdatePriority(u8 priority) {
this->priority = priority;
auto li_priority = static_cast<int8_t>(constant::priority_an.first + ((static_cast<float>(constant::priority_an.second - constant::priority_an.first) / static_cast<float>(constant::priority_nin.second - constant::priority_nin.first)) * (static_cast<float>(priority) - constant::priority_nin.first))); // Remap range priority_nin (Nintendo Priority) to priority_an (Android Priority)
if(setpriority(PRIO_PROCESS, static_cast<id_t>(pid), li_priority) == -1)
auto li_priority = static_cast<int8_t>(constant::priority_an.first + ((static_cast<float>(constant::priority_an.second - constant::priority_an.first) / static_cast<float>(constant::priority_nin.second - constant::priority_nin.first)) * (static_cast<float>(priority) - constant::priority_nin.first))); // Resize range priority_nin (Nintendo Priority) to priority_an (Android Priority)
if (setpriority(PRIO_PROCESS, static_cast<id_t>(pid), li_priority) == -1)
throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", li_priority, pid));
}
}

View File

@ -7,18 +7,18 @@ namespace lightSwitch::kernel::type {
* KThread class is responsible for holding the state of a thread
*/
class KThread : public KObject {
private:
private:
KProcess *parent; //!< The parent process of this thread
const device_state& state; //!< The state of the device
uint64_t entry_point; //!< The address to start execution at
uint64_t entry_arg; //!< An argument to pass to the process on entry
const device_state &state; //!< The state of the device
u64 entry_point; //!< The address to start execution at
u64 entry_arg; //!< An argument to pass to the process on entry
public:
public:
handle_t handle; //!< The handle of the current thread in it's parent process's handle table
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
uint64_t stack_top; //!< The top of the stack (Where it starts growing downwards from)
uint64_t tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
uint8_t priority; //!< Hold the priority of a thread in Nintendo format
u64 stack_top; //!< The top of the stack (Where it starts growing downwards from)
u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
u8 priority; //!< Hold the priority of a thread in Nintendo format
/**
* @param handle The handle of the current thread
@ -31,7 +31,7 @@ namespace lightSwitch::kernel::type {
* @param parent The parent process of this thread
* @param arg An optional argument to pass to the process
*/
KThread(handle_t handle, pid_t pid, uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint64_t tls, uint8_t priority, KProcess *parent, const device_state &state);
KThread(handle_t handle, pid_t pid, u64 entry_point, u64 entry_arg, u64 stack_top, u64 tls, u8 priority, KProcess *parent, const device_state &state);
/**
* Kills the thread and deallocates the memory allocated for stack.
@ -48,6 +48,6 @@ namespace lightSwitch::kernel::type {
* We rescale the priority from Nintendo scale to that of Android.
* @param priority The priority of the thread in Nintendo format
*/
void UpdatePriority(uint8_t priority);
void UpdatePriority(u8 priority);
};
}

View File

@ -5,7 +5,7 @@
namespace lightSwitch::loader {
class Loader {
protected:
protected:
std::string file_path; //!< The path to the ROM file
std::ifstream file; //!< An input stream from the file
@ -17,13 +17,13 @@ namespace lightSwitch::loader {
* @param size The amount to read in bytes
*/
template<typename T>
void ReadOffset(T *output, uint32_t offset, size_t size) {
void ReadOffset(T *output, u32 offset, size_t size) {
file.seekg(offset, std::ios_base::beg);
file.read(reinterpret_cast<char *>(output), size);
}
public:
public:
/**
* @param file_path_ The path to the ROM file
*/

View File

@ -4,42 +4,40 @@
namespace lightSwitch::loader {
NroLoader::NroLoader(std::string file_path, const device_state &state) : Loader(file_path) {
NroHeader header{};
ReadOffset((uint32_t *) &header, 0x0, sizeof(NroHeader));
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
if (header.magic != constant::nro_magic)
throw exception(fmt::format("Invalid NRO magic! 0x{0:X}", header.magic));
state.nce->MapShared(constant::base_addr, header.text.size, {true, true, true}, memory::Region::text); // RWX
state.nce->MapSharedRegion(constant::base_addr, header.text.size, {true, true, true}, {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::base_addr, header.text.size);
state.nce->MapShared(constant::base_addr + header.text.size, header.ro.size, {true, true, false}, memory::Region::rodata); // RW- but should be R--
auto rodata = state.nce->MapSharedRegion(constant::base_addr + header.text.size, header.ro.size, {true, true, false}, {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::base_addr + header.text.size, header.ro.size);
state.nce->MapShared(constant::base_addr + header.text.size + header.ro.size, header.data.size, {true, true, false}, memory::Region::data); // RW-
state.nce->MapSharedRegion(constant::base_addr + header.text.size + header.ro.size, header.data.size, {true, true, false}, {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::base_addr + header.text.size + header.ro.size, header.data.size);
state.nce->MapShared(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, {true, true, false}, memory::Region::bss); // RW-
state.nce->MapSharedRegion(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, {true, true, true}, {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::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize);
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr), header.text.offset, header.text.size);
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr + header.text.size), header.ro.offset, header.ro.size);
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr + header.text.size + header.ro.size), header.data.offset, header.data.size);
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr), header.text.offset, header.text.size);
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr + header.text.size), header.ro.offset, header.ro.size);
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr + header.text.size + header.ro.size), header.data.offset, header.data.size);
// Make .ro read-only after writing to it
state.nce->UpdatePermissionShared(memory::Region::rodata, {true, false, false});
// Replace SVC & MRS with BRK
auto address = (uint32_t *) constant::base_addr + header.text.offset;
size_t text_size = header.text.size / sizeof(uint32_t);
auto address = (u32 *) constant::base_addr + header.text.offset;
size_t text_size = header.text.size / sizeof(u32);
for (size_t iter = 0; iter < text_size; iter++) {
auto instr_svc = reinterpret_cast<instr::svc *>(address + iter);
auto instr_mrs = reinterpret_cast<instr::mrs *>(address + iter);
if (instr_svc->verify()) {
instr::brk brk(static_cast<uint16_t>(instr_svc->value));
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
instr::brk brk(static_cast<u16>(instr_svc->value));
address[iter] = *reinterpret_cast<u32 *>(&brk);
} else if (instr_mrs->verify() && instr_mrs->src_reg == constant::tpidrro_el0) {
instr::brk brk(static_cast<uint16_t>(constant::svc_last + 1 + instr_mrs->dst_reg));
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
instr::brk brk(static_cast<u16>(constant::svc_last + 1 + instr_mrs->dst_reg));
address[iter] = *reinterpret_cast<u32 *>(&brk);
}
}
}

View File

@ -5,37 +5,37 @@
namespace lightSwitch::loader {
class NroLoader : public Loader {
private:
private:
struct NroSegmentHeader {
uint32_t offset;
uint32_t size;
u32 offset;
u32 size;
}; //!< The structure of a single Segment descriptor in the NRO's header
struct NroHeader {
uint32_t : 32;
uint32_t mod_offset;
uint64_t : 64;
u32 : 32;
u32 mod_offset;
u64 : 64;
uint32_t magic;
uint32_t version;
uint32_t size;
uint32_t flags;
u32 magic;
u32 version;
u32 size;
u32 flags;
NroSegmentHeader text;
NroSegmentHeader ro;
NroSegmentHeader data;
uint32_t bssSize;
uint32_t : 32;
uint64_t build_id[4];
uint64_t : 64;
u32 bssSize;
u32 : 32;
u64 build_id[4];
u64 : 64;
NroSegmentHeader api_info;
NroSegmentHeader dynstr;
NroSegmentHeader dynsym;
}; //!< A bit-field struct to read the header of an NRO directly
public:
public:
/**
* @param file_path The path to the ROM file
* @param state The state of the device

View File

@ -0,0 +1,110 @@
#pragma once
#include "common.h"
namespace lightSwitch::Memory {
/**
* The Permission struct holds the permission of a particular chunk of memory
*/
struct Permission {
/**
* Initializes all values to false
*/
Permission() {
r = 0;
w = 0;
x = 0;
};
/**
* @param read If memory has read permission
* @param write If memory has write permission
* @param execute If memory has execute permission
*/
Permission(bool read, bool write, bool execute) {
r = read;
w = write;
x = execute;
};
/**
* Equality operator between two Permission objects
*/
bool operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); };
/**
* Inequality operator between two Permission objects
*/
bool operator!=(const Permission &rhs) const { return !operator==(rhs); };
/**
* @return The value of the permission struct in mmap(2) format
*/
int get() const {
int perm = 0;
if (r) perm |= PROT_READ;
if (w) perm |= PROT_WRITE;
if (x) perm |= PROT_EXEC;
return perm;
};
bool r : 1, w : 1, x : 1;
};
/**
* This holds certain attributes of a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryAttribute
*/
struct MemoryAttribute {
bool IsBorrowed : 1;
bool IsIpcLocked : 1;
bool IsDeviceShared : 1;
bool IsUncached : 1;
};
/**
* This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
*/
struct MemoryInfo {
u64 base_address : 64;
u64 size : 64;
u64 type : 64;
MemoryAttribute memory_attribute;
Permission perms;
u32 ipc_ref_count : 32;
u32 device_ref_count : 32;
u32 : 32;
};
static_assert(sizeof(MemoryInfo) == 0x28);
enum class Type : u32 {
Unmapped = 0x00000000,
Io = 0x00002001,
Normal = 0x00042002,
CodeStatic = 0x00DC7E03,
CodeMutable = 0x03FEBD04,
Heap = 0x037EBD05,
SharedMemory = 0x00402006,
Alias = 0x00482907,
ModuleCodeStatic = 0x00DD7E08,
ModuleCodeMutable = 0x03FFBD09,
Ipc = 0x005C3C0A,
Stack = 0x005C3C0B,
ThreadLocal = 0x0040200C,
TransferMemoryIsolated = 0x015C3C0D,
TransferMemory = 0x005C380E,
ProcessMemory = 0x0040380F,
Reserved = 0x00000010,
NonSecureIpc = 0x005C3811,
NonDeviceIpc = 0x004C2812,
KernelStack = 0x00002013,
CodeReadOnly = 0x00402214,
CodeWritable = 0x00402015
};
/**
* Memory Regions that are mapped by the kernel
*/
enum class Region {
heap, text, rodata, data, bss
};
}

View File

@ -1,9 +1,6 @@
#include <sched.h>
#include <linux/uio.h>
#include <linux/elf.h>
#include <fcntl.h>
#include <unistd.h>
#include <android/sharedmem.h>
#include "os.h"
#include "nce.h"
@ -22,46 +19,41 @@ namespace lightSwitch {
if (status == -1) throw exception(fmt::format("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno)));
}
instr::brk NCE::ReadBrk(uint64_t address, pid_t pid) const {
instr::brk NCE::ReadBrk(u64 address, pid_t pid) const {
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : curr_pid, address, NULL);
if (status == -1) throw exception(fmt::format("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno)));
return *(reinterpret_cast<instr::brk *>(&status));
}
NCE::~NCE() {
for (auto&region : region_memory_map) {
munmap(reinterpret_cast<void *>(region.second.address), region.second.size);
};
void NCE::Initialize(const device_state &state) {
this->state = &state;
}
void NCE::Execute(const device_state &state) {
this->state = const_cast<device_state *>(&state);
int status;
while (!halt && !state.os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
void NCE::Execute() {
int status = 0;
while (!halt && !state->os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
auto& curr_regs = register_map[curr_pid];
auto &curr_regs = register_map[curr_pid];
ReadRegisters(curr_regs);
auto instr = ReadBrk(curr_regs.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::svc_last) {
state.os->SvcHandler(static_cast<uint16_t>(instr.value), curr_pid);
state->os->SvcHandler(static_cast<u16>(instr.value), curr_pid);
} else if (instr.value > constant::svc_last && instr.value <= constant::svc_last + constant::num_regs) {
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
SetRegister(regs::xreg(instr.value - (constant::svc_last + 1)), state.os->process_map.at(curr_pid)->thread_map.at(curr_pid)->tls);
state.logger->Write(Logger::DEBUG, "\"MRS X{}, TPIDRRO_EL0\" has been called", instr.value - (constant::svc_last + 1));
SetRegister(static_cast<xreg>(instr.value - (constant::svc_last + 1)), state->os->process_map.at(curr_pid)->thread_map.at(curr_pid)->tls);
state->logger->Write(Logger::DEBUG, "\"MRS X{}, TPIDRRO_EL0\" has been called", instr.value - (constant::svc_last + 1));
} else if (instr.value == constant::brk_rdy)
continue;
else
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<uint64_t>(instr.value)));
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value)));
}
curr_regs.pc += 4; // Increment program counter by a single instruction (32 bits)
WriteRegisters(curr_regs);
} else {
auto& curr_regs = register_map[curr_pid];
ReadRegisters(curr_regs);
state.logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Status: 0x{:X}, INSTR: 0x{:X}", curr_pid, status, *(uint32_t*)curr_regs.pc);
state.os->KillThread(curr_pid);
state->logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Stop Signal: {}", curr_pid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
state->os->KillThread(curr_pid);
}
ResumeProcess();
}
@ -76,9 +68,9 @@ namespace lightSwitch {
bool was_running = PauseProcess(pid);
user_pt_regs backup_regs{};
ReadRegisters(backup_regs, pid);
func_regs.pc = reinterpret_cast<uint64_t>(func);
func_regs.pc = reinterpret_cast<u64>(func);
func_regs.sp = backup_regs.sp;
func_regs.regs[regs::x30] = reinterpret_cast<uint64_t>(brk_lr); // Set LR to 'brk_lr' so the application will hit a breakpoint after the function returns [LR is where the program goes after it returns from a function]
func_regs.regs[static_cast<uint>(xreg::x30)] = reinterpret_cast<u64>(brk_lr); // Set LR to 'brk_lr' so the application will hit a breakpoint after the function returns [LR is where the program goes after it returns from a function]
WriteRegisters(func_regs, pid);
ResumeProcess(pid);
func_regs = WaitRdy(pid);
@ -99,7 +91,7 @@ namespace lightSwitch {
WriteRegisters(regs, pid);
return regs;
} else
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<uint64_t>(instr.value)));
throw exception(fmt::format("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)
}
@ -120,7 +112,7 @@ namespace lightSwitch {
if (status == -1) throw exception(fmt::format("Cannot resume process: {}, Error: {}", pid, strerror(errno)));
}
void NCE::StartProcess(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint32_t handle, pid_t pid) const {
void NCE::StartProcess(u64 entry_point, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const {
user_pt_regs regs{0};
regs.sp = stack_top;
regs.pc = entry_point;
@ -130,165 +122,59 @@ namespace lightSwitch {
ResumeProcess(pid);
}
uint64_t NCE::GetRegister(regs::xreg reg_id, pid_t pid) {
return register_map.at(pid ? pid : curr_pid).regs[reg_id];
u64 NCE::GetRegister(xreg reg_id, pid_t pid) {
return register_map.at(pid ? pid : curr_pid).regs[static_cast<uint>(reg_id)];
}
void NCE::SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid) {
register_map.at(pid ? pid : curr_pid).regs[reg_id] = value;
void NCE::SetRegister(xreg reg_id, u64 value, pid_t pid) {
register_map.at(pid ? pid : curr_pid).regs[static_cast<uint>(reg_id)] = value;
}
uint64_t NCE::GetRegister(regs::wreg reg_id, pid_t pid) {
return (reinterpret_cast<uint32_t *>(&register_map.at(pid ? pid : curr_pid).regs))[reg_id * 2];
u64 NCE::GetRegister(wreg reg_id, pid_t pid) {
return (reinterpret_cast<u32 *>(&register_map.at(pid ? pid : curr_pid).regs))[static_cast<uint>(reg_id) * 2];
}
void NCE::SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid) {
(reinterpret_cast<uint32_t *>(&register_map.at(pid ? pid : curr_pid).regs))[reg_id * 2] = value;
void NCE::SetRegister(wreg reg_id, u32 value, pid_t pid) {
(reinterpret_cast<u32 *>(&register_map.at(pid ? pid : curr_pid).regs))[static_cast<uint>(reg_id) * 2] = value;
}
uint64_t NCE::GetRegister(regs::sreg reg_id, pid_t pid) {
u64 NCE::GetRegister(sreg reg_id, pid_t pid) {
pid = pid ? pid : curr_pid;
switch (reg_id) {
case regs::pc:
case sreg::pc:
return register_map.at(pid).pc;
case regs::sp:
case sreg::sp:
return register_map.at(pid).sp;
case regs::pstate:
case sreg::pstate:
return register_map.at(pid).pstate;
default:
return 0;
}
}
void NCE::SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid) {
void NCE::SetRegister(sreg reg_id, u32 value, pid_t pid) {
pid = pid ? pid : curr_pid;
switch (reg_id) {
case regs::pc:
case sreg::pc:
register_map.at(pid).pc = value;
case regs::sp:
case sreg::sp:
register_map.at(pid).sp = value;
case regs::pstate:
case sreg::pstate:
register_map.at(pid).pstate = value;
}
}
uint64_t MapSharedFunc(uint64_t address, size_t size, uint64_t perms, uint64_t fd) {
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
std::shared_ptr<kernel::type::KSharedMemory> NCE::MapSharedRegion(const u64 address, const size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, const Memory::Type type, const Memory::Region region) {
auto item = std::make_shared<kernel::type::KSharedMemory>(*state, size, local_permission, remote_permission, type, 0, 0);
item->Map(address);
memory_map[item->address] = item;
memory_region_map[region] = item;
return item;
}
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms) {
int fd = -1;
if(state && !state->os->process_map.empty()) {
fd = ASharedMemory_create("", size);
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
fregs.regs[3] = static_cast<uint64_t>(fd);
ExecuteFunction(reinterpret_cast<void *>(MapSharedFunc), fregs, process.second->main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping shared region in child process");
address = fregs.regs[0]; // In case address was 0, this ensures all processes allocate the same address
}
size_t NCE::GetSharedSize() {
size_t shared_size = 0;
for (auto &region : memory_map) {
shared_size += region.second->size;
}
void *ptr = mmap((void *) address, size, perms.get(), MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0), fd, 0); // NOLINT(hicpp-signed-bitwise)
if (ptr == MAP_FAILED)
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
addr_memory_map[address] = {address, size, perms, fd};
return reinterpret_cast<uint64_t>(ptr);
}
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
uint64_t addr = MapShared(address,size, perms); // TODO: Change MapShared return type to RegionData
region_memory_map[region] = {addr, size, perms};
return addr;
}
uint64_t RemapSharedFunc(uint64_t address, size_t old_size, size_t size) {
return reinterpret_cast<uint64_t>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void NCE::RemapShared(uint64_t address, size_t old_size, size_t size) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = old_size;
fregs.regs[2] = size;
ExecuteFunction(reinterpret_cast<void *>(RemapSharedFunc), fregs, process.second->main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping shared region in child process");
}
}
void *ptr = mremap(reinterpret_cast<void *>(address), old_size, size, 0);
if (ptr == MAP_FAILED)
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
addr_memory_map.at(address).size = size;
}
void NCE::RemapShared(const memory::Region region, size_t size) {
memory::RegionData& region_data = region_memory_map.at(region);
RemapShared(region_data.address, region_data.size, size);
region_data.size = size;
}
uint64_t UpdatePermissionSharedFunc(uint64_t address, size_t size, uint64_t perms) {
return static_cast<uint64_t>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
}
void NCE::UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionSharedFunc), fregs, process.second->main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating shared region's permissions in child process");
}
}
if (mprotect(reinterpret_cast<void *>(address), size, perms.get()) == -1)
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
addr_memory_map.at(address).perms = perms;
}
void NCE::UpdatePermissionShared(const memory::Region region, const memory::Permission perms) {
memory::RegionData& region_data = region_memory_map.at(region);
if (region_data.perms != perms) {
UpdatePermissionShared(region_data.address, region_data.size, perms);
region_data.perms = perms;
}
}
uint64_t UnmapSharedFunc(uint64_t address, size_t size) {
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
}
void NCE::UnmapShared(uint64_t address, size_t size) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process.second->main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while unmapping shared region in child process");
}
}
int err = munmap(reinterpret_cast<void *>(address), size);
if (err == -1)
throw exception(fmt::format("An occurred while unmapping shared region: {}", strerror(errno)));
if (addr_memory_map.at(address).fd!=-1) {
if (close(addr_memory_map.at(address).fd)==-1)
throw exception(fmt::format("An occurred while closing shared file descriptor: {}", strerror(errno)));
}
}
void NCE::UnmapShared(const memory::Region region) {
memory::RegionData& region_data = region_memory_map.at(region);
UnmapShared(region_data.address, region_data.size);
region_memory_map.erase(region);
return shared_size;
}
}

View File

@ -6,49 +6,50 @@
#include <vector>
#include <unordered_map>
#include "common.h"
#include "kernel/types/KSharedMemory.h"
namespace lightSwitch {
class NCE {
private:
private:
pid_t curr_pid = 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> register_map; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
device_state *state;
const device_state *state; //!< The state of the device
/**
* Reads process registers into the `registers` variable
* @param registers A set of registers to fill with values from the process
* @param pid The PID of the process
*/
void ReadRegisters(user_pt_regs &registers, pid_t pid=0) const;
void ReadRegisters(user_pt_regs &registers, pid_t pid = 0) const;
/**
* Writes process registers from the `registers` variable
* @param registers The registers to be written by the process
* @param pid The PID of the process
*/
void WriteRegisters(user_pt_regs &registers, pid_t pid=0) const;
void WriteRegisters(user_pt_regs &registers, pid_t pid = 0) const;
/**
* @param address The address of the BRK instruction
* @param pid The PID of the process
* @return An instance of BRK with the corresponding values
*/
instr::brk ReadBrk(uint64_t address, pid_t pid=0) const;
instr::brk ReadBrk(u64 address, pid_t pid = 0) const;
public:
std::map<memory::Region, memory::RegionData> region_memory_map; //!< A mapping from every memory::Region to it's corresponding memory::RegionData which holds it's address, size and fd
std::map<uint64_t, memory::RegionData> addr_memory_map; //!< A mapping from every address to it's corresponding memory::RegionData
public:
std::map<Memory::Region, std::shared_ptr<kernel::type::KSharedMemory>> memory_region_map; //!< A mapping from every Memory::Region to a shared pointer to it's corresponding kernel::type::KSharedMemory
std::map<u64, std::shared_ptr<kernel::type::KSharedMemory>> memory_map; //!< A mapping from every address to a shared pointer to it's corresponding kernel::type::KSharedMemory
/**
* Iterates over shared_memory_vec unmapping all memory
* Initialize NCE by setting the device_state variable
* @param state The state of the device
*/
~NCE();
void Initialize(const device_state &state);
/**
* Start managing child processes
* @param state The state of the device
*/
void Execute(const device_state& state);
void Execute();
/**
* Execute any arbitrary function on a particular child process
@ -76,7 +77,7 @@ namespace lightSwitch {
* Resumes a particular process, does nothing if it was already running
* @param pid The PID of the process
*/
void ResumeProcess(pid_t pid=0) const;
void ResumeProcess(pid_t pid = 0) const;
/**
* Starts a particular process, sets the registers to their expected values and jumps to address
@ -86,7 +87,7 @@ namespace lightSwitch {
* @param handle The handle of the main thread (Set to value of 1st register)
* @param pid The PID of the process
*/
void StartProcess(uint64_t address, uint64_t entry_arg, uint64_t stack_top, uint32_t handle, pid_t pid) const;
void StartProcess(u64 address, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const;
/**
* Get the value of a Xn register
@ -94,7 +95,7 @@ namespace lightSwitch {
* @param pid The PID of the process
* @return The value of the register
*/
uint64_t GetRegister(regs::xreg reg_id, pid_t pid=0);
u64 GetRegister(xreg reg_id, pid_t pid = 0);
/**
* Set the value of a Xn register
@ -102,7 +103,7 @@ namespace lightSwitch {
* @param value The value to set
* @param pid The PID of the process
*/
void SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid=0);
void SetRegister(xreg reg_id, u64 value, pid_t pid = 0);
/**
* Get the value of a Wn register
@ -110,7 +111,7 @@ namespace lightSwitch {
* @param pid The PID of the process
* @return The value in the register
*/
uint64_t GetRegister(regs::wreg reg_id, pid_t pid=0);
u64 GetRegister(wreg reg_id, pid_t pid = 0);
/**
* Set the value of a Wn register
@ -118,7 +119,7 @@ namespace lightSwitch {
* @param value The value to set
* @param pid The PID of the process
*/
void SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid=0);
void SetRegister(wreg reg_id, u32 value, pid_t pid = 0);
/**
* Get the value of a special register
@ -126,7 +127,7 @@ namespace lightSwitch {
* @param pid The PID of the process
* @return The value in the register
*/
uint64_t GetRegister(regs::sreg reg_id, pid_t pid=0);
u64 GetRegister(sreg reg_id, pid_t pid = 0);
/**
* Set the value of a special register
@ -134,69 +135,22 @@ namespace lightSwitch {
* @param value The value to set
* @param pid The PID of the process
*/
void SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid=0);
// TODO: Shared Memory mappings don't update after child process has been created
/**
* Map a chunk of shared memory
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapShared(uint64_t address, size_t size, const memory::Permission perms);
void SetRegister(sreg reg_id, u32 value, pid_t pid = 0);
/**
* Map a chunk of shared memory
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @param type The type of the memory
* @param region The specific region this memory is mapped for
* @return The address of the mapped chunk (Use when address is 0)
* @return A shared pointer to the kernel::type::KSharedMemory object
*/
uint64_t MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region);
std::shared_ptr<kernel::type::KSharedMemory> MapSharedRegion(const u64 address, const size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, const Memory::Type type, const Memory::Region region);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param address The address of the mapped memory
* @param old_size The current size of the memory
* @param size The new size of the memory
* @return The total size of allocated shared memory
*/
void RemapShared(uint64_t address, size_t old_size, size_t size);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param region The region of memory that was mapped
* @param size The new size of the memory
*/
void RemapShared(memory::Region region, size_t size);
/**
* Updates the permissions of a chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms);
/**
* Updates the permissions of a chunk of mapped memory
* @param region The region of memory that was mapped
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionShared(memory::Region region, memory::Permission perms);
/**
* Unmap a particular chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
*/
void UnmapShared(uint64_t address, size_t size);
/**
* Unmap a particular chunk of mapped memory
* @param region The region of mapped memory
*/
void UnmapShared(const memory::Region region);
size_t GetSharedSize();
};
}

View File

@ -9,15 +9,16 @@ namespace lightSwitch::kernel {
OS::OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, this_process, this_thread, std::make_shared<NCE>(), settings, logger) {}
void OS::Execute(std::string rom_file) {
state.nce->Initialize(state);
std::string rom_ext = rom_file.substr(rom_file.find_last_of('.') + 1);
std::transform(rom_ext.begin(), rom_ext.end(), rom_ext.begin(), [](unsigned char c) { return std::tolower(c); });
if (rom_ext == "nro") loader::NroLoader loader(rom_file, state);
else throw exception("Unsupported ROM extension.");
auto main_process = CreateProcess(state.nce->region_memory_map.at(memory::Region::text).address, constant::def_stack_size);
auto main_process = CreateProcess(state.nce->memory_region_map.at(Memory::Region::text)->address, constant::def_stack_size);
main_process->thread_map[main_process->main_thread]->Start(); // The kernel itself is responsible for starting the main thread
state.nce->Execute(state);
state.nce->Execute();
}
/**
@ -29,9 +30,8 @@ namespace lightSwitch::kernel {
return 0;
}
std::shared_ptr<type::KProcess> OS::CreateProcess(uint64_t address, size_t stack_size) {
auto *stack = static_cast<uint8_t *>(mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 address, size_t stack_size) {
auto *stack = static_cast<u8 *>(mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
if (stack == MAP_FAILED)
throw exception("Failed to allocate stack memory");
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
@ -39,22 +39,24 @@ namespace lightSwitch::kernel {
throw exception("Failed to create guard pages");
}
pid_t pid = clone(&ExecuteChild, stack + stack_size, CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
if(pid==-1) throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno)));
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(pid, address, reinterpret_cast<uint64_t>(stack), stack_size, state);
if (pid == -1) throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno)));
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(pid, address, reinterpret_cast<u64>(stack), stack_size, state);
process_map[pid] = process;
process_vec.push_back(pid);
state.logger->Write(Logger::DEBUG, "Successfully created process with PID: {}", pid);
return process;
}
void OS::KillThread(pid_t pid) {
auto process = process_map.at(pid);
if(process->main_thread==pid) {
if (process->main_thread == pid) {
state.logger->Write(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->thread_map) {
for (auto&[key, value]: process->thread_map) {
process_map.erase(key);
};
process_vec.erase(std::remove(process_vec.begin(), process_vec.end(), pid), process_vec.end());
} else {
state.logger->Write(Logger::DEBUG, "Exiting thread with TID: {}", pid);
process->handle_table.erase(process->thread_map[pid]->handle);
@ -63,7 +65,7 @@ namespace lightSwitch::kernel {
}
}
void OS::SvcHandler(uint16_t svc, pid_t pid) {
void OS::SvcHandler(u16 svc, pid_t pid) {
this_process = process_map.at(pid);
this_thread = this_process->thread_map.at(pid);
if (svc::svcTable[svc]) {
@ -74,17 +76,15 @@ namespace lightSwitch::kernel {
}
}
ipc::IpcResponse OS::IpcHandler(ipc::IpcRequest &request) {
ipc::IpcResponse response;
switch (request.req_info->type) {
case 4: // Request
response.SetError(0xDEADBEE5);
response.MoveHandle(0xBAADBEEF);
response.MoveHandle(0xFACCF00D);
response.Generate(state);
break;
ipc::IpcResponse OS::IpcHandler(handle_t handle) {
ipc::IpcRequest request(false, state);
ipc::IpcResponse response(false, state);
switch (request.header->type) {
case static_cast<u16>(ipc::CommandType::Request):
case static_cast<u16>(ipc::CommandType::RequestWithContext):
throw exception("Services are in-progress");
default:
throw exception(fmt::format("Unimplemented IPC message type {0}", uint16_t(request.req_info->type)));
throw exception(fmt::format("Unimplemented IPC message type {0}", u16(request.header->type)));
}
return response;
}

View File

@ -12,11 +12,12 @@ namespace lightSwitch::kernel {
* The OS class manages the interaction between OS components and the underlying hardware in NCE
*/
class OS {
private:
private:
device_state state; //!< The state of the device
public:
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< The state of the device
public:
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< A mapping from a process's PID to it's corresponding PID (Threads have their own PID too, so there are overlapping values)
std::vector<pid_t> process_vec; //!< A vector of all processes by their main thread's PID
std::shared_ptr<type::KProcess> this_process; //!< The corresponding KProcess object of the process that's called an SVC
std::shared_ptr<type::KThread> this_thread; //!< The corresponding KThread object of the thread that's called an SVC
@ -38,7 +39,7 @@ namespace lightSwitch::kernel {
* @param stack_size The size of the main stack
* @return An instance of the KProcess of the created process
*/
std::shared_ptr<type::KProcess> CreateProcess(uint64_t address, size_t stack_size);
std::shared_ptr<type::KProcess> CreateProcess(u64 address, size_t stack_size);
/**
* Kill a particular thread
@ -50,12 +51,12 @@ namespace lightSwitch::kernel {
* @param svc The ID of the SVC to be called
* @param pid The PID of the process/thread calling the SVC
*/
void SvcHandler(uint16_t svc, pid_t pid);
void SvcHandler(u16 svc, pid_t pid);
/**
* @param req The IPC request to be handled
* @param handle The handle of the object
* @return The corresponding response returned by a service
*/
ipc::IpcResponse IpcHandler(ipc::IpcRequest &req);
ipc::IpcResponse IpcHandler(handle_t handle);
};
}

View File

@ -12,6 +12,7 @@ import android.view.Window;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import java.io.File;

View File

@ -8,7 +8,9 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import me.xdrop.fuzzywuzzy.FuzzySearch;
import me.xdrop.fuzzywuzzy.model.ExtractedResult;
@ -67,10 +69,10 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
header_array.add((String) item);
type_array_uf.add(new ContentType(header_array.size() - 1, ContentType.Header));
}
if(search_term.length()!=0)
if (search_term.length() != 0)
this.getFilter().filter(search_term);
else
type_array=type_array_uf;
type_array = type_array_uf;
}
public void save(File file) throws IOException {

View File

@ -9,20 +9,36 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.preference.PreferenceManager;
import org.json.JSONObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
import static java.lang.Thread.interrupted;
public class LogActivity extends AppCompatActivity {
@ -47,42 +63,42 @@ public class LogActivity extends AppCompatActivity {
try {
InputStream inputStream = new FileInputStream(log_file);
reader = new BufferedReader(new InputStreamReader(inputStream));
thread = new Thread(new Runnable() {
@Override
public void run() {
@SuppressWarnings("deprecation") // 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(new Runnable() {
@Override
public void run() {
adapter.add(line);
}
});
thread = new Thread(new Runnable() {
@Override
public void run() {
@SuppressWarnings("deprecation") // 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(new Runnable() {
@Override
public void run() {
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();
}
} catch (IOException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
};
observer.onEvent(FileObserver.MODIFY, log_file.getPath());
observer.startWatching();
while (!interrupted()) ;
observer.stopWatching();
}
});
thread.start();
};
observer.onEvent(FileObserver.MODIFY, log_file.getPath());
observer.startWatching();
while (!interrupted()) ;
observer.stopWatching();
}
});
thread.start();
} catch (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();
@ -129,10 +145,15 @@ public class LogActivity extends AppCompatActivity {
Thread share_thread = new Thread(new Runnable() {
@Override
public void run() {
HttpsURLConnection urlConnection = null;
try {
URL url = new URL("https://hastebin.com/documents");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
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);
@ -143,25 +164,34 @@ public class LogActivity extends AppCompatActivity {
bufferedWriter.flush();
bufferedWriter.close();
outputStream.close();
urlConnection.connect();
if (urlConnection.getResponseCode() != 200) {
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");
bufferedReader.close();
inputStream.close();
urlConnection.disconnect();
String result = "https://hastebin.com/" + key;
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) {
runOnUiThread(new Runnable() {@Override public void run() {Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();}});
runOnUiThread(new Runnable() {
@Override
public void run() {Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();}
});
e.printStackTrace();
} finally {
assert urlConnection != null;
urlConnection.disconnect();
}
}});
}
});
share_thread.start();
try {
share_thread.join(1000);
} catch (InterruptedException e) {
} catch (Exception e) {
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
e.printStackTrace();
}

View File

@ -8,8 +8,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.google.android.material.snackbar.Snackbar;
class LogItem extends BaseItem {
private String content;
@ -60,8 +60,8 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
@Override
public boolean onLongClick(View view) {
LogItem item = (LogItem) getItem(((ViewHolder) view.getTag()).position);
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getLevel() + ": " + item.getMessage()));
Toast.makeText(view.getContext(), "Copied to clipboard", Snackbar.LENGTH_LONG).show();
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getMessage() + " (" + item.getLevel() + ")"));
Toast.makeText(view.getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show();
return false;
}

View File

@ -11,6 +11,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
@ -18,6 +19,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;

View File

@ -1,6 +1,7 @@
package emu.lightswitch;
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

View File

@ -1,34 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.85" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFF" android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.85"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z" />
</vector>

View File

@ -1,74 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.85" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFF" android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.85"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFF"
android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z" />
</vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.6" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.6"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z" />
</vector>

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFF"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
android:fillColor="#FFFF"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
</vector>

View File

@ -1,5 +1,11 @@
<vector android:alpha="0.85" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.85"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.85" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFF" android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.85"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFF"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" />
</vector>

View File

@ -1,5 +1,11 @@
<vector android:alpha="0.85" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.85"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
</vector>

View File

@ -1,34 +1,34 @@
<?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:orientation="vertical"
android:padding="15dp">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="15dp">
<ImageView
android:id="@+id/icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="false"
android:layout_centerVertical="true"
android:contentDescription="@string/icon"
android:src="@drawable/ic_missing_icon" />
android:id="@+id/icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="false"
android:layout_centerVertical="true"
android:contentDescription="@string/icon"
android:src="@drawable/ic_missing_icon" />
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginTop="5dp"
android:textAppearance="?android:attr/textAppearanceListItem" />
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginTop="5dp"
android:textAppearance="?android:attr/textAppearanceListItem" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light" />
android:id="@+id/text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light" />
</RelativeLayout>

View File

@ -1,29 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LogActivity">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LogActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ListView
android:id="@+id/log_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:transcriptMode="normal"
android:fastScrollEnabled="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
android:id="@+id/log_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fastScrollEnabled="true"
android:transcriptMode="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,26 +1,27 @@
<?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:orientation="vertical"
android:padding="15dp"
android:background="?attr/selectableItemBackground">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:textSize="15sp"
android:textAppearance="?android:attr/textAppearanceListItem" />
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:padding="15dp">
<TextView
android:id="@+id/text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_light" />
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textSize="15sp" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -1,39 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ListView
android:id="@+id/game_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
android:id="@+id/game_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/log_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_log" />
android:id="@+id/log_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_log" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,18 +1,18 @@
<?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:orientation="vertical"
android:clickable="false"
android:padding="12dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:textSize="13sp"
android:layout_marginTop="2dp"
android:layout_marginStart="5dp"/>
android:clickable="false"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:textColor="@color/colorPrimary"
android:textSize="13sp" />
</RelativeLayout>

View File

@ -1,22 +1,22 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search_log"
android:icon="@drawable/ic_search"
android:title="@string/search"
app:showAsAction="ifRoom|withText"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_share_log"
android:icon="@drawable/ic_share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_clear"
android:icon="@drawable/ic_clear"
android:title="@string/clear"
app:showAsAction="ifRoom"/>
app:showAsAction="ifRoom" />
</menu>

View File

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search_main"
android:icon="@drawable/ic_search"
android:title="@string/search"
app:showAsAction="ifRoom|withText"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
android:id="@+id/action_search_main"
android:icon="@drawable/ic_search"
android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"
android:title="@string/settings"
app:showAsAction="ifRoom"/>
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"
android:title="@string/settings"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_refresh"
android:icon="@drawable/ic_refresh"
android:title="@string/refresh"
app:showAsAction="ifRoom"/>
android:id="@+id/action_refresh"
android:icon="@drawable/ic_refresh"
android:title="@string/refresh"
app:showAsAction="ifRoom" />
</menu>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -6,6 +6,7 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="ToolbarTheme">
<!-- Query Text Color -->
<item name="android:textColorPrimary">@android:color/white</item>

View File

@ -15,36 +15,36 @@
-->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="category_search"
android:title="@string/search">
android:key="category_search"
android:title="@string/search">
<EditTextPreference
android:defaultValue="/sdcard/"
app:key="search_location"
app:title="@string/search_location"
app:useSimpleSummaryProvider="true" />
android:defaultValue="/sdcard/"
app:key="search_location"
app:title="@string/search_location"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory
android:key="category_log"
android:title="@string/logging">
android:key="category_log"
android:title="@string/logging">
<ListPreference
android:defaultValue="2"
android:entries="@array/log_level"
android:entryValues="@array/log_level_val"
app:key="log_level"
app:title="@string/log_level"
app:useSimpleSummaryProvider="true" />
android:defaultValue="2"
android:entries="@array/log_level"
android:entryValues="@array/log_level_val"
app:key="log_level"
app:title="@string/log_level"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory
android:key="category_localization"
android:title="@string/localization">
android:key="category_localization"
android:title="@string/localization">
<ListPreference
android:defaultValue="sys"
android:entries="@array/language_names"
android:entryValues="@array/language_values"
app:key="localization_language"
app:title="@string/localization_language"
app:useSimpleSummaryProvider="true" />
android:defaultValue="sys"
android:entries="@array/language_names"
android:entryValues="@array/language_values"
app:key="localization_language"
app:title="@string/localization_language"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>