// SPDX-License-Identifier: MPL-2.0 // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) #pragma once #include #include #include #include #include #include #include #include #include "base.h" #include "exception.h" namespace skyline::util { /** * @brief Concept for any trivial non-container type */ template concept TrivialObject = std::is_trivially_copyable_v && !requires(T v) { v.data(); }; namespace detail { /** * @brief Retrieves the system counter clock frequency * @note Some devices report an incorrect value so they need special handling */ inline u64 InitFrequency() { char buffer[PROP_VALUE_MAX]; int len{__system_property_get("ro.product.board", buffer)}; std::string_view board{buffer, static_cast(len)}; u64 frequency; if (board == "s5e9925") // Exynos 2200 frequency = 25600000; else if (board == "exynos2100") // Exynos 2100 frequency = 26000000; else if (board == "exynos9810") // Exynos 9810 frequency = 26000000; else if (board == "s5e8825") // Exynos 1280 frequency = 26000000; else asm volatile("MRS %0, CNTFRQ_EL0" : "=r"(frequency)); return frequency; } } inline const u64 ClockFrequency{detail::InitFrequency()}; //!< The system counter clock frequency in Hz template inline i64 GetTimeScaled() { u64 frequency{ClockFrequency}; u64 ticks; asm volatile("MRS %0, CNTVCT_EL0" : "=r"(ticks)); return static_cast(((ticks / frequency) * TargetFrequency) + (((ticks % frequency) * TargetFrequency + (frequency / 2)) / frequency)); } /** * @brief Returns the current time in nanoseconds * @return The current time in nanoseconds */ inline i64 GetTimeNs() { return GetTimeScaled(); } /** * @return The current time in guest ticks */ inline u64 GetTimeTicks() { constexpr i64 TegraX1ClockFrequency{19200000}; // The clock frequency of the Tegra X1 (19.2 MHz) return static_cast(GetTimeScaled()); } /** * @brief A way to implicitly convert a pointer to uintptr_t and leave it unaffected if it isn't a pointer */ template constexpr 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 constexpr Return ValuePointer(T item) { if constexpr (std::is_pointer::value) return reinterpret_cast(item); else return static_cast(item); } template concept IsPointerOrUnsignedIntegral = (std::is_unsigned_v && std::is_integral_v) || std::is_pointer_v; /** * @return The value aligned up to the next multiple * @note The multiple **must** be a power of 2 */ template requires IsPointerOrUnsignedIntegral constexpr TypeVal AlignUp(TypeVal value, size_t multiple) { multiple--; return ValuePointer((PointerValue(value) + multiple) & ~(multiple)); } /** * @return The value aligned up to the next multiple, the multiple is not restricted to being a power of two (NPOT) * @note This will round away from zero for negative numbers * @note This is costlier to compute than the power of 2 version, it should be preferred over this when possible */ template requires std::is_integral_v || std::is_pointer_v constexpr TypeVal AlignUpNpot(TypeVal value, ssize_t multiple) { return ValuePointer(((PointerValue(value) + multiple - 1) / multiple) * multiple); } /** * @return The value aligned down to the previous multiple * @note The multiple **must** be a power of 2 */ template requires IsPointerOrUnsignedIntegral constexpr TypeVal AlignDown(TypeVal value, size_t multiple) { return ValuePointer(PointerValue(value) & ~(multiple - 1)); } /** * @return If the address is aligned with the multiple */ template requires IsPointerOrUnsignedIntegral constexpr bool IsAligned(TypeVal value, size_t multiple) { if ((multiple & (multiple - 1)) == 0) return !(PointerValue(value) & (multiple - 1U)); else return (PointerValue(value) % multiple) == 0; } template requires IsPointerOrUnsignedIntegral constexpr bool IsPageAligned(TypeVal value) { return IsAligned(value, constant::PageSize); } template requires IsPointerOrUnsignedIntegral constexpr bool IsWordAligned(TypeVal value) { return IsAligned(value, WORD_BIT / 8); } /** * @return The value of division rounded up to the next integral */ template requires std::is_integral_v constexpr Type DivideCeil(Type dividend, Type divisor) { return (dividend + divisor - 1) / divisor; } /** * @param string The string to create a magic from * @return The magic of the supplied string */ template requires std::is_integral_v 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] = static_cast(HexDigitToNibble(string[index]) << 4) | HexDigitToNibble(string[index + 1]); } return result; } template requires std::is_integral_v 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); } template constexpr std::array SwapEndianness(std::array in) { std::reverse(in.begin(), in.end()); return in; } constexpr u64 SwapEndianness(u64 in) { return __builtin_bswap64(in); } constexpr u32 SwapEndianness(u32 in) { return __builtin_bswap32(in); } constexpr u16 SwapEndianness(u16 in) { return __builtin_bswap16(in); } /** * @brief A compile-time hash function as std::hash isn't constexpr */ constexpr std::size_t Hash(std::string_view view) { return frozen::elsa{}(frozen::string(view.data(), view.size()), 0); } /** * @brief A fast hash for any trivial object that is designed to be utilized with hash-based containers */ template requires std::is_trivial_v struct ObjectHash { size_t operator()(const T &object) const noexcept { return XXH64(&object, sizeof(object), 0); } }; /** * @brief Selects the largest possible integer type for representing an object alongside providing the size of the object in terms of the underlying type */ template struct IntegerFor { using Type = std::conditional_t > >; static constexpr size_t Count{sizeof(T) / sizeof(Type)}; }; namespace detail { inline thread_local std::mt19937_64 generator{GetTimeTicks()}; } /** * @brief Fills an array with random data from a Mersenne Twister pseudo-random generator * @note The generator is seeded with the the current time in ticks */ template requires std::is_integral_v void FillRandomBytes(std::span in) { std::uniform_int_distribution dist(std::numeric_limits::min(), std::numeric_limits::max()); std::generate(in.begin(), in.end(), [&]() { return dist(detail::generator); }); } template void FillRandomBytes(T &object) { FillRandomBytes(std::span(reinterpret_cast::Type *>(&object), IntegerFor::Count)); } template T RandomNumber(T min, T max) { std::uniform_int_distribution dist(PointerValue(min), PointerValue(max)); return ValuePointer(dist(detail::generator)); } /** * @brief A temporary shim for C++ 20's bit_cast to make transitioning to it easier */ template To BitCast(const From &from) { return *reinterpret_cast(&from); } /** * @brief A utility type for placing elements by offset in unions rather than relative position in structs * @tparam PadType The type of a unit of padding, total size of padding is `sizeof(PadType) * Offset` */ template struct OffsetMember { private: PadType _pad_[Offset]; ValueType value; public: OffsetMember &operator=(const ValueType &pValue) { value = pValue; return *this; } const auto &operator[](std::size_t index) const { return value[index]; } const ValueType &operator*() const { return value; } ValueType &operator*() { return value; } ValueType *operator->() { return &value; } }; template std::array MakeFilledArray(std::index_sequence, TArgs &&... args) { return {(void(Is), T(args...))...}; } template std::array MakeFilledArray(TArgs &&... args) { return MakeFilledArray(std::make_index_sequence(), std::forward(args)...); } template struct IncrementingT { using Type = T; }; template struct IsIncrementingT : std::false_type {}; template struct IsIncrementingT> : std::true_type {}; template T MakeMergeElem(TSrcs &&... srcs) { auto readElem{[index = Index](auto &&src) -> decltype(auto) { using SrcType = std::decay_t; if constexpr (requires { src[Index]; }) return src[Index]; else if constexpr (IsIncrementingT{}) return static_cast(index); else return src; }}; return T{readElem(std::forward(srcs))...}; } template std::array MergeInto(std::index_sequence seq, TSrcs &&... srcs) { return {MakeMergeElem(std::forward(srcs)...)...}; } /** * @brief Constructs {{scalar0, array0[0], array1[0], ... arrayN[0], scalar1}, {scalar0, array0[1], array1[1]. ... arrayN[1], scalar0}, ...} */ template std::array MergeInto(TSrcs &&... srcs) { return MergeInto(std::make_index_sequence(), std::forward(srcs)...); } inline std::string HexDump(std::span data) { return std::accumulate(data.begin(), data.end(), std::string{}, [](std::string str, u8 el) { return std::move(str) + fmt::format("{:02X}", el); }); } }