// SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage) namespace fmt { /** * @brief A std::bitset formatter for {fmt} */ template struct formatter> : formatter { template constexpr auto format(const std::bitset &s, FormatContext &ctx) { return formatter::format(s.to_string(), ctx); } }; } namespace skyline { using u128 = __uint128_t; //!< Unsigned 128-bit integer using u64 = __uint64_t; //!< Unsigned 64-bit integer using u32 = __uint32_t; //!< Unsigned 32-bit integer using u16 = __uint16_t; //!< Unsigned 16-bit integer using u8 = __uint8_t; //!< Unsigned 8-bit integer using i128 = __int128_t; //!< Signed 128-bit integer using i64 = __int64_t; //!< Signed 64-bit integer using i32 = __int32_t; //!< Signed 32-bit integer using i16 = __int16_t; //!< Signed 16-bit integer using i8 = __int8_t; //!< Signed 8-bit integer using KHandle = u32; //!< The type of a kernel handle namespace frz = frozen; /** * @brief The result of an operation in HOS * @url https://switchbrew.org/wiki/Error_codes */ union Result { u32 raw{}; struct __attribute__((packed)) { u16 module : 9; u16 id : 12; }; /** * @note Success is 0, 0 - it is the only error that's not specific to a module */ Result() = default; constexpr Result(u16 module, u16 id) { this->module = module; this->id = id; } constexpr operator u32() const { return raw; } }; namespace constant { // Display constexpr u16 HandheldResolutionW{1280}; //!< The width component of the handheld resolution constexpr u16 HandheldResolutionH{720}; //!< The height component of the handheld resolution constexpr u16 DockedResolutionW{1920}; //!< The width component of the docked resolution constexpr u16 DockedResolutionH{1080}; //!< The height component of the docked resolution // Time constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second } namespace util { /** * @brief A way to implicitly cast all pointers to uintptr_t, this is used for {fmt} as we use 0x{:X} to print pointers * @note There's the exception of signed char pointers as they represent C Strings * @note This does not cover std::shared_ptr or std::unique_ptr and those will have to be explicitly casted to uintptr_t or passed through fmt::ptr */ template constexpr auto FmtCast(T object) { if constexpr (std::is_pointer::value) if constexpr (std::is_same::type>::type>::value) return reinterpret_cast::type>(object); else return reinterpret_cast(object); else return object; } } /** * @brief A wrapper over std::runtime_error with {fmt} formatting */ class exception : public std::runtime_error { public: /** * @param formatStr The exception string to be written, with {fmt} formatting * @param args The arguments based on format_str */ template inline exception(const S &formatStr, Args &&... args) : runtime_error(fmt::format(formatStr, util::FmtCast(args)...)) {} }; namespace util { /** * @brief Returns the current time in nanoseconds * @return The current time in nanoseconds */ inline u64 GetTimeNs() { u64 frequency; asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency)); u64 ticks; asm("MRS %0, CNTVCT_EL0" : "=r"(ticks)); return ((ticks / frequency) * constant::NsInSecond) + (((ticks % frequency) * constant::NsInSecond + (frequency / 2)) / frequency); } /** * @brief Returns the current time in arbitrary ticks * @return The current time in ticks */ inline u64 GetTimeTicks() { u64 ticks; asm("MRS %0, CNTVCT_EL0" : "=r"(ticks)); return ticks; } /** * @brief A way to implicitly convert a pointer to uintptr_t and leave it unaffected if it isn't a pointer */ template T PointerValue(T item) { return item; } template uintptr_t PointerValue(T *item) { return reinterpret_cast(item); } /** * @brief A way to implicitly convert an integral to a pointer, if the return type is a pointer */ template Return ValuePointer(T item) { if constexpr (std::is_pointer::value) return reinterpret_cast(item); else return item; } /** * @return The value aligned up to the next multiple * @note The multiple needs to be a power of 2 */ template constexpr TypeVal AlignUp(TypeVal value, TypeMul multiple) { multiple--; return ValuePointer((PointerValue(value) + multiple) & ~(multiple)); } /** * @return The value aligned down to the previous multiple * @note The multiple needs to be a power of 2 */ template constexpr TypeVal AlignDown(TypeVal value, TypeMul multiple) { return ValuePointer(PointerValue(value) & ~(multiple - 1)); } /** * @return If the address is aligned with the multiple */ template constexpr bool IsAligned(TypeVal value, TypeMul multiple) { if ((multiple & (multiple - 1)) == 0) return !(PointerValue(value) & (multiple - 1U)); else return (PointerValue(value) % multiple) == 0; } /** * @return If the value is page aligned */ template constexpr bool PageAligned(TypeVal value) { return IsAligned(value, PAGE_SIZE); } /** * @return If the value is word aligned */ template constexpr bool WordAligned(TypeVal value) { return IsAligned(value, WORD_BIT / 8); } /** * @param string The string to create a magic from * @return The magic of the supplied string */ template constexpr Type MakeMagic(std::string_view string) { Type object{}; size_t offset{}; for (auto &character : string) { object |= static_cast(character) << offset; offset += sizeof(character) * 8; } return object; } constexpr u8 HexDigitToNibble(char digit) { if (digit >= '0' && digit <= '9') return digit - '0'; else if (digit >= 'a' && digit <= 'f') return digit - 'a' + 10; else if (digit >= 'A' && digit <= 'F') return digit - 'A' + 10; throw exception("Invalid hex character: '{}'", digit); } template constexpr std::array HexStringToArray(std::string_view string) { if (string.size() != Size * 2) throw exception("String size: {} (Expected {})", string.size(), Size); std::array result; for (size_t i{}; i < Size; i++) { size_t index{i * 2}; result[i] = (HexDigitToNibble(string[index]) << 4) | HexDigitToNibble(string[index + 1]); } return result; } template constexpr Type HexStringToInt(std::string_view string) { if (string.size() > sizeof(Type) * 2) throw exception("String size larger than type: {} (sizeof(Type): {})", string.size(), sizeof(Type)); Type result{}; size_t offset{(sizeof(Type) * 8) - 4}; for (size_t index{}; index < string.size(); index++, offset -= 4) { char digit{string[index]}; if (digit >= '0' && digit <= '9') result |= static_cast(digit - '0') << offset; else if (digit >= 'a' && digit <= 'f') result |= static_cast(digit - 'a' + 10) << offset; else if (digit >= 'A' && digit <= 'F') result |= static_cast(digit - 'A' + 10) << offset; else break; } return result >> (offset + 4); } /** * @brief A compile-time hash function as std::hash isn't constexpr */ constexpr std::size_t Hash(std::string_view view) { return frz::elsa{}(frz::string(view.data(), view.size()), 0); } } /** * @brief A custom wrapper over span that adds several useful methods to it * @note This class is completely transparent, it implicitly converts from and to span */ template class span : public std::span { public: using std::span::span; using std::span::operator=; typedef typename std::span::element_type elementType; typedef typename std::span::index_type indexType; constexpr span(const std::span &spn) : std::span(spn) {} /** * @brief We want to support implicitly casting from std::string_view -> span as it is just a specialization of a data view which span is a generic form of, the opposite doesn't hold true as not all data held by a span is string data therefore the conversion isn't implicit there */ template constexpr span(const std::basic_string_view &string) : std::span(const_cast(string.data()), string.size()) {} template constexpr Out &as() { if (span::size_bytes() >= sizeof(Out)) return *reinterpret_cast(span::data()); throw exception("Span size is less than Out type size (0x{:X}/0x{:X})", span::size_bytes(), sizeof(Out)); } /** * @param nullTerminated If true and the string is null-terminated, a view of it will be returned (not including the null terminator itself), otherwise the entire span will be returned as a string view */ constexpr std::string_view as_string(bool nullTerminated = false) { return std::string_view(reinterpret_cast(span::data()), nullTerminated ? (std::find(span::begin(), span::end(), 0) - span::begin()) : span::size_bytes()); } template constexpr span cast() { if (util::IsAligned(span::size_bytes(), sizeof(Out))) return span(reinterpret_cast(span::data()), span::size_bytes() / sizeof(Out)); throw exception("Span size not aligned with Out type size (0x{:X}/0x{:X})", span::size_bytes(), sizeof(Out)); } /** * @brief Copies data from the supplied span into this one * @param amount The amount of elements that need to be copied (in terms of the supplied span), 0 will try to copy the entirety of the other span */ template constexpr void copy_from(const span spn, indexType amount = 0) { auto size{amount ? amount * sizeof(In) : spn.size_bytes()}; if (span::size_bytes() < size) throw exception("Data being copied is larger than this span"); std::memmove(span::data(), spn.data(), size); } /** * @brief Implicit type conversion for copy_from, this allows passing in std::vector/std::array in directly is automatically passed by reference which is important for any containers */ template constexpr void copy_from(const In &in, indexType amount = 0) { copy_from(span::type>(in), amount); } /** Base Class Functions that return an instance of it, we upcast them **/ template constexpr span first() const noexcept { return std::span::template first(); } template constexpr span last() const noexcept { return std::span::template last(); } constexpr span first(indexType count) const noexcept { return std::span::first(count); } constexpr span last(indexType count) const noexcept { return std::span::last(count); } template constexpr auto subspan() const noexcept -> span { return std::span::template subspan(); } constexpr span subspan(indexType offset, indexType count = std::dynamic_extent) const noexcept { return std::span::subspan(offset, count); } }; /** * @brief Deduction guides required for arguments to span, CTAD will fail for iterators, arrays and containers without this */ template span(It, End) -> span::value_type, Extent>; template span(T (&)[Size]) -> span; template span(std::array &) -> span; template span(const std::array &) -> span; template span(Container &) -> span; template span(const Container &) -> span; /** * @brief A wrapper around writing logs into a log file and logcat using Android Log APIs */ class Logger { private: std::ofstream logFile; //!< An output stream to the log file std::mutex mtx; //!< A mutex to lock before logging anything public: enum class LogLevel { Error, Warn, Info, Debug, Verbose, }; LogLevel configLevel; //!< The minimum level of logs to write /** * @param path The path of the log file * @param configLevel The minimum level of logs to write */ Logger(const std::string &path, LogLevel configLevel); /** * @brief Writes the termination message to the log file */ ~Logger(); /** * @brief Update the tag in log messages with a new thread name */ static void UpdateTag(); /** * @brief Writes a header, should only be used for emulation starting and ending */ void WriteHeader(const std::string &str); void Write(LogLevel level, std::string str); template inline void Error(const S &formatStr, Args &&... args) { if (LogLevel::Error <= configLevel) { Write(LogLevel::Error, fmt::format(formatStr, util::FmtCast(args)...)); } } template inline void Warn(const S &formatStr, Args &&... args) { if (LogLevel::Warn <= configLevel) { Write(LogLevel::Warn, fmt::format(formatStr, util::FmtCast(args)...)); } } template inline void Info(const S &formatStr, Args &&... args) { if (LogLevel::Info <= configLevel) { Write(LogLevel::Info, fmt::format(formatStr, util::FmtCast(args)...)); } } template inline void Debug(const S &formatStr, Args &&... args) { if (LogLevel::Debug <= configLevel) { Write(LogLevel::Debug, fmt::format(formatStr, util::FmtCast(args)...)); } } template inline void Verbose(const S &formatStr, Args &&... args) { if (LogLevel::Verbose <= configLevel) { Write(LogLevel::Verbose, fmt::format(formatStr, util::FmtCast(args)...)); } } }; class Settings; namespace nce { class NCE; struct ThreadContext; } class JvmManager; namespace gpu { class GPU; } namespace kernel { namespace type { class KProcess; class KThread; } class Scheduler; class OS; } namespace audio { class Audio; } namespace input { class Input; } namespace loader { class Loader; } /** * @brief The state of the entire emulator is contained within this class, all objects related to emulation are tied into it */ struct DeviceState { DeviceState(kernel::OS *os, std::shared_ptr jvmManager, std::shared_ptr settings, std::shared_ptr logger); kernel::OS *os; std::shared_ptr jvm; std::shared_ptr settings; std::shared_ptr logger; std::shared_ptr loader; std::shared_ptr gpu; std::shared_ptr audio; std::shared_ptr nce; std::shared_ptr scheduler; std::shared_ptr process; static thread_local inline std::shared_ptr thread{}; //!< The KThread of the thread which accesses this object static thread_local inline nce::ThreadContext *ctx{}; //!< The context of the guest thread for the corresponding host thread std::shared_ptr input; }; }