diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..92ac957 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 The Skyline Project +Copyright (c) 2021 MasaGratoR + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7534360 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: all clean skyline + +NAME := $(shell basename $(CURDIR)) +NAME_LOWER := $(shell echo $(NAME) | tr A-Z a-z) + +BUILD_DIR := build + +MAKE_NSO := nso.mk + +all: skyline + +skyline: + $(MAKE) all -f $(MAKE_NSO) MAKE_NSO=$(MAKE_NSO) BUILD=$(BUILD_DIR) TARGET=$(NAME) + +clean: + $(MAKE) clean -f $(MAKE_NSO) diff --git a/README.md b/README.md index cc988c9..b12c64d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ -# PortalNXSideLoader -Portal Collection File Sideloader + + +# Portal Collection File Sideloader + +# Installation +From releases download: +- for Portal 1: +> Portal-NXSideLoader.zip +- for Portal 2: +>Without network patches: +> - Portal2-NXSideLoader.zip +> +> With network patches: +> - Portal2-NoWeb-NXSideLoader.zip + +Put `atmosphere` folder to root of sdcard (yes, your CFW won't be deleted...) + +# Informations for mod makers + +Few files in `nxcontent` may not be supported as they are preloaded with separate functions. I needed to add specific support for one function so `rom_boot_params.txt` could be loaded. If there is any file it's not working and you want it to work, write na issue. + +# How this works? + +Devs redesigned whole cstdio to use game.zip as filesystem. +Portal games to open most files are using function called `fopen_nx()`. To read this file - `fread_nx()`, etc. +All functions are cross compatible with cstdio, so solution was pretty easy: +1. Hook `fopen_nx()` +2. Detect if passed file path exists on SD card +3. If it exists, redirect call to `fopen()` with correct path starting with `rom:/` + +There were 2 issues with this solution: +- not all files are using this function. It seems there is not many of them and only important one in my opinion was `rom_boot_params.txt` so I have hooked function reading this file and redesigned it to load file from SD card. +- fopen_nx() path load is ignoring case + has priority to check first if file is inside `nxcontent` folder, and if not checks root of zip. Inside hook remade this check + used `tolower()` for file path since all files and folders names inside zip are lower case. + +# Compilation + +You need standard devkitpro install with Switch-dev. + +Patch `main.npdm` from exefs with this, otherwise plugin will crash: +https://github.com/skyline-dev/skyline/blob/master/scripts/patchNpdm.py + +To compile it for Portal 1 use command +``` +make PORTAL="-DPORTAL" +``` + +for Portal 2 +``` +make PORTAL="-DPORTAL2" +``` +with internet patches to allow debugging via network that has blocked access to Nintendo servers +``` +make PORTAL="-DPORTAL2 -DPDEBUG" +``` diff --git a/exported.txt b/exported.txt new file mode 100644 index 0000000..cd0d95b --- /dev/null +++ b/exported.txt @@ -0,0 +1,14 @@ +{ + global: + __custom_init; + __custom_fini; + skyline_tcp_send_raw; + getRegionAddress; + A64HookFunction; + A64InlineHook; + sky_memcpy; + get_program_id; + get_plugin_addresses; + + local: *; +}; diff --git a/include/ModuleObject.hpp b/include/ModuleObject.hpp new file mode 100644 index 0000000..280175a --- /dev/null +++ b/include/ModuleObject.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace rtld { +struct ModuleObject { + private: + // ResolveSymbols internals + inline void ResolveSymbolRelAbsolute(Elf64_Rel* entry); + inline void ResolveSymbolRelaAbsolute(Elf64_Rela* entry); + inline void ResolveSymbolRelJumpSlot(Elf64_Rel* entry, bool do_lazy_got_init); + inline void ResolveSymbolRelaJumpSlot(Elf64_Rela* entry, bool do_lazy_got_init); + + public: + struct ModuleObject* next; + struct ModuleObject* prev; + union { + Elf64_Rel* rel; + Elf64_Rela* rela; + void* raw; + } rela_or_rel_plt; + union { + Elf64_Rel* rel; + Elf64_Rela* rela; + } rela_or_rel; + uint64_t module_base; + Elf64_Dyn* dynamic; + bool is_rela; + uint64_t rela_or_rel_plt_size; + void (*dt_init)(void); + void (*dt_fini)(void); + uint32_t* hash_bucket; + uint32_t* hash_chain; + char* dynstr; + Elf64_Sym* dynsym; + uint64_t dynstr_size; + void** got; + uint64_t rela_dyn_size; + uint64_t rel_dyn_size; + uint64_t rel_count; + uint64_t rela_count; + uint64_t hash_nchain_value; + uint64_t hash_nbucket_value; + uint64_t got_stub_ptr; +#ifdef __RTLD_6XX__ + uint64_t soname_idx; + uint64_t nro_size; + bool cannot_revert_symbols; +#endif + + void Initialize(uint64_t aslr_base, Elf64_Dyn* dynamic); + void Relocate(); + Elf64_Sym* GetSymbolByName(const char* name); + void ResolveSymbols(bool do_lazy_got_init); + bool TryResolveSymbol(Elf64_Addr* target_symbol_address, Elf64_Sym* symbol); +}; + +#ifdef __RTLD_6XX__ +static_assert(sizeof(ModuleObject) == 0xD0, "ModuleObject size isn't valid"); +#else +static_assert(sizeof(ModuleObject) == 0xB8, "ModuleObject size isn't valid"); +#endif +} // namespace rtld \ No newline at end of file diff --git a/include/alloc.h b/include/alloc.h new file mode 100644 index 0000000..2cced20 --- /dev/null +++ b/include/alloc.h @@ -0,0 +1,23 @@ +/** + * @file alloc.h + * @brief Allocation functions. + */ + +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void* malloc(size_t size); +void free(void* src); +void* calloc(u64 num, u64 size); +void* realloc(void* ptr, u64 size); +void* aligned_alloc(u64 alignment, u64 size); +u64 malloc_usable_size(void* ptr); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/curl.h b/include/curl.h new file mode 100644 index 0000000..7513166 --- /dev/null +++ b/include/curl.h @@ -0,0 +1,37 @@ +/** + * @file curl.h + * @brief CURL implementation. + */ + +#pragma once + +#include "types.h" + +typedef u32 CURLCode; // basic code for getting results from functions + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void CURL; +enum CURLINFO {}; +enum CURLoption {}; + +// GLOBAL +CURLcode curl_global_init(s64 flags); + +// EASY +CURL* curl_easy_init(); +CURLcode curl_easy_setopt(CURL* curl, CURLoption, ...); +CURLcode curl_easy_perform(CURL* curl); +void curl_easy_cleanup(CURL* curl); +CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info, ...); +CURL* curl_easy_duphandle(CURL* curl); +void curl_easy_reset(CURL* curl); +CURLcode curl_easy_pause(CURL* curl, s32 mask); +CURLcode curl_easy_recv(CURL* curl, void* buffer, u64 bufferLength, u64*); +CURLcode curl_easy_send(CURL* curl, void const* buffer, u64 bufferLength, u64*); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/cxa.h b/include/cxa.h new file mode 100644 index 0000000..2ed4905 --- /dev/null +++ b/include/cxa.h @@ -0,0 +1,21 @@ +/** + * @file cxa.h + * @brief One-Time Constru(X)tion API. + */ + +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern s32 __cxa_guard_acquire(u32* guard); +extern void __cxa_guard_release(u32* guard); +extern void __cxa_pure_virtual(); +void __cxa_atexit(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/endianess.h b/include/endianess.h new file mode 100644 index 0000000..bf5c784 --- /dev/null +++ b/include/endianess.h @@ -0,0 +1,12 @@ +/** + * @file endianess.h + * @brief Functions for reversing data in between the two endianesses. + */ + +#pragma once + +#include "types.h" + +u16 swap16(u16 x) { return (x << 8) | (x >> 8); } + +u32 swap32(u32 x) { return (x << 24) | ((x << 8) & 0xFF0000) | ((x >> 24) & 0xFF) | ((x >> 8) & 0xFF00); } \ No newline at end of file diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 0000000..2c6e410 --- /dev/null +++ b/include/macros.h @@ -0,0 +1,23 @@ +/** + * @file macros.h + * @brief Various macros defined from IDA Pro. + */ + +#pragma once + +#include "types.h" + +// https://github.com/joxeankoret/tahh/blob/master/comodo/defs.h + +#define __CASSERT_N0__(l) COMPILE_TIME_ASSERT_##l +#define __CASSERT_N1__(l) __CASSERT_N0__(l) +#define CASSERT(cnd) typedef char __CASSERT_N1__(__LINE__)[(cnd) ? 1 : -1] + +#define LODWORD(x) (*((u32*)&(x))) + +template +bool is_mul_ok(T count, T elsize) { + CASSERT((T)(-1) > 0); + if (elsize == 0 || count == 0) return true; + return count <= ((T)(-1)) / elsize; +} \ No newline at end of file diff --git a/include/main.hpp b/include/main.hpp new file mode 100644 index 0000000..515611a --- /dev/null +++ b/include/main.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "ModuleObject.hpp" +#include "alloc.h" +#include "mem.h" +#include "nn/crypto.h" +#include "nn/diag.h" +#include "nn/err.h" +#include "nn/fs.h" +#include "nn/nn.h" +#include "nn/oe.h" +#include "nn/os.hpp" +#include "nn/prepo.h" +#include "nn/ro.h" +#include "nvn/pfnc.h" +#include "operator.h" +#include "skyline/inlinehook/And64InlineHook.hpp" +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/kernel/virtmem.h" +#include "skyline/nx/runtime/env.h" + +#ifdef __cplusplus +}; +#endif + +extern nn::os::EventType romMountedEvent; + +extern "C" void skyline_init(); \ No newline at end of file diff --git a/include/mem.h b/include/mem.h new file mode 100644 index 0000000..7487b3c --- /dev/null +++ b/include/mem.h @@ -0,0 +1,22 @@ +/** + * @file mem.h + * @brief Memory functions. + */ + +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void* memset(void* src, int val, u64 num); +void* memcpy(void* dest, void const* src, u64 count); +void* memmove(void* dest, const void* src, u64 count); +void* memalign(size_t alignment, size_t size); +void* memmem(void* needle, size_t needleLen, void* haystack, size_t haystackLen); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/nn/account.h b/include/nn/account.h new file mode 100644 index 0000000..ef90807 --- /dev/null +++ b/include/nn/account.h @@ -0,0 +1,45 @@ +/** + * @file account.h + * @brief Account service implementation. + */ + +#pragma once + +#include "os.hpp" +#include "types.h" + +namespace nn { +namespace account { + typedef char Nickname[0x21]; + struct Uid { + u64 _x0[2]; + }; + typedef u64 NetworkServiceAccountId; + + class AsyncContext; + struct UserHandle; + + void Initialize(); + Result ListAllUsers(s32*, nn::account::Uid*, s32 numUsers); + Result OpenUser(nn::account::UserHandle*, nn::account::Uid const&); + Result IsNetworkServiceAccountAvailable(bool* out, nn::account::UserHandle const&); + void CloseUser(nn::account::UserHandle const&); + + Result EnsureNetworkServiceAccountAvailable(nn::account::UserHandle const& userHandle); + Result EnsureNetworkServiceAccountIdTokenCacheAsync(nn::account::AsyncContext*, nn::account::UserHandle const&); + Result LoadNetworkServiceAccountIdTokenCache(u64*, char*, u64, nn::account::UserHandle const&); + + Result GetLastOpenedUser(nn::account::Uid*); + Result GetNickname(nn::account::Nickname* nickname, nn::account::Uid const& userID); + + class AsyncContext { + public: + AsyncContext(); + + Result HasDone(bool*); + Result GetResult(); + Result Cancel(); + Result GetSystemEvent(nn::os::SystemEvent*); + }; +}; // namespace account +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/SoundArchivePlayer.h b/include/nn/atk/SoundArchivePlayer.h new file mode 100644 index 0000000..69443a3 --- /dev/null +++ b/include/nn/atk/SoundArchivePlayer.h @@ -0,0 +1,37 @@ +/** + * @file SoundArchivePlayer.h + * @brief Basic sound player from a sound archive. + */ + +#pragma once + +#include "detail/AdvancedWaveSoundRuntime.h" +#include "detail/SequenceSoundRuntime.h" +#include "detail/SoundArchiveManager.h" +#include "detail/StreamSoundRuntime.h" +#include "detail/WaveSoundRuntime.h" + +namespace nn { +namespace atk { + class SoundArchivePlayer { + public: + SoundArchivePlayer(); + + virtual ~SoundArchivePlayer(); + + bool IsAvailable() const; + void Finalize(); + void StopAllSound(s32, bool); + void DisposeInstances(); + + nn::atk::detail::SoundArchiveManager mArchiveManager; // _8 + nn::atk::detail::SequenceSoundRuntime mSeqSoundRuntime; // _50 + nn::atk::detail::WaveSoundRuntime mWaveSoundRuntime; // _130 + nn::atk::detail::AdvancedWaveSoundRuntime mAdvancedWaveSound; // _1B0 + nn::atk::detail::StreamSoundRuntime mStreamSoundRuntime; // _1E0 + u64 _290; + u32 _298; + u8 _29C[0x2E8 - 0x29C]; + }; +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/SoundDataManager.h b/include/nn/atk/SoundDataManager.h new file mode 100644 index 0000000..4079d74 --- /dev/null +++ b/include/nn/atk/SoundDataManager.h @@ -0,0 +1,25 @@ +/** + * @file SoundDataManager.h + * @brief Sound data management implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + class SoundDataManager { + public: + SoundDataManager(); + virtual ~SoundDataManager(); + + virtual void InvalidateData(void const*, void const*); + virtual void SetFileAddressToTable(u32, void const*); + virtual u64 GetFileAddressFromTable(u32) const; + virtual u32 GetFileAddressImpl(u32) const; + + u8 _0[0x240]; + }; +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/SoundPlayer.h b/include/nn/atk/SoundPlayer.h new file mode 100644 index 0000000..609faef --- /dev/null +++ b/include/nn/atk/SoundPlayer.h @@ -0,0 +1,56 @@ +/** + * @file SoundPlayer.h + * @brief Sound player. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + enum PauseMode { + + }; + + class SoundPlayer { + public: + SoundPlayer(); + ~SoundPlayer(); + + void StopAllSound(s32); + void Update(); + void DoFreePlayerHeap(); + void detail_SortPriorityList(bool); + void PauseAllSound(s32, bool); + void PauseAllSound(bool, s32, nn::atk::PauseMode); + void SetVolume(f32 vol); + void SetLowPassFilterFrequency(f32 filterFreq); + void SetBiquadFilter(s32 filterType, f32 baseFreq); + void SetDefaultOutputLine(u32 line); + + void detail_SetPlayableSoundLimit(s32 limit); + bool CanPlaySound(s32); + + u64 _0; + u64 _8; + u64 _10; + u64 _18; + u64 _20; + u64 _28; + u64 _30; + u64 _38; + s32 _40; + s32 mPlayableSoundCount; // _44 + s32 _48; + f32 mVolume; // _4C + f32 mLowPassFreq; // _50 + s32 mFilterType; // _54 + f32 mBaseFreq; // _58 + u32 mDefaultOutputLine; // _5C + f32 mOutputVolume; // _60 + u64 _64; + u64 _6C; + }; +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/detail/AdvancedWaveSoundRuntime.h b/include/nn/atk/detail/AdvancedWaveSoundRuntime.h new file mode 100644 index 0000000..a7a36d4 --- /dev/null +++ b/include/nn/atk/detail/AdvancedWaveSoundRuntime.h @@ -0,0 +1,28 @@ +/** + * @file AdvancedWaveSoundRuntime.h + * @brief Runtime wave sound api. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + namespace detail { + class AdvancedWaveSoundRuntime { + public: + AdvancedWaveSoundRuntime(); + ~AdvancedWaveSoundRuntime(); + + void Initialize(s32, void**, void const*); + void Finalize(); + s32 GetActiveCount() const; + void SetupUserParam(void**, u64); + void Update(); + + u8 _0[0x30]; + }; + }; // namespace detail +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/detail/BasicSound.h b/include/nn/atk/detail/BasicSound.h new file mode 100644 index 0000000..fdf6eb3 --- /dev/null +++ b/include/nn/atk/detail/BasicSound.h @@ -0,0 +1,138 @@ +/** + * @file BasicSound.h + * @brief A basic sound. + */ + +#pragma once + +#include "nn/atk/SoundPlayer.h" +#include "types.h" + +namespace nn { +namespace atk { + class SoundActor; + + enum MixMode { + + }; + + namespace detail { + class PlayerHeap; + class ExternalSoundPlayer; + + class BasicSound { + public: + BasicSound(); + virtual ~BasicSound(); + + virtual void Initialize(); + virtual void Finalize(); + virtual bool IsPrepared() const = 0; + virtual bool IsAttachedTempSpecialHandle() = 0; + virtual void DetachTempSpecialHandle() = 0; + virtual void OnUpdatePlayerPriority(); + virtual void UpdateMoveValue(); + virtual void OnUpdateParam(); + + void SetPriority(s32, s32); + void GetPriority(s32*, s32*) const; + void ClearIsFinalizedForCannotAllocatedResourceFlag(); + void SetId(u32 newID); + bool IsAttachedGeneralHandle(); + void DetachGeneralHandle(); + bool IsAttachedTempGeneralHandle(); + void DetachTempGeneralHandle(); + void StartPrepared(); + void Stop(s32); + void SetPlayerPriority(s32); + void ForceStop(); + void Pause(bool, s32); + void Mute(bool, s32); + void SetAutoStopCounter(s32); + void FadeIn(s32); + bool IsPause() const; + bool IsMute() const; + void Update(); + void UpdateParam(); + void UpdateMoveValue(); + void CalculateVolume() const; + f32 CalculatePitch() const; + f32 CalculateLpfFrequency() const; + u32 CalculateOutLineFlag() const; + void CalculateBiquadFilter(s32*, f32*) const; + void AttachPlayerHeap(nn::atk::detail::PlayerHeap*); + void DetachPlayerHeap(nn::atk::detail::PlayerHeap*); + void AttachSoundPlayer(nn::atk::SoundPlayer*); + void DetachSoundPlayer(nn::atk::SoundPlayer*); + void AttachSoundActor(nn::atk::SoundActor*); + void DetachSoundActor(nn::atk::SoundActor*); + void AttachExternalSoundPlayer(nn::atk::detail::ExternalSoundPlayer*); + void DetachExternalSoundPlayer(nn::atk::detail::ExternalSoundPlayer*); + u32 GetRemainingFadeFrames() const; + u32 GetRemainingPauseFadeFrames() const; + u32 GetRemainingMuteFadeFrames() const; + void SetInitialVolume(f32 vol); + f32 GetInitialVolume() const; + void SetVolume(f32, s32); + s32 GetVolume() const; + void SetPitch(f32); + f32 GetPitch() const; + void SetLpfFreq(f32); + f32 GetLpfFreq() const; + void SetBiquadFilter(s32, f32); + void GetBiquadFilter(s32*, f32*) const; + void SetOutputLine(u32); + u32 GetOutputLine() const; + void ResetOutputLine(); + void SetMixMode(nn::atk::MixMode); + nn::atk::MixMode GetMixMode(); + void SetPan(f32); + f32 GetPan() const; + void SetSurroundPan(f32); + f32 GetSurroundPan() const; + void SetMainSend(f32); + f32 GetMainSend() const; + + u64* _8; // nn::atk::detail::PlayerHeap* + u64* _10; // nn::atk::SoundHandle* + u64* _18; // nn::atk::SoundHandle* + nn::atk::SoundPlayer* mSoundPlayer; // _20 + u64* _28; // nn::atk::SoundActor* + u64* _30; // nn::atk::detail::ExternalSoundPlayer* + u64* _38; // nn::atk::SoundArchive* + u8 _40[0xF0 - 0x40]; + s32 mPriority; // _F0 + u32 _F4; + u32 _F8; + s32 mAutoStopCounter; // _FC + u64 _100; + u32 mID; // _108 + u32 _10C; + u32 _110; + u32 _114; + f32 mInitialVolume; // _118 + f32 mPitch; // _11C + f32 mLpfFreq; // _120 + f32 _124; + u32 mOutputLine; // _128 + f32 _12C; + f32 mVolume; // _130 + u32 _134; + u32 _138; + nn::atk::MixMode mMixMode; // _13C + f32 mPan; // _140 + f32 mSurroundPan; // _144 + f32 mMainSend; // _148 + u8 _14C[0x158 - 0x14C]; + f32 mOutputVol; // _158 + u8 _15C[0x190 - 0x15C]; + f32 mOutputPan; // _190 + f32 mOutputSurroundPan; // _194 + f32 mOutputMainSend; // _198 + f32 mOutputFxSend; // _19C + + static u64 g_LastInstanceId; + }; + }; // namespace detail +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/detail/SequenceSoundRuntime.h b/include/nn/atk/detail/SequenceSoundRuntime.h new file mode 100644 index 0000000..157464d --- /dev/null +++ b/include/nn/atk/detail/SequenceSoundRuntime.h @@ -0,0 +1,37 @@ +/** + * @file SequenceSoundRuntime.h + * @brief Sequenced Sound Runtime Info + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + namespace detail { + class SoundArchiveManager; + + class SequenceSoundRuntime { + public: + SequenceSoundRuntime(); + ~SequenceSoundRuntime(); + + void Initialize(s32, void**, void const*); + void Finalize(); + void SetupSequenceTrack(s32, void**, void const*); + void SetupUserParam(void**, u64); + bool IsSoundArchiveAvailable() const; + s32 GetActiveCount() const; + s32 GetFreeCount() const; + void SetSequenceSkipIntervalTick(s32 tick); + s32 GetSequenceSkipIntervalTick(); + void Update(); + + u8 _0[0xD0]; + nn::atk::detail::SoundArchiveManager* mArchiveManager; // _D0 + u64 _D8; + }; + }; // namespace detail +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/detail/SoundArchiveManager.h b/include/nn/atk/detail/SoundArchiveManager.h new file mode 100644 index 0000000..755f640 --- /dev/null +++ b/include/nn/atk/detail/SoundArchiveManager.h @@ -0,0 +1,42 @@ +/** + * @file SoundArchiveManager.h + * @brief Sound archive manager implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + class SoundHandle; + class SoundArchive; + class SoundDataManager; + + namespace detail { + class AddonSoundArchiveContainer; + + class SoundArchiveManager { + public: + SoundArchiveManager(); + + virtual ~SoundArchiveManager(); + + void Initialize(nn::atk::SoundArchive const*, nn::atk::SoundDataManager const*); + void ChangeTargetArchive(char const*); + void Finalize(); + bool IsAvailable() const; + nn::atk::detail::AddonSoundArchiveContainer* GetAddonSoundArchive(char const*) const; + + u64 _8; + u64* _10; + nn::atk::detail::AddonSoundArchiveContainer* _18; + u64* _20; + nn::atk::SoundArchive* mSoundArchive; // _28 + u64 _30; + u64 _38; + u64 _40; + }; + }; // namespace detail +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/detail/StreamSoundRuntime.h b/include/nn/atk/detail/StreamSoundRuntime.h new file mode 100644 index 0000000..5a3969b --- /dev/null +++ b/include/nn/atk/detail/StreamSoundRuntime.h @@ -0,0 +1,22 @@ +/** + * @file StreamSoundRuntime.h + * @brief Stream sound runtime information. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + namespace detail { + class StreamSoundRuntime { + public: + StreamSoundRuntime(); + ~StreamSoundRuntime(); + + u8 _0[0xB0]; + }; + }; // namespace detail +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/atk/detail/WaveSoundRuntime.h b/include/nn/atk/detail/WaveSoundRuntime.h new file mode 100644 index 0000000..881b594 --- /dev/null +++ b/include/nn/atk/detail/WaveSoundRuntime.h @@ -0,0 +1,29 @@ +/** + * @file WaveSoundRuntime.h + * @brief Wave sound runtime info. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace atk { + namespace detail { + class WaveSoundRuntime { + public: + WaveSoundRuntime(); + ~WaveSoundRuntime(); + + void Initialize(s32, void**, void const*); + void Finalize(); + s32 GetActiveCount() const; + s32 GetFreeWaveSoundCount() const; + void SetupUserParam(void**, u64); + void Update(); + + u8 _0[0x80]; + }; + }; // namespace detail +}; // namespace atk +}; // namespace nn \ No newline at end of file diff --git a/include/nn/audio.h b/include/nn/audio.h new file mode 100644 index 0000000..5b26d2e --- /dev/null +++ b/include/nn/audio.h @@ -0,0 +1,43 @@ +/** + * @file audio.h + * @brief Audio implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace audio { + struct AudioRendererConfig { + u64* _0; + u64* _8; + u64* _10; + u64* _18; + u64* _20; + u64* _28; + u64* _30; + u64* _38; + u64* _40; + u64* _48; + u64* _50; + }; + + struct DelayType { + u64* _0; + }; + + struct FinalMixType { + u64* _0; + }; + + struct SubMixType { + u64* _0; + }; + + void SetDelayInputOutput(nn::audio::DelayType*, s8 const*, s8 const*, s32); + void* RemoveDelay(nn::audio::AudioRendererConfig*, nn::audio::DelayType*, nn::audio::FinalMixType*); + void* RemoveDelay(nn::audio::AudioRendererConfig*, nn::audio::DelayType*, nn::audio::SubMixType*); + bool IsDelayRemoveable(nn::audio::DelayType*); +}; // namespace audio +}; // namespace nn \ No newline at end of file diff --git a/include/nn/crypto.h b/include/nn/crypto.h new file mode 100644 index 0000000..eefa50d --- /dev/null +++ b/include/nn/crypto.h @@ -0,0 +1,96 @@ +/** + * @file crypto.h + * @brief Crypto service implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace crypto { + void GenerateSha256Hash(void*, ulong, void const*, ulong); + + class Sha256Context; + + void DecryptAes128Cbc(void*, u64, void const*, u64, void const*, u64, void const*, u64); + void EncryptAes128Cbc(void*, u64, void const*, u64, void const*, u64, void const*, u64); + void DecryptAes128Ccm(void*, u64, void*, u64, void const*, u64, void const*, u64, void const*, u64, void const*, + u64, u64); + + template + class AesEncryptor; + + namespace detail { + + template + class CbcMacImpl { + public: + size_t Update(void const*, ulong); + }; + + class Md5Impl { + public: + void Initialize(); + void Update(void const*, u64 dataSize); + void ProcessBlock(); + void GetHash(void*, u64 hashSize); + void ProcessLastBlock(); + + u32 _x0; + u32 _x4; + u32 _x8; + u32 _xC; + u8 _x10[0x50 - 0x10]; + u64 _x50; + u32 _x58; + }; + + class Sha1Impl { + public: + void Initialize(); + void Update(void const*, u64); + void ProcessBlock(void const*); + void GetHash(void* destHash, u64); + void ProcessLastBlock(); + + u64 _x0; + u64 _x8; + u32 _x10; + u128 _x14; + u128 _x24; + u128 _x34; + u32 _x44; + u64 _x48; + u64 _x50; + u64 _x58; + u64 _x60; + }; + + class Sha256Impl { + public: + void Initialize(); + void Update(void const*, u64); + void ProcessBlocks(u8 const*, u64); + void GetHash(void* destHash, u64); + void ProcessLastBlock(); + void InitializeWithContext(nn::crypto::Sha256Context const*); + void GetContext(nn::crypto::Sha256Context*) const; + + u64 _x0; + u64 _x8; + u32 _x10; + u128 _x14; + u128 _x24; + u128 _x34; + u32 _x44; + u64 _x48; + u64 _x50; + u64 _x58; + u64 _x60; + u64 _x68; + u32 _x70; + }; + }; // namespace detail +}; // namespace crypto +}; // namespace nn diff --git a/include/nn/diag.h b/include/nn/diag.h new file mode 100644 index 0000000..408af39 --- /dev/null +++ b/include/nn/diag.h @@ -0,0 +1,36 @@ +/** + * @file diag.h + * @brief Module, logging, and symbol operations. + */ + +#pragma once + +#include "types.h" +#include "os.hpp" + +namespace nn { +namespace diag { + struct LogMetaData; + + struct ModuleInfo { + char* mPath; + u64 mBaseAddr; + u64 mSize; + }; + + namespace detail { + // LOG + void LogImpl(nn::diag::LogMetaData const&, char const*, ...); + void AbortImpl(char const*, char const*, char const*, s32); + void VAbortImpl(char const*, char const*, char const*, int, Result const*, ::nn::os::UserExceptionInfo*, char const* fmt, va_list args); + }; // namespace detail + + // MODULE / SYMBOL + u32* GetSymbolName(char* name, u64 nameSize, u64 addr); + u64 GetRequiredBufferSizeForGetAllModuleInfo(); + s32 GetAllModuleInfo(nn::diag::ModuleInfo** out, void* buffer, u64 bufferSize); + u64 GetSymbolSize(u64 addr); + + int GetBacktrace(uintptr_t* pOutArray, int arrayCountMax); +}; // namespace diag +}; // namespace nn diff --git a/include/nn/err.h b/include/nn/err.h new file mode 100644 index 0000000..b5248b1 --- /dev/null +++ b/include/nn/err.h @@ -0,0 +1,31 @@ +#pragma once + +#include "settings.h" + +namespace nn { + namespace err { + enum ErrorCodeCategoryType : u32 + { + unk1, + unk2, + }; + + class ApplicationErrorArg { + public: + ApplicationErrorArg(); + ApplicationErrorArg(u32 error_code, const char* dialog_message, const char* fullscreen_message, const nn::settings::LanguageCode& languageCode); + void SetApplicationErrorCodeNumber(u32 error_code); + void SetDialogMessage(const char* message); + void SetFullScreenMessage(const char* message); + + u64 unk; + u32 error_code; + nn::settings::LanguageCode language_code; + char dialog_message[2048]; + char fullscreen_message[2048]; + }; + + u32 MakeErrorCode(ErrorCodeCategoryType err_category_type, u32 errorCodeNumber); + void ShowApplicationError(const ApplicationErrorArg& arg); + } +} \ No newline at end of file diff --git a/include/nn/friends.h b/include/nn/friends.h new file mode 100644 index 0000000..ce30a1c --- /dev/null +++ b/include/nn/friends.h @@ -0,0 +1,42 @@ +/** + * @file friends.h + * @brief Friend implementation. + */ + +#pragma once + +#include "account.h" +#include "os.hpp" + +namespace nn { +namespace friends { + typedef char Url[0xA0]; + + class AsyncContext; + class Profile; + + void Initialize(); + Result GetProfileList(nn::friends::AsyncContext* context, nn::friends::Profile* profiles, + nn::account::Uid const& userID, nn::account::NetworkServiceAccountId const* accountIDs, + s32 numAccounts); + + class Profile { + public: + Profile(); + + nn::account::NetworkServiceAccountId GetAccountId() const; + nn::account::Nickname& GetNickname() const; + bool IsValid() const; + Result GetProfileImageUrl(nn::friends::Url*, s32); + }; + + class AsyncContext { + public: + AsyncContext(); + ~AsyncContext(); + + Result GetSystemEvent(nn::os::SystemEvent*); + Result GetResult() const; + }; +}; // namespace friends +}; // namespace nn \ No newline at end of file diff --git a/include/nn/fs.h b/include/nn/fs.h new file mode 100644 index 0000000..ef5e4bb --- /dev/null +++ b/include/nn/fs.h @@ -0,0 +1,106 @@ +/** + * @file fs.h + * @brief Filesystem implementation. + */ + +#pragma once + +#include "account.h" +#include "types.h" + +namespace nn { +typedef u64 ApplicationId; + +namespace fs { + typedef u64 UserId; + + struct DirectoryEntry { + char name[0x300 + 1]; + char _x302[3]; + u8 type; + char _x304; + s64 fileSize; + }; + + struct FileHandle { + void* handle; + + inline bool operator==(const FileHandle& b) const { return handle == b.handle; } + }; + + struct DirectoryHandle { + void* handle; + }; + + enum DirectoryEntryType { DirectoryEntryType_Directory, DirectoryEntryType_File }; + + enum OpenMode { + OpenMode_Read = BIT(0), + OpenMode_Write = BIT(1), + OpenMode_Append = BIT(2), + + OpenMode_ReadWrite = OpenMode_Read | OpenMode_Write + }; + + enum OpenDirectoryMode { + OpenDirectoryMode_Directory = BIT(0), + OpenDirectoryMode_File = BIT(1), + OpenDirectoryMode_All = OpenDirectoryMode_Directory | OpenDirectoryMode_File + }; + + enum WriteOptionFlag { WriteOptionFlag_Flush = BIT(0) }; + + struct WriteOption { + int flags; + + static WriteOption CreateOption(int flags) { + WriteOption op; + op.flags = flags; + return op; + } + }; + + // ROM + Result QueryMountRomCacheSize(u64* size); + Result QueryMountRomCacheSize(u64* size, nn::ApplicationId); + Result MountRom(char const* name, void* buffer, ulong bufferSize); + Result CanMountRomForDebug(); + Result CanMountRom(nn::ApplicationId); + Result QueryMountRomOnFileCacheSize(u64*, nn::fs::FileHandle); + Result MountRomOnFile(char const*, nn::fs::FileHandle, void*, u64); + + // SAVE + Result EnsureSaveData(nn::account::Uid const&); + Result MountSaveData(char const*, nn::fs::UserId); + + // FILE + Result GetEntryType(nn::fs::DirectoryEntryType* type, char const* path); + Result CreateFile(char const* filepath, s64 size); + Result OpenFile(nn::fs::FileHandle*, char const* path, s32); + Result SetFileSize(FileHandle fileHandle, s64 filesize); + void CloseFile(FileHandle fileHandle); + Result FlushFile(FileHandle fileHandle); + Result DeleteFile(char const* filepath); + Result ReadFile(u64* outSize, nn::fs::FileHandle handle, s64 offset, void* buffer, u64 bufferSize, s32 const&); + Result ReadFile(u64* outSize, nn::fs::FileHandle handle, s64 offset, void* buffer, u64 bufferSize); + Result ReadFile(nn::fs::FileHandle handle, s64 offset, void* buffer, u64 bufferSize); + Result WriteFile(FileHandle handle, s64 fileOffset, void const* buff, u64 size, WriteOption const& option); + Result GetFileSize(s64* size, FileHandle fileHandle); + + // DIRECTORY + // there are three open modes; dir, file, all + Result OpenDirectory(DirectoryHandle* handle, char const* path, s32 openMode); + void CloseDirectory(DirectoryHandle directoryHandle); + Result ReadDirectory(s64*, DirectoryEntry*, DirectoryHandle directoryHandle, s64); + Result CreateDirectory(char const* directorypath); + Result GetDirectoryEntryCount(s64*, DirectoryHandle); + + // SD + Result MountSdCard(char const*); + Result MountSdCardForDebug(char const*); + bool IsSdCardInserted(); + Result FormatSdCard(); + Result FormatSdCardDryRun(); + bool IsExFatSupported(); +}; // namespace fs +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/BindFuncTable.h b/include/nn/g3d/BindFuncTable.h new file mode 100644 index 0000000..9eaceab --- /dev/null +++ b/include/nn/g3d/BindFuncTable.h @@ -0,0 +1,15 @@ +#pragma once + +#include "types.h" + +namespace nn { +namespace g3d { + struct DDLDeclarations { + u64 _0; + u32 _8; + u32 DDLDeclarations_xC; + u64 _10; + u64 _18; + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResFile.h b/include/nn/g3d/ResFile.h new file mode 100644 index 0000000..a1abdd4 --- /dev/null +++ b/include/nn/g3d/ResFile.h @@ -0,0 +1,67 @@ +/** + * @file ResFile.h + * @brief Resource file for models. + */ + +#pragma once + +#include "ResMaterialAnim.h" +#include "ResModel.h" +#include "ResSceneAnim.h" +#include "ResShapeAnim.h" +#include "nn/gfx/api.h" +#include "nn/gfx/device.h" +#include "nn/gfx/memory.h" +#include "nn/util.h" +#include "types.h" + +namespace nn { +namespace g3d { + typedef void* TextureRef; + + class ResFile : public nn::util::BinaryFileHeader { + public: + static bool IsValid(void const* modelSrc); + void Relocate(); + void Unrelocate(); + static nn::g3d::ResFile* ResCast(void*); + s32 BindTexture(nn::g3d::TextureRef (*ref)(char const*, void*), void*); + void ReleaseTexture(); + void Setup(nn::gfx::TDevice, nn::gfx::ApiVersion<8>>>*); + void Setup(nn::gfx::TDevice, nn::gfx::ApiVersion<8>>>*, + nn::gfx::TMemoryPool, nn::gfx::ApiVersion<8>>>*, s64, + u64); + void Cleanup(nn::gfx::TDevice, nn::gfx::ApiVersion<8>>>*); + void Reset(); + + u64 mFileNameLength; // _20 + nn::g3d::ResModel* mModels; // _28 + u64 mModelDictOffset; // _30 + u64 mSkeleAnimOffset; // _38 + u64 mSkeleAnimDictOffset; // _40 + nn::g3d::ResMaterialAnim* mMatAnims; // _48 + u64 mMatAnimsDictOffset; // _50 + u64 mBoneVisiOffset; // _58 + u64 mBoneVisiDictOffset; // _60 + nn::g3d::ResShapeAnim* mShapeAnims; // _68 + u64 mShapeAnimDictOffset; // _70 + nn::g3d::ResSceneAnim* mSceneAnims; // _78 + u64 mSceneAnimDictOffset; // _80 + u64 mMemoryPool; // _88 + u64 mBufferSection; // _90 + u64 mEmbeddedFilesOffset; // _98 + u64 mEmbeddedFilesDictOffset; // _A0 + u64 mPadding; // _A8 + u64 mStrTableOffset; // _B0 + u32 mStrTableSize; // _B8 + u16 mModelCount; // _BC + u16 mSkeleAnimCount; // _BE + u16 mMatAnimCount; // _C0 + u16 mBoneAnimCount; // _C2 + u16 mShapeAnimCount; // _C4 + u16 mSceneAnimCount; // _C6 + u16 mExternalFileCount; // _C8 + u8 mPad[0x6]; // _CA + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResFogAnim.h b/include/nn/g3d/ResFogAnim.h new file mode 100644 index 0000000..ae7fc9b --- /dev/null +++ b/include/nn/g3d/ResFogAnim.h @@ -0,0 +1,21 @@ +#pragma once + +#include "types.h" + +namespace nn { +namespace g3d { + class ResFogAnim { + public: + char mMagic[4]; // _0 + u16 mFlags; // _4 + u16 mPad; // _6 + s32 mNumFrames; // _8 + u8 mNumCurves; // _C + u8 mIdxDistanceAttnFunc; // _D + u16 mNumUserData; // _E + u32 mSizeBaked; // _10 + u64 mNameOffset; // _14 + u64 mFuncNameOffset; // _1C + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResLightAnim.h b/include/nn/g3d/ResLightAnim.h new file mode 100644 index 0000000..a923bbb --- /dev/null +++ b/include/nn/g3d/ResLightAnim.h @@ -0,0 +1,12 @@ +#pragma once + +#include "BindFuncTable.h" + +namespace nn { +namespace g3d { + class ResLightAnim { + public: + s32 Bind(nn::g3d::BindFuncTable const&); + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResMaterial.h b/include/nn/g3d/ResMaterial.h new file mode 100644 index 0000000..dd13d98 --- /dev/null +++ b/include/nn/g3d/ResMaterial.h @@ -0,0 +1,29 @@ +/** + * @file ResMaterial.h + * @brief Resource material for models. + */ + +#pragma once + +#include "nn/gfx/api.h" +#include "nn/gfx/device.h" +#include "types.h" + +namespace nn { +namespace g3d { + typedef void* TextureRef; + + class ResMaterial { + public: + u64 BindTexture(nn::g3d::TextureRef (*)(char const*, void*), void*); + void ForceBindTexture(nn::g3d::TextureRef const&, char const*); + void ReleaseTexture(); + void Setup(nn::gfx::TDevice, nn::gfx::ApiVersion<4>>>*); + void Cleanup(nn::gfx::TDevice, nn::gfx::ApiVersion<8>>>*); + void Reset(); + void Reset(u32); + + u8 _0[0xB4]; + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResMaterialAnim.h b/include/nn/g3d/ResMaterialAnim.h new file mode 100644 index 0000000..e36a1ca --- /dev/null +++ b/include/nn/g3d/ResMaterialAnim.h @@ -0,0 +1,19 @@ +/** + * @file ResMaterialAnim.h + * @brief Resource file for material animations. + */ + +#pragma once + +namespace nn { +namespace g3d { + typedef void* TextureRef; + + class ResMaterialAnim { + public: + void ReleaseTexture(); + s32 BindTexture(nn::g3d::TextureRef (*)(char const*, void*), void*); + void Reset(); + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResModel.h b/include/nn/g3d/ResModel.h new file mode 100644 index 0000000..a71f42c --- /dev/null +++ b/include/nn/g3d/ResModel.h @@ -0,0 +1,32 @@ +/** + * @file ResModel.h + * @brief Resource model. + */ + +#pragma once + +#include "nn/gfx/api.h" +#include "nn/gfx/device.h" +#include "types.h" + +namespace nn { +namespace g3d { + class ResMaterial; + + typedef void* TextureRef; + + class ResModel { + public: + u64 BindTexture(nn::g3d::TextureRef (*)(char const*, void*), void*); + void ForceBindTexture(nn::g3d::TextureRef const&, char const*); + void ReleaseTexture(); + void Setup(nn::gfx::TDevice, nn::gfx::ApiVersion<8>>>*); + void Cleanup(nn::gfx::TDevice, nn::gfx::ApiVersion<8>>>*); + void Reset(); + void Reset(u32); + nn::g3d::ResMaterial* FindMaterial(char const* materialName) const; + + u8 _0[0x70]; + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResSceneAnim.h b/include/nn/g3d/ResSceneAnim.h new file mode 100644 index 0000000..792d9b2 --- /dev/null +++ b/include/nn/g3d/ResSceneAnim.h @@ -0,0 +1,40 @@ +/** + * @file ResSceneAnim.h + * @brief Resource file for scene animations. + */ + +#pragma once + +#include "BindFuncTable.h" +#include "ResFogAnim.h" +#include "ResLightAnim.h" +#include "types.h" + +namespace nn { +namespace g3d { + class ResSceneAnim { + public: + s32 Bind(nn::g3d::BindFuncTable const&); + void Release(); + void Reset(); + + char mMagic[4]; // _0 + s32 mBlockOffset; // _4 + u64 mBlockSize; // _8 + u64 mNameOffset; // _10 + u64 mPathOffset; // _18 + u64 mCameraAnimOffset; // _20 + u64 mCameraAnimDictOffset; // _28 + nn::g3d::ResLightAnim* mLightAnims; // _30 + u64 mLightAnimDictOffset; // _38 + nn::g3d::ResFogAnim* mFogAnims; // _40 + u64 mFogAnimDictOffset; // _48 + u64 mUserDataOffset; // _50 + u64 mUserDataDictOffset; // _58 + u16 mUserDataCount; // _60 + u16 mCameraAnimCount; // _62 + u16 mLightAnimCount; // _64 + u16 mFogAnimCount; // _66 + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResShapeAnim.h b/include/nn/g3d/ResShapeAnim.h new file mode 100644 index 0000000..f8ba474 --- /dev/null +++ b/include/nn/g3d/ResShapeAnim.h @@ -0,0 +1,15 @@ +/** + * @file ResShapeAnim.h + * @brief Resource file for shape animations. + */ + +#pragma once + +namespace nn { +namespace g3d { + class ResShapeAnim { + public: + void Reset(); + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/g3d/ResSkeletalAnim.h b/include/nn/g3d/ResSkeletalAnim.h new file mode 100644 index 0000000..48de05c --- /dev/null +++ b/include/nn/g3d/ResSkeletalAnim.h @@ -0,0 +1,15 @@ +/** + * @file ResSkeletalAnim.h + * @brief Resource file for skeletal animations. + */ + +#pragma once + +namespace nn { +namespace g3d { + class ResSkeletalAnim { + public: + void Reset(); + }; +}; // namespace g3d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/api.h b/include/nn/gfx/api.h new file mode 100644 index 0000000..f3f9f30 --- /dev/null +++ b/include/nn/gfx/api.h @@ -0,0 +1,22 @@ +/** + * @file api.h + * @brief GFX API version and typing. + */ + +#pragma once + +namespace nn { +namespace gfx { + // passes both ApiType<4> and ApiVersion<8> + template + class ApiVariation {}; + + // usually passed as just a 4 + template + class ApiType {}; + + // usually passed as just a 8 + template + class ApiVersion {}; +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/buffer.h b/include/nn/gfx/buffer.h new file mode 100644 index 0000000..f1cf5d9 --- /dev/null +++ b/include/nn/gfx/buffer.h @@ -0,0 +1,28 @@ +/** + * @file buffer.h + * @brief GFX Buffers. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace gfx { + class BufferInfo { + public: + void SetDefault(); + + u64 mInfo; // _0 + }; + + class BufferTextureViewInfo { + public: + void SetDefault(); + + u64 _0; + u64 _8; + u64 _10; + }; +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/detail/bufferimpl.h b/include/nn/gfx/detail/bufferimpl.h new file mode 100644 index 0000000..42ae4b1 --- /dev/null +++ b/include/nn/gfx/detail/bufferimpl.h @@ -0,0 +1,51 @@ +/** + * @file bufferimpl.h + * @brief Buffer implementation for GFX. + */ + +#pragma once + +#include "nn/gfx/api.h" +#include "nn/gfx/buffer.h" + +namespace nn { +namespace gfx { + class GpuAddress; + + namespace detail { + template + class BufferImpl { + public: + BufferImpl(); + ~BufferImpl(); + + void Initialize( + nn::gfx::detail::DeviceImpl, nn::gfx::ApiVersion<8>>>*, + nn::gfx::BufferInfo const&, + nn::gfx::detail::MemoryPoolImpl, nn::gfx::ApiVersion<8>>>*, + s64, u64); + void Finalize( + nn::gfx::detail::DeviceImpl, nn::gfx::ApiVersion<8>>>*); + void* Map() const; + void Unmap() const; + void FlushMappedRange(s64, u64) const; + void InvalidateMappedRange(s64, u64) const; + void GetGpuAddress(nn::gfx::GpuAddress*) const; + + T* mBuff; // _0 + }; + + template + class CommandBufferImpl { + public: + CommandBufferImpl(); + ~CommandBufferImpl(); + + void Reset(); + void Begin(); + void End(); + void Dispatch(s32, s32, s32); + }; + }; // namespace detail +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/detail/deviceimpl.h b/include/nn/gfx/detail/deviceimpl.h new file mode 100644 index 0000000..9322a38 --- /dev/null +++ b/include/nn/gfx/detail/deviceimpl.h @@ -0,0 +1,24 @@ +/** + * @file deviceimpl.h + * @brief Device implementation for GFX. + */ + +#pragma once + +#include "nn/gfx/device.h" + +namespace nn { +namespace gfx { + namespace detail { + template + class DeviceImpl { + public: + DeviceImpl(); + ~DeviceImpl(); + + void Initialize(nn::gfx::DeviceInfo const& deviceInfo); + void Finalize(); + }; + }; // namespace detail +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/detail/pool.h b/include/nn/gfx/detail/pool.h new file mode 100644 index 0000000..efdce87 --- /dev/null +++ b/include/nn/gfx/detail/pool.h @@ -0,0 +1,39 @@ +#pragma once + +#include "deviceimpl.h" + +namespace nn { +namespace gfx { + struct MemoryPoolInfo; + + namespace detail { + class MemoryPoolData { + public: + void SetDefault(); + + s32 _0; // set to 0x88 + s32 _4; + u64 _8; + }; + + template + class MemoryPoolImpl { + public: + MemoryPoolImpl(); + ~MemoryPoolImpl(); + + void Initialize( + nn::gfx::detail::DeviceImpl, nn::gfx::ApiVersion<8>>>*, + nn::gfx::MemoryPoolInfo const&); + void Finalize( + nn::gfx::detail::DeviceImpl, nn::gfx::ApiVersion<8>>>*); + void* Map() const; + void Unmap() const; + void FlushMappedRange(s64, u64) const; + void InvalidateMappedRange(s64, u64) const; + + u8 _0[0x120]; // pool data + }; + }; // namespace detail +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/device.h b/include/nn/gfx/device.h new file mode 100644 index 0000000..8b46b64 --- /dev/null +++ b/include/nn/gfx/device.h @@ -0,0 +1,20 @@ +/** + * @file device.h + * @brief Device implementation for GFX. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace gfx { + class DeviceInfo { + public: + u64 mInfo; // _0 + }; + + template + class TDevice {}; +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/gfx/memory.h b/include/nn/gfx/memory.h new file mode 100644 index 0000000..a6d4865 --- /dev/null +++ b/include/nn/gfx/memory.h @@ -0,0 +1,14 @@ +/** + * @file memory.h + * @brief GFX Memory Pool. + */ + +#pragma once + +namespace nn { +namespace gfx { + // todo: finish me! + template + class TMemoryPool {}; +}; // namespace gfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/hid.hpp b/include/nn/hid.hpp new file mode 100644 index 0000000..de939f1 --- /dev/null +++ b/include/nn/hid.hpp @@ -0,0 +1,218 @@ +/** + * @file hid.hpp + * @brief Functions that help process gamepad inputs. + */ + +#pragma once + +#include "time.h" +#include "types.h" +#include "util.h" + +namespace nn { +namespace hid { + /// HidControllerKeys + typedef enum { + KEY_A = BIT(0), ///< A + KEY_B = BIT(1), ///< B + KEY_X = BIT(2), ///< X + KEY_Y = BIT(3), ///< Y + KEY_LSTICK = BIT(4), ///< Left Stick Button + KEY_RSTICK = BIT(5), ///< Right Stick Button + KEY_L = BIT(6), ///< L + KEY_R = BIT(7), ///< R + KEY_ZL = BIT(8), ///< ZL + KEY_ZR = BIT(9), ///< ZR + KEY_PLUS = BIT(10), ///< Plus + KEY_MINUS = BIT(11), ///< Minus + KEY_DLEFT = BIT(12), ///< D-Pad Left + KEY_DUP = BIT(13), ///< D-Pad Up + KEY_DRIGHT = BIT(14), ///< D-Pad Right + KEY_DDOWN = BIT(15), ///< D-Pad Down + KEY_LSTICK_LEFT = BIT(16), ///< Left Stick Left + KEY_LSTICK_UP = BIT(17), ///< Left Stick Up + KEY_LSTICK_RIGHT = BIT(18), ///< Left Stick Right + KEY_LSTICK_DOWN = BIT(19), ///< Left Stick Down + KEY_RSTICK_LEFT = BIT(20), ///< Right Stick Left + KEY_RSTICK_UP = BIT(21), ///< Right Stick Up + KEY_RSTICK_RIGHT = BIT(22), ///< Right Stick Right + KEY_RSTICK_DOWN = BIT(23), ///< Right Stick Down + KEY_SL_LEFT = BIT(24), ///< SL on Left Joy-Con + KEY_SR_LEFT = BIT(25), ///< SR on Left Joy-Con + KEY_SL_RIGHT = BIT(26), ///< SL on Right Joy-Con + KEY_SR_RIGHT = BIT(27), ///< SR on Right Joy-Con + + // Pseudo-key for at least one finger on the touch screen + KEY_TOUCH = BIT(28), + + // Buttons by orientation (for single Joy-Con), also works with Joy-Con pairs, Pro Controller + KEY_JOYCON_RIGHT = BIT(0), + KEY_JOYCON_DOWN = BIT(1), + KEY_JOYCON_UP = BIT(2), + KEY_JOYCON_LEFT = BIT(3), + + // Generic catch-all directions, also works for single Joy-Con + KEY_UP = KEY_DUP | KEY_LSTICK_UP | KEY_RSTICK_UP, ///< D-Pad Up or Sticks Up + KEY_DOWN = KEY_DDOWN | KEY_LSTICK_DOWN | KEY_RSTICK_DOWN, ///< D-Pad Down or Sticks Down + KEY_LEFT = KEY_DLEFT | KEY_LSTICK_LEFT | KEY_RSTICK_LEFT, ///< D-Pad Left or Sticks Left + KEY_RIGHT = KEY_DRIGHT | KEY_LSTICK_RIGHT | KEY_RSTICK_RIGHT, ///< D-Pad Right or Sticks Right + KEY_SL = KEY_SL_LEFT | KEY_SL_RIGHT, ///< SL on Left or Right Joy-Con + KEY_SR = KEY_SR_LEFT | KEY_SR_RIGHT, ///< SR on Left or Right Joy-Con + } HidControllerKeys; + + // NpadFlags + typedef enum { + NPAD_CONNECTED = BIT(0), + NPAD_WIRED = BIT(1), + } NpadFlags; + + /// NpadId + typedef enum { + CONTROLLER_PLAYER_1 = 0, + CONTROLLER_PLAYER_2 = 1, + CONTROLLER_PLAYER_3 = 2, + CONTROLLER_PLAYER_4 = 3, + CONTROLLER_PLAYER_5 = 4, + CONTROLLER_PLAYER_6 = 5, + CONTROLLER_PLAYER_7 = 6, + CONTROLLER_PLAYER_8 = 7, + CONTROLLER_HANDHELD = 0x20, + } NpadId; + + struct NpadHandheldState { + s64 updateCount; + u64 Buttons; + s32 LStickX; + s32 LStickY; + s32 RStickX; + s32 RStickY; + u32 Flags; + }; + // Seems to be the same? + struct NpadFullKeyState : NpadHandheldState {}; + struct NpadJoyDualState : NpadHandheldState {}; + + struct NpadStyleTag; + struct NpadStyleSet { + u32 flags; + }; + constexpr nn::hid::NpadStyleSet NpadStyleFullKey = {BIT(0)}; + constexpr nn::hid::NpadStyleSet NpadStyleHandheld = {BIT(1)}; + constexpr nn::hid::NpadStyleSet NpadStyleJoyDual = {BIT(2)}; + constexpr nn::hid::NpadStyleSet NpadStyleJoyLeft = {BIT(3)}; + constexpr nn::hid::NpadStyleSet NpadStyleJoyRight = {BIT(4)}; + + struct ControllerSupportArg { + u8 mMinPlayerCount; + u8 mMaxPlayerCount; + u8 mTakeOverConnection; + bool mLeftJustify; + bool mPermitJoyconDual; + bool mSingleMode; + bool mUseColors; + nn::util::Color4u8 mColors[4]; + u8 mUsingControllerNames; + char mControllerNames[4][0x81]; + }; + + struct ControllerSupportResultInfo { + int mPlayerCount; + int mSelectedId; + }; + + struct GesturePoint { + s32 x; + s32 y; + }; + + enum GestureDirection { + GESTUREDIRECTION_NONE, + GESTUREDIRECTION_LEFT, + GESTUREDIRECTION_UP, + GESTUREDIRECTION_RIGHT, + GESTUREDIRECTION_DOWN, + }; + + enum GestureType { + GESTURETYPE_IDLE, + GESTURETYPE_COMPLETE, + GESTURETYPE_CANCEL, + GESTURETYPE_TOUCH, + GESTURETYPE_PRESS, + GESTURETYPE_TAP, + GESTURETYPE_PAN, + GESTURETYPE_SWIPE, + GESTURETYPE_PINCH, + GESTURETYPE_ROTATE, + }; + + enum GyroscopeZeroDriftMode { + GyroscopeZeroDriftMode_Loose, + GyroscopeZeroDriftMode_Standard, + GyroscopeZeroDriftMode_Tight, + }; + + struct SixAxisSensorHandle { + u32 handle; + }; + + struct GestureState { + s64 updateNum; + s64 detectionNum; + s32 type; + s32 direction; + s32 x; + s32 y; + s32 deltaX; + s32 deltaY; + ::nn::util::Float2 velocity; + u32 attributes; + float scale; + float rotationAngle; + s32 pointCount; + GesturePoint points[4]; + }; + + struct DirectionState { + ::nn::util::Float3 x; + ::nn::util::Float3 y; + ::nn::util::Float3 z; + }; + + struct SixAxisSensorState { + ::nn::TimeSpan deltaUpdateTime; + s64 updateNum; + ::nn::util::Float3 acceleration; + ::nn::util::Float3 angularVelocity; + ::nn::util::Float3 angle; + DirectionState direction; + u32 attributes; + }; + + void InitializeNpad(); + void SetSupportedNpadIdType(u32 const*, u64); + void SetSupportedNpadStyleSet(nn::util::BitFlagSet<32, nn::hid::NpadStyleTag>); + NpadStyleSet GetNpadStyleSet(u32 const&); + // returns the number of states put into the array + int GetNpadStates(nn::hid::NpadHandheldState* outArray, s32 count, u32 const& controllerID); + int GetNpadStates(nn::hid::NpadFullKeyState* outArray, s32 count, u32 const& controllerID); + int GetNpadStates(nn::hid::NpadJoyDualState* outArray, s32 count, u32 const& controllerID); + void GetNpadState(nn::hid::NpadHandheldState* out, u32 const& controllerID); + void GetNpadState(nn::hid::NpadFullKeyState* out, u32 const& controllerID); + void GetNpadState(nn::hid::NpadJoyDualState* out, u32 const& controllerID); + int GetGestureStates(GestureState* outArray, int count); + int GetSixAxisSensorHandles(SixAxisSensorHandle* pOutValues, int count, u32 const& controllerID, + NpadStyleSet style); + void StartSixAxisSensor(const SixAxisSensorHandle& handle); + void StopSixAxisSensor(const SixAxisSensorHandle& handle); + bool IsSixAxisSensorAtRest(const SixAxisSensorHandle& handle); + void GetSixAxisSensorState(SixAxisSensorState* outValue, const SixAxisSensorHandle& handle); + int GetSixAxisSensorStates(SixAxisSensorState* outStates, int count, const SixAxisSensorHandle& handle); + bool IsSixAxisSensorFusionEnabled(const SixAxisSensorHandle& handle); + void EnableSixAxisSensorFusion(const SixAxisSensorHandle& handle, bool enable); + void SetGyroscopeZeroDriftMode(const SixAxisSensorHandle& handle, const GyroscopeZeroDriftMode& mode); + GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode(const SixAxisSensorHandle& handle); + int ShowControllerSupport(nn::hid::ControllerSupportResultInfo*, ControllerSupportArg const&); + +}; // namespace hid +}; // namespace nn diff --git a/include/nn/image.h b/include/nn/image.h new file mode 100644 index 0000000..17b99e3 --- /dev/null +++ b/include/nn/image.h @@ -0,0 +1,50 @@ +/** + * @file image.h + * @brief JPEG decoding library. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace image { + // there's probably more + enum JpegStatus { + OK = 0, + INVALID_FORMAT = -32, + UNSUPPORTED_FORMAT = -33, + OUT_OF_MEMORY = -64, + }; + + enum PixelFormat { RGBA32, RGB24 }; + + enum ProcessStage { UNREGISTERED = 0, REGISTERED = 1, ANALYZED = 2 }; + + struct Dimension { + f32 width; + f32 height; + }; + + class JpegDecoder { + public: + JpegDecoder(); + virtual ~JpegDecoder(); + + void SetImageData(void const* source, u64 size); + nn::image::JpegStatus Analyze(); + nn::image::Dimension GetAnalyzedDimension() const; + s64 GetAnalyzedWorkBufferSize() const; + JpegStatus Decode(void* out, s64, s32 alignment, void*, s64); + + nn::image::ProcessStage mProcessStage; // _8 + void* mData; // _C + s64 mSize; // _14 + s32 _18; + nn::image::PixelFormat mFormat; // _1C + Dimension mImgDimensions; // _20 + s64 _28; + // rest is related to EXIF processing + }; +}; // namespace image +}; // namespace nn \ No newline at end of file diff --git a/include/nn/init.h b/include/nn/init.h new file mode 100644 index 0000000..83812db --- /dev/null +++ b/include/nn/init.h @@ -0,0 +1,21 @@ +/** + * @file init.h + * @brief Initialization functions for OS related functions. + */ + +#pragma once + +#include "mem.h" +#include "types.h" + +namespace nn { +namespace init { + void InitializeAllocator(void* addr, u64 size); + nn::mem::StandardAllocator* GetAllocator(); + + namespace detail { + void* DefaultAllocatorForThreadLocal(u64, u64); + void* DefaultDeallocatorForThreadLocal(void*, u64); + }; // namespace detail +} // namespace init +}; // namespace nn \ No newline at end of file diff --git a/include/nn/mem.h b/include/nn/mem.h new file mode 100644 index 0000000..5e50c90 --- /dev/null +++ b/include/nn/mem.h @@ -0,0 +1,34 @@ +/** + * @file mem.h + * @brief Memory allocation functions. + */ + +#pragma once + +#include "os.hpp" +#include "types.h" + +namespace nn { +namespace mem { + + class StandardAllocator { + + public: + StandardAllocator(); + + void Initialize(void* address, u64 size); + void Finalize(); + void* Reallocate(void* address, u64 newSize); + void* Allocate(u64 size); + void* Allocate(u64 size, u64 alignment); + void Free(void* address); + void Dump(); + + bool mIsInitialized; // _0 + bool mIsEnabledThreadCache; // _1 + u16 _2; + u64* mAllocAddr; // _4 + u8 _12[20]; + }; +}; // namespace mem +}; // namespace nn diff --git a/include/nn/nex/RootObject.h b/include/nn/nex/RootObject.h new file mode 100644 index 0000000..cb9d87c --- /dev/null +++ b/include/nn/nex/RootObject.h @@ -0,0 +1,30 @@ +/** + * @file RootObject.h + * @brief RootObject for NEX. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace nex { + class RootObject { + public: + enum TargetPool; + + virtual ~RootObject(); + + void* operator new(std::u64); + void operator delete(void*); + void* operator new(std::u64, char const*, u32); + void* operator new[](std::u64); + void* operator new[](std::u64, char const*, u32); + void operator delete[](void*); + void operator delete(void*, char const*, u32); + void operator delete[](void*, char const*, u32); + void* operator new(std::u64, nn::nex::RootObject::TargetPool); + void* operator new(std::u64, nn::nex::RootObject::TargetPool, char const*, u32); + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/auth.h b/include/nn/nex/auth.h new file mode 100644 index 0000000..3426005 --- /dev/null +++ b/include/nn/nex/auth.h @@ -0,0 +1,21 @@ +/** + * @file auth.h + * @brief Authorization for DDL. + */ + +#pragma once + +#include "ddl.h" +#include "types.h" + +namespace nn { +namespace nex { + class NintendoAuthenticationDDLDeclarations : public nn::nex::DDLDeclarations { + public: + virtual ~NintendoAuthenticationDDLDeclarations(); + virtual void Init(); + + void Register(); + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/buffer.h b/include/nn/nex/buffer.h new file mode 100644 index 0000000..6689f36 --- /dev/null +++ b/include/nn/nex/buffer.h @@ -0,0 +1,25 @@ +/** + * @file buffer.h + * @brief NEX buffer implementation. + */ + +#pragma once + +#include "string.h" +#include "types.h" + +namespace nn { +namespace nex { + // todo + class Buffer { + public: + Buffer(nn::nex::Buffer const&); + Buffer(nn::nex::Buffer&&); + Buffer(nn::nex::String const&); + + void FreeDataBuffer(u8*, u64); + + virtual ~Buffer(); + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/cache.h b/include/nn/nex/cache.h new file mode 100644 index 0000000..21cafc8 --- /dev/null +++ b/include/nn/nex/cache.h @@ -0,0 +1,32 @@ +/** + * @file cache.h + * @brief NEX Cache Mangement. + */ + +#pragma once + +#include "string.h" + +namespace nn { +namespace nex { + class BasicCache; + + class CacheManager { + public: + CacheManager(); + ~CacheManager(); + + nn::nex::BasicCache* GetCache(nn::nex::String const&); + }; + + class BasicCache { + public: + BasicCache(nn::nex::String const&); + + virtual ~BasicCache(); + + u64 _8; + u8 _10; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/checksum.h b/include/nn/nex/checksum.h new file mode 100644 index 0000000..9c80c79 --- /dev/null +++ b/include/nn/nex/checksum.h @@ -0,0 +1,61 @@ +/** + * @file checksum.h + * @brief NEX Checksum Implementation. + */ + +#pragma once + +#include "RootObject.h" +#include "buffer.h" +#include "types.h" + +namespace nn { +namespace nex { + class ChecksumAlgorithm : public nn::nex::RootObject { + public: + ChecksumAlgorithm(); + + virtual ~ChecksumAlgorithm(); + + virtual bool ComputeChecksum(nn::nex::Buffer const&, nn::nex::Buffer*) = 0; + // virtual bool ComputeChecksum(u8 const **, u64 const *, u64, nn::nex::SignatureBytes &) = 0; + virtual bool IsReady() const; + virtual void ComputeChecksumForTransport(u8 const*, u64); + virtual u32 ComputeChecksumForTransportArray(u8 const**, u64 const*, u64) = 0; + virtual u32 GetChecksumLength() = 0; + + u64 _8; + u8 _10; + }; + + class MD5Checksum : public nn::nex::ChecksumAlgorithm { + public: + MD5Checksum(); + + virtual ~MD5Checksum(); + + virtual bool ComputeChecksum(nn::nex::Buffer const&, nn::nex::Buffer*); + virtual u32 ComputeChecksumForTransportArray(u8 const**, u64 const*, u64); + virtual u32 GetChecksumLength(); + }; + + class CRC16Checksum : public nn::nex::ChecksumAlgorithm { + public: + CRC16Checksum(); + + virtual ~CRC16Checksum(); + + virtual bool ComputeChecksum(nn::nex::Buffer const&, nn::nex::Buffer*); + virtual u32 ComputeChecksumForTransportArray(u8 const**, u64 const*, u64); + virtual u32 GetChecksumLength(); + }; + + /* + this actually inherits some sort of KeyedChecksum thing. whatever + class HMACChecksum : public nn::nex::ChecksumAlgorithm + { + + }; + */ +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/client.h b/include/nn/nex/client.h new file mode 100644 index 0000000..64db8ee --- /dev/null +++ b/include/nn/nex/client.h @@ -0,0 +1,110 @@ +/** + * @file client.h + * @brief Client implementations for NEX. + */ +#pragma once + +#include "system.h" + +namespace nn { +namespace nex { + class Credentials; + class EndPoint; + class Message; + class ProtocolCallContext; + class ProtocolRequestBrokerInterface; + + class Protocol : public nn::nex::SystemComponent { + public: + enum _Command { Response, Request }; + + enum _Type { + Client, // implemented in nn::nex::ClientProtocol + Server // implemented in nn::nex::ServerProtocol + }; + + Protocol(u32); + + virtual ~Protocol(); + + virtual char* GetType() const; + virtual bool IsAKindOf(char const*) const; + virtual void EnforceDeclareSysComponentMacro(); + + virtual bool BeginInitialization(); + virtual bool BeginTermination(); + + virtual nn::nex::Protocol::_Type GetProtocolType() const = 0; + virtual void EndPointDisconnected(nn::nex::EndPoint*); + virtual void FaultDetected(nn::nex::EndPoint*, u32); + virtual nn::nex::Protocol* Clone() const; + virtual bool Reload(); + + nn::nex::EndPoint* GetOutgoingConnection() const; + void SetIncomingConnection(nn::nex::EndPoint*); + void SetProtocolID(u16); + void AddMethodID(nn::nex::Message*, u32); + void CopyMembers(nn::nex::Protocol const*); + void AssociateProtocolRequestBroker(nn::nex::ProtocolRequestBrokerInterface*); + void ClearFlag(u32 newFlag); + + static void ExtractProtocolKey(nn::nex::Message*, nn::nex::Protocol::_Command&, u16&); + static bool IsOldRVDDLVersion(nn::nex::EndPoint*); + + u16 mProtocolID; // _48 + u16 _4A; + u32 _4C; + nn::nex::EndPoint* mOutgoingConnection; // _50 + nn::nex::ProtocolRequestBrokerInterface* mBrokerInterface; // _58 + u32 mFlags; // _60 + u32 _64; + nn::nex::EndPoint* mIncomingConnection; // _68 + u32 mUseLoopback; // _70 (boolean) + u32 _74; + u64 _78; + u32 _80; + u32 _84; + }; + + class ClientProtocol : public nn::nex::Protocol { + public: + ClientProtocol(u32); + + virtual ~ClientProtocol(); + + virtual char* GetType() const; + virtual bool IsAKindOf(char const*) const; + virtual void EnforceDeclareSysComponentMacro(); + + virtual nn::nex::Protocol::_Type GetProtocolType() const = 0; + + virtual void ExtractCallSpecificResults(nn::nex::Message*, nn::nex::ProtocolCallContext*) = 0; + virtual nn::nex::ClientProtocol* CreateResponder() const = 0; + virtual void SetDefaultCredentials(nn::nex::Credentials*); + + bool SendOverLocalLoopback(nn::nex::ProtocolCallContext*, nn::nex::Message*); + bool SendRMCMessage(nn::nex::ProtocolCallContext*, nn::nex::Message*); + void ProcessResponse(nn::nex::Message*, nn::nex::EndPoint*); + + nn::nex::Credentials* mCredentials; // _88 + }; + + class ServerProtocol : public nn::nex::Protocol { + public: + ServerProtocol(u32); + + virtual ~ServerProtocol(); + + virtual char* GetType() const; + virtual bool IsAKindOf(char const*) const; + virtual void EnforceDeclareSysComponentMacro(); + + virtual nn::nex::Protocol::_Type GetProtocolType() const = 0; + + virtual void DispatchProtocolMessage(nn::nex::Message*, nn::nex::Message*, bool*, nn::nex::EndPoint*) = 0; + virtual void DispatchProtocolMessageWithAttemptCount(u64, nn::nex::Message*, nn::nex::Message*, bool*, int*, + nn::nex::EndPoint*); + virtual bool UseAttemptCountMethod(); + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/data.h b/include/nn/nex/data.h new file mode 100644 index 0000000..8ccb6cb --- /dev/null +++ b/include/nn/nex/data.h @@ -0,0 +1,21 @@ +/** + * @file data.h + * @brief NEX Data. + */ + +#pragma once + +#include "RootObject.h" + +namespace nn { +namespace nex { + class Data : public nn::nex::RootObject { + public: + Data(); + + virtual ~Data(); + + u8 _8; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/ddl.h b/include/nn/nex/ddl.h new file mode 100644 index 0000000..91b8a31 --- /dev/null +++ b/include/nn/nex/ddl.h @@ -0,0 +1,41 @@ +/** + * @file ddl.h + * @brief DDL Declaration Implementation. + */ + +#pragma once + +#include "RootObject.h" +#include "types.h" + +namespace nn { +namespace nex { + class DDLDeclarations : public nn::nex::RootObject { + public: + DDLDeclarations(bool); + + virtual ~DDLDeclarations(); + + virtual void Init() = 0; + + void RegisterIfRequired(); + void Unregister(); + static void UnregisterAll(); + void LoadAll(); + void Load(); + void UnloadAll(); + void Unload(); + void ResetDOClassIDs(); + + u32 mNumDecsLoaded; // _8 + u8 DDLDeclarations_xC; + u8 _D; // padding + u8 _E; // ^^ + u8 _F; // ^^ + u64 _10; + bool _18; + + static nn::nex::DDLDeclarations* s_pFirstDDLDecl; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/dynamic.h b/include/nn/nex/dynamic.h new file mode 100644 index 0000000..fbc780f --- /dev/null +++ b/include/nn/nex/dynamic.h @@ -0,0 +1,21 @@ +/** + * @file dynamic.h + * @brief NEX Dyamnic Runtime. + */ + +#pragma once + +#include "RootObject.h" + +namespace nn { +namespace nex { + class DynamicRunTimeInterface : public nn::nex::RootObject { + public: + DynamicRunTimeInterface(); + + virtual ~DynamicRunTimeInterface(); + + u64* GetInstance(); + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/encryption.h b/include/nn/nex/encryption.h new file mode 100644 index 0000000..f88d3ec --- /dev/null +++ b/include/nn/nex/encryption.h @@ -0,0 +1,61 @@ +/** + * @file encryption.h + * @brief NEX Encryption Algorithm. + */ + +#pragma once + +#include "RootObject.h" +#include "buffer.h" +#include "key.h" +#include "sead/critical.h" + +namespace nn { +namespace nex { + class EncryptionAlgorithm : public nn::nex::RootObject { + public: + EncryptionAlgorithm(u32, u32); + + virtual ~EncryptionAlgorithm(); + + virtual bool Encrypt(nn::nex::Buffer const&, nn::nex::Buffer*) = 0; + virtual bool Encrypt(nn::nex::Buffer*); + virtual bool Decrypt(nn::nex::Buffer const&, nn::nex::Buffer*) = 0; + virtual bool Decrypt(nn::nex::Buffer*); + virtual bool GetErrorString(u32, char* destStr, u64 errLen); + virtual void KeyHasChanged(); + + bool SetKey(nn::nex::Key const& key); + + u64 _8; + u64 _10; + u64 _18; + u64 _20; + u64 _28; + u64 _30; + u64 _38; + u64 _40; + }; + + class RC4Encryption : public nn::nex::EncryptionAlgorithm { + public: + RC4Encryption(); + + virtual ~RC4Encryption(); + + virtual bool Encrypt(nn::nex::Buffer const&, nn::nex::Buffer*); + virtual bool Encrypt(nn::nex::Buffer*); + virtual bool Decrypt(nn::nex::Buffer const&, nn::nex::Buffer*); + virtual bool Decrypt(nn::nex::Buffer*); + + virtual void KeyHasChanged(); + + void GetDefaultKey(); + void PrepareEncryption(); + void ReinitStateArray(); + void SetReinitEverytime(bool); + + u8 _48[0x298 - 0x48]; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/hash.h b/include/nn/nex/hash.h new file mode 100644 index 0000000..bd29ea7 --- /dev/null +++ b/include/nn/nex/hash.h @@ -0,0 +1,36 @@ +/** + * @file hash.h + * @brief NEX Hash Implementation. + */ + +#pragma once + +#include "RootObject.h" +#include "nn/crypto.h" +#include "types.h" + +namespace nn { +namespace nex { + class MD5 : public nn::crypto::detail::Md5Impl, public nn::nex::RootObject { + public: + MD5(); + + void init(); + void raw_digest(u8*); + void hex_digest(); + + u8 _5C[0x74 - 0x5C]; + }; + + class Sha1 : public nn::crypto::detail::Sha1Impl, public nn::nex::RootObject { + public: + Sha1(); + + void Update(void const*, u64); + void GetHash(void*, u64); + void GenerateHash(void*, u64, void const*, u64); + + u32 _68; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/instance.h b/include/nn/nex/instance.h new file mode 100644 index 0000000..bb3b919 --- /dev/null +++ b/include/nn/nex/instance.h @@ -0,0 +1,46 @@ +/** + * @file instance.h + * @brief NEX Instance Controllers. + */ +#pragma once + +#include "RootObject.h" + +namespace nn { +namespace nex { + class InstanceTable; + + class InstanceControl : public nn::nex::RootObject { + public: + InstanceControl(u32, u32); + + u32 mInstanceContext; // _8 + u32 mInstanceType; // _C + void* mDelegateInstance; // _10 + bool mIsValidControl; // _18 + u8 _19; // probably padding + u8 _1A; + u8 _1B; + + static nn::nex::InstanceTable* s_oInstanceTable; + }; + + class InstanceTable : public nn::nex::RootObject { + public: + InstanceTable(); + + virtual ~InstanceTable(); + + bool AddInstance(nn::nex::InstanceControl*, u32, u32); + void DelInstance(nn::nex::InstanceControl*, u32, u32); + u32 CreateContext(); + bool DeleteContext(u32); + void AllocateExtraContexts(u64 size); + void FreeExtraContexts(); + u32 GetHighestID() const; + u32 FindInstanceContext(nn::nex::InstanceControl*, u32); + + u8 _0[0x94]; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/key.h b/include/nn/nex/key.h new file mode 100644 index 0000000..87a867d --- /dev/null +++ b/include/nn/nex/key.h @@ -0,0 +1,38 @@ +/** + * @file key.h + * @brief NEX Key Implementation. + */ + +#pragma once + +#include "RootObject.h" +#include "string.h" + +namespace nn { +namespace nex { + class Key : public nn::nex::RootObject { + public: + Key(); + Key(u8 const* src, u64 size); + Key(u64 size); + Key(nn::nex::Key const&); + Key(nn::nex::String const&); + + virtual ~Key(); + + u64* GetContentPtr(); + u64 GetLength() const; + nn::nex::Key& operator=(nn::nex::Key const&); + bool operator==(nn::nex::Key const&); + bool operator!=(nn::nex::Key const&); + void PrepareContentPtr(u64); + nn::nex::String* ToString(); + void ExtractToString(nn::nex::String*) const; + void Trace(u64) const; + void GenerateRandomKey(u64); + + u64* mContentPtrStart; // _10 + u64* mContentPtrEnd; // _18 + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/plugin.h b/include/nn/nex/plugin.h new file mode 100644 index 0000000..54ac090 --- /dev/null +++ b/include/nn/nex/plugin.h @@ -0,0 +1,33 @@ +/** + * @file plugin.h + * @brief Plugin interface for NEX. + */ + +#pragma once + +#include "RootObject.h" + +namespace nn { +namespace nex { + class PluginObject : public nn::nex::RootObject {}; + + class Plugin : public nn::nex::RootObject { + public: + Plugin(); + + virtual ~Plugin(); + + // there's a bunch of pure virtual methods but nothing ever inherits this class... + virtual bool Initalize(); + virtual void ThreadAttach(); + virtual void ThreadDetach(); + virtual void Destroy(); + + void SetLibrary(void*); + + void* mLibrary; // _8s + + static nn::nex::Plugin* s_pInstance; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/pseudo.h b/include/nn/nex/pseudo.h new file mode 100644 index 0000000..a48e3df --- /dev/null +++ b/include/nn/nex/pseudo.h @@ -0,0 +1,81 @@ +/** + * @file psuedo.h + * @brief Psuedo variable implementation for NEX. + */ + +#pragma once + +#include "RootObject.h" +#include "instance.h" +#include "types.h" + +namespace nn { +namespace nex { + class PseudoGlobalVariableList; + + class PseudoGlobalVariableRoot : public nn::nex::RootObject { + public: + PseudoGlobalVariableRoot(); + + virtual ~PseudoGlobalVariableRoot(); + + virtual void AllocateExtraContexts() = 0; + virtual void FreeExtraContexts() = 0; + virtual void ResetContext(u32) = 0; + virtual PseudoGlobalVariableRoot* GetNext() = 0; + virtual void SetNext(PseudoGlobalVariableRoot* pNextVariable) = 0; + + static void ResetContextForAllVariables(u32); + static void AllocateExtraContextsForAllVariables(u64); + static void FreeExtraContextsForAllVariables(); + static s64 GetNbOfExtraContexts(); + + nn::nex::PseudoGlobalVariableRoot* mNextRoot; // _8 + + static s64 s_uiNbOfExtraContexts; + static PseudoGlobalVariableList s_oList; + }; + + class PseudoGlobalVariableList : public nn::nex::RootObject { + public: + PseudoGlobalVariableList(); + + virtual ~PseudoGlobalVariableList(); + + void AddVariable(nn::nex::PseudoGlobalVariableRoot*); + void RemoveVariable(nn::nex::PseudoGlobalVariableRoot*); + static nn::nex::PseudoGlobalVariableRoot* GetVariable(u32 idx); + static u32 FindVariableIndex(nn::nex::PseudoGlobalVariableRoot*); + void AllocateExtraContextsForAllVariables(); + void FreeExtraContextsForAllVariables(); + void ResetContextForAllVariables(u32); + static u32 GetNbOfVariables(); + + static PseudoGlobalVariableRoot* s_pVariableListHead; + static u32 m_uiNbOfVariables; + }; + + template + class PseudoGlobalVariable : public nn::nex::PseudoGlobalVariableRoot { + public: + PseudoGlobalVariable(); + + virtual ~PseudoGlobalVariable(); + + virtual void AllocateExtraContexts(); + virtual void FreeExtraContexts(); + virtual void ResetContext(u32); + virtual PseudoGlobalVariableRoot* GetNext(); + virtual void SetNext(PseudoGlobalVariableRoot* pNextVariable); + }; + + class PseudoSingleton : public nn::nex::InstanceControl { + public: + PseudoSingleton(u32); + + virtual ~PseudoSingleton(); + + static bool s_bUseInstantiationContext; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/reference.h b/include/nn/nex/reference.h new file mode 100644 index 0000000..d3010eb --- /dev/null +++ b/include/nn/nex/reference.h @@ -0,0 +1,18 @@ +/** + * @file reference.h + * @brief Reference implementations for NEX. + */ +#pragma once + +#include "RootObject.h" + +namespace nn { +namespace nex { + class RefCountedObject : public nn::nex::RootObject { + public: + virtual ~RefCountedObject(); + + u32 _8; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/socket.h b/include/nn/nex/socket.h new file mode 100644 index 0000000..5398709 --- /dev/null +++ b/include/nn/nex/socket.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "RootObject.h" +#include "types.h" + +namespace nn { +namespace nex { + class TransportProtocol { + public: + enum Type { + Sock_Default = 0, + Sock_Stream = SOCK_STREAM, + Sock_DGram = SOCK_DGRAM, + Sock_Raw = SOCK_RAW, + Sock_SeqPacket = SOCK_SEQPACKET, + Sock_NonBlock = SOCK_NONBLOCK + }; + }; + + class SocketDriver : nn::nex::RootObject { + public: + typedef in_addr_t InetAddress; + + enum _SocketFlag : int32_t { + Msg_None = 0, + Msg_Oob = MSG_OOB, + Msg_Peek = MSG_PEEK, + Msg_DontRoute = MSG_DONTROUTE, + Msg_Eor = MSG_EOR, + Msg_Trunc = MSG_TRUNC, + Msg_CTrunc = MSG_CTRUNC, + Msg_WaitAll = MSG_WAITALL, + Msg_DontWait = MSG_DONTWAIT, + Msg_Eof = MSG_EOF, + Msg_Notification = MSG_NOTIFICATION, + Msg_Nbio = MSG_NBIO, + Msg_Compat = MSG_COMPAT, + // Msg_SoCallbck = MSG_SOCALLBCK, + // Msg_NoSignal = MSG_NOSIGNAL, + Msg_CMsg_CloExec = MSG_CMSG_CLOEXEC + }; + + class Socket { + virtual void Open(nn::nex::TransportProtocol::Type); + virtual void Close(); + virtual void Bind(ushort&); + virtual void RecvFrom(uchar*, ulong, InetAddress*, ulong*, nn::nex::SocketDriver::_SocketFlag); + virtual void SendTo(uchar const*, ulong, nn::nex::SocketDriver::InetAddress const&, ulong*); + }; + + class PollInfo {}; + + virtual Socket* Create(); + virtual void Delete(Socket*); + virtual int Poll(PollInfo*, uint, uint); + virtual bool CanUseGetAllReceivableSockets(); + virtual void GetAllReceivableSockets(Socket**, ulong, uint); + }; + + class BerkelySocket : public SocketDriver::Socket {}; + + class BerkeleySocketDriver : SocketDriver { // inherits SocketDriver and RootObject but not documented + virtual ~BerkeleySocketDriver(); + }; + + class ClientWebSocketDriver : SocketDriver { + class ClientWebSocket : Socket {}; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/string.h b/include/nn/nex/string.h new file mode 100644 index 0000000..6daf92a --- /dev/null +++ b/include/nn/nex/string.h @@ -0,0 +1,47 @@ +/** + * @file string.h + * @brief NEX String Implementation. + */ + +#pragma once + +#include "RootObject.h" + +namespace nn { +namespace nex { + class String : public nn::nex::RootObject { + public: + String(); + String(const char*); + String(const wchar_t*); + String(const char16_t*); + String(const String&); + String(String&&); + + String& operator=(String&&); + String& operator=(const char*); + String& operator=(const wchar_t*); + String& operator=(const String&); + String& operator=(const char16_t*); + String& operator+=(const String&); + String operator==(const String&); + bool operator<(nn::nex::String const&); + + void Truncate(u64) const; + u64 GetLength() const; + void Reserve(u64); + void SetBufferChar(char*); + void SetStringToPreReservedBuffer(char const*); + s32 GetWideCharLength() const; + void CopyString(char*, u64) const; + void CreateCopy(wchar_t**) const; + void ReleaseCopy(wchar_t*); + void ToUpper(); + void ToLower(); + void DeleteContent(); + + template + void Assign(T const*); + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/system.h b/include/nn/nex/system.h new file mode 100644 index 0000000..4c7a7b1 --- /dev/null +++ b/include/nn/nex/system.h @@ -0,0 +1,65 @@ +/** + * @file system.h + * @brief System state / component interface for NEX. + */ + +#pragma once + +#include "reference.h" +#include "string.h" + +namespace nn { +namespace nex { + class SystemComponent : public nn::nex::RefCountedObject { + public: + enum _State { + State_Uninitialized = 1 << 0, + State_Initializing = 1 << 1, + State_Ready = 1 << 2, + State_ReadyInUse = 1 << 3, + State_Terminating = 1 << 4, + State_TerminatingWhileInUse = 1 << 5, + State_Terminated = 1 << 6, + State_Faulty = 1 << 7, + State_Unknown = 1 << 8, + State_HighestState = 1 << 8 + }; + + SystemComponent(nn::nex::String const&); + + virtual ~SystemComponent(); + + virtual char* GetType() const; + virtual bool IsAKindOf(char const*) const; + virtual void EnforceDeclareSysComponentMacro() = 0; + virtual void StateTransition(nn::nex::SystemComponent::_State); + virtual void OnInitialize(); + virtual void OnTerminate(); + virtual bool BeginInitialization(); + virtual bool EndInitialization(); + virtual bool BeginTermination(); + virtual bool EndTermination(); + virtual bool ValidTransition(nn::nex::SystemComponent::_State); + virtual bool UseIsAllowed(); + virtual nn::nex::SystemComponent::_State TestState(); + virtual void DoWork(); + + nn::nex::SystemComponent::_State Initialize(); + nn::nex::SystemComponent::_State Terminate(); + + u8 SystemComponent_xC; + u8 _D; + u8 _E; + u8 _F; + u64 _10; + u64 _18; + u64 _20; + u32 _28; + u32 _2C; + u64 _30; + nn::nex::SystemComponent::_State mState; // _38 + u32 _3C; + u64 _40; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nex/time.h b/include/nn/nex/time.h new file mode 100644 index 0000000..d24282d --- /dev/null +++ b/include/nn/nex/time.h @@ -0,0 +1,49 @@ +/** + * @file time.h + * @brief NEX Time Library. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace nex { + class TimeProvider; + + class Time { + public: + static void Reset(); + static void RegisterTimeProvider(TimeProvider* provider); + + Time Multiply(f32) const; + Time Divide(f32) const; + Time Scale(f32) const; + + static Time ConvertTimeoutToDeadline(u32 timeout); + static u32 ConvertDeadlineToTimeout(Time deadline); + + u64 mCurTime; // _0 + + static u64* s_pfGetSessionTime; // some sort of callback? + }; + + class SystemClock { + public: + SystemClock(); + + virtual ~SystemClock(); + + static void RegisterTimeProvider(nn::nex::TimeProvider*, bool); + static void ApplyCorrection(Time curTime, Time newTime); + static Time ProtectedGetTime(); + static Time GetTimeImpl(bool); + static Time GetTimeImplCorrectless(); + static void Reset(); + + static nn::nex::TimeProvider* s_pTimeProvider; + static bool s_needCorrection; + static bool s_tiCorrection; + }; +}; // namespace nex +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nifm.h b/include/nn/nifm.h new file mode 100644 index 0000000..02b21c3 --- /dev/null +++ b/include/nn/nifm.h @@ -0,0 +1,21 @@ +/** + * @file nifm.h + * @brief Network inferface module. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace nifm { + Result Initialize(); + void SetLocalNetworkMode(bool); + void SubmitNetworkRequestAndWait(); + bool IsNetworkAvailable(); + Result HandleNetworkRequestResult(); + void SubmitNetworkRequest(); + bool IsNetworkRequestOnHold(); + Result GetCurrentPrimaryIpAddress(u64* inAddr); +}; // namespace nifm +}; // namespace nn \ No newline at end of file diff --git a/include/nn/nn.h b/include/nn/nn.h new file mode 100644 index 0000000..8edd4dc --- /dev/null +++ b/include/nn/nn.h @@ -0,0 +1,33 @@ +/** + * @file nn.h + * @brief Barebones NN functions, such as init and nnMain. + */ + +#pragma once + +#include "types.h" + +namespace nn { +typedef u64 ApplicationId; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int main(int argc, char** argv); +void nninitStartup(); + +void _init(); +void _fini(); +void __nnDetailNintendoSdkRuntimeObjectFileRefer(); +void __nnDetailNintendoSdkRuntimeObjectFile(); +void __nnDetailNintendoSdkNsoFileRefer(); + +void __nnmusl_init_dso_0(); +void __nnmusl_fini_dso_0(); +void __nnDetailNintendoSdkNsoFile_0(); + +#ifdef __cplusplus +} +#endif diff --git a/include/nn/nn.rs b/include/nn/nn.rs new file mode 100644 index 0000000..d2f294b --- /dev/null +++ b/include/nn/nn.rs @@ -0,0 +1,8 @@ +# 1 "" +# 1 "" 1 +# 1 "" 3 +# 386 "" 3 +# 1 "" 1 +# 1 "" 2 +# 1 "" 2 + diff --git a/include/nn/oe.h b/include/nn/oe.h new file mode 100644 index 0000000..a579da4 --- /dev/null +++ b/include/nn/oe.h @@ -0,0 +1,51 @@ +/** + * @file oe.h + * @brief Extenstions to OS functions. + */ + +#pragma once + +#include "settings.h" +#include "types.h" + +namespace nn { +namespace oe { + typedef s32 FocusHandlingMode; + + enum CpuBoostMode + { + Disabled, + BoostCPU, + ThrottleGPU, + }; + + enum PerformanceMode + { + Invalid = -1, + Normal, + Boost, + }; + + struct DisplayVersion { + char name[16]; + }; + + void Initialize(); + void SetPerformanceConfiguration(nn::oe::PerformanceMode, s32); + s32 GetOperationMode(); + s32 GetPerformanceMode(); + void SetResumeNotificationEnabled(bool); + void SetOperationModeChangedNotificationEnabled(bool); + void SetPerformanceModeChangedNotificationEnabled(bool); + void SetFocusHandlingMode(nn::oe::FocusHandlingMode); + bool TryPopNotificationMessage(u32*); + s32 GetCurrentFocusState(); + void EnableGamePlayRecording(void*, u64); + bool IsUserInactivityDetectionTimeExtended(); + void SetUserInactivityDetectionTimeExtended(bool); + void FinishStartupLogo(); + nn::settings::LanguageCode GetDesiredLanguage(); + void GetDisplayVersion(DisplayVersion*); + void SetCpuBoostMode(nn::oe::CpuBoostMode); +}; // namespace oe +}; // namespace nn \ No newline at end of file diff --git a/include/nn/os.h b/include/nn/os.h new file mode 100644 index 0000000..52eec18 --- /dev/null +++ b/include/nn/os.h @@ -0,0 +1,26 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct nnosMutexType { + u8 curState; // _0 + bool isRecursiveMutex; // _1 + s32 lockLevel; // _2 + u8 _6[0x20 - 0xE]; +} nnosMutexType; + +void nnosInitializeMutex(nnosMutexType*, bool, s32); +void nnosFinalizeMutex(nnosMutexType*); +void nnosLockMutex(nnosMutexType*); +bool nnosTryLockMutex(nnosMutexType*); +void nnosUnlockMutex(nnosMutexType*); + +long long int llabs(long long int n); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/nn/os.hpp b/include/nn/os.hpp new file mode 100644 index 0000000..6249958 --- /dev/null +++ b/include/nn/os.hpp @@ -0,0 +1,216 @@ +/** + * @file os.hpp + * @brief Operating System implementations. + */ + +#pragma once + +#include "os.h" +#include "time.h" +#include "types.h" + +namespace nn { +namespace os { + namespace detail { + class InternalCriticalSection { + u32 Image; + }; + + class InternalConditionVariable { + u32 Image; + }; + } // namespace detail + + typedef u64 Tick; + typedef u64 LightEventType; + + // https://github.com/misson20000/nn-types/blob/master/nn_os.h + struct EventType { + nn::os::EventType* _x0; + nn::os::EventType* _x8; + bool isSignaled; + bool initiallySignaled; + bool shouldAutoClear; + bool isInit; + u32 signalCounter; + u32 signalCounter2; + nn::os::detail::InternalCriticalSection crit; + nn::os::detail::InternalConditionVariable condvar; + }; + typedef EventType Event; + + enum EventClearMode { EventClearMode_ManualClear, EventClearMode_AutoClear }; + + struct ThreadType { + u8 _0[0x40]; + u32 State; + bool _44; + bool _45; + u8 _46; + u32 PriorityBase; + void* StackBase; + void* Stack; + size_t StackSize; + void* Arg; + u64 ThreadFunc; + u8 _88[0x100]; + char Name[0x20]; + detail::InternalCriticalSection Crit; + detail::InternalConditionVariable Condvar; + u32 Handle; + u8 padding[0x18]; + + ThreadType(){}; + }; + static_assert(sizeof(ThreadType) == 0x1C0, ""); + + struct MessageQueueType { + u64 _x0; + u64 _x8; + u64 _x10; + u64 _x18; + void* Buffer; + u32 MaxCount; + u32 Count; + u32 Offset; + bool Initialized; + detail::InternalCriticalSection _x38; + detail::InternalConditionVariable _x3C; + detail::InternalConditionVariable _x40; + }; + + struct ConditionVariableType {}; + + struct SystemEvent; + struct SystemEventType; + + // ARG + void SetHostArgc(s32); + s32 GetHostArgc(); + void SetHostArgv(char**); + char** GetHostArgv(); + + // MEMORY + void InitializeVirtualAddressMemory(); + Result AllocateAddressRegion(u64*, u64); + Result AllocateMemory(u64*, u64); + Result AllocateMemoryPages(u64, u64); + void AllocateMemoryBlock(u64*, u64); + void FreeMemoryBlock(u64, u64); + void SetMemoryHeapSize(u64); + + // MUTEX + typedef struct MutexType { + nnosMutexType impl; + } MutexType; + + void InitializeMutex(nn::os::MutexType*, bool, s32); + void FinalizeMutex(nn::os::MutexType*); + void LockMutex(nn::os::MutexType*); + bool TryLockMutex(nn::os::MutexType*); + void UnlockMutex(nn::os::MutexType*); + bool IsMutexLockedByCurrentThread(nn::os::MutexType const*); + + // QUEUE + void InitializeMessageQueue(nn::os::MessageQueueType*, u64* buf, u64 queueCount); + void FinalizeMessageQueue(nn::os::MessageQueueType*); + + bool TrySendMessageQueue(MessageQueueType*, u64); + void SendMessageQueue(MessageQueueType*, u64); + bool TimedSendMessageQueue(MessageQueueType*, u64, nn::TimeSpan); + + bool TryReceiveMessageQueue(u64* out, MessageQueueType*); + void ReceiveMessageQueue(u64* out, MessageQueueType*); + bool TimedReceiveMessageQueue(u64* out, MessageQueueType*, nn::TimeSpan); + + bool TryPeekMessageQueue(u64*, MessageQueueType const*); + void PeekMessageQueue(u64*, MessageQueueType const*); + bool TimedPeekMessageQueue(u64*, MessageQueueType const*); + + bool TryJamMessageQueue(nn::os::MessageQueueType*, u64); + void JamMessageQueue(nn::os::MessageQueueType*, u64); + bool TimedJamMessageQueue(nn::os::MessageQueueType*, u64, nn::TimeSpan); + + // CONDITION VARIABLE + void InitializeConditionVariable(ConditionVariableType*); + void FinalizeConditionVariable(ConditionVariableType*); + + void SignalConditionVariable(ConditionVariableType*); + void BroadcastConditionVariable(ConditionVariableType*); + void WaitConditionVariable(ConditionVariableType*); + u8 TimedWaitConditionVariable(ConditionVariableType*, MutexType*, nn::TimeSpan); + + // THREAD + Result CreateThread(nn::os::ThreadType*, void (*)(void*), void* arg, void* srcStack, u64 stackSize, s32 priority, + s32 coreNum); + void DestroyThread(nn::os::ThreadType*); + void StartThread(nn::os::ThreadType*); + void SetThreadName(nn::os::ThreadType*, char const* threadName); + void SetThreadNamePointer(nn::os::ThreadType*, char const*); + char* GetThreadNamePointer(nn::os::ThreadType const*); + nn::os::ThreadType* GetCurrentThread(); + s32 ChangeThreadPriority(nn::os::ThreadType* thread, s32 priority); + s32 GetThreadPriority(nn::os::ThreadType const* thread); + void YieldThread(); + void SuspendThread(nn::os::ThreadType*); + void ResumeThread(nn::os::ThreadType*); + void SleepThread(nn::TimeSpan); + + // EVENTS + void InitializeEvent(EventType*, bool initiallySignaled, EventClearMode clearMode); + void FinalizeEvent(EventType*); + void SignalEvent(EventType*); + void WaitEvent(EventType*); + bool TryWaitEvent(EventType*); + bool TimedWaitEvent(EventType*, nn::TimeSpan); + void ClearEvent(EventType*); + + // EXCEPTION HANDLING + typedef union { + u64 x; ///< 64-bit AArch64 register view. + u32 w; ///< 32-bit AArch64 register view. + u32 r; ///< AArch32 register view. + } CpuRegister; + /// Armv8 NEON register. + + typedef union { + u128 v; ///< 128-bit vector view. + double d; ///< 64-bit double-precision view. + float s; ///< 32-bit single-precision view. + } FpuRegister; + + struct UserExceptionInfo { + u32 ErrorDescription; ///< See \ref ThreadExceptionDesc. + u32 pad[3]; + + CpuRegister CpuRegisters[29]; ///< GPRs 0..28. Note: also contains AArch32 registers. + CpuRegister FP; ///< Frame pointer. + CpuRegister LR; ///< Link register. + CpuRegister SP; ///< Stack pointer. + CpuRegister PC; ///< Program counter (elr_el1). + + u64 padding; + + FpuRegister FpuRegisters[32]; ///< 32 general-purpose NEON registers. + + u32 PState; ///< pstate & 0xFF0FFE20 + u32 AFSR0; + u32 AFSR1; + u32 ESR; + + CpuRegister FAR; ///< Fault Address Register. + }; + void SetUserExceptionHandler(void (*)(UserExceptionInfo*), void*, ulong, UserExceptionInfo*); + + // OTHER + void GenerateRandomBytes(void*, u64); + nn::os::Tick GetSystemTick(); + u64 GetThreadAvailableCoreMask(); + void SetMemoryHeapSize(u64 size); + + namespace detail { + extern s32 g_CommandLineParameter; + extern char** g_CommandLineParameterArgv; + }; // namespace detail +}; // namespace os +}; // namespace nn \ No newline at end of file diff --git a/include/nn/prepo.h b/include/nn/prepo.h new file mode 100644 index 0000000..e6fa5db --- /dev/null +++ b/include/nn/prepo.h @@ -0,0 +1,27 @@ +#pragma once + +#include "account.h" +#include "types.h" + +namespace nn { +namespace prepo { + class PlayReport { + public: + char m_EventName[0x20]; + void* m_Buff; + size_t m_BuffLength; + u64 m_End; + + PlayReport(); + Result SetEventId(char const*); + Result SetBuffer(); + + Result Add(char const*, long); + Result Add(char const*, double); + Result Add(char const*, char const*); + + Result Save(); + Result Save(account::Uid const&); + }; +}; // namespace prepo +}; // namespace nn \ No newline at end of file diff --git a/include/nn/ro.h b/include/nn/ro.h new file mode 100644 index 0000000..6d7319d --- /dev/null +++ b/include/nn/ro.h @@ -0,0 +1,109 @@ +/** + * @file ro.h + * @brief Dynamic module API. + */ + +#pragma once + +#include "ModuleObject.hpp" +#include "types.h" + +namespace nn { +namespace ro { + class Module { + public: + rtld::ModuleObject* ModuleObject; + u32 State; + void* NroPtr; + void* BssPtr; + void* _x20; + void* SourceBuffer; + char Name[256]; /* Created by retype action */ + u8 _x130; + u8 _x131; + bool isLoaded; // bool + }; + + struct ModuleId { + u8 build_id[0x20]; + }; + + struct NroHeader { + u32 entrypoint_insn; + u32 mod_offset; + u8 _x8[0x8]; + u32 magic; + u8 _x14[0x4]; + u32 size; + u8 reserved_1C[0x4]; + u32 text_offset; + u32 text_size; + u32 ro_offset; + u32 ro_size; + u32 rw_offset; + u32 rw_size; + u32 bss_size; + u8 _x3C[0x4]; + ModuleId module_id; + u8 _x60[0x20]; + }; + static_assert(sizeof(NroHeader) == 0x80, "NroHeader definition!"); + + struct ProgramId { + u64 value; + + inline explicit operator u64() const { return this->value; } + }; + + struct NrrHeader { + u32 magic; + u8 _x4[0xC]; + u64 program_id_mask; + u64 program_id_pattern; + u8 _x20[0x10]; + u8 modulus[0x100]; + u8 fixed_key_signature[0x100]; + u8 nrr_signature[0x100]; + ProgramId program_id; + u32 size; + u8 type; /* 7.0.0+ */ + u8 _x33D[3]; + u32 hashes_offset; + u32 num_hashes; + u8 _x348[8]; + }; + static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!"); + + struct RegistrationInfo { + enum State { + State_Unregistered, + State_Registered, + }; + State state; + NrrHeader* nrrPtr; + u64 _x10; + u64 _x18; + }; + + enum BindFlag { + BindFlag_Now = BIT(0), + BindFlag_Lazy = BIT(1), + }; + + Result Initialize(); + + Result LookupSymbol(uintptr_t* pOutAddress, const char* name); + + Result LookupModuleSymbol(uintptr_t* pOutAddress, const Module* pModule, const char* name); + Result LoadModule(Module* pOutModule, const void* pImage, void* buffer, size_t bufferSize, int flag); + // Result LoadModule(Module *pOutModule, const void *pImage, void *buffer, size_t bufferSize,int flag, bool + // isNotReferenced); + Result UnloadModule(Module*); + Result GetBufferSize(size_t*, const void*); + + Result RegisterModuleInfo(RegistrationInfo*, void const*); + Result RegisterModuleInfo(RegistrationInfo*, void const*, uint); + Result UnregisterModuleInfo(RegistrationInfo*, void const*); +}; // namespace ro + +}; // namespace nn \ No newline at end of file diff --git a/include/nn/settings.h b/include/nn/settings.h new file mode 100644 index 0000000..ce28945 --- /dev/null +++ b/include/nn/settings.h @@ -0,0 +1,57 @@ +#pragma once + +namespace nn { +namespace settings { + namespace system { + struct FirmwareVersion { + u8 major; + u8 minor; + u8 micro; + u8 padding1; + u8 revision_major; + u8 revision_minor; + u8 padding2; + u8 padding3; + char platform[0x20]; + char version_hash[0x40]; + char display_version[0x18]; + char display_title[0x80]; + + constexpr inline u32 getVersion() const { + return (static_cast(major) << 16) | (static_cast(minor) << 8) | + (static_cast(micro) << 0); + } + }; + + Result GetFirmwareVersion(FirmwareVersion*); + } // namespace system + + enum Language { + Language_Japanese, + Language_English, + Language_French, + Language_German, + Language_Italian, + Language_Spanish, + Language_Chinese, + Language_Korean, + Language_Dutch, + Language_Portuguese, + Language_Russian, + Language_Taiwanese, + Language_BritishEnglish, + Language_CanadianFrench, + Language_LatinAmericanSpanish + }; + + struct LanguageCode { + char code[0x8]; + + static LanguageCode Make(nn::settings::Language); + }; + + void GetLanguageCode(nn::settings::LanguageCode*); + + bool operator==(nn::settings::LanguageCode const&, nn::settings::LanguageCode const&); +}; // namespace settings +}; // namespace nn \ No newline at end of file diff --git a/include/nn/sf/hipc.h b/include/nn/sf/hipc.h new file mode 100644 index 0000000..f8f1be3 --- /dev/null +++ b/include/nn/sf/hipc.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../svc.h" +#include "types.h" + +namespace nn::sf::hipc { +void* GetMessageBufferOnTls(); + +Result InitializeHipcServiceResolution(); +Result ConnectToHipcService(nn::svc::Handle*, char const*); +Result FinalizeHipcServiceResolution(); + +Result SendSyncRequest(nn::svc::Handle, void*, ulong); +Result CloseClientSessionHandle(nn::svc::Handle); + +namespace detail {} +}; // namespace nn::sf::hipc \ No newline at end of file diff --git a/include/nn/socket.h b/include/nn/socket.h new file mode 100644 index 0000000..6e1444f --- /dev/null +++ b/include/nn/socket.h @@ -0,0 +1,30 @@ +/** + * @file socket.h + * @brief Functions for opening sockets for wireless communication. + */ + +#pragma once + +#include + +#include "types.h" + +namespace nn { +namespace socket { + struct InAddr { + u32 addr; + }; + + Result Initialize(void* pool, ulong poolSize, ulong allocPoolSize, int concurLimit); + Result Finalize(); + s32 SetSockOpt(s32 socket, s32 socketLevel, s32 option, void const*, u32 len); + u64 Send(s32 socket, void const* buffer, u64 bufferLength, s32 flags); + s32 Socket(s32 domain, s32 type, s32 proto); + u16 InetHtons(u16); + u32 InetAton(const char* str, InAddr*); + u32 Connect(s32 socket, const sockaddr* addr, u32 addrLen); + u32 Bind(s32 socket, const sockaddr* addr, u32 addrLen); + u32 Listen(s32 socket, s32 backlog); + u32 Accept(s32 socket, sockaddr* addrOut, u32* addrLenOut); +}; // namespace socket +}; // namespace nn \ No newline at end of file diff --git a/include/nn/ssl.h b/include/nn/ssl.h new file mode 100644 index 0000000..3bb3d89 --- /dev/null +++ b/include/nn/ssl.h @@ -0,0 +1,25 @@ +/** + * @file ssl.h + * @brief SSL implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace ssl { + enum CertificateFormat { PEM = 0x01, DER = 0x02 }; + + class Context { + public: + enum SslVersion { Auto = 0x01, v10 = 0x08, v11 = 0x10, v12 = 0x20 }; + + Result Create(nn::ssl::Context::SslVersion version); + Result ImportServerPki(u64*, char const* certData, u32 certSize, nn::ssl::CertificateFormat certFormat); + }; + + Result Initialize(); + Result Finalize(); +}; // namespace ssl +}; // namespace nn \ No newline at end of file diff --git a/include/nn/svc.h b/include/nn/svc.h new file mode 100644 index 0000000..c05fd6a --- /dev/null +++ b/include/nn/svc.h @@ -0,0 +1,16 @@ +#pragma once + +#include "types.h" + +namespace nn::svc { +struct Handle { + u32 handle; + + Handle(u32 h) { handle = h; } + + Handle() : Handle(0) {} + + operator u32() const { return handle; } +}; + +}; // namespace nn::svc \ No newline at end of file diff --git a/include/nn/time.h b/include/nn/time.h new file mode 100644 index 0000000..5c7e8f9 --- /dev/null +++ b/include/nn/time.h @@ -0,0 +1,69 @@ +/** + * @file time.h + * @brief Time implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +class TimeSpan { + public: + u64 nanoseconds; + + static TimeSpan FromNanoSeconds(u64 nanoSeconds) { + TimeSpan ret; + ret.nanoseconds = nanoSeconds; + return ret; + } + + static TimeSpan FromSeconds(u64 seconds) { return FromNanoSeconds(seconds * 1000 * 1000 * 1000); } + static TimeSpan FromMinutes(u64 minutes) { return FromNanoSeconds(minutes * 1000 * 1000 * 1000 * 60); } + static TimeSpan FromHours(u64 hours) { return FromNanoSeconds(hours * 1000 * 1000 * 1000 * 60 * 60); } + static TimeSpan FromDays(u64 days) { return FromNanoSeconds(days * 1000 * 1000 * 1000 * 60 * 60 * 24); } +}; + +namespace time { + + Result Initialize(); + bool IsInitialized(); + + struct CalendarTime { + s16 year; + s8 month; + s8 day; + s8 hour; + s8 minute; + s8 second; + }; + + enum DayOfTheWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }; + + struct TimeZone { + char standardTimeName[0x8]; + bool _9; // daylight savings or something? + s32 utcOffset; // in seconds + }; + + struct CalendarAdditionalInfo { + nn::time::DayOfTheWeek dayOfTheWeek; + s32 dayofYear; + nn::time::TimeZone timeZone; + }; + + struct PosixTime { + u64 time; + }; + + class StandardUserSystemClock { + public: + static Result GetCurrentTime(nn::time::PosixTime*); + }; + + struct TimeZoneRule; // shrug + + Result ToCalendarTime(nn::time::CalendarTime*, nn::time::CalendarAdditionalInfo*, nn::time::PosixTime const&, + nn::time::TimeZoneRule const&); +}; // namespace time +}; // namespace nn diff --git a/include/nn/ui2d/Layout.h b/include/nn/ui2d/Layout.h new file mode 100644 index 0000000..9cf6437 --- /dev/null +++ b/include/nn/ui2d/Layout.h @@ -0,0 +1,49 @@ +/** + * @file Layout.h + * @brief UI Layout implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace ui2d { + class AnimTransform; + class Pane; + + class Layout { + public: + Layout(); + + virtual ~Layout(); + + virtual void DeleteAnimTransform(nn::ui2d::AnimTransform*); + virtual void BindAnimation(nn::ui2d::AnimTransform*); + virtual void UnbindAnimation(nn::ui2d::AnimTransform*); + virtual void UnbindAnimation(nn::ui2d::Pane*); + virtual void UnbindAllAnimation(); + + virtual void Animate(); + virtual void UpdateAnimFrame(f32 frame); + virtual void AnimateAndUpdateAnimFrame(f32 frame); + + void SetAllocator(void* (*)(u64, u64, void*), void (*)(void*, void*), void*); + void AllocateMemory(u64, u64); + void AllocateMemory(u64); + void FreeMemory(void* src); + + u64 _10; + u64 _18; + u64 _20; + u64 _28; + u64 _30; + + u64 _40; + u64 _48; + u64 _50; + u64 _58; + u64 _60; + }; +}; // namespace ui2d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/ui2d/Material.h b/include/nn/ui2d/Material.h new file mode 100644 index 0000000..c1f6ecc --- /dev/null +++ b/include/nn/ui2d/Material.h @@ -0,0 +1,29 @@ +/** + * @file Material.h + * @brief UI Material implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace ui2d { + class AnimTransform; + class BuildResultInformation; + struct UserShaderInformation; + + class Material { + public: + Material(); + + void Initialize(); + void ReserveMem(s32, s32, s32, s32, bool, s32, bool, s32, bool, bool); + void SetupUserShaderConstantBufferInformation(nn::ui2d::UserShaderInformation const&); + + virtual ~Material(); + virtual void BindAnimation(nn::ui2d::AnimTransform*); + virtual void UnbindAnimation(nn::ui2d::AnimTransform*); + }; +}; // namespace ui2d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/ui2d/Pane.h b/include/nn/ui2d/Pane.h new file mode 100644 index 0000000..d923b37 --- /dev/null +++ b/include/nn/ui2d/Pane.h @@ -0,0 +1,75 @@ +/** + * @file Pane.h + * @brief Base UI panel. + */ + +#pragma once + +#include "sead/runtime.h" +#include "types.h" + +namespace nn { +namespace ui2d { + class AnimTransform; + class Layout; + + class Pane { + public: + Pane(); + Pane(nn::ui2d::Pane const&); + + virtual ~Pane(); + + virtual sead::RuntimeTypeInfo::Interface* GetRuntimeTypeInfo() const; + virtual s32 GetVertexColor(s32); + virtual u8 GetColorElement(s32); + virtual void SetColorElement(u32, u8); + virtual u8 GetVertexColorElement(s32); + virtual void SetVertexColorElement(u32, u8); + virtual u32 GetMaterialCount() const; + virtual u64* GetMaterial(s32) const; + + virtual void BindAnimation(nn::ui2d::AnimTransform*, bool, bool); + virtual void UnbindAnimation(nn::ui2d::AnimTransform*, bool); + + void Initialize(); + void SetName(char const*); + void SetUserData(char const*); + void AppendChild(nn::ui2d::Pane*); + void PrependChild(nn::ui2d::Pane*); + void InsertChild(nn::ui2d::Pane*, nn::ui2d::Pane*); + void RemoveChild(nn::ui2d::Pane*); + void GetVertexPos() const; + + u64 _8; + u64 _10; + u64 _18; + u64 _20; + u64 _28; + u64 _30; + u64 _38; + u64 _40; + u32 _48; + u32 _4C; + u64 _50; + u16 _58; + u16 _5A; + u32 _5C; + u64 _60; + nn::ui2d::Layout* mLayout; // _68 + u128 _70; + u128 _80; + u128 _90; + u64 _A0; + u64 _A8; + u64 _B0; + u64 _B8; + u64 _C0; + u64 _C8; + u64 _D0; + u16 _D8; + u16 _DA; + u32 _DC; + }; +}; // namespace ui2d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/ui2d/Parts.h b/include/nn/ui2d/Parts.h new file mode 100644 index 0000000..0318645 --- /dev/null +++ b/include/nn/ui2d/Parts.h @@ -0,0 +1,29 @@ +/** + * @file Parts.h + * @brief Layout parts. + */ + +#pragma once + +#include "Pane.h" + +namespace nn { +namespace ui2d { + struct BuildArgSet; + struct ResParts; + + class Parts : nn::ui2d::Pane { + public: + Parts(); + Parts(nn::ui2d::ResParts const*, nn::ui2d::ResParts const*, nn::ui2d::BuildArgSet const&); + Parts(nn::ui2d::Parts const&); + + virtual ~Parts(); + virtual sead::RuntimeTypeInfo::Interface* GetRuntimeTypeInfo() const; + + u64 _E0; + u64 _E8; + u32 _F0; + }; +}; // namespace ui2d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/ui2d/detail/TexCoordArray.h b/include/nn/ui2d/detail/TexCoordArray.h new file mode 100644 index 0000000..43c0ef7 --- /dev/null +++ b/include/nn/ui2d/detail/TexCoordArray.h @@ -0,0 +1,34 @@ +/** + * @file TexCoordArray.h + * @brief Texture coordinate array implementation. + */ + +#pragma once + +#include "../util/Float2.h" +#include "types.h" + +namespace nn { +namespace ui2d { + class Layout; + + namespace detail { + class TexCoordArray { + public: + void Initialize(); + void Free(); + void Reserve(s32); + void SetSize(s32 size); + void GetCoord(nn::util::Float2*, s32) const; + void SetCoord(s32, nn::util::Float2 const*); + void Copy(void const*, s32); + bool CompareCopiedInstanceTest(nn::ui2d::detail::TexCoordArray const&) const; + + u16 _0; + u16 _2; + u32 _4; // padding? + nn::ui2d::Layout* mLayout; // _8 + }; + }; // namespace detail +}; // namespace ui2d +}; // namespace nn \ No newline at end of file diff --git a/include/nn/util.h b/include/nn/util.h new file mode 100644 index 0000000..9a790b9 --- /dev/null +++ b/include/nn/util.h @@ -0,0 +1,85 @@ +/** + * @file util.h + * @brief Helper functions for OS functionality. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace util { + struct Unorm8x4 { + u8 elements[0x4]; + }; + + struct Color4u8 { + u8 r, g, b, a; + }; + + struct Float2 { + union { + float v[2]; + struct { + float x; + float y; + }; + }; + }; + + struct Float3 { + union { + float v[3]; + struct { + float x; + float y; + float z; + }; + }; + }; + + enum CharacterEncodingResult { Success, BadLength, InvalidFormat }; + + CharacterEncodingResult PickOutCharacterFromUtf8String(char*, char const** str); + CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32* dest, char const* src); + CharacterEncodingResult ConvertStringUtf16NativeToUtf8(char*, s32, u16 const*, s32); + CharacterEncodingResult ConvertStringUtf8ToUtf16Native(u16*, s32, char const*, s32); + + class RelocationTable { + public: + void Relocate(); + void Unrelocate(); + + s32 mMagic; // _0 + u32 mPosition; // _4 + s32 mSectionCount; // _8 + }; + + class BinaryFileHeader { + public: + bool IsValid(s64 packedSig, s32 majorVer, s32 minorVer, s32 microVer) const; + bool IsRelocated() const; + bool IsEndianReverse() const; + nn::util::RelocationTable* GetRelocationTable(); + + s32 mMagic; // _0 + u32 mSig; // _4 + u8 mVerMicro; // _8 + u8 mVerMinor; // _9 + u16 mVerMajor; // _A + u16 mBOM; // _C + u8 mAlignment; // _E + u8 mTargetAddrSize; // _F + u32 mFileNameOffset; // _10 + u16 mFlag; // _14 + u16 mFirstBlockOffs; // _16 + u32 mRelocationTableOffs; // _18 + u32 mSize; // _1C + }; + + template + struct BitFlagSet {}; +}; // namespace util + +void ReferSymbol(void const*); +}; // namespace nn diff --git a/include/nn/util/Float2.h b/include/nn/util/Float2.h new file mode 100644 index 0000000..ec52b9f --- /dev/null +++ b/include/nn/util/Float2.h @@ -0,0 +1,19 @@ +/** + * @file Float2.h + * @brief Some odd float implementation that I don't understand yet... + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace util { + struct Float2 { + u64 _0; + u64 _8; + u64 _10; + u64 _18; + }; +}; // namespace util +}; // namespace nn \ No newline at end of file diff --git a/include/nn/vfx/Config.h b/include/nn/vfx/Config.h new file mode 100644 index 0000000..c5d401b --- /dev/null +++ b/include/nn/vfx/Config.h @@ -0,0 +1,15 @@ +/** + * @file Config.h + * @brief VFX configuration. + */ + +#pragma once + +namespace nn { +namespace vfx { + class Config { + public: + virtual ~Config(); + }; +}; // namespace vfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/vfx/Heap.h b/include/nn/vfx/Heap.h new file mode 100644 index 0000000..9a0c282 --- /dev/null +++ b/include/nn/vfx/Heap.h @@ -0,0 +1,17 @@ +/** + * @file Heap.h + * @brief VFX heap implementation. + */ + +#pragma once + +#include "types.h" + +namespace nn { +namespace vfx { + class Heap { + public: + virtual ~Heap(); + }; +}; // namespace vfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/vfx/System.h b/include/nn/vfx/System.h new file mode 100644 index 0000000..ba7209f --- /dev/null +++ b/include/nn/vfx/System.h @@ -0,0 +1,24 @@ +/** + * @file System.h + * @brief VFX system implementation. + */ + +#pragma once + +#include "Config.h" +#include "Heap.h" + +// this class is massive +namespace nn { +namespace vfx { + class System { + public: + System(nn::vfx::Config const&); + + virtual ~System(); + virtual void Initialize(nn::vfx::Heap*, nn::vfx::Heap*, nn::vfx::Config const&); + + u8 _0[0x1700]; + }; +}; // namespace vfx +}; // namespace nn \ No newline at end of file diff --git a/include/nn/vi.h b/include/nn/vi.h new file mode 100644 index 0000000..68ac534 --- /dev/null +++ b/include/nn/vi.h @@ -0,0 +1,25 @@ +/** + * @file vi.h + * @brief Visual interface implementation. + */ + +#pragma once + +#include "os.hpp" +#include "types.h" + +namespace nn { +namespace vi { + class Display; + class Layer; + + enum ScalingMode { None, Exact, FitLayer, ScaleAndCrop, PreserveAspectRatio }; + + void Initialize(); + Result OpenDefaultDisplay(nn::vi::Display** out_Disp); + Result CreateLayer(nn::vi::Layer* out_Layer, nn::vi::Display* disp); + Result SetLayerScalingMode(nn::vi::Layer* layer, nn::vi::ScalingMode scalingMode); + Result GetDisplayVsyncEvent(nn::os::SystemEventType*, nn::vi::Display*); + Result GetNativeWindow(void** window, nn::vi::Layer*); +}; // namespace vi +}; // namespace nn diff --git a/include/nn/wrapper.hpp b/include/nn/wrapper.hpp new file mode 100644 index 0000000..af31618 --- /dev/null +++ b/include/nn/wrapper.hpp @@ -0,0 +1,26 @@ +#include "account.h" +#include "audio.h" +#include "crypto.h" +#include "diag.h" +#include "friends.h" +#include "fs.h" +#include "hid.h" +#include "image.h" +#include "init.h" +#include "mem.h" +#include "nifm.h" +#include "nn.h" +#include "nn.rs" +#include "oe.h" +#include "os.h" +#include "os.hpp" +#include "prepo.h" +#include "ro.h" +#include "settings.h" +#include "socket.h" +#include "ssl.h" +#include "svc.h" +#include "time.h" +#include "util.h" +#include "vi.h" +#include "err.h" diff --git a/include/nvn/pfnc.h b/include/nvn/pfnc.h new file mode 100644 index 0000000..e3f0843 --- /dev/null +++ b/include/nvn/pfnc.h @@ -0,0 +1,100 @@ +/** + * @file pfnc.h + * @brief Functions that communicate with the NVN function pool. + */ + +#pragma once + +#include + +#include "nvn/types.h" + +#define NVN_DEFPROC(s) PFNC_##s s +#define NVN_GETPROCADDR(s) s = reinterpret_cast(nvnDeviceGetProcAddress(device, #s)) + +void nvnInit(NVNdevice*); + +extern "C" void* nvnBootstrapLoader(const char*); + +// DEVICE BUILDER +typedef NVNdeviceFlags (*PFNC_nvnDeviceBuilderGetFlags)(NVNdeviceBuilder*); +typedef void (*PFNC_nvnDeviceBuilderSetDefaults)(NVNdeviceBuilder*); +typedef void (*PFNC_nvnDeviceBuilderSetFlags)(NVNdeviceBuilder*, u32); + +// DEVICE +typedef bool (*PFNC_nvnDeviceInitialize)(NVNdevice*, NVNdeviceBuilder*); +typedef void (*PFNC_nvnDeviceFinalize)(NVNdevice*); +typedef void (*PFNC_nvnDeviceGetInteger)(NVNdevice*, NVNdeviceInfo, u32*); +typedef NVNimageHandle (*PFNC_nvnDeviceGetImageHandle)(NVNdevice*, u32); +typedef NVNdepthMode (*PFNC_nvnDeviceGetDepthMode)(NVNdevice*); +typedef void* (*PFNC_nvnDeviceGetProcAddress)(NVNdevice*, const char*); + +// SYNC +typedef bool (*PFNC_nvnSyncInitalize)(NVNsync*, NVNdevice*); +typedef void (*PFNC_nvnSyncFinalize)(NVNsync*); +typedef u32 (*PFNC_nvnSyncWait)(NVNsync*, u64); +typedef void (*PFNC_nvnSyncSetDebugLabel)(NVNsync*, char*); + +// WINDOW +typedef bool (*PFNC_nvnWindowInitialize)(NVNwindow*, const NVNwindowBuilder*); +typedef void (*PFNC_nvnWindowFinalize)(NVNwindow*); +typedef void (*PFNC_nvnWindowSetCrop)(NVNwindow*, int x, int y, int width, int height); +typedef int (*PFNC_nvnWindowPresentInterval)(NVNwindow*); +typedef void (*PFNC_nvnWindowSetPresentInterval)(NVNwindow*, int); +typedef void (*PFNC_nvnWindowSetDebugLabel)(NVNwindow*, char*); + +// QUEUE +typedef bool (*PFNC_nvnQueueInitalize)(NVNqueue*); +typedef void (*PFNC_nvnQueueFinalize)(NVNqueue*); +typedef void (*PFNC_nvnQueueFlush)(NVNqueue*); +typedef void (*PFNC_nvnQueueFenceSync)(NVNqueue*, NVNsync*, NVNsync*, u32, u32); +typedef void (*PFNC_nvnQueueSubmitCommands)(NVNqueue*, u32, NVNcommandHandle*); +typedef bool (*PFNC_nvnQueueWaitSync)(NVNqueue*, NVNsync*); +typedef void (*PFNC_nvnQueuePresentTexure)(NVNqueue*, NVNwindow*, u32); +typedef u8 (*PFNC_nvnQueueGetError)(NVNqueue*, u64*); +typedef size_t (*PFNC_nvnQueueGetTotalCommandMemoryUsed)(NVNqueue*); +typedef size_t (*PFNC_nvnQueueGetTotalComputeMemoryUsed)(NVNqueue*); +typedef size_t (*PFNC_nvnQueueGetTotalControlMemoryUsed)(NVNqueue*); +typedef void (*PFNC_nvnQueueResetMemoryUsageCounts)(NVNqueue*); +typedef void (*PFNC_nvnQueueSetDebugLabel)(NVNqueue*, char*); + +// SAMPLER BUILDER +typedef void (*PFNC_nvnSamplerBuilderSetMaxAnisotropy)(void *, float); + +extern NVN_DEFPROC(nvnDeviceBuilderGetFlags); +extern NVN_DEFPROC(nvnDeviceBuilderSetDefaults); +extern NVN_DEFPROC(nvnDeviceBuilderSetFlags); + +extern NVN_DEFPROC(nvnDeviceInitialize); +extern NVN_DEFPROC(nvnDeviceFinalize); +extern NVN_DEFPROC(nvnDeviceGetInteger); +extern NVN_DEFPROC(nvnDeviceGetImageHandle); +extern NVN_DEFPROC(nvnDeviceGetDepthMode); +extern NVN_DEFPROC(nvnDeviceGetProcAddress); + +extern NVN_DEFPROC(nvnSyncInitalize); +extern NVN_DEFPROC(nvnSyncFinalize); +extern NVN_DEFPROC(nvnSyncWait); +extern NVN_DEFPROC(nvnSyncSetDebugLabel); + +extern NVN_DEFPROC(nvnWindowInitialize); +extern NVN_DEFPROC(nvnWindowFinalize); +extern NVN_DEFPROC(nvnWindowSetCrop); +extern NVN_DEFPROC(nvnWindowPresentInterval); +extern NVN_DEFPROC(nvnWindowSetPresentInterval); +extern NVN_DEFPROC(nvnWindowSetDebugLabel); + +extern NVN_DEFPROC(nvnQueueInitalize); +extern NVN_DEFPROC(nvnQueueFinalize); +extern NVN_DEFPROC(nvnQueueFlush); +extern NVN_DEFPROC(nvnQueueFenceSync); +extern NVN_DEFPROC(nvnQueueSubmitCommands); +extern NVN_DEFPROC(nvnQueueWaitSync); +extern NVN_DEFPROC(nvnQueuePresentTexure); +extern NVN_DEFPROC(nvnQueueGetError); +extern NVN_DEFPROC(nvnQueueGetTotalCommandMemoryUsed); +extern NVN_DEFPROC(nvnQueueGetTotalComputeMemoryUsed); +extern NVN_DEFPROC(nvnQueueGetTotalControlMemoryUsed); +extern NVN_DEFPROC(nvnQueueSetDebugLabel); + +extern NVN_DEFPROC(nvnSamplerBuilderSetMaxAnisotropy); diff --git a/include/nvn/types.h b/include/nvn/types.h new file mode 100644 index 0000000..140a52c --- /dev/null +++ b/include/nvn/types.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../types.h" + +typedef u64 NVNcommandHandle; +typedef u64 NVNdeviceInfo; +typedef u64 NVNimageHandle; +typedef u64 NVNdepthMode; +typedef u32 NVNdeviceFlags; + +typedef struct { + char _x0[0x3000]; +} NVNdevice; + +typedef struct { + char _x0[0x2000]; +} NVNqueue; + +typedef struct { + char _x0[0xA0]; +} NVNcommandBuffer; + +typedef struct { + char _x0[0x180]; +} NVNwindow; + +typedef struct { + char _x0[0x40]; +} NVNwindowBuilder; + +typedef struct { + char _x0[0x40]; +} NVNsync; + +typedef struct { + char _x0[0x40]; +} NVNdeviceBuilder; \ No newline at end of file diff --git a/include/operator.h b/include/operator.h new file mode 100644 index 0000000..ad33438 --- /dev/null +++ b/include/operator.h @@ -0,0 +1,29 @@ +/** + * @file operator.h + * @brief Common operators, and implementations with Heaps. + */ + +#pragma once + +#include +#include + +#include "types.h" + +// Nintendo didn't implement these for some reason +void* operator new(std::size_t size, void*); +void* operator new[](std::size_t size, void*); + +void* operator new(std::size_t size); +void* operator new[](std::size_t size); +void* operator new(std::size_t size, std::nothrow_t const&); +void* operator new[](std::size_t size, std::nothrow_t const&); +void* operator new(std::size_t size, ulong); +void* operator new[](std::size_t size, ulong); + +void operator delete(void*); +void operator delete(void*, std::size_t); +void operator delete(void*, std::nothrow_t const&); +void operator delete[](void*); +void operator delete[](void*, std::size_t); +void operator delete[](void*, std::nothrow_t const&); \ No newline at end of file diff --git a/include/search.h b/include/search.h new file mode 100644 index 0000000..18effd2 --- /dev/null +++ b/include/search.h @@ -0,0 +1,13 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void* bsearch(const void* key, const void* base, size_t num, size_t size, int (*compar)(const void*, const void*)); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/skyline/inlinehook/And64InlineHook.hpp b/include/skyline/inlinehook/And64InlineHook.hpp new file mode 100644 index 0000000..943fc72 --- /dev/null +++ b/include/skyline/inlinehook/And64InlineHook.hpp @@ -0,0 +1,59 @@ +/* + * @date : 2018/04/18 + * @author : Rprop (r_prop@outlook.com) + * https://github.com/Rprop/And64InlineHook + */ +/* + MIT License + + Copyright (c) 2018 Rprop (r_prop@outlook.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +#pragma once +#define A64_MAX_BACKUPS 256 * 2 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../nx/kernel/jit.h" +#include "../nx/result.h" +#include "../utils/utils.h" +#include "alloc.h" +#include "mem.h" + +long long int llabs(long long int n); + +#ifdef __cplusplus +} +#endif + +#include "controlledpages.hpp" +#include "skyline/logger/Logger.hpp" + +struct InlineCtx { + nn::os::CpuRegister registers[29]; +}; + +void A64HookInit(); +extern "C" void A64HookFunction(void* const symbol, void* const replace, void** result); +void* A64HookFunctionV(void* const symbol, void* const replace, void* const rxtr, void* const rwtr, + const uintptr_t rwx_size); +extern "C" void A64InlineHook(void* const symbol, void* const replace); diff --git a/include/skyline/inlinehook/controlledpages.hpp b/include/skyline/inlinehook/controlledpages.hpp new file mode 100644 index 0000000..b2a673a --- /dev/null +++ b/include/skyline/inlinehook/controlledpages.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "types.h" + +namespace skyline::inlinehook { +class ControlledPages { + private: + bool isClaimed; + size_t size; + + public: + void* rx; + void* rw; + + ControlledPages(void* addr, size_t size); + + void claim(); + void unclaim(); +}; +}; // namespace skyline::inlinehook \ No newline at end of file diff --git a/include/skyline/inlinehook/memcpy_controlled.hpp b/include/skyline/inlinehook/memcpy_controlled.hpp new file mode 100644 index 0000000..fbbaffe --- /dev/null +++ b/include/skyline/inlinehook/memcpy_controlled.hpp @@ -0,0 +1,3 @@ +#include "controlledpages.hpp" + +extern "C" Result sky_memcpy(void* dest, const void* src, size_t n); diff --git a/include/skyline/logger/KernelLogger.hpp b/include/skyline/logger/KernelLogger.hpp new file mode 100644 index 0000000..85e8468 --- /dev/null +++ b/include/skyline/logger/KernelLogger.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "skyline/logger/Logger.hpp" + +namespace skyline::logger { +class KernelLogger : public Logger { + public: + KernelLogger(); + + virtual void Initialize(); + virtual void SendRaw(void*, size_t); + virtual std::string FriendlyName() { return "KernelLogger"; } +}; +}; // namespace skyline::logger \ No newline at end of file diff --git a/include/skyline/logger/Logger.hpp b/include/skyline/logger/Logger.hpp new file mode 100644 index 0000000..98d1970 --- /dev/null +++ b/include/skyline/logger/Logger.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include "nn/os.hpp" +#include "types.h" + +namespace skyline::logger { +class Logger; +extern Logger* s_Instance; + +class Logger { + public: + virtual void Initialize() = 0; + virtual void SendRaw(void*, size_t) = 0; + virtual std::string FriendlyName() = 0; + +#ifndef NOLOG + void StartThread(); + void Log(const char* data, size_t size = UINT32_MAX); + void Log(std::string str); + void LogFormat(const char* format, ...); + void SendRaw(const char*); + void SendRawFormat(const char*, ...); + void Flush(); +#else + inline void StartThread() {} + inline void Log(const char* data, size_t size = UINT32_MAX) {} + inline void Log(std::string str) {} + inline void LogFormat(const char* format, ...) {} + inline void SendRaw(const char*) {} + inline void SendRawFormat(const char*, ...) {} + inline void Flush() {} +#endif +}; +}; // namespace skyline::logger diff --git a/include/skyline/logger/SdLogger.hpp b/include/skyline/logger/SdLogger.hpp new file mode 100644 index 0000000..37e024c --- /dev/null +++ b/include/skyline/logger/SdLogger.hpp @@ -0,0 +1,17 @@ + +#pragma once + +#include + +#include "skyline/logger/Logger.hpp" + +namespace skyline::logger { +class SdLogger : public Logger { + public: + SdLogger(std::string); + + virtual void Initialize(); + virtual void SendRaw(void*, size_t); + virtual std::string FriendlyName() { return "SdLogger"; } +}; +}; // namespace skyline::logger \ No newline at end of file diff --git a/include/skyline/nx/arm/atomics.h b/include/skyline/nx/arm/atomics.h new file mode 100644 index 0000000..8dd96d0 --- /dev/null +++ b/include/skyline/nx/arm/atomics.h @@ -0,0 +1,20 @@ +/** + * @file atomics.h + * @brief AArch64 atomic operations. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" + +/// Atomically increments a 32-bit value. +static inline u32 atomicIncrement32(u32* p) { return __atomic_fetch_add(p, 1, __ATOMIC_SEQ_CST); } + +/// Atomically decrements a 32-bit value. +static inline u32 atomicDecrement32(u32* p) { return __atomic_sub_fetch(p, 1, __ATOMIC_SEQ_CST); } + +/// Atomically increments a 64-bit value. +static inline u64 atomicIncrement64(u64* p) { return __atomic_fetch_add(p, 1, __ATOMIC_SEQ_CST); } + +/// Atomically decrements a 64-bit value. +static inline u64 atomicDecrement64(u64* p) { return __atomic_sub_fetch(p, 1, __ATOMIC_SEQ_CST); } diff --git a/include/skyline/nx/arm/cache.h b/include/skyline/nx/arm/cache.h new file mode 100644 index 0000000..d107e3c --- /dev/null +++ b/include/skyline/nx/arm/cache.h @@ -0,0 +1,45 @@ +/** + * @file cache.h + * @brief AArch64 cache operations. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/** + * @brief Performs a data cache flush on the specified buffer. + * @param addr Address of the buffer. + * @param size Size of the buffer, in bytes. + * @remarks Cache flush is defined as Clean + Invalidate. + * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 + * system register). + */ +void armDCacheFlush(void* addr, size_t size); + +/** + * @brief Performs a data cache clean on the specified buffer. + * @param addr Address of the buffer. + * @param size Size of the buffer, in bytes. + * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 + * system register). + */ +void armDCacheClean(void* addr, size_t size); + +/** + * @brief Performs an instruction cache invalidation clean on the specified buffer. + * @param addr Address of the buffer. + * @param size Size of the buffer, in bytes. + * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 + * system register). + */ +void armICacheInvalidate(void* addr, size_t size); + +/** + * @brief Performs a data cache zeroing operation on the specified buffer. + * @param addr Address of the buffer. + * @param size Size of the buffer, in bytes. + * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 + * system register). + */ +void armDCacheZero(void* addr, size_t size); diff --git a/include/skyline/nx/arm/counter.h b/include/skyline/nx/arm/counter.h new file mode 100644 index 0000000..f4ebd6e --- /dev/null +++ b/include/skyline/nx/arm/counter.h @@ -0,0 +1,42 @@ +/** + * @file counter.h + * @brief AArch64 system counter-timer. + * @author fincs + * @copyright libnx Authors + */ +#pragma once +#include "../../../types.h" + +/** + * @brief Gets the current system tick. + * @return The current system tick. + */ +static inline u64 armGetSystemTick(void) { + u64 ret; + __asm__ __volatile__("mrs %x[data], cntpct_el0" : [ data ] "=r"(ret)); + return ret; +} + +/** + * @brief Gets the system counter-timer frequency + * @return The system counter-timer frequency, in Hz. + */ +static inline u64 armGetSystemTickFreq(void) { + u64 ret; + __asm__("mrs %x[data], cntfrq_el0" : [ data ] "=r"(ret)); + return ret; +} + +/** + * @brief Converts from nanoseconds to CPU ticks unit. + * @param ns Time in nanoseconds. + * @return Time in CPU ticks. + */ +static inline u64 armNsToTicks(u64 ns) { return (ns * 12) / 625; } + +/** + * @brief Converts from CPU ticks unit to nanoseconds. + * @param tick Time in ticks. + * @return Time in nanoseconds. + */ +static inline u64 armTicksToNs(u64 tick) { return (tick * 625) / 12; } diff --git a/include/skyline/nx/arm/thread_context.h b/include/skyline/nx/arm/thread_context.h new file mode 100644 index 0000000..cb8b4c8 --- /dev/null +++ b/include/skyline/nx/arm/thread_context.h @@ -0,0 +1,125 @@ +/** + * @file thread_context.h + * @brief AArch64 register dump format and related definitions. + * @author TuxSH + * @copyright libnx Authors + */ + +#pragma once +#include "types.h" + +/// Armv8 CPU register. +typedef union { + u64 x; ///< 64-bit AArch64 register view. + u32 w; ///< 32-bit AArch64 register view. + u32 r; ///< AArch32 register view. +} CpuRegister; + +/// Armv8 NEON register. +typedef union { + u128 v; ///< 128-bit vector view. + double d; ///< 64-bit double-precision view. + float s; ///< 32-bit single-precision view. +} FpuRegister; + +/// Armv8 register group. @ref svcGetThreadContext3 uses @ref RegisterGroup_All. +typedef enum { + RegisterGroup_CpuGprs = BIT(0), ///< General-purpose CPU registers (x0..x28 or r0..r10,r12). + RegisterGroup_CpuSprs = BIT(1), ///< Special-purpose CPU registers (fp, lr, sp, pc, PSTATE or cpsr, TPIDR_EL0). + RegisterGroup_FpuGprs = BIT(2), ///< General-purpose NEON registers. + RegisterGroup_FpuSprs = BIT(3), ///< Special-purpose NEON registers. + + RegisterGroup_CpuAll = RegisterGroup_CpuGprs | RegisterGroup_CpuSprs, ///< All CPU registers. + RegisterGroup_FpuAll = RegisterGroup_FpuGprs | RegisterGroup_FpuSprs, ///< All NEON registers. + RegisterGroup_All = RegisterGroup_CpuAll | RegisterGroup_FpuAll, ///< All registers. +} RegisterGroup; + +/// This is for \ref ThreadExceptionDump error_desc. +typedef enum { + ThreadExceptionDesc_InstructionAbort = 0x100, ///< Instruction abort + ThreadExceptionDesc_MisalignedPC = 0x102, ///< Misaligned PC + ThreadExceptionDesc_MisalignedSP = 0x103, ///< Misaligned SP + ThreadExceptionDesc_SError = 0x106, ///< SError [not in 1.0.0?] + ThreadExceptionDesc_BadSVC = 0x301, ///< Bad SVC + ThreadExceptionDesc_Trap = + 0x104, ///< Uncategorized, CP15RTTrap, CP15RRTTrap, CP14RTTrap, CP14RRTTrap, IllegalState, SystemRegisterTrap + ThreadExceptionDesc_Other = 0x101, ///< None of the above, EC <= 0x34 and not a breakpoint +} ThreadExceptionDesc; + +/// Thread context structure (register dump) +typedef struct { + CpuRegister cpu_gprs[29]; ///< GPRs 0..28. Note: also contains AArch32 SPRs. + u64 fp; ///< Frame pointer (x29) (AArch64). For AArch32, check r11. + u64 lr; ///< Link register (x30) (AArch64). For AArch32, check r14. + u64 sp; ///< Stack pointer (AArch64). For AArch32, check r13. + CpuRegister pc; ///< Program counter. + u32 psr; ///< PSTATE or cpsr. + + FpuRegister fpu_gprs[32]; ///< 32 general-purpose NEON registers. + u32 fpcr; ///< Floating-point control register. + u32 fpsr; ///< Floating-point status register. + + u64 tpidr; ///< EL0 Read/Write Software Thread ID Register. +} ThreadContext; + +/// Thread exception dump structure. +typedef struct { + u32 error_desc; ///< See \ref ThreadExceptionDesc. + u32 pad[3]; + + CpuRegister cpu_gprs[29]; ///< GPRs 0..28. Note: also contains AArch32 registers. + CpuRegister fp; ///< Frame pointer. + CpuRegister lr; ///< Link register. + CpuRegister sp; ///< Stack pointer. + CpuRegister pc; ///< Program counter (elr_el1). + + u64 padding; + + FpuRegister fpu_gprs[32]; ///< 32 general-purpose NEON registers. + + u32 pstate; ///< pstate & 0xFF0FFE20 + u32 afsr0; + u32 afsr1; + u32 esr; + + CpuRegister far; ///< Fault Address Register. +} ThreadExceptionDump; + +typedef struct { + u64 cpu_gprs[9]; ///< GPRs 0..8. + u64 lr; + u64 sp; + u64 elr_el1; + u32 pstate; ///< pstate & 0xFF0FFE20 + u32 afsr0; + u32 afsr1; + u32 esr; + u64 far; +} ThreadExceptionFrameA64; + +typedef struct { + u32 cpu_gprs[8]; ///< GPRs 0..7. + u32 sp; + u32 lr; + u32 elr_el1; + u32 tpidr_el0; ///< tpidr_el0 = 1 + u32 cpsr; ///< cpsr & 0xFF0FFE20 + u32 afsr0; + u32 afsr1; + u32 esr; + u32 far; +} ThreadExceptionFrameA32; + +/** + * @brief Determines whether a thread context belong to an AArch64 process based on the PSR. + * @param[in] ctx Thread context to which PSTATE/cspr has been dumped to. + * @return true if and only if the thread context belongs to an AArch64 process. + */ +static inline bool threadContextIsAArch64(const ThreadContext* ctx) { return (ctx->psr & 0x10) == 0; } + +/** + * @brief Determines whether a ThreadExceptionDump belongs to an AArch64 process based on the PSTATE. + * @param[in] ctx ThreadExceptionDump. + * @return true if and only if the ThreadExceptionDump belongs to an AArch64 process. + */ +static inline bool threadExceptionIsAArch64(const ThreadExceptionDump* ctx) { return (ctx->pstate & 0x10) == 0; } diff --git a/include/skyline/nx/arm/tls.h b/include/skyline/nx/arm/tls.h new file mode 100644 index 0000000..427fc77 --- /dev/null +++ b/include/skyline/nx/arm/tls.h @@ -0,0 +1,18 @@ +/** + * @file tls.h + * @brief AArch64 thread local storage. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/** + * @brief Gets the thread local storage buffer. + * @return The thread local storage buffer. + */ +static inline void* armGetTls(void) { + void* ret; + __asm__("mrs %x[data], tpidrro_el0" : [ data ] "=r"(ret)); + return ret; +} diff --git a/include/skyline/nx/kernel/barrier.h b/include/skyline/nx/kernel/barrier.h new file mode 100644 index 0000000..7024eba --- /dev/null +++ b/include/skyline/nx/kernel/barrier.h @@ -0,0 +1,30 @@ +/** + * @file barrier.h + * @brief Multi-threading Barrier + * @author tatehaga + * @copyright libnx Authors + */ +#pragma once +#include "condvar.h" +#include "mutex.h" + +/// Barrier structure. +typedef struct Barrier { + u64 count; ///< Number of threads to reach the barrier. + u64 total; ///< Number of threads to wait on. + Mutex mutex; + CondVar condvar; +} Barrier; + +/** + * @brief Initializes a barrier and the number of threads to wait on. + * @param b Barrier object. + * @param thread_count Initial value for the number of threads the barrier must wait for. + */ +void barrierInit(Barrier* b, u64 thread_count); + +/** + * @brief Forces threads to wait until all threads have called barrierWait. + * @param b Barrier object. + */ +void barrierWait(Barrier* b); diff --git a/include/skyline/nx/kernel/condvar.h b/include/skyline/nx/kernel/condvar.h new file mode 100644 index 0000000..67233b5 --- /dev/null +++ b/include/skyline/nx/kernel/condvar.h @@ -0,0 +1,63 @@ +/** + * @file condvar.h + * @brief Condition variable synchronization primitive. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "../kernel/mutex.h" +#include "../kernel/svc.h" +#include "types.h" + +/// Condition variable. +typedef u32 CondVar; + +/** + * @brief Initializes a condition variable. + * @param[in] c Condition variable object. + */ +static inline void condvarInit(CondVar* c) { *c = 0; } + +/** + * @brief Waits on a condition variable with a timeout. + * @param[in] c Condition variable object. + * @param[in] m Mutex object to use inside the condition variable. + * @param[in] timeout Timeout in nanoseconds. + * @return Result code (0xEA01 on timeout). + * @remark On function return, the underlying mutex is acquired. + */ +Result condvarWaitTimeout(CondVar* c, Mutex* m, u64 timeout); + +/** + * @brief Waits on a condition variable. + * @param[in] c Condition variable object. + * @param[in] m Mutex object to use inside the condition variable. + * @return Result code. + * @remark On function return, the underlying mutex is acquired. + */ +static inline Result condvarWait(CondVar* c, Mutex* m) { return condvarWaitTimeout(c, m, UINT64_MAX); } + +/** + * @brief Wakes up up to the specified number of threads waiting on a condition variable. + * @param[in] c Condition variable object. + * @param[in] num Maximum number of threads to wake up (or -1 to wake them all up). + * @return Result code. + */ +static inline Result condvarWake(CondVar* c, int num) { + svcSignalProcessWideKey(c, num); + return 0; +} + +/** + * @brief Wakes up a single thread waiting on a condition variable. + * @param[in] c Condition variable object. + * @return Result code. + */ +static inline Result condvarWakeOne(CondVar* c) { return condvarWake(c, 1); } + +/** + * @brief Wakes up all thread waiting on a condition variable. + * @param[in] c Condition variable object. + * @return Result code. + */ +static inline Result condvarWakeAll(CondVar* c) { return condvarWake(c, -1); } diff --git a/include/skyline/nx/kernel/detect.h b/include/skyline/nx/kernel/detect.h new file mode 100644 index 0000000..77924a1 --- /dev/null +++ b/include/skyline/nx/kernel/detect.h @@ -0,0 +1,39 @@ +/** + * @file detect.h + * @brief Kernel capability detection + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/// Returns the kernel version that can be detected by checking kernel capabilities. This only goes from 1 +/// (representing 1.0.0) up to 6 (representing 6.0.0 and above). Generally, \ref hosversionGet should be used instead +/// of this function. +int detectKernelVersion(void); +/// Returns true if the process has a debugger attached. +bool detectDebugger(void); +/// Returns true if the kernel is patched to allow self-process-jit. +bool detectJitKernelPatch(void); +/// After this has been called, libnx will ignore the self-process-jit kernel patch. For testing purposes only. +void detectIgnoreJitKernelPatch(void); + +/// Returns true if the kernel version is equal to or above 2.0.0. Generally, \ref hosversionAtLeast should be used +/// instead of this function. +static inline bool kernelAbove200(void) { return detectKernelVersion() >= 2; } + +/// Returns true if the kernel version is equal to or above 3.0.0. Generally, \ref hosversionAtLeast should be used +/// instead of this function. +static inline bool kernelAbove300(void) { return detectKernelVersion() >= 3; } + +/// Returns true if the kernel version is equal to or above 4.0.0. Generally, \ref hosversionAtLeast should be used +/// instead of this function. +static inline bool kernelAbove400(void) { return detectKernelVersion() >= 4; } + +/// Returns true if the kernel version is equal to or above 5.0.0. Generally, \ref hosversionAtLeast should be used +/// instead of this function. +static inline bool kernelAbove500(void) { return detectKernelVersion() >= 5; } + +/// Returns true if the kernel version is equal to or above 6.0.0. Generally, \ref hosversionAtLeast should be used +/// instead of this function. +static inline bool kernelAbove600(void) { return detectKernelVersion() >= 6; } diff --git a/include/skyline/nx/kernel/event.h b/include/skyline/nx/kernel/event.h new file mode 100644 index 0000000..08806e1 --- /dev/null +++ b/include/skyline/nx/kernel/event.h @@ -0,0 +1,81 @@ +/** + * @file event.h + * @brief Kernel-mode event synchronization primitive. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "../result.h" +#include "../types.h" +#include "wait.h" + +/// Kernel-mode event structure. +typedef struct { + Handle revent; ///< Read-only event handle + Handle wevent; ///< Write-only event handle + bool autoclear; ///< Autoclear flag +} Event; + +/// Creates a \ref Waiter for a kernel-mode event. +static inline Waiter waiterForEvent(Event* t) { + Waiter wait_obj; + wait_obj.type = t->autoclear ? WaiterType_HandleWithClear : WaiterType_Handle; + wait_obj.handle = t->revent; + return wait_obj; +} + +/** + * @brief Creates a kernel-mode event. + * @param[out] t Pointer to \ref Event structure. + * @param[in] autoclear Autoclear flag. + * @return Result code. + * @warning This is a privileged operation; in normal circumstances applications shouldn't use this function. + */ +Result eventCreate(Event* t, bool autoclear); + +/** + * @brief Loads a kernel-mode event obtained from IPC. + * @param[out] t Pointer to \ref Event structure. + * @param[in] handle Read-only event handle. + * @param[in] autoclear Autoclear flag. + */ +void eventLoadRemote(Event* t, Handle handle, bool autoclear); + +/** + * @brief Closes a kernel-mode event. + * @param[in] t Pointer to \ref Event structure. + */ +void eventClose(Event* t); + +/** + * @brief Returns whether an \ref Event is initialized. + * @param[in] t Pointer to \ref Event structure. + * @return Initialization status. + */ +static inline bool eventActive(Event* t) { return t->revent != INVALID_HANDLE; } + +/** + * @brief Waits on a kernel-mode event. + * @param[in] t Pointer to \ref Event structure. + * @param[in] timeout Timeout in nanoseconds (pass UINT64_MAX to wait indefinitely). + * @return Result code. + */ +Result eventWait(Event* t, u64 timeout); + +/** + * @brief Signals a kernel-mode event. + * @param[in] t Pointer to \ref Event structure. + * @return Result code. + * @note This function only works for events initialized with \ref eventCreate, it doesn't work with events initialized + * with \ref eventLoadRemote. + * @warning This is a privileged operation; in normal circumstances applications shouldn't use this function. + */ +Result eventFire(Event* t); + +/** + * @brief Clears a kernel-mode event. + * @param[in] t Pointer to \ref Event structure. + * @return Result code. + * @note This function shouldn't be used on autoclear events. + */ +Result eventClear(Event* t); diff --git a/include/skyline/nx/kernel/jit.h b/include/skyline/nx/kernel/jit.h new file mode 100644 index 0000000..ca52401 --- /dev/null +++ b/include/skyline/nx/kernel/jit.h @@ -0,0 +1,69 @@ +/** + * @file jit.h + * @brief Just-in-time compilation support. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/// JIT implementation type. +typedef enum { + JitType_CodeMemory, ///< JIT supported using svcSetProcessMemoryPermission + JitType_JitMemory, ///< JIT supported using [4.0.0+] code-memory syscalls (this isn't usable on [5.0.0+] so + ///< JitType_CodeMemory is used instead). +} JitType; + +/// JIT buffer object. +typedef struct { + JitType type; + size_t size; + void* src_addr; + void* rx_addr; + void* rw_addr; + bool is_executable; + Handle handle; +} Jit; + +/** + * @brief Creates a JIT buffer. + * @param j JIT buffer. + * @param size Size of the JIT buffer. + * @return Result code. + */ +Result jitCreate(Jit* j, void* src_addr, size_t size); + +/** + * @brief Transition a JIT buffer to have writable permission. + * @param j JIT buffer. + * @return Result code. + */ +Result jitTransitionToWritable(Jit* j); + +/** + * @brief Transition a JIT buffer to have executable permission. + * @param j JIT buffer. + * @return Result code. + */ +Result jitTransitionToExecutable(Jit* j); + +/** + * @brief Destroys a JIT buffer. + * @param j JIT buffer. + * @return Result code. + */ +Result jitClose(Jit* j); + +/** + * @brief Gets the address of the writable memory alias of a JIT buffer. + * @param j JIT buffer. + * @return Pointer to alias of the JIT buffer that can be written to. + */ +void* jitGetRwAddr(Jit* j); + +/** + * @brief Gets the address of the executable memory alias of a JIT buffer. + * @param j JIT buffer. + * @return Pointer to alias of the JIT buffer that can be executed. + */ +void* jitGetRxAddr(Jit* j); diff --git a/include/skyline/nx/kernel/mutex.h b/include/skyline/nx/kernel/mutex.h new file mode 100644 index 0000000..c3f22ca --- /dev/null +++ b/include/skyline/nx/kernel/mutex.h @@ -0,0 +1,68 @@ +/** + * @file mutex.h + * @brief Mutex synchronization primitive. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include + +#include "nn/os.h" +#include "types.h" + +/// Mutex datatype, defined in newlib. +typedef nnosMutexType Mutex; +/// Recursive mutex datatype, defined in newlib. +typedef nnosMutexType RMutex; + +/** + * @brief Initializes a mutex. + * @param m Mutex object. + * @note A mutex can also be statically initialized by assigning 0 to it. + */ +static inline void mutexInit(Mutex* m) { nnosInitializeMutex(m, false, 0); } + +/** + * @brief Locks a mutex. + * @param m Mutex object. + */ +void mutexLock(Mutex* m); + +/** + * @brief Attempts to lock a mutex without waiting. + * @param m Mutex object. + * @return 1 if the mutex has been acquired successfully, and 0 on contention. + */ +bool mutexTryLock(Mutex* m); + +/** + * @brief Unlocks a mutex. + * @param m Mutex object. + */ +void mutexUnlock(Mutex* m); + +/** + * @brief Initializes a recursive mutex. + * @param m Recursive mutex object. + * @note A recursive mutex can also be statically initialized by assigning {0,0,0} to it. + */ +static inline void rmutexInit(RMutex* m) { nnosInitializeMutex(m, true, 0); } + +/** + * @brief Locks a recursive mutex. + * @param m Recursive mutex object. + */ +void rmutexLock(RMutex* m); + +/** + * @brief Attempts to lock a recursive mutex without waiting. + * @param m Recursive mutex object. + * @return 1 if the mutex has been acquired successfully, and 0 on contention. + */ +bool rmutexTryLock(RMutex* m); + +/** + * @brief Unlocks a recursive mutex. + * @param m Recursive mutex object. + */ +void rmutexUnlock(RMutex* m); diff --git a/include/skyline/nx/kernel/random.h b/include/skyline/nx/kernel/random.h new file mode 100644 index 0000000..5f5e2bd --- /dev/null +++ b/include/skyline/nx/kernel/random.h @@ -0,0 +1,21 @@ +/** + * @file random.h + * @brief OS-seeded pseudo-random number generation support (ChaCha algorithm). + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" + +/** + * @brief Fills a buffer with random data. + * @param buf Pointer to the buffer. + * @param len Size of the buffer in bytes. + */ +void randomGet(void* buf, size_t len); + +/** + * @brief Returns a random 64-bit value. + * @return Random value. + */ +u64 randomGet64(void); diff --git a/include/skyline/nx/kernel/rwlock.h b/include/skyline/nx/kernel/rwlock.h new file mode 100644 index 0000000..c8d0999 --- /dev/null +++ b/include/skyline/nx/kernel/rwlock.h @@ -0,0 +1,80 @@ +/** + * @file rwlock.h + * @brief Read/write lock synchronization primitive. + * @author plutoo, SciresM + * @copyright libnx Authors + */ +#pragma once +#include "../kernel/condvar.h" +#include "../kernel/mutex.h" + +/// Read/write lock structure. +typedef struct { + Mutex mutex; + CondVar condvar_reader_wait; + CondVar condvar_writer_wait; + u32 read_lock_count; + u32 read_waiter_count; + u32 write_lock_count; + u32 write_waiter_count; + u32 write_owner_tag; +} RwLock; + +/** + * @brief Initializes the read/write lock. + * @param r Read/write lock object. + */ +void rwlockInit(RwLock* r); + +/** + * @brief Locks the read/write lock for reading. + * @param r Read/write lock object. + */ +void rwlockReadLock(RwLock* r); + +/** + * @brief Attempts to lock the read/write lock for reading without waiting. + * @param r Read/write lock object. + * @return 1 if the mutex has been acquired successfully, and 0 on contention. + */ +bool rwlockTryReadLock(RwLock* r); + +/** + * @brief Unlocks the read/write lock for reading. + * @param r Read/write lock object. + */ +void rwlockReadUnlock(RwLock* r); + +/** + * @brief Locks the read/write lock for writing. + * @param r Read/write lock object. + */ +void rwlockWriteLock(RwLock* r); + +/** + * @brief Attempts to lock the read/write lock for writing without waiting. + * @param r Read/write lock object. + * @return 1 if the mutex has been acquired successfully, and 0 on contention. + */ +bool rwlockTryWriteLock(RwLock* r); + +/** + * @brief Unlocks the read/write lock for writing. + * @param r Read/write lock object. + */ +void rwlockWriteUnlock(RwLock* r); + +/** + * @brief Checks if the write lock is held by the current thread. + * @param r Read/write lock object. + * @return 1 if the current hold holds the write lock, and 0 if it does not. + */ +bool rwlockIsWriteLockHeldByCurrentThread(RwLock* r); + +/** + * @brief Checks if the read/write lock is owned by the current thread. + * @param r Read/write lock object. + * @return 1 if the current hold holds the write lock or if it holds read locks acquired + * while it held the write lock, and 0 if it does not. + */ +bool rwlockIsOwnedByCurrentThread(RwLock* r); diff --git a/include/skyline/nx/kernel/semaphore.h b/include/skyline/nx/kernel/semaphore.h new file mode 100644 index 0000000..89ad920 --- /dev/null +++ b/include/skyline/nx/kernel/semaphore.h @@ -0,0 +1,43 @@ +/** + * @file semaphore.h + * @brief Thread synchronization based on Mutex. + * @author SciresM & Kevoot + * @copyright libnx Authors + */ +#pragma once + +#include "condvar.h" +#include "mutex.h" + +/// Semaphore structure. +typedef struct Semaphore { + CondVar condvar; ///< Condition variable object. + Mutex mutex; ///< Mutex object. + u64 count; ///< Internal counter. +} Semaphore; + +/** + * @brief Initializes a semaphore and its internal counter. + * @param s Semaphore object. + * @param initial_count initial value for internal counter (typically the # of free resources). + */ +void semaphoreInit(Semaphore* s, u64 initial_count); + +/** + * @brief Increments the Semaphore to allow other threads to continue. + * @param s Semaphore object. + */ +void semaphoreSignal(Semaphore* s); + +/** + * @brief Decrements Semaphore and waits if 0. + * @param s Semaphore object. + */ +void semaphoreWait(Semaphore* s); + +/** + * @brief Attempts to get lock without waiting. + * @param s Semaphore object. + * @return true if no wait and successful lock, false otherwise. + */ +bool semaphoreTryWait(Semaphore* s); diff --git a/include/skyline/nx/kernel/shmem.h b/include/skyline/nx/kernel/shmem.h new file mode 100644 index 0000000..b46c158 --- /dev/null +++ b/include/skyline/nx/kernel/shmem.h @@ -0,0 +1,77 @@ +/** + * @file shmem.h + * @brief Shared memory object handling + * @author plutoo + * @copyright libnx Authors + * @remark Shared memory differs from transfer memory in the fact that the kernel (as opposed to the user process) + * allocates and owns its backing memory. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/kernel/svc.h" +#include "types.h" + +/// Shared memory information structure. +typedef struct { + Handle handle; ///< Kernel object handle. + size_t size; ///< Size of the shared memory object. + Permission perm; ///< Permissions. + void* map_addr; ///< Address to which the shared memory object is mapped. +} SharedMemory; + +/** + * @brief Creates a shared memory object. + * @param s Shared memory information structure which will be filled in. + * @param size Size of the shared memory object to create. + * @param local_perm Permissions with which the shared memory object will be mapped in the local process. + * @param remote_perm Permissions with which the shared memory object will be mapped in the remote process (can be + * Perm_DontCare). + * @return Result code. + * @warning This is a privileged operation; in normal circumstances applications cannot use this function. + */ +Result shmemCreate(SharedMemory* s, size_t size, Permission local_perm, Permission remote_perm); + +/** + * @brief Loads a shared memory object coming from a remote process. + * @param s Shared memory information structure which will be filled in. + * @param handle Handle of the shared memory object. + * @param size Size of the shared memory object that is being loaded. + * @param perm Permissions with which the shared memory object will be mapped in the local process. + */ +void shmemLoadRemote(SharedMemory* s, Handle handle, size_t size, Permission perm); + +/** + * @brief Maps a shared memory object. + * @param s Shared memory information structure. + * @return Result code. + */ +Result shmemMap(SharedMemory* s); + +/** + * @brief Unmaps a shared memory object. + * @param s Shared memory information structure. + * @return Result code. + */ +Result shmemUnmap(SharedMemory* s); + +/** + * @brief Retrieves the mapped address of a shared memory object. + * @param s Shared memory information structure. + * @return Mapped address of the shared memory object. + */ +static inline void* shmemGetAddr(SharedMemory* s) { return s->map_addr; } + +/** + * @brief Frees up resources used by a shared memory object, unmapping and closing handles, etc. + * @param s Shared memory information structure. + * @return Result code. + */ +Result shmemClose(SharedMemory* s); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/include/skyline/nx/kernel/svc.h b/include/skyline/nx/kernel/svc.h new file mode 100644 index 0000000..53feb8e --- /dev/null +++ b/include/skyline/nx/kernel/svc.h @@ -0,0 +1,1247 @@ +/** + * @file svc.h + * @brief Wrappers for kernel syscalls. + * @copyright libnx Authors + */ +#pragma once +#include "../arm/thread_context.h" +#include "types.h" + +/// Pseudo handle for the current process. +#define CUR_PROCESS_HANDLE 0xFFFF8001 + +/// Pseudo handle for the current thread. +#define CUR_THREAD_HANDLE 0xFFFF8000 + +/// Maximum number of objects that can be waited on by \ref svcWaitSynchronization (Horizon kernel limitation). +#define MAX_WAIT_OBJECTS 0x40 + +/// Memory type enumeration (lower 8 bits of \ref MemoryState) +typedef enum { + MemType_Unmapped = 0x00, ///< Unmapped memory. + MemType_Io = 0x01, ///< Mapped by kernel capability parsing in \ref svcCreateProcess. + MemType_Normal = 0x02, ///< Mapped by kernel capability parsing in \ref svcCreateProcess. + MemType_CodeStatic = 0x03, ///< Mapped during \ref svcCreateProcess. + MemType_CodeMutable = + 0x04, ///< Transition from MemType_CodeStatic performed by \ref svcSetProcessMemoryPermission. + MemType_Heap = 0x05, ///< Mapped using \ref svcSetHeapSize. + MemType_SharedMem = 0x06, ///< Mapped using \ref svcMapSharedMemory. + MemType_WeirdMappedMem = 0x07, ///< Mapped using \ref svcMapMemory. + MemType_ModuleCodeStatic = 0x08, ///< Mapped using \ref svcMapProcessCodeMemory. + MemType_ModuleCodeMutable = + 0x09, ///< Transition from \ref MemType_ModuleCodeStatic performed by \ref svcSetProcessMemoryPermission. + MemType_IpcBuffer0 = 0x0A, ///< IPC buffers with descriptor flags=0. + MemType_MappedMemory = 0x0B, ///< Mapped using \ref svcMapMemory. + MemType_ThreadLocal = 0x0C, ///< Mapped during \ref svcCreateThread. + MemType_TransferMemIsolated = + 0x0D, ///< Mapped using \ref svcMapTransferMemory when the owning process has perm=0. + MemType_TransferMem = 0x0E, ///< Mapped using \ref svcMapTransferMemory when the owning process has perm!=0. + MemType_ProcessMem = 0x0F, ///< Mapped using \ref svcMapProcessMemory. + MemType_Reserved = 0x10, ///< Reserved. + MemType_IpcBuffer1 = 0x11, ///< IPC buffers with descriptor flags=1. + MemType_IpcBuffer3 = 0x12, ///< IPC buffers with descriptor flags=3. + MemType_KernelStack = 0x13, ///< Mapped in kernel during \ref svcCreateThread. + MemType_CodeReadOnly = 0x14, ///< Mapped in kernel during \ref svcControlCodeMemory. + MemType_CodeWritable = 0x15, ///< Mapped in kernel during \ref svcControlCodeMemory. +} MemoryType; + +/// Memory state bitmasks. +typedef enum { + MemState_Type = 0xFF, ///< Type field (see \ref MemoryType). + MemState_PermChangeAllowed = BIT(8), ///< Permission change allowed. + MemState_ForceRwByDebugSyscalls = BIT(9), ///< Force read/writable by debug syscalls. + MemState_IpcSendAllowed_Type0 = BIT(10), ///< IPC type 0 send allowed. + MemState_IpcSendAllowed_Type3 = BIT(11), ///< IPC type 3 send allowed. + MemState_IpcSendAllowed_Type1 = BIT(12), ///< IPC type 1 send allowed. + MemState_ProcessPermChangeAllowed = BIT(14), ///< Process permission change allowed. + MemState_MapAllowed = BIT(15), ///< Map allowed. + MemState_UnmapProcessCodeMemAllowed = BIT(16), ///< Unmap process code memory allowed. + MemState_TransferMemAllowed = BIT(17), ///< Transfer memory allowed. + MemState_QueryPAddrAllowed = BIT(18), ///< Query physical address allowed. + MemState_MapDeviceAllowed = + BIT(19), ///< Map device allowed (\ref svcMapDeviceAddressSpace and \ref svcMapDeviceAddressSpaceByForce). + MemState_MapDeviceAlignedAllowed = BIT(20), ///< Map device aligned allowed. + MemState_IpcBufferAllowed = BIT(21), ///< IPC buffer allowed. + MemState_IsPoolAllocated = BIT(22), ///< Is pool allocated. + MemState_IsRefCounted = MemState_IsPoolAllocated, ///< Alias for \ref MemState_IsPoolAllocated. + MemState_MapProcessAllowed = BIT(23), ///< Map process allowed. + MemState_AttrChangeAllowed = BIT(24), ///< Attribute change allowed. + MemState_CodeMemAllowed = BIT(25), ///< Code memory allowed. +} MemoryState; + +/// Memory attribute bitmasks. +typedef enum { + MemAttr_IsBorrowed = BIT(0), ///< Is borrowed memory. + MemAttr_IsIpcMapped = BIT(1), ///< Is IPC mapped (when IpcRefCount > 0). + MemAttr_IsDeviceMapped = BIT(2), ///< Is device mapped (when DeviceRefCount > 0). + MemAttr_IsUncached = BIT(3), ///< Is uncached. +} MemoryAttribute; + +/// Memory permission bitmasks. +typedef enum { + Perm_None = 0, ///< No permissions. + Perm_R = BIT(0), ///< Read permission. + Perm_W = BIT(1), ///< Write permission. + Perm_X = BIT(2), ///< Execute permission. + Perm_Rw = Perm_R | Perm_W, ///< Read/write permissions. + Perm_Rx = Perm_R | Perm_X, ///< Read/execute permissions. + Perm_DontCare = BIT(28), ///< Don't care +} Permission; + +/// Memory information structure. +typedef struct { + u64 addr; ///< Base address. + u64 size; ///< Size. + u32 type; ///< Memory type (see lower 8 bits of \ref MemoryState). + u32 attr; ///< Memory attributes (see \ref MemoryAttribute). + u32 perm; ///< Memory permissions (see \ref Permission). + u32 device_refcount; ///< Device reference count. + u32 ipc_refcount; ///< IPC reference count. + u32 padding; ///< Padding. +} MemoryInfo; + +/// Secure monitor arguments. +typedef struct { + u64 X[8]; ///< Values of X0 through X7. +} PACKED SecmonArgs; + +/// Code memory mapping operations +typedef enum { + CodeMapOperation_MapOwner = 0, ///< Map owner. + CodeMapOperation_MapSlave = 1, ///< Map slave. + CodeMapOperation_UnmapOwner = 2, ///< Unmap owner. + CodeMapOperation_UnmapSlave = 3, ///< Unmap slave. +} CodeMapOperation; + +/// Limitable Resources. +typedef enum { + LimitableResource_Memory = 0, ///< How much memory can a process map. + LimitableResource_Threads = 1, ///< How many threads can a process spawn. + LimitableResource_Events = 2, ///< How many events can a process have. + LimitableResource_TransferMemories = 3, ///< How many transfer memories can a process make. + LimitableResource_Sessions = 4, ///< How many sessions can a process own. +} LimitableResource; + +/// Process Information. +typedef enum { + ProcessInfoType_ProcessState = 0, ///< What state is a process in. +} ProcessInfoType; + +/// Process States. +typedef enum { + ProcessState_Created = 0, ///< Newly-created process, not yet started. + ProcessState_CreatedAttached = 1, ///< Newly-created process, not yet started but attached to debugger. + ProcessState_Running = 2, ///< Process that is running normally (and detached from any debugger). + ProcessState_Crashed = 3, ///< Process that has just crashed. + ProcessState_RunningAttached = 4, ///< Process that is running normally, attached to a debugger. + ProcessState_Exiting = 5, ///< Process has begun exiting. + ProcessState_Exited = 6, ///< Process has finished exiting. + ProcessState_DebugSuspended = 7, ///< Process execution suspended by debugger. +} ProcessState; + +/// Debug Thread Parameters. +typedef enum { + DebugThreadParam_ActualPriority = 0, + DebugThreadParam_State = 1, + DebugThreadParam_IdealCore = 2, + DebugThreadParam_CurrentCore = 3, + DebugThreadParam_CoreMask = 4, +} DebugThreadParam; + +/// GetInfo IDs. +typedef enum { + InfoType_CoreMask = 0, ///< Bitmask of allowed Core IDs. + InfoType_PriorityMask = 1, ///< Bitmask of allowed Thread Priorities. + InfoType_AliasRegionAddress = 2, ///< Base of the Alias memory region. + InfoType_AliasRegionSize = 3, ///< Size of the Alias memory region. + InfoType_HeapRegionAddress = 4, ///< Base of the Heap memory region. + InfoType_HeapRegionSize = 5, ///< Size of the Heap memory region. + InfoType_TotalMemorySize = 6, ///< Total amount of memory available for process. + InfoType_UsedMemorySize = 7, ///< Amount of memory currently used by process. + InfoType_DebuggerAttached = 8, ///< Whether current process is being debugged. + InfoType_ResourceLimit = 9, ///< Current process's resource limit handle. + InfoType_IdleTickCount = 10, ///< Number of idle ticks on CPU. + InfoType_RandomEntropy = 11, ///< [2.0.0+] Random entropy for current process. + InfoType_AslrRegionAddress = 12, ///< [2.0.0+] Base of the process's address space. + InfoType_AslrRegionSize = 13, ///< [2.0.0+] Size of the process's address space. + InfoType_StackRegionAddress = 14, ///< [2.0.0+] Base of the Stack memory region. + InfoType_StackRegionSize = 15, ///< [2.0.0+] Size of the Stack memory region. + InfoType_SystemResourceSizeTotal = 16, ///< [3.0.0+] Total memory allocated for process memory management. + InfoType_SystemResourceSizeUsed = 17, ///< [3.0.0+] Amount of memory currently used by process memory management. + InfoType_ProgramId = 18, ///< [3.0.0+] Program ID for the process. + InfoType_InitialProcessIdRange = 19, ///< [4.0.0-4.1.0] Min/max initial process IDs. + InfoType_UserExceptionContextAddress = 20, ///< [5.0.0+] Address of the process's exception context (for break). + InfoType_TotalNonSystemMemorySize = + 21, ///< [6.0.0+] Total amount of memory available for process, excluding that for process memory management. + InfoType_UsedNonSystemMemorySize = + 22, ///< [6.0.0+] Amount of memory used by process, excluding that for process memory management. + InfoType_IsApplication = 23, ///< [9.0.0+] Whether the specified process is an Application. + + InfoType_ThreadTickCount = 0xF0000002, ///< Number of ticks spent on thread. +} InfoType; + +/// GetSystemInfo IDs. +typedef enum { + SystemInfoType_TotalPhysicalMemorySize = 0, ///< Total amount of DRAM available to system. + SystemInfoType_UsedPhysicalMemorySize = 1, ///< Current amount of DRAM used by system. + SystemInfoType_InitialProcessIdRange = 2, ///< Min/max initial process IDs. +} SystemInfoType; + +/// GetInfo Idle/Thread Tick Count Sub IDs. +typedef enum { + TickCountInfo_Core0 = 0, ///< Tick count on core 0. + TickCountInfo_Core1 = 1, ///< Tick count on core 1. + TickCountInfo_Core2 = 2, ///< Tick count on core 2. + TickCountInfo_Core3 = 3, ///< Tick count on core 3. + + TickCountInfo_Total = UINT64_MAX, ///< Tick count on all cores. +} TickCountInfo; + +/// GetInfo InitialProcessIdRange Sub IDs. +typedef enum { + InitialProcessIdRangeInfo_Minimum = 0, ///< Lowest initial process ID. + InitialProcessIdRangeInfo_Maximum = 1, ///< Highest initial process ID. +} InitialProcessIdRangeInfo; + +/// GetSystemInfo PhysicalMemory Sub IDs. +typedef enum { + PhysicalMemoryInfo_Application = 0, ///< Memory allocated for application usage. + PhysicalMemoryInfo_Applet = 1, ///< Memory allocated for applet usage. + PhysicalMemoryInfo_System = 2, ///< Memory allocated for system usage. + PhysicalMemoryInfo_SystemUnsafe = 3, ///< Memory allocated for unsafe system usage (accessible to devices). +} PhysicalMemoryInfo; + +///@name Memory management +///@{ + +/** + * @brief Set the process heap to a given size. It can both extend and shrink the heap. + * @param[out] out_addr Variable to which write the address of the heap (which is randomized and fixed by the kernel) + * @param[in] size Size of the heap, must be a multiple of 0x2000000 and [2.0.0+] less than 0x18000000. + * @return Result code. + * @note Syscall number 0x00. + */ +Result svcSetHeapSize(void** out_addr, u64 size); + +/** + * @brief Set the memory permissions of a (page-aligned) range of memory. + * @param[in] addr Start address of the range. + * @param[in] size Size of the range, in bytes. + * @param[in] perm Permissions (see \ref Permission). + * @return Result code. + * @remark Perm_X is not allowed. Setting write-only is not allowed either (Perm_W). + * This can be used to move back and forth between Perm_None, Perm_R and Perm_Rw. + * @note Syscall number 0x01. + */ +Result svcSetMemoryPermission(void* addr, u64 size, u32 perm); + +/** + * @brief Set the memory attributes of a (page-aligned) range of memory. + * @param[in] addr Start address of the range. + * @param[in] size Size of the range, in bytes. + * @param[in] val0 State0 + * @param[in] val1 State1 + * @return Result code. + * @remark See switchbrew.org Wiki for more + * details. + * @note Syscall number 0x02. + */ +Result svcSetMemoryAttribute(void* addr, u64 size, u32 val0, u32 val1); + +/** + * @brief Maps a memory range into a different range. Mainly used for adding guard pages around stack. + * Source range gets reprotected to Perm_None (it can no longer be accessed), and \ref MemAttr_IsBorrowed is set in the + * source \ref MemoryAttribute. + * @param[in] dst_addr Destination address. + * @param[in] src_addr Source address. + * @param[in] size Size of the range. + * @return Result code. + * @note Syscall number 0x04. + */ +Result svcMapMemory(void* dst_addr, void* src_addr, u64 size); + +/** + * @brief Unmaps a region that was previously mapped with \ref svcMapMemory. + * @param[in] dst_addr Destination address. + * @param[in] src_addr Source address. + * @param[in] size Size of the range. + * @return Result code. + * @note Syscall number 0x05. + */ +Result svcUnmapMemory(void* dst_addr, void* src_addr, u64 size); + +/** + * @brief Query information about an address. Will always fetch the lowest page-aligned mapping that contains the + * provided address. + * @param[out] meminfo_ptr \ref MemoryInfo structure which will be filled in. + * @param[out] pageinfo Page information which will be filled in. + * @param[in] addr Address to query. + * @return Result code. + * @note Syscall number 0x06. + */ +Result svcQueryMemory(MemoryInfo* meminfo_ptr, u32* pageinfo, u64 addr); + +///@} + +///@name Process and thread management +///@{ + +/** + * @brief Exits the current process. + * @note Syscall number 0x07. + */ + +void NORETURN svcExitProcess(void); + +/** + * @brief Creates a thread. + * @return Result code. + * @note Syscall number 0x08. + */ +Result svcCreateThread(Handle* out, void* entry, void* arg, void* stack_top, int prio, int cpuid); + +/** + * @brief Starts a freshly created thread. + * @return Result code. + * @note Syscall number 0x09. + */ +Result svcStartThread(Handle handle); + +/** + * @brief Exits the current thread. + * @note Syscall number 0x0A. + */ +void NORETURN svcExitThread(void); + +/** + * @brief Sleeps the current thread for the specified amount of time. + * @param[in] nano Number of nanoseconds to sleep, or 0, -1, -2 for yield. + * @note Syscall number 0x0B. + */ +void svcSleepThread(s64 nano); + +/** + * @brief Gets a thread's priority. + * @return Result code. + * @note Syscall number 0x0C. + */ +Result svcGetThreadPriority(u32* priority, Handle handle); + +/** + * @brief Sets a thread's priority. + * @return Result code. + * @note Syscall number 0x0D. + */ +Result svcSetThreadPriority(Handle handle, u32 priority); + +/** + * @brief Gets a thread's core mask. + * @return Result code. + * @note Syscall number 0x0E. + */ +Result svcGetThreadCoreMask(s32* preferred_core, u32* affinity_mask, Handle handle); + +/** + * @brief Sets a thread's core mask. + * @return Result code. + * @note Syscall number 0x0F. + */ +Result svcSetThreadCoreMask(Handle handle, s32 preferred_core, u32 affinity_mask); + +/** + * @brief Gets the current processor's number. + * @return The current processor's number. + * @note Syscall number 0x10. + */ +u32 svcGetCurrentProcessorNumber(void); + +///@} + +///@name Synchronization +///@{ + +/** + * @brief Sets an event's signalled status. + * @return Result code. + * @note Syscall number 0x11. + */ +Result svcSignalEvent(Handle handle); + +/** + * @brief Clears an event's signalled status. + * @return Result code. + * @note Syscall number 0x12. + */ +Result svcClearEvent(Handle handle); + +///@} + +///@name Inter-process memory sharing +///@{ + +/** + * @brief Maps a block of shared memory. + * @return Result code. + * @note Syscall number 0x13. + */ +Result svcMapSharedMemory(Handle handle, void* addr, size_t size, u32 perm); + +/** + * @brief Unmaps a block of shared memory. + * @return Result code. + * @note Syscall number 0x14. + */ +Result svcUnmapSharedMemory(Handle handle, void* addr, size_t size); + +/** + * @brief Creates a block of transfer memory. + * @return Result code. + * @note Syscall number 0x15. + */ +Result svcCreateTransferMemory(Handle* out, void* addr, size_t size, u32 perm); + +///@} + +///@name Miscellaneous +///@{ + +/** + * @brief Closes a handle, decrementing the reference count of the corresponding kernel object. + * This might result in the kernel freeing the object. + * @param handle Handle to close. + * @return Result code. + * @note Syscall number 0x16. + */ +Result svcCloseHandle(Handle handle); + +///@} + +///@name Synchronization +///@{ + +/** + * @brief Resets a signal. + * @return Result code. + * @note Syscall number 0x17. + */ +Result svcResetSignal(Handle handle); + +///@} + +///@name Synchronization +///@{ + +/** + * @brief Waits on one or more synchronization objects, optionally with a timeout. + * @return Result code. + * @note Syscall number 0x18. + * @note \p handleCount must not be greater than \ref MAX_WAIT_OBJECTS. This is a Horizon kernel limitation. + * @note This is the raw syscall, which can be cancelled by \ref svcCancelSynchronization or other means. \ref + * waitHandles or \ref waitMultiHandle should normally be used instead. + */ +Result svcWaitSynchronization(s32* index, const Handle* handles, s32 handleCount, u64 timeout); + +/** + * @brief Waits on a single synchronization object, optionally with a timeout. + * @return Result code. + * @note Wrapper for \ref svcWaitSynchronization. + * @note This is the raw syscall, which can be cancelled by \ref svcCancelSynchronization or other means. \ref + * waitSingleHandle should normally be used instead. + */ +static inline Result svcWaitSynchronizationSingle(Handle handle, u64 timeout) { + s32 tmp; + return svcWaitSynchronization(&tmp, &handle, 1, timeout); +} + +/** + * @brief Waits a \ref svcWaitSynchronization operation being done on a synchronization object in another thread. + * @return Result code. + * @note Syscall number 0x19. + */ +Result svcCancelSynchronization(Handle thread); + +/** + * @brief Arbitrates a mutex lock operation in userspace. + * @return Result code. + * @note Syscall number 0x1A. + */ +Result svcArbitrateLock(u32 wait_tag, u32* tag_location, u32 self_tag); + +/** + * @brief Arbitrates a mutex unlock operation in userspace. + * @return Result code. + * @note Syscall number 0x1B. + */ +Result svcArbitrateUnlock(u32* tag_location); + +/** + * @brief Performs a condition variable wait operation in userspace. + * @return Result code. + * @note Syscall number 0x1C. + */ +Result svcWaitProcessWideKeyAtomic(u32* key, u32* tag_location, u32 self_tag, u64 timeout); + +/** + * @brief Performs a condition variable wake-up operation in userspace. + * @note Syscall number 0x1D. + */ +void svcSignalProcessWideKey(u32* key, s32 num); + +///@} + +///@name Miscellaneous +///@{ + +/** + * @brief Gets the current system tick. + * @return The current system tick. + * @note Syscall number 0x1E. + */ +u64 svcGetSystemTick(void); + +///@} + +///@name Inter-process communication (IPC) +///@{ + +/** + * @brief Connects to a registered named port. + * @return Result code. + * @note Syscall number 0x1F. + */ +Result svcConnectToNamedPort(Handle* session, const char* name); + +/** + * @brief Sends an IPC synchronization request to a session. + * @return Result code. + * @note Syscall number 0x21. + */ +Result svcSendSyncRequest(Handle session); + +/** + * @brief Sends an IPC synchronization request to a session from an user allocated buffer. + * @return Result code. + * @remark size must be allocated to 0x1000 bytes. + * @note Syscall number 0x22. + */ +Result svcSendSyncRequestWithUserBuffer(void* usrBuffer, u64 size, Handle session); + +/** + * @brief Sends an IPC synchronization request to a session from an user allocated buffer (asynchronous version). + * @return Result code. + * @remark size must be allocated to 0x1000 bytes. + * @note Syscall number 0x23. + */ +Result svcSendAsyncRequestWithUserBuffer(Handle* handle, void* usrBuffer, u64 size, Handle session); + +///@} + +///@name Process and thread management +///@{ + +/** + * @brief Gets the PID associated with a process. + * @return Result code. + * @note Syscall number 0x24. + */ +Result svcGetProcessId(u64* processID, Handle handle); + +/** + * @brief Gets the TID associated with a process. + * @return Result code. + * @note Syscall number 0x25. + */ +Result svcGetThreadId(u64* threadID, Handle handle); + +///@} + +///@name Miscellaneous +///@{ + +/** + * @brief Breaks execution. Panic. + * @param[in] breakReason Break reason. + * @param[in] inval1 First break parameter. + * @param[in] inval2 Second break parameter. + * @return Result code. + * @note Syscall number 0x26. + */ +Result svcBreak(u32 breakReason, u64 inval1, u64 inval2); + +///@} + +///@name Debugging +///@{ + +/** + * @brief Outputs debug text, if used during debugging. + * @param[in] str Text to output. + * @param[in] size Size of the text in bytes. + * @return Result code. + * @note Syscall number 0x27. + */ +Result svcOutputDebugString(const char* str, u64 size); + +///@} + +///@name Miscellaneous +///@{ + +/** + * @brief Returns from an exception. + * @param[in] res Result code. + * @note Syscall number 0x28. + */ +void NORETURN svcReturnFromException(Result res); + +/** + * @brief Retrieves information about the system, or a certain kernel object. + * @param[out] out Variable to which store the information. + * @param[in] id0 First ID of the property to retrieve. + * @param[in] handle Handle of the object to retrieve information from, or \ref INVALID_HANDLE to retrieve information + * about the system. + * @param[in] id1 Second ID of the property to retrieve. + * @return Result code. + * @remark The full list of property IDs can be found on the switchbrew.org wiki. + * @note Syscall number 0x29. + */ +Result svcGetInfo(u64* out, u32 id0, Handle handle, u64 id1); + +///@} + +///@name Memory management +///@{ + +/** + * @brief Maps new heap memory at the desired address. [3.0.0+] + * @return Result code. + * @note Syscall number 0x2C. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapPhysicalMemory(void* address, u64 size); + +/** + * @brief Undoes the effects of \ref svcMapPhysicalMemory. [3.0.0+] + * @return Result code. + * @note Syscall number 0x2D. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcUnmapPhysicalMemory(void* address, u64 size); + +///@} + +///@name Resource Limit Management +///@{ + +/** + * @brief Gets the maximum value a LimitableResource can have, for a Resource Limit handle. + * @return Result code. + * @note Syscall number 0x30. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetResourceLimitLimitValue(u64* out, Handle reslimit_h, LimitableResource which); + +/** + * @brief Gets the maximum value a LimitableResource can have, for a Resource Limit handle. + * @return Result code. + * @note Syscall number 0x31. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetResourceLimitCurrentValue(u64* out, Handle reslimit_h, LimitableResource which); + +///@} + +///@name Process and thread management +///@{ + +/** + * @brief Configures the pause/unpause status of a thread. + * @return Result code. + * @note Syscall number 0x32. + */ +Result svcSetThreadActivity(Handle thread, bool paused); + +/** + * @brief Dumps the registers of a thread paused by @ref svcSetThreadActivity (register groups: all). + * @param[out] ctx Output thread context (register dump). + * @param[in] thread Thread handle. + * @return Result code. + * @note Syscall number 0x33. + * @warning Official kernel will not dump x0..x18 if the thread is currently executing a system call, and prior + * to 6.0.0 doesn't dump TPIDR_EL0. + */ +Result svcGetThreadContext3(ThreadContext* ctx, Handle thread); + +///@} + +///@name Inter-process communication (IPC) +///@{ + +/** + * @brief Creates an IPC session. + * @return Result code. + * @note Syscall number 0x40. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateSession(Handle* server_handle, Handle* client_handle, u32 unk0, u64 unk1); // unk* are normally 0? + +/** + * @brief Accepts an IPC session. + * @return Result code. + * @note Syscall number 0x41. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcAcceptSession(Handle* session_handle, Handle port_handle); + +/** + * @brief Performs IPC input/output. + * @return Result code. + * @note Syscall number 0x43. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcReplyAndReceive(s32* index, const Handle* handles, s32 handleCount, Handle replyTarget, u64 timeout); + +/** + * @brief Performs IPC input/output from an user allocated buffer. + * @return Result code. + * @note Syscall number 0x44. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcReplyAndReceiveWithUserBuffer(s32* index, void* usrBuffer, u64 size, const Handle* handles, s32 handleCount, + Handle replyTarget, u64 timeout); + +///@} + +///@name Synchronization +///@{ + +/** + * @brief Creates a system event. + * @return Result code. + * @note Syscall number 0x45. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateEvent(Handle* server_handle, Handle* client_handle); + +///@} + +///@name Memory management +///@{ + +/** + * @brief Maps unsafe memory (usable for GPU DMA) for a system module at the desired address. [5.0.0+] + * @return Result code. + * @note Syscall number 0x48. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapPhysicalMemoryUnsafe(void* address, u64 size); + +/** + * @brief Undoes the effects of \ref svcMapPhysicalMemoryUnsafe. [5.0.0+] + * @return Result code. + * @note Syscall number 0x49. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcUnmapPhysicalMemoryUnsafe(void* address, u64 size); + +/** + * @brief Sets the system-wide limit for unsafe memory mappable using \ref svcMapPhysicalMemoryUnsafe. [5.0.0+] + * @return Result code. + * @note Syscall number 0x4A. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcSetUnsafeLimit(u64 size); + +///@} + +///@name Code memory / Just-in-time (JIT) compilation support +///@{ + +/** + * @brief Creates code memory in the caller's address space [4.0.0+]. + * @return Result code. + * @note Syscall number 0x4B. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateCodeMemory(Handle* code_handle, void* src_addr, u64 size); + +/** + * @brief Maps code memory in the caller's address space [4.0.0+]. + * @return Result code. + * @note Syscall number 0x4C. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcControlCodeMemory(Handle code_handle, CodeMapOperation op, void* dst_addr, u64 size, u64 perm); + +///@} + +///@name Device memory-mapped I/O (MMIO) +///@{ + +/** + * @brief Reads/writes a protected MMIO register. + * @return Result code. + * @note Syscall number 0x4E. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcReadWriteRegister(u32* outVal, u64 regAddr, u32 rwMask, u32 inVal); + +///@} + +///@name Inter-process memory sharing +///@{ + +/** + * @brief Creates a block of shared memory. + * @return Result code. + * @note Syscall number 0x50. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateSharedMemory(Handle* out, size_t size, u32 local_perm, u32 other_perm); + +/** + * @brief Maps a block of transfer memory. + * @return Result code. + * @note Syscall number 0x51. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapTransferMemory(Handle tmem_handle, void* addr, size_t size, u32 perm); + +/** + * @brief Unmaps a block of transfer memory. + * @return Result code. + * @note Syscall number 0x52. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcUnmapTransferMemory(Handle tmem_handle, void* addr, size_t size); + +///@} + +///@name Device memory-mapped I/O (MMIO) +///@{ + +/** + * @brief Creates an event and binds it to a specific hardware interrupt. + * @return Result code. + * @note Syscall number 0x53. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateInterruptEvent(Handle* handle, u64 irqNum, u32 flag); + +/** + * @brief Queries information about a certain virtual address, including its physical address. + * @return Result code. + * @note Syscall number 0x54. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcQueryPhysicalAddress(u64 out[3], u64 virtaddr); + +/** + * @brief Returns a virtual address mapped to a given IO range. + * @return Result code. + * @note Syscall number 0x55. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcQueryIoMapping(u64* virtaddr, u64 physaddr, u64 size); + +///@} + +///@name I/O memory management unit (IOMMU) +///@{ + +/** + * @brief Creates a virtual address space for binding device address spaces. + * @return Result code. + * @note Syscall number 0x56. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateDeviceAddressSpace(Handle* handle, u64 dev_addr, u64 dev_size); + +/** + * @brief Attaches a device address space to a device. + * @return Result code. + * @note Syscall number 0x57. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcAttachDeviceAddressSpace(u64 device, Handle handle); + +/** + * @brief Detaches a device address space from a device. + * @return Result code. + * @note Syscall number 0x58. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcDetachDeviceAddressSpace(u64 device, Handle handle); + +/** + * @brief Maps an attached device address space to an userspace address. + * @return Result code. + * @remark The userspace destination address must have the \ref MemState_MapDeviceAllowed bit set. + * @note Syscall number 0x59. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapDeviceAddressSpaceByForce(Handle handle, Handle proc_handle, u64 map_addr, u64 dev_size, u64 dev_addr, + u32 perm); + +/** + * @brief Maps an attached device address space to an userspace address. + * @return Result code. + * @remark The userspace destination address must have the \ref MemState_MapDeviceAlignedAllowed bit set. + * @note Syscall number 0x5A. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapDeviceAddressSpaceAligned(Handle handle, Handle proc_handle, u64 map_addr, u64 dev_size, u64 dev_addr, + u32 perm); + +/** + * @brief Unmaps an attached device address space from an userspace address. + * @return Result code. + * @note Syscall number 0x5C. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcUnmapDeviceAddressSpace(Handle handle, Handle proc_handle, u64 map_addr, u64 map_size, u64 dev_addr); + +///@} + +///@name Debugging +///@{ + +/** + * @brief Debugs an active process. + * @return Result code. + * @note Syscall number 0x60. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcDebugActiveProcess(Handle* debug, u64 processID); + +/** + * @brief Breaks an active debugging session. + * @return Result code. + * @note Syscall number 0x61. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcBreakDebugProcess(Handle debug); + +/** + * @brief Terminates the process of an active debugging session. + * @return Result code. + * @note Syscall number 0x62. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcTerminateDebugProcess(Handle debug); + +/** + * @brief Gets an incoming debug event from a debugging session. + * @return Result code. + * @note Syscall number 0x63. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetDebugEvent(u8* event_out, Handle debug); + +/** + * @brief Continues a debugging session. + * @return Result code. + * @note Syscall number 0x64. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + * @warning Only exists on [3.0.0+]. For older versions use \ref svcLegacyContinueDebugEvent. + */ +Result svcContinueDebugEvent(Handle debug, u32 flags, u64* tid_list, u32 num_tids); + +/** + * @brief Continues a debugging session. + * @return Result code. + * @note Syscall number 0x64. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + * @warning Only exists on [1.0.0-2.3.0]. For newer versions use \ref svcContinueDebugEvent. + */ +Result svcLegacyContinueDebugEvent(Handle debug, u32 flags, u64 threadID); + +/** + * @brief Gets the context (dump the registers) of a thread in a debugging session. + * @return Result code. + * @param[out] ctx Output thread context (register dump). + * @param[in] debug Debug handle. + * @param[in] threadID ID of the thread to dump the context of. + * @param[in] flags Register groups to select, combination of @ref RegisterGroup flags. + * @note Syscall number 0x67. + * @warning Official kernel will not dump any CPU GPR if the thread is currently executing a system call (except @ref + * svcBreak and @ref svcReturnFromException). + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetDebugThreadContext(ThreadContext* ctx, Handle debug, u64 threadID, u32 flags); + +/** + * @brief Gets the context (dump the registers) of a thread in a debugging session. + * @return Result code. + * @param[in] debug Debug handle. + * @param[in] threadID ID of the thread to set the context of. + * @param[in] ctx Input thread context (register dump). + * @param[in] flags Register groups to select, combination of @ref RegisterGroup flags. + * @note Syscall number 0x68. + * @warning Official kernel will return an error if the thread is currently executing a system call (except @ref + * svcBreak and @ref svcReturnFromException). + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcSetDebugThreadContext(Handle debug, u64 threadID, const ThreadContext* ctx, u32 flags); + +///@} + +///@name Process and thread management +///@{ + +/** + * @brief Retrieves a list of all running processes. + * @return Result code. + * @note Syscall number 0x65. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetProcessList(u32* num_out, u64* pids_out, u32 max_pids); + +/** + * @brief Retrieves a list of all threads for a debug handle (or zero). + * @return Result code. + * @note Syscall number 0x66. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetThreadList(u32* num_out, u64* tids_out, u32 max_tids, Handle debug); + +///@} + +///@name Debugging +///@{ + +/** + * @brief Queries memory information from a process that is being debugged. + * @return Result code. + * @note Syscall number 0x69. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcQueryDebugProcessMemory(MemoryInfo* meminfo_ptr, u32* pageinfo, Handle debug, u64 addr); + +/** + * @brief Reads memory from a process that is being debugged. + * @return Result code. + * @note Syscall number 0x6A. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcReadDebugProcessMemory(void* buffer, Handle debug, u64 addr, u64 size); + +/** + * @brief Writes to memory in a process that is being debugged. + * @return Result code. + * @note Syscall number 0x6B. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcWriteDebugProcessMemory(Handle debug, const void* buffer, u64 addr, u64 size); + +/** + * @brief Gets parameters from a thread in a debugging session. + * @return Result code. + * @note Syscall number 0x6D. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetDebugThreadParam(u64* out_64, u32* out_32, Handle debug, u64 threadID, DebugThreadParam param); + +///@} + +///@name Miscellaneous +///@{ + +/** + * @brief Retrieves privileged information about the system, or a certain kernel object. + * @param[out] out Variable to which store the information. + * @param[in] id0 First ID of the property to retrieve. + * @param[in] handle Handle of the object to retrieve information from, or \ref INVALID_HANDLE to retrieve information + * about the system. + * @param[in] id1 Second ID of the property to retrieve. + * @return Result code. + * @remark The full list of property IDs can be found on the switchbrew.org wiki. + * @note Syscall number 0x6F. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetSystemInfo(u64* out, u64 id0, Handle handle, u64 id1); + +///@} + +///@name Inter-process communication (IPC) +///@{ + +/** + * @brief Creates a port. + * @return Result code. + * @note Syscall number 0x70. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreatePort(Handle* portServer, Handle* portClient, s32 max_sessions, bool is_light, const char* name); + +/** + * @brief Manages a named port. + * @return Result code. + * @note Syscall number 0x71. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcManageNamedPort(Handle* portServer, const char* name, s32 maxSessions); + +/** + * @brief Manages a named port. + * @return Result code. + * @note Syscall number 0x72. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcConnectToPort(Handle* session, Handle port); + +///@} + +///@name Memory management +///@{ + +/** + * @brief Sets the memory permissions for the specified memory with the supplied process handle. + * @param[in] proc Process handle. + * @param[in] addr Address of the memory. + * @param[in] size Size of the memory. + * @param[in] perm Permissions (see \ref Permission). + * @return Result code. + * @remark This returns an error (0xD801) when \p perm is >0x5, hence -WX and RWX are not allowed. + * @note Syscall number 0x73. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcSetProcessMemoryPermission(Handle proc, u64 addr, u64 size, u32 perm); + +/** + * @brief Maps the src address from the supplied process handle into the current process. + * @param[in] dst Address to which map the memory in the current process. + * @param[in] proc Process handle. + * @param[in] src Source mapping address. + * @param[in] size Size of the memory. + * @return Result code. + * @remark This allows mapping code and rodata with RW- permission. + * @note Syscall number 0x74. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapProcessMemory(void* dst, Handle proc, u64 src, u64 size); + +/** + * @brief Undoes the effects of \ref svcMapProcessMemory. + * @param[in] dst Destination mapping address + * @param[in] proc Process handle. + * @param[in] src Address of the memory in the process. + * @param[in] size Size of the memory. + * @return Result code. + * @remark This allows mapping code and rodata with RW- permission. + * @note Syscall number 0x75. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcUnmapProcessMemory(void* dst, Handle proc, u64 src, u64 size); + +/** + * @brief Equivalent to \ref svcQueryMemory, for another process. + * @param[out] meminfo_ptr \ref MemoryInfo structure which will be filled in. + * @param[out] pageinfo Page information which will be filled in. + * @param[in] proc Process handle. + * @param[in] addr Address to query. + * @return Result code. + * @note Syscall number 0x76. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcQueryProcessMemory(MemoryInfo* meminfo_ptr, u32* pageinfo, Handle proc, u64 addr); + +/** + * @brief Maps normal heap in a certain process as executable code (used when loading NROs). + * @param[in] proc Process handle (cannot be \ref CUR_PROCESS_HANDLE). + * @param[in] dst Destination mapping address. + * @param[in] src Source mapping address. + * @param[in] size Size of the mapping. + * @return Result code. + * @note Syscall number 0x77. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcMapProcessCodeMemory(Handle proc, u64 dst, u64 src, u64 size); + +/** + * @brief Undoes the effects of \ref svcMapProcessCodeMemory. + * @param[in] proc Process handle (cannot be \ref CUR_PROCESS_HANDLE). + * @param[in] dst Destination mapping address. + * @param[in] src Source mapping address. + * @param[in] size Size of the mapping. + * @return Result code. + * @note Syscall number 0x78. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcUnmapProcessCodeMemory(Handle proc, u64 dst, u64 src, u64 size); + +///@} + +///@name Process and thread management +///@{ + +/** + * @brief Creates a new process. + * @return Result code. + * @note Syscall number 0x79. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateProcess(Handle* out, const void* proc_info, const u32* caps, u64 cap_num); + +/** + * @brief Starts executing a freshly created process. + * @return Result code. + * @note Syscall number 0x7A. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcStartProcess(Handle proc, s32 main_prio, s32 default_cpu, u32 stack_size); + +/** + * @brief Terminates a running process. + * @return Result code. + * @note Syscall number 0x7B. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcTerminateProcess(Handle proc); + +/** + * @brief Gets a \ref ProcessInfoType for a process. + * @return Result code. + * @note Syscall number 0x7C. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcGetProcessInfo(u64* out, Handle proc, ProcessInfoType which); + +///@} + +///@name Resource Limit Management +///@{ + +/** + * @brief Creates a new Resource Limit handle. + * @return Result code. + * @note Syscall number 0x7D. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcCreateResourceLimit(Handle* out); + +/** + * @brief Sets the value for a \ref LimitableResource for a Resource Limit handle. + * @return Result code. + * @note Syscall number 0x7E. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +Result svcSetResourceLimitLimitValue(Handle reslimit, LimitableResource which, u64 value); + +///@} + +///@name ( ͡° ͜ʖ ͡°) +///@{ + +/** + * @brief Calls a secure monitor function (TrustZone, EL3). + * @param regs Arguments to pass to the secure monitor. + * @return Return value from the secure monitor. + * @note Syscall number 0x7F. + * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available. + */ +u64 svcCallSecureMonitor(SecmonArgs* regs); + +///@} diff --git a/include/skyline/nx/kernel/thread.h b/include/skyline/nx/kernel/thread.h new file mode 100644 index 0000000..d8c81c5 --- /dev/null +++ b/include/skyline/nx/kernel/thread.h @@ -0,0 +1,125 @@ +/** + * @file thread.h + * @brief Multi-threading support + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "../arm/thread_context.h" +#include "types.h" +#include "wait.h" + +/// Thread information structure. +typedef struct Thread { + Handle handle; ///< Thread handle. + bool owns_stack_mem; ///< Whether the stack memory is automatically allocated. + void* stack_mem; ///< Pointer to stack memory. + void* stack_mirror; ///< Pointer to stack memory mirror. + size_t stack_sz; ///< Stack size. + void** tls_array; + struct Thread* next; + struct Thread** prev_next; +} Thread; + +/// Creates a \ref Waiter for a \ref Thread. +static inline Waiter waiterForThread(Thread* t) { return waiterForHandle(t->handle); } + +/** + * @brief Creates a thread. + * @param t Thread information structure which will be filled in. + * @param entry Entrypoint of the thread. + * @param arg Argument to pass to the entrypoint. + * @param stack_mem Memory to use as backing for stack/tls/reent. Must be page-aligned. NULL argument means to allocate + * new memory. + * @param stack_sz If stack_mem is NULL, size to use for stack. If stack_mem is non-NULL, size to use for stack + + * reent + tls (must be page-aligned). + * @param prio Thread priority (0x00~0x3F); 0x2C is the usual priority of the main thread, 0x3B is a special priority + * on cores 0..2 that enables preemptive multithreading (0x3F on core 3). + * @param cpuid ID of the core on which to create the thread (0~3); or -2 to use the default core for the current + * process. + * @return Result code. + */ +Result threadCreate(Thread* t, ThreadFunc entry, void* arg, void* stack_mem, size_t stack_sz, int prio, int cpuid); + +/** + * @brief Starts the execution of a thread. + * @param t Thread information structure. + * @return Result code. + */ +Result threadStart(Thread* t); + +/** + * @brief Exits the current thread immediately. + */ +void NORETURN threadExit(void); + +/** + * @brief Waits for a thread to finish executing. + * @param t Thread information structure. + * @return Result code. + */ +Result threadWaitForExit(Thread* t); + +/** + * @brief Frees up resources associated with a thread. + * @param t Thread information structure. + * @return Result code. + */ +Result threadClose(Thread* t); + +/** + * @brief Pauses the execution of a thread. + * @param t Thread information structure. + * @return Result code. + */ +Result threadPause(Thread* t); + +/** + * @brief Resumes the execution of a thread, after having been paused. + * @param t Thread information structure. + * @return Result code. + */ +Result threadResume(Thread* t); + +/** + * @brief Dumps the registers of a thread paused by @ref threadPause (register groups: all). + * @param[out] ctx Output thread context (register dump). + * @param t Thread information structure. + * @return Result code. + * @warning Official kernel will not dump x0..x18 if the thread is currently executing a system call, and prior + * to 6.0.0 doesn't dump TPIDR_EL0. + */ +Result threadDumpContext(ThreadContext* ctx, Thread* t); + +/** + * @brief Gets the raw handle to the current thread. + * @return The current thread's handle. + */ +Handle threadGetCurHandle(void); + +/** + * @brief Allocates a TLS slot. + * @param destructor Function to run automatically when a thread exits. + * @return TLS slot ID on success, or a negative value on failure. + */ +s32 threadTlsAlloc(void (*destructor)(void*)); + +/** + * @brief Retrieves the value stored in a TLS slot. + * @param slot_id TLS slot ID. + * @return Value. + */ +void* threadTlsGet(s32 slot_id); + +/** + * @brief Stores the specified value into a TLS slot. + * @param slot_id TLS slot ID. + * @param value Value. + */ +void threadTlsSet(s32 slot_id, void* value); + +/** + * @brief Frees a TLS slot. + * @param slot_id TLS slot ID. + */ +void threadTlsFree(s32 slot_id); diff --git a/include/skyline/nx/kernel/tmem.h b/include/skyline/nx/kernel/tmem.h new file mode 100644 index 0000000..d7e1943 --- /dev/null +++ b/include/skyline/nx/kernel/tmem.h @@ -0,0 +1,79 @@ +/** + * @file tmem.h + * @brief Transfer memory handling + * @author plutoo + * @copyright libnx Authors + * @remark Transfer memory differs from shared memory in the fact that the user process (as opposed to the kernel) + * allocates and owns its backing memory. + */ +#pragma once +#include "../kernel/svc.h" +#include "../types.h" + +/// Transfer memory information structure. +typedef struct { + Handle handle; ///< Kernel object handle. + size_t size; ///< Size of the transfer memory object. + Permission perm; ///< Permissions of the transfer memory object. + void* src_addr; ///< Address of the source backing memory. + void* map_addr; ///< Address to which the transfer memory object is mapped. +} TransferMemory; + +/** + * @brief Creates a transfer memory object. + * @param t Transfer memory information structure that will be filled in. + * @param size Size of the transfer memory object to create. + * @param perm Permissions with which to protect the transfer memory in the local process. + * @return Result code. + */ +Result tmemCreate(TransferMemory* t, size_t size, Permission perm); + +/** + * @brief Creates a transfer memory object from existing memory. + * @param t Transfer memory information structure that will be filled in. + * @param buf Pointer to a page-aligned buffer. + * @param size Size of the transfer memory object to create. + * @param perm Permissions with which to protect the transfer memory in the local process. + * @return Result code. + */ +Result tmemCreateFromMemory(TransferMemory* t, void* buf, size_t size, Permission perm); + +/** + * @brief Loads a transfer memory object coming from a remote process. + * @param t Transfer memory information structure which will be filled in. + * @param handle Handle of the transfer memory object. + * @param size Size of the transfer memory object that is being loaded. + * @param perm Permissions which the transfer memory is expected to have in the process that owns the memory. + * @warning This is a privileged operation; in normal circumstances applications shouldn't use this function. + */ +void tmemLoadRemote(TransferMemory* t, Handle handle, size_t size, Permission perm); + +/** + * @brief Maps a transfer memory object. + * @param t Transfer memory information structure. + * @return Result code. + * @warning This is a privileged operation; in normal circumstances applications cannot use this function. + */ +Result tmemMap(TransferMemory* t); + +/** + * @brief Unmaps a transfer memory object. + * @param t Transfer memory information structure. + * @return Result code. + * @warning This is a privileged operation; in normal circumstances applications cannot use this function. + */ +Result tmemUnmap(TransferMemory* t); + +/** + * @brief Retrieves the mapped address of a transfer memory object. + * @param t Transfer memory information structure. + * @return Mapped address of the transfer memory object. + */ +static inline void* tmemGetAddr(TransferMemory* t) { return t->map_addr; } + +/** + * @brief Frees up resources used by a transfer memory object, unmapping and closing handles, etc. + * @param t Transfer memory information structure. + * @return Result code. + */ +Result tmemClose(TransferMemory* t); diff --git a/include/skyline/nx/kernel/uevent.h b/include/skyline/nx/kernel/uevent.h new file mode 100644 index 0000000..1ba7097 --- /dev/null +++ b/include/skyline/nx/kernel/uevent.h @@ -0,0 +1,46 @@ +/** + * @file uevent.h + * @brief User-mode event synchronization primitive. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "wait.h" + +typedef struct UEvent UEvent; + +/// User-mode event object. +struct UEvent { + Waitable waitable; + bool signal; + bool auto_clear; +}; + +/// Creates a waiter for a user-mode event. +static inline Waiter waiterForUEvent(UEvent* e) { + Waiter wait_obj; + wait_obj.type = WaiterType_Waitable; + wait_obj.waitable = &e->waitable; + return wait_obj; +} + +/** + * @brief Creates a user-mode event. + * @param[out] e UEvent object. + * @param[in] auto_clear Whether to automatically clear the event. + * @note It is safe to wait on this event with several threads simultaneously. + * @note If more than one thread is listening on it, at least one thread will get the signal. No other guarantees. + */ +void ueventCreate(UEvent* e, bool auto_clear); + +/** + * @brief Clears the event signal. + * @param[in] e UEvent object. + */ +void ueventClear(UEvent* e); + +/** + * @brief Signals the event. + * @param[in] e UEvent object. + */ +void ueventSignal(UEvent* e); diff --git a/include/skyline/nx/kernel/utimer.h b/include/skyline/nx/kernel/utimer.h new file mode 100644 index 0000000..4b515e2 --- /dev/null +++ b/include/skyline/nx/kernel/utimer.h @@ -0,0 +1,57 @@ +/** + * @file utimer.h + * @brief User-mode timer synchronization primitive. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "wait.h" + +typedef struct UTimer UTimer; + +/// Valid types for a user-mode timer. +typedef enum { + TimerType_OneShot, ///< Timers of this kind fire once and then stop automatically. + TimerType_Repeating, ///< Timers of this kind fire periodically. +} TimerType; + +/// User-mode timer object. +struct UTimer { + Waitable waitable; + TimerType type : 8; + bool started : 1; + u64 next_tick; + u64 interval; +}; + +/// Creates a waiter for a user-mode timer. +static inline Waiter waiterForUTimer(UTimer* t) { + Waiter wait_obj; + wait_obj.type = WaiterType_Waitable; + wait_obj.waitable = &t->waitable; + return wait_obj; +} + +/** + * @brief Creates a user-mode timer. + * @param[out] t UTimer object. + * @param[in] interval Interval (in nanoseconds). + * @param[in] type Type of timer to create (see \ref TimerType). + * @note The timer is stopped when it is created. Use \ref utimerStart to start it. + * @note It is safe to wait on this timer with several threads simultaneously. + * @note If more than one thread is listening on it, at least one thread will get the signal. No other guarantees. + * @note For a repeating timer: If the timer triggers twice before you wait on it, you will only get one signal. + */ +void utimerCreate(UTimer* t, u64 interval, TimerType type); + +/** + * @brief Starts the timer. + * @param[in] t UTimer object. + */ +void utimerStart(UTimer* t); + +/** + * @brief Stops the timer. + * @param[in] t UTimer object. + */ +void utimerStop(UTimer* t); diff --git a/include/skyline/nx/kernel/virtmem.h b/include/skyline/nx/kernel/virtmem.h new file mode 100644 index 0000000..eb703e5 --- /dev/null +++ b/include/skyline/nx/kernel/virtmem.h @@ -0,0 +1,46 @@ +/** + * @file virtmem.h + * @brief Virtual memory mapping utilities + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Reserves a slice of general purpose address space. + * @param size The size of the slice of address space that will be reserved (rounded up to page alignment). + * @return Pointer to the slice of address space, or NULL on failure. + */ +void* virtmemReserve(size_t size); + +/** + * @brief Relinquishes a slice of address space reserved with virtmemReserve (currently no-op). + * @param addr Pointer to the slice. + * @param size Size of the slice. + */ +void virtmemFree(void* addr, size_t size); + +/** + * @brief Reserves a slice of address space inside the stack memory mapping region (for use with svcMapMemory). + * @param size The size of the slice of address space that will be reserved (rounded up to page alignment). + * @return Pointer to the slice of address space, or NULL on failure. + */ +void* virtmemReserveStack(size_t size); + +/** + * @brief Relinquishes a slice of address space reserved with virtmemReserveStack (currently no-op). + * @param addr Pointer to the slice. + * @param size Size of the slice. + */ +void virtmemFreeStack(void* addr, size_t size); + +void virtmemSetup(); + +#ifdef __cplusplus +} +#endif diff --git a/include/skyline/nx/kernel/wait.h b/include/skyline/nx/kernel/wait.h new file mode 100644 index 0000000..c06c415 --- /dev/null +++ b/include/skyline/nx/kernel/wait.h @@ -0,0 +1,119 @@ +/** + * @file wait.h + * @brief User mode synchronization primitive waiting operations. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "mutex.h" + +// Implementation details. + +typedef struct Waitable Waitable; +typedef struct WaitableMethods WaitableMethods; +typedef struct WaitableNode WaitableNode; + +struct WaitableNode { + WaitableNode* prev; + WaitableNode* next; +}; + +struct Waitable { + const WaitableMethods* vt; + WaitableNode list; + Mutex mutex; +}; + +typedef enum { + WaiterType_Handle, + WaiterType_HandleWithClear, + WaiterType_Waitable, +} WaiterType; + +// User-facing API starts here. + +/// Waiter structure, representing any generic waitable synchronization object; both kernel-mode and user-mode. +typedef struct { + WaiterType type; + + union { + Handle handle; + Waitable* waitable; + }; +} Waiter; + +/// Creates a \ref Waiter for a kernel-mode \ref Handle. +static inline Waiter waiterForHandle(Handle h) { + Waiter wait_obj; + wait_obj.type = WaiterType_Handle; + wait_obj.handle = h; + return wait_obj; +} + +/** + * @brief Waits for an arbitrary number of generic waitable synchronization objects, optionally with a timeout. + * @param[out] idx_out Variable that will received the index of the signalled object. + * @param[in] objects Array containing \ref Waiter structures. + * @param[in] num_objects Number of objects in the array. + * @param[in] timeout Timeout (in nanoseconds). + * @return Result code. + * @note The number of objects must not be greater than \ref MAX_WAIT_OBJECTS. This is a Horizon kernel limitation. + */ +Result waitObjects(s32* idx_out, const Waiter* objects, s32 num_objects, u64 timeout); + +/** + * @brief Waits for an arbitrary number of kernel synchronization objects, optionally with a timeout. This function + * replaces \ref svcWaitSynchronization. + * @param[out] idx_out Variable that will received the index of the signalled object. + * @param[in] handles Array containing handles. + * @param[in] num_handles Number of handles in the array. + * @param[in] timeout Timeout (in nanoseconds). + * @return Result code. + * @note The number of objects must not be greater than \ref MAX_WAIT_OBJECTS. This is a Horizon kernel limitation. + */ +Result waitHandles(s32* idx_out, const Handle* handles, s32 num_handles, u64 timeout); + +/** + * @brief Helper macro for \ref waitObjects that accepts \ref Waiter structures as variadic arguments instead of as an + * array. + * @param[out] idx_out The index of the signalled waiter. + * @param[in] timeout Timeout (in nanoseconds). + * @note The number of objects must not be greater than \ref MAX_WAIT_OBJECTS. This is a Horizon kernel limitation. + */ +#define waitMulti(idx_out, timeout, ...) \ + ({ \ + Waiter __objects[] = {__VA_ARGS__}; \ + waitObjects((idx_out), __objects, sizeof(__objects) / sizeof(Waiter), (timeout)); \ + }) + +/** + * @brief Helper macro for \ref waitHandles that accepts handles as variadic arguments instead of as an array. + * @param[out] idx_out The index of the signalled handle. + * @param[in] timeout Timeout (in nanoseconds). + * @note The number of objects must not be greater than \ref MAX_WAIT_OBJECTS. This is a Horizon kernel limitation. + */ +#define waitMultiHandle(idx_out, timeout, ...) \ + ({ \ + Handle __handles[] = {__VA_ARGS__}; \ + waitHandles((idx_out), __handles, sizeof(__handles) / sizeof(Handle), (timeout)); \ + }) + +/** + * @brief Waits on a single generic waitable synchronization object, optionally with a timeout. + * @param[in] w \ref Waiter structure. + * @param[in] timeout Timeout (in nanoseconds). + */ +static inline Result waitSingle(Waiter w, u64 timeout) { + s32 idx; + return waitObjects(&idx, &w, 1, timeout); +} + +/** + * @brief Waits for a single kernel synchronization object, optionally with a timeout. + * @param[in] h \ref Handle of the object. + * @param[in] timeout Timeout (in nanoseconds). + */ +static inline Result waitSingleHandle(Handle h, u64 timeout) { + s32 idx; + return waitHandles(&idx, &h, 1, timeout); +} diff --git a/include/skyline/nx/result.h b/include/skyline/nx/result.h new file mode 100644 index 0000000..8d10de5 --- /dev/null +++ b/include/skyline/nx/result.h @@ -0,0 +1,188 @@ +/** + * @file result.h + * @brief Switch result code tools. + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/// Checks whether a result code indicates success. +#define R_SUCCEEDED(res) ((res) == 0) +/// Checks whether a result code indicates failure. +#define R_FAILED(res) ((res) != 0) +/// Returns the module ID of a result code. +#define R_MODULE(res) ((res)&0x1FF) +/// Returns the description of a result code. +#define R_DESCRIPTION(res) (((res) >> 9) & 0x1FFF) +/// Masks out unused bits in a result code, retrieving the actual value for use in comparisons. +#define R_VALUE(res) ((res)&0x3FFFFF) + +/// Evaluates an expression that returns a result, and returns the result if it would fail. +#define R_TRY(res_expr) \ + ({ \ + const auto _tmp_r_try_rc = res_expr; \ + if (R_FAILED(_tmp_r_try_rc)) { \ + return _tmp_r_try_rc; \ + } \ + }) + +/// Builds a result code from its constituent components. +#define MAKERESULT(module, description) ((((module)&0x1FF)) | ((description)&0x1FFF) << 9) + +/// Builds a kernel error result code. +#define KERNELRESULT(description) MAKERESULT(Module_Kernel, KernelError_##description) + +/// Module values +enum { + Module_Kernel = 1, + Module_Libnx = 345, + Module_HomebrewAbi = 346, + Module_HomebrewLoader = 347, + Module_LibnxNvidia = 348, + Module_LibnxBinder = 349, + Module_Skyline = 0x1A4, +}; + +/// Kernel error codes +enum { + KernelError_OutOfSessions = 7, + KernelError_InvalidCapabilityDescriptor = 14, + KernelError_NotImplemented = 33, + KernelError_ThreadTerminating = 59, + KernelError_OutOfDebugEvents = 70, + KernelError_InvalidSize = 101, + KernelError_InvalidAddress = 102, + KernelError_ResourceExhausted = 103, + KernelError_OutOfMemory = 104, + KernelError_OutOfHandles = 105, + KernelError_InvalidMemoryState = 106, + KernelError_InvalidMemoryPermissions = 108, + KernelError_InvalidMemoryRange = 110, + KernelError_InvalidPriority = 112, + KernelError_InvalidCoreId = 113, + KernelError_InvalidHandle = 114, + KernelError_InvalidUserBuffer = 115, + KernelError_InvalidCombination = 116, + KernelError_TimedOut = 117, + KernelError_Cancelled = 118, + KernelError_OutOfRange = 119, + KernelError_InvalidEnumValue = 120, + KernelError_NotFound = 121, + KernelError_AlreadyExists = 122, + KernelError_ConnectionClosed = 123, + KernelError_UnhandledUserInterrupt = 124, + KernelError_InvalidState = 125, + KernelError_ReservedValue = 126, + KernelError_InvalidHwBreakpoint = 127, + KernelError_FatalUserException = 128, + KernelError_OwnedByAnotherProcess = 129, + KernelError_ConnectionRefused = 131, + KernelError_OutOfResource = 132, + KernelError_IpcMapFailed = 259, + KernelError_IpcCmdbufTooSmall = 260, + KernelError_NotDebugged = 520, +}; + +/// libnx error codes +enum { + LibnxError_BadReloc = 1, + LibnxError_OutOfMemory, + LibnxError_AlreadyMapped, + LibnxError_BadGetInfo_Stack, + LibnxError_BadGetInfo_Heap, + LibnxError_BadQueryMemory, + LibnxError_AlreadyInitialized, + LibnxError_NotInitialized, + LibnxError_NotFound, + LibnxError_IoError, + LibnxError_BadInput, + LibnxError_BadReent, + LibnxError_BufferProducerError, + LibnxError_HandleTooEarly, + LibnxError_HeapAllocFailed, + LibnxError_TooManyOverrides, + LibnxError_ParcelError, + LibnxError_BadGfxInit, + LibnxError_BadGfxEventWait, + LibnxError_BadGfxQueueBuffer, + LibnxError_BadGfxDequeueBuffer, + LibnxError_AppletCmdidNotFound, + LibnxError_BadAppletReceiveMessage, + LibnxError_BadAppletNotifyRunning, + LibnxError_BadAppletGetCurrentFocusState, + LibnxError_BadAppletGetOperationMode, + LibnxError_BadAppletGetPerformanceMode, + LibnxError_BadUsbCommsRead, + LibnxError_BadUsbCommsWrite, + LibnxError_InitFail_SM, + LibnxError_InitFail_AM, + LibnxError_InitFail_HID, + LibnxError_InitFail_FS, + LibnxError_BadGetInfo_Rng, + LibnxError_JitUnavailable, + LibnxError_WeirdKernel, + LibnxError_IncompatSysVer, + LibnxError_InitFail_Time, + LibnxError_TooManyDevOpTabs, + LibnxError_DomainMessageUnknownType, + LibnxError_DomainMessageTooManyObjectIds, + LibnxError_AppletFailedToInitialize, + LibnxError_ApmFailedToInitialize, + LibnxError_NvinfoFailedToInitialize, + LibnxError_NvbufFailedToInitialize, + LibnxError_LibAppletBadExit, + LibnxError_InvalidCmifOutHeader, + LibnxError_ShouldNotHappen, +}; + +/// libnx binder error codes +enum { + LibnxBinderError_Unknown = 1, + LibnxBinderError_NoMemory, + LibnxBinderError_InvalidOperation, + LibnxBinderError_BadValue, + LibnxBinderError_BadType, + LibnxBinderError_NameNotFound, + LibnxBinderError_PermissionDenied, + LibnxBinderError_NoInit, + LibnxBinderError_AlreadyExists, + LibnxBinderError_DeadObject, + LibnxBinderError_FailedTransaction, + LibnxBinderError_BadIndex, + LibnxBinderError_NotEnoughData, + LibnxBinderError_WouldBlock, + LibnxBinderError_TimedOut, + LibnxBinderError_UnknownTransaction, + LibnxBinderError_FdsNotAllowed, +}; + +/// libnx nvidia error codes +enum { + LibnxNvidiaError_Unknown = 1, + LibnxNvidiaError_NotImplemented, ///< Maps to Nvidia: 1 + LibnxNvidiaError_NotSupported, ///< Maps to Nvidia: 2 + LibnxNvidiaError_NotInitialized, ///< Maps to Nvidia: 3 + LibnxNvidiaError_BadParameter, ///< Maps to Nvidia: 4 + LibnxNvidiaError_Timeout, ///< Maps to Nvidia: 5 + LibnxNvidiaError_InsufficientMemory, ///< Maps to Nvidia: 6 + LibnxNvidiaError_ReadOnlyAttribute, ///< Maps to Nvidia: 7 + LibnxNvidiaError_InvalidState, ///< Maps to Nvidia: 8 + LibnxNvidiaError_InvalidAddress, ///< Maps to Nvidia: 9 + LibnxNvidiaError_InvalidSize, ///< Maps to Nvidia: 10 + LibnxNvidiaError_BadValue, ///< Maps to Nvidia: 11 + LibnxNvidiaError_AlreadyAllocated, ///< Maps to Nvidia: 13 + LibnxNvidiaError_Busy, ///< Maps to Nvidia: 14 + LibnxNvidiaError_ResourceError, ///< Maps to Nvidia: 15 + LibnxNvidiaError_CountMismatch, ///< Maps to Nvidia: 16 + LibnxNvidiaError_SharedMemoryTooSmall, ///< Maps to Nvidia: 0x1000 + LibnxNvidiaError_FileOperationFailed, ///< Maps to Nvidia: 0x30003 + LibnxNvidiaError_IoctlFailed, ///< Maps to Nvidia: 0x3000F +}; + +/// skyline error codes +enum { + SkylineError_SlServiceInitFailed = 1, + SkylineError_InvalidPluginName, + SkylineError_InlineHookHandlerSizeInvalid, + SkylineError_InlineHookPoolExhausted, +}; diff --git a/include/skyline/nx/runtime/devices/console.h b/include/skyline/nx/runtime/devices/console.h new file mode 100644 index 0000000..bd306ba --- /dev/null +++ b/include/skyline/nx/runtime/devices/console.h @@ -0,0 +1,189 @@ +/** + * @file console.h + * @brief Framebuffer text console. + * @author yellows8 + * @author WinterMute + * @copyright libnx Authors + * + * Provides stdio integration for printing to the Switch screen as well as debug print + * functionality provided by stderr. + * + * General usage is to initialize the console by: + * @code + * consoleInit(NULL) + * @endcode + * optionally customizing the console usage by passing a pointer to a custom PrintConsole struct. + */ +#pragma once +#include "../../types.h" + +#define CONSOLE_ESC(x) "\x1b[" #x +#define CONSOLE_RESET CONSOLE_ESC(0m) +#define CONSOLE_BLACK CONSOLE_ESC(30m) +#define CONSOLE_RED CONSOLE_ESC(31; 1m) +#define CONSOLE_GREEN CONSOLE_ESC(32; 1m) +#define CONSOLE_YELLOW CONSOLE_ESC(33; 1m) +#define CONSOLE_BLUE CONSOLE_ESC(34; 1m) +#define CONSOLE_MAGENTA CONSOLE_ESC(35; 1m) +#define CONSOLE_CYAN CONSOLE_ESC(36; 1m) +#define CONSOLE_WHITE CONSOLE_ESC(37; 1m) + +// Forward declaration +typedef struct PrintConsole PrintConsole; + +/// Renderer interface for the console. +typedef struct ConsoleRenderer { + bool (*init)(PrintConsole* con); + void (*deinit)(PrintConsole* con); + void (*drawChar)(PrintConsole* con, int x, int y, int c); + void (*scrollWindow)(PrintConsole* con); + void (*flushAndSwap)(PrintConsole* con); +} ConsoleRenderer; + +/// A font struct for the console. +typedef struct ConsoleFont { + const void* gfx; ///< A pointer to the font graphics + u16 asciiOffset; ///< Offset to the first valid character in the font table + u16 numChars; ///< Number of characters in the font graphics + u16 tileWidth; + u16 tileHeight; +} ConsoleFont; + +/** + * @brief Console structure used to store the state of a console render context. + * + * Default values from consoleGetDefault(); + * @code + * PrintConsole defaultConsole = + * { + * //Font: + * { + * default_font_bin, //font gfx + * 0, //first ascii character in the set + * 256, //number of characters in the font set + * 16, //tile width + * 16, //tile height + * }, + * NULL, //renderer + * 0,0, //cursorX cursorY + * 0,0, //prevcursorX prevcursorY + * 80, //console width + * 45, //console height + * 0, //window x + * 0, //window y + * 80, //window width + * 45, //window height + * 3, //tab size + * 7, // foreground color + * 0, // background color + * 0, // flags + * false //console initialized + * }; + * @endcode + */ +struct PrintConsole { + ConsoleFont font; ///< Font of the console + ConsoleRenderer* renderer; ///< Renderer of the console + + int cursorX; ///< Current X location of the cursor (as a tile offset by default) + int cursorY; ///< Current Y location of the cursor (as a tile offset by default) + + int prevCursorX; ///< Internal state + int prevCursorY; ///< Internal state + + int consoleWidth; ///< Width of the console hardware layer in characters + int consoleHeight; ///< Height of the console hardware layer in characters + + int windowX; ///< Window X location in characters + int windowY; ///< Window Y location in characters + int windowWidth; ///< Window width in characters + int windowHeight; ///< Window height in characters + + int tabSize; ///< Size of a tab + int fg; ///< Foreground color + int bg; ///< Background color + int flags; ///< Reverse/bright flags + + bool consoleInitialised; ///< True if the console is initialized +}; + +#define CONSOLE_COLOR_BOLD (1 << 0) ///< Bold text +#define CONSOLE_COLOR_FAINT (1 << 1) ///< Faint text +#define CONSOLE_ITALIC (1 << 2) ///< Italic text +#define CONSOLE_UNDERLINE (1 << 3) ///< Underlined text +#define CONSOLE_BLINK_SLOW (1 << 4) ///< Slow blinking text +#define CONSOLE_BLINK_FAST (1 << 5) ///< Fast blinking text +#define CONSOLE_COLOR_REVERSE (1 << 6) ///< Reversed color text +#define CONSOLE_CONCEAL (1 << 7) ///< Concealed text +#define CONSOLE_CROSSED_OUT (1 << 8) ///< Crossed out text + +/// Console debug devices supported by libnx. +typedef enum { + debugDevice_NULL, ///< Swallows prints to stderr + debugDevice_SVC, ///< Outputs stderr debug statements using svcOutputDebugString, which can then be captured by + ///< interactive debuggers + debugDevice_CONSOLE, ///< Directs stderr debug statements to Switch console window + debugDevice_3DMOO = debugDevice_SVC, +} debugDevice; + +/** + * @brief Loads the font into the console. + * @param console Pointer to the console to update, if NULL it will update the current console. + * @param font The font to load. + */ +void consoleSetFont(PrintConsole* console, ConsoleFont* font); + +/** + * @brief Sets the print window. + * @param console Console to set, if NULL it will set the current console window. + * @param x X location of the window. + * @param y Y location of the window. + * @param width Width of the window. + * @param height Height of the window. + */ +void consoleSetWindow(PrintConsole* console, int x, int y, int width, int height); + +/** + * @brief Gets a pointer to the console with the default values. + * This should only be used when using a single console or without changing the console that is returned, otherwise use + * consoleInit(). + * @return A pointer to the console with the default values. + */ +PrintConsole* consoleGetDefault(void); + +/** + * @brief Make the specified console the render target. + * @param console A pointer to the console struct (must have been initialized with consoleInit(PrintConsole* console)). + * @return A pointer to the previous console. + */ +PrintConsole* consoleSelect(PrintConsole* console); + +/** + * @brief Initialise the console. + * @param console A pointer to the console data to initialize (if it's NULL, the default console will be used). + * @return A pointer to the current console. + */ +PrintConsole* consoleInit(PrintConsole* console); + +/** + * @brief Deinitialise the console. + * @param console A pointer to the console data to initialize (if it's NULL, the default console will be used). + */ +void consoleExit(PrintConsole* console); + +/** + * @brief Updates the console, submitting a new frame to the display. + * @param console A pointer to the console data to initialize (if it's NULL, the default console will be used). + * @remark This function should be called periodically. Failure to call this function will result in lack of screen + * updating. + */ +void consoleUpdate(PrintConsole* console); + +/** + * @brief Initializes debug console output on stderr to the specified device. + * @param device The debug device (or devices) to output debug print statements to. + */ +void consoleDebugInit(debugDevice device); + +/// Clears the screan by using printf("\x1b[2J"); +void consoleClear(void); diff --git a/include/skyline/nx/runtime/devices/fs_dev.h b/include/skyline/nx/runtime/devices/fs_dev.h new file mode 100644 index 0000000..f1f9ae3 --- /dev/null +++ b/include/skyline/nx/runtime/devices/fs_dev.h @@ -0,0 +1,72 @@ +/** + * @file fs_dev.h + * @brief FS driver, using devoptab. + * @author yellows8 + * @author mtheall + * @copyright libnx Authors + */ +#pragma once + +#include + +#include "../../services/fs.h" + +#define FSDEV_DIRITER_MAGIC 0x66736476 ///< "fsdv" + +/// Open directory struct +typedef struct { + u32 magic; ///< "fsdv" + FsDir fd; ///< File descriptor + ssize_t index; ///< Current entry index + size_t size; ///< Current batch size +} fsdev_dir_t; + +/// Retrieves a pointer to temporary stage for reading entries +NX_CONSTEXPR FsDirectoryEntry* fsdevDirGetEntries(fsdev_dir_t* dir) { return (FsDirectoryEntry*)(void*)(dir + 1); } + +/// Initializes and mounts the sdmc device if accessible. +Result fsdevMountSdmc(void); + +/// Mounts the specified save data. +Result fsdevMountSaveData(const char* name, u64 application_id, AccountUid uid); + +/// Mounts the specified system save data. +Result fsdevMountSystemSaveData(const char* name, FsSaveDataSpaceId save_data_space_id, u64 system_save_data_id, + AccountUid uid); + +/// Mounts the input fs with the specified device name. fsdev will handle closing the fs when required, including when +/// fsdevMountDevice() fails. Returns -1 when any errors occur. +int fsdevMountDevice(const char* name, FsFileSystem fs); + +/// Unmounts the specified device. +int fsdevUnmountDevice(const char* name); + +/// Uses fsFsCommit() with the specified device. This must be used after any savedata-write operations(not just +/// file-write). This should be used after each file-close where file-writing was done. This is not used automatically +/// at device unmount. +Result fsdevCommitDevice(const char* name); + +/// Returns the FsFileSystem for the specified device. Returns NULL when the specified device isn't found. +FsFileSystem* fsdevGetDeviceFileSystem(const char* name); + +/// Writes the FS-path to outpath (which has buffer size FS_MAX_PATH), for the input path (as used in stdio). The +/// FsFileSystem is also written to device when not NULL. +int fsdevTranslatePath(const char* path, FsFileSystem** device, char* outpath); + +/// This calls fsFsSetConcatenationFileAttribute on the filesystem specified by the input path (as used in stdio). +Result fsdevSetConcatenationFileAttribute(const char* path); + +// Uses \ref fsFsIsValidSignedSystemPartitionOnSdCard with the specified device. +Result fsdevIsValidSignedSystemPartitionOnSdCard(const char* name, bool* out); + +/// This calls fsFsCreateFile on the filesystem specified by the input path (as used in stdio). +Result fsdevCreateFile(const char* path, size_t size, u32 flags); + +/// Recursively deletes the directory specified by the input path (as used in stdio). +Result fsdevDeleteDirectoryRecursively(const char* path); + +/// Unmounts all devices and cleans up any resources used by the FS driver. +Result fsdevUnmountAll(void); + +/// Retrieves the last native result code generated during a failed fsdev operation. +Result fsdevGetLastResult(void); diff --git a/include/skyline/nx/runtime/devices/romfs_dev.h b/include/skyline/nx/runtime/devices/romfs_dev.h new file mode 100644 index 0000000..e0b40de --- /dev/null +++ b/include/skyline/nx/runtime/devices/romfs_dev.h @@ -0,0 +1,105 @@ +/** + * @file romfs_dev.h + * @brief RomFS driver. + * @author yellows8 + * @author mtheall + * @author fincs + * @copyright libnx Authors + */ +#pragma once + +#include "../../services/fs.h" +#include "../../services/ncm_types.h" +#include "../../types.h" + +/// RomFS header. +typedef struct { + u64 headerSize; ///< Size of the header. + u64 dirHashTableOff; ///< Offset of the directory hash table. + u64 dirHashTableSize; ///< Size of the directory hash table. + u64 dirTableOff; ///< Offset of the directory table. + u64 dirTableSize; ///< Size of the directory table. + u64 fileHashTableOff; ///< Offset of the file hash table. + u64 fileHashTableSize; ///< Size of the file hash table. + u64 fileTableOff; ///< Offset of the file table. + u64 fileTableSize; ///< Size of the file table. + u64 fileDataOff; ///< Offset of the file data. +} romfs_header; + +/// RomFS directory. +typedef struct { + u32 parent; ///< Offset of the parent directory. + u32 sibling; ///< Offset of the next sibling directory. + u32 childDir; ///< Offset of the first child directory. + u32 childFile; ///< Offset of the first file. + u32 nextHash; ///< Directory hash table pointer. + u32 nameLen; ///< Name length. + uint8_t name[]; ///< Name. (UTF-8) +} romfs_dir; + +/// RomFS file. +typedef struct { + u32 parent; ///< Offset of the parent directory. + u32 sibling; ///< Offset of the next sibling file. + u64 dataOff; ///< Offset of the file's data. + u64 dataSize; ///< Length of the file's data. + u32 nextHash; ///< File hash table pointer. + u32 nameLen; ///< Name length. + uint8_t name[]; ///< Name. (UTF-8) +} romfs_file; + +/** + * @brief Mounts the Application's RomFS. + * @param name Device mount name. + * @remark This function is intended to be used to access one's own RomFS. + * If the application is running as NRO, it mounts the embedded RomFS section inside the NRO. + * If on the other hand it's an NSO, it behaves identically to \ref romfsMountFromCurrentProcess. + */ +Result romfsMountSelf(const char* name); + +/** + * @brief Mounts RomFS from an open file. + * @param file FsFile of the RomFS image. + * @param offset Offset of the RomFS within the file. + * @param name Device mount name. + */ +Result romfsMountFromFile(FsFile file, u64 offset, const char* name); + +/** + * @brief Mounts RomFS from an open storage. + * @param storage FsStorage of the RomFS image. + * @param offset Offset of the RomFS within the storage. + * @param name Device mount name. + */ +Result romfsMountFromStorage(FsStorage storage, u64 offset, const char* name); + +/** + * @brief Mounts RomFS using the current process host program RomFS. + * @param name Device mount name. + */ +Result romfsMountFromCurrentProcess(const char* name); + +/** + * @brief Mounts RomFS from a file path in a mounted fsdev device. + * @param path File path. + * @param offset Offset of the RomFS within the file. + * @param name Device mount name. + */ +Result romfsMountFromFsdev(const char* path, u64 offset, const char* name); + +/** + * @brief Mounts RomFS from SystemData. + * @param dataId SystemDataId to mount. + * @param storageId Storage ID to mount from. + * @param name Device mount name. + */ +Result romfsMountFromDataArchive(u64 dataId, NcmStorageId storageId, const char* name); + +/// Unmounts the RomFS device. +Result romfsUnmount(const char* name); + +/// Wrapper for \ref romfsMountSelf with the default "romfs" device name. +static inline Result romfsInit(void) { return romfsMountSelf("romfs"); } + +/// Wrapper for \ref romfsUnmount with the default "romfs" device name. +static inline Result romfsExit(void) { return romfsUnmount("romfs"); } diff --git a/include/skyline/nx/runtime/devices/socket.h b/include/skyline/nx/runtime/devices/socket.h new file mode 100644 index 0000000..82f7e77 --- /dev/null +++ b/include/skyline/nx/runtime/devices/socket.h @@ -0,0 +1,43 @@ +#pragma once +#include "../../types.h" + +/// BSD service type used by the socket driver. +typedef enum { + BsdServiceType_User = BIT(0), ///< Uses bsd:u (default). + BsdServiceType_System = BIT(1), ///< Uses bsd:s. + BsdServiceType_Auto = + BsdServiceType_User | BsdServiceType_System, ///< Tries to use bsd:s first, and if that fails uses bsd:u + ///< (official software behavior). +} BsdServiceType; + +/// Configuration structure for socketInitalize +typedef struct { + u32 bsdsockets_version; ///< Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. + + u32 tcp_tx_buf_size; ///< Size of the TCP transfer (send) buffer (initial or fixed). + u32 tcp_rx_buf_size; ///< Size of the TCP recieve buffer (initial or fixed). + u32 tcp_tx_buf_max_size; ///< Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer + ///< is fixed to its initial value. + u32 tcp_rx_buf_max_size; ///< Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed + ///< to its initial value. + + u32 udp_tx_buf_size; ///< Size of the UDP transfer (send) buffer (typically 0x2400 bytes). + u32 udp_rx_buf_size; ///< Size of the UDP receive buffer (typically 0xA500 bytes). + + u32 sb_efficiency; ///< Number of buffers for each socket (standard values range from 1 to 8). + + u32 num_bsd_sessions; ///< Number of BSD service sessions (typically 3). + BsdServiceType bsd_service_type; ///< BSD service type (typically \ref BsdServiceType_User). +} SocketInitConfig; + +/// Fetch the default configuration for the socket driver. +const SocketInitConfig* socketGetDefaultInitConfig(void); +/// Initalize the socket driver. +Result socketInitialize(const SocketInitConfig* config); +/// Fetch the last bsd:u/s Switch result code (thread-local). +Result socketGetLastResult(void); +/// Deinitialize the socket driver. +void socketExit(void); + +/// Initalize the socket driver using the default configuration. +NX_INLINE Result socketInitializeDefault(void) { return socketInitialize(NULL); } diff --git a/include/skyline/nx/runtime/devices/usb_comms.h b/include/skyline/nx/runtime/devices/usb_comms.h new file mode 100644 index 0000000..9d611d5 --- /dev/null +++ b/include/skyline/nx/runtime/devices/usb_comms.h @@ -0,0 +1,40 @@ +/** + * @file usb_comms.h + * @brief USB comms. + * @author yellows8 + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "../../types.h" + +typedef struct { + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; +} UsbCommsInterfaceInfo; + +/// Initializes usbComms with the default number of interfaces (1) +Result usbCommsInitialize(void); + +/// Initializes usbComms with a specific number of interfaces. +Result usbCommsInitializeEx(u32 num_interfaces, const UsbCommsInterfaceInfo* infos); + +/// Exits usbComms. +void usbCommsExit(void); + +/// Sets whether to throw a fatal error in usbComms{Read/Write}* on failure, or just return the transferred size. By +/// default (false) the latter is used. +void usbCommsSetErrorHandling(bool flag); + +/// Read data with the default interface. +size_t usbCommsRead(void* buffer, size_t size); + +/// Write data with the default interface. +size_t usbCommsWrite(const void* buffer, size_t size); + +/// Same as usbCommsRead except with the specified interface. +size_t usbCommsReadEx(void* buffer, size_t size, u32 interface); + +/// Same as usbCommsWrite except with the specified interface. +size_t usbCommsWriteEx(const void* buffer, size_t size, u32 interface); diff --git a/include/skyline/nx/runtime/env.h b/include/skyline/nx/runtime/env.h new file mode 100644 index 0000000..3a45d8a --- /dev/null +++ b/include/skyline/nx/runtime/env.h @@ -0,0 +1,113 @@ +/** + * @file env.h + * @brief Homebrew environment definitions and utilities. + * @author plutoo + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/// Structure representing an entry in the homebrew environment configuration. +typedef struct { + u32 Key; ///< Type of entry + u32 Flags; ///< Entry flags + u64 Value[2]; ///< Entry arguments (type-specific) +} ConfigEntry; + +/// Entry flags +enum { + EntryFlag_IsMandatory = BIT(0), ///< Specifies that the entry **must** be processed by the homebrew application. +}; + +///< Types of entry +enum { + EntryType_EndOfList = 0, ///< Entry list terminator. + EntryType_MainThreadHandle = 1, ///< Provides the handle to the main thread. + EntryType_NextLoadPath = + 2, ///< Provides a buffer containing information about the next homebrew application to load. + EntryType_OverrideHeap = 3, ///< Provides heap override information. + EntryType_OverrideService = 4, ///< Provides service override information. + EntryType_Argv = 5, ///< Provides argv. + EntryType_SyscallAvailableHint = 6, ///< Provides syscall availability hints. + EntryType_AppletType = 7, ///< Provides APT applet type. + EntryType_AppletWorkaround = 8, ///< Indicates that APT is broken and should not be used. + EntryType_Reserved9 = 9, ///< Unused/reserved entry type, formerly used by StdioSockets. + EntryType_ProcessHandle = 10, ///< Provides the process handle. + EntryType_LastLoadResult = 11, ///< Provides the last load result. + EntryType_RandomSeed = 14, ///< Provides random data used to seed the pseudo-random number generator. + EntryType_UserIdStorage = 15, ///< Provides persistent storage for the preselected user id. + EntryType_HosVersion = 16, ///< Provides the currently running Horizon OS version. +}; + +enum { + EnvAppletFlags_ApplicationOverride = + BIT(0) ///< Use AppletType_Application instead of AppletType_SystemApplication. +}; + +/// Loader return function. +typedef void NORETURN (*LoaderReturnFn)(int result_code); + +/** + * @brief Parses the homebrew loader environment block (internally called). + * @param ctx Reserved. + * @param main_thread Reserved. + * @param saved_lr Reserved. + */ +void envSetup(void* ctx, Handle main_thread, LoaderReturnFn saved_lr); + +/// Returns information text about the loader, if present. +const char* envGetLoaderInfo(void); +/// Returns the size of the loader information text. +u64 envGetLoaderInfoSize(void); + +/// Retrieves the handle to the main thread. +Handle envGetMainThreadHandle(void); +/// Returns true if the application is running as NSO, otherwise NRO. +bool envIsNso(void); + +/// Returns true if the environment has a heap override. +bool envHasHeapOverride(void); +/// Returns the address of the overriden heap. +void* envGetHeapOverrideAddr(void); +/// Returns the size of the overriden heap. +u64 envGetHeapOverrideSize(void); + +/// Returns true if the environment has an argv array. +bool envHasArgv(void); +/// Returns the pointer to the argv array. +void* envGetArgv(void); + +/** + * @brief Returns whether a syscall is hinted to be available. + * @param svc Syscall number to test. + * @returns true if the syscall is available. + */ +bool envIsSyscallHinted(u8 svc); + +/// Returns the handle to the running homebrew process. +Handle envGetOwnProcessHandle(void); + +// Set the current handle to the running process. +void envSetOwnProcessHandle(Handle); + +/** + * @brief Configures the next homebrew application to load. + * @param path Path to the next homebrew application to load (.nro). + * @param argv Argument string to pass. + */ +Result envSetNextLoad(const char* path, const char* argv); + +/// Returns true if the environment supports envSetNextLoad. +bool envHasNextLoad(void); + +/// Returns the Result from the last NRO. +Result envGetLastLoadResult(void); + +/// Returns true if the environment provides a random seed. +bool envHasRandomSeed(void); + +/** + * @brief Retrieves the random seed provided by the environment. + * @param out Pointer to a u64[2] buffer which will contain the random seed on return. + */ +void envGetRandomSeed(u64 out[2]); diff --git a/include/skyline/nx/runtime/hosversion.h b/include/skyline/nx/runtime/hosversion.h new file mode 100644 index 0000000..994a36f --- /dev/null +++ b/include/skyline/nx/runtime/hosversion.h @@ -0,0 +1,42 @@ +/** + * @file hosversion.h + * @brief Horizon OS (HOS) version detection utilities. + * @author fincs + * @copyright libnx Authors + */ +#pragma once +#include "types.h" + +/// Builds a HOS version value from its constituent components. +#define MAKEHOSVERSION(_major, _minor, _micro) (((u32)(_major) << 16) | ((u32)(_minor) << 8) | (u32)(_micro)) + +/// Extracts the major number from a HOS version value. +#define HOSVER_MAJOR(_version) (((_version) >> 16) & 0xFF) + +/// Extracts the minor number from a HOS version value. +#define HOSVER_MINOR(_version) (((_version) >> 8) & 0xFF) + +/// Extracts the micro number from a HOS version value. +#define HOSVER_MICRO(_version) ((_version)&0xFF) + +/// Returns the current HOS version that was previously set with \ref hosversionSet. If version initialization fails +/// during startup (such as in the case set:sys is not available), this function returns zero. +u32 hosversionGet(void); + +/// Sets or overrides the current HOS version. This function is normally called automatically by libnx on startup with +/// the version info obtained with \ref setsysGetFirmwareVersion. +void hosversionSet(u32 version); + +/// Returns true if the current HOS version is equal to or above the specified major/minor/micro version. +static inline bool hosversionAtLeast(u8 major, u8 minor, u8 micro) { + return hosversionGet() >= MAKEHOSVERSION(major, minor, micro); +} + +/// Returns true if the current HOS version is earlier than the specified major/minor/micro version. +static inline bool hosversionBefore(u8 major, u8 minor, u8 micro) { return !hosversionAtLeast(major, minor, micro); } + +/// Returns true if the current HOS version is between the two specified major versions, i.e. [major1, major2). +static inline bool hosversionBetween(u8 major1, u8 major2) { + u32 ver = hosversionGet(); + return ver >= MAKEHOSVERSION(major1, 0, 0) && ver < MAKEHOSVERSION(major2, 0, 0); +} diff --git a/include/skyline/nx/runtime/nxlink.h b/include/skyline/nx/runtime/nxlink.h new file mode 100644 index 0000000..72e6e39 --- /dev/null +++ b/include/skyline/nx/runtime/nxlink.h @@ -0,0 +1,22 @@ +/** + * @file nxlink.h + * @brief Netloader (nxlink) utilities + * @author WinterMute + * @copyright libnx Authors + */ +#pragma once + +struct in_addr; + +/// Address of the host connected through nxlink +extern struct in_addr __nxlink_host; + +#define NXLINK_SERVER_PORT 28280 ///< nxlink TCP server port +#define NXLINK_CLIENT_PORT 28771 ///< nxlink TCP client port + +/** + * @brief Sets up stdout/stderr redirection to the nxlink host. + * @return Socket fd on success, negative number on failure. + * @note The socket should be closed with close() during application cleanup. + */ +int nxlinkStdio(void); diff --git a/include/skyline/nx/runtime/resolver.h b/include/skyline/nx/runtime/resolver.h new file mode 100644 index 0000000..f192607 --- /dev/null +++ b/include/skyline/nx/runtime/resolver.h @@ -0,0 +1,30 @@ +#pragma once +#include "../types.h" + +/// Fetches the last resolver Switch result code of the current thread. +Result resolverGetLastResult(void); + +/// Retrieves a handle used to cancel the next resolver command on the current thread. +u32 resolverGetCancelHandle(void); + +/// Retrieves whether service discovery is enabled for resolver commands on the current thread. +bool resolverGetEnableServiceDiscovery(void); + +/// [5.0.0+] Retrieves whether the DNS cache is used to resolve queries on the current thread (not implemented). +bool resolverGetEnableDnsCache(void); + +/// Enables or disables service discovery for the current thread. +void resolverSetEnableServiceDiscovery(bool enable); + +/// [5.0.0+] Enables or disables the usage of the DNS cache on the current thread (not implemented). +void resolverSetEnableDnsCache(bool enable); + +/// Cancels a previous resolver command (handle obtained with \ref resolverGetCancelHandle prior to calling the +/// command). +Result resolverCancel(u32 handle); + +/// [5.0.0+] Removes a hostname from the DNS cache (not implemented). +Result resolverRemoveHostnameFromCache(const char* hostname); + +/// [5.0.0+] Removes an IP address from the DNS cache (not implemented). +Result resolverRemoveIpAddressFromCache(u32 ip); diff --git a/include/skyline/nx/runtime/util/utf.h b/include/skyline/nx/runtime/util/utf.h new file mode 100644 index 0000000..2a34e61 --- /dev/null +++ b/include/skyline/nx/runtime/util/utf.h @@ -0,0 +1,157 @@ +/** + * @file utf.h + * @brief UTF conversion functions. + * @author mtheall + * @copyright libnx Authors + */ +#pragma once +#include + +#include "../../types.h" + +/** Convert a UTF-8 sequence into a UTF-32 codepoint + * + * @param[out] out Output codepoint + * @param[in] in Input sequence + * + * @returns number of input code units consumed + * @returns -1 for error + */ +ssize_t decode_utf8(uint32_t* out, const uint8_t* in); + +/** Convert a UTF-16 sequence into a UTF-32 codepoint + * + * @param[out] out Output codepoint + * @param[in] in Input sequence + * + * @returns number of input code units consumed + * @returns -1 for error + */ +ssize_t decode_utf16(uint32_t* out, const uint16_t* in); + +/** Convert a UTF-32 codepoint into a UTF-8 sequence + * + * @param[out] out Output sequence + * @param[in] in Input codepoint + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out must be able to store 4 code units + */ +ssize_t encode_utf8(uint8_t* out, uint32_t in); + +/** Convert a UTF-32 codepoint into a UTF-16 sequence + * + * @param[out] out Output sequence + * @param[in] in Input codepoint + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out must be able to store 2 code units + */ +ssize_t encode_utf16(uint16_t* out, uint32_t in); + +/** Convert a UTF-8 sequence into a UTF-16 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf8_to_utf16(uint16_t* out, const uint8_t* in, size_t len); + +/** Convert a UTF-8 sequence into a UTF-32 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf8_to_utf32(uint32_t* out, const uint8_t* in, size_t len); + +/** Convert a UTF-16 sequence into a UTF-8 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf16_to_utf8(uint8_t* out, const uint16_t* in, size_t len); + +/** Convert a UTF-16 sequence into a UTF-32 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf16_to_utf32(uint32_t* out, const uint16_t* in, size_t len); + +/** Convert a UTF-32 sequence into a UTF-8 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf32_to_utf8(uint8_t* out, const uint32_t* in, size_t len); + +/** Convert a UTF-32 sequence into a UTF-16 sequence + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf32_to_utf16(uint16_t* out, const uint32_t* in, size_t len); diff --git a/include/skyline/nx/sf/cmif.h b/include/skyline/nx/sf/cmif.h new file mode 100644 index 0000000..93f5cd1 --- /dev/null +++ b/include/skyline/nx/sf/cmif.h @@ -0,0 +1,317 @@ +/** + * @file cmif.h + * @brief Common Message Interface Framework protocol + * @author fincs + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include "hipc.h" + +#define CMIF_IN_HEADER_MAGIC 0x49434653 // "SFCI" +#define CMIF_OUT_HEADER_MAGIC 0x4F434653 // "SFCO" + +typedef enum CmifCommandType { + CmifCommandType_Invalid = 0, + CmifCommandType_LegacyRequest = 1, + CmifCommandType_Close = 2, + CmifCommandType_LegacyControl = 3, + CmifCommandType_Request = 4, + CmifCommandType_Control = 5, + CmifCommandType_RequestWithContext = 6, + CmifCommandType_ControlWithContext = 7, +} CmifCommandType; + +typedef enum CmifDomainRequestType { + CmifDomainRequestType_Invalid = 0, + CmifDomainRequestType_SendMessage = 1, + CmifDomainRequestType_Close = 2, +} CmifDomainRequestType; + +typedef struct CmifInHeader { + u32 magic; + u32 version; + u32 command_id; + u32 token; +} CmifInHeader; + +typedef struct CmifOutHeader { + u32 magic; + u32 version; + Result result; + u32 token; +} CmifOutHeader; + +typedef struct CmifDomainInHeader { + u8 type; + u8 num_in_objects; + u16 data_size; + u32 object_id; + u32 padding; + u32 token; +} CmifDomainInHeader; + +typedef struct CmifDomainOutHeader { + u32 num_out_objects; + u32 padding[3]; +} CmifDomainOutHeader; + +typedef struct CmifRequestFormat { + u32 object_id; + u32 request_id; + u32 context; + u32 data_size; + u32 server_pointer_size; + u32 num_in_auto_buffers; + u32 num_out_auto_buffers; + u32 num_in_buffers; + u32 num_out_buffers; + u32 num_inout_buffers; + u32 num_in_pointers; + u32 num_out_pointers; + u32 num_out_fixed_pointers; + u32 num_objects; + u32 num_handles; + u32 send_pid; +} CmifRequestFormat; + +typedef struct CmifRequest { + HipcRequest hipc; + void* data; + u16* out_pointer_sizes; + u32* objects; + u32 server_pointer_size; + u32 cur_in_ptr_id; +} CmifRequest; + +typedef struct CmifResponse { + void* data; + u32* objects; + Handle* copy_handles; + Handle* move_handles; +} CmifResponse; + +NX_CONSTEXPR void* cmifGetAlignedDataStart(u32* data_words, void* base) { + intptr_t data_start = ((u8*)data_words - (u8*)base + 15) & ~15; + return (u8*)base + data_start; +} + +NX_CONSTEXPR CmifRequest cmifMakeRequest(void* base, CmifRequestFormat fmt) { + // First of all, we need to figure out what size we need. + u32 actual_size = 16; + if (fmt.object_id) actual_size += sizeof(CmifDomainInHeader) + fmt.num_objects * sizeof(u32); + actual_size += sizeof(CmifInHeader) + fmt.data_size; + actual_size = (actual_size + 1) & ~1; // hword-align + u32 out_pointer_size_table_offset = actual_size; + u32 out_pointer_size_table_size = fmt.num_out_auto_buffers + fmt.num_out_pointers; + actual_size += sizeof(u16) * out_pointer_size_table_size; + u32 num_data_words = (actual_size + 3) / 4; + + CmifRequest req = {}; + req.hipc = hipcMakeRequestInline( + base, .type = fmt.context ? CmifCommandType_RequestWithContext : CmifCommandType_Request, + .num_send_statics = fmt.num_in_auto_buffers + fmt.num_in_pointers, + .num_send_buffers = fmt.num_in_auto_buffers + fmt.num_in_buffers, + .num_recv_buffers = fmt.num_out_auto_buffers + fmt.num_out_buffers, .num_exch_buffers = fmt.num_inout_buffers, + .num_data_words = num_data_words, .num_recv_statics = out_pointer_size_table_size + fmt.num_out_fixed_pointers, + .send_pid = fmt.send_pid, .num_copy_handles = fmt.num_handles, .num_move_handles = 0, ); + + CmifInHeader* hdr = NULL; + void* start = cmifGetAlignedDataStart(req.hipc.data_words, base); + if (fmt.object_id) { + CmifDomainInHeader* domain_hdr = (CmifDomainInHeader*)start; + u32 payload_size = sizeof(CmifInHeader) + fmt.data_size; + *domain_hdr = (CmifDomainInHeader){ + .type = CmifDomainRequestType_SendMessage, + .num_in_objects = (u8)fmt.num_objects, + .data_size = (u16)payload_size, + .object_id = fmt.object_id, + .padding = 0, + .token = fmt.context, + }; + hdr = (CmifInHeader*)(domain_hdr + 1); + req.objects = (u32*)((u8*)hdr + payload_size); + } else + hdr = (CmifInHeader*)start; + + *hdr = (CmifInHeader){ + .magic = CMIF_IN_HEADER_MAGIC, + .version = fmt.context ? 1U : 0U, + .command_id = fmt.request_id, + .token = fmt.object_id ? 0U : fmt.context, + }; + + req.data = hdr + 1; + req.out_pointer_sizes = (u16*)(void*)((u8*)(void*)req.hipc.data_words + out_pointer_size_table_offset); + req.server_pointer_size = fmt.server_pointer_size; + + return req; +} + +NX_CONSTEXPR void* cmifMakeControlRequest(void* base, u32 request_id, u32 size) { + u32 actual_size = 16 + sizeof(CmifInHeader) + size; + HipcRequest hipc = + hipcMakeRequestInline(base, .type = CmifCommandType_Control, .num_data_words = (actual_size + 3) / 4, ); + CmifInHeader* hdr = (CmifInHeader*)cmifGetAlignedDataStart(hipc.data_words, base); + *hdr = (CmifInHeader){ + .magic = CMIF_IN_HEADER_MAGIC, + .version = 0, + .command_id = request_id, + .token = 0, + }; + return hdr + 1; +} + +NX_CONSTEXPR void cmifMakeCloseRequest(void* base, u32 object_id) { + if (object_id) { + HipcRequest hipc = hipcMakeRequestInline(base, .type = CmifCommandType_Request, + .num_data_words = (16 + sizeof(CmifDomainInHeader)) / 4, ); + CmifDomainInHeader* domain_hdr = (CmifDomainInHeader*)cmifGetAlignedDataStart(hipc.data_words, base); + *domain_hdr = (CmifDomainInHeader){ + .type = CmifDomainRequestType_Close, + .object_id = object_id, + }; + } else { + hipcMakeRequestInline(base, .type = CmifCommandType_Close, ); + } +} + +NX_CONSTEXPR void cmifRequestInBuffer(CmifRequest* req, const void* buffer, size_t size, HipcBufferMode mode) { + *req->hipc.send_buffers++ = hipcMakeBuffer(buffer, size, mode); +} + +NX_CONSTEXPR void cmifRequestOutBuffer(CmifRequest* req, void* buffer, size_t size, HipcBufferMode mode) { + *req->hipc.recv_buffers++ = hipcMakeBuffer(buffer, size, mode); +} + +NX_CONSTEXPR void cmifRequestInOutBuffer(CmifRequest* req, void* buffer, size_t size, HipcBufferMode mode) { + *req->hipc.exch_buffers++ = hipcMakeBuffer(buffer, size, mode); +} + +NX_CONSTEXPR void cmifRequestInPointer(CmifRequest* req, const void* buffer, size_t size) { + *req->hipc.send_statics++ = hipcMakeSendStatic(buffer, size, req->cur_in_ptr_id++); + req->server_pointer_size -= size; +} + +NX_CONSTEXPR void cmifRequestOutFixedPointer(CmifRequest* req, void* buffer, size_t size) { + *req->hipc.recv_list++ = hipcMakeRecvStatic(buffer, size); + req->server_pointer_size -= size; +} + +NX_CONSTEXPR void cmifRequestOutPointer(CmifRequest* req, void* buffer, size_t size) { + cmifRequestOutFixedPointer(req, buffer, size); + *req->out_pointer_sizes++ = size; +} + +NX_CONSTEXPR void cmifRequestInAutoBuffer(CmifRequest* req, const void* buffer, size_t size) { + if (req->server_pointer_size && size <= req->server_pointer_size) { + cmifRequestInPointer(req, buffer, size); + cmifRequestInBuffer(req, NULL, 0, HipcBufferMode_Normal); + } else { + cmifRequestInPointer(req, NULL, 0); + cmifRequestInBuffer(req, buffer, size, HipcBufferMode_Normal); + } +} + +NX_CONSTEXPR void cmifRequestOutAutoBuffer(CmifRequest* req, void* buffer, size_t size) { + if (req->server_pointer_size && size <= req->server_pointer_size) { + cmifRequestOutPointer(req, buffer, size); + cmifRequestOutBuffer(req, NULL, 0, HipcBufferMode_Normal); + } else { + cmifRequestOutPointer(req, NULL, 0); + cmifRequestOutBuffer(req, buffer, size, HipcBufferMode_Normal); + } +} + +NX_CONSTEXPR void cmifRequestObject(CmifRequest* req, u32 object_id) { *req->objects++ = object_id; } + +NX_CONSTEXPR void cmifRequestHandle(CmifRequest* req, Handle handle) { *req->hipc.copy_handles++ = handle; } + +NX_CONSTEXPR Result cmifParseResponse(CmifResponse* res, void* base, bool is_domain, u32 size) { + HipcResponse hipc = hipcParseResponse(base); + void* start = cmifGetAlignedDataStart(hipc.data_words, base); + + CmifOutHeader* hdr = NULL; + u32* objects = NULL; + if (is_domain) { + CmifDomainOutHeader* domain_hdr = (CmifDomainOutHeader*)start; + hdr = (CmifOutHeader*)(domain_hdr + 1); + objects = (u32*)((u8*)hdr + sizeof(CmifOutHeader) + size); + } else + hdr = (CmifOutHeader*)start; + + if (hdr->magic != CMIF_OUT_HEADER_MAGIC) return MAKERESULT(Module_Libnx, LibnxError_InvalidCmifOutHeader); + if (R_FAILED(hdr->result)) return hdr->result; + + *res = (CmifResponse){ + .data = hdr + 1, + .objects = objects, + .copy_handles = hipc.copy_handles, + .move_handles = hipc.move_handles, + }; + + return 0; +} + +NX_CONSTEXPR u32 cmifResponseGetObject(CmifResponse* res) { return *res->objects++; } + +NX_CONSTEXPR Handle cmifResponseGetCopyHandle(CmifResponse* res) { return *res->copy_handles++; } + +NX_CONSTEXPR Handle cmifResponseGetMoveHandle(CmifResponse* res) { return *res->move_handles++; } + +NX_INLINE Result cmifConvertCurrentObjectToDomain(Handle h, u32* out_object_id) { + cmifMakeControlRequest(armGetTls(), 0, 0); + Result rc = svcSendSyncRequest(h); + if (R_SUCCEEDED(rc)) { + CmifResponse resp = {}; + rc = cmifParseResponse(&resp, armGetTls(), false, sizeof(u32)); + if (R_SUCCEEDED(rc) && out_object_id) *out_object_id = *(u32*)resp.data; + } + return rc; +} + +NX_INLINE Result cmifCopyFromCurrentDomain(Handle h, u32 object_id, Handle* out_h) { + void* raw = cmifMakeControlRequest(armGetTls(), 1, sizeof(u32)); + *(u32*)raw = object_id; + Result rc = svcSendSyncRequest(h); + if (R_SUCCEEDED(rc)) { + CmifResponse resp = {}; + rc = cmifParseResponse(&resp, armGetTls(), false, 0); + if (R_SUCCEEDED(rc) && out_h) *out_h = resp.move_handles[0]; + } + return rc; +} + +NX_INLINE Result cmifCloneCurrentObject(Handle h, Handle* out_h) { + cmifMakeControlRequest(armGetTls(), 2, 0); + Result rc = svcSendSyncRequest(h); + if (R_SUCCEEDED(rc)) { + CmifResponse resp = {}; + rc = cmifParseResponse(&resp, armGetTls(), false, 0); + if (R_SUCCEEDED(rc) && out_h) *out_h = resp.move_handles[0]; + } + return rc; +} + +NX_INLINE Result cmifQueryPointerBufferSize(Handle h, u16* out_size) { + cmifMakeControlRequest(armGetTls(), 3, 0); + Result rc = svcSendSyncRequest(h); + if (R_SUCCEEDED(rc)) { + CmifResponse resp = {}; + rc = cmifParseResponse(&resp, armGetTls(), false, sizeof(u16)); + if (R_SUCCEEDED(rc) && out_size) *out_size = *(u16*)resp.data; + } + return rc; +} + +NX_INLINE Result cmifCloneCurrentObjectEx(Handle h, u32 tag, Handle* out_h) { + void* raw = cmifMakeControlRequest(armGetTls(), 4, sizeof(u32)); + *(u32*)raw = tag; + Result rc = svcSendSyncRequest(h); + if (R_SUCCEEDED(rc)) { + CmifResponse resp = {}; + rc = cmifParseResponse(&resp, armGetTls(), false, 0); + if (R_SUCCEEDED(rc) && out_h) *out_h = resp.move_handles[0]; + } + return rc; +} diff --git a/include/skyline/nx/sf/hipc.h b/include/skyline/nx/sf/hipc.h new file mode 100644 index 0000000..1987ca4 --- /dev/null +++ b/include/skyline/nx/sf/hipc.h @@ -0,0 +1,349 @@ +/** + * @file hipc.h + * @brief Horizon Inter-Process Communication protocol + * @author fincs + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include "../arm/tls.h" +#include "../kernel/svc.h" +#include "../result.h" + +#define HIPC_AUTO_RECV_STATIC UINT8_MAX +#define HIPC_RESPONSE_NO_PID UINT32_MAX + +typedef struct HipcMetadata { + u32 type; + u32 num_send_statics; + u32 num_send_buffers; + u32 num_recv_buffers; + u32 num_exch_buffers; + u32 num_data_words; + u32 num_recv_statics; // also accepts HIPC_AUTO_RECV_STATIC + u32 send_pid; + u32 num_copy_handles; + u32 num_move_handles; +} HipcMetadata; + +typedef struct HipcHeader { + u32 type : 16; + u32 num_send_statics : 4; + u32 num_send_buffers : 4; + u32 num_recv_buffers : 4; + u32 num_exch_buffers : 4; + u32 num_data_words : 10; + u32 recv_static_mode : 4; + u32 padding : 6; + u32 recv_list_offset : 11; // Unused. + u32 has_special_header : 1; +} HipcHeader; + +typedef struct HipcSpecialHeader { + u32 send_pid : 1; + u32 num_copy_handles : 4; + u32 num_move_handles : 4; + u32 padding : 23; +} HipcSpecialHeader; + +typedef struct HipcStaticDescriptor { + u32 index : 6; + u32 address_high : 6; + u32 address_mid : 4; + u32 size : 16; + u32 address_low; +} HipcStaticDescriptor; + +typedef struct HipcBufferDescriptor { + u32 size_low; + u32 address_low; + u32 mode : 2; + u32 address_high : 22; + u32 size_high : 4; + u32 address_mid : 4; +} HipcBufferDescriptor; + +typedef struct HipcRecvListEntry { + u32 address_low; + u32 address_high : 16; + u32 size : 16; +} HipcRecvListEntry; + +typedef struct HipcRequest { + HipcStaticDescriptor* send_statics; + HipcBufferDescriptor* send_buffers; + HipcBufferDescriptor* recv_buffers; + HipcBufferDescriptor* exch_buffers; + u32* data_words; + HipcRecvListEntry* recv_list; + Handle* copy_handles; + Handle* move_handles; +} HipcRequest; + +typedef struct HipcParsedRequest { + HipcMetadata meta; + HipcRequest data; + u64 pid; +} HipcParsedRequest; + +typedef struct HipcResponse { + u64 pid; + u32 num_statics; + u32 num_data_words; + u32 num_copy_handles; + u32 num_move_handles; + HipcStaticDescriptor* statics; + u32* data_words; + Handle* copy_handles; + Handle* move_handles; +} HipcResponse; + +typedef enum HipcBufferMode { + HipcBufferMode_Normal = 0, + HipcBufferMode_NonSecure = 1, + HipcBufferMode_Invalid = 2, + HipcBufferMode_NonDevice = 3, +} HipcBufferMode; + +NX_CONSTEXPR HipcStaticDescriptor hipcMakeSendStatic(const void* buffer, size_t size, u8 index) { + return (HipcStaticDescriptor){ + .index = index, + .address_high = (u32)((uintptr_t)buffer >> 36), + .address_mid = (u32)((uintptr_t)buffer >> 32), + .size = (u32)size, + .address_low = (u32)(uintptr_t)buffer, + }; +} + +NX_CONSTEXPR HipcBufferDescriptor hipcMakeBuffer(const void* buffer, size_t size, HipcBufferMode mode) { + return (HipcBufferDescriptor){ + .size_low = (u32)size, + .address_low = (u32)(uintptr_t)buffer, + .mode = mode, + .address_high = (u32)((uintptr_t)buffer >> 36), + .size_high = (u32)(size >> 32), + .address_mid = (u32)((uintptr_t)buffer >> 32), + }; +} + +NX_CONSTEXPR HipcRecvListEntry hipcMakeRecvStatic(void* buffer, size_t size) { + return (HipcRecvListEntry){ + .address_low = (u32)((uintptr_t)buffer), + .address_high = (u32)((uintptr_t)buffer >> 32), + .size = (u32)size, + }; +} + +NX_CONSTEXPR void* hipcGetStaticAddress(const HipcStaticDescriptor* desc) { + return (void*)(desc->address_low | ((uintptr_t)desc->address_mid << 32) | ((uintptr_t)desc->address_high << 36)); +} + +NX_CONSTEXPR size_t hipcGetStaticSize(const HipcStaticDescriptor* desc) { return desc->size; } + +NX_CONSTEXPR void* hipcGetBufferAddress(const HipcBufferDescriptor* desc) { + return (void*)(desc->address_low | ((uintptr_t)desc->address_mid << 32) | ((uintptr_t)desc->address_high << 36)); +} + +NX_CONSTEXPR size_t hipcGetBufferSize(const HipcBufferDescriptor* desc) { + return desc->size_low | ((size_t)desc->size_high << 32); +} + +NX_CONSTEXPR HipcRequest hipcCalcRequestLayout(HipcMetadata meta, void* base) { + // Copy handles + Handle* copy_handles = NULL; + if (meta.num_copy_handles) { + copy_handles = (Handle*)base; + base = copy_handles + meta.num_copy_handles; + } + + // Move handles + Handle* move_handles = NULL; + if (meta.num_move_handles) { + move_handles = (Handle*)base; + base = move_handles + meta.num_move_handles; + } + + // Send statics + HipcStaticDescriptor* send_statics = NULL; + if (meta.num_send_statics) { + send_statics = (HipcStaticDescriptor*)base; + base = send_statics + meta.num_send_statics; + } + + // Send buffers + HipcBufferDescriptor* send_buffers = NULL; + if (meta.num_send_buffers) { + send_buffers = (HipcBufferDescriptor*)base; + base = send_buffers + meta.num_send_buffers; + } + + // Recv buffers + HipcBufferDescriptor* recv_buffers = NULL; + if (meta.num_recv_buffers) { + recv_buffers = (HipcBufferDescriptor*)base; + base = recv_buffers + meta.num_recv_buffers; + } + + // Exch buffers + HipcBufferDescriptor* exch_buffers = NULL; + if (meta.num_exch_buffers) { + exch_buffers = (HipcBufferDescriptor*)base; + base = exch_buffers + meta.num_exch_buffers; + } + + // Data words + u32* data_words = NULL; + if (meta.num_data_words) { + data_words = (u32*)base; + base = data_words + meta.num_data_words; + } + + // Recv list + HipcRecvListEntry* recv_list = NULL; + if (meta.num_recv_statics) recv_list = (HipcRecvListEntry*)base; + + return (HipcRequest){ + .send_statics = send_statics, + .send_buffers = send_buffers, + .recv_buffers = recv_buffers, + .exch_buffers = exch_buffers, + .data_words = data_words, + .recv_list = recv_list, + .copy_handles = copy_handles, + .move_handles = move_handles, + }; +} + +NX_CONSTEXPR HipcRequest hipcMakeRequest(void* base, HipcMetadata meta) { + // Write message header + bool has_special_header = meta.send_pid || meta.num_copy_handles || meta.num_move_handles; + HipcHeader* hdr = (HipcHeader*)base; + base = hdr + 1; + *hdr = (HipcHeader){ + .type = meta.type, + .num_send_statics = meta.num_send_statics, + .num_send_buffers = meta.num_send_buffers, + .num_recv_buffers = meta.num_recv_buffers, + .num_exch_buffers = meta.num_exch_buffers, + .num_data_words = meta.num_data_words, + .recv_static_mode = meta.num_recv_statics + ? (meta.num_recv_statics != HIPC_AUTO_RECV_STATIC ? 2u + meta.num_recv_statics : 2u) + : 0u, + .padding = 0, + .recv_list_offset = 0, + .has_special_header = has_special_header, + }; + + // Write special header + if (has_special_header) { + HipcSpecialHeader* sphdr = (HipcSpecialHeader*)base; + base = sphdr + 1; + *sphdr = (HipcSpecialHeader){ + .send_pid = meta.send_pid, + .num_copy_handles = meta.num_copy_handles, + .num_move_handles = meta.num_move_handles, + }; + if (meta.send_pid) base = (u8*)base + sizeof(u64); + } + + // Calculate layout + return hipcCalcRequestLayout(meta, base); +} + +#define hipcMakeRequestInline(_base, ...) hipcMakeRequest((_base), (HipcMetadata){__VA_ARGS__}) + +NX_CONSTEXPR HipcParsedRequest hipcParseRequest(void* base) { + // Parse message header + HipcHeader hdr = {}; + __builtin_memcpy(&hdr, base, sizeof(hdr)); + base = (u8*)base + sizeof(hdr); + u32 num_recv_statics = 0; + u64 pid = 0; + + // Parse recv static mode + if (hdr.recv_static_mode) { + if (hdr.recv_static_mode == 2u) + num_recv_statics = HIPC_AUTO_RECV_STATIC; + else if (hdr.recv_static_mode > 2u) + num_recv_statics = hdr.recv_static_mode - 2u; + } + + // Parse special header + HipcSpecialHeader sphdr = {}; + if (hdr.has_special_header) { + __builtin_memcpy(&sphdr, base, sizeof(sphdr)); + base = (u8*)base + sizeof(sphdr); + + // Read PID descriptor + if (sphdr.send_pid) { + pid = *(u64*)base; + base = (u8*)base + sizeof(u64); + } + } + + const HipcMetadata meta = { + .type = hdr.type, + .num_send_statics = hdr.num_send_statics, + .num_send_buffers = hdr.num_send_buffers, + .num_recv_buffers = hdr.num_recv_buffers, + .num_exch_buffers = hdr.num_exch_buffers, + .num_data_words = hdr.num_data_words, + .num_recv_statics = num_recv_statics, + .send_pid = sphdr.send_pid, + .num_copy_handles = sphdr.num_copy_handles, + .num_move_handles = sphdr.num_move_handles, + }; + + return (HipcParsedRequest){ + .meta = meta, + .data = hipcCalcRequestLayout(meta, base), + .pid = pid, + }; +} + +NX_CONSTEXPR HipcResponse hipcParseResponse(void* base) { + // Parse header + HipcHeader hdr = {}; + __builtin_memcpy(&hdr, base, sizeof(hdr)); + base = (u8*)base + sizeof(hdr); + + // Initialize response + HipcResponse response = {}; + response.num_statics = hdr.num_send_statics; + response.num_data_words = hdr.num_data_words; + response.pid = HIPC_RESPONSE_NO_PID; + + // Parse special header + if (hdr.has_special_header) { + HipcSpecialHeader sphdr = {}; + __builtin_memcpy(&sphdr, base, sizeof(sphdr)); + base = (u8*)base + sizeof(sphdr); + + // Update response + response.num_copy_handles = sphdr.num_copy_handles; + response.num_move_handles = sphdr.num_move_handles; + + // Parse PID descriptor + if (sphdr.send_pid) { + response.pid = *(u64*)base; + base = (u8*)base + sizeof(u64); + } + } + + // Copy handles + response.copy_handles = (Handle*)base; + base = response.copy_handles + response.num_copy_handles; + + // Move handles + response.move_handles = (Handle*)base; + base = response.move_handles + response.num_move_handles; + + // Send statics + response.statics = (HipcStaticDescriptor*)base; + base = response.statics + response.num_statics; + + // Data words + response.data_words = (u32*)base; + + return response; +} diff --git a/include/skyline/nx/sf/service.h b/include/skyline/nx/sf/service.h new file mode 100644 index 0000000..9e01294 --- /dev/null +++ b/include/skyline/nx/sf/service.h @@ -0,0 +1,420 @@ +/** + * @file service.h + * @brief Service wrapper object + * @author fincs + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include "cmif.h" +#include "hipc.h" + +/// Service object structure +typedef struct Service { + Handle session; + u32 own_handle; + u32 object_id; + u16 pointer_buffer_size; +} Service; + +enum { + SfBufferAttr_In = BIT(0), + SfBufferAttr_Out = BIT(1), + SfBufferAttr_HipcMapAlias = BIT(2), + SfBufferAttr_HipcPointer = BIT(3), + SfBufferAttr_FixedSize = BIT(4), + SfBufferAttr_HipcAutoSelect = BIT(5), + SfBufferAttr_HipcMapTransferAllowsNonSecure = BIT(6), + SfBufferAttr_HipcMapTransferAllowsNonDevice = BIT(7), +}; + +typedef struct SfBufferAttrs { + u32 attr0; + u32 attr1; + u32 attr2; + u32 attr3; + u32 attr4; + u32 attr5; + u32 attr6; + u32 attr7; +} SfBufferAttrs; + +typedef struct SfBuffer { + const void* ptr; + size_t size; +} SfBuffer; + +typedef enum SfOutHandleAttr { + SfOutHandleAttr_None = 0, + SfOutHandleAttr_HipcCopy = 1, + SfOutHandleAttr_HipcMove = 2, +} SfOutHandleAttr; + +typedef struct SfOutHandleAttrs { + SfOutHandleAttr attr0; + SfOutHandleAttr attr1; + SfOutHandleAttr attr2; + SfOutHandleAttr attr3; + SfOutHandleAttr attr4; + SfOutHandleAttr attr5; + SfOutHandleAttr attr6; + SfOutHandleAttr attr7; +} SfOutHandleAttrs; + +typedef struct SfDispatchParams { + Handle target_session; + u32 context; + + SfBufferAttrs buffer_attrs; + SfBuffer buffers[8]; + + bool in_send_pid; + + u32 in_num_objects; + const Service* in_objects[8]; + + u32 in_num_handles; + Handle in_handles[8]; + + u32 out_num_objects; + Service* out_objects; + + SfOutHandleAttrs out_handle_attrs; + Handle* out_handles; +} SfDispatchParams; + +/** + * @brief Returns whether a service has been initialized. + * @param[in] s Service object. + * @return true if initialized. + */ +NX_CONSTEXPR bool serviceIsActive(Service* s) { return s->session != INVALID_HANDLE; } + +/** + * @brief Returns whether a service is overriden in the homebrew environment. + * @param[in] s Service object. + * @return true if overriden. + */ +NX_CONSTEXPR bool serviceIsOverride(Service* s) { return serviceIsActive(s) && !s->own_handle && !s->object_id; } + +/** + * @brief Returns whether a service is a domain. + * @param[in] s Service object. + * @return true if a domain. + */ +NX_CONSTEXPR bool serviceIsDomain(Service* s) { return serviceIsActive(s) && s->own_handle && s->object_id; } + +/** + * @brief Returns whether a service is a domain subservice. + * @param[in] s Service object. + * @return true if a domain subservice. + */ +NX_CONSTEXPR bool serviceIsDomainSubservice(Service* s) { + return serviceIsActive(s) && !s->own_handle && s->object_id; +} + +/** + * @brief For a domain/domain subservice, return the associated object ID. + * @param[in] s Service object, necessarily a domain or domain subservice. + * @return The object ID. + */ +NX_CONSTEXPR u32 serviceGetObjectId(Service* s) { return s->object_id; } + +/** + * @brief Creates a service object from an IPC session handle. + * @param[out] s Service object. + * @param[in] h IPC session handle. + */ +NX_INLINE void serviceCreate(Service* s, Handle h) { + s->session = h; + s->own_handle = 1; + s->object_id = 0; + s->pointer_buffer_size = 0; + cmifQueryPointerBufferSize(h, &s->pointer_buffer_size); +} + +/** + * @brief Creates a non-domain subservice object from a parent service. + * @param[out] s Service object. + * @param[in] parent Parent service. + * @param[in] h IPC session handle for this subservice. + */ +NX_INLINE void serviceCreateNonDomainSubservice(Service* s, Service* parent, Handle h) { + s->session = h; + s->own_handle = 1; + s->object_id = 0; + s->pointer_buffer_size = parent->pointer_buffer_size; +} + +/** + * @brief Creates a domain subservice object from a parent service. + * @param[out] s Service object. + * @param[in] parent Parent service, necessarily a domain or domain subservice. + * @param[in] object_id Object ID for this subservice. + */ +NX_CONSTEXPR void serviceCreateDomainSubservice(Service* s, Service* parent, u32 object_id) { + s->session = parent->session; + s->own_handle = 0; + s->object_id = object_id; + s->pointer_buffer_size = parent->pointer_buffer_size; +} + +/** + * @brief Hints the compiler that a service will always contain a domain object. + * @param[in] _s Service object. + */ +#define serviceAssumeDomain(_s) \ + do { \ + if (!(_s)->object_id) __builtin_unreachable(); \ + } while (0) + +/** + * @brief Closes a service. + * @param[in] s Service object. + */ +NX_INLINE void serviceClose(Service* s) { +#if defined(NX_SERVICE_ASSUME_NON_DOMAIN) + if (s->object_id) __builtin_unreachable(); +#endif + + if (s->own_handle || s->object_id) { + cmifMakeCloseRequest(armGetTls(), s->own_handle ? 0 : s->object_id); + svcSendSyncRequest(s->session); + if (s->own_handle) svcCloseHandle(s->session); + } + *s = (Service){}; +} + +/** + * @brief Clones a service. + * @param[in] s Service object. + * @param[out] out_s Output service object. + */ +NX_INLINE Result serviceClone(Service* s, Service* out_s) { +#if defined(NX_SERVICE_ASSUME_NON_DOMAIN) + if (s->object_id) __builtin_unreachable(); +#endif + + out_s->session = 0; + out_s->own_handle = 1; + out_s->object_id = s->object_id; + out_s->pointer_buffer_size = s->pointer_buffer_size; + return cmifCloneCurrentObject(s->session, &out_s->session); +} + +/** + * @brief Clones a service with a session manager tag. + * @param[in] s Service object. + * @param[in] tag Session manager tag (unused in current official server code) + * @param[out] out_s Output service object. + */ +NX_INLINE Result serviceCloneEx(Service* s, u32 tag, Service* out_s) { +#if defined(NX_SERVICE_ASSUME_NON_DOMAIN) + if (s->object_id) __builtin_unreachable(); +#endif + + out_s->session = 0; + out_s->own_handle = 1; + out_s->object_id = s->object_id; + out_s->pointer_buffer_size = s->pointer_buffer_size; + return cmifCloneCurrentObjectEx(s->session, tag, &out_s->session); +} + +/** + * @brief Converts a regular service to a domain. + * @param[in] s Service object. + * @return Result code. + */ +NX_INLINE Result serviceConvertToDomain(Service* s) { + if (!s->own_handle) { + // For overridden services, create a clone first. + Result rc = cmifCloneCurrentObjectEx(s->session, 0, &s->session); + if (R_FAILED(rc)) return rc; + s->own_handle = 1; + } + + return cmifConvertCurrentObjectToDomain(s->session, &s->object_id); +} + +NX_CONSTEXPR void _serviceRequestFormatProcessBuffer(CmifRequestFormat* fmt, u32 attr) { + if (!attr) return; + const bool is_in = (attr & SfBufferAttr_In) != 0; + const bool is_out = (attr & SfBufferAttr_Out) != 0; + + if (attr & SfBufferAttr_HipcAutoSelect) { + if (is_in) fmt->num_in_auto_buffers++; + if (is_out) fmt->num_out_auto_buffers++; + } else if (attr & SfBufferAttr_HipcPointer) { + if (is_in) fmt->num_in_pointers++; + if (is_out) { + if (attr & SfBufferAttr_FixedSize) + fmt->num_out_fixed_pointers++; + else + fmt->num_out_pointers++; + } + } else if (attr & SfBufferAttr_HipcMapAlias) { + if (is_in && is_out) + fmt->num_inout_buffers++; + else if (is_in) + fmt->num_in_buffers++; + else if (is_out) + fmt->num_out_buffers++; + } +} + +NX_CONSTEXPR void _serviceRequestProcessBuffer(CmifRequest* req, const SfBuffer* buf, u32 attr) { + if (!attr) return; + const bool is_in = (attr & SfBufferAttr_In); + const bool is_out = (attr & SfBufferAttr_Out); + + if (attr & SfBufferAttr_HipcAutoSelect) { + if (is_in) cmifRequestInAutoBuffer(req, buf->ptr, buf->size); + if (is_out) cmifRequestOutAutoBuffer(req, (void*)buf->ptr, buf->size); + } else if (attr & SfBufferAttr_HipcPointer) { + if (is_in) cmifRequestInPointer(req, buf->ptr, buf->size); + if (is_out) { + if (attr & SfBufferAttr_FixedSize) + cmifRequestOutFixedPointer(req, (void*)buf->ptr, buf->size); + else + cmifRequestOutPointer(req, (void*)buf->ptr, buf->size); + } + } else if (attr & SfBufferAttr_HipcMapAlias) { + HipcBufferMode mode = HipcBufferMode_Normal; + if (attr & SfBufferAttr_HipcMapTransferAllowsNonSecure) mode = HipcBufferMode_NonSecure; + if (attr & SfBufferAttr_HipcMapTransferAllowsNonDevice) mode = HipcBufferMode_NonDevice; + + if (is_in && is_out) + cmifRequestInOutBuffer(req, (void*)buf->ptr, buf->size, mode); + else if (is_in) + cmifRequestInBuffer(req, buf->ptr, buf->size, mode); + else if (is_out) + cmifRequestOutBuffer(req, (void*)buf->ptr, buf->size, mode); + } +} + +NX_INLINE void* serviceMakeRequest(Service* s, u32 request_id, u32 context, u32 data_size, bool send_pid, + const SfBufferAttrs buffer_attrs, const SfBuffer* buffers, u32 num_objects, + const Service* const* objects, u32 num_handles, const Handle* handles) { +#if defined(NX_SERVICE_ASSUME_NON_DOMAIN) + if (s->object_id) __builtin_unreachable(); +#endif + + CmifRequestFormat fmt = {}; + fmt.object_id = s->object_id; + fmt.request_id = request_id; + fmt.context = context; + fmt.data_size = data_size; + fmt.server_pointer_size = s->pointer_buffer_size; + fmt.num_objects = num_objects; + fmt.num_handles = num_handles; + fmt.send_pid = send_pid; + + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr0); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr1); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr2); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr3); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr4); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr5); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr6); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr7); + + CmifRequest req = cmifMakeRequest(armGetTls(), fmt); + + if (s->object_id) // TODO: Check behavior of input objects in non-domain sessions + for (u32 i = 0; i < num_objects; i++) cmifRequestObject(&req, objects[i]->object_id); + + for (u32 i = 0; i < num_handles; i++) cmifRequestHandle(&req, handles[i]); + + _serviceRequestProcessBuffer(&req, &buffers[0], buffer_attrs.attr0); + _serviceRequestProcessBuffer(&req, &buffers[1], buffer_attrs.attr1); + _serviceRequestProcessBuffer(&req, &buffers[2], buffer_attrs.attr2); + _serviceRequestProcessBuffer(&req, &buffers[3], buffer_attrs.attr3); + _serviceRequestProcessBuffer(&req, &buffers[4], buffer_attrs.attr4); + _serviceRequestProcessBuffer(&req, &buffers[5], buffer_attrs.attr5); + _serviceRequestProcessBuffer(&req, &buffers[6], buffer_attrs.attr6); + _serviceRequestProcessBuffer(&req, &buffers[7], buffer_attrs.attr7); + + return req.data; +} + +NX_CONSTEXPR void _serviceResponseGetHandle(CmifResponse* res, SfOutHandleAttr type, Handle* out) { + switch (type) { + default: + case SfOutHandleAttr_None: + break; + case SfOutHandleAttr_HipcCopy: + *out = cmifResponseGetCopyHandle(res); + break; + case SfOutHandleAttr_HipcMove: + *out = cmifResponseGetMoveHandle(res); + break; + } +} + +NX_INLINE Result serviceParseResponse(Service* s, u32 out_size, void** out_data, u32 num_out_objects, + Service* out_objects, const SfOutHandleAttrs out_handle_attrs, + Handle* out_handles) { +#if defined(NX_SERVICE_ASSUME_NON_DOMAIN) + if (s->object_id) __builtin_unreachable(); +#endif + + CmifResponse res = {}; + bool is_domain = s->object_id != 0; + Result rc = cmifParseResponse(&res, armGetTls(), is_domain, out_size); + if (R_FAILED(rc)) return rc; + + if (out_size) *out_data = res.data; + + for (u32 i = 0; i < num_out_objects; i++) { + if (is_domain) + serviceCreateDomainSubservice(&out_objects[i], s, cmifResponseGetObject(&res)); + else // Output objects are marshalled as move handles at the beginning of the list. + serviceCreateNonDomainSubservice(&out_objects[i], s, cmifResponseGetMoveHandle(&res)); + } + + _serviceResponseGetHandle(&res, out_handle_attrs.attr0, &out_handles[0]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr1, &out_handles[1]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr2, &out_handles[2]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr3, &out_handles[3]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr4, &out_handles[4]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr5, &out_handles[5]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr6, &out_handles[6]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr7, &out_handles[7]); + + return 0; +} + +NX_INLINE Result serviceDispatchImpl(Service* s, u32 request_id, const void* in_data, u32 in_data_size, void* out_data, + u32 out_data_size, SfDispatchParams disp) { + // Make a copy of the service struct, so that the compiler can assume that it won't be modified by function calls. + Service srv = *s; + + void* in = + serviceMakeRequest(&srv, request_id, disp.context, in_data_size, disp.in_send_pid, disp.buffer_attrs, + disp.buffers, disp.in_num_objects, disp.in_objects, disp.in_num_handles, disp.in_handles); + + if (in_data_size) __builtin_memcpy(in, in_data, in_data_size); + + Result rc = svcSendSyncRequest(disp.target_session == INVALID_HANDLE ? s->session : disp.target_session); + if (R_SUCCEEDED(rc)) { + void* out = NULL; + rc = serviceParseResponse(&srv, out_data_size, &out, disp.out_num_objects, disp.out_objects, + disp.out_handle_attrs, disp.out_handles); + + if (R_SUCCEEDED(rc) && out_data && out_data_size) __builtin_memcpy(out_data, out, out_data_size); + } + + return rc; +} + +#define serviceDispatch(_s, _rid, ...) \ + serviceDispatchImpl((_s), (_rid), NULL, 0, NULL, 0, (SfDispatchParams){__VA_ARGS__}) + +#define serviceDispatchIn(_s, _rid, _in, ...) \ + serviceDispatchImpl((_s), (_rid), &(_in), sizeof(_in), NULL, 0, (SfDispatchParams){__VA_ARGS__}) + +#define serviceDispatchOut(_s, _rid, _out, ...) \ + serviceDispatchImpl((_s), (_rid), NULL, 0, &(_out), sizeof(_out), (SfDispatchParams){__VA_ARGS__}) + +#define serviceDispatchInOut(_s, _rid, _in, _out, ...) \ + serviceDispatchImpl((_s), (_rid), &(_in), sizeof(_in), &(_out), sizeof(_out), (SfDispatchParams){__VA_ARGS__}) diff --git a/include/skyline/nx/sf/sessionmgr.h b/include/skyline/nx/sf/sessionmgr.h new file mode 100644 index 0000000..8bdbfe3 --- /dev/null +++ b/include/skyline/nx/sf/sessionmgr.h @@ -0,0 +1,22 @@ +#pragma once +#include "../kernel/condvar.h" +#include "../kernel/mutex.h" +#include "types.h" + +#define NX_SESSION_MGR_MAX_SESSIONS 16 + +typedef struct SessionMgr { + Handle sessions[NX_SESSION_MGR_MAX_SESSIONS]; + u32 num_sessions; + u32 free_mask; + Mutex mutex; + CondVar condvar; + bool is_waiting; +} SessionMgr; + +Result sessionmgrCreate(SessionMgr* mgr, Handle root_session, u32 num_sessions); +void sessionmgrClose(SessionMgr* mgr); +int sessionmgrAttachClient(SessionMgr* mgr); +void sessionmgrDetachClient(SessionMgr* mgr, int slot); + +NX_CONSTEXPR Handle sessionmgrGetClientSession(SessionMgr* mgr, int slot) { return mgr->sessions[slot]; } diff --git a/include/skyline/nx/smc.h b/include/skyline/nx/smc.h new file mode 100644 index 0000000..12381fe --- /dev/null +++ b/include/skyline/nx/smc.h @@ -0,0 +1,65 @@ +/** + * @file smc.h + * @brief Wrappers for secure monitor calls. + * @copyright libnx Authors + */ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + EXO_EMUMMC_TYPE_NONE = 0, + EXO_EMUMMC_TYPE_PARTITION = 1, + EXO_EMUMMC_TYPE_FILES = 2, +} exo_emummc_type_t; + +typedef enum { + EXO_EMUMMC_MMC_NAND = 0, + EXO_EMUMMC_MMC_SD = 1, + EXO_EMUMMC_MMC_GC = 2, +} exo_emummc_mmc_t; + +typedef struct { + uint32_t magic; + uint32_t type; + uint32_t id; + uint32_t fs_version; +} exo_emummc_base_config_t; + +typedef struct { + uint64_t start_sector; +} exo_emummc_partition_config_t; + +typedef struct { + exo_emummc_base_config_t base_cfg; + union { + exo_emummc_partition_config_t partition_cfg; + }; +} exo_emummc_config_t; + +void smcRebootToRcm(void); +void smcRebootToIramPayload(void); +void smcPerformShutdown(void); + +Result smcCopyToIram(uintptr_t iram_addr, const void* src_addr, u32 size); +Result smcCopyFromIram(void* dst_addr, uintptr_t iram_addr, u32 size); + +Result smcReadWriteRegister(u32 phys_addr, u32 value, u32 mask); + +void* smcMemCpy(void* dst_addr, void* src_addr, size_t size); +void* smcMemSet(void* dst_addr, u32 value, size_t size); + +Result smcWriteAddress8(void* dst_addr, u8 val); +Result smcWriteAddress16(void* dst_addr, u16 val); +Result smcWriteAddress32(void* dst_addr, u32 val); +Result smcWriteAddress64(void* dst_addr, u64 val); + +Result smcGetEmummcConfig(exo_emummc_mmc_t mmc_id, exo_emummc_config_t* out_cfg, void* out_paths); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/skyline/utils/cpputils.hpp b/include/skyline/utils/cpputils.hpp new file mode 100644 index 0000000..adde113 --- /dev/null +++ b/include/skyline/utils/cpputils.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +#include "nn/fs.h" +#include "nn/settings.h" +#include "operator.h" +#include "types.h" + +namespace skyline::utils { + +enum region : u8 { Text, Rodata, Data, Bss, Heap }; + +extern std::string g_RomMountStr; + +extern u64 g_MainTextAddr; +extern u64 g_MainRodataAddr; +extern u64 g_MainDataAddr; +extern u64 g_MainBssAddr; +extern u64 g_MainHeapAddr; + +extern nn::settings::system::FirmwareVersion g_CachedFwVer; + +void init(); + +Result walkDirectory(std::string const&, + std::function)>, + bool recursive = true); +Result readEntireFile(std::string const&, void**, size_t*); +Result readFile(std::string const&, s64, void*, size_t); +Result writeFile(std::string const&, s64, void*, size_t); +Result entryCount(u64*, std::string const&, nn::fs::DirectoryEntryType); +extern "C" void* getRegionAddress(skyline::utils::region); + +struct Sha256Hash { + u8 hash[0x20]; + + bool operator==(const Sha256Hash& o) const { return std::memcmp(this, &o, sizeof(*this)) == 0; } + bool operator!=(const Sha256Hash& o) const { return std::memcmp(this, &o, sizeof(*this)) != 0; } + bool operator<(const Sha256Hash& o) const { return std::memcmp(this, &o, sizeof(*this)) < 0; } + bool operator>(const Sha256Hash& o) const { return std::memcmp(this, &o, sizeof(*this)) > 0; } +}; +}; // namespace skyline::utils \ No newline at end of file diff --git a/include/skyline/utils/ipc.hpp b/include/skyline/utils/ipc.hpp new file mode 100644 index 0000000..97b31a5 --- /dev/null +++ b/include/skyline/utils/ipc.hpp @@ -0,0 +1,137 @@ +#pragma once + +#include "nn/sf/hipc.h" +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/sf/service.h" + +#ifdef __cplusplus +} +#endif + +namespace skyline::utils { + +Result nnServiceCreate(Service* srv, const char* name); +void nnServiceClose(Service* s); + +NX_INLINE void* nnServiceMakeRequest(Service* s, u32 request_id, u32 context, u32 data_size, bool send_pid, + const SfBufferAttrs buffer_attrs, const SfBuffer* buffers, u32 num_objects, + const Service* const* objects, u32 num_handles, const Handle* handles) { + void* base = nn::sf::hipc::GetMessageBufferOnTls(); + + CmifRequestFormat fmt = {}; + fmt.object_id = s->object_id; + fmt.request_id = request_id; + fmt.context = context; + fmt.data_size = data_size; + fmt.server_pointer_size = s->pointer_buffer_size; + fmt.num_objects = num_objects; + fmt.num_handles = num_handles; + fmt.send_pid = send_pid; + + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr0); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr1); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr2); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr3); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr4); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr5); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr6); + _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr7); + + CmifRequest req = cmifMakeRequest(base, fmt); + + if (s->object_id) // TODO: Check behavior of input objects in non-domain sessions + for (u32 i = 0; i < num_objects; i++) cmifRequestObject(&req, objects[i]->object_id); + + for (u32 i = 0; i < num_handles; i++) cmifRequestHandle(&req, handles[i]); + + _serviceRequestProcessBuffer(&req, &buffers[0], buffer_attrs.attr0); + _serviceRequestProcessBuffer(&req, &buffers[1], buffer_attrs.attr1); + _serviceRequestProcessBuffer(&req, &buffers[2], buffer_attrs.attr2); + _serviceRequestProcessBuffer(&req, &buffers[3], buffer_attrs.attr3); + _serviceRequestProcessBuffer(&req, &buffers[4], buffer_attrs.attr4); + _serviceRequestProcessBuffer(&req, &buffers[5], buffer_attrs.attr5); + _serviceRequestProcessBuffer(&req, &buffers[6], buffer_attrs.attr6); + _serviceRequestProcessBuffer(&req, &buffers[7], buffer_attrs.attr7); + + return req.data; +} + +NX_INLINE Result nnServiceParseResponse(Service* s, u32 out_size, void** out_data, u32 num_out_objects, + Service* out_objects, const SfOutHandleAttrs out_handle_attrs, + Handle* out_handles) { + void* base = nn::sf::hipc::GetMessageBufferOnTls(); + + CmifResponse res = {}; + bool is_domain = s->object_id != 0; + Result rc = cmifParseResponse(&res, base, is_domain, out_size); + if (R_FAILED(rc)) return rc; + + if (out_size) *out_data = res.data; + + for (u32 i = 0; i < num_out_objects; i++) { + if (is_domain) + serviceCreateDomainSubservice(&out_objects[i], s, cmifResponseGetObject(&res)); + else // Output objects are marshalled as move handles at the beginning of the list. + serviceCreateNonDomainSubservice(&out_objects[i], s, cmifResponseGetMoveHandle(&res)); + } + + _serviceResponseGetHandle(&res, out_handle_attrs.attr0, &out_handles[0]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr1, &out_handles[1]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr2, &out_handles[2]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr3, &out_handles[3]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr4, &out_handles[4]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr5, &out_handles[5]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr6, &out_handles[6]); + _serviceResponseGetHandle(&res, out_handle_attrs.attr7, &out_handles[7]); + + return 0; +} + +NX_INLINE Result nnServiceDispatchImpl(Service* s, u32 request_id, const void* in_data, u32 in_data_size, + void* out_data, u32 out_data_size, SfDispatchParams disp) { + void* base = nn::sf::hipc::GetMessageBufferOnTls(); + Service srv = *s; + + void* in = + nnServiceMakeRequest(&srv, request_id, disp.context, in_data_size, disp.in_send_pid, disp.buffer_attrs, + disp.buffers, disp.in_num_objects, disp.in_objects, disp.in_num_handles, disp.in_handles); + + if (in_data_size) __builtin_memcpy(in, in_data, in_data_size); + + R_TRY(nn::sf::hipc::SendSyncRequest(disp.target_session == INVALID_HANDLE ? s->session : disp.target_session, base, + 0x100)); + + void* out = NULL; + R_TRY(nnServiceParseResponse(&srv, out_data_size, &out, disp.out_num_objects, disp.out_objects, + disp.out_handle_attrs, disp.out_handles)); + + if (out_data && out_data_size) __builtin_memcpy(out_data, out, out_data_size); + + return 0; +} + +#define nnServiceDispatch(_s, _rid, ...) \ + skyline::utils::ServiceDispatchImpl((_s), (_rid), NULL, 0, NULL, 0, (SfDispatchParams){__VA_ARGS__}) + +#define nnServiceDispatchIn(_s, _rid, _in, ...) \ + skyline::utils::nnServiceDispatchImpl((_s), (_rid), &(_in), sizeof(_in), NULL, 0, (SfDispatchParams){__VA_ARGS__}) + +#define nnServiceDispatchOut(_s, _rid, _out, ...) \ + skyline::utils::nnServiceDispatchImpl((_s), (_rid), NULL, 0, &(_out), sizeof(_out), \ + (SfDispatchParams){__VA_ARGS__}) + +#define nnServiceDispatchInOut(_s, _rid, _in, _out, ...) \ + skyline::utils::nnServiceDispatchImpl((_s), (_rid), &(_in), sizeof(_in), &(_out), sizeof(_out), \ + (SfDispatchParams){__VA_ARGS__}) + +class Ipc { + public: + static Result getOwnProcessHandle(Handle*); +}; + +} // namespace skyline::utils diff --git a/include/skyline/utils/utils.h b/include/skyline/utils/utils.h new file mode 100644 index 0000000..367d530 --- /dev/null +++ b/include/skyline/utils/utils.h @@ -0,0 +1,24 @@ +#pragma once + +#include "types.h" +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/kernel/svc.h" + +u32 previousPowerOfTwo(u32 x); +Result memGetMap(MemoryInfo* info, u64 addr); +u64 memGetMapAddr(u64 addr); +u64 memNextMap(u64); +u64 memNextMapOfType(u64, u32); +u64 memNextMapOfPerm(u64, u32); +u64 get_program_id(); +void inlineHandler(); + +#ifdef __cplusplus +}; +#endif diff --git a/include/std.h b/include/std.h new file mode 100644 index 0000000..0520316 --- /dev/null +++ b/include/std.h @@ -0,0 +1,93 @@ +/** + * @file std.h + * @brief Classes that are a part of the standard library (std) + */ + +#pragma once + +#include + +#include "operator.h" +#include "types.h" + +namespace std { +struct nothrow_t; +struct __va_list; + +namespace __l { + template + class __tree_node; + + template + class list { + public: + list(T const&); + }; + + template + class pair { + public: + ~pair(); + }; + + template + class vector { + public: + void reserve(u64); + + void __push_back_slow_path(T const&); + }; + + template + class __tree { + public: + void destroy(std::__l::__tree_node*); + }; + + template + void __sort(T2, T2, T2, T); + + template + void __sort3(T2, T2, T); + + template + void __sort5(T2, T2, T2, T2, T2, T); + + template + void __insertion_sort_incomplete(T2, T2, T); +}; // namespace __l + +namespace __1 { + class locale { + class facet { + ~facet(); + void __on_zero_shared(); + }; + + class id { + void __init(); + void __get(); + }; + + public: + static locale global(locale const&); + static locale __global(); + static const locale& classic(); + + locale(); + locale(locale const&); + locale(locale const&, locale const&, int); + locale(locale const&, std::string const&, int); + locale(std::string const&); + + std::string name() const; + + bool has_facet(locale::id const&); + + int operator=(locale const&); + void operator==(locale const&); + + ~locale(); + }; +}; // namespace __1 +}; // namespace std \ No newline at end of file diff --git a/include/stdalign.h b/include/stdalign.h new file mode 100644 index 0000000..1520c38 --- /dev/null +++ b/include/stdalign.h @@ -0,0 +1,128 @@ +/* A substitute for ISO C11 . + + Copyright 2011-2019 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +/* Written by Paul Eggert and Bruno Haible. */ + +#ifndef _GL_STDALIGN_H +#define _GL_STDALIGN_H + +/* ISO C11 for platforms that lack it. + + References: + ISO C11 (latest free draft + ) + sections 6.5.3.4, 6.7.5, 7.15. + C++11 (latest free draft + ) + section 18.10. */ + +/* alignof (TYPE), also known as _Alignof (TYPE), yields the alignment + requirement of a structure member (i.e., slot or field) that is of + type TYPE, as an integer constant expression. + + This differs from GCC's __alignof__ operator, which can yield a + better-performing alignment for an object of that type. For + example, on x86 with GCC, __alignof__ (double) and __alignof__ + (long long) are 8, whereas alignof (double) and alignof (long long) + are 4 unless the option '-malign-double' is used. + + The result cannot be used as a value for an 'enum' constant, if you + want to be portable to HP-UX 10.20 cc and AIX 3.2.5 xlc. + + Include for offsetof. */ +#include + +/* FreeBSD 9.1 , included by and lots of other + standard headers, defines conflicting implementations of _Alignas + and _Alignof that are no better than ours; override them. */ +#undef _Alignas +#undef _Alignof + +/* GCC releases before GCC 4.9 had a bug in _Alignof. See GCC bug 52023 + . */ +#if (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 || \ + (defined __GNUC__ && __GNUC__ < 4 + (__GNUC_MINOR__ < 9))) +#ifdef __cplusplus +#if 201103 <= __cplusplus +#define _Alignof(type) alignof(type) +#else +template +struct __alignof_helper { + char __a; + __t __b; +}; +#define _Alignof(type) offsetof(__alignof_helper, __b) +#endif +#else +#define _Alignof(type) \ + offsetof( \ + struct { \ + char __a; \ + type __b; \ + }, \ + __b) +#endif +#endif +#if !(defined __cplusplus && 201103 <= __cplusplus) +#define alignof _Alignof +#endif +#define __alignof_is_defined 1 + +/* alignas (A), also known as _Alignas (A), aligns a variable or type + to the alignment A, where A is an integer constant expression. For + example: + + int alignas (8) foo; + struct s { int a; int alignas (8) bar; }; + + aligns the address of FOO and the offset of BAR to be multiples of 8. + + A should be a power of two that is at least the type's alignment + and at most the implementation's alignment limit. This limit is + 2**28 on typical GNUish hosts, and 2**13 on MSVC. To be portable + to MSVC through at least version 10.0, A should be an integer + constant, as MSVC does not support expressions such as 1 << 3. + To be portable to Sun C 5.11, do not align auto variables to + anything stricter than their default alignment. + + The following C11 requirements are not supported here: + + - If A is zero, alignas has no effect. + - alignas can be used multiple times; the strictest one wins. + - alignas (TYPE) is equivalent to alignas (alignof (TYPE)). + + */ + +#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 +#if defined __cplusplus && 201103 <= __cplusplus +#define _Alignas(a) alignas(a) +#elif ((defined __APPLE__ && defined __MACH__ ? 4 < __GNUC__ + (1 <= __GNUC_MINOR__) : __GNUC__) || \ + (__ia64 && (61200 <= __HP_cc || 61200 <= __HP_aCC)) || __ICC || 0x590 <= __SUNPRO_C || 0x0600 <= __xlC__) +#define _Alignas(a) __attribute__((__aligned__(a))) +#elif 1300 <= _MSC_VER +#define _Alignas(a) __declspec(align(a)) +#endif +#endif +#if ((defined _Alignas && !(defined __cplusplus && 201103 <= __cplusplus)) || \ + (defined __STDC_VERSION__ && 201112 <= __STDC_VERSION__)) +#define alignas _Alignas +#endif +#if defined alignas || (defined __cplusplus && 201103 <= __cplusplus) +#define __alignas_is_defined 1 +#endif + +#endif /* _GL_STDALIGN_H */ diff --git a/include/str.h b/include/str.h new file mode 100644 index 0000000..a9dc9b1 --- /dev/null +++ b/include/str.h @@ -0,0 +1,22 @@ +/** + * @file str.h + * @brief Operations for strings. + */ + +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int strcmp(char const* str1, char const* str2); +u64 strlen(char const* str); +u128 strtoull(char const* str, char** strEnd, s32 base); + +char* strcpy(char* dest, char const* src); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..0757247 --- /dev/null +++ b/include/types.h @@ -0,0 +1,99 @@ +/** + * @file types.h + * @brief Defines common types. + */ + +#include +#include +#include +#include +#include + +#include "skyline/nx/result.h" + +#pragma once + +#define __int8 char +#define __int16 short +#define __int32 int +#define __int64 long long +#define _QWORD __int64 +#define _DWORD __int32 +#define _WORD __int16 +#define _BYTE char + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; +typedef __int128_t s128; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef __uint128_t u128; +typedef float f32; +typedef double f64; + +typedef unsigned char uchar; +typedef unsigned long ulong; +typedef u32 uint; + +// stores a result on a lot of OS-related functions +typedef u32 Result; +typedef u32 Handle; +typedef void (*ThreadFunc)(void*); + +#ifndef BIT +#define BIT(n) (1ULL << (n)) +#endif + +#ifndef PACKED +#define PACKED __attribute__((packed)) +#endif + +#ifndef NORETURN +#define NORETURN __attribute__((noreturn)) +#endif + +#ifndef WEAK +#define WEAK __attribute__((weak)) +#endif + +#ifndef ALIGNA +#define ALIGNA(x) __attribute__((aligned(x))) +#endif + +#ifndef IGNORE_ARG +#define IGNORE_ARG(x) (void)(x) +#endif + +#define NX_INLINE __attribute__((always_inline)) static inline + +#if __cplusplus >= 201402L +#define NX_CONSTEXPR NX_INLINE constexpr +#else +#define NX_CONSTEXPR NX_INLINE +#endif + +#define INVALID_HANDLE ((Handle)0) + +#define PAGE_SIZE 0x1000 + +#ifndef ALIGN_UP +#define ALIGN_UP(x, a) (((x) + ((a)-1)) & ~((a)-1)) +#endif + +#ifndef ALIGN_DOWN +#define ALIGN_DOWN(x, a) ((unsigned long)(x) & ~(((unsigned long)(a)) - 1)) +#endif + +#define R_ERRORONFAIL(r) \ + if (R_FAILED(r)) *((Result*)0x69) = r; + +#define R_UNLESS(expr, res) \ + ({ \ + if (!(expr)) { \ + return static_cast(res); \ + } \ + }) diff --git a/linkerscripts/application.ld b/linkerscripts/application.ld new file mode 100644 index 0000000..a84ba53 --- /dev/null +++ b/linkerscripts/application.ld @@ -0,0 +1,172 @@ +OUTPUT_FORMAT(elf64-littleaarch64) +OUTPUT_ARCH(aarch64) + +PHDRS +{ + text PT_LOAD FLAGS(5); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(6); + dynamic PT_DYNAMIC; +} + +SECTIONS +{ + PROVIDE(__start__ = 0x0); + . = __start__; + __code_start__ = .; + + /* App code */ + .text : { + HIDDEN(__text_start__ = .); + KEEP (*(.text.crt0)) + *(.text .text.*) + HIDDEN(__text_end__ = .); + } :text + + /* Trampoline and stuffs */ + .plt : { *(.plt .plt.*) } :text + __code_end__ = .; + + /* Read-only sections */ + . = ALIGN(0x1000); + + /* App name */ + .module_name : { KEEP (*(.rodata.module_name)) } :rodata + + /* Make sure everything is aligned */ + . = ALIGN(8); + + /* App rodata */ + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* All the symbols needed for relocation lookup */ + .hash : { *(.hash) } :rodata + .gnu.hash : { *(.gnu.hash) } :rodata + .dynsym : { *(.dynsym .dynsym.*) } :rodata + .dynstr : { *(.dynstr .dynstr.*) } :rodata + + __rel_dyn_start__ = .; + .rel.dyn : { *(.rel.dyn) } :rodata + __rel_dyn_end__ = .; + + __rela_dyn_start__ = .; + .rela.dyn : { *(.rela.dyn) } :rodata + __rela_dyn_end__ = .; + + __rel_plt_start__ = .; + .rel.plt : { *(.rel.plt) } :rodata + __rel_plt_end__ = .; + + __rela_plt_start__ = .; + .rela.plt : { *(.rela.plt) } :rodata + __rela_plt_end__ = .; + + /* All exception handling sections */ + .gcc_except_table : { *(.gcc_except_table .gcc_except_table.*) } :rodata + .eh_frame_hdr : { + HIDDEN(__eh_frame_hdr_start__ = .); + *(.eh_frame_hdr) + HIDDEN(__eh_frame_hdr_end__ = .); + } :rodata + .eh_frame : { KEEP (*(.eh_frame)) } :rodata + + /* Misc .rodata stuffs (build-id, ect) */ + .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata + + /* Read-write sections */ + . = ALIGN(0x1000); + + /* App data */ + .data : { + *(.data .data.*) + } :data + + /* This section should be made read only after relocation but in practice we will not do that */ + .data.rela.ro : { + *(.data.rela.ro.local*) + *(.data.rela.ro .data.rela.ro.*) + } :data + + /* This section should be made read only after relocation but in practice we will not do that */ + .data.rel.ro : { + *(.data.rel.ro.local*) + *(.data.rel.ro .data.rel.ro.*) + } :data + + /* All GOT sections */ + __got_start__ = .; + .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } :data + __got_end__ = .; + + /* The dynamic section as we need it to be stored in the binary */ + .dynamic : { + HIDDEN(__dynamic_start__ = .); + *(.dynamic) + } :data :dynamic + + /* Align for .init_array/.fini_array */ + . = ALIGN(8); + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start__ = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end__ = .); + } :data + + /* App init array */ + .init_array : { + PROVIDE (__init_array_start__ = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end__ = .); + } :data + + /* App fini array */ + .fini_array : { + PROVIDE (__fini_array_start__ = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*))) + KEEP (*(.fini_array)) + PROVIDE (__fini_array_end__ = .); + } :data + + /* Thread Local sections */ + + .tdata : { + __tdata_align_abs__ = ABSOLUTE(.); + __tdata_start__ = .; + *(.tdata .tdata.*) + __tdata_end__ = .; + } :data + + .tbss : { + __tbss_align_abs__ = ABSOLUTE(.); + __tbss_start__ = .; + *(.tbss .tbss.*) + *(.tcommon) + __tbss_end__ = .; + } :data + + /* BSS section */ + . = ALIGN(0x1000); + + .bss : { + HIDDEN(__bss_start__ = .); + *(.bss .bss.*) + *(COMMON) + . = ALIGN(8); + HIDDEN(__bss_end__ = .); + . = ALIGN(0x1000); + } :data + + __end__ = ABSOLUTE(.); + + HIDDEN(__argdata__ = .); + + /DISCARD/ : { + /* No need of the interpreter */ + *(.interp) + } +} \ No newline at end of file diff --git a/nso.mk b/nso.mk new file mode 100644 index 0000000..517899f --- /dev/null +++ b/nso.mk @@ -0,0 +1,178 @@ + +LINKERSCRIPTS := linkerscripts + +#--------------------------------------------------------------------------------- +#.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- + +# NOTE: TARGET and BUILD are now passed from parent Makefile +TARGET ?= $(notdir $(CURDIR)) +BUILD ?= build +SOURCES := source $(filter-out %.c %.cpp %.s,$(wildcard source/* source/*/* source/*/*/* source/*/*/*/*)) +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +# change to O3 or even Ofast if nothing breaks for final release +# also try enabling LTO to get max perf +CFLAGS := -g -O3 -flto -Wall -Wno-multichar -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ -DNOLOG + +CXXFLAGS := $(PORTAL) $(CFLAGS) -std=gnu++20 -fpermissive -fno-rtti -fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables -enable-libstdcxx-allocator=new -fpermissive + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=../switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--version-script=$(TOPDIR)/exported.txt -Wl,-init=__custom_init -Wl,-fini=__custom_fini -Wl,--export-dynamic -nodefaultlibs + +LIBS := -lstdc++ -u malloc -lzstd + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) +export OUTPUTNSO := $(CURDIR)/subsdk9 + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR ?= $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) \ + -I$(PORTLIBS)/include/freetype2 + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- + +all: $(BUILD) + +$(BUILD): + @echo $(LDFLAGS) + @echo "${SOURCES}" + @echo "${CPPFILES}" + @echo "${CXXFLAGS}" + @[ -d $@ ] || mkdir -p $@ + $(MAKE) -C $(BUILD) -f $(CURDIR)/$(MAKE_NSO) + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr build* *.nso *.elf subsdk9 + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +%.nso: %.elf + elf2nso $< $(OUTPUTNSO) + @echo built ... $(notdir $@) + +%.nro: %.elf + @elf2nro $< $@ $(NROFLAGS) + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +all : $(OUTPUT).nso + +$(OUTPUT).elf : $(OFILES) + + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/source/Portal/main_patch.cpp b/source/Portal/main_patch.cpp new file mode 100644 index 0000000..ab4da5e --- /dev/null +++ b/source/Portal/main_patch.cpp @@ -0,0 +1,163 @@ +#if defined(PORTAL) && defined(PORTAL2) + #error You cannot define PORTAL and PORTAL2 at the same time! +#endif + +#if !defined(PORTAL) && !defined(PORTAL2) + #error You must define if you are using PORTAL or PORTAL2! +#endif + +#include "skyline/inlinehook/And64InlineHook.hpp" +#include "skyline/utils/cpputils.hpp" +#include "skyline/inlinehook/memcpy_controlled.hpp" +#include "nn/fs.h" +#include "nn/account.h" +#include "nn/nifm.h" +#include "nn/ro.h" +#include +#include +#include + +uintptr_t TextRegionOffset = 0; + +ptrdiff_t returnInstructionOffset(uintptr_t LR) { + return LR - TextRegionOffset; +} + +extern "C" { + FILE* fopen ( const char * filename, const char * mode ); + FILE* fopen_nx ( const char * filename, const char * mode ); + int fseek( FILE * stream, long offset, int origin ); + size_t fread( void * buffer, size_t size, size_t count, FILE * stream ); + int fclose( FILE * stream ); + long ftell( FILE * stream ); + #ifdef PORTAL2 + void* _ZN2nn3mem17StandardAllocator8AllocateEm(void* _this, size_t size); + #endif +} + +namespace nn { + + namespace nifm { + uint64_t (*Initialize_original)(); + uint64_t Initialize_hook() { + return 0x27C; + } + } +} + +char * strtolower( char * dest, const char * src, size_t n ) +{ + if( !n ) + return 0; + + else + { + n += 1; + char * d = dest; + while ( *src && --n > 0) + *d++ = tolower(*src++); + + return dest; + } +} + +bool formatPath (const char* path, char* filepath, bool NXCONTENT) { + char temp[256] = ""; + if (!NXCONTENT) { + if (!strncmp(path, "/", 1)) + sprintf(&temp[0], "rom:%s", path); + else + sprintf(&temp[0], "rom:/%s", path); + strtolower(filepath, &temp[0], strlen(&temp[0])); + } + else { + if (!strncmp(path, "/", 1)) + sprintf(&temp[0], "rom:/nxcontent%s", path); + else + sprintf(&temp[0], "rom:/nxcontent/%s", path); + strtolower(filepath, &temp[0], strlen(&temp[0])); + } + return true; +} + +struct fopen2Struct { + uint64_t buffer_size; + void* buffer; +}; + +void (*fopen2_original)(fopen2Struct* _struct, void* x1, const char* path); +void fopen2_hook(fopen2Struct* _struct, void* x1, const char* path){ + char filepath[256] = ""; + bool formatted = formatPath(path, &filepath[0], false); + if (!formatted) + return fopen2_original(_struct, x1, path); + + FILE* file = fopen(filepath, "r"); + if(!file) + return fopen2_original(_struct, x1, path); + + int32_t size = 0; + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + #ifdef PORTAL + void* new_buffer = malloc(size); + #endif + #ifdef PORTAL2 + void* Allocator = (void*)(TextRegionOffset+0x861078); + void* new_buffer = _ZN2nn3mem17StandardAllocator8AllocateEm(Allocator, size); + #endif + + fread(new_buffer, size, 1, file); + fclose(file); + + _struct->buffer_size = size; + _struct->buffer = new_buffer; +} + +char filepath[256] = ""; + +FILE* (*fopen_nx_original)(const char* path, const char* mode); +FILE* fopen_nx_hook(const char* path, const char* mode) { + nn::fs::FileHandle filehandle; + memset(&filepath[0], 0, strlen(&filepath[0])); + bool formatted = formatPath(path, &filepath[0], true); + if (!formatted) + return fopen_nx_original(path, mode); + + if(R_FAILED(nn::fs::OpenFile(&filehandle, &filepath[0], nn::fs::OpenMode_Read))) { + memset(&filepath[0], 0, strlen(&filepath[0])); + bool formatted = formatPath(path, &filepath[0], false); + if (!formatted) + return fopen_nx_original(path, mode); + if(R_FAILED(nn::fs::OpenFile(&filehandle, &filepath[0], nn::fs::OpenMode_Read))) + return fopen_nx_original(path, mode); + } + nn::fs::CloseFile(filehandle); + return fopen(&filepath[0], mode); +} + +void Portal_main() +{ + TextRegionOffset = (uintptr_t)skyline::utils::getRegionAddress(skyline::utils::region::Text); + + #ifdef PORTAL + A64HookFunction((void*)(TextRegionOffset + 0x7D978), reinterpret_cast(fopen2_hook), (void**)&fopen2_original); + #endif + #ifdef PORTAL2 + A64HookFunction((void*)(TextRegionOffset + 0x182F50), reinterpret_cast(fopen2_hook), (void**)&fopen2_original); + #endif + + + A64HookFunction((void**)&fopen_nx, reinterpret_cast(fopen_nx_hook), (void**)&fopen_nx_original); + + #ifdef PORTAL2 + #ifdef PDEBUG + //Hook needed to run game with connected internet and blocked Nintendo servers + + A64HookFunction((void**)&nn::nifm::Initialize, reinterpret_cast(nn::nifm::Initialize_hook), (void**)&nn::nifm::Initialize_original); + #endif + #endif + +} diff --git a/source/crt0.s b/source/crt0.s new file mode 100644 index 0000000..5d99cc2 --- /dev/null +++ b/source/crt0.s @@ -0,0 +1,60 @@ +.section ".text.crt0","ax" +.global __module_start +.extern __nx_module_runtime + +__module_start: + b startup + .word __nx_mod0 - __module_start + +.org __module_start+0x80 +startup: + // save lr + mov x7, x30 + + // get aslr base + bl +4 + sub x6, x30, #0x88 + + // context ptr and main thread handle + mov x5, x0 + mov x4, x1 +bssclr_start: + mov x27, x7 + mov x25, x5 + mov x26, x4 + + // clear .bss + adrp x0, __bss_start__ + adrp x1, __bss_end__ + add x0, x0, #:lo12:__bss_start__ + add x1, x1, #:lo12:__bss_end__ + sub x1, x1, x0 // calculate size + add x1, x1, #7 // round up to 8 + bic x1, x1, #7 + +bss_loop: + str xzr, [x0], #8 + subs x1, x1, #8 + bne bss_loop + + // store stack pointer + mov x1, sp + adrp x0, __stack_top + str x1, [x0, #:lo12:__stack_top] + + // initialize system + mov x0, x25 + mov x1, x26 + mov x2, x27 + .word deadbeef + +.section ".rodata.mod0" +.global __nx_mod0 +__nx_mod0: + .ascii "MOD0" + .word __dynamic_start__ - __nx_mod0 + .word __bss_start__ - __nx_mod0 + .word __bss_end__ - __nx_mod0 + .word 0 + .word 0 + .word __nx_module_runtime - __nx_mod0 diff --git a/source/crti.c b/source/crti.c new file mode 100644 index 0000000..aa322dc --- /dev/null +++ b/source/crti.c @@ -0,0 +1,22 @@ +#include "types.h" + +// forward declare main +void skyline_init(); + +typedef void (*func_ptr)(void); + +extern func_ptr __preinit_array_start__[0], __preinit_array_end__[0]; +extern func_ptr __init_array_start__[0], __init_array_end__[0]; +extern func_ptr __fini_array_start__[0], __fini_array_end__[0]; + +void __custom_init(void) { + for (func_ptr* func = __preinit_array_start__; func != __preinit_array_end__; func++) (*func)(); + + for (func_ptr* func = __init_array_start__; func != __init_array_end__; func++) (*func)(); + + skyline_init(); +} + +void __custom_fini(void) { + for (func_ptr* func = __fini_array_start__; func != __fini_array_end__; func++) (*func)(); +} \ No newline at end of file diff --git a/source/cxa.c b/source/cxa.c new file mode 100644 index 0000000..bdd03d1 --- /dev/null +++ b/source/cxa.c @@ -0,0 +1,3 @@ +#include "cxa.h" + +void __cxa_atexit() {} \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..c0082d4 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,25 @@ +#include "main.hpp" + +#include "skyline/utils/cpputils.hpp" +#include "skyline/utils/ipc.hpp" + +void Portal_main(); + +void skyline_main() { + // populate our own process handle + Handle h; + skyline::utils::Ipc::getOwnProcessHandle(&h); + envSetOwnProcessHandle(h); + + // init hooking setup + A64HookInit(); + + Portal_main(); +} + +extern "C" void skyline_init() { + skyline::utils::init(); + virtmemSetup(); // needed for libnx JIT + + skyline_main(); +} diff --git a/source/module.cpp b/source/module.cpp new file mode 100644 index 0000000..0a745e0 --- /dev/null +++ b/source/module.cpp @@ -0,0 +1,14 @@ +#define MODULE_NAME "Skyline" +#define MODULE_NAME_LEN 7 + +// rtld working object +__attribute__((section(".bss"))) char __nx_module_runtime[0xD0]; + +struct ModuleName { + int unknown; + int name_lengh; + char name[MODULE_NAME_LEN + 1]; +}; + +__attribute__((section(".rodata.module_name"))) +const ModuleName module_name = {.unknown = 0, .name_lengh = MODULE_NAME_LEN, .name = MODULE_NAME}; diff --git a/source/nvn/pfnc.cpp b/source/nvn/pfnc.cpp new file mode 100644 index 0000000..1c56c3a --- /dev/null +++ b/source/nvn/pfnc.cpp @@ -0,0 +1,80 @@ +#include "nvn/pfnc.h" + +NVN_DEFPROC(nvnDeviceBuilderGetFlags); +NVN_DEFPROC(nvnDeviceBuilderSetDefaults); +NVN_DEFPROC(nvnDeviceBuilderSetFlags); + +NVN_DEFPROC(nvnDeviceInitialize); +NVN_DEFPROC(nvnDeviceFinalize); +NVN_DEFPROC(nvnDeviceGetInteger); +NVN_DEFPROC(nvnDeviceGetImageHandle); +NVN_DEFPROC(nvnDeviceGetDepthMode); +NVN_DEFPROC(nvnDeviceGetProcAddress); + +NVN_DEFPROC(nvnSyncInitalize); +NVN_DEFPROC(nvnSyncFinalize); +NVN_DEFPROC(nvnSyncWait); +NVN_DEFPROC(nvnSyncSetDebugLabel); + +NVN_DEFPROC(nvnWindowInitialize); +NVN_DEFPROC(nvnWindowFinalize); +NVN_DEFPROC(nvnWindowSetCrop); +NVN_DEFPROC(nvnWindowPresentInterval); +NVN_DEFPROC(nvnWindowSetPresentInterval); +NVN_DEFPROC(nvnWindowSetDebugLabel); + +NVN_DEFPROC(nvnQueueInitalize); +NVN_DEFPROC(nvnQueueFinalize); +NVN_DEFPROC(nvnQueueFlush); +NVN_DEFPROC(nvnQueueFenceSync); +NVN_DEFPROC(nvnQueueSubmitCommands); +NVN_DEFPROC(nvnQueueWaitSync); +NVN_DEFPROC(nvnQueuePresentTexure); +NVN_DEFPROC(nvnQueueGetError); +NVN_DEFPROC(nvnQueueGetTotalCommandMemoryUsed); +NVN_DEFPROC(nvnQueueGetTotalComputeMemoryUsed); +NVN_DEFPROC(nvnQueueGetTotalControlMemoryUsed); +NVN_DEFPROC(nvnQueueSetDebugLabel); + +NVN_DEFPROC(nvnSamplerBuilderSetMaxAnisotropy); + +void nvnInit(NVNdevice* device) { + nvnDeviceGetProcAddress = + reinterpret_cast(nvnBootstrapLoader("nvnDeviceGetProcAddress")); + + NVN_GETPROCADDR(nvnDeviceBuilderGetFlags); + NVN_GETPROCADDR(nvnDeviceBuilderSetDefaults); + NVN_GETPROCADDR(nvnDeviceBuilderSetFlags); + + NVN_GETPROCADDR(nvnDeviceInitialize); + NVN_GETPROCADDR(nvnDeviceFinalize); + NVN_GETPROCADDR(nvnDeviceGetInteger); + NVN_GETPROCADDR(nvnDeviceGetImageHandle); + NVN_GETPROCADDR(nvnDeviceGetDepthMode); + + NVN_GETPROCADDR(nvnSyncInitalize); + NVN_GETPROCADDR(nvnSyncFinalize); + NVN_GETPROCADDR(nvnSyncWait); + NVN_GETPROCADDR(nvnSyncSetDebugLabel); + + NVN_GETPROCADDR(nvnWindowInitialize); + NVN_GETPROCADDR(nvnWindowFinalize); + NVN_GETPROCADDR(nvnWindowSetCrop); + NVN_GETPROCADDR(nvnWindowPresentInterval); + NVN_GETPROCADDR(nvnWindowSetPresentInterval); + NVN_GETPROCADDR(nvnWindowSetDebugLabel); + + NVN_GETPROCADDR(nvnQueueInitalize); + NVN_GETPROCADDR(nvnQueueFinalize); + NVN_GETPROCADDR(nvnQueueFlush); + NVN_GETPROCADDR(nvnQueueFenceSync); + NVN_GETPROCADDR(nvnQueueSubmitCommands); + NVN_GETPROCADDR(nvnQueueWaitSync); + NVN_GETPROCADDR(nvnQueueGetError); + NVN_GETPROCADDR(nvnQueueGetTotalCommandMemoryUsed); + NVN_GETPROCADDR(nvnQueueGetTotalComputeMemoryUsed); + NVN_GETPROCADDR(nvnQueueGetTotalControlMemoryUsed); + NVN_GETPROCADDR(nvnQueuePresentTexure); + + NVN_GETPROCADDR(nvnSamplerBuilderSetMaxAnisotropy); +} \ No newline at end of file diff --git a/source/operator.cpp b/source/operator.cpp new file mode 100644 index 0000000..7c1a9c1 --- /dev/null +++ b/source/operator.cpp @@ -0,0 +1,9 @@ +#include "operator.h" + +// Nintendo didn't implement these for some reason +/*void* operator new(std::size_t size, void* ptr){ + return ptr; +} +void* operator new[](std::size_t size, void* ptr){ + return ptr; +}*/ \ No newline at end of file diff --git a/source/skyline/inlinehook/And64InlineHook.cpp b/source/skyline/inlinehook/And64InlineHook.cpp new file mode 100644 index 0000000..64458e3 --- /dev/null +++ b/source/skyline/inlinehook/And64InlineHook.cpp @@ -0,0 +1,718 @@ +/* + * @date : 2018/04/18 + * @author : Rprop (r_prop@outlook.com) + * https://github.com/Rprop/And64InlineHook + */ +/* + MIT License + + Copyright (c) 2018 Rprop (r_prop@outlook.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +#define __STDC_FORMAT_MACROS +#include + +#if defined(__aarch64__) + +#include "nn/os.h" +#include "skyline/inlinehook/And64InlineHook.hpp" +#include "skyline/utils/cpputils.hpp" + +#define A64_MAX_INSTRUCTIONS 5 +#define A64_MAX_REFERENCES (A64_MAX_INSTRUCTIONS * 2) +#define A64_NOP 0xd503201fu +typedef uint32_t* __restrict* __restrict instruction; +typedef struct { + struct fix_info { + uint32_t* bprx; + uint32_t* bprw; + uint32_t ls; // left-shift counts + uint32_t ad; // & operand + }; + struct insns_info { + union { + uint64_t insu; + int64_t ins; + void* insp; + }; + fix_info fmap[A64_MAX_REFERENCES]; + }; + int64_t basep; + int64_t endp; + insns_info dat[A64_MAX_INSTRUCTIONS]; + + public: + inline bool is_in_fixing_range(const int64_t absolute_addr) { + return absolute_addr >= this->basep && absolute_addr < this->endp; + } + inline intptr_t get_ref_ins_index(const int64_t absolute_addr) { + return static_cast((absolute_addr - this->basep) / sizeof(uint32_t)); + } + inline intptr_t get_and_set_current_index(uint32_t* __restrict inp, uint32_t* __restrict outp) { + intptr_t current_idx = this->get_ref_ins_index(reinterpret_cast(inp)); + this->dat[current_idx].insp = outp; + return current_idx; + } + inline void reset_current_ins(const intptr_t idx, uint32_t* __restrict outp) { this->dat[idx].insp = outp; } + void insert_fix_map(const intptr_t idx, uint32_t* bprw, uint32_t* bprx, uint32_t ls = 0u, + uint32_t ad = 0xffffffffu) { + for (auto& f : this->dat[idx].fmap) { + if (f.bprw == NULL) { + f.bprw = bprw; + f.bprx = bprx; + f.ls = ls; + f.ad = ad; + return; + } // if + } + // What? GGing.. + } + void process_fix_map(const intptr_t idx) { + for (auto& f : this->dat[idx].fmap) { + if (f.bprw == NULL) break; + *(f.bprw) = + *(f.bprx) | (((int32_t(this->dat[idx].ins - reinterpret_cast(f.bprx)) >> 2) << f.ls) & f.ad); + f.bprw = NULL; + f.bprx = NULL; + } + } +} context; + +//------------------------------------------------------------------------- + +static bool __fix_branch_imm(instruction inprwp, instruction inprxp, instruction outprw, instruction outprx, + context* ctxp) { + static constexpr uint32_t mbits = 6u; + static constexpr uint32_t mask = 0xfc000000u; // 0b11111100000000000000000000000000 + static constexpr uint32_t rmask = 0x03ffffffu; // 0b00000011111111111111111111111111 + static constexpr uint32_t op_b = 0x14000000u; // "b" ADDR_PCREL26 + static constexpr uint32_t op_bl = 0x94000000u; // "bl" ADDR_PCREL26 + + const uint32_t ins = *(*inprwp); + const uint32_t opc = ins & mask; + switch (opc) { + case op_b: + case op_bl: { + intptr_t current_idx = ctxp->get_and_set_current_index(*inprxp, *outprx); + int64_t absolute_addr = reinterpret_cast(*inprxp) + + (static_cast(ins << mbits) >> (mbits - 2u)); // sign-extended + int64_t new_pc_offset = + static_cast(absolute_addr - reinterpret_cast(*outprx)) >> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + // whether the branch should be converted to absolute jump + if (!special_fix_type && llabs(new_pc_offset) >= (rmask >> 1)) { + bool b_aligned = (reinterpret_cast(*outprx + 2) & 7u) == 0u; + if (opc == op_b) { + if (b_aligned != true) { + (*outprw)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outprx)); + ++(*outprw); + } // if + (*outprw)[0] = 0x58000051u; // LDR X17, #0x8 + (*outprw)[1] = 0xd61f0220u; // BR X17 + memcpy(*outprw + 2, &absolute_addr, sizeof(absolute_addr)); + *outprx += 4; + *outprw += 4; + } else { + if (b_aligned == true) { + (*outprw)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outprx)); + (*outprw)++; + } // if + (*outprw)[0] = 0x58000071u; // LDR X17, #12 + (*outprw)[1] = 0x1000009eu; // ADR X30, #16 + (*outprw)[2] = 0xd61f0220u; // BR X17 + memcpy(*outprw + 3, &absolute_addr, sizeof(absolute_addr)); + *outprw += 5; + *outprx += 5; + } // if + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); + if (ref_idx <= current_idx) { + new_pc_offset = + static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outprx)) >> 2; + } else { + ctxp->insert_fix_map(ref_idx, *outprw, *outprx, 0u, rmask); + new_pc_offset = 0; + } // if + } // if + + (*outprw)[0] = opc | (new_pc_offset & ~mask); + ++(*outprw); + ++(*outprx); + } // if + + ++(*inprxp); + ++(*inprwp); + return ctxp->process_fix_map(current_idx), true; + } + } + return false; +} + +//------------------------------------------------------------------------- + +static bool __fix_cond_comp_test_branch(instruction inprwp, instruction inprxp, instruction outprw, instruction outprx, + context* ctxp) { + static constexpr uint32_t lsb = 5u; + static constexpr uint32_t lmask01 = 0xff00001fu; // 0b11111111000000000000000000011111 + static constexpr uint32_t mask0 = 0xff000010u; // 0b11111111000000000000000000010000 + static constexpr uint32_t op_bc = 0x54000000u; // "b.c" ADDR_PCREL19 + static constexpr uint32_t mask1 = 0x7f000000u; // 0b01111111000000000000000000000000 + static constexpr uint32_t op_cbz = 0x34000000u; // "cbz" Rt, ADDR_PCREL19 + static constexpr uint32_t op_cbnz = 0x35000000u; // "cbnz" Rt, ADDR_PCREL19 + static constexpr uint32_t lmask2 = 0xfff8001fu; // 0b11111111111110000000000000011111 + static constexpr uint32_t mask2 = 0x7f000000u; // 0b01111111000000000000000000000000 + static constexpr uint32_t op_tbz = + 0x36000000u; // 0b00110110000000000000000000000000 "tbz" Rt, BIT_NUM, ADDR_PCREL14 + static constexpr uint32_t op_tbnz = + 0x37000000u; // 0b00110111000000000000000000000000 "tbnz" Rt, BIT_NUM, ADDR_PCREL14 + + const uint32_t ins = *(*inprwp); + uint32_t lmask = lmask01; + if ((ins & mask0) != op_bc) { + uint32_t opc = ins & mask1; + if (opc != op_cbz && opc != op_cbnz) { + opc = ins & mask2; + if (opc != op_tbz && opc != op_tbnz) { + return false; + } // if + lmask = lmask2; + } // if + } // if + + intptr_t current_idx = ctxp->get_and_set_current_index(*inprxp, *outprx); + int64_t absolute_addr = reinterpret_cast(*inprxp) + ((ins & ~lmask) >> (lsb - 2u)); + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outprx)) >> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + if (!special_fix_type && llabs(new_pc_offset) >= (~lmask >> (lsb + 1))) { + if ((reinterpret_cast(*outprx + 4) & 7u) != 0u) { + (*outprw)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, *outprx); + + (*outprx)++; + (*outprw)++; + } // if + (*outprw)[0] = (((8u >> 2u) << lsb) & ~lmask) | (ins & lmask); // B.C #0x8 + (*outprw)[1] = 0x14000005u; // B #0x14 + (*outprw)[2] = 0x58000051u; // LDR X17, #0x8 + (*outprw)[3] = 0xd61f0220u; // BR X17 + memcpy(*outprw + 4, &absolute_addr, sizeof(absolute_addr)); + *outprw += 6; + *outprx += 6; + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outprx)) >> 2; + } else { + ctxp->insert_fix_map(ref_idx, *outprw, *outprx, lsb, ~lmask); + new_pc_offset = 0; + } // if + } // if + + (*outprw)[0] = (static_cast(new_pc_offset << lsb) & ~lmask) | (ins & lmask); + ++(*outprw); + ++(*outprx); + } // if + + ++(*inprxp); + ++(*inprwp); + return ctxp->process_fix_map(current_idx), true; +} + +//------------------------------------------------------------------------- + +static bool __fix_loadlit(instruction inprwp, instruction inprxp, instruction outprw, instruction outprx, + context* ctxp) { + const uint32_t ins = *(*inprwp); + + // memory prefetch("prfm"), just skip it + // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897420050.html + if ((ins & 0xff000000u) == 0xd8000000u) { + ctxp->process_fix_map(ctxp->get_and_set_current_index(*inprxp, *outprx)); + ++(*inprwp); + ++(*inprxp); + return true; + } // if + + static constexpr uint32_t msb = 8u; + static constexpr uint32_t lsb = 5u; + static constexpr uint32_t mask_30 = 0x40000000u; // 0b01000000000000000000000000000000 + static constexpr uint32_t mask_31 = 0x80000000u; // 0b10000000000000000000000000000000 + static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 + static constexpr uint32_t mask_ldr = 0xbf000000u; // 0b10111111000000000000000000000000 + static constexpr uint32_t op_ldr = + 0x18000000u; // 0b00011000000000000000000000000000 "LDR Wt/Xt, label" | ADDR_PCREL19 + static constexpr uint32_t mask_ldrv = 0x3f000000u; // 0b00111111000000000000000000000000 + static constexpr uint32_t op_ldrv = + 0x1c000000u; // 0b00011100000000000000000000000000 "LDR St/Dt/Qt, label" | ADDR_PCREL19 + static constexpr uint32_t mask_ldrsw = 0xff000000u; // 0b11111111000000000000000000000000 + static constexpr uint32_t op_ldrsw = 0x98000000u; // "LDRSW Xt, label" | ADDR_PCREL19 | load register signed word + // LDR S0, #0 | 0b00011100000000000000000000000000 | 32-bit + // LDR D0, #0 | 0b01011100000000000000000000000000 | 64-bit + // LDR Q0, #0 | 0b10011100000000000000000000000000 | 128-bit + // INVALID | 0b11011100000000000000000000000000 | may be 256-bit + + uint32_t mask = mask_ldr; + uintptr_t faligned = (ins & mask_30) ? 7u : 3u; + if ((ins & mask_ldr) != op_ldr) { + mask = mask_ldrv; + if (faligned != 7u) faligned = (ins & mask_31) ? 15u : 3u; + if ((ins & mask_ldrv) != op_ldrv) { + if ((ins & mask_ldrsw) != op_ldrsw) { + return false; + } // if + mask = mask_ldrsw; + faligned = 7u; + } // if + } // if + + intptr_t current_idx = ctxp->get_and_set_current_index(*inprxp, *outprx); + int64_t absolute_addr = + reinterpret_cast(*inprxp) + ((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u); + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outprx)) >> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + // special_fix_type may encounter issue when there are mixed data and code + if (special_fix_type || + (llabs(new_pc_offset) + (faligned + 1u - 4u) / 4u) >= (~lmask >> (lsb + 1))) { // inaccurate, but it works + while ((reinterpret_cast(*outprx + 2) & faligned) != 0u) { + *(*outprw)++ = A64_NOP; + (*outprx)++; + } + ctxp->reset_current_ins(current_idx, *outprx); + + // Note that if memory at absolute_addr is writeable (non-const), we will fail to fetch it. + // And what's worse, we may unexpectedly overwrite something if special_fix_type is true... + uint32_t ns = static_cast((faligned + 1) / sizeof(uint32_t)); + (*outprw)[0] = (((8u >> 2u) << lsb) & ~mask) | (ins & lmask); // LDR #0x8 + (*outprw)[1] = 0x14000001u + ns; // B #0xc + memcpy(*outprw + 2, reinterpret_cast(absolute_addr), faligned + 1); + *outprw += 2 + ns; + *outprx += 2 + ns; + } else { + faligned >>= 2; // new_pc_offset is shifted and 4-byte aligned + while ((new_pc_offset & faligned) != 0) { + *(*outprw)++ = A64_NOP; + (*outprx)++; + new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outprx)) >> 2; + } + ctxp->reset_current_ins(current_idx, *outprx); + + (*outprw)[0] = (static_cast(new_pc_offset << lsb) & ~mask) | (ins & lmask); + ++(*outprx); + ++(*outprw); + } // if + + ++(*inprxp); + ++(*inprwp); + return ctxp->process_fix_map(current_idx), true; +} + +//------------------------------------------------------------------------- + +static bool __fix_pcreladdr(instruction inprwp, instruction inprxp, instruction outprw, instruction outprx, + context* ctxp) { + // Load a PC-relative address into a register + // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897645644.html + static constexpr uint32_t msb = 8u; + static constexpr uint32_t lsb = 5u; + static constexpr uint32_t mask = 0x9f000000u; // 0b10011111000000000000000000000000 + static constexpr uint32_t rmask = 0x0000001fu; // 0b00000000000000000000000000011111 + static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 + static constexpr uint32_t fmask = 0x00ffffffu; // 0b00000000111111111111111111111111 + static constexpr uint32_t max_val = 0x001fffffu; // 0b00000000000111111111111111111111 + static constexpr uint32_t op_adr = 0x10000000u; // "adr" Rd, ADDR_PCREL21 + static constexpr uint32_t op_adrp = 0x90000000u; // "adrp" Rd, ADDR_ADRP + + const uint32_t ins = *(*inprwp); + intptr_t current_idx; + switch (ins & mask) { + case op_adr: { + current_idx = ctxp->get_and_set_current_index(*inprxp, *outprx); + int64_t lsb_bytes = static_cast(ins << 1u) >> 30u; + int64_t absolute_addr = reinterpret_cast(*inprxp) + + (((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes); + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outprx)); + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + if (!special_fix_type && llabs(new_pc_offset) >= (max_val >> 1)) { + if ((reinterpret_cast(*outprx + 2) & 7u) != 0u) { + (*outprw)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outprx)); + ++*(outprw); + } // if + + (*outprw)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 + (*outprw)[1] = 0x14000003u; // B #0xc + memcpy(*outprw + 2, &absolute_addr, sizeof(absolute_addr)); + *outprw += 4; + *outprx += 4; + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr & ~3ull); + if (ref_idx <= current_idx) { + new_pc_offset = + static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outprx)); + } else { + ctxp->insert_fix_map(ref_idx, *outprw, *outprx, lsb, fmask); + new_pc_offset = 0; + } // if + } // if + + // the lsb_bytes will never be changed, so we can use lmask to keep it + (*outprw)[0] = (static_cast(new_pc_offset << (lsb - 2u)) & fmask) | (ins & lmask); + ++(*outprw); + ++(*outprx); + } // if + } break; + case op_adrp: { + current_idx = ctxp->get_and_set_current_index(*inprxp, *outprw); + int32_t lsb_bytes = static_cast(ins << 1u) >> 30u; + int64_t absolute_addr = + (reinterpret_cast(*inprxp) & ~0xfffll) + + ((((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes) << 12); + skyline::logger::s_Instance->LogFormat("[And64InlineHook] ins = 0x%.8X, pc = %p, abs_addr = %p", ins, + *inprxp, reinterpret_cast(absolute_addr)); + if (ctxp->is_in_fixing_range(absolute_addr)) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr /* & ~3ull*/); + if (ref_idx > current_idx) { + // the bottom 12 bits of absolute_addr are masked out, + // so ref_idx must be less than or equal to current_idx! + skyline::logger::s_Instance->Log( + "[And64InlineHook] ref_idx must be less than or equal to current_idx!\n"); + } // if + + // *absolute_addr may be changed due to relocation fixing + skyline::logger::s_Instance->Log("What is the correct way to fix this?\n"); + *(*outprw)++ = ins; // 0x90000000u; + (*outprx)++; + } else { + if ((reinterpret_cast(*outprx + 2) & 7u) != 0u) { + (*outprw)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outprx)); + ++*(outprw); + } // if + + (*outprw)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 + (*outprw)[1] = 0x14000003u; // B #0xc + memcpy(*outprw + 2, &absolute_addr, sizeof(absolute_addr)); // potential overflow? + *outprw += 4; + *outprx += 4; + } // if + } break; + default: + return false; + } + + ctxp->process_fix_map(current_idx); + ++(*inprxp); + ++(*inprwp); + return true; +} + +#define __flush_cache(c, n) __builtin___clear_cache(reinterpret_cast(c), reinterpret_cast(c) + n) + +//------------------------------------------------------------------------- + +static void __fix_instructions(uint32_t* __restrict inprw, uint32_t* __restrict inprx, int32_t count, + uint32_t* __restrict outrwp, uint32_t* __restrict outrxp) { + context ctx; + ctx.basep = reinterpret_cast(inprx); + ctx.endp = reinterpret_cast(inprx + count); + memset(ctx.dat, 0, sizeof(ctx.dat)); + static_assert(sizeof(ctx.dat) / sizeof(ctx.dat[0]) == A64_MAX_INSTRUCTIONS, "please use A64_MAX_INSTRUCTIONS!"); +#ifndef NDEBUG + if (count > A64_MAX_INSTRUCTIONS) { + skyline::logger::s_Instance->Log("[And64InlineHook] too many fixing instructions!\n"); + } // if +#endif // NDEBUG + + uint32_t* const outprx_base = outrxp; + uint32_t* const outprw_base = outrwp; + + while (--count >= 0) { + if (__fix_branch_imm(&inprw, &inprx, &outrwp, &outrxp, &ctx)) continue; + if (__fix_cond_comp_test_branch(&inprw, &inprx, &outrwp, &outrxp, &ctx)) continue; + if (__fix_loadlit(&inprw, &inprx, &outrwp, &outrxp, &ctx)) continue; + if (__fix_pcreladdr(&inprw, &inprx, &outrwp, &outrxp, &ctx)) continue; + + // without PC-relative offset + ctx.process_fix_map(ctx.get_and_set_current_index(inprx, outrxp)); + *(outrwp++) = *(inprw++); + outrxp++; + inprx++; + } + + static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 + auto callback = reinterpret_cast(inprx); + auto pc_offset = static_cast(callback - reinterpret_cast(outrxp)) >> 2; + if (llabs(pc_offset) >= (mask >> 1)) { + if ((reinterpret_cast(outrxp + 2) & 7u) != 0u) { + outrwp[0] = A64_NOP; + ++outrxp; + ++outrwp; + } // if + outrwp[0] = 0x58000051u; // LDR X17, #0x8 + outrwp[1] = 0xd61f0220u; // BR X17 + *reinterpret_cast(outrwp + 2) = callback; + outrwp += 4; + outrxp += 4; + } else { + outrwp[0] = 0x14000000u | (pc_offset & mask); // "B" ADDR_PCREL26 + ++outrwp; + ++outrxp; + } // if + + const uintptr_t total = (outrxp - outprx_base) * sizeof(uint32_t); + __flush_cache(outprx_base, total); // necessary + __flush_cache(outprw_base, total); +} + +//------------------------------------------------------------------------- + +#define __attribute __attribute__ +#define aligned(x) __aligned__(x) +#define __intval(p) reinterpret_cast(p) +#define __uintval(p) reinterpret_cast(p) +#define __ptr(p) reinterpret_cast(p) +#define __page_size PAGE_SIZE +#define __page_align(n) __align_up(static_cast(n), __page_size) +#define __ptr_align(x) __ptr(__align_down(reinterpret_cast(x), __page_size)) +#define __align_up(x, n) (((x) + ((n)-1)) & ~((n)-1)) +#define __align_down(x, n) ((x) & -(n)) +#define __countof(x) static_cast(sizeof(x) / sizeof((x)[0])) // must be signed +#define __atomic_increase(p) __sync_add_and_fetch(p, 1) +#define __sync_cmpswap(p, v, n) __sync_bool_compare_and_swap(p, v, n) +typedef uint32_t insns_t[A64_MAX_BACKUPS][A64_MAX_INSTRUCTIONS * 10u]; + +constexpr size_t inline_hook_handler_size = 0xC; // correct if handler size changes +struct PACKED inline_hook_entry { + std::array handler; + const void* cur_handler; + const void* callback; + const void* trampoline; +}; + +constexpr size_t inline_hook_size = sizeof(inline_hook_entry); +constexpr size_t inline_hook_count = 0x1000; +constexpr size_t inline_hook_pool_size = inline_hook_size * inline_hook_count; + +//------------------------------------------------------------------------- + +static Jit __insns_jit; +static Jit __inline_hook_jit; +static nn::os::MutexType hookMutex; + +//------------------------------------------------------------------------- + +void A64HookInit() { + nn::os::InitializeMutex(&hookMutex, false, 0); + + // allocate normal hook JIT + Result rc = jitCreate(&__insns_jit, NULL, sizeof(insns_t)); + R_ERRORONFAIL(rc); + memset(__insns_jit.rw_addr, 0, __insns_jit.size); + rc = jitTransitionToExecutable(&__insns_jit); + R_ERRORONFAIL(rc); + + // search for applicable space for inline hook JIT + auto cur_searching_addr = + skyline::utils::g_MainTextAddr - inline_hook_pool_size; // start searching from right before .text + + MemoryInfo mem; + while (true) { + u32 page_info; + if (R_SUCCEEDED(svcQueryMemory(&mem, &page_info, cur_searching_addr)) && mem.type == MemType_Unmapped && + mem.size >= ALIGN_UP(inline_hook_pool_size, PAGE_SIZE)) { + break; + } + cur_searching_addr -= PAGE_SIZE; + } + + // allocate inline hook JIT + rc = jitCreate(&__inline_hook_jit, (void*)ALIGN_DOWN(mem.addr + mem.size - inline_hook_pool_size, PAGE_SIZE), + inline_hook_pool_size); + R_ERRORONFAIL(rc); +} + +//------------------------------------------------------------------------- + +static void FastAllocateTrampoline(uint32_t** rx, uint32_t** rw) { + static_assert((A64_MAX_INSTRUCTIONS * 10 * sizeof(uint32_t)) % 8 == 0, "8-byte align"); + static volatile int32_t __index = -1; + + uint32_t i = __atomic_increase(&__index); + insns_t* rwptr = (insns_t*)__insns_jit.rw_addr; + insns_t* rxptr = (insns_t*)__insns_jit.rx_addr; + *rw = (*rwptr)[i]; + *rx = (*rxptr)[i]; +} + +//------------------------------------------------------------------------- + +void* A64HookFunctionV(void* const symbol, void* const replace, void* const rxtr, void* const rwtr, + const uintptr_t rwx_size) { + static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 + + uint32_t *rxtrampoline = static_cast(rxtr), *rwtrampoline = static_cast(rwtr), + *original = static_cast(symbol); + + static_assert(A64_MAX_INSTRUCTIONS >= 5, "please fix A64_MAX_INSTRUCTIONS!"); + auto pc_offset = static_cast(__intval(replace) - __intval(symbol)) >> 2; + if (llabs(pc_offset) >= (mask >> 1)) { + skyline::inlinehook::ControlledPages control(original, 5 * sizeof(uint32_t)); + control.claim(); + + int32_t count = (reinterpret_cast(original + 2) & 7u) != 0u ? 5 : 4; + + original = (u32*)control.rw; + + if (rxtrampoline) { + if (rwx_size < count * 10u) { + skyline::logger::s_Instance->LogFormat( + "[And64InlineHook] rwx size is too small to hold %u bytes backup instructions!", count * 10u); + control.unclaim(); + return NULL; + } // if + __fix_instructions(original, (u32*)control.rx, count, rwtrampoline, rxtrampoline); + } // if + + if (count == 5) { + original[0] = A64_NOP; + ++original; + } // if + original[0] = 0x58000051u; // LDR X17, #0x8 + original[1] = 0xd61f0220u; // BR X17 + *reinterpret_cast(original + 2) = __intval(replace); + __flush_cache(symbol, 5 * sizeof(uint32_t)); + + skyline::logger::s_Instance->LogFormat( + "[And64InlineHook] inline hook %p->%p successfully! %zu bytes overwritten", symbol, replace, + 5 * sizeof(uint32_t)); + + control.unclaim(); + } else { + skyline::inlinehook::ControlledPages control(original, 1 * sizeof(uint32_t)); + control.claim(); + + original = (u32*)control.rw; + + if (rwtrampoline) { + if (rwx_size < 1u * 10u) { + skyline::logger::s_Instance->LogFormat( + "[And64InlineHook] rwx size is too small to hold %u bytes backup instructions!", 1u * 10u); + control.unclaim(); + return NULL; + } // if + __fix_instructions(original, (u32*)control.rx, 1, rwtrampoline, rxtrampoline); + } // if + + __sync_cmpswap(original, *original, 0x14000000u | (pc_offset & mask)); // "B" ADDR_PCREL26 + __flush_cache(symbol, 1 * sizeof(uint32_t)); + + skyline::logger::s_Instance->LogFormat( + "[And64InlineHook] inline hook %p->%p successfully! %zu bytes overwritten", symbol, replace, + 1 * sizeof(uint32_t)); + + control.unclaim(); + } // if + + // if(rwtrampoline) + // rwtrampoline[0] = 0xDEADBEEF; + return rxtrampoline; +} + +//------------------------------------------------------------------------- + +extern "C" void A64HookFunction(void* const symbol, void* const replace, void** result) { + nn::os::LockMutex(&hookMutex); + + R_ERRORONFAIL(jitTransitionToWritable(&__insns_jit)); + + uint32_t *rxtrampoline = NULL, *rwtrampoline = NULL; + if (result != NULL) { + FastAllocateTrampoline(&rxtrampoline, &rwtrampoline); + *result = rxtrampoline; + if (rxtrampoline == NULL) { + *(u64*)rxtrampoline = 0; + return; + }; + } // if + + rxtrampoline = + (uint32_t*)A64HookFunctionV(symbol, replace, rxtrampoline, rwtrampoline, A64_MAX_INSTRUCTIONS * 10u); + if (rxtrampoline == NULL && result != NULL) { + *result = NULL; + } // if + + R_ERRORONFAIL(jitTransitionToExecutable(&__insns_jit)); + + nn::os::UnlockMutex(&hookMutex); +} + +extern const void (*inlineHandlerStart)(void); +extern const void* inlineHandlerEnd; +extern const void (*inlineHandlerImpl)(void); + +u64 inline_hook_curridx = 0; + +extern "C" void A64InlineHook(void* const address, void* const callback) { + u64 handler_start_addr = (u64)&inlineHandlerStart; + u64 handler_end_addr = (u64)&inlineHandlerEnd; + + // make sure inline hook handler constexpr is correct + if (inline_hook_handler_size != handler_end_addr - handler_start_addr) { + skyline::logger::s_Instance->LogFormat("[A64InlineHook] invalid handler size, mannual updating required"); + R_ERRORONFAIL(MAKERESULT(Module_Skyline, SkylineError_InlineHookHandlerSizeInvalid)); + } + + // check pool availability + if (inline_hook_curridx >= inline_hook_count) { + skyline::logger::s_Instance->LogFormat("[A64InlineHook] inline hook pool exausted"); + R_ERRORONFAIL(MAKERESULT(Module_Skyline, SkylineError_InlineHookPoolExhausted)); + } + + // prepare to hook + jitTransitionToExecutable(&__inline_hook_jit); + auto& rx_entries = *reinterpret_cast*>(__inline_hook_jit.rx_addr); + auto& rx = rx_entries[inline_hook_curridx]; + + // hook to call the handler + void* trampoline; + A64HookFunction(address, &rx.handler, &trampoline); + + // populate handler entry + jitTransitionToWritable(&__inline_hook_jit); + auto& rw_entries = *reinterpret_cast*>(__inline_hook_jit.rw_addr); + auto& rw = rw_entries[inline_hook_curridx]; + + memcpy(rw.handler.data(), (void*)handler_start_addr, inline_hook_handler_size); + rw.cur_handler = &inlineHandlerImpl; + rw.callback = callback; + rw.trampoline = trampoline; + + // finalize, make handler executable + jitTransitionToExecutable(&__inline_hook_jit); + + inline_hook_curridx++; +} + +#endif // defined(__aarch64__) diff --git a/source/skyline/inlinehook/controlledpages.cpp b/source/skyline/inlinehook/controlledpages.cpp new file mode 100644 index 0000000..cf49282 --- /dev/null +++ b/source/skyline/inlinehook/controlledpages.cpp @@ -0,0 +1,160 @@ +#include "skyline/inlinehook/controlledpages.hpp" + +#include "nn/os.hpp" +#include "skyline/utils/cpputils.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/arm/cache.h" +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/kernel/virtmem.h" +#include "skyline/nx/runtime/env.h" +#include "skyline/utils/utils.h" + +#ifdef __cplusplus +} +#endif + +#define PAGE_SIZE 0x1000 + +namespace skyline::inlinehook { + +// mostly Atmosphere's libstratosphere +// TODO: this assumes sys ver v2.0.0+ish, could this support anything earlier? +struct AddressSpaceInfo { + uintptr_t heap_base; + size_t heap_size; + uintptr_t heap_end; + uintptr_t alias_base; + size_t alias_size; + uintptr_t alias_end; + uintptr_t aslr_base; + size_t aslr_size; + uintptr_t aslr_end; +}; + +static constexpr uintptr_t AslrBase32Bit = 0x0000200000ul; +static constexpr size_t AslrSize32Bit = 0x003FE00000ul; +static constexpr uintptr_t AslrBase64BitDeprecated = 0x0008000000ul; +static constexpr size_t AslrSize64BitDeprecated = 0x0078000000ul; +static constexpr uintptr_t AslrBase64Bit = 0x0008000000ul; +static constexpr size_t AslrSize64Bit = 0x7FF8000000ul; + +static Result getProcessAddressSpaceInfo(AddressSpaceInfo* out, Handle process_h) { + /* Clear output. */ + memset(out, 0, sizeof(*out)); + + /* Retrieve info from kernel. */ + R_TRY(svcGetInfo(&out->heap_base, InfoType_HeapRegionAddress, process_h, 0)); + R_TRY(svcGetInfo(&out->heap_size, InfoType_HeapRegionSize, process_h, 0)); + R_TRY(svcGetInfo(&out->alias_base, InfoType_AliasRegionAddress, process_h, 0)); + R_TRY(svcGetInfo(&out->alias_size, InfoType_AliasRegionSize, process_h, 0)); + + R_TRY(svcGetInfo(&out->aslr_base, InfoType_AslrRegionAddress, process_h, 0)); + R_TRY(svcGetInfo(&out->aslr_size, InfoType_AslrRegionSize, process_h, 0)); + + out->heap_end = out->heap_base + out->heap_size; + out->alias_end = out->alias_base + out->alias_size; + out->aslr_end = out->aslr_base + out->aslr_size; + return 0; +} + +static Result locateMappableSpaceModern(uintptr_t* out_address, size_t size) { + MemoryInfo mem_info = {}; + u32 page_info = 0; + uintptr_t cur_base = 0, cur_end = 0; + + AddressSpaceInfo address_space; + R_TRY(getProcessAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE)); + cur_base = address_space.aslr_base; + cur_end = cur_base + size; + + R_UNLESS(cur_base < cur_end, 0x10); + + s64 off; + + while (true) { + off += sizeof(cur_base); + + if (address_space.heap_size && + (address_space.heap_base <= cur_end - 1 && cur_base <= address_space.heap_end - 1)) { + /* If we overlap the heap region, go to the end of the heap region. */ + R_UNLESS(cur_base != address_space.heap_end, 0x104); + cur_base = address_space.heap_end; + } else if (address_space.alias_size && + (address_space.alias_base <= cur_end - 1 && cur_base <= address_space.alias_end - 1)) { + /* If we overlap the alias region, go to the end of the alias region. */ + R_UNLESS(cur_base != address_space.alias_end, 0x104); + cur_base = address_space.alias_end; + } else { + R_ERRORONFAIL(svcQueryMemory(&mem_info, &page_info, cur_base)); + if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= size) { + *out_address = cur_base; + return 0; + } + R_UNLESS(cur_base < mem_info.addr + mem_info.size, 0x104); + + cur_base = mem_info.addr + mem_info.size; + R_UNLESS(cur_base < address_space.aslr_end, 0x104); + } + cur_end = cur_base + size; + R_UNLESS(cur_base < cur_base + size, 0x104); + } +} + +static Result locateMappableSpace(uintptr_t* out_address, size_t size) { + return locateMappableSpaceModern(out_address, size); +} + +ControlledPages::ControlledPages(void* rx, size_t size) { + this->rx = rx; + this->size = size; + isClaimed = false; +} + +void ControlledPages::claim() { + if (!isClaimed) { + // get actual pages + u64 alignedSrc = ALIGN_DOWN((u64)rx, PAGE_SIZE); + size_t alignedSize = ALIGN_UP((u64)rx + size, PAGE_SIZE) - alignedSrc; + + // reserve space for rw pages + u64 dst; + R_ERRORONFAIL(locateMappableSpace(&dst, alignedSize)); + + // map pages + R_ERRORONFAIL(svcMapProcessMemory((void*)dst, envGetOwnProcessHandle(), alignedSrc, alignedSize)); + + // provide rw pointer into the respective location in newly mapped pages + rw = (void*)(dst + ((s64)rx - alignedSrc)); + isClaimed = true; + + // sanity check... + if (*(u64*)rx != *(u64*)rw) { + R_ERRORONFAIL(-1); + } + } +} + +void ControlledPages::unclaim() { + if (isClaimed) { + // get actual pages + u64 alignedSrc = ALIGN_DOWN((u64)rx, PAGE_SIZE); + void* alignedDst = (void*)ALIGN_DOWN((u64)rw, PAGE_SIZE); + size_t alignedSize = ALIGN_UP((u64)rx + size, PAGE_SIZE) - alignedSrc; + + // invalidate caches + armDCacheFlush((void*)alignedDst, size); + armICacheInvalidate((void*)alignedSrc, size); + + // unmap pages + R_ERRORONFAIL(svcUnmapProcessMemory(alignedDst, envGetOwnProcessHandle(), alignedSrc, alignedSize)); + + // clean up variables + rw = NULL; + isClaimed = false; + } +} +}; // namespace skyline::inlinehook diff --git a/source/skyline/inlinehook/memcpy_controlled.cpp b/source/skyline/inlinehook/memcpy_controlled.cpp new file mode 100644 index 0000000..c956a01 --- /dev/null +++ b/source/skyline/inlinehook/memcpy_controlled.cpp @@ -0,0 +1,19 @@ +#include "skyline/inlinehook/memcpy_controlled.hpp" + +#include + +extern "C" Result sky_memcpy(void* dest, const void* src, size_t n) { + if (dest == NULL || src == NULL) { + return -1; + } + + skyline::inlinehook::ControlledPages control(dest, n); + + control.claim(); + + memcpy(control.rw, src, n); + + control.unclaim(); + + return 0; +} diff --git a/source/skyline/logger/KernelLogger.cpp b/source/skyline/logger/KernelLogger.cpp new file mode 100644 index 0000000..1f4d854 --- /dev/null +++ b/source/skyline/logger/KernelLogger.cpp @@ -0,0 +1,27 @@ +#include "skyline/logger/KernelLogger.hpp" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/kernel/svc.h" + +#ifdef __cplusplus +} +#endif + +namespace skyline::logger { + +KernelLogger::KernelLogger() {} + +void KernelLogger::Initialize() { + // nothing to do +} + +void KernelLogger::SendRaw(void* data, size_t size) { + const char* str = (const char*)data; + svcOutputDebugString(str, strlen(str)); +}; +}; // namespace skyline::logger \ No newline at end of file diff --git a/source/skyline/logger/Logger.cpp b/source/skyline/logger/Logger.cpp new file mode 100644 index 0000000..e6d9ea9 --- /dev/null +++ b/source/skyline/logger/Logger.cpp @@ -0,0 +1,104 @@ +#include "skyline/logger/Logger.hpp" + +#include + +#include "alloc.h" +#include "mem.h" +#include "operator.h" + +namespace skyline::logger { + +Logger* s_Instance; + +#ifndef NOLOG + +std::queue* g_msgQueue = nullptr; + +void ThreadMain(void* arg) { + Logger* t = (Logger*)arg; + + t->Initialize(); + + t->LogFormat("[%s] Logger initialized.", t->FriendlyName().c_str()); + + while (true) { + t->Flush(); + nn::os::YieldThread(); // let other parts of OS do their thing + nn::os::SleepThread(nn::TimeSpan::FromNanoSeconds(100000000)); + } +} + +void Logger::StartThread() { + const size_t stackSize = 0x3000; + void* threadStack = memalign(0x1000, stackSize); + + nn::os::ThreadType* thread = new nn::os::ThreadType; + nn::os::CreateThread(thread, ThreadMain, this, threadStack, stackSize, 16, 0); + nn::os::StartThread(thread); +} + +void Logger::SendRaw(const char* data) { SendRaw((void*)data, strlen(data)); } + +void Logger::SendRawFormat(const char* format, ...) { + va_list args; + char buff[0x1000] = {0}; + va_start(args, format); + + int len = vsnprintf(buff, sizeof(buff), format, args); + + SendRaw(buff, len); + + va_end(args); +} + +void AddToQueue(char* data) { + if (!g_msgQueue) g_msgQueue = new std::queue(); + + g_msgQueue->push(data); +} + +void Logger::Flush() { + if (!g_msgQueue) return; + + while (!g_msgQueue->empty()) { + auto data = g_msgQueue->front(); + + SendRaw(data, strlen(data)); + delete[] data; + g_msgQueue->pop(); + } +} + +void Logger::Log(const char* data, size_t size) { + if (size == UINT32_MAX) size = strlen(data); + + char* ptr = new char[size + 2]; + memset(ptr, 0, size + 2); + memcpy(ptr, data, size); + // ptr[size] = '\n'; + + AddToQueue(ptr); + return; +} + +void Logger::Log(std::string str) { Log(str.data(), str.size()); } + +void Logger::LogFormat(const char* format, ...) { + va_list args; + va_start(args, format); + + size_t len = vsnprintf(NULL, 0, format, args); + char* ptr = new char[len + 2]; + memset(ptr, 0, len + 2); + vsnprintf(ptr, len + 1, format, args); + ptr[len] = '\n'; + + AddToQueue(ptr); + va_end(args); + + return; +} + +#endif // NOLOG + +}; // namespace skyline::logger diff --git a/source/skyline/logger/SdLogger.cpp b/source/skyline/logger/SdLogger.cpp new file mode 100644 index 0000000..5a87abe --- /dev/null +++ b/source/skyline/logger/SdLogger.cpp @@ -0,0 +1,35 @@ +#include "skyline/logger/SdLogger.hpp" + +#include "nn/fs.h" + +namespace skyline::logger { +nn::fs::FileHandle fileHandle; +s64 offset; + +SdLogger::SdLogger(std::string path) { + /*nn::fs::DirectoryEntryType type; + Result rc = nn::fs::GetEntryType(&type, path.c_str()); + + if (rc == 0x202) { // Path does not exist + rc = nn::fs::CreateFile(path.c_str(), 0); + } else if (R_FAILED(rc)) + return; + + if (type == nn::fs::DirectoryEntryType_Directory) return; + + R_ERRORONFAIL(nn::fs::OpenFile(&fileHandle, path.c_str(), nn::fs::OpenMode_ReadWrite | nn::fs::OpenMode_Append));*/ +} + +void SdLogger::Initialize() { + // nothing to do +} + +void SdLogger::SendRaw(void* data, size_t size) { + nn::fs::SetFileSize(fileHandle, offset + size); + + nn::fs::WriteFile(fileHandle, offset, data, size, + nn::fs::WriteOption::CreateOption(nn::fs::WriteOptionFlag_Flush)); + + offset += size; +}; +}; // namespace skyline::logger \ No newline at end of file diff --git a/source/skyline/nx/arm/cache.s b/source/skyline/nx/arm/cache.s new file mode 100644 index 0000000..1a2ad4b --- /dev/null +++ b/source/skyline/nx/arm/cache.s @@ -0,0 +1,95 @@ +.macro CODE_BEGIN name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 + .cfi_startproc +\name: +.endm + +.macro CODE_END + .cfi_endproc +.endm + +CODE_BEGIN armDCacheFlush + add x1, x1, x0 + mrs x8, CTR_EL0 + lsr x8, x8, #16 + and x8, x8, #0xf + mov x9, #4 + lsl x9, x9, x8 + sub x10, x9, #1 + bic x8, x0, x10 + mov x10, x1 + +armDCacheFlush_L0: + dc civac, x8 + add x8, x8, x9 + cmp x8, x10 + bcc armDCacheFlush_L0 + + dsb sy + ret +CODE_END + +CODE_BEGIN armDCacheClean + add x1, x1, x0 + mrs x8, CTR_EL0 + lsr x8, x8, #16 + and x8, x8, #0xf + mov x9, #4 + lsl x9, x9, x8 + sub x10, x9, #1 + bic x8, x0, x10 + mov x10, x1 + +armDCacheClean_L0: + dc cvac, x8 + add x8, x8, x9 + cmp x8, x10 + bcc armDCacheClean_L0 + + dsb sy + ret +CODE_END + +CODE_BEGIN armICacheInvalidate + add x1, x1, x0 + mrs x8, CTR_EL0 + and x8, x8, #0xf + mov x9, #4 + lsl x9, x9, x8 + sub x10, x9, #1 + bic x8, x0, x10 + mov x10, x1 + +armICacheInvalidate_L0: + ic ivau, x8 + add x8, x8, x9 + cmp x8, x10 + bcc armICacheInvalidate_L0 + + dsb sy + ret +CODE_END + +CODE_BEGIN armDCacheZero + add x1, x1, x0 + mrs x8, CTR_EL0 + lsr x8, x8, #16 + and x8, x8, #0xf + mov x9, #4 + lsl x9, x9, x8 + sub x10, x9, #1 + bic x8, x0, x10 + mov x10, x1 + +armDCacheZero_L0: + dc zva, x8 + add x8, x8, x9 + cmp x8, x10 + bcc armDCacheZero_L0 + + dsb sy + ret +CODE_END \ No newline at end of file diff --git a/source/skyline/nx/internal.h b/source/skyline/nx/internal.h new file mode 100644 index 0000000..5b9ba7e --- /dev/null +++ b/source/skyline/nx/internal.h @@ -0,0 +1,27 @@ +#pragma once + +#include "skyline/nx/arm/tls.h" +#include "skyline/nx/kernel/thread.h" +#include "types.h" + +#define THREADVARS_MAGIC 0x21545624 // !TV$ + +// This structure is exactly 0x20 bytes +typedef struct { + // Magic value used to check if the struct is initialized + u32 magic; + + // Thread handle, for mutexes + Handle handle; + + // Pointer to the current thread (if exists) + Thread* thread_ptr; + + // Pointer to this thread's newlib state + struct _reent* reent; + + // Pointer to this thread's thread-local segment + void* tls_tp; // !! Offset needs to be TLS+0x1F8 for __aarch64_read_tp !! +} ThreadVars; + +static inline ThreadVars* getThreadVars(void) { return (ThreadVars*)((u8*)armGetTls() + 0x200 - sizeof(ThreadVars)); } \ No newline at end of file diff --git a/source/skyline/nx/kernel/condvar.c b/source/skyline/nx/kernel/condvar.c new file mode 100644 index 0000000..eb0fd11 --- /dev/null +++ b/source/skyline/nx/kernel/condvar.c @@ -0,0 +1,17 @@ +// Copyright 2018 plutoo +#include "skyline/nx/kernel/condvar.h" + +#include "../internal.h" +#include "skyline/nx/kernel/svc.h" +#include "types.h" + +Result condvarWaitTimeout(CondVar* c, Mutex* m, u64 timeout) { + Result rc; + + rc = svcWaitProcessWideKeyAtomic((u32*)m, c, getThreadVars()->handle, timeout); + + // On timeout, we need to acquire it manually. + if (rc == 0xEA01) mutexLock(m); + + return rc; +} \ No newline at end of file diff --git a/source/skyline/nx/kernel/detect.c b/source/skyline/nx/kernel/detect.c new file mode 100644 index 0000000..7349449 --- /dev/null +++ b/source/skyline/nx/kernel/detect.c @@ -0,0 +1,106 @@ +// Copyright 2017 plutoo +#include "skyline/nx/kernel/detect.h" + +#include "alloc.h" +#include "mem.h" +#include "skyline/nx/kernel/mutex.h" +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/result.h" +#include "types.h" + +static bool g_VersionCached; +static Mutex g_VersionMutex; +static int g_Version; + +static bool g_JitKernelPatchCached; +static Mutex g_JitKernelPatchMutex; +static bool g_JitKernelPatchDetected; + +static void _CacheVersion(void) { + if (__atomic_load_n(&g_VersionCached, __ATOMIC_SEQ_CST)) return; + + mutexLock(&g_VersionMutex); + + if (g_VersionCached) { + mutexUnlock(&g_VersionMutex); + return; + } + + u64 tmp; + g_Version = 1; + if (R_VALUE(svcGetInfo(&tmp, InfoType_AslrRegionAddress, INVALID_HANDLE, 0)) != KERNELRESULT(InvalidEnumValue)) + g_Version = 2; + if (R_VALUE(svcGetInfo(&tmp, InfoType_ProgramId, INVALID_HANDLE, 0)) != KERNELRESULT(InvalidEnumValue)) + g_Version = 3; + if (R_VALUE(svcGetInfo(&tmp, InfoType_InitialProcessIdRange, INVALID_HANDLE, 0)) != KERNELRESULT(InvalidEnumValue)) + g_Version = 4; + if (R_VALUE(svcGetInfo(&tmp, InfoType_UserExceptionContextAddress, INVALID_HANDLE, 0)) != + KERNELRESULT(InvalidEnumValue)) + g_Version = 5; + if (R_VALUE(svcGetInfo(&tmp, InfoType_TotalNonSystemMemorySize, INVALID_HANDLE, 0)) != + KERNELRESULT(InvalidEnumValue)) + g_Version = 6; + + __atomic_store_n(&g_VersionCached, true, __ATOMIC_SEQ_CST); + + mutexUnlock(&g_VersionMutex); +} + +static void _CacheJitKernelPatch(void) { + if (__atomic_load_n(&g_JitKernelPatchCached, __ATOMIC_SEQ_CST)) return; + + mutexLock(&g_JitKernelPatchMutex); + + if (g_JitKernelPatchCached) { + mutexUnlock(&g_JitKernelPatchMutex); + return; + } + + void* heap = memalign(0x1000, 0x1000); + + if (heap != NULL) { + Handle code; + Result rc; + rc = svcCreateCodeMemory(&code, heap, 0x1000); + + if (R_SUCCEEDED(rc)) { + // On an unpatched kernel on [5.0.0+], this would return InvalidMemoryState (0xD401). + // It is not allowed for the creator-process of a CodeMemory object to use svcControlCodeMemory on it. + // If the patch is present, the function should return InvalidEnumValue (0xF001), because -1 is not a valid + // enum CodeOperation. + rc = svcControlCodeMemory(code, -1, 0, 0x1000, 0); + + g_JitKernelPatchDetected = R_VALUE(rc) == KERNELRESULT(InvalidEnumValue); + __atomic_store_n(&g_JitKernelPatchCached, true, __ATOMIC_SEQ_CST); + + svcCloseHandle(code); + } + + free(heap); + } + + mutexUnlock(&g_JitKernelPatchMutex); +} + +int detectKernelVersion(void) { + _CacheVersion(); + return g_Version; +} + +bool detectDebugger(void) { + u64 tmp; + svcGetInfo(&tmp, InfoType_DebuggerAttached, INVALID_HANDLE, 0); + return !!tmp; +} + +bool detectJitKernelPatch(void) { + _CacheJitKernelPatch(); + return g_JitKernelPatchDetected; +} + +void detectIgnoreJitKernelPatch(void) { + mutexLock(&g_JitKernelPatchMutex); + g_JitKernelPatchDetected = false; + __atomic_store_n(&g_JitKernelPatchCached, true, __ATOMIC_SEQ_CST); + mutexUnlock(&g_JitKernelPatchMutex); +} \ No newline at end of file diff --git a/source/skyline/nx/kernel/jit.c b/source/skyline/nx/kernel/jit.c new file mode 100644 index 0000000..608ef15 --- /dev/null +++ b/source/skyline/nx/kernel/jit.c @@ -0,0 +1,176 @@ +#include "skyline/nx/kernel/jit.h" + +#include "alloc.h" +#include "mem.h" +#include "skyline/nx/arm/cache.h" +#include "skyline/nx/kernel/detect.h" +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/kernel/virtmem.h" +#include "skyline/nx/result.h" +#include "skyline/nx/runtime/env.h" +#include "types.h" + +Result jitCreate(Jit* j, void* rx_addr, size_t size) { + JitType type; + + // Use new jit primitive introduced in [4.0.0+], if available. + // Not usable with [5.0.0+] since svcMapJitMemory doesn't allow using that SVC under the same process which owns + // that object. + if (kernelAbove400() && envIsSyscallHinted(0x4B) && envIsSyscallHinted(0x4C) && + (!kernelAbove500() || detectJitKernelPatch())) { + type = JitType_JitMemory; + } + // Fall back to MapProcessCodeMemory if available. + else if (envIsSyscallHinted(0x73) && envIsSyscallHinted(0x77) && envIsSyscallHinted(0x78) && + (envGetOwnProcessHandle() != INVALID_HANDLE)) { + type = JitType_CodeMemory; + } else { + // Jit is unavailable. :( + return MAKERESULT(Module_Libnx, LibnxError_JitUnavailable); + } + + size = (size + 0xFFF) & ~0xFFF; + + void* src_addr = memalign(0x1000, size); + + if (src_addr == NULL) return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + + j->type = type; + j->size = size; + j->src_addr = src_addr; + j->rx_addr = rx_addr != NULL ? rx_addr : virtmemReserve(j->size); + j->handle = INVALID_HANDLE; + j->is_executable = 0; + + Result rc = 0; + + switch (j->type) { + case JitType_CodeMemory: + j->rw_addr = j->src_addr; + break; + + case JitType_JitMemory: + j->rw_addr = virtmemReserve(j->size); + + rc = svcCreateCodeMemory(&j->handle, j->src_addr, j->size); + if (R_SUCCEEDED(rc)) { + rc = svcControlCodeMemory(j->handle, CodeMapOperation_MapOwner, j->rw_addr, j->size, Perm_Rw); + if (R_SUCCEEDED(rc)) { + rc = svcControlCodeMemory(j->handle, CodeMapOperation_MapSlave, j->rx_addr, j->size, Perm_Rx); + + if (R_FAILED(rc)) { + svcControlCodeMemory(j->handle, CodeMapOperation_UnmapOwner, j->rw_addr, j->size, 0); + } + } + + if (R_FAILED(rc)) { + svcCloseHandle(j->handle); + j->handle = INVALID_HANDLE; + } + } + + if (R_FAILED(rc)) { + virtmemFree(j->rw_addr, j->size); + j->rw_addr = NULL; + } + + break; + } + + if (R_FAILED(rc)) { + virtmemFree(j->rx_addr, j->size); + free(j->src_addr); + j->src_addr = NULL; + } + + return rc; +} + +Result jitTransitionToWritable(Jit* j) { + Result rc = 0; + + switch (j->type) { + case JitType_CodeMemory: + if (j->is_executable) + rc = svcUnmapProcessCodeMemory(envGetOwnProcessHandle(), (u64)j->rx_addr, (u64)j->src_addr, j->size); + break; + + case JitType_JitMemory: + // No need to do anything. + break; + } + + if (R_SUCCEEDED(rc)) j->is_executable = 0; + + return rc; +} + +Result jitTransitionToExecutable(Jit* j) { + Result rc = 0; + + switch (j->type) { + case JitType_CodeMemory: + if (!j->is_executable) { + rc = svcMapProcessCodeMemory(envGetOwnProcessHandle(), (u64)j->rx_addr, (u64)j->src_addr, j->size); + + if (R_SUCCEEDED(rc)) { + rc = svcSetProcessMemoryPermission(envGetOwnProcessHandle(), (u64)j->rx_addr, j->size, Perm_Rx); + + if (R_FAILED(rc)) { + jitTransitionToWritable(j); + } + } + } + break; + + case JitType_JitMemory: + armDCacheFlush(j->rw_addr, j->size); + armICacheInvalidate(j->rx_addr, j->size); + break; + } + + if (R_SUCCEEDED(rc)) j->is_executable = 1; + + return rc; +} + +Result jitClose(Jit* j) { + Result rc = 0; + + switch (j->type) { + case JitType_CodeMemory: + rc = jitTransitionToWritable(j); + + if (R_SUCCEEDED(rc)) { + virtmemFree(j->rx_addr, j->size); + } + break; + + case JitType_JitMemory: + rc = svcControlCodeMemory(j->handle, CodeMapOperation_UnmapOwner, j->rw_addr, j->size, 0); + + if (R_SUCCEEDED(rc)) { + virtmemFree(j->rw_addr, j->size); + + rc = svcControlCodeMemory(j->handle, CodeMapOperation_UnmapSlave, j->rx_addr, j->size, 0); + + if (R_SUCCEEDED(rc)) { + virtmemFree(j->rx_addr, j->size); + svcCloseHandle(j->handle); + } + } + break; + } + + if (R_SUCCEEDED(rc)) { + if (j->src_addr != NULL) { + free(j->src_addr); + j->src_addr = NULL; + } + } + return rc; +} + +void* jitGetRwAddr(Jit* j) { return j->rw_addr; } + +void* jitGetRxAddr(Jit* j) { return j->rx_addr; } \ No newline at end of file diff --git a/source/skyline/nx/kernel/mutex.c b/source/skyline/nx/kernel/mutex.c new file mode 100644 index 0000000..a2cd925 --- /dev/null +++ b/source/skyline/nx/kernel/mutex.c @@ -0,0 +1,20 @@ +// Copyright 2017 plutoo +#include "skyline/nx/kernel/mutex.h" + +#include "../internal.h" +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/runtime/env.h" + +#define HAS_LISTENERS 0x40000000 + +void mutexLock(Mutex* m) { nnosLockMutex(m); } + +bool mutexTryLock(Mutex* m) { return nnosTryLockMutex(m); } + +void mutexUnlock(Mutex* m) { nnosUnlockMutex(m); } + +void rmutexLock(RMutex* m) { mutexLock(m); } + +bool rmutexTryLock(RMutex* m) { return mutexTryLock(m); } + +void rmutexUnlock(RMutex* m) { mutexUnlock(m); } \ No newline at end of file diff --git a/source/skyline/nx/kernel/shmem.c b/source/skyline/nx/kernel/shmem.c new file mode 100644 index 0000000..df39d0c --- /dev/null +++ b/source/skyline/nx/kernel/shmem.c @@ -0,0 +1,80 @@ +// Copyright 2017 plutoo +#include "skyline/nx/kernel/shmem.h" + +#include + +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/kernel/virtmem.h" +#include "skyline/nx/result.h" +#include "types.h" + +Result shmemCreate(SharedMemory* s, size_t size, Permission local_perm, Permission remote_perm) { + Result rc; + + s->handle = INVALID_HANDLE; + s->size = size; + s->map_addr = NULL; + s->perm = local_perm; + + rc = svcCreateSharedMemory(&s->handle, size, local_perm, remote_perm); + + return rc; +} + +void shmemLoadRemote(SharedMemory* s, Handle handle, size_t size, Permission perm) { + s->handle = handle; + s->size = size; + s->map_addr = NULL; + s->perm = perm; +} + +Result shmemMap(SharedMemory* s) { + Result rc = 0; + + if (s->map_addr == NULL) { + void* addr = virtmemReserve(s->size); + + rc = svcMapSharedMemory(s->handle, addr, s->size, s->perm); + + if (R_SUCCEEDED(rc)) { + s->map_addr = addr; + } else { + virtmemFree(addr, s->size); + } + } else { + rc = MAKERESULT(Module_Libnx, LibnxError_AlreadyMapped); + } + + return rc; +} + +Result shmemUnmap(SharedMemory* s) { + Result rc; + + rc = svcUnmapSharedMemory(s->handle, s->map_addr, s->size); + + if (R_SUCCEEDED(rc)) { + virtmemFree(s->map_addr, s->size); + s->map_addr = NULL; + } + + return rc; +} + +Result shmemClose(SharedMemory* s) { + Result rc = 0; + + if (s->map_addr != NULL) { + rc = shmemUnmap(s); + } + + if (R_SUCCEEDED(rc)) { + if (s->handle != INVALID_HANDLE) { + rc = svcCloseHandle(s->handle); + } + + s->handle = INVALID_HANDLE; + } + + return rc; +} diff --git a/source/skyline/nx/kernel/svc.s b/source/skyline/nx/kernel/svc.s new file mode 100644 index 0000000..2b870d4 --- /dev/null +++ b/source/skyline/nx/kernel/svc.s @@ -0,0 +1,646 @@ +.macro SVC_BEGIN name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 + .cfi_startproc +\name: +.endm + +.macro SVC_END + .cfi_endproc +.endm + +SVC_BEGIN svcSetHeapSize + str x0, [sp, #-16]! + svc 0x1 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcSetMemoryPermission + svc 0x2 + ret +SVC_END + +SVC_BEGIN svcSetMemoryAttribute + svc 0x3 + ret +SVC_END + +SVC_BEGIN svcMapMemory + svc 0x4 + ret +SVC_END + +SVC_BEGIN svcUnmapMemory + svc 0x5 + ret +SVC_END + +SVC_BEGIN svcQueryMemory + str x1, [sp, #-16]! + svc 0x6 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcExitProcess + svc 0x7 + ret +SVC_END + +SVC_BEGIN svcCreateThread + str x0, [sp, #-16]! + svc 0x8 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcStartThread + svc 0x9 + ret +SVC_END + +SVC_BEGIN svcExitThread + svc 0xA + ret +SVC_END + +SVC_BEGIN svcSleepThread + svc 0xB + ret +SVC_END + +SVC_BEGIN svcGetThreadPriority + str x0, [sp, #-16]! + svc 0xC + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcSetThreadPriority + svc 0xD + ret +SVC_END + +SVC_BEGIN svcGetThreadCoreMask + stp x0, x1, [sp, #-16]! + svc 0xE + ldp x3, x4, [sp], #16 + str w1, [x3] + str w2, [x4] + ret +SVC_END + +SVC_BEGIN svcSetThreadCoreMask + svc 0xF + ret +SVC_END + +SVC_BEGIN svcGetCurrentProcessorNumber + svc 0x10 + ret +SVC_END + +SVC_BEGIN svcSignalEvent + svc 0x11 + ret +SVC_END + +SVC_BEGIN svcClearEvent + svc 0x12 + ret +SVC_END + +SVC_BEGIN svcMapSharedMemory + svc 0x13 + ret +SVC_END + +SVC_BEGIN svcUnmapSharedMemory + svc 0x14 + ret +SVC_END + +SVC_BEGIN svcCreateTransferMemory + str x0, [sp, #-16]! + svc 0x15 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcCloseHandle + svc 0x16 + ret +SVC_END + +SVC_BEGIN svcResetSignal + svc 0x17 + ret +SVC_END + +SVC_BEGIN svcWaitSynchronization + str x0, [sp, #-16]! + svc 0x18 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcCancelSynchronization + svc 0x19 + ret +SVC_END + +SVC_BEGIN svcArbitrateLock + svc 0x1A + ret +SVC_END + +SVC_BEGIN svcArbitrateUnlock + svc 0x1B + ret +SVC_END + +SVC_BEGIN svcWaitProcessWideKeyAtomic + svc 0x1C + ret +SVC_END + +SVC_BEGIN svcSignalProcessWideKey + svc 0x1D + ret +SVC_END + +SVC_BEGIN svcGetSystemTick + svc 0x1E + ret +SVC_END + +SVC_BEGIN svcConnectToNamedPort + str x0, [sp, #-16]! + svc 0x1F + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcSendSyncRequest + svc 0x21 + ret +SVC_END + +SVC_BEGIN svcSendSyncRequestWithUserBuffer + svc 0x22 + ret +SVC_END + +SVC_BEGIN svcSendAsyncRequestWithUserBuffer + str x0, [sp, #-16]! + svc 0x23 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcGetProcessId + str x0, [sp, #-16]! + svc 0x24 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcGetThreadId + str x0, [sp, #-16]! + svc 0x25 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcBreak + svc 0x26 + ret +SVC_END + +SVC_BEGIN svcOutputDebugString + svc 0x27 + ret +SVC_END + +SVC_BEGIN svcReturnFromException + svc 0x28 + ret +SVC_END + +SVC_BEGIN svcGetInfo + str x0, [sp, #-16]! + svc 0x29 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcMapPhysicalMemory + svc 0x2C + ret +SVC_END + +SVC_BEGIN svcUnmapPhysicalMemory + svc 0x2D + ret +SVC_END + +SVC_BEGIN svcGetResourceLimitLimitValue + str x0, [sp, #-16]! + svc 0x30 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcGetResourceLimitCurrentValue + str x0, [sp, #-16]! + svc 0x31 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcSetThreadActivity + svc 0x32 + ret +SVC_END + +SVC_BEGIN svcGetThreadContext3 + svc 0x33 + ret +SVC_END + +SVC_BEGIN svcCreateSession + stp x0, x1, [sp, #-16]! + svc 0x40 + ldp x3, x4, [sp], #16 + str w1, [x3] + str w2, [x4] + ret +SVC_END + +SVC_BEGIN svcAcceptSession + str x0, [sp, #-16]! + svc 0x41 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcReplyAndReceive + str x0, [sp, #-16]! + svc 0x43 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcReplyAndReceiveWithUserBuffer + str x0, [sp, #-16]! + svc 0x44 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcCreateEvent + stp x0, x1, [sp, #-16]! + svc 0x45 + ldp x3, x4, [sp], #16 + str w1, [x3] + str w2, [x4] + ret +SVC_END + +SVC_BEGIN svcMapPhysicalMemoryUnsafe + svc 0x48 + ret +SVC_END + +SVC_BEGIN svcUnmapPhysicalMemoryUnsafe + svc 0x49 + ret +SVC_END + +SVC_BEGIN svcSetUnsafeLimit + svc 0x4A + ret +SVC_END + +SVC_BEGIN svcCreateCodeMemory + str x0, [sp, #-16]! + svc 0x4B + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcControlCodeMemory + svc 0x4C + ret +SVC_END + +SVC_BEGIN svcReadWriteRegister + str x0, [sp, #-16]! + svc 0x4E + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcCreateSharedMemory + str x0, [sp, #-16]! + svc 0x50 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcMapTransferMemory + svc 0x51 + ret +SVC_END + +SVC_BEGIN svcUnmapTransferMemory + svc 0x52 + ret +SVC_END + +SVC_BEGIN svcCreateInterruptEvent + str x0, [sp, #-16]! + svc 0x53 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcQueryPhysicalAddress + str x0, [sp, #-16]! + svc 0x54 + ldr x4, [sp], #16 + stp x1, x2, [x4] + str x3, [x4, #16] + ret +SVC_END + +SVC_BEGIN svcQueryIoMapping + str x0, [sp, #-16]! + svc 0x55 + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcCreateDeviceAddressSpace + str x0, [sp, #-16]! + svc 0x56 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcAttachDeviceAddressSpace + svc 0x57 + ret +SVC_END + +SVC_BEGIN svcDetachDeviceAddressSpace + svc 0x58 + ret +SVC_END + +SVC_BEGIN svcMapDeviceAddressSpaceByForce + svc 0x59 + ret +SVC_END + +SVC_BEGIN svcMapDeviceAddressSpaceAligned + svc 0x5A + ret +SVC_END + +SVC_BEGIN svcUnmapDeviceAddressSpace + svc 0x5C + ret +SVC_END + +SVC_BEGIN svcDebugActiveProcess + str x0, [sp, #-16]! + svc 0x60 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcBreakDebugProcess + svc 0x61 + ret +SVC_END + +SVC_BEGIN svcTerminateDebugProcess + svc 0x62 + ret +SVC_END + +SVC_BEGIN svcGetDebugEvent + svc 0x63 + ret +SVC_END + +SVC_BEGIN svcLegacyContinueDebugEvent + svc 0x64 + ret +SVC_END + +SVC_BEGIN svcContinueDebugEvent + svc 0x64 + ret +SVC_END + +SVC_BEGIN svcGetProcessList + str x0, [sp, #-16]! + svc 0x65 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcGetThreadList + str x0, [sp, #-16]! + svc 0x66 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcGetDebugThreadContext + svc 0x67 + ret +SVC_END + +SVC_BEGIN svcSetDebugThreadContext + svc 0x68 + ret +SVC_END + +SVC_BEGIN svcQueryDebugProcessMemory + str x1, [sp, #-16]! + svc 0x69 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcReadDebugProcessMemory + svc 0x6A + ret +SVC_END + +SVC_BEGIN svcWriteDebugProcessMemory + svc 0x6B + ret +SVC_END + +SVC_BEGIN svcGetDebugThreadParam + stp x0, x1, [sp, #-16]! + svc 0x6D + ldp x3, x4, [sp], #16 + str x1, [x3] + str w2, [x4] + ret +SVC_END + +SVC_BEGIN svcGetSystemInfo + str x0, [sp, #-16]! + svc 0x6F + ldr x2, [sp], #16 + str x1, [x2] + ret +SVC_END + +SVC_BEGIN svcCreatePort + stp x0, x1, [sp, #-16]! + svc 0x70 + ldp x3, x4, [sp], #16 + str w1, [x3] + str w2, [x4] + ret +SVC_END + +SVC_BEGIN svcManageNamedPort + str x0, [sp, #-16]! + svc 0x71 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcConnectToPort + str x0, [sp, #-16]! + svc 0x72 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcSetProcessMemoryPermission + svc 0x73 + ret +SVC_END + +SVC_BEGIN svcMapProcessMemory + svc 0x74 + ret +SVC_END + +SVC_BEGIN svcUnmapProcessMemory + svc 0x75 + ret +SVC_END + +SVC_BEGIN svcQueryProcessMemory + str x1, [sp, #-16]! + svc 0x76 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcMapProcessCodeMemory + svc 0x77 + ret +SVC_END + +SVC_BEGIN svcUnmapProcessCodeMemory + svc 0x78 + ret +SVC_END + +SVC_BEGIN svcCreateProcess + str x0, [sp, #-16]! + svc 0x79 + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcStartProcess + svc 0x7A + ret +SVC_END + +SVC_BEGIN svcTerminateProcess + svc 0x7B + ret +SVC_END + +SVC_BEGIN svcGetProcessInfo + str x0, [sp, #-16]! + svc 0x7C + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcCreateResourceLimit + str x0, [sp, #-16]! + svc 0x7D + ldr x2, [sp], #16 + str w1, [x2] + ret +SVC_END + +SVC_BEGIN svcSetResourceLimitLimitValue + svc 0x7E + ret +SVC_END + +SVC_BEGIN svcCallSecureMonitor + str x0, [sp, #-16]! + mov x8, x0 + ldp x0, x1, [x8] + ldp x2, x3, [x8, #0x10] + ldp x4, x5, [x8, #0x20] + ldp x6, x7, [x8, #0x30] + svc 0x7F + ldr x8, [sp], #16 + stp x0, x1, [x8] + stp x2, x3, [x8, #0x10] + stp x4, x5, [x8, #0x20] + stp x6, x7, [x8, #0x30] + ret +SVC_END \ No newline at end of file diff --git a/source/skyline/nx/kernel/thread.c b/source/skyline/nx/kernel/thread.c new file mode 100644 index 0000000..be2dbc6 --- /dev/null +++ b/source/skyline/nx/kernel/thread.c @@ -0,0 +1,113 @@ +// Copyright 2017 plutoo +#include "skyline/nx/kernel/thread.h" + +#include + +#include "../internal.h" +#include "alloc.h" +#include "mem.h" +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/kernel/virtmem.h" +#include "skyline/nx/result.h" +#include "types.h" + +extern const u8 __tdata_lma[]; +extern const u8 __tdata_lma_end[]; +extern u8 __tls_start[]; +extern u8 __tls_end[]; + +// Thread creation args; keep this struct's size 16-byte aligned +typedef struct { + Thread* t; + ThreadFunc entry; + void* arg; + struct _reent* reent; + void* tls; + void* padding; +} ThreadEntryArgs; + +static void _EntryWrap(ThreadEntryArgs* args) { + // Initialize thread vars + ThreadVars* tv = getThreadVars(); + tv->magic = THREADVARS_MAGIC; + tv->thread_ptr = args->t; + tv->reent = args->reent; + tv->tls_tp = (u8*)args->tls - 2 * sizeof(void*); // subtract size of Thread Control Block (TCB) + tv->handle = args->t->handle; + + // Launch thread entrypoint + args->entry(args->arg); + svcExitThread(); +} + +Result threadCreate(Thread* t, ThreadFunc entry, void* arg, void* stack, size_t stack_sz, int prio, int cpuid) { + stack_sz = (stack_sz + 0xFFF) & ~0xFFF; + + Result rc = 0; + size_t reent_sz = (sizeof(struct _reent) + 0xF) & ~0xF; + size_t tls_sz = (__tls_end - __tls_start + 0xF) & ~0xF; + + if (stack == NULL) stack = memalign(0x1000, stack_sz + reent_sz + tls_sz); + + if (stack == NULL) { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } else { + void* stack_mirror = virtmemReserve(stack_sz); + rc = svcMapMemory(stack_mirror, stack, stack_sz); + + if (R_SUCCEEDED(rc)) { + u64 stack_top = ((u64)stack_mirror) + stack_sz - sizeof(ThreadEntryArgs); + ThreadEntryArgs* args = (ThreadEntryArgs*)stack_top; + Handle handle; + + rc = svcCreateThread(&handle, (ThreadFunc)&_EntryWrap, args, (void*)stack_top, prio, cpuid); + + if (R_SUCCEEDED(rc)) { + t->handle = handle; + t->stack_mem = stack; + t->stack_mirror = stack_mirror; + t->stack_sz = stack_sz; + + args->t = t; + args->entry = entry; + args->arg = arg; + args->reent = (struct _reent*)((u8*)stack + stack_sz); + args->tls = (u8*)stack + stack_sz + reent_sz; + + // Set up child thread's reent struct, inheriting standard file handles + _REENT_INIT_PTR(args->reent); + struct _reent* cur = getThreadVars()->reent; + args->reent->_stdin = cur->_stdin; + args->reent->_stdout = cur->_stdout; + args->reent->_stderr = cur->_stderr; + + // Set up child thread's TLS segment + size_t tls_load_sz = __tdata_lma_end - __tdata_lma; + size_t tls_bss_sz = tls_sz - tls_load_sz; + if (tls_load_sz) memcpy(args->tls, __tdata_lma, tls_load_sz); + if (tls_bss_sz) memset(args->tls + tls_load_sz, 0, tls_bss_sz); + } + } + } + + return rc; +} + +Result threadStart(Thread* t) { return svcStartThread(t->handle); } + +Result threadWaitForExit(Thread* t) { return svcWaitSynchronizationSingle(t->handle, -1); } + +Result threadClose(Thread* t) { + Result rc; + + rc = svcUnmapMemory(t->stack_mirror, t->stack_mem, t->stack_sz); + virtmemFree(t->stack_mirror, t->stack_sz); + free(t->stack_mem); + svcCloseHandle(t->handle); + + return rc; +} + +Result threadPause(Thread* t) { return svcSetThreadActivity(t->handle, 1); } + +Result threadResume(Thread* t) { return svcSetThreadActivity(t->handle, 0); } \ No newline at end of file diff --git a/source/skyline/nx/kernel/virtmem.c b/source/skyline/nx/kernel/virtmem.c new file mode 100644 index 0000000..3b35a80 --- /dev/null +++ b/source/skyline/nx/kernel/virtmem.c @@ -0,0 +1,190 @@ +#include "skyline/nx/kernel/virtmem.h" + +#include "skyline/nx/kernel/mutex.h" +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/result.h" +#include "types.h" + +typedef struct { + u64 start; + u64 end; +} VirtualRegion; + +enum { REGION_STACK = 0, REGION_HEAP = 1, REGION_LEGACY_ALIAS = 2, REGION_MAX }; + +static VirtualRegion g_AddressSpace; +static VirtualRegion g_Region[REGION_MAX]; +static u64 g_CurrentAddr; +static u64 g_CurrentMapAddr; +static Mutex g_VirtMemMutex; + +static Result _GetRegionFromInfo(VirtualRegion* r, u64 id0_addr, u32 id0_sz) { + u64 base; + Result rc = svcGetInfo(&base, id0_addr, CUR_PROCESS_HANDLE, 0); + + if (R_SUCCEEDED(rc)) { + u64 size; + rc = svcGetInfo(&size, id0_sz, CUR_PROCESS_HANDLE, 0); + + if (R_SUCCEEDED(rc)) { + r->start = base; + r->end = base + size; + } + } + + return rc; +} + +static inline bool _InRegion(VirtualRegion* r, u64 addr) { return (addr >= r->start) && (addr < r->end); } + +void virtmemSetup(void) { + if (R_FAILED(_GetRegionFromInfo(&g_AddressSpace, InfoType_AslrRegionAddress, InfoType_AslrRegionSize))) { + // [1.0.0] doesn't expose address space size so we have to do this dirty hack to detect it. + // Forgive me. + + Result rc = svcUnmapMemory((void*)0xFFFFFFFFFFFFE000ULL, (void*)0xFFFFFE000ull, 0x1000); + + if (rc == 0xD401) { + // Invalid src-address error means that a valid 36-bit address was rejected. + // Thus we are 32-bit. + g_AddressSpace.start = 0x200000ull; + g_AddressSpace.end = 0x100000000ull; + + g_Region[REGION_STACK].start = 0x200000ull; + g_Region[REGION_STACK].end = 0x40000000ull; + } else if (rc == 0xDC01) { + // Invalid dst-address error means our 36-bit src-address was valid. + // Thus we are 36-bit. + g_AddressSpace.start = 0x8000000ull; + g_AddressSpace.end = 0x1000000000ull; + + g_Region[REGION_STACK].start = 0x8000000ull; + g_Region[REGION_STACK].end = 0x80000000ull; + } else { + R_ERRORONFAIL(rc); + } + } else { + R_ERRORONFAIL( + _GetRegionFromInfo(&g_Region[REGION_STACK], InfoType_StackRegionAddress, InfoType_StackRegionSize)); + } + + R_ERRORONFAIL(_GetRegionFromInfo(&g_Region[REGION_HEAP], InfoType_HeapRegionAddress, InfoType_HeapRegionSize)); + + _GetRegionFromInfo(&g_Region[REGION_LEGACY_ALIAS], InfoType_AliasRegionAddress, InfoType_AliasRegionSize); +} + +void* virtmemReserve(size_t size) { + MemoryInfo meminfo; + u32 pageinfo; + size_t i; + + size = (size + 0xFFF) & ~0xFFF; + + mutexLock(&g_VirtMemMutex); + u64 addr = g_CurrentAddr; + + while (1) { + // Add a guard page. + addr += 0x1000; + + // If we go outside address space, let's go back to start. + if (!_InRegion(&g_AddressSpace, addr)) { + addr = g_AddressSpace.start; + } + // Query information about address. + Result rc = svcQueryMemory(&meminfo, &pageinfo, addr); + + R_ERRORONFAIL(rc); + + if (meminfo.type != 0) { + // Address is already taken, let's move past it. + addr = meminfo.addr + meminfo.size; + continue; + } + + if (addr + size > meminfo.addr + meminfo.size) { + // We can't fit in this region, let's move past it. + addr = meminfo.addr + meminfo.size; + continue; + } + + // Check if we end up in a reserved region. + for (i = 0; i < REGION_MAX; i++) { + u64 end = addr + size - 1; + + if (_InRegion(&g_Region[i], addr) || _InRegion(&g_Region[i], end)) { + break; + } + } + + // Did we? + if (i != REGION_MAX) { + addr = g_Region[i].end; + continue; + } + + // Not in a reserved region, we're good to go! + break; + } + + g_CurrentAddr = addr + size; + + mutexUnlock(&g_VirtMemMutex); + return (void*)addr; +} + +void virtmemFree(void* addr, size_t size) { + IGNORE_ARG(addr); + IGNORE_ARG(size); +} + +void* virtmemReserveStack(size_t size) { + Result rc; + + MemoryInfo meminfo; + u32 pageinfo; + + size = (size + 0xFFF) & ~0xFFF; + + mutexLock(&g_VirtMemMutex); + u64 addr = g_CurrentMapAddr; + + while (1) { + // Add a guard page. + addr += 0x1000; + + // Make sure we stay inside the reserved map region. + if (!_InRegion(&g_Region[REGION_STACK], addr)) { + addr = g_Region[REGION_STACK].start; + } + + // Query information about address. + rc = svcQueryMemory(&meminfo, &pageinfo, addr); + + R_ERRORONFAIL(rc); + + if (meminfo.type != 0) { + // Address is already taken, let's move past it. + addr = meminfo.addr + meminfo.size; + continue; + } + + if (addr + size > meminfo.addr + meminfo.size) { + // We can't fit in this region, let's move past it. + addr = meminfo.addr + meminfo.size; + continue; + } + + break; + } + + g_CurrentMapAddr = addr + size; + + mutexUnlock(&g_VirtMemMutex); + return (void*)addr; +} + +void virtmemFreeStack(void* addr, size_t size) { + IGNORE_ARG(addr); + IGNORE_ARG(size); +} \ No newline at end of file diff --git a/source/skyline/nx/runtime/env.c b/source/skyline/nx/runtime/env.c new file mode 100644 index 0000000..59ba995 --- /dev/null +++ b/source/skyline/nx/runtime/env.c @@ -0,0 +1,148 @@ +// Copyright 2018 plutoo +#include "skyline/nx/runtime/env.h" + +#include + +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/kernel/thread.h" +#include "skyline/nx/runtime/hosversion.h" +#include "skyline/nx/sf/hipc.h" + +static bool g_isNso = false; +static const char* g_loaderInfo = NULL; +static u64 g_loaderInfoSize = 0; +static Handle g_mainThreadHandle = INVALID_HANDLE; +static LoaderReturnFn g_loaderRetAddr = NULL; +static void* g_overrideHeapAddr = NULL; +static u64 g_overrideHeapSize = 0; +static void* g_overrideArgv = NULL; +static u64 g_syscallHints[2]; +static Handle g_processHandle = CUR_PROCESS_HANDLE; +static char* g_nextLoadPath = NULL; +static char* g_nextLoadArgv = NULL; +static Result g_lastLoadResult = 0; +static bool g_hasRandomSeed = false; +static u64 g_randomSeed[2] = {0, 0}; + +extern __attribute__((weak)) u32 __nx_applet_type; + +void envSetup(void* ctx, Handle main_thread, LoaderReturnFn saved_lr) { + // Detect NSO environment. + if (main_thread != -1) { + g_mainThreadHandle = main_thread; + g_isNso = true; + + // For NSO we assume kernelhax thus access to all syscalls. + g_syscallHints[0] = g_syscallHints[1] = -1ull; + + return; + } + + // Parse NRO config entries. + ConfigEntry* ent = ctx; + + while (ent->Key != EntryType_EndOfList) { + switch (ent->Key) { + case EntryType_MainThreadHandle: + g_mainThreadHandle = ent->Value[0]; + break; + + case EntryType_NextLoadPath: + g_nextLoadPath = (char*)ent->Value[0]; + g_nextLoadArgv = (char*)ent->Value[1]; + break; + + case EntryType_OverrideHeap: + g_overrideHeapAddr = (void*)ent->Value[0]; + g_overrideHeapSize = ent->Value[1]; + break; + + case EntryType_Argv: + g_overrideArgv = (void*)ent->Value[1]; + break; + + case EntryType_SyscallAvailableHint: + g_syscallHints[0] = ent->Value[0]; + g_syscallHints[1] = ent->Value[1]; + break; + case EntryType_ProcessHandle: + g_processHandle = ent->Value[0]; + break; + + case EntryType_LastLoadResult: + g_lastLoadResult = ent->Value[0]; + break; + + case EntryType_RandomSeed: + g_hasRandomSeed = true; + g_randomSeed[0] = ent->Value[0]; + g_randomSeed[1] = ent->Value[1]; + break; + case EntryType_HosVersion: + hosversionSet(ent->Value[0]); + break; + } + + ent++; + } + + g_loaderInfoSize = ent->Value[1]; + if (g_loaderInfoSize) { + g_loaderInfo = (const char*)(uintptr_t)ent->Value[0]; + } +} +const char* envGetLoaderInfo(void) { return g_loaderInfo; } + +u64 envGetLoaderInfoSize(void) { return g_loaderInfoSize; } + +Handle envGetMainThreadHandle(void) { return g_mainThreadHandle; } + +bool envIsNso(void) { return g_isNso; } + +bool envHasHeapOverride(void) { return g_overrideHeapAddr != NULL; } + +void* envGetHeapOverrideAddr(void) { return g_overrideHeapAddr; } + +u64 envGetHeapOverrideSize(void) { return g_overrideHeapSize; } + +bool envHasArgv(void) { return g_overrideArgv != NULL; } + +void* envGetArgv(void) { return g_overrideArgv; } + +bool envIsSyscallHinted(u8 svc) { + // return !!(g_syscallHints[svc/64] & (1ull << (svc%64))); + + return true; // assume all svc calls are avaiable +} + +Handle envGetOwnProcessHandle(void) { return g_processHandle; } + +void envSetOwnProcessHandle(Handle handle) { g_processHandle = handle; } + +LoaderReturnFn envGetExitFuncPtr(void) { return g_loaderRetAddr; } + +void envSetExitFuncPtr(LoaderReturnFn addr) { g_loaderRetAddr = addr; } + +Result envSetNextLoad(const char* path, const char* argv) { + strcpy(g_nextLoadPath, path); + + if (g_nextLoadArgv != NULL) { + if (argv == NULL) + g_nextLoadArgv[0] = '\0'; + else + strcpy(g_nextLoadArgv, argv); + } + + return 0; +} + +bool envHasNextLoad(void) { return g_nextLoadPath != NULL; } + +Result envGetLastLoadResult(void) { return g_lastLoadResult; } + +bool envHasRandomSeed(void) { return g_hasRandomSeed; } + +void envGetRandomSeed(u64 out[2]) { + out[0] = g_randomSeed[0]; + out[1] = g_randomSeed[1]; +} diff --git a/source/skyline/nx/runtime/hosversion.c b/source/skyline/nx/runtime/hosversion.c new file mode 100644 index 0000000..dfc2946 --- /dev/null +++ b/source/skyline/nx/runtime/hosversion.c @@ -0,0 +1,7 @@ +#include "skyline/nx/runtime/hosversion.h" + +static u32 g_hosVersion; + +u32 hosversionGet(void) { return __atomic_load_n(&g_hosVersion, __ATOMIC_SEQ_CST); } + +void hosversionSet(u32 version) { __atomic_store_n(&g_hosVersion, version, __ATOMIC_SEQ_CST); } \ No newline at end of file diff --git a/source/skyline/nx/runtime/init.c b/source/skyline/nx/runtime/init.c new file mode 100644 index 0000000..fe740ce --- /dev/null +++ b/source/skyline/nx/runtime/init.c @@ -0,0 +1,3 @@ +#include "types.h" + +void* __stack_top; \ No newline at end of file diff --git a/source/skyline/nx/sf/sessionmgr.c b/source/skyline/nx/sf/sessionmgr.c new file mode 100644 index 0000000..443134b --- /dev/null +++ b/source/skyline/nx/sf/sessionmgr.c @@ -0,0 +1,60 @@ +#include "skyline/nx/sf/sessionmgr.h" + +#include "skyline/nx/kernel/svc.h" +#include "skyline/nx/sf/cmif.h" + +Result sessionmgrCreate(SessionMgr* mgr, Handle root_session, u32 num_sessions) { + if (root_session == INVALID_HANDLE) return MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (num_sessions < 1 || num_sessions > NX_SESSION_MGR_MAX_SESSIONS) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (mgr->sessions[0] != INVALID_HANDLE) return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + + __builtin_memset(mgr, 0, sizeof(*mgr)); + mgr->sessions[0] = root_session; + mgr->num_sessions = num_sessions; + mgr->free_mask = (1U << num_sessions) - 1U; + + Result rc = 0; + for (u32 i = 1; R_SUCCEEDED(rc) && i < num_sessions; i++) + rc = cmifCloneCurrentObject(root_session, &mgr->sessions[i]); + + return rc; +} + +void sessionmgrClose(SessionMgr* mgr) { + if (mgr->sessions[0] == INVALID_HANDLE) return; + + mgr->sessions[0] = INVALID_HANDLE; + for (u32 i = 1; i < mgr->num_sessions; i++) { + if (mgr->sessions[i] != INVALID_HANDLE) { + cmifMakeCloseRequest(armGetTls(), 0); + svcSendSyncRequest(mgr->sessions[i]); + svcCloseHandle(mgr->sessions[i]); + mgr->sessions[i] = INVALID_HANDLE; + } + } +} + +int sessionmgrAttachClient(SessionMgr* mgr) { + mutexLock(&mgr->mutex); + int slot; + for (;;) { + slot = __builtin_ffs(mgr->free_mask) - 1; + if (slot >= 0) break; + mgr->is_waiting = true; + condvarWait(&mgr->condvar, &mgr->mutex); + } + mgr->free_mask &= ~(1U << slot); + mutexUnlock(&mgr->mutex); + return slot; +} + +void sessionmgrDetachClient(SessionMgr* mgr, int slot) { + mutexLock(&mgr->mutex); + mgr->free_mask |= 1U << slot; + if (mgr->is_waiting) { + mgr->is_waiting = false; + condvarWakeOne(&mgr->condvar); + } + mutexUnlock(&mgr->mutex); +} diff --git a/source/skyline/utils/armutils.s b/source/skyline/utils/armutils.s new file mode 100644 index 0000000..1aa6ac8 --- /dev/null +++ b/source/skyline/utils/armutils.s @@ -0,0 +1,82 @@ +.macro CODE_BEGIN name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 + .cfi_startproc +\name: +.endm + +.macro CODE_END + .cfi_endproc +.endm + +.macro armBackupRegisters + sub sp, sp, #0x100 + stp x29, x30, [sp, #0xE8] + stp x27, x28, [sp, #0xD8] + stp x25, x26, [sp, #0xC8] + stp x23, x24, [sp, #0xB8] + stp x21, x22, [sp, #0xA8] + stp x19, x20, [sp, #0x98] + stp x17, x18, [sp, #0x88] + stp x15, x16, [sp, #0x78] + stp x13, x14, [sp, #0x68] + stp x11, x12, [sp, #0x58] + stp x9, x10, [sp, #0x48] + stp x7, x8, [sp, #0x38] + stp x5, x6, [sp, #0x28] + stp x3, x4, [sp, #0x18] + stp x1, x2, [sp, #0x8] + str x0, [sp, #0x0] +.endm + +.macro armRecoverRegisters + ldr x0, [sp, #0x0] + ldp x1, x2, [sp, #0x8] + ldp x3, x4, [sp, #0x18] + ldp x5, x6, [sp, #0x28] + ldp x7, x8, [sp, #0x38] + ldp x9, x10, [sp, #0x48] + ldp x11, x12, [sp, #0x58] + ldp x13, x14, [sp, #0x68] + ldp x15, x16, [sp, #0x78] + ldp x17, x18, [sp, #0x88] + ldp x19, x20, [sp, #0x98] + ldp x21, x22, [sp, #0xA8] + ldp x23, x24, [sp, #0xB8] + ldp x25, x26, [sp, #0xC8] + ldp x27, x28, [sp, #0xD8] + ldp x29, x30, [sp, #0xE8] + add sp, sp, #0x100 +.endm + + +CODE_BEGIN inlineHandlerImpl + armBackupRegisters + + // branch and link to hook callback + mov x0, sp + ldr x16, [x17, #8] + blr x16 + + armRecoverRegisters + + // branch to trampoline + ldr x16, [x17, #0x10] + br x16 + +CODE_END + + +.global inlineHandlerStart +.global inlineHandlerEnd + +CODE_BEGIN inlineHandler +inlineHandlerStart: + adr x17, inlineHandlerEnd + ldr x16, [x17] + br x16 + +CODE_END +inlineHandlerEnd: diff --git a/source/skyline/utils/cpputils.cpp b/source/skyline/utils/cpputils.cpp new file mode 100644 index 0000000..ee80d51 --- /dev/null +++ b/source/skyline/utils/cpputils.cpp @@ -0,0 +1,197 @@ +#include "skyline/utils/cpputils.hpp" + +#include "nn/nn.h" +#include "skyline/utils/utils.h" + +namespace skyline { + +std::string utils::g_RomMountStr = "rom:/"; + +u64 utils::g_MainTextAddr; +u64 utils::g_MainRodataAddr; +u64 utils::g_MainDataAddr; +u64 utils::g_MainBssAddr; +u64 utils::g_MainHeapAddr; + +nn::settings::system::FirmwareVersion utils::g_CachedFwVer; + +void utils::init() { + // find .text + utils::g_MainTextAddr = + memGetMapAddr((u64)nninitStartup); // nninitStartup can be reasonably assumed to be exported by main + // find .rodata + utils::g_MainRodataAddr = memNextMap(utils::g_MainTextAddr); + // find .data + utils::g_MainDataAddr = memNextMap(utils::g_MainRodataAddr); + // find .bss + utils::g_MainBssAddr = memNextMap(utils::g_MainDataAddr); + // find heap + utils::g_MainHeapAddr = memNextMapOfType(utils::g_MainBssAddr, MemType_Heap); + // Causes a crash on some games, might want to do this differently. (Calling SVCs implemented later?) + // nn::settings::system::GetFirmwareVersion(&g_CachedFwVer); +} + +bool endsWith(std::string const& str1, std::string const& str2) { + return str2.size() <= str1.size() && str1.find(str2, str1.size() - str2.size()) != str1.npos; +} + +Result utils::walkDirectory(std::string const& root, + std::function)> callback, + bool recursive) { + Result r; + + nn::fs::DirectoryHandle rootHandle; + R_TRY(nn::fs::OpenDirectory(&rootHandle, root.c_str(), nn::fs::OpenDirectoryMode_All)); + + s64 entryCount; + r = nn::fs::GetDirectoryEntryCount(&entryCount, rootHandle); + if (R_FAILED(r)) { + nn::fs::CloseDirectory(rootHandle); + return r; + } + + nn::fs::DirectoryEntry* entryBuffer = new nn::fs::DirectoryEntry[entryCount]; + r = nn::fs::ReadDirectory(&entryCount, entryBuffer, rootHandle, entryCount); + nn::fs::CloseDirectory(rootHandle); + + if (R_FAILED(r)) goto exit; + + for (int i = 0; i < entryCount; i++) { + nn::fs::DirectoryEntry& entry = entryBuffer[i]; + + std::string entryStr(entry.name); + std::string fullPath = root; + + while (endsWith(fullPath, "/")) fullPath.pop_back(); + fullPath += "/"; + fullPath += entryStr; + + if (entry.type == nn::fs::DirectoryEntryType_Directory && recursive) r = walkDirectory(fullPath, callback); + + if (R_FAILED(r)) goto exit; + + callback(entry, std::make_shared(fullPath)); + } + +exit: + delete[] entryBuffer; + return r; +} + +Result utils::readEntireFile(std::string const& str, void** dataptr, u64* length) { + if (dataptr == NULL) return -1; + nn::fs::FileHandle handle; + R_TRY(nn::fs::OpenFile(&handle, str.c_str(), nn::fs::OpenMode_Read)); + + s64 size; + Result r = nn::fs::GetFileSize(&size, handle); + if (R_FAILED(r)) { + nn::fs::CloseFile(handle); + return r; + } + + if (length != NULL) *length = size; + + void* bin = malloc(size); + r = nn::fs::ReadFile(handle, 0, bin, size); + if (R_FAILED(r)) { + free(bin); + } else + *dataptr = bin; + nn::fs::CloseFile(handle); + return r; +} + +Result utils::readFile(std::string const& str, s64 offset, void* data, size_t length) { + if (data == NULL) return -1; + nn::fs::FileHandle handle; + R_TRY(nn::fs::OpenFile(&handle, str.c_str(), nn::fs::OpenMode_Read)); + + Result r; + s64 fileSize; + r = nn::fs::GetFileSize(&fileSize, handle); + if (R_FAILED(r)) { + nn::fs::CloseFile(handle); + return r; + } + + length = MIN((s64)length, fileSize); + + r = nn::fs::ReadFile(handle, offset, data, length); + + nn::fs::CloseFile(handle); + return r; +} + +Result utils::writeFile(std::string const& str, s64 offset, void* data, size_t length) { + nn::fs::DirectoryEntryType entryType; + Result rc = nn::fs::GetEntryType(&entryType, str.c_str()); + + if (rc == 0x202) { // Path does not exist + R_TRY(nn::fs::CreateFile(str.c_str(), offset + length)); + } else if (R_FAILED(rc)) + return rc; + + if (entryType == nn::fs::DirectoryEntryType_Directory) return -1; + + nn::fs::FileHandle handle; + R_TRY(nn::fs::OpenFile(&handle, str.c_str(), nn::fs::OpenMode_ReadWrite | nn::fs::OpenMode_Append)); + + Result r; + s64 fileSize; + r = nn::fs::GetFileSize(&fileSize, handle); + if (R_FAILED(r)) { + nn::fs::CloseFile(handle); + return r; + } + + if (fileSize < offset + (s64)length) { // make sure we have enough space + r = nn::fs::SetFileSize(handle, offset + length); + + if (R_FAILED(r)) { + nn::fs::CloseFile(handle); + return r; + } + } + + r = nn::fs::WriteFile(handle, offset, data, length, + nn::fs::WriteOption::CreateOption(nn::fs::WriteOptionFlag_Flush)); + + nn::fs::CloseFile(handle); + return r; +} + +Result utils::entryCount(u64* out, std::string const& path, nn::fs::DirectoryEntryType entryType) { + nn::fs::DirectoryEntryType pathType; + R_TRY(nn::fs::GetEntryType(&pathType, path.c_str())); + + if (pathType == nn::fs::DirectoryEntryType_File) { + *out = 0; + return 0; + } + + return walkDirectory( + path, + [out, entryType](nn::fs::DirectoryEntry const& entry, std::shared_ptr) { + if (entry.type == entryType) (*out)++; + }, + false); // not recursive +} + +void* utils::getRegionAddress(skyline::utils::region region) { + switch (region) { + case region::Text: + return (void*)g_MainTextAddr; + case region::Rodata: + return (void*)g_MainRodataAddr; + case region::Data: + return (void*)g_MainDataAddr; + case region::Bss: + return (void*)g_MainBssAddr; + case region::Heap: + return (void*)g_MainHeapAddr; + default: + return NULL; + } +} +}; // namespace skyline diff --git a/source/skyline/utils/ipc.cpp b/source/skyline/utils/ipc.cpp new file mode 100644 index 0000000..ca26753 --- /dev/null +++ b/source/skyline/utils/ipc.cpp @@ -0,0 +1,83 @@ +#include "skyline/utils/ipc.hpp" + +#include "skyline/utils/cpputils.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "skyline/nx/sf/cmif.h" + +#ifdef __cplusplus +} +#endif + +namespace skyline::utils { + +Result nnServiceCreate(Service* srv, const char* name) { + // open session + nn::sf::hipc::InitializeHipcServiceResolution(); + nn::svc::Handle svcHandle; + nn::sf::hipc::ConnectToHipcService(&svcHandle, name); + nn::sf::hipc::FinalizeHipcServiceResolution(); + + void* base = nn::sf::hipc::GetMessageBufferOnTls(); + + cmifMakeControlRequest(base, 3, 0); + R_TRY(nn::sf::hipc::SendSyncRequest(svcHandle, base, 0x100)); + + CmifResponse resp = {}; + R_TRY(cmifParseResponse(&resp, base, false, sizeof(u16))); + + // build srv obj + srv->session = svcHandle.handle; + srv->own_handle = 1; + srv->object_id = 0; + srv->pointer_buffer_size = *(u16*)resp.data; + + return 0; +} + +void nnServiceClose(Service* s) { + void* base = nn::sf::hipc::GetMessageBufferOnTls(); + + if (s->own_handle || s->object_id) { + cmifMakeCloseRequest(base, s->own_handle ? 0 : s->object_id); + nn::sf::hipc::SendSyncRequest(s->session, base, 0x100); + if (s->own_handle) nn::sf::hipc::CloseClientSessionHandle(s->session); + } + *s = (Service){}; +} + +Result Ipc::getOwnProcessHandle(Handle* handleOut) { + Service srv; + u64 pid; + + svcGetProcessId(&pid, 0xFFFF8001); + // skyline::utils::writeFile("sd:/tmp/pid.bin", 0, &pid, sizeof(pid)); + + nnServiceCreate(&srv, "pm:dmnt"); + + Handle tmp_handle; + + struct { + u64 loc; + u8 status; + } out; + + Result rc = nnServiceDispatchInOut(&srv, 65000, pid, out, .out_handle_attrs = {SfOutHandleAttr_HipcCopy}, + .out_handles = &tmp_handle, ); + + nnServiceClose(&srv); + + if (R_SUCCEEDED(rc)) { + if (handleOut) { + *handleOut = tmp_handle; + } else { + svcCloseHandle(tmp_handle); + } + } + + return 0; +} +} // namespace skyline::utils diff --git a/source/skyline/utils/utils.c b/source/skyline/utils/utils.c new file mode 100644 index 0000000..4ee854e --- /dev/null +++ b/source/skyline/utils/utils.c @@ -0,0 +1,61 @@ +#include "skyline/utils/utils.h" + +u32 previousPowerOfTwo(u32 x) { + if (x == 0) { + return 0; + } + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return x - (x >> 1); +} + +Result memGetMap(MemoryInfo* info, u64 addr) { + u32 map; + return svcQueryMemory(info, &map, addr); +} + +u64 memGetMapAddr(u64 addr) { + MemoryInfo map; + memGetMap(&map, addr); + return map.addr; +} + +u64 memNextMap(u64 addr) { + MemoryInfo map; + memGetMap(&map, addr); + memGetMap(&map, map.addr + map.size); + + if (map.type != MemType_Unmapped) return map.addr; + + return memNextMap(map.addr); +} + +u64 memNextMapOfType(u64 addr, u32 type) { + MemoryInfo map; + memGetMap(&map, addr); + memGetMap(&map, map.addr + map.size); + + if (map.type == type) return map.addr; + + return memNextMapOfType(map.addr, type); +} + +u64 memNextMapOfPerm(u64 addr, u32 perm) { + MemoryInfo map; + memGetMap(&map, addr); + memGetMap(&map, map.addr + map.size); + + if (map.perm == perm) return map.addr; + + return memNextMapOfType(map.addr, perm); +} + +u64 get_program_id() { + u64 program_id; + svcGetInfo(&program_id, 18, CUR_PROCESS_HANDLE, 0); + return program_id; +} + diff --git a/switch.specs b/switch.specs new file mode 100644 index 0000000..b495c5a --- /dev/null +++ b/switch.specs @@ -0,0 +1,7 @@ +%rename link old_link + +*link: +%(old_link) -T ../linkerscripts/application.ld --shared --gc-sections --build-id=sha1 + +*startfile: +crti%O%s crtbegin%O%s \ No newline at end of file