skyline/app/src/main/cpp/skyline/common.h

386 lines
14 KiB
C++

// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <map>
#include <unordered_map>
#include <vector>
#include <fstream>
#include <syslog.h>
#include <mutex>
#include <thread>
#include <string>
#include <sstream>
#include <memory>
#include <fmt/format.h>
#include <sys/mman.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <jni.h>
#include "nce/guest_common.h"
namespace skyline {
using KHandle = u32; //!< The type of a kernel handle
namespace constant {
// Memory
constexpr auto BaseAddress = 0x8000000; //!< The address space base
constexpr auto DefStackSize = 0x1E8480; //!< The default amount of stack: 2 MB
// Kernel
constexpr std::pair<int8_t, int8_t> AndroidPriority = {19, -8}; //!< The range of priority for Android
constexpr std::pair<u8, u8> SwitchPriority = {0, 63}; //!< The range of priority for the Nintendo Switch
// Display
constexpr auto HandheldResolutionW = 1280; //!< The width component of the handheld resolution
constexpr auto HandheldResolutionH = 720; //!< The height component of the handheld resolution
constexpr auto DockedResolutionW = 1920; //!< The width component of the docked resolution
constexpr auto DockedResolutionH = 1080; //!< The height component of the docked resolution
// Status codes
namespace status {
constexpr u32 Success = 0x0; //!< "Success"
constexpr u32 NoMessages = 0x680; //!< "No message available"
constexpr u32 ServiceInvName = 0xC15; //!< "Invalid name"
constexpr u32 ServiceNotReg = 0xE15; //!< "Service not registered"
constexpr u32 InvSize = 0xCA01; //!< "Invalid size"
constexpr u32 InvAddress = 0xCC01; //!< "Invalid address"
constexpr u32 InvState = 0xD401; //!< "Invalid MemoryState"
constexpr u32 InvPermission = 0xD801; //!< "Invalid Permission"
constexpr u32 InvMemRange = 0xD801; //!< "Invalid Memory Range"
constexpr u32 InvPriority = 0xE001; //!< "Invalid Priority"
constexpr u32 InvHandle = 0xE401; //!< "Invalid handle"
constexpr u32 InvCombination = 0xE801; //!< "Invalid combination"
constexpr u32 Timeout = 0xEA01; //!< "Timeout"
constexpr u32 Interrupted = 0xEC01; //!< "Interrupted"
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
constexpr u32 NotFound = 0xF201; //!< "Not found"
constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour"
}
};
/**
* @brief This enumerates the types of the ROM
* @note This needs to be synchronized with emu.skyline.loader.BaseLoader.TitleFormat
*/
enum class TitleFormat {
NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO
XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
};
namespace util {
/**
* @brief Returns the current time in nanoseconds
* @return The current time in nanoseconds
*/
inline u64 GetTimeNs() {
constexpr uint64_t nsInSecond = 1000000000;
static u64 frequency{};
if (!frequency)
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
u64 ticks;
asm("MRS %0, CNTVCT_EL0" : "=r"(ticks));
return ((ticks / frequency) * nsInSecond) + (((ticks % frequency) * nsInSecond + (frequency / 2)) / frequency);
}
/**
* @brief Aligns up a value to a multiple of two
* @tparam Type The type of the values
* @param value The value to round up
* @param multiple The multiple to round up to (Should be a multiple of 2)
* @tparam TypeVal The type of the value
* @tparam TypeMul The type of the multiple
* @return The aligned value
*/
template<typename TypeVal, typename TypeMul>
constexpr inline TypeVal AlignUp(TypeVal value, TypeMul multiple) {
static_assert(std::is_integral<TypeVal>() && std::is_integral<TypeMul>());
multiple--;
return (value + multiple) & ~(multiple);
}
/**
* @brief Aligns down a value to a multiple of two
* @param value The value to round down
* @param multiple The multiple to round down to (Should be a multiple of 2)
* @tparam TypeVal The type of the value
* @tparam TypeMul The type of the multiple
* @return The aligned value
*/
template<typename TypeVal, typename TypeMul>
constexpr inline TypeVal AlignDown(TypeVal value, TypeMul multiple) {
static_assert(std::is_integral<TypeVal>() && std::is_integral<TypeMul>());
return value & ~(multiple - 1);
}
/**
* @param address The address to check for alignment
* @return If the address is page aligned
*/
constexpr inline bool PageAligned(u64 address) {
return !(address & (PAGE_SIZE - 1U));
}
/**
* @param address The address to check for alignment
* @return If the address is word aligned
*/
constexpr inline bool WordAligned(u64 address) {
return !(address & 3U);
}
/**
* @param string The string to create a magic from
* @return The magic of the supplied string
*/
template<typename Type>
constexpr Type MakeMagic(std::string_view string) {
Type object{};
auto offset = 0;
for (auto &character : string) {
object |= static_cast<Type>(character) << offset;
offset += sizeof(character) * 8;
}
return object;
}
}
/**
* @brief The Mutex class is a wrapper around an atomic bool used for synchronization
*/
class Mutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT; //!< An atomic flag to hold the state of the mutex
public:
/**
* @brief Wait on and lock the mutex
*/
void lock();
/**
* @brief Try to lock the mutex if it is unlocked else return
* @return If the mutex was successfully locked or not
*/
inline bool try_lock() {
return !flag.test_and_set(std::memory_order_acquire);
}
/**
* @brief Unlock the mutex if it is held by this thread
*/
inline void unlock() {
flag.clear(std::memory_order_release);
}
};
/**
* @brief The GroupMutex class is a special type of mutex that allows two groups of users and only allows one group to run in parallel
*/
class GroupMutex {
public:
/**
* @brief This enumeration holds all the possible owners of the mutex
*/
enum class Group : u8 {
None = 0, //!< No group owns this mutex
Group1 = 1, //!< Group 1 owns this mutex
Group2 = 2 //!< Group 2 owns this mutex
};
/**
* @brief Wait on and lock the mutex
*/
void lock(Group group = Group::Group1);
/**
* @brief Unlock the mutex
* @note Undefined behavior in case unlocked by thread in non-owner group
*/
void unlock();
private:
std::atomic<Group> flag{Group::None}; //!< An atomic flag to hold which group holds the mutex
std::atomic<Group> next{Group::None}; //!< An atomic flag to hold which group will hold the mutex next
std::atomic<u8> num{0}; //!< An atomic u8 keeping track of how many users are holding the mutex
Mutex mtx; //!< A mutex to lock before changing of num and flag
};
/**
* @brief The Logger class is to write log output to file and logcat
*/
class Logger {
private:
std::ofstream logFile; //!< An output stream to the log file
const char *levelStr[4] = {"0", "1", "2", "3"}; //!< This is used to denote the LogLevel when written out to a file
static constexpr int levelSyslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG}; //!< This corresponds to LogLevel and provides it's equivalent for syslog
Mutex mtx; //!< A mutex to lock before logging anything
public:
enum class LogLevel { Error, Warn, Info, Debug }; //!< The level of a particular log
LogLevel configLevel; //!< The level of logs to write
/**
* @param logFd A FD to the log file
* @param configLevel The minimum level of logs to write
*/
Logger(const int logFd, LogLevel configLevel);
/**
* @brief Writes the termination message to the log file
*/
~Logger();
/**
* @brief Writes a header, should only be used for emulation starting and ending
* @param str The value to be written
*/
void WriteHeader(const std::string &str);
/**
* @brief Write a log to the log file
* @param level The level of the log
* @param str The value to be written
*/
void Write(const LogLevel level, std::string str);
/**
* @brief Write an error log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Error(const S &formatStr, Args &&... args) {
if (LogLevel::Error <= configLevel) {
Write(LogLevel::Error, fmt::format(formatStr, args...));
}
}
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Warn(const S &formatStr, Args &&... args) {
if (LogLevel::Warn <= configLevel) {
Write(LogLevel::Warn, fmt::format(formatStr, args...));
}
}
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Info(const S &formatStr, Args &&... args) {
if (LogLevel::Info <= configLevel) {
Write(LogLevel::Info, fmt::format(formatStr, args...));
}
}
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline void Debug(const S &formatStr, Args &&... args) {
if (LogLevel::Debug <= configLevel) {
Write(LogLevel::Debug, fmt::format(formatStr, args...));
}
}
};
/**
* @brief The Settings class is used to access the parameters set in the Java component of the application
*/
class Settings {
private:
std::map<std::string, std::string> stringMap; //!< A mapping from all keys to their corresponding string value
std::map<std::string, bool> boolMap; //!< A mapping from all keys to their corresponding boolean value
std::map<std::string, int> intMap; //!< A mapping from all keys to their corresponding integer value
public:
/**
* @param preferenceFd An FD to the preference XML file
*/
Settings(const int preferenceFd);
/**
* @brief Retrieves a particular setting as a string
* @param key The key of the setting
* @return The string value of the setting
*/
std::string GetString(const std::string &key);
/**
* @brief Retrieves a particular setting as a boolean
* @param key The key of the setting
* @return The boolean value of the setting
*/
bool GetBool(const std::string &key);
/**
* @brief Retrieves a particular setting as a integer
* @param key The key of the setting
* @return The integer value of the setting
*/
int GetInt(const std::string &key);
/**
* @brief Writes all settings keys and values to syslog. This function is for development purposes.
*/
void List(const std::shared_ptr<Logger> &logger);
};
/**
* @brief This is a std::runtime_error with libfmt formatting
*/
class exception : public std::runtime_error {
public:
/**
* @param formatStr The exception string to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
inline exception(const S &formatStr, Args &&... args) : runtime_error(fmt::format(formatStr, args...)) {}
};
class NCE;
class JvmManager;
namespace gpu {
class GPU;
}
namespace kernel {
namespace type {
class KProcess;
class KThread;
}
class OS;
}
namespace audio {
class Audio;
}
/**
* @brief This struct is used to hold the state of a device
*/
struct DeviceState {
DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &process, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger);
kernel::OS *os; //!< This holds a reference to the OS class
std::shared_ptr<kernel::type::KProcess> &process; //!< This holds a reference to the process object
thread_local static std::shared_ptr<kernel::type::KThread> thread; //!< This holds a reference to the current thread object
thread_local static ThreadContext *ctx; //!< This holds the context of the thread
std::shared_ptr<NCE> nce; //!< This holds a reference to the NCE class
std::shared_ptr<gpu::GPU> gpu; //!< This holds a reference to the GPU class
std::shared_ptr<audio::Audio> audio; //!< This holds a reference to the Audio class
std::shared_ptr<JvmManager> jvm; //!< This holds a reference to the JvmManager class
std::shared_ptr<Settings> settings; //!< This holds a reference to the Settings class
std::shared_ptr<Logger> logger; //!< This holds a reference to the Logger class
};
}