├── .github └── workflow_templates │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── addons ├── DebugRenderer │ ├── CMakeLists.txt │ ├── README.md │ ├── data │ │ ├── font.hkf │ │ ├── shader.bin │ │ ├── shader_fsh.glsl │ │ └── shader_vsh.glsl │ ├── include │ │ └── hk │ │ │ └── gfx │ │ │ ├── DebugRenderer.h │ │ │ ├── DebugRendererSettings.h │ │ │ └── Font.h │ ├── src │ │ └── hk │ │ │ └── gfx │ │ │ ├── DebugRenderer.cpp │ │ │ ├── DebugRendererImpl.cpp │ │ │ └── Font.cpp │ └── tools │ │ └── generate_font.py ├── ExpHeap │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── include │ │ └── hk │ │ │ └── mem │ │ │ └── ExpHeap.h │ └── src │ │ ├── ams │ │ ├── lmem │ │ │ ├── impl │ │ │ │ ├── lmem_impl_common.hpp │ │ │ │ ├── lmem_impl_common_heap.cpp │ │ │ │ ├── lmem_impl_common_heap.hpp │ │ │ │ ├── lmem_impl_exp_heap.cpp │ │ │ │ └── lmem_impl_exp_heap.hpp │ │ │ ├── lmem_common.cpp │ │ │ ├── lmem_common.hpp │ │ │ ├── lmem_exp_heap.cpp │ │ │ └── lmem_exp_heap.hpp │ │ └── util │ │ │ ├── util_endian.hpp │ │ │ ├── util_fourcc.hpp │ │ │ ├── util_intrusive_list.hpp │ │ │ └── util_parent_of_member.hpp │ │ └── hk │ │ └── mem │ │ └── ExpHeap.cpp ├── HeapSourceBss │ ├── CMakeLists.txt │ ├── README.md │ ├── include │ │ └── hk │ │ │ └── mem │ │ │ └── BssHeap.h │ └── src │ │ └── hk │ │ └── mem │ │ ├── BssHeap.cpp │ │ └── BssHeapWrappers.cpp ├── HeapSourceDynamic │ ├── CMakeLists.txt │ ├── README.md │ ├── src │ │ └── hk │ │ │ └── mem │ │ │ └── DynamicHeapWrappers.cpp │ └── syms │ │ └── hk │ │ └── mem │ │ └── DynamicHeapWrappers.sym ├── ImGui │ ├── CMakeLists.txt │ ├── README.md │ ├── data │ │ ├── shader.bin │ │ ├── shader_fsh.glsl │ │ └── shader_vsh.glsl │ ├── include │ │ └── hk │ │ │ └── gfx │ │ │ ├── ImGuiBackendNvn.h │ │ │ └── ImGuiConfig.h │ └── src │ │ └── hk │ │ └── gfx │ │ ├── ImGuiBackendNvn.cpp │ │ └── ImGuiBackendNvnImpl.cpp ├── LogManager │ ├── CMakeLists.txt │ ├── README.md │ └── include │ │ └── hk │ │ └── nn │ │ └── diag.h └── Nvn │ ├── CMakeLists.txt │ ├── README.md │ ├── include │ └── hk │ │ ├── gfx │ │ ├── Nvn.h │ │ ├── Shader.h │ │ ├── Texture.h │ │ ├── Ubo.h │ │ ├── Util.h │ │ └── Vertex.h │ │ └── nvn │ │ ├── MemoryBuffer.h │ │ ├── nvn.h │ │ ├── nvn_Cpp.h │ │ ├── nvn_CppFuncPtrBase.h │ │ ├── nvn_CppMethods.h │ │ ├── nvn_FuncPtrBase.h │ │ └── nvn_FuncPtrInline.h │ ├── src │ └── hk │ │ ├── gfx │ │ ├── NvnBootstrapOverride.cpp │ │ ├── ShaderImpl.cpp │ │ ├── TextureImpl.cpp │ │ └── UboImpl.cpp │ │ └── nvn │ │ └── nvn_CppFuncPtrImpl.cpp │ └── tools │ └── compile_shader.py ├── cmake ├── addons.cmake ├── apply_config.cmake ├── bin2s.cmake ├── deploy.cmake ├── generate_exefs.cmake ├── impl │ └── bin2s.cmake ├── module.cmake ├── rtld_dummy.cmake ├── sail.cmake ├── toolchain.cmake └── watch.cmake ├── data ├── link.aarch64.ld ├── link.armv7a.ld ├── misc.ld ├── replace_bin2s.sed └── visibility.txt ├── hakkun ├── CMakeLists.txt ├── include │ ├── hk │ │ ├── Result.h │ │ ├── diag │ │ │ ├── diag.h │ │ │ └── results.h │ │ ├── hook │ │ │ ├── InstrUtil.h │ │ │ ├── MapUtil.h │ │ │ ├── Replace.h │ │ │ ├── Trampoline.h │ │ │ ├── a64 │ │ │ │ └── Assembler.h │ │ │ └── results.h │ │ ├── init │ │ │ └── module.h │ │ ├── os │ │ │ └── Mutex.h │ │ ├── ro │ │ │ ├── RoModule.h │ │ │ ├── RoUtil.h │ │ │ └── results.h │ │ ├── sail │ │ │ ├── detail.h │ │ │ └── init.h │ │ ├── svc │ │ │ ├── api.h │ │ │ └── types.h │ │ ├── types.h │ │ └── util │ │ │ ├── Algorithm.h │ │ │ ├── BitArray.h │ │ │ ├── Context.h │ │ │ ├── FixedCapacityArray.h │ │ │ ├── InitializeGuard.h │ │ │ ├── Lambda.h │ │ │ ├── Math.h │ │ │ ├── PoolAllocator.h │ │ │ ├── Random.h │ │ │ ├── Singleton.h │ │ │ ├── Storage.h │ │ │ ├── TemplateString.h │ │ │ └── hash.h │ └── rtld │ │ ├── ModuleHeader.h │ │ ├── RoModule.h │ │ ├── RoModuleList.h │ │ ├── elf.h │ │ └── types.h ├── sail │ ├── CMakeLists.txt │ ├── include │ │ ├── config.h │ │ ├── fakelib.h │ │ ├── hash.h │ │ ├── parser.h │ │ ├── symbol.h │ │ ├── types.h │ │ └── util.h │ └── src │ │ ├── config.cpp │ │ ├── fakelib.cpp │ │ ├── main.cpp │ │ └── parser.cpp └── src │ ├── hk │ ├── diag │ │ └── diag.cpp │ ├── hook │ │ ├── MapUtil.cpp │ │ └── Trampoline.cpp │ ├── init │ │ ├── module.S │ │ └── module.cpp │ ├── os │ │ └── Libcxx.cpp │ ├── ro │ │ ├── RoModule.cpp │ │ └── RoUtil.cpp │ ├── sail │ │ ├── detail.cpp │ │ └── init.cpp │ ├── svc │ │ ├── api.aarch64.S │ │ └── api.armv7a.S │ └── util │ │ └── Context.cpp │ └── rtld │ ├── DummyRtld.cpp │ └── RoModule.cpp └── tools ├── bake_hashes.py ├── deploy.py ├── elf2nso.py ├── nso.py ├── setup_libcxx.py ├── setup_libcxx_prepackaged.py └── setup_sail.py /.github/workflow_templates/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [workflow_dispatch, push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | with: 12 | submodules: true 13 | - name: Set up dependencies 14 | run: | 15 | sudo apt-get update 16 | sudo apt-get install -y python3 python3-pip git wget 17 | python sys/tools/setup_libcxx_prepackaged.py 18 | python sys/tools/setup_sail.py 19 | git clone https://github.com/switchbrew/switch-tools && cd switch-tools && ./autogen.sh && ./configure && make -j $(nproc) && cd .. 20 | wget https://apt.llvm.org/llvm.sh 21 | chmod +x llvm.sh 22 | sudo ./llvm.sh 19 23 | sudo apt-get install -y clang-19 llvm-19 lld-19 24 | python3 -m pip install pyelftools mmh lz4 25 | - name: Build 26 | run: | 27 | export SWITCHTOOLS=$(pwd)/switch-tools 28 | cmake -B build -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_ASM_COMPILER=clang-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld-19" -DCMAKE_MODULE_LINKER_FLAGS="-fuse-ld=lld-19" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld-19" 29 | cmake --build build -j $(nproc) 30 | - name: Upload Artifacts 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: build 34 | path: | 35 | build/*.nss 36 | build/main.npdm 37 | build/*.nso 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .cache/ 3 | compile_commands.json 4 | lib/std 5 | __pycache__/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 2 | 3 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 4 | 5 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 6 | 7 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /addons/DebugRenderer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include_directories(include) 4 | set(SOURCES 5 | src/hk/gfx/DebugRenderer.cpp 6 | src/hk/gfx/Font.cpp 7 | ) 8 | add_library(DebugRenderer ${SOURCES}) 9 | 10 | include(../../../config/config.cmake) 11 | include(../../cmake/apply_config.cmake) 12 | include(../../cmake/bin2s.cmake) 13 | 14 | list(FIND HAKKUN_ADDONS Nvn HAS_NVN) 15 | if (HAS_NVN EQUAL -1) 16 | message(FATAL_ERROR "Enable Nvn addon to use DebugRenderer") 17 | endif() 18 | 19 | embed_file(DebugRenderer ${CMAKE_CURRENT_SOURCE_DIR}/data/shader.bin shader 0x1000 TRUE) 20 | embed_file(DebugRenderer ${CMAKE_CURRENT_SOURCE_DIR}/data/font.hkf font 0x1000 TRUE) 21 | 22 | apply_config(DebugRenderer) 23 | 24 | target_compile_definitions(DebugRenderer PRIVATE) 25 | target_include_directories(DebugRenderer PRIVATE include/hk ${CMAKE_CURRENT_BINARY_DIR}) 26 | 27 | if (NOT DEFINED HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE) 28 | message(WARNING "HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE not defined, using 0x1000") 29 | set(HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE 0x1000) 30 | endif() 31 | 32 | target_compile_definitions(DebugRenderer PUBLIC HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE=${HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE}) 33 | 34 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../) 35 | -------------------------------------------------------------------------------- /addons/DebugRenderer/README.md: -------------------------------------------------------------------------------- 1 | # DebugRenderer 2 | 3 | This addon provides an NVN debug renderer that can draw 2D triangles, quads, and text. The default font is MS Gothic, but new ones can be generated with the provided script. See [Hakkun-Example](https://github.com/fruityloops1/Hakkun-Example) for example usage. 4 | Depends on Nvn. -------------------------------------------------------------------------------- /addons/DebugRenderer/data/font.hkf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruityloops1/LibHakkun/afee0a7c33e6a89a38fb858e5d575e473f59c065/addons/DebugRenderer/data/font.hkf -------------------------------------------------------------------------------- /addons/DebugRenderer/data/shader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruityloops1/LibHakkun/afee0a7c33e6a89a38fb858e5d575e473f59c065/addons/DebugRenderer/data/shader.bin -------------------------------------------------------------------------------- /addons/DebugRenderer/data/shader_fsh.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(location = 0) in vec2 vtxUv; 4 | layout(location = 1) in vec4 vtxColor; 5 | 6 | layout(binding = 0) uniform sampler2D tex; 7 | 8 | layout(location = 0) out vec4 outColor; 9 | 10 | vec4 toLinear(vec4 sRGB) { 11 | bvec3 cutoff = lessThan(sRGB.rgb, vec3(0.04045)); 12 | vec3 higher = pow((sRGB.rgb + vec3(0.055)) / vec3(1.055), vec3(2.4)); 13 | vec3 lower = sRGB.rgb / vec3(12.92); 14 | 15 | return vec4(mix(higher, lower, cutoff), sRGB.a); 16 | } 17 | 18 | void main() { outColor = toLinear(vtxColor) * texture(tex, vtxUv); } 19 | -------------------------------------------------------------------------------- /addons/DebugRenderer/data/shader_vsh.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(location = 0) in vec2 inPos; 4 | layout(location = 1) in vec2 inUv; 5 | layout(location = 2) in vec4 inColor; 6 | 7 | layout(location = 0) out vec2 vtxUv; 8 | layout(location = 1) out vec4 vtxColor; 9 | 10 | vec2 toNDC(vec2 coords) { 11 | return vec2(coords.x * 2.0 - 1.0, -(coords.y * 2.0 - 1.0)); 12 | } 13 | 14 | void main() { 15 | gl_Position = vec4(toNDC(inPos), 0.0, 1.0); 16 | vtxUv = inUv; 17 | vtxColor = inColor; 18 | } -------------------------------------------------------------------------------- /addons/DebugRenderer/include/hk/gfx/DebugRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/gfx/Font.h" 4 | #include "hk/gfx/Vertex.h" 5 | #include "hk/util/Math.h" 6 | 7 | namespace hk::gfx { 8 | 9 | class DebugRendererImpl; 10 | #ifdef HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE 11 | constexpr static size cVtxBufferSize = alignUpPage(HAKKUN_DEBUGRENDERER_VTXBUFFER_SIZE * sizeof(Vertex)); 12 | constexpr static size cDebugRendererImplSize = 49152 + cVtxBufferSize; // This needs to GO 13 | #else 14 | constexpr static size cDebugRendererImplSize = 0; 15 | #endif 16 | 17 | class Texture; 18 | 19 | class DebugRenderer { 20 | u8 mStorage[cDebugRendererImplSize] __attribute__((aligned(cPageSize))); 21 | 22 | static DebugRenderer sInstance; 23 | 24 | public: 25 | DebugRenderer(); 26 | 27 | DebugRendererImpl* get() { return reinterpret_cast(mStorage); } 28 | 29 | static DebugRenderer* instance() { return &sInstance; } 30 | 31 | void installHooks(bool initializeAutomatically = true); 32 | bool tryInitialize(); 33 | 34 | void setResolution(const util::Vector2f& res); 35 | void setGlyphSize(const util::Vector2f& size); 36 | void setGlyphSize(float scale); 37 | void setGlyphHeight(float height); 38 | void setFont(Font* font); 39 | void setCursor(const util::Vector2f& pos); 40 | void setPrintColor(u32 color); 41 | 42 | void bindTexture(const TextureHandle& tex); 43 | void bindDefaultTexture(); 44 | 45 | void clear(); 46 | void begin(void* commandBuffer /* nvn::CommandBuffer* */); 47 | void drawTri(const Vertex& a, const Vertex& b, const Vertex& c); 48 | void drawQuad(const Vertex& tl, const Vertex& tr, const Vertex& br, const Vertex& bl, f32 round = 0.0f, u32 numSides = 4); 49 | void drawLine(const Vertex& a, const Vertex& b, f32 width); 50 | void drawCircle(const Vertex& center, f32 radius, f32 width = 1.0f, u32 numSides = 16); 51 | void drawDisk(const Vertex& center, f32 radius, u32 numSides = 16); 52 | util::Vector2f drawString(const util::Vector2f& pos, const char* str, u32 color); 53 | util::Vector2f drawString(const util::Vector2f& pos, const char16_t* str, u32 color); 54 | void printf(const char* fmt, ...); 55 | void end(); 56 | 57 | void setDevice(void* device /* nvn::Device* */); 58 | void* getDevice(); 59 | void setPrevTexturePool(void* pool /* nvn::TexturePool* */); 60 | void setPrevSamplerPool(void* pool /* nvn::SamplerPool* */); 61 | }; 62 | 63 | } // namespace hk::gfx 64 | -------------------------------------------------------------------------------- /addons/DebugRenderer/include/hk/gfx/DebugRendererSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nvn/nvn_Cpp.h" 4 | 5 | namespace hk::gfx { 6 | 7 | struct DebugRendererSettings { 8 | nvn::PolygonMode polygonMode = nvn::PolygonMode::FILL; 9 | nvn::FrontFace frontFace = nvn::FrontFace::CCW; 10 | 11 | nvn::LogicOp logicOp = nvn::LogicOp::COPY; 12 | nvn::AlphaFunc alphaFunc = nvn::AlphaFunc::ALWAYS; 13 | bool blendEnable[8] = { true, true, true, true, true, true, true, true }; 14 | 15 | nvn::BlendFunc srcBlendFunc = nvn::BlendFunc::SRC_ALPHA; 16 | nvn::BlendFunc dstBlendFunc = nvn::BlendFunc::ONE_MINUS_SRC_ALPHA; 17 | nvn::BlendFunc srcBlendFuncAlpha = nvn::BlendFunc::ONE; 18 | nvn::BlendFunc dstBlendFuncAlpha = nvn::BlendFunc::ZERO; 19 | 20 | nvn::BlendEquation blendEquationColor = nvn::BlendEquation::ADD; 21 | nvn::BlendEquation blendEquationAlpha = nvn::BlendEquation::ADD; 22 | 23 | bool depthWriteEnable = false; 24 | }; 25 | 26 | } // namespace hk::gfx 27 | -------------------------------------------------------------------------------- /addons/DebugRenderer/include/hk/gfx/Font.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/gfx/Texture.h" 5 | #include "hk/types.h" 6 | #include "hk/util/Math.h" 7 | #include "hk/util/Storage.h" 8 | #include 9 | 10 | namespace hk::gfx { 11 | 12 | class Font { 13 | constexpr static int cCharsPerRow = 32; 14 | constexpr static float cCharWidthUv = 1.0f / cCharsPerRow; 15 | 16 | const char16_t* mCharset = nullptr; 17 | size mNumChars = 0; 18 | util::Storage mTexture; 19 | 20 | struct FontHeader { 21 | size charsetSize; 22 | int width; 23 | int height; 24 | size texSize; 25 | u8 data[]; 26 | }; 27 | 28 | public: 29 | float getCharWidthUv() const { 30 | return cCharWidthUv; 31 | } 32 | 33 | float getCharHeightUv() const { 34 | return 1.0f / (mNumChars / float(cCharsPerRow)); 35 | } 36 | 37 | Font(void* fontData, void* device, void* memory); 38 | 39 | static size calcMemorySize(void* nvnDevice, void* fontData) { 40 | FontHeader* header = reinterpret_cast(fontData); 41 | return alignUpPage((header->charsetSize + 1) * sizeof(char16_t)) + Texture::calcMemorySize(nvnDevice, header->width * header->height * sizeof(u8)); 42 | } 43 | 44 | Texture& getTexture() { return *mTexture.get(); } 45 | 46 | ~Font() { 47 | mTexture.tryDestroy(); 48 | } 49 | 50 | template 51 | util::Vector2f getCharUvTopLeft(Char value) { 52 | size idx; 53 | for (idx = 0; idx < mNumChars; idx++) { // meh 54 | if (mCharset[idx] == value) 55 | break; 56 | } 57 | 58 | int row = idx / cCharsPerRow; 59 | int col = idx % cCharsPerRow; 60 | 61 | return { col * getCharWidthUv(), row * getCharHeightUv() }; 62 | } 63 | 64 | util::Vector2f getGlyphSize() { 65 | auto texSize = util::Vector2f(mTexture.get()->getSize()); 66 | return texSize / util::Vector2f(cCharsPerRow, std::ceilf(mNumChars / float(cCharsPerRow))); 67 | } 68 | }; 69 | 70 | } // namespace hk::gfx 71 | -------------------------------------------------------------------------------- /addons/DebugRenderer/src/hk/gfx/DebugRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/gfx/DebugRenderer.h" 5 | #include "hk/hook/Trampoline.h" 6 | #include "hk/util/Math.h" 7 | 8 | #include "nvn/nvn_Cpp.h" 9 | #include "nvn/nvn_CppFuncPtrBase.h" 10 | 11 | #include "DebugRendererImpl.cpp" 12 | 13 | namespace hk::gfx { 14 | 15 | static_assert(sizeof(DebugRendererImpl) == sizeof(DebugRenderer)); 16 | 17 | DebugRenderer DebugRenderer::sInstance; 18 | 19 | DebugRenderer::DebugRenderer() { 20 | new (get()) DebugRendererImpl; 21 | } 22 | 23 | // wrappers 24 | 25 | bool DebugRenderer::tryInitialize() { return get()->tryInitialize(); } 26 | 27 | void DebugRenderer::setResolution(const util::Vector2f& res) { get()->setResolution(res); } 28 | void DebugRenderer::setGlyphSize(const util::Vector2f& size) { get()->setGlyphSize(size); } 29 | void DebugRenderer::setGlyphSize(float scale) { get()->setGlyphSize(scale); } 30 | void DebugRenderer::setGlyphHeight(float height) { get()->setGlyphHeight(height); } 31 | void DebugRenderer::setFont(Font* font) { get()->setFont(font); } 32 | void DebugRenderer::setCursor(const util::Vector2f& pos) { get()->setCursor(pos); } 33 | void DebugRenderer::setPrintColor(u32 color) { get()->setPrintColor(color); } 34 | 35 | void DebugRenderer::bindTexture(const TextureHandle& tex) { get()->bindTexture(tex); } 36 | void DebugRenderer::bindDefaultTexture() { get()->bindDefaultTexture(); } 37 | 38 | void DebugRenderer::clear() { get()->clear(); } 39 | void DebugRenderer::begin(void* commandBuffer) { get()->begin(static_cast(commandBuffer)); } 40 | void DebugRenderer::drawTri(const Vertex& a, const Vertex& b, const Vertex& c) { get()->drawTri(a, b, c); } 41 | void DebugRenderer::drawQuad(const Vertex& tl, const Vertex& tr, const Vertex& br, const Vertex& bl, f32 round, u32 numSides) { get()->drawQuad(tl, tr, br, bl, round, numSides); } 42 | void DebugRenderer::drawLine(const Vertex& a, const Vertex& b, f32 width) { get()->drawLine(a, b, width); } 43 | void DebugRenderer::drawCircle(const Vertex& center, f32 radius, f32 width, u32 numSides) { get()->drawCircle(center, radius, width, numSides); } 44 | void DebugRenderer::drawDisk(const Vertex& center, f32 radius, u32 numSides) { get()->drawDisk(center, radius, numSides); } 45 | util::Vector2f DebugRenderer::drawString(const util::Vector2f& pos, const char* str, u32 color) { return get()->drawString(pos, str, color); } 46 | util::Vector2f DebugRenderer::drawString(const util::Vector2f& pos, const char16_t* str, u32 color) { return get()->drawString(pos, str, color); } 47 | void DebugRenderer::printf(const char* fmt, ...) { 48 | std::va_list arg; 49 | va_start(arg, fmt); 50 | 51 | get()->printf(fmt, arg); 52 | 53 | va_end(arg); 54 | } 55 | void DebugRenderer::end() { get()->end(); } 56 | 57 | void DebugRenderer::setDevice(void* device) { get()->setDevice(static_cast(device)); } 58 | void* DebugRenderer::getDevice() { return get()->getDevice(); } 59 | void DebugRenderer::setPrevTexturePool(void* pool) { get()->setPrevTexturePool(static_cast(pool)); } 60 | void DebugRenderer::setPrevSamplerPool(void* pool) { get()->setPrevSamplerPool(static_cast(pool)); } 61 | 62 | } // namespace hk::gfx 63 | -------------------------------------------------------------------------------- /addons/DebugRenderer/src/hk/gfx/Font.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/gfx/Font.h" 2 | #include "gfx/Nvn.h" 3 | #include "hk/diag/diag.h" 4 | #include "nvn/nvn_Cpp.h" 5 | #include "nvn/nvn_CppMethods.h" 6 | 7 | namespace hk::gfx { 8 | Font::Font(void* fontData, void* device, void* memory) { 9 | HK_ABORT_UNLESS(alignUpPage(memory) == memory, "Memory must be page (%x) aligned (%p)", cPageSize, memory); 10 | 11 | FontHeader* header = reinterpret_cast(fontData); 12 | char16_t* charset = reinterpret_cast(header->data); 13 | u8* textureData = header->data + (header->charsetSize + 1) * sizeof(char16_t); 14 | 15 | mCharset = reinterpret_cast(memory); 16 | std::memcpy((void*)mCharset, charset, (header->charsetSize + 1) * sizeof(char16_t)); 17 | mNumChars = header->charsetSize; 18 | 19 | nvn::SamplerBuilder samp; 20 | samp.SetDefaults() 21 | .SetMinMagFilter(nvn::MinFilter::LINEAR, nvn::MagFilter::LINEAR) 22 | .SetWrapMode(nvn::WrapMode::CLAMP, nvn::WrapMode::CLAMP, nvn::WrapMode::CLAMP); 23 | nvn::TextureBuilder tex; 24 | tex.SetDefaults() 25 | .SetTarget(nvn::TextureTarget::TARGET_2D) 26 | .SetFormat(getAstcFormat(textureData)) 27 | .SetSize2D(header->width, header->height); 28 | uintptr_t textureBufferOffset = alignUpPage((header->charsetSize + 1) * sizeof(char16_t)); 29 | mTexture.create(device, &samp, &tex, header->texSize, (void*)(uintptr_t(textureData) + sizeof(AstcHeader)), (void*)(uintptr_t(memory) + textureBufferOffset)); 30 | } 31 | 32 | } // namespace hk::gfx 33 | -------------------------------------------------------------------------------- /addons/DebugRenderer/tools/generate_font.py: -------------------------------------------------------------------------------- 1 | # this script Sucks ass and only works (properly) on certain fonts because its Simple and it needs to stay Simple 2 | 3 | from PIL import Image, ImageFont, ImageDraw 4 | import sys 5 | import math 6 | import struct 7 | import subprocess 8 | 9 | if len(sys.argv) != 5: 10 | print("generate_font.py ") 11 | exit(1) 12 | 13 | fontPath = sys.argv[1] 14 | charset = sys.argv[2] 15 | fontSize = int(sys.argv[3]) 16 | outFile = sys.argv[4] 17 | 18 | font = ImageFont.truetype(fontPath, fontSize) 19 | 20 | glyphWidth = 0 21 | glyphHeight = 0 22 | 23 | for char in charset: 24 | size, _ = font.font.getsize(char) 25 | if size[0] > glyphWidth: 26 | glyphWidth = size[0] 27 | if size[1] > glyphHeight: 28 | glyphHeight = size[1] 29 | 30 | texWidth = int(glyphWidth * 32) 31 | texHeight = int(math.ceil(len(charset) / 32)) * glyphHeight 32 | 33 | print(str(texWidth) + " " + str(texHeight)) 34 | 35 | image = Image.new("RGBA", (int(texWidth), int(texHeight)), (255, 255, 255, 0)) 36 | draw = ImageDraw.Draw(image) 37 | 38 | i = 0 39 | for char in charset: 40 | x = i % 32 41 | y = int(i / 32) 42 | 43 | draw.text((x * glyphWidth, y * glyphHeight), char, (255,255,255), font=font, anchor='la') 44 | i = i + 1 45 | 46 | image.save("font.png") 47 | 48 | subprocess.run(['astcenc', '-cl', 'font.png', 'font.astc', '6x6', '-exhaustive']) 49 | 50 | astcTexture = open('font.astc', 'rb').read() 51 | 52 | with open(outFile, "wb") as f: 53 | f.write(struct.pack('qiiq', len(charset), texWidth, texHeight, len(astcTexture))) 54 | for char in charset: 55 | f.write(struct.pack('H', ord(char))) 56 | f.write(struct.pack('H', 0)) 57 | f.write(astcTexture) 58 | -------------------------------------------------------------------------------- /addons/ExpHeap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include_directories(include) 4 | set(SOURCES 5 | src/ams/lmem/lmem_common.cpp 6 | src/ams/lmem/lmem_exp_heap.cpp 7 | 8 | src/ams/lmem/impl/lmem_impl_common_heap.cpp 9 | src/ams/lmem/impl/lmem_impl_exp_heap.cpp 10 | 11 | src/hk/mem/ExpHeap.cpp 12 | ) 13 | add_library(ExpHeap ${SOURCES}) 14 | 15 | include(../../../config/config.cmake) 16 | include(../../cmake/apply_config.cmake) 17 | 18 | apply_config(ExpHeap) 19 | 20 | target_compile_definitions(ExpHeap PRIVATE) 21 | target_include_directories(ExpHeap PRIVATE include src ${CMAKE_CURRENT_BINARY_DIR}) 22 | 23 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../) 24 | -------------------------------------------------------------------------------- /addons/ExpHeap/README.md: -------------------------------------------------------------------------------- 1 | # ExpHeap 2 | 3 | This addon provides hk::mem::ExpHeap, a general purpose arena heap backed by Atmosphère's reimplementation of the nn::lmem ExpHeap. Note this addon is licensed as GPLv2. -------------------------------------------------------------------------------- /addons/ExpHeap/include/hk/mem/ExpHeap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace hk::mem { 6 | 7 | enum AllocationMode 8 | { 9 | AllocationMode_FirstFit, 10 | AllocationMode_BestFit, 11 | }; 12 | 13 | enum AllocationDirection 14 | { 15 | AllocationDirection_Front, 16 | AllocationDirection_Back, 17 | }; 18 | 19 | class ExpHeap { 20 | void* mHeapHandle = nullptr; 21 | size mHeapSize = 0; 22 | 23 | public: 24 | using HeapVisitor = void (*)(void* ptr, ExpHeap& heap, void* userData); 25 | 26 | NON_COPYABLE(ExpHeap); 27 | NON_MOVABLE(ExpHeap); 28 | 29 | ExpHeap() = default; 30 | ~ExpHeap(); 31 | 32 | void initialize(void* arena, size size); 33 | void destroy(); 34 | void adjust(); 35 | 36 | void* allocate(size size, s32 alignment = 4); 37 | void* reallocate(void* ptr, size size); 38 | void free(void* ptr); 39 | 40 | size getTotalSize() const { return mHeapSize; } 41 | size getFreeSize() const; 42 | size getAllocatableSize() const; 43 | 44 | void setAllocationMode(AllocationMode mode); 45 | AllocationMode getAllocationMode() const; 46 | 47 | void setGroupId(u16 groupId); 48 | u16 getGroupId() const; 49 | 50 | void forEachAllocation(HeapVisitor callback, void* userData = nullptr); 51 | 52 | static size getAllocationSize(void* ptr); 53 | static u16 getAllocationGroupId(void* ptr); 54 | static AllocationDirection getAllocationDirection(void* ptr); 55 | }; 56 | 57 | } // namespace hk::mem 58 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/impl/lmem_impl_common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "ams/util/util_intrusive_list.hpp" 20 | #include "hk/os/Mutex.h" 21 | #include "hk/types.h" 22 | 23 | namespace ams::lmem::impl { 24 | 25 | /* NOTE: Nintendo does not use util::IntrusiveListNode. */ 26 | /* They seem to manually manage linked list pointers. */ 27 | /* This is pretty gross, so we're going to use util::IntrusiveListNode. */ 28 | 29 | struct ExpHeapMemoryBlockHead { 30 | u16 magic; 31 | u32 attributes; 32 | size_t block_size; 33 | util::IntrusiveListNode list_node; 34 | }; 35 | static_assert(std::is_trivially_destructible::value); 36 | 37 | using ExpHeapMemoryBlockList = typename util::IntrusiveListMemberTraits<&ExpHeapMemoryBlockHead::list_node>::ListType; 38 | 39 | struct ExpHeapHead { 40 | ExpHeapMemoryBlockList free_list; 41 | ExpHeapMemoryBlockList used_list; 42 | u16 group_id; 43 | u16 mode; 44 | bool use_alignment_margins; 45 | char pad[3]; 46 | }; 47 | static_assert(sizeof(ExpHeapHead) == 0x28); 48 | static_assert(std::is_trivially_destructible::value); 49 | 50 | struct FrameHeapHead { 51 | void* next_block_head; 52 | void* next_block_tail; 53 | }; 54 | static_assert(sizeof(FrameHeapHead) == 0x10); 55 | static_assert(std::is_trivially_destructible::value); 56 | 57 | struct UnitHead { 58 | UnitHead* next; 59 | }; 60 | 61 | struct UnitHeapList { 62 | UnitHead* head; 63 | }; 64 | 65 | struct UnitHeapHead { 66 | UnitHeapList free_list; 67 | size_t unit_size; 68 | s32 alignment; 69 | s32 num_units; 70 | }; 71 | static_assert(sizeof(UnitHeapHead) == 0x18); 72 | static_assert(std::is_trivially_destructible::value); 73 | 74 | union ImplementationHeapHead { 75 | ExpHeapHead exp_heap_head; 76 | FrameHeapHead frame_heap_head; 77 | UnitHeapHead unit_heap_head; 78 | }; 79 | 80 | struct HeapHead { 81 | u32 magic; 82 | util::IntrusiveListNode list_node; 83 | 84 | using ChildListTraits = util::IntrusiveListMemberTraits<&HeapHead::list_node>; 85 | using ChildList = ChildListTraits::ListType; 86 | ChildList child_list; 87 | 88 | void* heap_start; 89 | void* heap_end; 90 | hk::os::Mutex mutex; 91 | u8 option; 92 | ImplementationHeapHead impl_head; 93 | }; 94 | static_assert(std::is_trivially_destructible::value); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/impl/lmem_impl_common_heap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #include "lmem_impl_common_heap.hpp" 18 | 19 | namespace ams::lmem::impl { 20 | 21 | namespace { 22 | 23 | u32 g_fill_values[FillType_Count] = { 24 | 0xC3C3C3C3, /* FillType_Unallocated */ 25 | 0xF3F3F3F3, /* FillType_Allocated */ 26 | 0xD3D3D3D3, /* FillType_Freed */ 27 | }; 28 | 29 | } 30 | 31 | void InitializeHeapHead(HeapHead* out, u32 magic, void* start, void* end, u32 option) { 32 | /* Call member constructors. */ 33 | std::construct_at(std::addressof(out->list_node)); 34 | std::construct_at(std::addressof(out->child_list)); 35 | 36 | /* Set fields. */ 37 | out->magic = magic; 38 | out->heap_start = start; 39 | out->heap_end = end; 40 | out->option = static_cast(option); 41 | 42 | /* Fill memory with pattern if needed. */ 43 | FillUnallocatedMemory(out, start, GetPointerDifference(start, end)); 44 | } 45 | 46 | void FinalizeHeap(HeapHead* heap) { 47 | /* Nothing actually needs to be done here. */ 48 | } 49 | 50 | bool ContainsAddress(HeapHandle handle, const void* address) { 51 | const uintptr_t uptr_handle = reinterpret_cast(handle); 52 | const uintptr_t uptr_start = reinterpret_cast(handle->heap_start); 53 | const uintptr_t uptr_end = reinterpret_cast(handle->heap_end); 54 | const uintptr_t uptr_addr = reinterpret_cast(address); 55 | 56 | if (uptr_start - sizeof(HeapHead) == uptr_handle) { 57 | /* The heap head is at the start of the managed memory. */ 58 | return uptr_handle <= uptr_addr && uptr_addr < uptr_end; 59 | } else if (uptr_handle == uptr_end) { 60 | /* The heap head is at the end of the managed memory. */ 61 | return uptr_start <= uptr_addr && uptr_addr < uptr_end + sizeof(HeapHead); 62 | } else { 63 | /* Heap head is somewhere unrelated to managed memory. */ 64 | return uptr_start <= uptr_addr && uptr_addr < uptr_end; 65 | } 66 | } 67 | 68 | size_t GetHeapTotalSize(HeapHandle handle) { 69 | const uintptr_t uptr_start = reinterpret_cast(handle->heap_start); 70 | const uintptr_t uptr_end = reinterpret_cast(handle->heap_end); 71 | 72 | if (ContainsAddress(handle, reinterpret_cast(handle))) { 73 | /* The heap metadata is contained within the heap, either before or after. */ 74 | return static_cast(uptr_end - uptr_start + sizeof(HeapHead)); 75 | } else { 76 | /* The heap metadata is not contained within the heap. */ 77 | return static_cast(uptr_end - uptr_start); 78 | } 79 | } 80 | 81 | u32 GetDebugFillValue(FillType type) { 82 | return g_fill_values[type]; 83 | } 84 | 85 | u32 SetDebugFillValue(FillType type, u32 value) { 86 | const u32 old_value = g_fill_values[type]; 87 | g_fill_values[type] = value; 88 | return old_value; 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/impl/lmem_impl_common_heap.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #pragma once 17 | 18 | #include "ams/lmem/impl/lmem_impl_common.hpp" 19 | #include "ams/lmem/lmem_common.hpp" 20 | #include "ams/util/util_fourcc.hpp" 21 | #include "hk/types.h" 22 | 23 | namespace ams::lmem::impl { 24 | 25 | constexpr inline u32 ExpHeapMagic = util::ReverseFourCC<'E', 'X', 'P', 'H'>::Code; 26 | constexpr inline u32 FrameHeapMagic = util::ReverseFourCC<'F', 'R', 'M', 'H'>::Code; 27 | constexpr inline u32 UnitHeapMagic = util::ReverseFourCC<'U', 'N', 'T', 'H'>::Code; 28 | 29 | class ScopedHeapLock { 30 | NON_COPYABLE(ScopedHeapLock); 31 | NON_MOVABLE(ScopedHeapLock); 32 | 33 | private: 34 | HeapHandle m_handle; 35 | 36 | public: 37 | explicit ScopedHeapLock(HeapHandle h) 38 | : m_handle(h) { 39 | if (m_handle->option & CreateOption_ThreadSafe) { 40 | m_handle->mutex.lock(); 41 | } 42 | } 43 | 44 | ~ScopedHeapLock() { 45 | if (m_handle->option & CreateOption_ThreadSafe) { 46 | m_handle->mutex.unlock(); 47 | } 48 | } 49 | }; 50 | 51 | hk_alwaysinline inline MemoryRange MakeMemoryRange(void* address, size_t size) { 52 | return MemoryRange { .address = reinterpret_cast(address), .size = size }; 53 | } 54 | 55 | hk_alwaysinline inline void* GetHeapStartAddress(HeapHandle handle) { 56 | return handle->heap_start; 57 | } 58 | 59 | hk_alwaysinline inline size_t GetPointerDifference(const void* start, const void* end) { 60 | return reinterpret_cast(end) - reinterpret_cast(start); 61 | } 62 | 63 | constexpr hk_alwaysinline size_t GetPointerDifference(uintptr_t start, uintptr_t end) { 64 | return end - start; 65 | } 66 | 67 | void InitializeHeapHead(HeapHead* out, u32 magic, void* start, void* end, u32 option); 68 | void FinalizeHeap(HeapHead* heap); 69 | bool ContainsAddress(HeapHandle handle, const void* address); 70 | size_t GetHeapTotalSize(HeapHandle handle); 71 | 72 | /* Debug Fill */ 73 | u32 GetDebugFillValue(FillType type); 74 | u32 SetDebugFillValue(FillType type, u32 value); 75 | 76 | inline void FillMemory(void* dst, u32 fill_value, size_t size) { 77 | /* All heap blocks must be at least 32-bit aligned. */ 78 | HK_ASSERT(hk::isAligned(reinterpret_cast(dst), alignof(u32))); 79 | HK_ASSERT(hk::isAligned(size, sizeof(u32))); 80 | for (size_t i = 0; i < size / sizeof(fill_value); i++) { 81 | reinterpret_cast(dst)[i] = fill_value; 82 | } 83 | } 84 | 85 | inline void FillUnallocatedMemory(HeapHead* heap, void* address, size_t size) { 86 | if (heap->option & CreateOption_DebugFill) { 87 | FillMemory(address, impl::GetDebugFillValue(FillType_Unallocated), size); 88 | } 89 | } 90 | 91 | inline void FillAllocatedMemory(HeapHead* heap, void* address, size_t size) { 92 | if (heap->option & CreateOption_ZeroClear) { 93 | FillMemory(address, 0, size); 94 | } else if (heap->option & CreateOption_DebugFill) { 95 | FillMemory(address, impl::GetDebugFillValue(FillType_Allocated), size); 96 | } 97 | } 98 | 99 | inline void FillFreedMemory(HeapHead* heap, void* address, size_t size) { 100 | if (heap->option & CreateOption_DebugFill) { 101 | FillMemory(address, impl::GetDebugFillValue(FillType_Freed), size); 102 | } 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/impl/lmem_impl_exp_heap.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #pragma once 17 | 18 | #include "ams/lmem/lmem_exp_heap.hpp" 19 | 20 | namespace ams::lmem::impl { 21 | 22 | HeapHandle CreateExpHeap(void* address, size_t size, u32 option); 23 | void DestroyExpHeap(HeapHandle handle); 24 | MemoryRange AdjustExpHeap(HeapHandle handle); 25 | 26 | void* AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment); 27 | void FreeToExpHeap(HeapHandle handle, void* block); 28 | 29 | size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void* block, size_t size); 30 | 31 | size_t GetExpHeapTotalFreeSize(HeapHandle handle); 32 | size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment); 33 | 34 | AllocationMode GetExpHeapAllocationMode(HeapHandle handle); 35 | AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode); 36 | 37 | bool GetExpHeapUseMarginsOfAlignment(HeapHandle handle); 38 | bool SetExpHeapUseMarginsOfAlignment(HeapHandle handle, bool use_margins); 39 | 40 | u16 GetExpHeapGroupId(HeapHandle handle); 41 | u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id); 42 | 43 | size_t GetExpHeapMemoryBlockSize(const void* memory_block); 44 | u16 GetExpHeapMemoryBlockGroupId(const void* memory_block); 45 | AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void* memory_block); 46 | 47 | void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/lmem_common.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #include "impl/lmem_impl_common_heap.hpp" 18 | 19 | namespace ams::lmem { 20 | 21 | u32 GetDebugFillValue(FillType fill_type) { 22 | return impl::GetDebugFillValue(fill_type); 23 | } 24 | 25 | void SetDebugFillValue(FillType fill_type, u32 value) { 26 | impl::SetDebugFillValue(fill_type, value); 27 | } 28 | 29 | size_t GetTotalSize(HeapHandle handle) { 30 | impl::ScopedHeapLock lk(handle); 31 | return impl::GetHeapTotalSize(handle); 32 | } 33 | 34 | void* GetStartAddress(HeapHandle handle) { 35 | impl::ScopedHeapLock lk(handle); 36 | return impl::GetHeapStartAddress(handle); 37 | } 38 | 39 | bool ContainsAddress(HeapHandle handle, const void* address) { 40 | impl::ScopedHeapLock lk(handle); 41 | return impl::ContainsAddress(handle, address); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/lmem_common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "hk/types.h" 20 | 21 | namespace ams::lmem { 22 | 23 | enum CreateOption 24 | { 25 | CreateOption_None = (0), 26 | CreateOption_ZeroClear = (1 << 0), 27 | CreateOption_DebugFill = (1 << 1), 28 | CreateOption_ThreadSafe = (1 << 2), 29 | }; 30 | 31 | enum FillType 32 | { 33 | FillType_Unallocated, 34 | FillType_Allocated, 35 | FillType_Freed, 36 | FillType_Count, 37 | }; 38 | 39 | namespace impl { 40 | 41 | struct HeapHead; 42 | 43 | } 44 | 45 | using HeapHandle = impl::HeapHead*; 46 | 47 | using HeapCommonHead = impl::HeapHead; 48 | 49 | struct MemoryRange { 50 | uintptr_t address; 51 | size_t size; 52 | }; 53 | 54 | constexpr inline s32 DefaultAlignment = 0x8; 55 | 56 | /* Common API. */ 57 | u32 GetDebugFillValue(FillType fill_type); 58 | void SetDebugFillValue(FillType fill_type, u32 value); 59 | 60 | size_t GetTotalSize(HeapHandle handle); 61 | void* GetStartAddress(HeapHandle handle); 62 | bool ContainsAddress(HeapHandle handle, const void* address); 63 | 64 | } 65 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/lmem_exp_heap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #include "ams/lmem/impl/lmem_impl_common_heap.hpp" 18 | #include "impl/lmem_impl_exp_heap.hpp" 19 | 20 | namespace ams::lmem { 21 | 22 | HeapHandle CreateExpHeap(void* address, size_t size, u32 option) { 23 | HeapHandle handle = impl::CreateExpHeap(address, size, option); 24 | if (option & CreateOption_ThreadSafe) { 25 | /* No initialization needed for hk::os::Mutex */ 26 | } 27 | return handle; 28 | } 29 | 30 | void DestroyExpHeap(HeapHandle handle) { 31 | impl::DestroyExpHeap(handle); 32 | } 33 | 34 | MemoryRange AdjustExpHeap(HeapHandle handle) { 35 | impl::ScopedHeapLock lk(handle); 36 | return impl::AdjustExpHeap(handle); 37 | } 38 | 39 | void* AllocateFromExpHeap(HeapHandle handle, size_t size) { 40 | impl::ScopedHeapLock lk(handle); 41 | return impl::AllocateFromExpHeap(handle, size, DefaultAlignment); 42 | } 43 | 44 | void* AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment) { 45 | impl::ScopedHeapLock lk(handle); 46 | return impl::AllocateFromExpHeap(handle, size, alignment); 47 | } 48 | 49 | void FreeToExpHeap(HeapHandle handle, void* block) { 50 | impl::ScopedHeapLock lk(handle); 51 | impl::FreeToExpHeap(handle, block); 52 | } 53 | 54 | size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void* block, size_t size) { 55 | impl::ScopedHeapLock lk(handle); 56 | return impl::ResizeExpHeapMemoryBlock(handle, block, size); 57 | } 58 | 59 | size_t GetExpHeapTotalFreeSize(HeapHandle handle) { 60 | impl::ScopedHeapLock lk(handle); 61 | return impl::GetExpHeapTotalFreeSize(handle); 62 | } 63 | 64 | size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment) { 65 | impl::ScopedHeapLock lk(handle); 66 | return impl::GetExpHeapAllocatableSize(handle, alignment); 67 | } 68 | 69 | AllocationMode GetExpHeapAllocationMode(HeapHandle handle) { 70 | impl::ScopedHeapLock lk(handle); 71 | return impl::GetExpHeapAllocationMode(handle); 72 | } 73 | 74 | AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode) { 75 | impl::ScopedHeapLock lk(handle); 76 | return impl::SetExpHeapAllocationMode(handle, new_mode); 77 | } 78 | 79 | bool GetExpHeapUseMarginsOfAlignment(HeapHandle handle) { 80 | impl::ScopedHeapLock lk(handle); 81 | return impl::GetExpHeapUseMarginsOfAlignment(handle); 82 | } 83 | 84 | bool SetExpHeapUseMarginsOfAlignment(HeapHandle handle, bool use_margins) { 85 | impl::ScopedHeapLock lk(handle); 86 | return impl::SetExpHeapUseMarginsOfAlignment(handle, use_margins); 87 | } 88 | 89 | u16 GetExpHeapGroupId(HeapHandle handle) { 90 | impl::ScopedHeapLock lk(handle); 91 | return impl::GetExpHeapGroupId(handle); 92 | } 93 | 94 | u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id) { 95 | impl::ScopedHeapLock lk(handle); 96 | return impl::SetExpHeapGroupId(handle, group_id); 97 | } 98 | 99 | size_t GetExpHeapMemoryBlockSize(const void* memory_block) { 100 | return impl::GetExpHeapMemoryBlockSize(memory_block); 101 | } 102 | 103 | u16 GetExpHeapMemoryBlockGroupId(const void* memory_block) { 104 | return impl::GetExpHeapMemoryBlockGroupId(memory_block); 105 | } 106 | 107 | AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void* memory_block) { 108 | return impl::GetExpHeapMemoryBlockAllocationDirection(memory_block); 109 | } 110 | 111 | void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data) { 112 | impl::ScopedHeapLock lk(handle); 113 | impl::VisitExpHeapAllocatedBlocks(handle, visitor, user_data); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/lmem/lmem_exp_heap.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "ams/lmem/lmem_common.hpp" 20 | #include "hk/types.h" 21 | 22 | namespace ams::lmem { 23 | 24 | enum AllocationMode 25 | { 26 | AllocationMode_FirstFit, 27 | AllocationMode_BestFit, 28 | }; 29 | 30 | enum AllocationDirection 31 | { 32 | AllocationDirection_Front, 33 | AllocationDirection_Back, 34 | }; 35 | 36 | using HeapVisitor = void (*)(void* block, HeapHandle handle, uintptr_t user_data); 37 | 38 | HeapHandle CreateExpHeap(void* address, size_t size, u32 option); 39 | void DestroyExpHeap(HeapHandle handle); 40 | MemoryRange AdjustExpHeap(HeapHandle handle); 41 | 42 | void* AllocateFromExpHeap(HeapHandle handle, size_t size); 43 | void* AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment); 44 | void FreeToExpHeap(HeapHandle handle, void* block); 45 | 46 | size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void* block, size_t size); 47 | 48 | size_t GetExpHeapTotalFreeSize(HeapHandle handle); 49 | size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment); 50 | 51 | AllocationMode GetExpHeapAllocationMode(HeapHandle handle); 52 | AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode); 53 | 54 | bool GetExpHeapUseMarginsOfAlignment(HeapHandle handle); 55 | bool SetExpHeapUseMarginsOfAlignment(HeapHandle handle, bool use_margins); 56 | 57 | u16 GetExpHeapGroupId(HeapHandle handle); 58 | u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id); 59 | 60 | size_t GetExpHeapMemoryBlockSize(const void* memory_block); 61 | u16 GetExpHeapMemoryBlockGroupId(const void* memory_block); 62 | AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void* memory_block); 63 | 64 | void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /addons/ExpHeap/src/ams/util/util_fourcc.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Atmosphère-NX 3 | * 4 | * This program is free software; you can redistribute it and/or modify it 5 | * under the terms and conditions of the GNU General Public License, 6 | * version 2, as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "hk/types.h" 20 | #include "util_endian.hpp" 21 | 22 | namespace ams::util { 23 | 24 | template 25 | struct FourCC { 26 | static constexpr u32 Code = IsLittleEndian() ? ((static_cast(A) << 0x00) | (static_cast(B) << 0x08) | (static_cast(C) << 0x10) | (static_cast(D) << 0x18)) 27 | : ((static_cast(A) << 0x18) | (static_cast(B) << 0x10) | (static_cast(C) << 0x08) | (static_cast(D) << 0x00)); 28 | 29 | static constexpr const char String[] = { A, B, C, D }; 30 | 31 | static_assert(sizeof(Code) == 4); 32 | static_assert(sizeof(String) == 4); 33 | }; 34 | 35 | template 36 | struct ReverseFourCC { 37 | static constexpr u32 Code = IsLittleEndian() ? ((static_cast(A) << 0x18) | (static_cast(B) << 0x10) | (static_cast(C) << 0x08) | (static_cast(D) << 0x00)) 38 | : ((static_cast(A) << 0x00) | (static_cast(B) << 0x08) | (static_cast(C) << 0x10) | (static_cast(D) << 0x18)); 39 | 40 | static constexpr const char String[] = { D, C, B, A }; 41 | 42 | static_assert(sizeof(Code) == 4); 43 | static_assert(sizeof(String) == 4); 44 | }; 45 | 46 | } -------------------------------------------------------------------------------- /addons/ExpHeap/src/hk/mem/ExpHeap.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/mem/ExpHeap.h" 2 | #include "ams/lmem/lmem_common.hpp" 3 | #include "ams/lmem/lmem_exp_heap.hpp" 4 | 5 | namespace hk::mem { 6 | 7 | #define getHeapHandle() reinterpret_cast<::ams::lmem::HeapHandle>(mHeapHandle) 8 | 9 | ExpHeap::~ExpHeap() { 10 | destroy(); 11 | } 12 | 13 | void ExpHeap::initialize(void* arena, size size) { 14 | destroy(); 15 | mHeapSize = size; 16 | mHeapHandle = ams::lmem::CreateExpHeap(arena, size, 0); 17 | } 18 | 19 | void ExpHeap::destroy() { 20 | if (mHeapHandle) { 21 | ams::lmem::DestroyExpHeap(getHeapHandle()); 22 | mHeapSize = 0; 23 | mHeapHandle = nullptr; 24 | } 25 | } 26 | 27 | void ExpHeap::adjust() { 28 | ams::lmem::AdjustExpHeap(getHeapHandle()); 29 | } 30 | 31 | void* ExpHeap::allocate(size size, s32 alignment) { 32 | return ams::lmem::AllocateFromExpHeap(getHeapHandle(), size, alignment); 33 | } 34 | 35 | void* ExpHeap::reallocate(void* oldPtr, size size) { 36 | auto resizedSize = ams::lmem::ResizeExpHeapMemoryBlock(getHeapHandle(), oldPtr, size); 37 | if (resizedSize > 0) 38 | return oldPtr; 39 | 40 | void* newPtr = allocate(size); 41 | if (newPtr) { 42 | auto copySize = getAllocationSize(oldPtr); 43 | __builtin_memcpy(newPtr, oldPtr, copySize); 44 | free(oldPtr); 45 | } 46 | return newPtr; 47 | } 48 | 49 | void ExpHeap::free(void* ptr) { 50 | ams::lmem::FreeToExpHeap(getHeapHandle(), ptr); 51 | } 52 | 53 | size ExpHeap::getFreeSize() const { 54 | return ams::lmem::GetExpHeapTotalFreeSize(getHeapHandle()); 55 | } 56 | 57 | size ExpHeap::getAllocatableSize() const { 58 | return ams::lmem::GetExpHeapAllocatableSize(getHeapHandle(), 4); 59 | } 60 | 61 | void ExpHeap::setAllocationMode(AllocationMode mode) { 62 | ams::lmem::SetExpHeapAllocationMode(getHeapHandle(), static_cast<::ams::lmem::AllocationMode>(mode)); 63 | } 64 | 65 | AllocationMode ExpHeap::getAllocationMode() const { 66 | return static_cast(ams::lmem::GetExpHeapAllocationMode(getHeapHandle())); 67 | } 68 | 69 | void ExpHeap::setGroupId(u16 groupId) { 70 | ams::lmem::SetExpHeapGroupId(getHeapHandle(), groupId); 71 | } 72 | 73 | u16 ExpHeap::getGroupId() const { 74 | return ams::lmem::GetExpHeapGroupId(getHeapHandle()); 75 | } 76 | 77 | void ExpHeap::forEachAllocation(HeapVisitor callback, void* userData) { 78 | struct UserData { 79 | HeapVisitor callback; 80 | ExpHeap& heap; 81 | void* userData; 82 | } data = { callback, *this, userData }; 83 | 84 | constexpr auto visit = [](void* block, ams::lmem::HeapHandle handle, uintptr_t user_data) -> void { 85 | const UserData* data = reinterpret_cast(user_data); 86 | data->callback(block, data->heap, data->userData); 87 | }; 88 | 89 | ams::lmem::VisitExpHeapAllocatedBlocks(getHeapHandle(), visit, uintptr_t(&data)); 90 | } 91 | 92 | size ExpHeap::getAllocationSize(void* ptr) { 93 | return ams::lmem::GetExpHeapMemoryBlockSize(ptr); 94 | } 95 | 96 | u16 ExpHeap::getAllocationGroupId(void* ptr) { 97 | return ams::lmem::GetExpHeapMemoryBlockGroupId(ptr); 98 | } 99 | 100 | AllocationDirection ExpHeap::getAllocationDirection(void* ptr) { 101 | return static_cast(ams::lmem::GetExpHeapMemoryBlockAllocationDirection(ptr)); 102 | } 103 | 104 | } // namespace hk::mem 105 | -------------------------------------------------------------------------------- /addons/HeapSourceBss/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include_directories(include) 4 | set(SOURCES 5 | src/hk/mem/BssHeap.cpp 6 | src/hk/mem/BssHeapWrappers.cpp 7 | ) 8 | add_library(HeapSourceBss ${SOURCES}) 9 | 10 | include(../../../config/config.cmake) 11 | include(../../cmake/apply_config.cmake) 12 | 13 | list(FIND HAKKUN_ADDONS HeapSourceDynamic HAS_DYNHEAP) 14 | if (NOT HAS_DYNHEAP EQUAL -1) 15 | message(FATAL_ERROR "HeapSourceBss and HeapSourceDynamic cannot be used at the same time") 16 | endif() 17 | 18 | list(FIND HAKKUN_ADDONS ExpHeap HAS_EXPHEAP) 19 | if (HAS_EXPHEAP EQUAL -1) 20 | message(FATAL_ERROR "Enable ExpHeap addon to use HeapSourceBss") 21 | endif() 22 | 23 | if (HAKKUN_MAIN_HEAP_USER_ARENA) 24 | target_compile_definitions(HeapSourceBss PRIVATE HAKKUN_MAIN_HEAP_USER_ARENA) 25 | message("Using user arena for main heap") 26 | else() 27 | if (NOT DEFINED HAKKUN_BSS_HEAP_SIZE) 28 | message(WARNING "HAKKUN_BSS_HEAP_SIZE not defined, using 256KB") 29 | set(HAKKUN_BSS_HEAP_SIZE 0x40000) 30 | endif() 31 | endif() 32 | 33 | apply_config(HeapSourceBss) 34 | 35 | target_compile_definitions(HeapSourceBss PRIVATE HAKKUN_BSS_HEAP_SIZE=${HAKKUN_BSS_HEAP_SIZE}) 36 | target_include_directories(HeapSourceBss PRIVATE include src ${CMAKE_CURRENT_BINARY_DIR}) 37 | 38 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../) 39 | -------------------------------------------------------------------------------- /addons/HeapSourceBss/README.md: -------------------------------------------------------------------------------- 1 | # HeapSourceBss 2 | 3 | This addon defines a global ExpHeap to use for malloc/new allocations. The size can be changed in the config with HAKKUN_BSS_HEAP_SIZE, 256KB by default. If HAKKUN_MAIN_HEAP_USER_ARENA is enabled, the user can override hk::mem::initializeMainHeap to create the heap (hk::mem::sMainHeap) with a custom arena. 4 | Depends on ExpHeap. -------------------------------------------------------------------------------- /addons/HeapSourceBss/include/hk/mem/BssHeap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/mem/ExpHeap.h" 4 | 5 | namespace hk::mem { 6 | 7 | extern ExpHeap sMainHeap; 8 | 9 | void initializeMainHeap(); 10 | 11 | } // namespace hk::mem 12 | -------------------------------------------------------------------------------- /addons/HeapSourceBss/src/hk/mem/BssHeap.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/mem/BssHeap.h" 2 | 3 | namespace hk::mem { 4 | 5 | constexpr size cHeapSize = HAKKUN_BSS_HEAP_SIZE; 6 | 7 | ExpHeap sMainHeap; 8 | 9 | #ifndef HAKKUN_MAIN_HEAP_USER_ARENA 10 | __attribute__((aligned(cPageSize))) static u8 sMainHeapMem[cHeapSize] { 0 }; 11 | 12 | void initializeMainHeap() { 13 | sMainHeap.initialize(sMainHeapMem, cHeapSize); 14 | } 15 | #endif 16 | 17 | } // namespace hk::mem 18 | -------------------------------------------------------------------------------- /addons/HeapSourceBss/src/hk/mem/BssHeapWrappers.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/mem/BssHeap.h" 2 | #include 3 | 4 | extern "C" { 5 | void* malloc(std::size_t size) { 6 | return hk::mem::sMainHeap.allocate(size); 7 | } 8 | void* aligned_alloc(std::size_t alignment, std::size_t size) { 9 | return hk::mem::sMainHeap.allocate(size, alignment); 10 | } 11 | void* realloc(void* ptr, std::size_t size) { 12 | return hk::mem::sMainHeap.reallocate(ptr, size); 13 | } 14 | void free(void* ptr) { 15 | hk::mem::sMainHeap.free(ptr); 16 | } 17 | void* calloc(std::size_t nmemb, std::size_t size) { 18 | void* ptr = malloc(nmemb * size); 19 | __builtin_memset(ptr, 0, nmemb * size); 20 | return ptr; 21 | } 22 | } 23 | 24 | void* operator new(std::size_t size) { 25 | return malloc(size); 26 | } 27 | 28 | void* operator new(std::size_t size, const std::nothrow_t& t) noexcept { 29 | return malloc(size); 30 | } 31 | 32 | void* operator new[](std::size_t size) { 33 | return malloc(size); 34 | } 35 | 36 | void* operator new[](std::size_t size, const std::nothrow_t& t) noexcept { 37 | return malloc(size); 38 | } 39 | 40 | void operator delete(void* ptr) { 41 | return free(ptr); 42 | } 43 | 44 | void operator delete(void* ptr, const std::nothrow_t& t) { 45 | return free(ptr); 46 | } 47 | 48 | void operator delete(void* ptr, std::size_t size) { 49 | return free(ptr); 50 | } 51 | 52 | void operator delete[](void* ptr) { 53 | return free(ptr); 54 | } 55 | 56 | void operator delete[](void* ptr, const std::nothrow_t& t) { 57 | return free(ptr); 58 | } 59 | 60 | void operator delete[](void* ptr, std::size_t size) { 61 | return free(ptr); 62 | } 63 | -------------------------------------------------------------------------------- /addons/HeapSourceDynamic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include_directories(include) 4 | set(SOURCES 5 | src/hk/mem/DynamicHeapWrappers.cpp 6 | ) 7 | add_library(HeapSourceDynamic ${SOURCES}) 8 | 9 | include(../../../config/config.cmake) 10 | include(../../cmake/apply_config.cmake) 11 | 12 | list(FIND HAKKUN_ADDONS HeapSourceBss HAS_BSSHEAP) 13 | if (NOT HAS_BSSHEAP EQUAL -1) 14 | message(FATAL_ERROR "HeapSourceDynamic and HeapSourceBss cannot be used at the same time") 15 | endif() 16 | 17 | apply_config(HeapSourceDynamic) 18 | 19 | target_compile_definitions(HeapSourceDynamic PRIVATE) 20 | target_include_directories(HeapSourceDynamic PRIVATE include src ${CMAKE_CURRENT_BINARY_DIR}) 21 | 22 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../) 23 | -------------------------------------------------------------------------------- /addons/HeapSourceDynamic/README.md: -------------------------------------------------------------------------------- 1 | # HeapSourceDynamic 2 | 3 | When enabled, this addon allows you to call functions such as malloc/free/operator new/operator delete etc. dynamically linked in from other modules, such as from main or sdk. -------------------------------------------------------------------------------- /addons/HeapSourceDynamic/src/hk/mem/DynamicHeapWrappers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" { 5 | void* hk_Znwm(std::size_t size); 6 | void* hk_ZnwmRKSt9nothrow_t(std::size_t size, const std::nothrow_t&); 7 | void* hk_Znam(std::size_t size); 8 | void* hk_ZnamRKSt9nothrow_t(std::size_t size, const std::nothrow_t&); 9 | void hk_ZdlPv(void* ptr); 10 | void hk_ZdlPvRKSt9nothrow_t(void* ptr, const std::nothrow_t&); 11 | void hk_ZdlPvm(void* ptr, std::size_t size); 12 | void hk_ZdaPv(void* ptr); 13 | void hk_ZdaPvRKSt9nothrow_t(void* ptr, const std::nothrow_t&); 14 | void hk_ZdaPvm(void* ptr, std::size_t size); 15 | void* hk_malloc(std::size_t size); 16 | void* hk_realloc(void* ptr, std::size_t size); 17 | void* hk_aligned_alloc(std::size_t alignment, std::size_t size); 18 | void hk_free(void* ptr); 19 | } 20 | void* operator new(std::size_t size) { 21 | return hk_Znwm(size); 22 | } 23 | 24 | void* operator new(std::size_t size, const std::nothrow_t& t) noexcept { 25 | return hk_ZnwmRKSt9nothrow_t(size, t); 26 | } 27 | 28 | void* operator new[](std::size_t size) { 29 | return hk_Znam(size); 30 | } 31 | 32 | void* operator new[](std::size_t size, const std::nothrow_t& t) noexcept { 33 | return hk_ZnamRKSt9nothrow_t(size, t); 34 | } 35 | 36 | void operator delete(void* ptr) { 37 | return hk_ZdlPv(ptr); 38 | } 39 | 40 | void operator delete(void* ptr, const std::nothrow_t& t) { 41 | return hk_ZdlPvRKSt9nothrow_t(ptr, t); 42 | } 43 | 44 | void operator delete(void* ptr, std::size_t size) { 45 | return hk_ZdlPvm(ptr, size); 46 | } 47 | 48 | void operator delete[](void* ptr) { 49 | return hk_ZdaPv(ptr); 50 | } 51 | 52 | void operator delete[](void* ptr, const std::nothrow_t& t) { 53 | return hk_ZdaPvRKSt9nothrow_t(ptr, t); 54 | } 55 | 56 | void operator delete[](void* ptr, std::size_t size) { 57 | return hk_ZdaPvm(ptr, size); 58 | } 59 | 60 | extern "C" { 61 | void* malloc(std::size_t size) { 62 | return hk_malloc(size); 63 | } 64 | void* aligned_alloc(std::size_t alignment, std::size_t size) { 65 | return hk_aligned_alloc(alignment, size); 66 | } 67 | void* realloc(void* ptr, std::size_t size) { 68 | return hk_realloc(ptr, size); 69 | } 70 | void free(void* ptr) { 71 | hk_free(ptr); 72 | } 73 | void* calloc(std::size_t nmemb, std::size_t size) { 74 | void* ptr = hk_malloc(nmemb * size); 75 | __builtin_memset(ptr, 0, nmemb * size); 76 | return ptr; 77 | } 78 | void* __libc_calloc(std::size_t nmemb, std::size_t size) { 79 | return calloc(nmemb, size); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /addons/HeapSourceDynamic/syms/hk/mem/DynamicHeapWrappers.sym: -------------------------------------------------------------------------------- 1 | hk_Znwm = "_Znwm" 2 | hk_ZnwmRKSt9nothrow_t = "_ZnwmRKSt9nothrow_t" 3 | hk_Znam = "_Znam" 4 | hk_ZnamRKSt9nothrow_t = "_ZnamRKSt9nothrow_t" 5 | hk_ZdlPv = "_ZdlPv" 6 | hk_ZdlPvRKSt9nothrow_t = "_ZdlPvRKSt9nothrow_t" 7 | hk_ZdlPvm = "_ZdlPvm" 8 | hk_ZdaPv = "_ZdaPv" 9 | hk_ZdaPvRKSt9nothrow_t = "_ZdaPvRKSt9nothrow_t" 10 | hk_ZdaPvm = "_ZdaPvm" 11 | 12 | hk_malloc = "malloc" 13 | hk_aligned_alloc = "aligned_alloc" 14 | hk_realloc = "realloc" 15 | hk_free = "free" 16 | -------------------------------------------------------------------------------- /addons/ImGui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../) 4 | set(IMGUI_DIR ${ROOTDIR}/lib/imgui) 5 | if (NOT IS_DIRECTORY ${IMGUI_DIR}) 6 | message(FATAL_ERROR "Please clone imgui into ${IMGUI_DIR}") 7 | endif() 8 | 9 | include_directories(include) 10 | set(SOURCES 11 | ${IMGUI_DIR}/imgui.cpp 12 | ${IMGUI_DIR}/imgui_draw.cpp 13 | ${IMGUI_DIR}/imgui_tables.cpp 14 | ${IMGUI_DIR}/imgui_widgets.cpp 15 | ${IMGUI_DIR}/imgui_demo.cpp 16 | 17 | src/hk/gfx/ImGuiBackendNvn.cpp 18 | ) 19 | add_library(ImGui ${SOURCES}) 20 | 21 | include(../../../config/config.cmake) 22 | include(../../cmake/apply_config.cmake) 23 | include(../../cmake/bin2s.cmake) 24 | 25 | list(FIND HAKKUN_ADDONS Nvn HAS_NVN) 26 | if (HAS_NVN EQUAL -1) 27 | message(FATAL_ERROR "Enable Nvn addon to use DebugRenderer") 28 | endif() 29 | 30 | embed_file(ImGui ${CMAKE_CURRENT_SOURCE_DIR}/data/shader.bin shader 0x1000 TRUE) 31 | 32 | apply_config(ImGui) 33 | 34 | target_compile_definitions(ImGui PRIVATE IMGUI_DISABLE_DEFAULT_ALLOCATORS IMGUI_USER_CONFIG="${CMAKE_CURRENT_SOURCE_DIR}/include/hk/gfx/ImGuiConfig.h") 35 | target_include_directories(ImGui PRIVATE include/hk ${CMAKE_CURRENT_BINARY_DIR} ${IMGUI_DIR}) 36 | -------------------------------------------------------------------------------- /addons/ImGui/README.md: -------------------------------------------------------------------------------- 1 | # ImGui 2 | 3 | This addon provides an NVN backend for ImGui. Input events such as Mouse/Keyboard/Gamepad/Touch have to be provided manually. To use, make sure ImGui is cloned into lib/imgui in your repository. See [Hakkun-Example's imgui branch](https://github.com/fruityloops1/Hakkun-Example/tree/imgui) for example usage. 4 | Depends on Nvn. -------------------------------------------------------------------------------- /addons/ImGui/data/shader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fruityloops1/LibHakkun/afee0a7c33e6a89a38fb858e5d575e473f59c065/addons/ImGui/data/shader.bin -------------------------------------------------------------------------------- /addons/ImGui/data/shader_fsh.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(location = 0) in vec2 vtxUv; 4 | layout(location = 1) in vec4 vtxColor; 5 | 6 | layout(binding = 0) uniform sampler2D tex; 7 | 8 | layout(location = 0) out vec4 outColor; 9 | 10 | vec4 toLinear(vec4 sRGB) { 11 | bvec3 cutoff = lessThan(sRGB.rgb, vec3(0.04045)); 12 | vec3 higher = pow((sRGB.rgb + vec3(0.055)) / vec3(1.055), vec3(2.4)); 13 | vec3 lower = sRGB.rgb / vec3(12.92); 14 | 15 | return vec4(mix(higher, lower, cutoff), sRGB.a); 16 | } 17 | 18 | void main() { outColor = toLinear(vtxColor) * texture(tex, vtxUv); } 19 | -------------------------------------------------------------------------------- /addons/ImGui/data/shader_vsh.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(location = 0) in vec2 inPos; 4 | layout(location = 1) in vec2 inUv; 5 | layout(location = 2) in vec4 inColor; 6 | 7 | layout(location = 0) out vec2 vtxUv; 8 | layout(location = 1) out vec4 vtxColor; 9 | 10 | // layout(std140, binding = 0) uniform UBO { vec2 resolution; } 11 | // ubo; 12 | 13 | vec2 toNDC(vec2 coords) { 14 | return vec2(coords.x * 2.0 - 1.0, -(coords.y * 2.0 - 1.0)); 15 | } 16 | 17 | void main() { 18 | gl_Position = vec4(toNDC(inPos), 0.0, 1.0); 19 | vtxUv = inUv; 20 | vtxColor = inColor; 21 | } -------------------------------------------------------------------------------- /addons/ImGui/include/hk/gfx/ImGuiBackendNvn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include "hk/util/Math.h" 5 | 6 | namespace hk::gfx { 7 | class ImGuiBackendNvnImpl; 8 | constexpr static size cImGuiBackendNvnImplSize = 2232; // This needs to GO 9 | 10 | class ImGuiBackendNvn { 11 | u8 mStorage[cImGuiBackendNvnImplSize]; 12 | 13 | static ImGuiBackendNvn sInstance; 14 | 15 | public: 16 | ImGuiBackendNvnImpl* get() { return reinterpret_cast(mStorage); } 17 | 18 | ImGuiBackendNvn(); 19 | 20 | struct Allocator { 21 | using Alloc = void* (*)(size, size); 22 | using Free = void (*)(void*); 23 | 24 | Alloc alloc = nullptr; 25 | Free free = nullptr; 26 | }; 27 | 28 | void installHooks(bool initializeAutomatically = true); 29 | 30 | bool tryInitialize(); 31 | void initTexture(bool useLinearFilter = false); 32 | 33 | void update(); 34 | void draw(const void* imDrawData, void* cmdBuf /* nvn::CommandBuffer* */); 35 | 36 | void setAllocator(const Allocator& allocator); 37 | void setDevice(void* device /* nvn::Device* */); 38 | void setPrevTexturePool(void* pool /* nvn::TexturePool* */); 39 | void setPrevSamplerPool(void* pool /* nvn::SamplerPool* */); 40 | void setResolution(const util::Vector2f& res); 41 | void* /* nvn::Device* */ getDevice(); 42 | 43 | static ImGuiBackendNvn* instance() { return &sInstance; } 44 | }; 45 | 46 | } // namespace hk::gfx 47 | -------------------------------------------------------------------------------- /addons/ImGui/include/hk/gfx/ImGuiConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/svc/api.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace hk::gfx { 10 | 11 | inline void imguiLog(const char* fmt, ...) { 12 | std::va_list arg; 13 | va_start(arg, fmt); 14 | 15 | size_t size = vsnprintf(nullptr, 0, fmt, arg); 16 | char* buf = (char*)__builtin_alloca(size + 1); 17 | vsnprintf(buf, size + 1, fmt, arg); 18 | va_end(arg); 19 | 20 | svc::OutputDebugString(buf, size); 21 | } 22 | 23 | } // namespace hk::gfx 24 | 25 | #define IMGUI_DEBUG_PRINTF(_FMT, ...) \ 26 | ::hk::gfx::imguiLog(_FMT, __VA_ARGS__) 27 | 28 | #define IM_ASSERT(_EXPR) HK_ASSERT(_EXPR) 29 | -------------------------------------------------------------------------------- /addons/ImGui/src/hk/gfx/ImGuiBackendNvn.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/gfx/ImGuiBackendNvn.h" 2 | 3 | #include 4 | 5 | #include "ImGuiBackendNvnImpl.cpp" 6 | #include "imgui.h" 7 | 8 | namespace hk::gfx { 9 | 10 | static_assert(sizeof(ImGuiBackendNvnImpl) == sizeof(ImGuiBackendNvn)); 11 | 12 | ImGuiBackendNvn ImGuiBackendNvn::sInstance; 13 | 14 | ImGuiBackendNvn::ImGuiBackendNvn() { 15 | new (get()) ImGuiBackendNvnImpl; 16 | } 17 | 18 | bool ImGuiBackendNvn::tryInitialize() { 19 | return get()->tryInitialize(); 20 | } 21 | void ImGuiBackendNvn::initTexture(bool useLinearFilter) { get()->initTexture(useLinearFilter); } 22 | 23 | void ImGuiBackendNvn::update() { 24 | get()->update(); 25 | } 26 | 27 | void ImGuiBackendNvn::draw(const void* drawData, void* cmdBuf) { 28 | get()->draw(*static_cast(drawData), static_cast(cmdBuf)); 29 | } 30 | 31 | void ImGuiBackendNvn::setAllocator(const Allocator& allocator) { get()->setAllocator(allocator); } 32 | void ImGuiBackendNvn::setDevice(void* device) { get()->setDevice(static_cast(device)); } 33 | void ImGuiBackendNvn::setPrevTexturePool(void* pool) { get()->setPrevTexturePool(static_cast(pool)); } 34 | void ImGuiBackendNvn::setPrevSamplerPool(void* pool) { get()->setPrevSamplerPool(static_cast(pool)); } 35 | void* ImGuiBackendNvn::getDevice() { return get()->getDevice(); } 36 | 37 | void ImGuiBackendNvn::setResolution(const util::Vector2f& res) { 38 | ImGui::GetIO().DisplaySize = ImVec2(res.x, res.y); 39 | } 40 | 41 | } // namespace hk::gfx 42 | -------------------------------------------------------------------------------- /addons/LogManager/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include_directories(include) 4 | set(SOURCES 5 | include/hk/nn/diag.h 6 | ) 7 | add_library(LogManager ${SOURCES}) 8 | set_target_properties(LogManager PROPERTIES LINKER_LANGUAGE CXX) 9 | 10 | include(../../../config/config.cmake) 11 | include(../../cmake/apply_config.cmake) 12 | 13 | apply_config(LogManager) 14 | 15 | target_compile_definitions(LogManager PRIVATE) 16 | 17 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../) 18 | -------------------------------------------------------------------------------- /addons/LogManager/README.md: -------------------------------------------------------------------------------- 1 | # LogManager 2 | 3 | This addon pipes logs produced by hk::diag::debugLog into nn::lm. Requires the user to provide nn::diag::detail::PutImpl. 4 | -------------------------------------------------------------------------------- /addons/LogManager/include/hk/nn/diag.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace nn::diag { 6 | 7 | struct LogMetaData { 8 | size line = 0; 9 | const char* file = nullptr; 10 | const char* function = nullptr; 11 | const char* _18 = ""; 12 | u32 _20 = 1; 13 | u32 _24 = 0; 14 | void* _28 = nullptr; 15 | void* _30 = nullptr; 16 | void* _38 = nullptr; 17 | }; 18 | 19 | namespace detail { 20 | 21 | void PutImpl(const LogMetaData& metaData, const char* msg, size len); 22 | 23 | } // namespace detail 24 | 25 | } // namespace nn::diag 26 | -------------------------------------------------------------------------------- /addons/Nvn/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include_directories(include) 4 | set(SOURCES 5 | src/hk/nvn/nvn_CppFuncPtrImpl.cpp 6 | 7 | src/hk/gfx/ShaderImpl.cpp 8 | src/hk/gfx/TextureImpl.cpp 9 | src/hk/gfx/UboImpl.cpp 10 | 11 | src/hk/gfx/NvnBootstrapOverride.cpp 12 | ) 13 | add_library(Nvn ${SOURCES}) 14 | 15 | include(../../../config/config.cmake) 16 | include(../../cmake/apply_config.cmake) 17 | 18 | apply_config(Nvn) 19 | 20 | target_compile_definitions(Nvn PRIVATE) 21 | target_include_directories(Nvn PRIVATE include/hk ${CMAKE_CURRENT_BINARY_DIR}) 22 | 23 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../) 24 | -------------------------------------------------------------------------------- /addons/Nvn/README.md: -------------------------------------------------------------------------------- 1 | # Nvn 2 | 3 | This addon provides NVN headers and some util classes for rendering with NVN, as well as a script that compiles GLSL shaders into a format loadable by hk::gfx::Shader using either glslc, or [uam-nvn](https://github.com/nvnprogram/uam-nvn/). -------------------------------------------------------------------------------- /addons/Nvn/include/hk/gfx/Nvn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/gfx/Texture.h" 5 | #include "hk/util/Math.h" 6 | #include "nvn/nvn_Cpp.h" 7 | 8 | namespace hk::gfx { 9 | 10 | inline nvn::Format getAstcFormat(const void* tex) { 11 | const AstcHeader* header = reinterpret_cast(tex); 12 | nvn::Format format = nvn::Format::NONE; 13 | if (header->block_x == 4 && header->block_y == 4) 14 | format = nvn::Format::RGBA_ASTC_4x4_SRGB; 15 | else if (header->block_x == 5 && header->block_y == 4) 16 | format = nvn::Format::RGBA_ASTC_5x4_SRGB; 17 | else if (header->block_x == 5 && header->block_y == 5) 18 | format = nvn::Format::RGBA_ASTC_5x5_SRGB; 19 | else if (header->block_x == 6 && header->block_y == 5) 20 | format = nvn::Format::RGBA_ASTC_6x5_SRGB; 21 | else if (header->block_x == 6 && header->block_y == 6) 22 | format = nvn::Format::RGBA_ASTC_6x6_SRGB; 23 | else if (header->block_x == 8 && header->block_y == 5) 24 | format = nvn::Format::RGBA_ASTC_8x5_SRGB; 25 | else if (header->block_x == 8 && header->block_y == 6) 26 | format = nvn::Format::RGBA_ASTC_8x6_SRGB; 27 | else if (header->block_x == 8 && header->block_y == 8) 28 | format = nvn::Format::RGBA_ASTC_8x8_SRGB; 29 | else if (header->block_x == 10 && header->block_y == 5) 30 | format = nvn::Format::RGBA_ASTC_10x5_SRGB; 31 | else if (header->block_x == 10 && header->block_y == 6) 32 | format = nvn::Format::RGBA_ASTC_10x6_SRGB; 33 | else if (header->block_x == 10 && header->block_y == 8) 34 | format = nvn::Format::RGBA_ASTC_10x8_SRGB; 35 | else if (header->block_x == 10 && header->block_y == 10) 36 | format = nvn::Format::RGBA_ASTC_10x10_SRGB; 37 | else if (header->block_x == 12 && header->block_y == 10) 38 | format = nvn::Format::RGBA_ASTC_12x10_SRGB; 39 | else if (header->block_x == 12 && header->block_y == 12) 40 | format = nvn::Format::RGBA_ASTC_12x12_SRGB; 41 | else 42 | HK_ABORT("Unknown ASTC block type %d %d", header->block_x, header->block_y); 43 | 44 | return format; 45 | } 46 | 47 | inline util::Vector2i getAstcSize(const void* tex) { 48 | const AstcHeader* header = reinterpret_cast(tex); 49 | return { header->getWidth(), header->getHeight() }; 50 | } 51 | 52 | } // namespace hk::gfx 53 | -------------------------------------------------------------------------------- /addons/Nvn/include/hk/gfx/Shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/gfx/Ubo.h" 4 | #include "hk/types.h" 5 | 6 | namespace hk::gfx { 7 | 8 | class ShaderImpl; 9 | constexpr static size cShaderImplSize = 568; // This needs to GO 10 | 11 | class Shader { 12 | u8 mStorage[cShaderImplSize]; 13 | Ubo* mUbo = nullptr; 14 | u32 mUboBinding = 0; 15 | u32 mReserved; 16 | 17 | public: 18 | ShaderImpl* get() { return reinterpret_cast(mStorage); } 19 | 20 | Shader(u8* shaderData, size shaderSize, void* nvnDevice, void* attribStates, int numAttribStates, void* streamState, const char* shaderName); 21 | ~Shader(); 22 | 23 | void use(void* nvnCommandBuffer); 24 | 25 | void setUbo(Ubo* ubo, u32 binding) { 26 | mUbo = ubo; 27 | mUboBinding = binding; 28 | } 29 | }; 30 | 31 | } // namespace hk::gfx 32 | -------------------------------------------------------------------------------- /addons/Nvn/include/hk/gfx/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include "hk/util/Math.h" 5 | 6 | namespace hk::gfx { 7 | 8 | class TextureImpl; 9 | constexpr static size cTextureImplSize = // This needs to GO 10 | #ifdef __aarch64__ 11 | 872 12 | #else 13 | 868 14 | #endif 15 | ; 16 | 17 | struct TextureHandle { 18 | void* texturePool; 19 | void* samplerPool; 20 | int textureId = 0, samplerId = 0; 21 | }; 22 | 23 | class Texture { 24 | u8 mStorage[cTextureImplSize]; 25 | 26 | public: 27 | TextureImpl* get() { return reinterpret_cast(mStorage); } 28 | 29 | Texture(void* nvnDevice, void* samplerBuilder, void* textureBuilder, size texSize, void* texData, void* memory); 30 | ~Texture(); 31 | 32 | util::Vector2i getSize(); 33 | 34 | TextureHandle getTextureHandle(); 35 | 36 | static size calcMemorySize(void* nvnDevice, size texSize); 37 | }; 38 | 39 | struct AstcHeader { 40 | uint8_t magic[4]; 41 | uint8_t block_x; 42 | uint8_t block_y; 43 | uint8_t block_z; 44 | uint8_t dim_x[3]; 45 | uint8_t dim_y[3]; 46 | uint8_t dim_z[3]; 47 | 48 | int getWidth() const { return dim_x[0] + (dim_x[1] << 8) + (dim_x[2] << 16); } 49 | int getHeight() const { return dim_y[0] + (dim_y[1] << 8) + (dim_y[2] << 16); } 50 | }; 51 | 52 | } // namespace hk::gfx 53 | -------------------------------------------------------------------------------- /addons/Nvn/include/hk/gfx/Ubo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace hk::gfx { 6 | 7 | struct UboImpl; 8 | constexpr static size cUboImplSize = 344; // This needs to GO 9 | 10 | class Ubo { 11 | u8 mStorage[cUboImplSize]; 12 | 13 | public: 14 | UboImpl* get() { return reinterpret_cast(mStorage); } 15 | 16 | Ubo(void* device /* nvn::Device* */, void* memory, size uboSize, int stage = 0 /* nvn::ShaderStage::VERTEX */); 17 | ~Ubo(); 18 | 19 | void bind(void* cmdBuf /* nvn::CommandBuffer* */, u32 binding); 20 | void update(void* cmdBuf, u32 binding, const void* data, size size); 21 | 22 | template 23 | void update(void* cmdBuf, u32 binding, const T& data) { 24 | update(cmdBuf, binding, &data, sizeof(T)); 25 | } 26 | }; 27 | 28 | } // namespace hk::gfx 29 | -------------------------------------------------------------------------------- /addons/Nvn/include/hk/gfx/Util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace hk::gfx { 6 | 7 | constexpr u32 rgba(u8 r, u8 g, u8 b, u8 a) { 8 | return r | g << 8 | b << 16 | a << 24; 9 | } 10 | 11 | constexpr u32 rgbaf(f32 r, f32 g, f32 b, f32 a) { 12 | return rgba(u8(r * 255), u8(g * 255), u8(b * 255), u8(a * 255)); 13 | } 14 | 15 | constexpr u32 rgbaf32(f64 r, f64 g, f64 b, f64 a) { 16 | return rgba(u8(r * 255), u8(g * 255), u8(b * 255), u8(a * 255)); 17 | } 18 | 19 | } // namespace hk::gfx 20 | -------------------------------------------------------------------------------- /addons/Nvn/include/hk/gfx/Vertex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/util/Math.h" 4 | 5 | namespace hk::gfx { 6 | 7 | struct Vertex { 8 | util::Vector2f pos; 9 | util::Vector2f uv; 10 | u32 color; 11 | }; 12 | 13 | } // namespace hk::gfx 14 | -------------------------------------------------------------------------------- /addons/Nvn/include/hk/nvn/MemoryBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/types.h" 5 | 6 | #include "nvn/nvn_Cpp.h" 7 | 8 | namespace hk::nvn { 9 | 10 | using namespace ::nvn; 11 | 12 | class MemoryBuffer { 13 | nvn::MemoryPool mPool; 14 | nvn::Buffer mBuffer; 15 | void* mMemory = nullptr; 16 | bool mInitialized = false; 17 | 18 | public: 19 | void initialize(void* buffer, size size, nvn::Device* device, nvn::MemoryPoolFlags flags) { 20 | HK_ASSERT(!mInitialized); 21 | HK_ASSERT(alignDownPage(buffer) == buffer); 22 | size = alignUpPage(size); 23 | mMemory = buffer; 24 | 25 | { 26 | nvn::MemoryPoolBuilder builder; 27 | builder.SetDefaults() 28 | .SetDevice(device) 29 | .SetFlags(flags) 30 | .SetStorage(buffer, size); 31 | 32 | HK_ASSERT(mPool.Initialize(&builder)); 33 | } 34 | 35 | { 36 | nvn::BufferBuilder builder; 37 | builder.SetDevice(device) 38 | .SetDefaults() 39 | .SetStorage(&mPool, 0x0, size); 40 | HK_ASSERT(mBuffer.Initialize(&builder)); 41 | } 42 | 43 | mInitialized = true; 44 | } 45 | 46 | bool isInitialized() const { return mInitialized; } 47 | 48 | void finalize() { 49 | HK_ASSERT(mInitialized); 50 | mMemory = nullptr; 51 | mPool.Finalize(); 52 | mBuffer.Finalize(); 53 | mInitialized = false; 54 | } 55 | 56 | void* getMemory() const { return mMemory; } 57 | nvn::BufferAddress getAddress() const { return mBuffer.GetAddress(); } 58 | size getSize() const { return mPool.GetSize(); } 59 | void* map() const { return mPool.Map(); } 60 | void flush(uintptr_t offset, size size) { mPool.FlushMappedRange(offset, size); } 61 | }; 62 | 63 | } // namespace hk::nvn 64 | -------------------------------------------------------------------------------- /addons/Nvn/src/hk/gfx/ShaderImpl.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/gfx/Shader.h" 2 | #include "hk/gfx/Vertex.h" 3 | #include "hk/nvn/MemoryBuffer.h" 4 | 5 | #include "nvn/nvn_Cpp.h" 6 | #include "nvn/nvn_CppMethods.h" 7 | 8 | #include 9 | 10 | namespace hk::gfx { 11 | 12 | static nvn::VertexAttribState* getDefaultAttribStates() { 13 | static nvn::VertexAttribState states[3]; 14 | 15 | states[0].SetDefaults().SetFormat(nvn::Format::RG32F, offsetof(Vertex, pos)); 16 | states[1].SetDefaults().SetFormat(nvn::Format::RG32F, offsetof(Vertex, uv)); 17 | states[2].SetDefaults().SetFormat(nvn::Format::RGBA8, offsetof(Vertex, color)); 18 | return states; 19 | } 20 | 21 | static nvn::VertexStreamState* getDefaultStreamState() { 22 | static nvn::VertexStreamState state; 23 | 24 | state.SetDefaults().SetStride(sizeof(Vertex)); 25 | return &state; 26 | } 27 | 28 | class ShaderImpl { 29 | struct BinaryHeader { 30 | u32 fragmentControlOffset; 31 | u32 vertexControlOffset; 32 | u32 fragmentDataOffset; 33 | u32 vertexDataOffset; 34 | }; 35 | 36 | nvn::Program mProgram; 37 | hk::nvn::MemoryBuffer mShaderBuffer; 38 | int mNumAttribStates = 3; 39 | nvn::VertexAttribState* mAttribStates = nullptr; 40 | nvn::VertexStreamState* mStreamState = nullptr; 41 | nvn::ShaderData mShaderDatas[2]; 42 | 43 | public: 44 | ShaderImpl(u8* shaderData, size shaderSize, nvn::Device* device, nvn::VertexAttribState* attribStates, int numAttribStates, nvn::VertexStreamState* streamState, const char* shaderName) { 45 | HK_ABORT_UNLESS(alignUpPage(shaderData) == shaderData, "Memory must be page (%x) aligned (%p)", cPageSize, shaderData); 46 | 47 | const BinaryHeader* header = reinterpret_cast(shaderData); 48 | 49 | HK_ASSERT(mProgram.Initialize(device)); 50 | 51 | mShaderBuffer.initialize(shaderData, shaderSize, device, 52 | nvn::MemoryPoolFlags::CPU_UNCACHED | nvn::MemoryPoolFlags::GPU_CACHED | nvn::MemoryPoolFlags::SHADER_CODE); 53 | nvn::BufferAddress addr = mShaderBuffer.getAddress(); 54 | 55 | mShaderDatas[0].data = addr + header->vertexDataOffset; 56 | mShaderDatas[0].control = shaderData + header->vertexControlOffset; 57 | mShaderDatas[1].data = addr + header->fragmentDataOffset; 58 | mShaderDatas[1].control = shaderData + header->fragmentControlOffset; 59 | 60 | HK_ASSERT(mProgram.SetShaders(2, mShaderDatas)); 61 | 62 | if (shaderName) 63 | mProgram.SetDebugLabel(shaderName); 64 | 65 | if (attribStates) 66 | mNumAttribStates = numAttribStates; 67 | mAttribStates = attribStates; 68 | mStreamState = streamState; 69 | 70 | if (mAttribStates == nullptr) 71 | mAttribStates = getDefaultAttribStates(); 72 | if (mStreamState == nullptr) 73 | mStreamState = getDefaultStreamState(); 74 | } 75 | 76 | void use(nvn::CommandBuffer* commandBuffer) { 77 | commandBuffer->BindProgram(&mProgram, nvn::ShaderStageBits::ALL_GRAPHICS_BITS); 78 | commandBuffer->BindVertexAttribState(mNumAttribStates, mAttribStates); 79 | commandBuffer->BindVertexStreamState(1, mStreamState); 80 | } 81 | }; 82 | 83 | // wrappers 84 | 85 | Shader::Shader(u8* shaderData, size shaderSize, void* nvnDevice, void* attribStates, int numAttribStates, void* streamState, const char* shaderName) { 86 | new (get()) ShaderImpl(shaderData, shaderSize, static_cast(nvnDevice), static_cast(attribStates), numAttribStates, static_cast(streamState), shaderName); 87 | } 88 | 89 | Shader::~Shader() { 90 | get()->~ShaderImpl(); 91 | } 92 | 93 | void Shader::use(void* nvnCommandBuffer) { 94 | get()->use(static_cast(nvnCommandBuffer)); 95 | if (mUbo) 96 | mUbo->bind(nvnCommandBuffer, mUboBinding); 97 | } 98 | 99 | static_assert(sizeof(ShaderImpl) + sizeof(Ubo*) + sizeof(u32) + sizeof(u32) == sizeof(Shader)); 100 | 101 | } // namespace hk::gfx 102 | -------------------------------------------------------------------------------- /addons/Nvn/src/hk/gfx/TextureImpl.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/diag/diag.h" 2 | #include "hk/gfx/Texture.h" 3 | 4 | #include "nvn/nvn_Cpp.h" 5 | #include "nvn/nvn_CppMethods.h" 6 | 7 | #include 8 | 9 | namespace hk::gfx { 10 | 11 | static int cSamplerDescriptorSize = 0; 12 | static int cTextureDescriptorSize = 0; 13 | 14 | class TextureImpl { 15 | nvn::Device* mDevice = nullptr; 16 | nvn::MemoryPool mMemForPools; 17 | nvn::TexturePool mTexturePool; 18 | nvn::SamplerPool mSamplerPool; 19 | nvn::MemoryPool mMemForTexture; 20 | nvn::Texture mTexture; 21 | nvn::Sampler mSampler; 22 | 23 | public: 24 | static size calcPoolsMemSize(nvn::Device* device) { 25 | device->GetInteger(nvn::DeviceInfo::SAMPLER_DESCRIPTOR_SIZE, &cSamplerDescriptorSize); 26 | device->GetInteger(nvn::DeviceInfo::TEXTURE_DESCRIPTOR_SIZE, &cTextureDescriptorSize); 27 | return alignUpPage(cSamplerDescriptorSize * 2 + cTextureDescriptorSize * 2); 28 | } 29 | 30 | TextureImpl(nvn::Device* device, nvn::SamplerBuilder& samplerBuilder, nvn::TextureBuilder& textureBuilder, size texSize, void* texData, void* memory) { 31 | HK_ABORT_UNLESS(alignUpPage(memory) == memory, "Memory must be page (%x) aligned (%p)", cPageSize, memory); 32 | 33 | size poolsSize = calcPoolsMemSize(device); 34 | { 35 | nvn::MemoryPoolBuilder builder; 36 | builder.SetDefaults() 37 | .SetDevice(device) 38 | .SetFlags(nvn::MemoryPoolFlags::CPU_UNCACHED | nvn::MemoryPoolFlags::GPU_CACHED) 39 | .SetStorage(memory, poolsSize); 40 | 41 | HK_ASSERT(mMemForPools.Initialize(&builder)); 42 | } 43 | 44 | HK_ASSERT(mSamplerPool.Initialize(&mMemForPools, 0, 2)); 45 | HK_ASSERT(mTexturePool.Initialize(&mMemForPools, cSamplerDescriptorSize * 2, 2)); 46 | 47 | { 48 | nvn::MemoryPoolBuilder builder; 49 | builder.SetDefaults() 50 | .SetDevice(device) 51 | .SetFlags(nvn::MemoryPoolFlags::CPU_UNCACHED | nvn::MemoryPoolFlags::GPU_CACHED) 52 | .SetStorage((void*)(uintptr_t(memory) + poolsSize), alignUpPage(texSize)); 53 | 54 | HK_ASSERT(mMemForTexture.Initialize(&builder)); 55 | } 56 | 57 | textureBuilder = textureBuilder.SetStorage(&mMemForTexture, 0); 58 | textureBuilder = textureBuilder.SetDevice(device); 59 | samplerBuilder = samplerBuilder.SetDevice(device); 60 | 61 | HK_ASSERT(mTexture.Initialize(&textureBuilder)); 62 | write(mTexture.GetWidth(), mTexture.GetHeight(), texData); 63 | 64 | HK_ASSERT(mSampler.Initialize(&samplerBuilder)); 65 | 66 | mTexturePool.RegisterTexture(0, &mTexture, nullptr); 67 | mSamplerPool.RegisterSampler(0, &mSampler); 68 | } 69 | 70 | void write(int width, int height, void* data) { 71 | const nvn::CopyRegion region = { 72 | .xoffset = 0, 73 | .yoffset = 0, 74 | .zoffset = 0, 75 | .width = width, 76 | .height = height, 77 | .depth = 1 78 | }; 79 | mTexture.WriteTexels(nullptr, ®ion, data); 80 | mTexture.FlushTexels(nullptr, ®ion); 81 | } 82 | 83 | ~TextureImpl() { 84 | mTexturePool.Finalize(); 85 | mSamplerPool.Finalize(); 86 | mTexture.Finalize(); 87 | mSampler.Finalize(); 88 | mMemForTexture.Finalize(); 89 | mMemForPools.Finalize(); 90 | } 91 | 92 | nvn::Texture& getTexture() { return mTexture; } 93 | TextureHandle getTextureHandle() { 94 | return { &mTexturePool, &mSamplerPool, 0, 0 }; 95 | } 96 | }; 97 | 98 | Texture::Texture(void* nvnDevice, void* samplerBuilder, void* textureBuilder, size texSize, void* texData, void* memory) { 99 | new (get()) TextureImpl(reinterpret_cast(nvnDevice), *reinterpret_cast(samplerBuilder), *reinterpret_cast(textureBuilder), texSize, texData, memory); 100 | } 101 | 102 | Texture::~Texture() { 103 | get()->~TextureImpl(); 104 | } 105 | 106 | TextureHandle Texture::getTextureHandle() { 107 | return { get()->getTextureHandle() }; 108 | } 109 | 110 | size Texture::calcMemorySize(void* nvnDevice, size texSize) { 111 | nvn::Device* device = reinterpret_cast(nvnDevice); 112 | 113 | return TextureImpl::calcPoolsMemSize(device) + alignUpPage(texSize); 114 | } 115 | 116 | util::Vector2i Texture::getSize() { 117 | auto& tex = get()->getTexture(); 118 | return { tex.GetWidth(), tex.GetHeight() }; 119 | } 120 | 121 | static_assert(sizeof(TextureImpl) == sizeof(Texture)); 122 | 123 | } // namespace hk::gfx 124 | -------------------------------------------------------------------------------- /addons/Nvn/src/hk/gfx/UboImpl.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/gfx/Ubo.h" 2 | 3 | #include "nvn/MemoryBuffer.h" 4 | #include "nvn/nvn_Cpp.h" 5 | #include "nvn/nvn_CppMethods.h" 6 | 7 | namespace hk::gfx { 8 | 9 | struct UboImpl { 10 | void* memory; 11 | size uboSize; 12 | hk::nvn::MemoryBuffer uboBuffer; 13 | nvn::ShaderStage stage; 14 | }; 15 | 16 | Ubo::Ubo(void* device, void* memory, size uboSize, int stage) { 17 | HK_ABORT_UNLESS(alignUp(memory, 0x100) == memory, "Memory must be (%x) aligned (%p)", 0x100, memory); 18 | 19 | auto* instance = get(); 20 | instance->uboSize = uboSize; 21 | instance->memory = memory; 22 | instance->uboBuffer.initialize(memory, uboSize, static_cast(device), nvn::MemoryPoolFlags::CPU_CACHED | nvn::MemoryPoolFlags::GPU_CACHED); 23 | instance->stage = nvn::ShaderStage::Enum(stage); 24 | } 25 | 26 | Ubo::~Ubo() { 27 | auto* instance = get(); 28 | instance->uboBuffer.finalize(); 29 | instance->uboSize = 0; 30 | instance->memory = nullptr; 31 | } 32 | 33 | void Ubo::bind(void* cmdBufPtr, u32 binding) { 34 | nvn::CommandBuffer* cmdBuf = static_cast(cmdBufPtr); 35 | nvn::BufferAddress addr = get()->uboBuffer.getAddress(); 36 | 37 | cmdBuf->BindUniformBuffer(get()->stage, binding, addr, get()->uboSize); 38 | } 39 | 40 | void Ubo::update(void* cmdBufPtr, u32 binding, const void* data, size size) { 41 | nvn::CommandBuffer* cmdBuf = static_cast(cmdBufPtr); 42 | nvn::BufferAddress addr = get()->uboBuffer.getAddress(); 43 | 44 | cmdBuf->BindUniformBuffer(get()->stage, binding, addr, get()->uboSize); 45 | cmdBuf->UpdateUniformBuffer(addr, get()->uboSize, binding, size, data); 46 | } 47 | 48 | static_assert(sizeof(UboImpl) == sizeof(Ubo)); 49 | 50 | } // namespace hk::gfx 51 | -------------------------------------------------------------------------------- /addons/Nvn/tools/compile_shader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | import os 4 | import math 5 | import struct 6 | import requests 7 | 8 | if len(sys.argv) != 5: 9 | print("compile_shader.py ") 10 | print("pass 'glslc' into uam-bin arg to use glslc API") 11 | exit(1) 12 | 13 | uam_bin = sys.argv[1] 14 | vertex_path = sys.argv[2] 15 | frag_path = sys.argv[3] 16 | out_file = sys.argv[4] 17 | 18 | def readBinaryFile(path: str): 19 | with open(path, 'rb') as f: 20 | return f.read() 21 | def alignUp(value, alignment): 22 | return (value + (alignment - 1)) & ~(alignment - 1) 23 | 24 | glslc_api = "https://glslc.littun.co" 25 | 26 | def compileGlslc(srcFile: str, stage: str, part: str): 27 | url = f"{glslc_api}/{stage}/{part}" 28 | print(url) 29 | res = requests.post(url, data=open(srcFile, 'rb').read()) 30 | if (res.status_code == 200): 31 | return res.content 32 | else: 33 | print(res.content.decode('utf-8')) 34 | exit(1) 35 | 36 | if uam_bin == 'glslc': 37 | vertCtrl = compileGlslc(vertex_path, 'compileVert', 'control') 38 | fragCtrl = compileGlslc(frag_path, 'compileFrag', 'control') 39 | vertCode = compileGlslc(vertex_path, 'compileVert', 'code') 40 | fragCode = compileGlslc(frag_path, 'compileFrag', 'code') 41 | else: 42 | vertexCtrlPath = ".vertex_ctrl.bin" 43 | fragCtrlPath = ".frag_ctrl.bin" 44 | vertexCodePath = ".vertex_code.bin" 45 | fragCodePath = ".frag_code.bin" 46 | 47 | subprocess.check_call([uam_bin, '--glslcbinds', f'--nvnctrl={vertexCtrlPath}', f'--nvngpu={vertexCodePath}', vertex_path, '--stage', 'vert']) 48 | subprocess.check_call([uam_bin, '--glslcbinds', f'--nvnctrl={fragCtrlPath}', f'--nvngpu={fragCodePath}', frag_path, '--stage', 'frag']) 49 | 50 | vertCtrl = readBinaryFile(vertexCtrlPath) 51 | fragCtrl = readBinaryFile(fragCtrlPath) 52 | vertCode = readBinaryFile(vertexCodePath) 53 | fragCode = readBinaryFile(fragCodePath) 54 | 55 | fragCtrlOffs = 0x10 56 | vertCtrlOffs = alignUp(fragCtrlOffs + len(fragCtrl), 0x100) 57 | fragCodeOffs = alignUp(vertCtrlOffs + len(vertCtrl), 0x100) 58 | vertCodeOffs = alignUp(fragCodeOffs + len(fragCode), 0x100) 59 | 60 | with open(out_file, 'wb') as f: 61 | f.write(struct.pack('IIII', fragCtrlOffs,vertCtrlOffs, fragCodeOffs, vertCodeOffs)) 62 | f.write(fragCtrl) 63 | 64 | while f.tell() != vertCtrlOffs: 65 | f.write(b'\x00') 66 | 67 | f.write(vertCtrl) 68 | 69 | while f.tell() != fragCodeOffs: 70 | f.write(b'\x00') 71 | 72 | f.write(fragCode) 73 | 74 | while f.tell() != vertCodeOffs: 75 | f.write(b'\x00') 76 | 77 | f.write(vertCode) 78 | 79 | if uam_bin != 'glslc': 80 | os.remove(vertexCtrlPath) 81 | os.remove(fragCtrlPath) 82 | os.remove(vertexCodePath) 83 | os.remove(fragCodePath) 84 | -------------------------------------------------------------------------------- /cmake/addons.cmake: -------------------------------------------------------------------------------- 1 | function(enable_addons) 2 | foreach(addon IN LISTS HAKKUN_ADDONS) 3 | set(ADDON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sys/addons/${addon}) 4 | if (NOT IS_DIRECTORY ${ADDON_DIR}) 5 | message(FATAL_ERROR "${addon} is not an addon!") 6 | endif() 7 | 8 | add_subdirectory(${ADDON_DIR}) 9 | target_include_directories(LibHakkun PRIVATE ${ADDON_DIR}/include) 10 | target_include_directories(${PROJECT_NAME} PRIVATE ${ADDON_DIR}/include) 11 | target_include_directories(${addon} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/sys/hakkun/include) 12 | target_link_libraries(${PROJECT_NAME} PRIVATE ${addon}) 13 | 14 | target_compile_definitions(${PROJECT_NAME} PRIVATE HK_ADDON_${addon}) 15 | target_compile_definitions(${addon} PRIVATE HK_ADDON_${addon}) 16 | target_compile_definitions(LibHakkun PRIVATE HK_ADDON_${addon}) 17 | 18 | message("Enabled addon ${addon}") 19 | endforeach() 20 | 21 | foreach(addon IN LISTS HAKKUN_ADDONS) 22 | foreach(saddon IN LISTS HAKKUN_ADDONS) 23 | if (NOT saddon STREQUAL addon) 24 | set(ADDON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sys/addons/${saddon}) 25 | target_include_directories(${addon} PRIVATE ${ADDON_DIR}/include ${ADDON_DIR}/include/hk) 26 | endif() 27 | target_compile_definitions(${addon} PRIVATE HK_ADDON_${saddon}) 28 | endforeach() 29 | endforeach() 30 | endfunction() 31 | -------------------------------------------------------------------------------- /cmake/apply_config.cmake: -------------------------------------------------------------------------------- 1 | function(apply_config project) 2 | foreach(item IN LISTS LLDFLAGS) 3 | list(APPEND LLDFLAGS_WL "-Wl,${item}") 4 | endforeach() 5 | target_link_options(${project} PRIVATE ${LLDFLAGS_WL}) 6 | 7 | if (TARGET_IS_STATIC) 8 | target_compile_definitions(${project} PRIVATE TARGET_IS_STATIC) 9 | endif() 10 | 11 | if (SDK_PAST_1900) 12 | target_compile_definitions(${project} PRIVATE __RTLD_PAST_19XX__) 13 | endif() 14 | 15 | if (NOT USE_SAIL) 16 | target_compile_definitions(${project} PRIVATE HK_DISABLE_SAIL) 17 | endif() 18 | 19 | target_compile_definitions(${project} PRIVATE NNSDK HK_HOOK_TRAMPOLINE_POOL_SIZE=${TRAMPOLINE_POOL_SIZE} MODULE_NAME=${MODULE_NAME}) 20 | 21 | target_include_directories(${project} PRIVATE ${INCLUDES}) 22 | target_compile_definitions(${project} PRIVATE ${DEFINITIONS}) 23 | 24 | if (BAKE_SYMBOLS) 25 | target_compile_definitions(${project} PRIVATE HK_USE_PRECALCULATED_SYMBOL_DB_HASHES) 26 | endif() 27 | 28 | if(CMAKE_BUILD_TYPE STREQUAL Release) 29 | set(OPTIMIZE_OPTIONS ${OPTIMIZE_OPTIONS_RELEASE}) 30 | else() 31 | set(OPTIMIZE_OPTIONS ${OPTIMIZE_OPTIONS_DEBUG}) 32 | endif() 33 | 34 | target_compile_options(${project} PRIVATE 35 | $<$:${ASM_OPTIONS}> 36 | ) 37 | target_compile_options(${project} PRIVATE 38 | $<$:${OPTIMIZE_OPTIONS} ${WARN_OPTIONS} ${C_OPTIONS}> 39 | ) 40 | target_compile_options(${project} PRIVATE 41 | $<$:${OPTIMIZE_OPTIONS} ${WARN_OPTIONS} ${C_OPTIONS} ${CXX_OPTIONS}> 42 | ) 43 | 44 | target_link_options(${project} PRIVATE ${LINKFLAGS} ${OPTIMIZE_OPTIONS}) 45 | 46 | target_compile_definitions(${project} PRIVATE HK_TITLE_ID=${TITLE_ID}) 47 | endfunction() 48 | -------------------------------------------------------------------------------- /cmake/bin2s.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | function(embed_file PROJECT_NAME INPUT_FILE FILENAME ALIGN IS_DATA) 4 | set(SPATH ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}_bin2s.S) 5 | set(HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/embed_${FILENAME}.h) 6 | 7 | get_filename_component(FILENAME ${INPUT_FILE} NAME) 8 | 9 | string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" SYMBOL_NAME ${FILENAME}) 10 | string(REGEX REPLACE "^[0-9]" "_\\0" SYMBOL_NAME ${SYMBOL_NAME}) 11 | 12 | add_custom_command( 13 | OUTPUT ${SPATH} ${HEADER_FILE} 14 | COMMAND ${CMAKE_COMMAND} 15 | -DINPUT_FILE=${INPUT_FILE} 16 | -DOUTPUT_ASM_FILE=${SPATH} 17 | -DOUTPUT_HEADER_FILE=${HEADER_FILE} 18 | -DSYMBOL_NAME=${SYMBOL_NAME} 19 | -DALIGNMENT=${ALIGN} 20 | -DIS_DATA=${IS_DATA} 21 | -P ${ROOTDIR}/sys/cmake/impl/bin2s.cmake 22 | DEPENDS ${INPUT_FILE} 23 | VERBATIM 24 | ) 25 | 26 | target_sources(${PROJECT_NAME} PRIVATE ${SPATH}) 27 | 28 | get_filename_component(HEADER_DIR ${HEADER_FILE} DIRECTORY) 29 | target_include_directories(${PROJECT_NAME} PRIVATE ${HEADER_DIR}) 30 | endfunction() 31 | -------------------------------------------------------------------------------- /cmake/deploy.cmake: -------------------------------------------------------------------------------- 1 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/exefs) 2 | 3 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 4 | COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/sys/tools/deploy.py ${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_NAME} ${TITLE_ID} ${MODULE_BINARY} ${TARGET_IS_STATIC} 5 | ) 6 | -------------------------------------------------------------------------------- /cmake/generate_exefs.cmake: -------------------------------------------------------------------------------- 1 | function(generate_exefs) 2 | configure_file(${PROJECT_SOURCE_DIR}/config/npdm.json ${CMAKE_CURRENT_BINARY_DIR}/npdm.json) 3 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 4 | COMMAND ${CMAKE_COMMAND} -E echo "-- Generating main.npdm" 5 | COMMAND ${SWITCHTOOLS}/npdmtool ${CMAKE_CURRENT_BINARY_DIR}/npdm.json ${CMAKE_CURRENT_BINARY_DIR}/main.npdm 2>> ${CMAKE_CURRENT_BINARY_DIR}/npdmtool.log 6 | ) 7 | 8 | if (USE_SAIL AND BAKE_SYMBOLS) 9 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 10 | COMMAND ${CMAKE_COMMAND} -E echo "-- Generating ${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}.baked" 11 | COMMAND python ${CMAKE_SOURCE_DIR}/sys/tools/bake_hashes.py ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX} 12 | ) 13 | 14 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 15 | COMMAND ${CMAKE_COMMAND} -E echo "-- Generating ${PROJECT_NAME}.nso" 16 | COMMAND ${PROJECT_SOURCE_DIR}/sys/tools/elf2nso.py ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}.baked ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.nso -c 17 | ) 18 | else() 19 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 20 | COMMAND ${CMAKE_COMMAND} -E echo "-- Generating ${PROJECT_NAME}.nso" 21 | COMMAND ${PROJECT_SOURCE_DIR}/sys/tools/elf2nso.py ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.nso -c 22 | ) 23 | endif() 24 | endfunction() 25 | -------------------------------------------------------------------------------- /cmake/impl/bin2s.cmake: -------------------------------------------------------------------------------- 1 | # Parameters: 2 | # - INPUT_FILE: The binary file to process 3 | # - OUTPUT_ASM_FILE: The output assembly file 4 | # - OUTPUT_HEADER_FILE: The output header file 5 | # - SYMBOL_NAME: The symbol name 6 | # - ALIGNMENT: The alignment value to use 7 | # - IS_DATA: Whether to put in .data or .rodata 8 | 9 | file(READ ${INPUT_FILE} BINARY_CONTENT HEX) 10 | file(SIZE ${INPUT_FILE} FILE_SIZE) 11 | 12 | string(LENGTH ${BINARY_CONTENT} HEX_LENGTH) 13 | math(EXPR BYTE_COUNT "${HEX_LENGTH} / 2") 14 | 15 | if(IS_DATA) 16 | set(SECTION ".data") 17 | else() 18 | set(SECTION ".rodata") 19 | endif() 20 | 21 | set(ASMF "// Generated by BIN2S - please don't edit directly\n") 22 | set(ASMF "${ASMF}\t.section ${SECTION}.${SYMBOL_NAME}, \"a\"\n") 23 | set(ASMF "${ASMF}\t.balign ${ALIGNMENT}\n") 24 | set(ASMF "${ASMF}\t.global ${SYMBOL_NAME}\n") 25 | set(ASMF "${ASMF}${SYMBOL_NAME}:\n") 26 | 27 | set(ASMF "${ASMF}\t.byte ") 28 | set(COUNT 0) 29 | set(LINE_COUNT 0) 30 | 31 | while(COUNT LESS BYTE_COUNT) 32 | math(EXPR START_POS "${COUNT} * 2") 33 | string(SUBSTRING ${BINARY_CONTENT} ${START_POS} 2 BYTE_HEX) 34 | 35 | string(PREPEND BYTE_HEX "0x") 36 | math(EXPR BYTE_DEC "${BYTE_HEX}") 37 | 38 | set(FORMATTED_BYTE "${BYTE_DEC}") 39 | if(BYTE_DEC LESS 10) 40 | set(FORMATTED_BYTE " ${BYTE_DEC}") 41 | elseif(BYTE_DEC LESS 100) 42 | set(FORMATTED_BYTE " ${BYTE_DEC}") 43 | endif() 44 | 45 | set(ASMF "${ASMF}${FORMATTED_BYTE}") 46 | 47 | math(EXPR COUNT "${COUNT} + 1") 48 | math(EXPR LINE_COUNT "${LINE_COUNT} + 1") 49 | 50 | if(COUNT LESS BYTE_COUNT) 51 | if(LINE_COUNT EQUAL 16) 52 | set(ASMF "${ASMF}\n\t.byte ") 53 | set(LINE_COUNT 0) 54 | else() 55 | set(ASMF "${ASMF},") 56 | endif() 57 | endif() 58 | endwhile() 59 | 60 | set(ASMF "${ASMF}\n\n\t.global ${SYMBOL_NAME}_end\n") 61 | set(ASMF "${ASMF}${SYMBOL_NAME}_end:\n\n") 62 | 63 | set(ASMF "${ASMF}\t.global ${SYMBOL_NAME}_size\n") 64 | set(ASMF "${ASMF}\t.balign 4\n") 65 | set(ASMF "${ASMF}${SYMBOL_NAME}_size: .int ${FILE_SIZE}\n\n") 66 | 67 | set(ASMF "${ASMF}#if defined(__linux__) && defined(__ELF__)\n.section .note.GNU-stack,\"\",\"%progbits\n#endif") 68 | 69 | file(WRITE ${OUTPUT_ASM_FILE} "${ASMF}") 70 | 71 | if (IS_DATA) 72 | set(CONST_SPECIFIER "") 73 | else() 74 | set(CONST_SPECIFIER "const") 75 | endif() 76 | 77 | set(HDRF "// Generated by BIN2S - don't edit directly\n") 78 | set(HDRF "${HDRF}#pragma once\n") 79 | set(HDRF "${HDRF}#include \n") 80 | set(HDRF "${HDRF}#include \n\n") 81 | set(HDRF "${HDRF}extern ${CONST_SPECIFIER} uint8_t ${SYMBOL_NAME}[];\n") 82 | set(HDRF "${HDRF}extern ${CONST_SPECIFIER} uint8_t ${SYMBOL_NAME}_end[];\n") 83 | set(HDRF "${HDRF}#if __cplusplus >= 201103L\n") 84 | set(HDRF "${HDRF}static constexpr size_t ${SYMBOL_NAME}_size=${FILE_SIZE};\n") 85 | set(HDRF "${HDRF}#else\n") 86 | set(HDRF "${HDRF}static const size_t ${SYMBOL_NAME}_size=${FILE_SIZE};\n") 87 | set(HDRF "${HDRF}#endif\n") 88 | 89 | file(WRITE ${OUTPUT_HEADER_FILE} "${HDRF}") 90 | -------------------------------------------------------------------------------- /cmake/module.cmake: -------------------------------------------------------------------------------- 1 | file(TO_CMAKE_PATH "$ENV{SWITCHTOOLS}" SWITCHTOOLS) 2 | file(TO_CMAKE_PATH "$ENV{DEVKITPRO}" DEVKITPRO) 3 | 4 | include(config/config.cmake) 5 | include(sys/cmake/apply_config.cmake) 6 | include(sys/cmake/generate_exefs.cmake) 7 | include(sys/cmake/addons.cmake) 8 | 9 | if(NOT IS_DIRECTORY ${SWITCHTOOLS}) 10 | if(NOT IS_DIRECTORY ${DEVKITPRO}) 11 | message(FATAL_ERROR "Please install devkitA64 or set SWITCHTOOLS in your environment.") 12 | else() 13 | set(SWITCHTOOLS ${DEVKITPRO}/tools/bin) 14 | endif() 15 | endif() 16 | 17 | if (MODULE_BINARY STREQUAL "rtld") 18 | message(FATAL_ERROR "Hakkun cannot be used in place of rtld") 19 | endif() 20 | 21 | set(CMAKE_EXECUTABLE_SUFFIX ".nss") 22 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}) 23 | 24 | include(sys/cmake/watch.cmake) 25 | 26 | set(VERSION_SCRIPT "${CMAKE_SOURCE_DIR}/sys/data/visibility.txt") 27 | set(USER_VERSION_SCRIPT "${CMAKE_SOURCE_DIR}/config/visibility.txt") 28 | if (EXISTS ${USER_VERSION_SCRIPT}) 29 | set(USER_VERSION_SCRIPT_ARG "-Wl,--version-script=${USER_VERSION_SCRIPT}") 30 | else() 31 | set(USER_VERSION_SCRIPT_ARG "") 32 | endif() 33 | 34 | if (IS_32_BIT) 35 | set(LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/sys/data/link.armv7a.ld") 36 | else() 37 | set(LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/sys/data/link.aarch64.ld") 38 | endif() 39 | set(MISC_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/sys/data/misc.ld") 40 | 41 | watch(${PROJECT_NAME} "${LINKER_SCRIPT};${MISC_LINKER_SCRIPT}") 42 | 43 | target_link_options(${PROJECT_NAME} PRIVATE -T${LINKER_SCRIPT} -T${MISC_LINKER_SCRIPT}) 44 | target_link_options(${PROJECT_NAME} PRIVATE -Wl,-init=__module_entry__ -Wl,--pie -Wl,--export-dynamic-symbol=_ZN2nn2ro6detail15g_pAutoLoadListE -Wl,--version-script=${VERSION_SCRIPT} ${USER_VERSION_SCRIPT_ARG}) 45 | 46 | apply_config(${PROJECT_NAME}) 47 | 48 | add_subdirectory(sys/hakkun) 49 | target_link_libraries(${PROJECT_NAME} PRIVATE LibHakkun) 50 | target_include_directories(${PROJECT_NAME} PRIVATE sys/hakkun/include) 51 | 52 | generate_exefs() 53 | 54 | if (TARGET_IS_STATIC) 55 | include(sys/cmake/rtld_dummy.cmake) 56 | 57 | add_rtld_dummy() 58 | endif() 59 | 60 | enable_addons() 61 | -------------------------------------------------------------------------------- /cmake/rtld_dummy.cmake: -------------------------------------------------------------------------------- 1 | function(add_rtld_dummy) 2 | add_executable(rtld 3 | ${PROJECT_SOURCE_DIR}/sys/hakkun/src/rtld/DummyRtld.cpp 4 | ${PROJECT_SOURCE_DIR}/sys/hakkun/src/hk/init/module.S 5 | ${PROJECT_SOURCE_DIR}/sys/hakkun/src/hk/svc/api.aarch64.S 6 | ) 7 | 8 | target_include_directories(rtld PRIVATE 9 | ${PROJECT_SOURCE_DIR}/sys/hakkun/include 10 | ) 11 | 12 | target_link_options(rtld PRIVATE -T${LINKER_SCRIPT} -Wl,--export-dynamic -Wl,--pie) 13 | 14 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 15 | COMMAND ${CMAKE_COMMAND} -E echo "-- Generating rtld.nso" 16 | COMMAND ${PROJECT_SOURCE_DIR}/sys/tools/elf2nso.py ${CMAKE_CURRENT_BINARY_DIR}/rtld${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_CURRENT_BINARY_DIR}/rtld.nso -c 17 | ) 18 | endfunction() -------------------------------------------------------------------------------- /cmake/sail.cmake: -------------------------------------------------------------------------------- 1 | set(SAIL_BIN ${CMAKE_CURRENT_SOURCE_DIR}/sys/hakkun/sail/build/sail) 2 | set(SAIL_LIBS 3 | ${CMAKE_CURRENT_BINARY_DIR}/symboldb.o 4 | ${CMAKE_CURRENT_BINARY_DIR}/datablocks.o 5 | ${CMAKE_CURRENT_BINARY_DIR}/fakesymbols.so 6 | ) 7 | 8 | include(config/config.cmake) 9 | include(sys/cmake/watch.cmake) 10 | set(SAIL_REVISION D) 11 | 12 | function (usesail lib) 13 | if (USE_SAIL) 14 | target_link_options(${lib} PRIVATE ${SAIL_LIBS}) 15 | 16 | if(NOT EXISTS ${SAIL_BIN}) 17 | message(WARNING "Sail binary not found! Running setup_sail.py") 18 | execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/sys/tools/setup_sail.py 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 20 | RESULT_VARIABLE result 21 | ) 22 | endif() 23 | 24 | file(GLOB_RECURSE SYM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/syms/*.sym ${CMAKE_CURRENT_SOURCE_DIR}/sys/addons/*/syms/*.sym) 25 | 26 | set(SYMDEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/config/ModuleList.sym") 27 | set(SYMDEPENDS "${SYMDEPENDS};${CMAKE_CURRENT_SOURCE_DIR}/config/VersionList.sym") 28 | 29 | foreach(item IN LISTS SYM_FILES) 30 | set(SYMDEPENDS "${SYMDEPENDS};${item}") 31 | watch(${lib} ${item}) 32 | endforeach() 33 | 34 | watch(${lib} ${SYMDEPENDS}) 35 | 36 | set(SAIL_CMD ${SAIL_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/config/ModuleList.sym ${CMAKE_CURRENT_SOURCE_DIR}/config/VersionList.sym ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_ASM_COMPILER} $,1,0> ${SAIL_REVISION} ${CMAKE_CURRENT_SOURCE_DIR}/syms) 37 | 38 | file(GLOB_RECURSE ADDONS_SYMS_EMPTY_TEST ${CMAKE_CURRENT_SOURCE_DIR}/sys/addons/*/syms/*.sym) 39 | if (ADDONS_SYMS_EMPTY_TEST) 40 | set(SAIL_CMD ${SAIL_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/sys/addons/*/syms) 41 | endif() 42 | 43 | add_custom_command(TARGET ${lib} PRE_LINK 44 | COMMAND ${SAIL_CMD} 45 | ) 46 | endif() 47 | endfunction() 48 | -------------------------------------------------------------------------------- /cmake/toolchain.cmake: -------------------------------------------------------------------------------- 1 | message(STATUS "Current working directory: ${CMAKE_BINARY_DIR}") 2 | 3 | string(FIND ${CMAKE_SOURCE_DIR} "TryCompile" TRYCOMPILE_IDX) 4 | if (TRYCOMPILE_IDX EQUAL -1) # There is no Better way to eliminate this Parasite 5 | include(config/config.cmake) 6 | endif() 7 | 8 | if (IS_32_BIT) 9 | set(TARGET_TRIPLE "armv7-none-eabi") 10 | set(MARCH "armv7-a") 11 | set(CMAKE_SYSTEM_PROCESSOR armv7-a) 12 | set(ARCH_NAME "aarch32") 13 | else() 14 | set(TARGET_TRIPLE "aarch64-none-elf") 15 | set(MARCH "armv8-a") 16 | set(CMAKE_SYSTEM_PROCESSOR aarch64) 17 | set(ARCH_NAME "aarch64") 18 | endif() 19 | 20 | set(CMAKE_SYSTEM_NAME Generic) 21 | set(CMAKE_SYSTEM_VERSION "NX/Clang") 22 | set(CMAKE_ASM_COMPILER "clang") 23 | set(CMAKE_C_COMPILER "clang") 24 | set(CMAKE_CXX_COMPILER "clang++") 25 | set(CMAKE_EXECUTABLE_SUFFIX ".nss") 26 | set(ARCH_FLAGS "--target=${TARGET_TRIPLE} -march=${MARCH} -mtune=cortex-a57 -fPIC -nodefaultlibs") 27 | 28 | set(DEFAULTDEFINES 29 | -D_POSIX_C_SOURCE=200809L 30 | -D_LIBUNWIND_IS_BAREMETAL 31 | -D_LIBCPP_HAS_THREAD_API_PTHREAD 32 | -D_GNU_SOURCE 33 | -DNNSDK 34 | ) 35 | 36 | set(LIBSTD_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/std") 37 | 38 | set(DEFAULTLIBS 39 | ${LIBSTD_PATH}/libc.a 40 | ${LIBSTD_PATH}/libc++.a 41 | ${LIBSTD_PATH}/libc++abi.a 42 | ${LIBSTD_PATH}/libm.a 43 | ${LIBSTD_PATH}/libunwind.a 44 | ) 45 | 46 | if (IS_32_BIT) 47 | list(APPEND DEFAULTLIBS 48 | ${LIBSTD_PATH}/libclang_rt.builtins-arm.a 49 | ) 50 | else() 51 | list(APPEND DEFAULTLIBS 52 | ${LIBSTD_PATH}/libclang_rt.builtins-aarch64.a 53 | ) 54 | endif() 55 | 56 | set(DEFAULTINCLUDES 57 | ${LIBSTD_PATH}/llvm-project/build/include/c++/v1 58 | ${LIBSTD_PATH}/llvm-project/libunwind/include 59 | ${LIBSTD_PATH}/musl/include 60 | ${LIBSTD_PATH}/musl/obj/include 61 | ${LIBSTD_PATH}/musl/arch/generic 62 | ) 63 | 64 | if (IS_32_BIT) 65 | list(APPEND DEFAULTINCLUDES 66 | ${LIBSTD_PATH}/musl/arch/arm 67 | ) 68 | else() 69 | list(APPEND DEFAULTINCLUDES 70 | ${LIBSTD_PATH}/musl/arch/aarch64 71 | ) 72 | endif() 73 | 74 | set(STDLIB_FOUND TRUE) 75 | foreach (item IN LISTS DEFAULTLIBS) 76 | if (NOT EXISTS ${item}) 77 | set(STDLIB_FOUND FALSE) 78 | endif() 79 | endforeach() 80 | foreach (item IN LISTS DEFAULTINCLUDES) 81 | if (NOT EXISTS ${item}) 82 | set(STDLIB_FOUND FALSE) 83 | endif() 84 | endforeach() 85 | 86 | if (NOT STDLIB_FOUND) 87 | message(WARNING "Standard library not found! Running setup_libcxx_prepackaged.py") 88 | execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/sys/tools/setup_libcxx_prepackaged.py ${ARCH_NAME} 89 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 90 | RESULT_VARIABLE result 91 | ) 92 | endif() 93 | 94 | set(DEFAULTINCLUDES_F "") 95 | foreach(item IN LISTS DEFAULTINCLUDES) 96 | set(DEFAULTINCLUDES_F "${DEFAULTINCLUDES_F} -isystem ${item}") 97 | endforeach() 98 | set(DEFAULTLIBS_F "") 99 | foreach(item IN LISTS DEFAULTLIBS) 100 | set(DEFAULTLIBS_F "${DEFAULTLIBS_F} ${item}") 101 | endforeach() 102 | 103 | if (CMAKE_BUILD_TYPE STREQUAL Release) 104 | list(APPEND DEFAULTDEFINES -DHK_RELEASE) 105 | endif() 106 | if (CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) 107 | list(APPEND DEFAULTDEFINES -DHK_RELEASE -DHK_RELEASE_DEBINFO) 108 | endif() 109 | 110 | if (NOT EXCEPTION_FLAGS) 111 | message(WARNING "EXCEPTION_FLAGS not set, using -fno-exceptions -fno-rtti") 112 | set(EXCEPTION_FLAGS "-fno-exceptions -fno-rtti") 113 | endif() 114 | 115 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ARCH_FLAGS} ${DEFAULTINCLUDES_F}") 116 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ARCH_FLAGS} ${DEFAULTINCLUDES_F} ${EXCEPTION_FLAGS}") 117 | set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} ${ARCH_FLAGS}") 118 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${DEFAULTLIBS_F}") 119 | 120 | add_definitions(${DEFAULTDEFINES}) 121 | -------------------------------------------------------------------------------- /cmake/watch.cmake: -------------------------------------------------------------------------------- 1 | function(watch lib) 2 | set_target_properties( 3 | ${lib} 4 | PROPERTIES 5 | LINK_DEPENDS 6 | "${ARGN}" 7 | ) 8 | endfunction() 9 | -------------------------------------------------------------------------------- /data/link.aarch64.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-littleaarch64) 2 | OUTPUT_ARCH(aarch64) 3 | ENTRY(__module_start__) 4 | 5 | PHDRS 6 | { 7 | text PT_LOAD FLAGS(5); 8 | rodata PT_LOAD FLAGS(4); 9 | data PT_LOAD FLAGS(6); 10 | dynamic PT_DYNAMIC; 11 | tdata PT_TLS FLAGS(6); 12 | } 13 | 14 | SECTIONS 15 | { 16 | . = 0x0; 17 | __module_start__ = .; 18 | 19 | .text : { 20 | __text_start__ = .; 21 | KEEP (*(.text.modulestart)) 22 | KEEP (*(.text.crt0)) 23 | *(.text .text.*) 24 | *(.plt .plt.*) 25 | __text_end__ = .; 26 | } :text 27 | 28 | . = ALIGN(0x1000); 29 | 30 | .rodata : { 31 | KEEP (*(.rodata.modulename)) 32 | KEEP (*(.rodata.mod0)) 33 | *(.rodata .rodata.*) 34 | } :rodata 35 | 36 | .hash : { *(.hash) } :rodata 37 | .gnu.hash : { *(.gnu.hash) } :rodata 38 | .dynsym : { *(.dynsym .dynsym.*) } :rodata 39 | .dynstr : { *(.dynstr .dynstr.*) } :rodata 40 | 41 | .gcc_except_table : { *(.gcc_except_table .gcc_except_table.*) } :rodata 42 | .eh_frame_hdr : { 43 | __eh_frame_hdr_start = .; 44 | *(.eh_frame_hdr) 45 | __eh_frame_hdr_end = .; 46 | 47 | __eh_frame_start = .; 48 | *(.eh_frame) 49 | __eh_frame_end = .; 50 | } :rodata 51 | .eh_frame : { KEEP (*(.eh_frame)) } :rodata 52 | 53 | .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata 54 | 55 | . = ALIGN(0x1000); 56 | 57 | .data : { 58 | *(.data .data.*) 59 | } :data 60 | 61 | .data.rela.ro : { 62 | *(.data.rela.ro.local*) 63 | *(.data.rela.ro .data.rela.ro.*) 64 | } :data 65 | 66 | .data.rel.ro : { 67 | *(.data.rel.ro.local*) 68 | *(.data.rel.ro .data.rel.ro.*) 69 | } :data 70 | 71 | .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } :data 72 | 73 | .preinit_array ALIGN(8) : { 74 | __preinit_array_start__ = .; 75 | KEEP(*(.preinit_array)) 76 | __preinit_array_end__ = .; 77 | } :data 78 | 79 | .init_array ALIGN(8) : { 80 | __init_array_start__ = .; 81 | KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*))) 82 | KEEP(*(.init_array)) 83 | __init_array_end__ = .; 84 | } :data 85 | 86 | .fini_array ALIGN(8) : { 87 | __fini_array_start__ = .; 88 | KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*))) 89 | KEEP(*(.fini_array)) 90 | __fini_array_end__ = .; 91 | } :data 92 | 93 | .saildb : { 94 | *(.saildb) 95 | } :data 96 | 97 | .tdata : { 98 | __tdata_align_abs__ = ABSOLUTE(.); 99 | *(.tdata .tdata.*) 100 | } :tdata 101 | 102 | . = ALIGN(0x1000); 103 | 104 | .tbss : { 105 | __tbss_align_abs__ = ABSOLUTE(.); 106 | *(.tbss .tbss.*) 107 | *(.tcommon) 108 | } :tdata 109 | 110 | .bss : { 111 | __bss_start__ = .; 112 | KEEP (*(.bss.rtldmodule)) 113 | *(.bss .bss.*) 114 | *(COMMON) 115 | . = ALIGN(8); 116 | __bss_end__ = .; 117 | . = ALIGN(0x1000); 118 | } :data 119 | 120 | __module_end__ = ABSOLUTE(.); 121 | } 122 | -------------------------------------------------------------------------------- /data/link.armv7a.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf32-littlearm) 2 | OUTPUT_ARCH(armv7-a) 3 | ENTRY(__module_start__) 4 | 5 | PHDRS 6 | { 7 | text PT_LOAD FLAGS(5); 8 | rodata PT_LOAD FLAGS(4); 9 | data PT_LOAD FLAGS(6); 10 | dynamic PT_DYNAMIC; 11 | tdata PT_TLS FLAGS(6); 12 | } 13 | 14 | SECTIONS 15 | { 16 | . = 0x0; 17 | __module_start__ = .; 18 | 19 | .text : { 20 | __text_start__ = .; 21 | KEEP (*(.text.modulestart)) 22 | KEEP (*(.text.crt0)) 23 | *(.text .text.*) 24 | *(.plt .plt.*) 25 | __text_end__ = .; 26 | } :text 27 | 28 | . = ALIGN(0x1000); 29 | 30 | .rodata : { 31 | KEEP (*(.rodata.modulename)) 32 | KEEP (*(.rodata.mod0)) 33 | *(.rodata .rodata.*) 34 | } :rodata 35 | 36 | .hash : { *(.hash) } :rodata 37 | .gnu.hash : { *(.gnu.hash) } :rodata 38 | .dynsym : { *(.dynsym .dynsym.*) } :rodata 39 | .dynstr : { *(.dynstr .dynstr.*) } :rodata 40 | 41 | .gcc_except_table : { *(.gcc_except_table .gcc_except_table.*) } :rodata 42 | .eh_frame_hdr : { 43 | __eh_frame_hdr_start = .; 44 | *(.eh_frame_hdr) 45 | __eh_frame_hdr_end = .; 46 | 47 | __eh_frame_start = .; 48 | *(.eh_frame) 49 | __eh_frame_end = .; 50 | } :rodata 51 | .eh_frame : { KEEP (*(.eh_frame)) } :rodata 52 | 53 | .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata 54 | 55 | . = ALIGN(0x1000); 56 | 57 | .data : { 58 | *(.data .data.*) 59 | } :data 60 | 61 | .data.rela.ro : { 62 | *(.data.rela.ro.local*) 63 | *(.data.rela.ro .data.rela.ro.*) 64 | } :data 65 | 66 | .data.rel.ro : { 67 | *(.data.rel.ro.local*) 68 | *(.data.rel.ro .data.rel.ro.*) 69 | } :data 70 | 71 | .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } :data 72 | 73 | .preinit_array ALIGN(8) : { 74 | __preinit_array_start__ = .; 75 | KEEP(*(.preinit_array)) 76 | __preinit_array_end__ = .; 77 | } :data 78 | 79 | .init_array ALIGN(8) : { 80 | __init_array_start__ = .; 81 | KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*))) 82 | KEEP(*(.init_array)) 83 | __init_array_end__ = .; 84 | } :data 85 | 86 | .fini_array ALIGN(8) : { 87 | __fini_array_start__ = .; 88 | KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*))) 89 | KEEP(*(.fini_array)) 90 | __fini_array_end__ = .; 91 | } :data 92 | 93 | .saildb : { 94 | *(.saildb) 95 | } :data 96 | 97 | .tdata : { 98 | __tdata_align_abs__ = ABSOLUTE(.); 99 | *(.tdata .tdata.*) 100 | } :tdata 101 | 102 | . = ALIGN(0x1000); 103 | 104 | .tbss : { 105 | __tbss_align_abs__ = ABSOLUTE(.); 106 | *(.tbss .tbss.*) 107 | *(.tcommon) 108 | } :tdata 109 | 110 | .bss : { 111 | __bss_start__ = .; 112 | KEEP (*(.bss.rtldmodule)) 113 | *(.bss .bss.*) 114 | *(COMMON) 115 | . = ALIGN(8); 116 | __bss_end__ = .; 117 | . = ALIGN(0x1000); 118 | } :data 119 | 120 | __module_end__ = ABSOLUTE(.); 121 | } 122 | -------------------------------------------------------------------------------- /data/misc.ld: -------------------------------------------------------------------------------- 1 | __cxa_guard_acquire = hk_guard_acquire; 2 | __cxa_guard_release = hk_guard_release; 3 | __cxa_atexit = hk_atexit; -------------------------------------------------------------------------------- /data/replace_bin2s.sed: -------------------------------------------------------------------------------- 1 | s/.rodata/.data/g 2 | s/.balign 4/.balign 0x1000/g -------------------------------------------------------------------------------- /data/visibility.txt: -------------------------------------------------------------------------------- 1 | { 2 | local: 3 | *; 4 | global: 5 | _ZN2nn2ro6detail15g_pAutoLoadListE; 6 | }; -------------------------------------------------------------------------------- /hakkun/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include(../../config/config.cmake) 4 | include(../cmake/apply_config.cmake) 5 | 6 | include_directories(include) 7 | set(SOURCES 8 | src/hk/diag/diag.cpp 9 | 10 | src/hk/hook/MapUtil.cpp 11 | src/hk/hook/Trampoline.cpp 12 | 13 | src/hk/init/module.S 14 | src/hk/init/module.cpp 15 | 16 | src/hk/os/Libcxx.cpp 17 | 18 | src/hk/ro/RoUtil.cpp 19 | src/hk/ro/RoModule.cpp 20 | 21 | src/hk/sail/detail.cpp 22 | src/hk/sail/init.cpp 23 | 24 | src/hk/util/Context.cpp 25 | 26 | src/rtld/RoModule.cpp 27 | ) 28 | 29 | if (IS_32_BIT) 30 | set(SOURCES ${SOURCES} 31 | src/hk/svc/api.armv7a.S 32 | ) 33 | else() 34 | set(SOURCES ${SOURCES} 35 | src/hk/svc/api.aarch64.S 36 | ) 37 | endif() 38 | 39 | add_library(LibHakkun ${SOURCES}) 40 | 41 | apply_config(LibHakkun) 42 | 43 | set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../) 44 | -------------------------------------------------------------------------------- /hakkun/include/hk/Result.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace hk { 6 | 7 | class Result { 8 | u32 mValue = 0; 9 | 10 | // BOOOOOOOOOOOORIIIING 11 | static constexpr u32 makeResult(int module, int description) { 12 | return (module & 0b0111111111) | ((description) & 0b01111111111111) << 9; 13 | } 14 | 15 | public: 16 | constexpr Result() 17 | : mValue(0) { } 18 | constexpr Result(u32 value) 19 | : mValue(value) { } 20 | constexpr Result(int module, int description) 21 | : mValue(makeResult(module, description)) { } 22 | 23 | constexpr u32 getValue() const { return mValue; } 24 | constexpr operator u32() const { return mValue; } 25 | constexpr operator bool() const { return failed(); } 26 | 27 | constexpr int getModule() const { return mValue & 0b0111111111; } 28 | constexpr int getDescription() const { return ((mValue) >> 9) & 0b01111111111111; } 29 | 30 | constexpr bool operator==(const Result& rhs) const { return rhs.mValue == mValue; } 31 | 32 | constexpr bool succeeded() const { return mValue == 0; } 33 | constexpr bool failed() const { return !succeeded(); } 34 | }; 35 | 36 | template 37 | class ResultV : public Result { 38 | public: 39 | constexpr ResultV() 40 | : Result(Module, Description) { } 41 | 42 | ResultV(u32 value) = delete; 43 | ResultV(int module, int description) = delete; 44 | }; 45 | 46 | template 47 | struct ResultRange { 48 | static constexpr bool includes(Result value) { 49 | u32 intval = value; 50 | return intval >= Min && intval <= Max; 51 | } 52 | }; 53 | 54 | using ResultSuccess = ResultV<0, 0>; 55 | 56 | #define HK_RESULT_MODULE(ID) \ 57 | namespace _hk_result_id_namespace { \ 58 | constexpr int module = ID; \ 59 | } 60 | 61 | #define HK_DEFINE_RESULT_RANGE(NAME, MIN, MAX) using ResultRange##NAME = ::hk::ResultRange<::hk::ResultV<_hk_result_id_namespace::module, MIN>().getValue(), ::hk::ResultV<_hk_result_id_namespace::module, MAX>().getValue()>; 62 | #define HK_DEFINE_RESULT(NAME, DESCRIPTION) \ 63 | using Result##NAME = ::hk::ResultV<_hk_result_id_namespace::module, DESCRIPTION>; 64 | 65 | template 66 | hk_alwaysinline bool isResult(Result value) { 67 | return value == ResultType(); 68 | } 69 | 70 | #define HK_TRY(RESULT) \ 71 | { \ 72 | const ::hk::Result _result_temp = RESULT; \ 73 | if (_result_temp.failed()) \ 74 | return _result_temp; \ 75 | } 76 | 77 | #define HK_UNLESS(CONDITION, RESULT) \ 78 | { \ 79 | const bool _condition_temp = (CONDITION); \ 80 | const ::hk::Result _result_temp = RESULT; \ 81 | if (_condition_temp == false) \ 82 | return _result_temp; \ 83 | } 84 | 85 | } // namespace hk 86 | -------------------------------------------------------------------------------- /hakkun/include/hk/diag/results.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/Result.h" 4 | 5 | namespace hk::diag { 6 | 7 | HK_RESULT_MODULE(412) 8 | HK_DEFINE_RESULT_RANGE(Diag, 10, 19) 9 | 10 | HK_DEFINE_RESULT(AssertionFailure, 10) 11 | HK_DEFINE_RESULT(Abort, 11) 12 | HK_DEFINE_RESULT(NotAnNnsdkThread, 19) 13 | 14 | } // namespace hk::ro 15 | -------------------------------------------------------------------------------- /hakkun/include/hk/hook/MapUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace hk::hook { 6 | 7 | ptr findAslr(size searchSize); 8 | Result mapRoToRw(ptr addr, size mapSize, ptr* outRw); 9 | 10 | } // namespace hk::hook 11 | -------------------------------------------------------------------------------- /hakkun/include/hk/hook/Replace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/Result.h" 4 | #include "hk/hook/InstrUtil.h" 5 | #include "hk/hook/results.h" 6 | #include "hk/ro/RoUtil.h" 7 | #include "hk/util/Context.h" 8 | #include "hk/util/Lambda.h" 9 | #include 10 | 11 | namespace hk::hook { 12 | 13 | template 14 | class ReplaceHook { 15 | protected: 16 | const Func mFunc = nullptr; 17 | const ro::RoModule* mModule = nullptr; 18 | ptr mOffset = 0; 19 | u32 mOrigInstr = 0; 20 | 21 | ptr getAt() const { return mModule->range().start() + mOffset; } 22 | 23 | public: 24 | ReplaceHook(Func func) 25 | : mFunc(func) { } 26 | 27 | template 28 | ReplaceHook(L func) 29 | : mFunc((typename util::FunctionTraits::FuncPtrType)func) { } 30 | 31 | bool isInstalled() const { return mOrigInstr != 0; } 32 | 33 | virtual Result installAtOffset(const ro::RoModule* module, ptr offset) { 34 | HK_UNLESS(!isInstalled(), ResultAlreadyInstalled()); 35 | 36 | mModule = module; 37 | mOffset = offset; 38 | 39 | mOrigInstr = *cast(getAt()); 40 | Result rc = writeBranch(mModule, mOffset, mFunc); 41 | if (rc.failed()) { 42 | mModule = nullptr; 43 | mOffset = 0; 44 | mOrigInstr = 0; 45 | return rc; 46 | } 47 | 48 | return ResultSuccess(); 49 | } 50 | 51 | template 52 | Result installAtPtr(T* addr) { 53 | auto* module = ro::getModuleContaining(ptr(addr)); 54 | HK_UNLESS(module != nullptr, ResultOutOfBounds()); 55 | 56 | return installAtOffset(module, ptr(addr) - module->range().start()); 57 | } 58 | 59 | template 60 | hk_alwaysinline Result installAtSym() { 61 | ptr addr = util::lookupSymbol(); 62 | 63 | return installAtPtr(cast(addr)); 64 | } 65 | 66 | virtual Result uninstall() { 67 | HK_UNLESS(isInstalled(), ResultNotInstalled()); 68 | 69 | HK_TRY(mModule->writeRo(mOffset, mOrigInstr)); 70 | mModule = nullptr; 71 | mOffset = 0; 72 | mOrigInstr = 0; 73 | 74 | return ResultSuccess(); 75 | } 76 | 77 | Result installAtMainOffset(ptr offset) { 78 | return installAtOffset(ro::getMainModule(), offset); 79 | } 80 | }; 81 | 82 | /*template 83 | ReplaceHook replace(Return (*func)(Args...)) { 84 | return { func }; 85 | }*/ 86 | 87 | template 88 | typename std::enable_if::value, ReplaceHook::FuncPtrType>>::type replace(L func) { 89 | using Func = typename util::FunctionTraits::FuncPtrType; 90 | return { (Func)func }; 91 | } 92 | 93 | } // namespace hk::hook 94 | 95 | template 96 | using HkReplace = hk::hook::ReplaceHook; 97 | template 98 | using HkReplaceVarArgs = hk::hook::ReplaceHook; 99 | -------------------------------------------------------------------------------- /hakkun/include/hk/hook/Trampoline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/hook/InstrUtil.h" 4 | #include "hk/hook/Replace.h" 5 | #include "hk/svc/api.h" 6 | #include "hk/util/PoolAllocator.h" 7 | 8 | namespace hk::hook { 9 | 10 | namespace detail { 11 | 12 | struct TrampolineBackup { 13 | Instr origInstr; 14 | Instr bRetInstr; 15 | 16 | ptr getRx() const; 17 | }; 18 | 19 | extern util::PoolAllocator sTrampolinePool; 20 | 21 | } // namespace detail 22 | 23 | template 24 | class TrampolineHook : public ReplaceHook { 25 | using Rp = ReplaceHook; 26 | 27 | detail::TrampolineBackup* mBackup = nullptr; 28 | 29 | Func getBackupFuncPtr() const { return cast(mBackup->getRx()); } 30 | 31 | using Rp::getAt; 32 | using Rp::mFunc; 33 | using Rp::mModule; 34 | using Rp::mOffset; 35 | using Rp::mOrigInstr; 36 | 37 | public: 38 | TrampolineHook(Func func) 39 | : Rp(func) { } 40 | 41 | template 42 | TrampolineHook(L func) 43 | : Rp(func) { } 44 | 45 | Result installAtOffset(const ro::RoModule* module, ptr offset) override { 46 | HK_UNLESS(!Rp::isInstalled(), ResultAlreadyInstalled()); 47 | 48 | mModule = module; 49 | mOffset = offset; 50 | 51 | mOrigInstr = *cast(getAt()); 52 | Result rc = writeBranch(mModule, mOffset, mFunc); 53 | if (rc.failed()) { 54 | mModule = nullptr; 55 | mOffset = 0; 56 | mOrigInstr = 0; 57 | return rc; 58 | } 59 | 60 | mBackup = detail::sTrampolinePool.allocate(); 61 | HK_ABORT_UNLESS(mBackup != nullptr, "TrampolinePool full! Current size: 0x%x", HK_HOOK_TRAMPOLINE_POOL_SIZE); 62 | mBackup->origInstr = mOrigInstr; // TODO: Relocate instruction, or at least abort if instruction needs to be relocated 63 | mBackup->bRetInstr = makeB(mBackup->getRx() + sizeof(Instr), getAt() + sizeof(Instr)); 64 | svc::clearCache(mBackup->getRx(), sizeof(detail::TrampolineBackup)); 65 | 66 | orig = getBackupFuncPtr(); 67 | 68 | return ResultSuccess(); 69 | } 70 | 71 | Result uninstall() override { 72 | HK_UNLESS(Rp::isInstalled(), ResultNotInstalled()); 73 | 74 | detail::sTrampolinePool.free(mBackup); 75 | mBackup = nullptr; 76 | 77 | HK_TRY(Rp::mModule->writeRo(mOffset, mOrigInstr)); 78 | mModule = nullptr; 79 | mOffset = 0; 80 | mOrigInstr = 0; 81 | 82 | return ResultSuccess(); 83 | } 84 | 85 | Func orig = nullptr; 86 | }; 87 | 88 | template 89 | typename std::enable_if::value, TrampolineHook::FuncPtrType>>::type trampoline(L func) { 90 | using Func = typename util::FunctionTraits::FuncPtrType; 91 | return { (Func)func }; 92 | } 93 | 94 | } // namespace hk::hook 95 | 96 | template 97 | using HkTrampoline = hk::hook::TrampolineHook; 98 | template 99 | using HkTrampolineVarArgs = hk::hook::TrampolineHook; 100 | -------------------------------------------------------------------------------- /hakkun/include/hk/hook/results.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/Result.h" 4 | 5 | namespace hk::hook { 6 | 7 | HK_RESULT_MODULE(412) 8 | HK_DEFINE_RESULT_RANGE(Hook, 20, 29) 9 | 10 | HK_DEFINE_RESULT(AlreadyInstalled, 20) 11 | HK_DEFINE_RESULT(NotInstalled, 21) 12 | HK_DEFINE_RESULT(OutOfBounds, 22) 13 | HK_DEFINE_RESULT(MismatchedInstruction, 23) 14 | HK_DEFINE_RESULT(InvalidRead, 24) 15 | HK_DEFINE_RESULT(NotUninstallable, 25) 16 | 17 | } // namespace hk::hook 18 | -------------------------------------------------------------------------------- /hakkun/include/hk/init/module.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include "rtld/RoModule.h" 5 | 6 | namespace hk::init { 7 | 8 | #define MODULE_NAME_STR STR(MODULE_NAME) ".nss" 9 | 10 | struct ModuleName { 11 | u32 _0 = 0; 12 | u32 nameLen = sizeof(MODULE_NAME_STR) - 1; 13 | char name[sizeof(MODULE_NAME_STR)] = MODULE_NAME_STR; 14 | }; 15 | 16 | extern "C" { 17 | extern nn::ro::detail::RoModule hkRtldModule; 18 | extern u8 __module_start__; 19 | extern const Elf_Dyn _DYNAMIC[]; 20 | extern const Elf_Rela __rela_start__[]; 21 | extern const Elf_Rela __rela_end__[]; 22 | 23 | using InitFuncPtr = void (*)(); 24 | 25 | extern InitFuncPtr __preinit_array_start__[]; 26 | extern InitFuncPtr __preinit_array_end__[]; 27 | extern InitFuncPtr __init_array_start__[]; 28 | extern InitFuncPtr __init_array_end__[]; 29 | 30 | extern const ModuleName hkModuleName; 31 | } 32 | 33 | inline ptr getModuleStart() { return cast(&__module_start__); } 34 | 35 | } // namespace hk::init 36 | -------------------------------------------------------------------------------- /hakkun/include/hk/os/Mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/svc/api.h" 4 | #include "hk/types.h" 5 | 6 | namespace hk::os { 7 | 8 | class Mutex { 9 | Handle mValue = cInvalidHandle; 10 | 11 | static constexpr Handle cInvalidHandle = 0; 12 | static constexpr Handle cWaitMask = 1 << 30; 13 | 14 | static u32 getCurrentThreadHandle() { return hk::svc::getTLS()->nnsdk_thread_ptr->handle; } 15 | 16 | class Lock { 17 | hk::os::Mutex& mMutex; 18 | 19 | public: 20 | Lock(Mutex& mutex) 21 | : mMutex(mutex) { 22 | mMutex.lock(); 23 | } 24 | 25 | ~Lock() { 26 | mMutex.unlock(); 27 | } 28 | 29 | Lock(const Lock&) = delete; 30 | Lock& operator=(const Lock&) = delete; 31 | 32 | Lock(Lock&& other) noexcept 33 | : mMutex(other.mMutex) { 34 | } 35 | 36 | Lock& operator=(Lock&& other) noexcept { 37 | if (this != &other) { 38 | mMutex.unlock(); 39 | mMutex = other.mMutex; 40 | } 41 | return *this; 42 | } 43 | }; 44 | 45 | public: 46 | void lock() { 47 | const Handle currentThread = getCurrentThreadHandle(); 48 | 49 | while (true) { 50 | u32 value = svc::loadExclusive(&mValue); 51 | 52 | if (value == cInvalidHandle) { 53 | if (svc::storeExclusive(&mValue, currentThread)) 54 | return; 55 | continue; 56 | } 57 | 58 | if ((value & ~cWaitMask) == currentThread) { 59 | svc::clearExclusive(); 60 | return; 61 | } 62 | 63 | if ((value & cWaitMask) == 0) { 64 | if (!svc::storeExclusive(&mValue, value | cWaitMask)) 65 | continue; 66 | } else 67 | svc::clearExclusive(); 68 | 69 | hk::svc::ArbitrateLock(value & ~cWaitMask, uintptr_t(&mValue), currentThread); 70 | } 71 | } 72 | 73 | bool tryLock() { 74 | const Handle currentThread = getCurrentThreadHandle(); 75 | u32 value = svc::loadExclusive(&mValue); 76 | 77 | while (true) { 78 | u32 value = svc::loadExclusive(&mValue); 79 | 80 | if (value != cInvalidHandle) { 81 | svc::clearExclusive(); 82 | return false; 83 | } 84 | 85 | if (svc::storeExclusive(&mValue, currentThread)) 86 | return true; 87 | } 88 | } 89 | 90 | void unlock() { 91 | const Handle currentThread = getCurrentThreadHandle(); 92 | 93 | while (true) { 94 | u32 value = svc::loadExclusive(&mValue); 95 | 96 | if ((value & ~cWaitMask) != currentThread) { 97 | svc::clearExclusive(); 98 | break; 99 | } 100 | 101 | if (svc::storeExclusive(&mValue, cInvalidHandle)) { 102 | if (value & cWaitMask) 103 | hk::svc::ArbitrateUnlock(uintptr_t(&mValue)); 104 | return; 105 | } 106 | } 107 | } 108 | 109 | bool isLockedByCurrentThread() const { 110 | return (mValue & ~cWaitMask) == getCurrentThreadHandle(); 111 | } 112 | 113 | Lock lockScoped() { return { *this }; } 114 | }; 115 | 116 | } // namespace hk::os 117 | -------------------------------------------------------------------------------- /hakkun/include/hk/ro/RoModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include "rtld/RoModule.h" 5 | 6 | namespace hk::ro { 7 | 8 | struct RoModule { 9 | struct Range { 10 | ::size size() const { return mSize; } 11 | ptr start() const { return mStart; } 12 | ptr end() const { return mStart + mSize; } 13 | 14 | Range() = default; 15 | Range(ptr start, ::size size) 16 | : mStart(start) 17 | , mSize(size) { } 18 | 19 | private: 20 | ptr mStart = 0; 21 | ::size mSize = 0; 22 | }; 23 | 24 | nn::ro::detail::RoModule* module = nullptr; 25 | Range text; 26 | Range rodata; 27 | Range data; 28 | 29 | Range range() const { return { text.start(), text.size() + rodata.size() + data.size() }; } 30 | Result findRanges(); 31 | Result mapRw(); 32 | 33 | Result writeRo(ptr offset, const void* source, size writeSize) const; 34 | 35 | template 36 | Result writeRo(ptr offset, const T& value) const { 37 | return writeRo(offset, &value, sizeof(T)); 38 | } 39 | 40 | const char* getModuleName() const { 41 | struct { 42 | u32 _0; 43 | u32 nameLength; 44 | char name[]; 45 | }* name { (typeof(name))rodata.start() }; 46 | return name->name; 47 | } 48 | 49 | private: 50 | Range textRw; 51 | Range rodataRw; 52 | }; 53 | 54 | using RoWriteCallback = void (*)(const RoModule* module, ptr offsetIntoModule, const void* source, size writeSize); 55 | void setRoWriteCallback(RoWriteCallback callback); 56 | 57 | } // namespace hk::ro 58 | -------------------------------------------------------------------------------- /hakkun/include/hk/ro/RoUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/ro/RoModule.h" 4 | #include "hk/ro/results.h" 5 | #include "hk/types.h" 6 | #include "rtld/RoModule.h" 7 | 8 | namespace hk::ro { 9 | 10 | constexpr static size sMaxModuleNum = 1 /* rtld */ + 1 /* main */ + 10 /* subsdk0-subsdk9 */ + 1 /* nnsdk */; 11 | constexpr static size sBuildIdSize = 0x10; // 0x20 but 0x10 because that's usually the minimum size and the linker Loves to not give it 0x20 space and put some SDK+MW balls garbage right into the build id instwead of letting it pad the Zeroes 12 | 13 | void initModuleList(); 14 | size getNumModules(); 15 | const RoModule* getModuleByIndex(int idx); 16 | 17 | const RoModule* getMainModule(); 18 | const RoModule* getSelfModule(); 19 | const RoModule* getRtldModule(); 20 | #ifndef TARGET_IS_STATIC 21 | const RoModule* getSdkModule(); 22 | #endif 23 | 24 | const RoModule* getModuleContaining(ptr addr); 25 | 26 | Result getModuleBuildIdByIndex(int idx, u8* out); 27 | 28 | ptr lookupSymbol(const char* symbol); 29 | ptr lookupSymbol(uint64_t bucketHash, uint32_t djb2Hash, uint32_t murmurHash); 30 | 31 | } // namespace hk::ro 32 | -------------------------------------------------------------------------------- /hakkun/include/hk/ro/results.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/Result.h" 4 | 5 | namespace hk::ro { 6 | 7 | HK_RESULT_MODULE(412) 8 | HK_DEFINE_RESULT_RANGE(Ro, 0, 9) 9 | 10 | HK_DEFINE_RESULT(UnusualSectionLayout, 0) 11 | HK_DEFINE_RESULT(GnuHashMissing, 1) 12 | HK_DEFINE_RESULT(AlreadyMapped, 2) 13 | HK_DEFINE_RESULT(NotMapped, 3) 14 | HK_DEFINE_RESULT(OutOfRange, 4) 15 | HK_DEFINE_RESULT(RtldModuleInvalid, 5) 16 | 17 | } // namespace hk::ro 18 | -------------------------------------------------------------------------------- /hakkun/include/hk/sail/init.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace hk::sail { 4 | 5 | void loadSymbols(); 6 | 7 | } // namespace hk::sail 8 | -------------------------------------------------------------------------------- /hakkun/include/hk/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define hk_alwaysinline __attribute__((always_inline)) 8 | #define hk_noinline __attribute__((noinline)) 9 | #define hk_noreturn __attribute__((noreturn)) 10 | #define section(SECTION) __attribute__((section(#SECTION))) 11 | #define STRINGIFY(X) #X 12 | #define STR(X) STRINGIFY(X) 13 | #define CONCAT_IMPL(x, y) x##y 14 | #define CONCAT(x, y) CONCAT_IMPL(x, y) 15 | 16 | #define NON_COPYABLE(CLASS) \ 17 | CLASS(const CLASS&) = delete; \ 18 | CLASS& operator=(const CLASS&) = delete 19 | 20 | #define NON_MOVABLE(CLASS) \ 21 | CLASS(CLASS&&) = delete; \ 22 | CLASS& operator=(CLASS&&) = delete 23 | 24 | #ifdef HK_RELEASE 25 | #define ralwaysinline hk_alwaysinline 26 | #else 27 | #define ralwaysinline 28 | #endif 29 | 30 | using u8 = uint8_t; 31 | using u16 = uint16_t; 32 | using u32 = uint32_t; 33 | using u64 = uint64_t; 34 | using s8 = int8_t; 35 | using s16 = int16_t; 36 | using s32 = int32_t; 37 | using s64 = int64_t; 38 | 39 | using fu8 = uint_fast8_t; 40 | using fu16 = uint_fast16_t; 41 | using fu32 = uint_fast32_t; 42 | using fu64 = uint_fast64_t; 43 | using fs8 = int_fast8_t; 44 | using fs16 = int_fast16_t; 45 | using fs32 = int_fast32_t; 46 | using fs64 = int_fast64_t; 47 | 48 | using f32 = float; 49 | using f64 = double; 50 | 51 | using size = size_t; 52 | using ptrdiff = ptrdiff_t; 53 | using ptr = uintptr_t; 54 | 55 | template 56 | hk_alwaysinline constexpr To cast(From val) { 57 | return reinterpret_cast(val); 58 | } 59 | 60 | template 61 | hk_alwaysinline To pun(From val) { 62 | static_assert(sizeof(To) <= sizeof(From)); 63 | 64 | To toValue; 65 | __builtin_memcpy(&toValue, &val, sizeof(To)); 66 | return toValue; 67 | } 68 | 69 | constexpr size operator""_B(unsigned long long val) { return val; } 70 | constexpr size operator""_KB(unsigned long long val) { return val * 1024; } 71 | constexpr size operator""_MB(unsigned long long val) { return val * 1024 * 1024; } 72 | constexpr size operator""_GB(unsigned long long val) { return val * 1024 * 1024 * 1024; } 73 | 74 | namespace hk { 75 | 76 | using Handle = u32; 77 | 78 | constexpr size cPageSize = 4_KB; 79 | 80 | template 81 | constexpr T alignUp(T from, size alignment) { return T((ptr(from) + (alignment - 1)) & ~(alignment - 1)); } 82 | template 83 | constexpr T alignDown(T from, size alignment) { return T(ptr(from) & ~(alignment - 1)); } 84 | 85 | template 86 | constexpr T alignUpPage(T from) { return alignUp(from, cPageSize); } 87 | template 88 | constexpr T alignDownPage(T from) { return alignDown(from, cPageSize); } 89 | 90 | template 91 | constexpr bool isAligned(T from, size alignment) { return alignDown(from, alignment) == from; } 92 | template 93 | constexpr bool isAlignedPage(T from) { return alignDown(from, cPageSize) == from; } 94 | 95 | constexpr u64 bit(u8 n) { return 1ULL << n; } 96 | 97 | constexpr u64 bits(u8 n) { 98 | if (n == 64) 99 | return 0xFFFFFFFFFFFFFFFF; 100 | return bit(n) - 1; 101 | } 102 | 103 | template 104 | class ScopeGuard { 105 | L mFunc; 106 | bool mExec = true; 107 | 108 | public: 109 | hk_alwaysinline explicit constexpr ScopeGuard(L func, bool exec) 110 | : mFunc(func) 111 | , mExec(exec) { 112 | } 113 | 114 | hk_alwaysinline constexpr ~ScopeGuard() { 115 | if (mExec) 116 | mFunc(); 117 | } 118 | 119 | ScopeGuard(const ScopeGuard&) = delete; 120 | ScopeGuard& operator=(const ScopeGuard&) = delete; 121 | }; 122 | 123 | class ScopeGuardOnExit { 124 | bool mExec = false; 125 | 126 | public: 127 | hk_alwaysinline constexpr ScopeGuardOnExit(bool condition) 128 | : mExec(condition) { } 129 | 130 | template 131 | hk_alwaysinline ScopeGuard constexpr operator+(L&& func) { 132 | return ScopeGuard(func, mExec); 133 | } 134 | }; 135 | 136 | #define defer auto CONCAT(CONCAT(scope_exit_guard_, __LINE__), __COUNTER__) = ::hk::ScopeGuardOnExit(true) + [&]() 137 | #define defer_if(COND) auto CONCAT(CONCAT(scope_exit_guard_, __LINE__), __COUNTER__) = ::hk::ScopeGuardOnExit(COND) + [&]() 138 | 139 | } // namespace hk 140 | 141 | #include "hk/Result.h" 142 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/Algorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | 5 | namespace hk::util { 6 | 7 | template 8 | s32 binarySearch(GetFunc get, fs32 low, fs32 high, T searchValue) { 9 | while (low <= high) { 10 | fs32 mid = (low + high) / 2; 11 | if (get(mid) == searchValue) 12 | return mid; 13 | 14 | if (get(mid) < searchValue) 15 | low = mid + 1; 16 | else 17 | high = mid - 1; 18 | } 19 | 20 | return -1; 21 | } 22 | 23 | } // namespace hk::util 24 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/BitArray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/types.h" 5 | 6 | namespace hk::util { 7 | 8 | template 9 | class BitArray { 10 | static constexpr size sSizeBytes = alignUp(Size, 8) / 8; 11 | 12 | u8 mStorage[sSizeBytes] { 0 }; 13 | 14 | s32 getByteIdxByBitIdx(s32 bitIdx) const { 15 | return bitIdx / 8; 16 | } 17 | 18 | public: 19 | struct BitAccessor { 20 | BitAccessor(BitArray* arr, s32 idx) 21 | : mArray(arr) 22 | , mIndex(idx) { } 23 | 24 | operator bool() const { return mArray->get(mIndex); } 25 | void operator=(const bool& value) { mArray->set(mIndex, value); } 26 | 27 | private: 28 | BitArray* const mArray; 29 | const s32 mIndex = -1; 30 | }; 31 | 32 | struct ConstBitAccessor { 33 | ConstBitAccessor(const BitArray* arr, s32 idx) 34 | : mArray(arr) 35 | , mIndex(idx) { } 36 | 37 | operator bool() const { return mArray->get(mIndex); } 38 | 39 | private: 40 | const BitArray* const mArray; 41 | const s32 mIndex = -1; 42 | }; 43 | 44 | constexpr BitArray() = default; 45 | 46 | void set(s32 idx) { 47 | HK_ABORT_UNLESS(idx >= 0 && idx < Size, "BitArray: index out of bounds (%d/%d)", idx, Size); 48 | u8& byte = mStorage[getByteIdxByBitIdx(idx)]; 49 | int bitIdx = idx % 8; 50 | 51 | byte |= (1 << bitIdx); 52 | } 53 | 54 | void unset(s32 idx) { 55 | HK_ABORT_UNLESS(idx >= 0 && idx < Size, "BitArray: index out of bounds (%d/%d)", idx, Size); 56 | u8& byte = mStorage[getByteIdxByBitIdx(idx)]; 57 | int bitIdx = idx % 8; 58 | 59 | byte &= ~(1 << bitIdx); 60 | } 61 | 62 | bool get(s32 idx) const { 63 | HK_ABORT_UNLESS(idx >= 0 && idx < Size, "BitArray: index out of bounds (%d/%d)", idx, Size); 64 | const u8& byte = mStorage[getByteIdxByBitIdx(idx)]; 65 | int bitIdx = idx % 8; 66 | 67 | return byte & (1 << bitIdx); 68 | } 69 | 70 | void set(s32 idx, bool value) { 71 | value ? set(idx) : unset(idx); 72 | } 73 | 74 | BitAccessor operator[](s32 idx) { 75 | return { this, idx }; 76 | } 77 | 78 | ConstBitAccessor operator[](s32 idx) const { 79 | return { this, idx }; 80 | } 81 | }; 82 | 83 | } // namespace hk::util 84 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/Context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/ro/RoUtil.h" 4 | #include "hk/sail/detail.h" 5 | #include "hk/types.h" 6 | #include "hk/util/TemplateString.h" 7 | 8 | namespace hk::util { 9 | 10 | template 11 | hk_alwaysinline ptr lookupSymbol() { 12 | #ifdef HK_DISABLE_SAIL 13 | return ro::lookupSymbol(Symbol.value); 14 | #else 15 | static ptr addr = 0; 16 | if (addr) 17 | return addr; 18 | 19 | if constexpr (sail::sUsePrecalcHashes) { 20 | constexpr u32 symMurmur = util::hashMurmur(Symbol.value); 21 | addr = sail::lookupSymbolFromDb(&symMurmur); 22 | } else { 23 | addr = sail::lookupSymbolFromDb(Symbol.value); 24 | } 25 | return addr; 26 | #endif 27 | } 28 | 29 | hk_noinline ptr getReturnAddress(int n); 30 | 31 | } // namespace hk::util 32 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/FixedCapacityArray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/types.h" 5 | #include 6 | #include 7 | 8 | namespace hk::util { 9 | 10 | template 11 | class FixedCapacityArray { 12 | std::array mData; 13 | size mSize = 0; 14 | 15 | public: 16 | void add(T value) { 17 | HK_ABORT_UNLESS(mSize < Capacity, "hk::util::FixedCapacityArray::add: Full", Capacity); 18 | mData[mSize++] = value; 19 | } 20 | 21 | T& operator[](size index) { 22 | return mData[index]; 23 | } 24 | const T& operator[](size index) const { 25 | return mData[index]; 26 | } 27 | 28 | template 29 | void forEach(Callback func) { 30 | for (::size i = 0; i < mSize; i++) 31 | func(mData[i]); 32 | } 33 | 34 | bool empty() const { return mSize == 0; } 35 | 36 | void sort() { 37 | std::sort(mData.begin(), mData.begin() + mSize); 38 | } 39 | 40 | template 41 | void sort(Compare comp) { 42 | std::sort(mData.begin(), mData.begin() + mSize, comp); 43 | } 44 | 45 | ::size size() { return mSize; } 46 | constexpr static ::size capacity() { return Capacity; } 47 | }; 48 | 49 | } // namespace hk::util 50 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/InitializeGuard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include "hk/util/Context.h" 5 | #include "hk/util/TemplateString.h" 6 | 7 | namespace hk::util { 8 | 9 | namespace detail { 10 | 11 | extern "C" bool __cxa_guard_acquire(bool*); 12 | extern "C" bool __cxa_guard_release(bool*); 13 | 14 | } // namespace detail 15 | 16 | template 17 | class InitializeGuard { 18 | bool* mGuard = 0; 19 | 20 | public: 21 | hk_alwaysinline constexpr InitializeGuard(ptr guardPtr) 22 | : mGuard(reinterpret_cast(guardPtr)) { 23 | } 24 | 25 | hk_alwaysinline constexpr InitializeGuard() 26 | : InitializeGuard(lookupSymbol()) { } 27 | 28 | template 29 | hk_alwaysinline void constexpr operator+(L&& func) { 30 | if (detail::__cxa_guard_acquire(mGuard)) { 31 | func(); 32 | detail::__cxa_guard_release(mGuard); 33 | } 34 | } 35 | }; 36 | 37 | #define initialize_guard(SYM) ::hk::util::InitializeGuard<#SYM>() + [&]() 38 | #define initialize_guard_ptr(PTR) ::hk::util::InitializeGuard(ptr(PTR)) + [&]() 39 | 40 | } // namespace hk::util 41 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/Lambda.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace hk::util { 4 | 5 | template 6 | struct FunctionTraits : public FunctionTraits { }; 7 | 8 | template 9 | struct FunctionTraits { 10 | using ReturnType = Return; 11 | using FuncPtrType = ReturnType (*)(Args...); 12 | }; 13 | 14 | template 15 | struct FunctionTraits { 16 | using ReturnType = Return; 17 | using FuncPtrType = ReturnType (*)(Args..., ...); 18 | }; 19 | 20 | template 21 | struct LambdaHasCapture { 22 | constexpr static bool value = sizeof(L) != 1; 23 | }; 24 | 25 | } // namespace hk::util 26 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/PoolAllocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/diag/diag.h" 4 | #include "hk/types.h" 5 | #include "hk/util/BitArray.h" 6 | 7 | namespace hk::util { 8 | 9 | template 10 | class PoolAllocator { 11 | BitArray mAllocations; 12 | T* mBuffer = nullptr; 13 | 14 | T* getData(size index) const { 15 | HK_ABORT_UNLESS(index >= 0 && index < Capacity, "PoolAllocator: invalid index (%d not in buffer)", index); 16 | return mBuffer + index; 17 | } 18 | 19 | public: 20 | PoolAllocator(void* buffer) 21 | : mBuffer(cast(buffer)) { } 22 | 23 | s32 allocateIdx() { 24 | for (size i = 0; i < Capacity; i++) { 25 | if (mAllocations[i] == false) { 26 | mAllocations[i] = true; 27 | return i; 28 | } 29 | } 30 | return -1; 31 | } 32 | 33 | T* allocate() { 34 | s32 idx = allocateIdx(); 35 | return idx == -1 ? nullptr : getData(idx); 36 | } 37 | 38 | void freeIdx(s32 index) { 39 | HK_ABORT_UNLESS(index >= 0 && index < Capacity, "PoolAllocator: invalid free (%d not in buffer)", index); 40 | HK_ABORT_UNLESS(mAllocations[index] == true, "PoolAllocator: double free (idx %d)", index); 41 | 42 | mAllocations[index] = false; 43 | } 44 | 45 | void free(T* data) { 46 | size index = data - mBuffer; 47 | HK_ABORT_UNLESS(index >= 0 && index < Capacity, "PoolAllocator: invalid free (%p not in buffer)", data); 48 | HK_ABORT_UNLESS(mAllocations[index] == true, "PoolAllocator: double free (ptr %p, idx %d)", data, index); 49 | 50 | freeIdx(index); 51 | } 52 | }; 53 | 54 | template 55 | class BufferPoolAllocator : public PoolAllocator { 56 | u8 mData[sizeof(T) * Capacity] { 0 }; 57 | 58 | public: 59 | BufferPoolAllocator() 60 | : PoolAllocator(mData) { } 61 | }; 62 | 63 | } // namespace hk::util 64 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/svc/api.h" 4 | #include "hk/types.h" 5 | #include 6 | 7 | namespace hk::util { 8 | 9 | inline u64 getRandomU64() { 10 | std::mt19937_64 engine { svc::getSystemTick() }; 11 | return engine(); 12 | } 13 | 14 | inline u32 getRandomU32() { 15 | std::mt19937 engine { u32(svc::getSystemTick() & 0xFFFFFFFF) }; 16 | return engine(); 17 | } 18 | 19 | } // namespace hk::util 20 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/Singleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/util/Storage.h" 4 | 5 | #define HK_SINGLETON(CLASS) \ 6 | static ::hk::util::Storage sSingletonStorage; \ 7 | \ 8 | public: \ 9 | static CLASS* instance() { return sSingletonStorage.tryGet(); } \ 10 | template \ 11 | static void createInstance(Args... args) { sSingletonStorage.create(args...); } \ 12 | static void deleteInstance() { sSingletonStorage.destroy(); } 13 | 14 | #define HK_SINGLETON_IMPL(CLASS) \ 15 | ::hk::util::Storage CLASS::sSingletonStorage; 16 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/Storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include 5 | 6 | namespace hk::util { 7 | 8 | template 9 | class Storage { 10 | alignas(alignof(T)) u8 mStorage[sizeof(T)] { 0 }; 11 | bool mAlive = false; 12 | 13 | T* getUnsafe() { return cast(mStorage); } 14 | 15 | void destroyImpl() { 16 | getUnsafe()->~T(); 17 | mAlive = false; 18 | } 19 | 20 | template 21 | void createImpl(Args... args) { 22 | new (getUnsafe()) T(args...); 23 | mAlive = true; 24 | } 25 | 26 | public: 27 | bool isAlive() const { return mAlive; } 28 | 29 | bool tryDestroy() { 30 | if (!mAlive) 31 | return false; 32 | destroy(); 33 | return true; 34 | } 35 | 36 | void destroy() { 37 | // assert(mAlive) 38 | destroyImpl(); 39 | } 40 | 41 | template 42 | bool tryCreate(Args... args) { 43 | if (mAlive) 44 | return false; 45 | createImpl(args...); 46 | return true; 47 | } 48 | 49 | template 50 | void create(Args... args) { 51 | // assert(!mAlive); 52 | createImpl(args...); 53 | } 54 | 55 | T* get() { 56 | // assert(mAlive); 57 | return getUnsafe(); 58 | } 59 | 60 | T* tryGet() { 61 | if (!mAlive) 62 | return nullptr; 63 | return getUnsafe(); 64 | } 65 | }; 66 | 67 | } // namespace hk::util 68 | -------------------------------------------------------------------------------- /hakkun/include/hk/util/TemplateString.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hk/types.h" 4 | #include 5 | 6 | namespace hk::util { 7 | 8 | template 9 | struct TemplateString { 10 | char value[N]; 11 | 12 | constexpr TemplateString(const char (&str)[N]) { 13 | std::copy_n(str, N, value); 14 | } 15 | }; 16 | 17 | } // namespace hk::util 18 | -------------------------------------------------------------------------------- /hakkun/include/rtld/ModuleHeader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace rtld { 6 | 7 | struct ModuleHeader { 8 | constexpr static uint32_t MOD0_MAGIC = 0x30444F4D; 9 | 10 | uint32_t magic; 11 | uint32_t dynamic_offset; 12 | uint32_t bss_start_offset; 13 | uint32_t bss_end_offset; 14 | uint32_t unwind_start_offset; 15 | uint32_t unwind_end_offset; 16 | uint32_t module_object_offset; 17 | 18 | bool isValidMagic() const { return magic == MOD0_MAGIC; } 19 | }; 20 | 21 | } // namespace rtld 22 | -------------------------------------------------------------------------------- /hakkun/include/rtld/RoModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "rtld/types.h" 9 | 10 | namespace nn::ro::detail { 11 | 12 | class RoModule { 13 | 14 | private: 15 | // ResolveSymbols internals 16 | inline void ResolveSymbolRelAbsolute(Elf_Rel* entry); 17 | inline void ResolveSymbolRelaAbsolute(Elf_Rela* entry); 18 | inline void ResolveSymbolRelJumpSlot(Elf_Rel* entry, bool do_lazy_got_init); 19 | inline void ResolveSymbolRelaJumpSlot(Elf_Rela* entry, bool do_lazy_got_init); 20 | 21 | public: 22 | // nn::util::IntrusiveListBaseNode 23 | RoModule* next; 24 | RoModule* prev; 25 | 26 | union { 27 | Elf_Rel* rel; 28 | Elf_Rela* rela; 29 | } m_pPlt; 30 | union { 31 | Elf_Rel* rel; 32 | Elf_Rela* rela; 33 | } m_pDyn; 34 | #ifdef __RTLD_PAST_19XX__ 35 | bool m_IsPltRela; 36 | uintptr_t m_Base; 37 | Elf_Dyn* m_pDynamicSection; 38 | #else 39 | uintptr_t m_Base; 40 | Elf_Dyn* m_pDynamicSection; 41 | bool m_IsPltRela; 42 | #endif 43 | size_t m_PltRelSz; 44 | void (*m_pInit)(); 45 | void (*m_pFini)(); 46 | union { 47 | struct { 48 | uint32_t* m_pBuckets; 49 | uint32_t* m_pChains; 50 | }; 51 | struct { 52 | uint64_t* m_pGnuBloomFilter; 53 | uint32_t m_GnuHashMaskwords; 54 | }; 55 | }; 56 | char* m_pStrTab; 57 | Elf_Sym* m_pDynSym; 58 | size_t m_StrSz; 59 | uintptr_t* m_pGot; 60 | size_t m_DynRelaSz; 61 | size_t m_DynRelSz; 62 | size_t m_RelCount; 63 | size_t m_RelaCount; 64 | union { 65 | struct { 66 | size_t m_Symbols; 67 | size_t m_HashSize; 68 | }; 69 | struct { 70 | uint64_t m_GnuHashShift2; 71 | uint32_t* m_pGnuHash; 72 | }; 73 | }; 74 | void* got_stub_ptr; 75 | uint64_t _B8; 76 | uint8_t _C0[3]; 77 | struct { 78 | bool _C3_0 : 1; 79 | bool _C3_1 : 1; 80 | bool _C3_2 : 1; 81 | bool _C3_3 : 1; 82 | bool m_IsGnuHash : 1; 83 | }; 84 | 85 | private: 86 | char m_Padding[0x40]; // Not sure what they added, but this is in older versions of RTLD too. 87 | 88 | Elf_Sym* GetSymbolByNameElf(const char* name) const; 89 | Elf_Sym* GetSymbolByHashesElf(uint64_t bucketHash, uint32_t murmurHash) const; 90 | Elf_Sym* GetSymbolByNameGnu(const char* name) const; 91 | Elf_Sym* GetSymbolByHashesGnu(uint32_t djb2Hash, uint32_t murmurHash) const; 92 | 93 | public: 94 | void Initialize(char* aslr_base, Elf_Dyn* dynamic); 95 | void Relocate(); 96 | Elf_Sym* GetSymbolByName(const char* name) const; 97 | Elf_Sym* GetSymbolByHashes(uint64_t bucketHash, uint32_t djb2Hash, uint32_t murmurHash) const; 98 | void ResolveSymbols(bool do_lazy_got_init); 99 | bool ResolveSym(Elf_Addr* target_symbol_address, Elf_Sym* symbol) const; 100 | }; 101 | 102 | } // namespace nn::ro::detail 103 | -------------------------------------------------------------------------------- /hakkun/include/rtld/RoModuleList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RoModule.h" 4 | 5 | namespace nn::ro::detail { 6 | 7 | struct RoModuleList { 8 | RoModule* front; 9 | RoModule* back; 10 | 11 | class Iterator; 12 | 13 | Iterator begin() { return Iterator(this->back, false); } 14 | 15 | Iterator end() { return Iterator((RoModule*)this, false); } 16 | 17 | Iterator rbegin() { return Iterator(this->front, true); } 18 | 19 | Iterator rend() { return Iterator((RoModule*)this, true); } 20 | 21 | class Iterator { 22 | public: 23 | Iterator(RoModule* pModule, bool reverted) 24 | : m_pCurrentModule(pModule) 25 | , m_Reverted(reverted) { } 26 | 27 | Iterator& operator=(RoModule* pModule) { 28 | m_pCurrentModule = pModule; 29 | return *this; 30 | } 31 | 32 | Iterator& operator++() { 33 | if (m_pCurrentModule) { 34 | m_pCurrentModule = m_Reverted ? m_pCurrentModule->next 35 | : m_pCurrentModule->prev; 36 | } 37 | return *this; 38 | } 39 | 40 | bool operator!=(const Iterator& iterator) { 41 | return m_pCurrentModule != iterator.m_pCurrentModule; 42 | } 43 | 44 | RoModule* operator*() { return m_pCurrentModule; } 45 | 46 | private: 47 | RoModule* m_pCurrentModule; 48 | bool m_Reverted; 49 | }; 50 | }; 51 | 52 | #ifdef __aarch64__ 53 | static_assert(sizeof(RoModuleList) == 0x10, "RoModuleList isn't valid"); 54 | #elif __arm__ 55 | static_assert(sizeof(RoModuleList) == 0x8, "RoModuleList isn't valid"); 56 | #endif 57 | 58 | } // namespace nn::ro::detail 59 | -------------------------------------------------------------------------------- /hakkun/include/rtld/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "elf.h" 4 | 5 | #ifdef __aarch64__ 6 | using Elf_Addr = Elf64_Addr; 7 | using Elf_Rel = Elf64_Rel; 8 | using Elf_Rela = Elf64_Rela; 9 | using Elf_Dyn = Elf64_Dyn; 10 | using Elf_Sym = Elf64_Sym; 11 | using Elf_Xword = Elf64_Xword; 12 | 13 | #define ELF_R_SYM ELF64_R_SYM 14 | #define ELF_R_TYPE ELF64_R_TYPE 15 | #define ELF_ST_BIND ELF64_ST_BIND 16 | #define ELF_ST_VISIBILITY ELF64_ST_VISIBILITY 17 | 18 | #define ARCH_RELATIVE R_AARCH64_RELATIVE 19 | #define ARCH_JUMP_SLOT R_AARCH64_JUMP_SLOT 20 | #define ARCH_GLOB_DAT R_AARCH64_GLOB_DAT 21 | #define ARCH_ABS32 R_AARCH64_ABS32 22 | #define ARCH_ABS64 R_AARCH64_ABS64 23 | #define ARCH_IS_REL_ABSOLUTE(type) \ 24 | (type == R_AARCH64_ABS32 || type == R_AARCH64_ABS64) 25 | #else 26 | using Elf_Addr = Elf32_Addr; 27 | using Elf_Rel = Elf32_Rel; 28 | using Elf_Rela = Elf32_Rela; 29 | using Elf_Dyn = Elf32_Dyn; 30 | using Elf_Sym = Elf32_Sym; 31 | using Elf_Xword = Elf32_Xword; 32 | 33 | #define ELF_R_SYM ELF32_R_SYM 34 | #define ELF_R_TYPE ELF32_R_TYPE 35 | #define ELF_ST_BIND ELF32_ST_BIND 36 | #define ELF_ST_VISIBILITY ELF32_ST_VISIBILITY 37 | 38 | #define ARCH_RELATIVE R_ARM_RELATIVE 39 | #define ARCH_JUMP_SLOT R_ARM_JUMP_SLOT 40 | #define ARCH_GLOB_DAT R_ARM_GLOB_DAT 41 | #define ARCH_ABS32 R_ARM_ABS32 42 | #define ARCH_ABS64 R_ARM_ABS32 43 | #define ARCH_IS_REL_ABSOLUTE(type) \ 44 | (type == R_ARM_ABS32) 45 | #endif 46 | -------------------------------------------------------------------------------- /hakkun/sail/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(sail) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | 6 | set(CMAKE_C_COMPILER clang) 7 | set(CMAKE_CXX_COMPILER clang++) 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 10 | 11 | include_directories(${CMAKE_SOURCE_DIR}/include) 12 | 13 | add_executable(sail src/main.cpp src/parser.cpp src/config.cpp src/fakelib.cpp) 14 | 15 | target_compile_options(sail PRIVATE -Ofast -flto) 16 | target_link_options(sail PRIVATE -flto) 17 | -------------------------------------------------------------------------------- /hakkun/sail/include/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | #include 5 | #include 6 | 7 | namespace sail { 8 | 9 | void loadConfig(const std::string& moduleListPath, const std::string& versionListPath); 10 | int getModuleIndex(const std::string& module); 11 | int getVersionIndex(int moduleIdx, const std::string& version); 12 | 13 | const std::vector>>>& getVersionList(); 14 | 15 | void clearDestinationSymbols(); 16 | bool addDestinationSymbolAndCheckDuplicate(const std::string& symbol); 17 | 18 | bool& is32Bit(); 19 | 20 | } // namespace sail 21 | -------------------------------------------------------------------------------- /hakkun/sail/include/fakelib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "symbol.h" 4 | 5 | namespace sail { 6 | 7 | void generateFakeLib(const std::vector& symbols, const char* outPath, const char* clangBinary); 8 | void generateSymbolDb(const std::vector& symbols, const char* outPath, const char* clangBinary); 9 | 10 | } // namespace sail 11 | -------------------------------------------------------------------------------- /hakkun/sail/include/hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | namespace sail { 6 | namespace detail { 7 | 8 | template 9 | struct ReadDefault { 10 | static constexpr T read(const T* data, ptr offset, void*) { 11 | return data[offset]; 12 | } 13 | }; 14 | 15 | template 16 | constexpr u32 getBlock(const T* p, fu32 i, void* userData) { 17 | static_assert(sizeof(T) == 1); 18 | constexpr auto read = Read::read; 19 | 20 | const u32 a = read(p, 0 + i * 4, userData); 21 | const u32 b = read(p, 1 + i * 4, userData); 22 | const u32 c = read(p, 2 + i * 4, userData); 23 | const u32 d = read(p, 3 + i * 4, userData); 24 | 25 | const u32 block = a << 0 | b << 8 | c << 16 | d << 24; 26 | return block; 27 | } 28 | 29 | constexpr u32 rotateLeft32(u32 x, u32 r) { 30 | return (x << r) | (x >> (32 - r)); 31 | } 32 | 33 | constexpr u32 finalMix(u32 h) { 34 | h ^= h >> 16; 35 | h *= 0x85ebca6b; 36 | h ^= h >> 13; 37 | h *= 0xc2b2ae35; 38 | h ^= h >> 16; 39 | return h; 40 | } 41 | 42 | template 43 | constexpr u32 hashMurmurImpl(const T* data, const fu32 len, const u32 seed = 0, void* userData = nullptr) { 44 | static_assert(sizeof(T) == 1); 45 | constexpr auto read = Read::read; 46 | 47 | const fu32 nblocks = len / 4; 48 | 49 | u32 h1 = seed; 50 | 51 | constexpr u32 c1 = 0xcc9e2d51; 52 | constexpr u32 c2 = 0x1b873593; 53 | 54 | constexpr u32 c3 = 0xe6546b64; 55 | 56 | for (fu32 i = 0; i < nblocks; i++) { 57 | u32 k1 = detail::getBlock(data, i, userData); 58 | 59 | k1 *= c1; 60 | k1 = detail::rotateLeft32(k1, 15); 61 | k1 *= c2; 62 | 63 | h1 ^= k1; 64 | h1 = detail::rotateLeft32(h1, 13); 65 | h1 = h1 * 5 + c3; 66 | } 67 | 68 | u32 k1 = 0; 69 | 70 | const u32 tail = len - (len % 4); 71 | 72 | switch (len & 3) { 73 | case 3: 74 | k1 ^= read(data, tail + 2, userData) << 16; 75 | case 2: 76 | k1 ^= read(data, tail + 1, userData) << 8; 77 | case 1: 78 | k1 ^= read(data, tail + 0, userData); 79 | k1 *= c1; 80 | k1 = detail::rotateLeft32(k1, 15); 81 | k1 *= c2; 82 | h1 ^= k1; 83 | } 84 | 85 | h1 ^= len; 86 | 87 | return detail::finalMix(h1); 88 | } 89 | 90 | } // namespace detail 91 | 92 | constexpr u32 hashMurmur(const char* str, u32 seed = 0) { 93 | return detail::hashMurmurImpl>(str, __builtin_strlen(str), seed); 94 | } 95 | 96 | template 97 | constexpr u32 hashMurmur(const T* data, const fu32 len, const u32 seed = 0) { 98 | static_assert(sizeof(T) == 1); 99 | return detail::hashMurmurImpl>(data, len, seed); 100 | } 101 | 102 | constexpr u64 rtldElfHash(const char* name) { 103 | u64 h = 0; 104 | u64 g; 105 | 106 | while (*name) { 107 | h = (h << 4) + *name++; 108 | if ((g = h & 0xf0000000)) 109 | h ^= g >> 24; 110 | h &= ~g; 111 | } 112 | return h; 113 | } 114 | 115 | constexpr u32 djb2Hash(const char* name) { 116 | u32 h = 5381; 117 | for (const char* p = name; *p; p++) 118 | h = h * 33 + static_cast(*p); 119 | return h; 120 | } 121 | 122 | } // namespace sail 123 | -------------------------------------------------------------------------------- /hakkun/sail/include/parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "symbol.h" 4 | #include 5 | 6 | namespace sail { 7 | 8 | std::vector parseSymbolFile(const std::string& data, const std::string& filePath); 9 | std::vector> parseModuleList(const std::string& data, const std::string& filePath); 10 | std::vector>>> parseVersionList(const std::string& data, const std::string& filePath, const std::vector>& moduleList); 11 | 12 | } // namespace sail 13 | -------------------------------------------------------------------------------- /hakkun/sail/include/symbol.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | #include 5 | #include 6 | 7 | namespace sail { 8 | 9 | struct Symbol { 10 | enum Type 11 | { 12 | Immediate, 13 | Dynamic, 14 | DataBlock, 15 | ReadADRPGlobal, 16 | Arithmetic, 17 | MultipleCandidate, 18 | }; 19 | enum DataBlockVersionBoundary 20 | { 21 | None, 22 | Older, // older than version 23 | From, // version or newer than version 24 | }; 25 | enum Section 26 | { 27 | All, 28 | Text, 29 | Rodata, 30 | Data, // .data and .bss and etc 31 | }; 32 | 33 | bool useCache = true; 34 | Type type; 35 | struct { 36 | int moduleIdx; 37 | u32 offset; 38 | } dataImmediate; 39 | struct { 40 | std::string name; 41 | } dataDynamic; 42 | struct { 43 | int moduleIdx; 44 | std::vector data; 45 | std::vector dataMask; 46 | s32 offs = 0; 47 | DataBlockVersionBoundary boundary = None; 48 | Section sectionLimit; 49 | u32 versionBoundary = 0; 50 | 51 | bool hasMatches() const { 52 | for (u8 b : dataMask) 53 | if (b != 0xFF) 54 | return true; 55 | return false; 56 | } 57 | } dataDataBlock; 58 | struct { 59 | std::string atSymbol; 60 | s32 offsetToLoInstr; 61 | } dataReadADRPGlobal; 62 | struct { 63 | std::string symbolToAddTo; 64 | s32 offset; 65 | } dataArithmetic; 66 | 67 | std::string getDataBlockSearchFunctionName() const { 68 | std::string name = "__sail_datablock_"; 69 | name.append(std::to_string(dataDataBlock.versionBoundary)); 70 | name.append("_"); 71 | name.append(std::to_string(dataDataBlock.boundary)); 72 | name.append("__"); 73 | name.append(this->name); 74 | return name; 75 | } 76 | 77 | std::string name; 78 | std::vector versionIndices; 79 | }; 80 | 81 | } // namespace sail 82 | -------------------------------------------------------------------------------- /hakkun/sail/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using u8 = uint8_t; 7 | using u16 = uint16_t; 8 | using u32 = uint32_t; 9 | using u64 = uint64_t; 10 | using s8 = int8_t; 11 | using s16 = int16_t; 12 | using s32 = int32_t; 13 | using s64 = int64_t; 14 | 15 | using fu8 = uint_fast8_t; 16 | using fu16 = uint_fast16_t; 17 | using fu32 = uint_fast32_t; 18 | using fu64 = uint_fast64_t; 19 | using fs8 = int_fast8_t; 20 | using fs16 = int_fast16_t; 21 | using fs32 = int_fast32_t; 22 | using fs64 = int_fast64_t; 23 | 24 | using ptr = uintptr_t; 25 | using size = size_t; 26 | -------------------------------------------------------------------------------- /hakkun/sail/include/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace sail { 12 | 13 | template 14 | void writeFile(const std::string& filename, std::span data) { 15 | std::ofstream fstr(filename, std::ios::binary); 16 | fstr.unsetf(std::ios::skipws); 17 | fstr.write((const char*)data.data(), data.size() * sizeof(T)); 18 | } 19 | 20 | template 21 | [[noreturn]] void fail(const char* msg, Args... args) { 22 | fprintf(stderr, msg, args...); 23 | exit(ExitCode); 24 | } 25 | 26 | template 27 | std::vector readFile(const char* filename) { 28 | if (!std::filesystem::exists(filename)) 29 | fail<2>("%s does not exist\n", filename); 30 | 31 | std::ifstream fstr(filename, std::ios::binary); 32 | fstr.unsetf(std::ios::skipws); 33 | 34 | std::streampos fileSize; 35 | 36 | fstr.seekg(0, std::ios::end); 37 | fileSize = fstr.tellg(); 38 | fstr.seekg(0, std::ios::beg); 39 | 40 | std::vector data; 41 | data.resize(fileSize); 42 | fstr.read((char*)data.data(), fileSize); 43 | 44 | return data; 45 | } 46 | 47 | inline std::string readFileString(const char* filename) { 48 | if (!std::filesystem::exists(filename)) 49 | fail<2>("%s does not exist\n", filename); 50 | 51 | std::ifstream t(filename); 52 | std::stringstream buffer; 53 | buffer << t.rdbuf(); 54 | return buffer.str(); 55 | } 56 | 57 | inline std::deque split(const std::string& str, const char delim) { 58 | std::stringstream ss(str); 59 | std::string s; 60 | 61 | std::deque out; 62 | 63 | while (std::getline(ss, s, delim)) 64 | if (!s.empty()) 65 | out.push_back(s); 66 | 67 | return out; 68 | } 69 | 70 | inline bool hexStringToBytes(std::vector& out, const std::string& hex) { 71 | size_t length = hex.length(); 72 | 73 | if (length % 2 != 0) 74 | return false; 75 | 76 | for (size_t i = 0; i < length; i += 2) { 77 | std::string byteString = hex.substr(i, 2); 78 | uint8_t byte = static_cast(std::stoul(byteString, nullptr, 16)); 79 | out.push_back(byte); 80 | } 81 | 82 | return true; 83 | } 84 | 85 | inline bool hexStringToBytesWithMatch(std::vector& out, std::vector& outMask, const std::string& hex) { 86 | size_t length = hex.length(); 87 | if (length % 2 != 0) 88 | return false; 89 | 90 | out.clear(); 91 | outMask.clear(); 92 | 93 | for (size_t i = 0; i < length; i += 2) { 94 | uint8_t byte = 0; 95 | uint8_t mask = 0xFF; 96 | 97 | if (hex[i] == '?') { 98 | mask &= 0x0F; 99 | } else if (isxdigit(hex[i])) { 100 | uint8_t value = isdigit(hex[i]) ? (hex[i] - '0') : (tolower(hex[i]) - 'a' + 10); 101 | byte |= (value << 4); 102 | } else { 103 | return false; 104 | } 105 | 106 | if (hex[i + 1] == '?') { 107 | mask &= 0xF0; 108 | } else if (isxdigit(hex[i + 1])) { 109 | uint8_t value = isdigit(hex[i + 1]) ? (hex[i + 1] - '0') : (tolower(hex[i + 1]) - 'a' + 10); 110 | byte |= value; 111 | } else { 112 | return false; 113 | } 114 | 115 | out.push_back(byte); 116 | outMask.push_back(mask); 117 | } 118 | 119 | return true; 120 | } 121 | 122 | } // namespace sail 123 | -------------------------------------------------------------------------------- /hakkun/sail/src/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "hash.h" 3 | #include "parser.h" 4 | #include "util.h" 5 | #include 6 | 7 | namespace sail { 8 | 9 | static std::vector> sModuleList; 10 | static std::vector>>> sVersionList; 11 | 12 | void loadConfig(const std::string& moduleListPath, const std::string& versionListPath) { 13 | std::string data = sail::readFileString(moduleListPath.c_str()); 14 | 15 | sModuleList = sail::parseModuleList(data, moduleListPath); 16 | data = sail::readFileString(versionListPath.c_str()); 17 | 18 | sVersionList = sail::parseVersionList(data, versionListPath, sModuleList); 19 | } 20 | 21 | int getModuleIndex(const std::string& module) { 22 | for (const auto& pair : sModuleList) 23 | if (pair.first == module) 24 | return pair.second; 25 | return -1; 26 | } 27 | 28 | int getVersionIndex(int moduleIdx, const std::string& version) { 29 | for (int i = 0; i < sVersionList[moduleIdx].size(); i++) { 30 | if (sVersionList[moduleIdx][i].first == version) 31 | return i; 32 | } 33 | 34 | return -1; 35 | } 36 | 37 | const std::vector>>>& getVersionList() { return sVersionList; } 38 | 39 | static std::vector sSymbolHashes; 40 | 41 | void clearDestinationSymbols() { sSymbolHashes.clear(); } 42 | bool addDestinationSymbolAndCheckDuplicate(const std::string& symbol) { 43 | u32 hash = hashMurmur(symbol.c_str()); 44 | auto it = std::find(sSymbolHashes.begin(), sSymbolHashes.end(), hash); 45 | if (it != sSymbolHashes.end()) 46 | return false; 47 | 48 | sSymbolHashes.push_back(hash); 49 | return true; 50 | } 51 | 52 | bool& is32Bit() { 53 | static bool sIs32Bit = false; 54 | return sIs32Bit; 55 | } 56 | 57 | } // namespace sail 58 | -------------------------------------------------------------------------------- /hakkun/sail/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "fakelib.h" 3 | #include "parser.h" 4 | #include "util.h" 5 | #include 6 | 7 | int main(int argc, char* argv[]) { 8 | if (argc < 8) 9 | sail::fail<1>("%s \n", argv[0]); 10 | 11 | const char* moduleListPath = argv[1]; 12 | const char* versionListPath = argv[2]; 13 | const char* outFolder = argv[3]; 14 | const char* clangBinary = argv[4]; 15 | sail::is32Bit() = std::string(argv[5]) == "1"; 16 | if (*argv[6] != 'D') { 17 | printf("Wrong sail version! Have you run setup_sail.py after updating?\n"); 18 | return 1; 19 | } 20 | std::vector symbolTraversePaths; 21 | for (int i = 7; i < argc; i++) { 22 | symbolTraversePaths.push_back(argv[i]); 23 | } 24 | 25 | sail::loadConfig(moduleListPath, versionListPath); 26 | 27 | printf("-- Parsing symbols\n"); 28 | 29 | std::vector symbols; 30 | 31 | for (auto i : symbolTraversePaths) { 32 | for (const auto& entry : std::filesystem::recursive_directory_iterator(i)) { 33 | if (!entry.path().string().ends_with(".sym")) 34 | continue; 35 | 36 | const char* path = entry.path().c_str(); 37 | 38 | std::string data = sail::readFileString(path); 39 | auto syms = sail::parseSymbolFile(data, path); 40 | 41 | for (const auto& sym : syms) 42 | symbols.push_back(sym); 43 | } 44 | } 45 | 46 | printf("-- Generating symbols\n"); 47 | sail::generateFakeLib(symbols, outFolder, clangBinary); 48 | sail::generateSymbolDb(symbols, outFolder, clangBinary); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /hakkun/src/hk/diag/diag.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/diag/diag.h" 2 | #include "hk/Result.h" 3 | #include "hk/ro/RoUtil.h" 4 | #include "hk/svc/api.h" 5 | #include "hk/svc/types.h" 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef HK_ADDON_LogManager 11 | #include "hk/nn/diag.h" 12 | #endif 13 | 14 | namespace hk::diag { 15 | 16 | Result setCurrentThreadName(const char* name) { 17 | auto* tls = svc::getTLS(); 18 | auto* thread = tls->nnsdk_thread_ptr; 19 | HK_UNLESS(thread != nullptr, ResultNotAnNnsdkThread()); 20 | thread->threadNamePtr = thread->threadName; 21 | std::strncpy(thread->threadName, name, sizeof(thread->threadName)); 22 | return ResultSuccess(); 23 | } 24 | 25 | static void* setAbortMsg(const ro::RoModule* module, const char* msg, int idx) { 26 | const size arbitraryOffset = 0x69 * idx; 27 | 28 | Elf_Sym sym; 29 | sym.st_shndx = STT_FUNC; 30 | sym.st_name = 0x100 * idx; 31 | sym.st_size = 42; 32 | sym.st_info = 2; 33 | sym.st_other = 0; 34 | sym.st_value = arbitraryOffset; 35 | 36 | ptr strTab = ptr(module->module->m_pStrTab); 37 | strTab -= module->range().start(); 38 | ptr dynSym = ptr(module->module->m_pDynSym); 39 | dynSym -= module->range().start(); 40 | 41 | module->writeRo(dynSym + sizeof(Elf_Sym) * idx, &sym, sizeof(sym)); 42 | module->writeRo(strTab + 0x100 * idx, msg, __builtin_strlen(msg) + 1); 43 | 44 | return (void*)(module->range().start() + arbitraryOffset); 45 | } 46 | 47 | constexpr char sAbortFormat[] = R"( 48 | ~~~ HakkunAbort ~~~ 49 | File: %s:%d 50 | )"; 51 | 52 | hk_noreturn void abortImpl(svc::BreakReason reason, Result result, const char* file, int line, const char* msgFmt, ...) { 53 | va_list arg; 54 | va_start(arg, msgFmt); 55 | abortImpl(reason, result, file, line, msgFmt, arg); 56 | va_end(arg); 57 | } 58 | 59 | hk_noreturn void abortImpl(svc::BreakReason reason, Result result, const char* file, int line, const char* msgFmt, std::va_list arg) { 60 | #if !defined(HK_RELEASE) or defined(HK_RELEASE_DEBINFO) 61 | char userMsgBuf[0x80]; 62 | vsnprintf(userMsgBuf, sizeof(userMsgBuf), msgFmt, arg); 63 | 64 | char headerMsgBuf[0x80]; 65 | snprintf(headerMsgBuf, sizeof(headerMsgBuf), sAbortFormat, file, line); 66 | 67 | debugLog(headerMsgBuf); 68 | debugLog(userMsgBuf); 69 | 70 | auto* module = ro::getSelfModule(); 71 | if (module) { 72 | void* headerSym = setAbortMsg(module, headerMsgBuf, 0); 73 | void* userSym = setAbortMsg(module, userMsgBuf, 1); 74 | svc::hkBreakWithMessage(reason, &result, sizeof(result), headerSym, userSym); 75 | } else 76 | #endif 77 | svc::Break(reason, &result, sizeof(result)); 78 | } 79 | 80 | extern "C" void __attribute__((weak)) hkLogSink(const char* msg, size len) { } 81 | 82 | #if !defined(HK_RELEASE) or defined(HK_RELEASE_DEBINFO) 83 | void debugLog(const char* fmt, ...) { 84 | constexpr size bufSizeAddend = 85 | #ifdef HK_ADDON_LogManager 86 | 2 87 | #else 88 | 1 89 | #endif 90 | ; 91 | 92 | std::va_list args; 93 | va_start(args, fmt); 94 | size len = vsnprintf(nullptr, 0, fmt, args); 95 | char buf[len + bufSizeAddend]; 96 | vsnprintf(buf, len + 1, fmt, args); 97 | va_end(args); 98 | 99 | svc::OutputDebugString(buf, len + 1); 100 | hkLogSink(buf, len + 1); 101 | #ifdef HK_ADDON_LogManager 102 | { 103 | nn::diag::LogMetaData metaData; 104 | metaData.file = __FILE__; 105 | metaData.line = __LINE__; 106 | metaData.function = __PRETTY_FUNCTION__; 107 | 108 | buf[len] = '\n'; 109 | buf[len + 1] = '\0'; 110 | 111 | // In case the symbol is not yet applied 112 | volatile auto put = nn::diag::detail::PutImpl; 113 | if (put != nullptr) 114 | put(metaData, buf, len + 1); 115 | } 116 | #endif 117 | } 118 | #endif 119 | 120 | } // namespace hk::diag 121 | -------------------------------------------------------------------------------- /hakkun/src/hk/hook/MapUtil.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/hook/MapUtil.h" 2 | #include "hk/diag/diag.h" 3 | #include "hk/svc/api.h" 4 | #include "hk/types.h" 5 | #include "hk/util/Random.h" 6 | 7 | namespace hk::hook { 8 | 9 | static bool regionsOverlap(ptr a, size aSize, ptr b, size bSize) { 10 | return a < b + bSize && b < a + aSize; 11 | } 12 | 13 | ptr findAslr(size searchSize) { 14 | searchSize = alignUpPage(searchSize); 15 | 16 | u64 aslrStart = 0; 17 | HK_ABORT_UNLESS_R(svc::GetInfo(&aslrStart, svc::InfoType_AslrRegionAddress, svc::PseudoHandle::CurrentProcess, 0)); 18 | u64 aslrSize = 0; 19 | HK_ABORT_UNLESS_R(svc::GetInfo(&aslrSize, svc::InfoType_AslrRegionSize, svc::PseudoHandle::CurrentProcess, 0)); 20 | 21 | u64 aliasStart = 0; 22 | HK_ABORT_UNLESS_R(svc::GetInfo(&aliasStart, svc::InfoType_AliasRegionAddress, svc::PseudoHandle::CurrentProcess, 0)); 23 | u64 aliasSize = 0; 24 | HK_ABORT_UNLESS_R(svc::GetInfo(&aliasSize, svc::InfoType_AliasRegionSize, svc::PseudoHandle::CurrentProcess, 0)); 25 | 26 | u64 heapStart = 0; 27 | HK_ABORT_UNLESS_R(svc::GetInfo(&heapStart, svc::InfoType_HeapRegionAddress, svc::PseudoHandle::CurrentProcess, 0)); 28 | u64 heapSize = 0; 29 | HK_ABORT_UNLESS_R(svc::GetInfo(&heapSize, svc::InfoType_HeapRegionSize, svc::PseudoHandle::CurrentProcess, 0)); 30 | 31 | size maxPage = (aslrSize - searchSize) >> 12; 32 | 33 | while (true) { 34 | size randomPage = util::getRandomU64() % maxPage; 35 | 36 | ptr attempt = aslrStart + randomPage * cPageSize; 37 | svc::MemoryInfo memInfo; 38 | u32 pageInfo; 39 | 40 | HK_ABORT_UNLESS_R(svc::QueryMemory(&memInfo, &pageInfo, attempt)); 41 | 42 | if (memInfo.state == svc::MemoryState_Free && attempt + searchSize <= memInfo.base_address + memInfo.size 43 | && !regionsOverlap(attempt, searchSize, aliasStart, aliasSize) 44 | && !regionsOverlap(attempt, searchSize, heapStart, heapSize)) 45 | return attempt; 46 | } 47 | } 48 | 49 | Result mapRoToRw(ptr addr, size mapSize, ptr* outRw) { 50 | ptr srcAligned = alignDownPage(addr); 51 | ptrdiff ptrToAlignedDiff = addr - srcAligned; 52 | 53 | size uppedSize = alignUpPage(mapSize + ptrToAlignedDiff); 54 | ptr dest = findAslr(uppedSize); 55 | 56 | Handle curProcess; 57 | HK_ABORT_UNLESS_R(svc::getProcessHandleMesosphere(&curProcess)); 58 | 59 | // HK_TRY(svc::MapProcessMemory(dest, curProcess, srcAligned, uppedSize)); 60 | HK_ABORT_UNLESS_R(svc::MapProcessMemory(dest, curProcess, srcAligned, uppedSize)); 61 | 62 | *outRw = dest + ptrToAlignedDiff; 63 | 64 | return ResultSuccess(); 65 | } 66 | 67 | } // namespace hk::hook 68 | -------------------------------------------------------------------------------- /hakkun/src/hk/hook/Trampoline.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/hook/Trampoline.h" 2 | #include "hk/hook/MapUtil.h" 3 | 4 | namespace hk::hook { 5 | 6 | namespace detail { 7 | 8 | static ptr sRwAddr = 0; 9 | section(.text) static TrampolineBackup sTrampolinePoolData[HK_HOOK_TRAMPOLINE_POOL_SIZE]; 10 | 11 | static void* mapRw() { 12 | HK_ABORT_UNLESS_R(mapRoToRw(ptr(sTrampolinePoolData), sizeof(sTrampolinePoolData), &sRwAddr)); 13 | return cast(sRwAddr); 14 | } 15 | 16 | util::PoolAllocator sTrampolinePool { mapRw() }; 17 | 18 | ptr TrampolineBackup::getRx() const { 19 | ptr rw = ptr(this); 20 | 21 | return ptr(sTrampolinePoolData) + (rw - sRwAddr); 22 | } 23 | 24 | } // namespace detail 25 | 26 | } // namespace hk::hook 27 | -------------------------------------------------------------------------------- /hakkun/src/hk/init/module.S: -------------------------------------------------------------------------------- 1 | .global __module_start__ 2 | .global __mod0 3 | .hidden hk_rtld_module 4 | 5 | .section ".text.modulestart","a" 6 | __module_start__: 7 | b __module_entry__ 8 | .word __mod0 - __module_start__ 9 | 10 | .section ".rodata.mod0","a" 11 | .align 2 12 | __mod0: 13 | .ascii "MOD0" 14 | .word _DYNAMIC - __mod0 15 | .word __bss_start__ - __mod0 16 | .word __bss_end__ - __mod0 17 | .word __eh_frame_hdr_start - __mod0 18 | .word __eh_frame_hdr_end - __mod0 19 | .word hkRtldModule - __mod0 20 | -------------------------------------------------------------------------------- /hakkun/src/hk/init/module.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/init/module.h" 2 | #include "hk/diag/diag.h" 3 | #include "hk/ro/RoUtil.h" 4 | #include "hk/sail/init.h" 5 | #include "hk/svc/api.h" 6 | #include "rtld/ModuleHeader.h" 7 | #include "rtld/RoModule.h" 8 | 9 | #ifdef HK_ADDON_HeapSourceBss 10 | #include "hk/mem/BssHeap.h" 11 | #endif 12 | 13 | namespace hk::init { 14 | 15 | extern "C" { 16 | section(.bss.rtldmodule) nn::ro::detail::RoModule hkRtldModule; 17 | extern rtld::ModuleHeader __mod0; 18 | section(.rodata.modulename) const ModuleName hkModuleName; 19 | } 20 | 21 | static void callInitializers() { 22 | InitFuncPtr* current = __preinit_array_start__; 23 | while (current != __preinit_array_end__) 24 | (*current++)(); 25 | current = __init_array_start__; 26 | while (current != __init_array_end__) 27 | (*current++)(); 28 | } 29 | 30 | extern "C" void hkMain(); 31 | 32 | extern "C" void __module_entry__(void* x0, void* x1) { 33 | diag::debugLog("Hakkun __module_entry__"); 34 | 35 | ro::initModuleList(); 36 | #ifdef HK_DISABLE_SAIL 37 | diag::debugLog("hk::sail: disabled"); 38 | #else 39 | sail::loadSymbols(); 40 | #endif 41 | #ifdef HK_ADDON_HeapSourceBss 42 | mem::initializeMainHeap(); 43 | #endif 44 | callInitializers(); 45 | 46 | hkMain(); 47 | } 48 | 49 | } // namespace hk::init 50 | -------------------------------------------------------------------------------- /hakkun/src/hk/os/Libcxx.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/diag/diag.h" 2 | #include "hk/os/Mutex.h" 3 | 4 | struct Guard { 5 | bool initialized; 6 | hk::os::Mutex mutex; 7 | }; 8 | 9 | extern "C" { 10 | 11 | bool hk_guard_acquire(Guard* guard) { 12 | if (guard->initialized) 13 | return false; 14 | guard->mutex.lock(); 15 | return true; 16 | } 17 | 18 | void hk_guard_release(Guard* guard) { 19 | guard->initialized = true; 20 | guard->mutex.unlock(); 21 | } 22 | 23 | void hk_atexit(void (*)()) { 24 | // ... 25 | } 26 | 27 | /*void abort_message(const char* msg) { 28 | HK_ABORT("abort_message: %s", msg); 29 | }*/ 30 | 31 | void abort() { 32 | HK_ABORT("abort() called", 0); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hakkun/src/hk/ro/RoModule.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/ro/RoModule.h" 2 | #include "hk/diag/diag.h" 3 | #include "hk/hook/MapUtil.h" 4 | #include "hk/ro/results.h" 5 | #include "hk/svc/api.h" 6 | #include "hk/svc/types.h" 7 | 8 | namespace hk::ro { 9 | 10 | Result RoModule::findRanges() { 11 | svc::MemoryInfo curRangeInfo; 12 | u32 pageInfo; 13 | HK_TRY(svc::QueryMemory(&curRangeInfo, &pageInfo, module->m_Base)); 14 | 15 | HK_ASSERT(curRangeInfo.base_address == module->m_Base); 16 | 17 | HK_UNLESS(curRangeInfo.permission == svc::MemoryPermission_ReadExecute, ResultUnusualSectionLayout()); 18 | #ifdef __aarch64__ 19 | text = { curRangeInfo.base_address, curRangeInfo.size }; 20 | #else 21 | text = { uintptr_t(curRangeInfo.base_address), size(curRangeInfo.size) }; 22 | #endif 23 | 24 | auto prev = module->m_Base; 25 | HK_TRY(svc::QueryMemory(&curRangeInfo, &pageInfo, curRangeInfo.base_address + curRangeInfo.size)); 26 | 27 | HK_UNLESS(curRangeInfo.permission == svc::MemoryPermission_Read, ResultUnusualSectionLayout()); 28 | 29 | #ifdef __aarch64__ 30 | rodata = { curRangeInfo.base_address, curRangeInfo.size }; 31 | #else 32 | rodata = { uintptr_t(curRangeInfo.base_address), size(curRangeInfo.size) }; 33 | #endif 34 | 35 | while (curRangeInfo.permission == svc::MemoryPermission_Read) 36 | HK_TRY(svc::QueryMemory(&curRangeInfo, &pageInfo, curRangeInfo.base_address + curRangeInfo.size)); 37 | 38 | HK_UNLESS(curRangeInfo.permission == svc::MemoryPermission_ReadWrite, ResultUnusualSectionLayout()); 39 | 40 | #ifdef __aarch64__ 41 | data = { curRangeInfo.base_address, curRangeInfo.size }; 42 | #else 43 | data = { uintptr_t(curRangeInfo.base_address), size(curRangeInfo.size) }; 44 | #endif 45 | 46 | return ResultSuccess(); 47 | } 48 | 49 | Result RoModule::mapRw() { 50 | HK_UNLESS(textRw.start() == 0, ResultAlreadyMapped()); 51 | HK_UNLESS(rodataRw.start() == 0, ResultAlreadyMapped()); 52 | 53 | ptr rw = 0; 54 | HK_TRY(hook::mapRoToRw(text.start(), text.size(), &rw)); 55 | textRw = { rw, text.size() }; 56 | HK_TRY(hook::mapRoToRw(rodata.start(), rodata.size(), &rw)); 57 | rodataRw = { rw, rodata.size() }; 58 | 59 | return ResultSuccess(); 60 | } 61 | 62 | static RoWriteCallback sRoWriteCallback = nullptr; 63 | 64 | Result RoModule::writeRo(ptr offset, const void* source, size writeSize) const { 65 | HK_UNLESS(textRw.start() != 0, ResultNotMapped()); 66 | HK_UNLESS(rodataRw.start() != 0, ResultNotMapped()); 67 | 68 | ptr roPtr = text.start() + offset; 69 | 70 | HK_UNLESS(roPtr >= text.start(), ResultOutOfRange()); 71 | HK_UNLESS(roPtr + writeSize <= rodata.end(), ResultOutOfRange()); 72 | 73 | bool isText = !(roPtr >= rodata.start()); 74 | 75 | ptr rwPtr = isText ? ptr(textRw.start() + offset) : ptr(rodataRw.start() + offset - text.size()); 76 | 77 | if (sRoWriteCallback) 78 | sRoWriteCallback(this, offset, source, writeSize); 79 | __builtin_memcpy(cast(rwPtr), source, writeSize); 80 | if (isText) 81 | svc::clearCache(roPtr, writeSize); 82 | 83 | return ResultSuccess(); 84 | } 85 | 86 | void setRoWriteCallback(RoWriteCallback callback) { 87 | sRoWriteCallback = callback; 88 | } 89 | 90 | } // namespace hk::ro 91 | -------------------------------------------------------------------------------- /hakkun/src/hk/svc/api.aarch64.S: -------------------------------------------------------------------------------- 1 | .macro SVC_BEGIN name 2 | .section .text.\name, "ax", %progbits 3 | .global \name 4 | .type \name, %function 5 | .hidden \name 6 | .align 2 7 | .cfi_startproc 8 | \name: 9 | .endm 10 | 11 | .macro SVC_END 12 | .cfi_endproc 13 | .endm 14 | 15 | SVC_BEGIN _ZN2hk3svc11QueryMemoryEPNS0_10MemoryInfoEPjm 16 | str x1, [sp, #-16]! 17 | svc 0x6 18 | ldr x2, [sp], #16 19 | str w1, [x2] 20 | ret 21 | SVC_END 22 | 23 | SVC_BEGIN _ZN2hk3svc13ArbitrateLockEjmj 24 | svc 0x1A 25 | ret 26 | SVC_END 27 | 28 | SVC_BEGIN _ZN2hk3svc15ArbitrateUnlockEm 29 | svc 0x1B 30 | ret 31 | SVC_END 32 | 33 | SVC_BEGIN _ZN2hk3svc15SendSyncRequestEj 34 | svc 0x21 35 | ret 36 | SVC_END 37 | 38 | SVC_BEGIN _ZN2hk3svc5BreakENS0_11BreakReasonEPvm 39 | svc 0x26 40 | ret 41 | SVC_END 42 | 43 | SVC_BEGIN _ZN2hk3svc18hkBreakWithMessageENS0_11BreakReasonEPvmS2_S2_ 44 | svc 0x26 45 | ret 46 | SVC_END 47 | 48 | SVC_BEGIN _ZN2hk3svc17OutputDebugStringEPKcm 49 | svc 0x27 50 | ret 51 | SVC_END 52 | 53 | SVC_BEGIN _ZN2hk3svc7GetInfoEPmNS0_8InfoTypeEjm 54 | str x0, [sp, #-16]! 55 | svc 0x29 56 | ldr x2, [sp], #16 57 | str x1, [x2] 58 | ret 59 | SVC_END 60 | 61 | SVC_BEGIN _ZN2hk3svc14GetProcessListEPiPmi 62 | str x0, [sp, #-16]! 63 | svc 0x65 64 | ldr x2, [sp], #16 65 | str w1, [x2] 66 | ret 67 | SVC_END 68 | 69 | SVC_BEGIN _ZN2hk3svc16MapProcessMemoryEmjmm 70 | svc 0x74 71 | ret 72 | SVC_END 73 | -------------------------------------------------------------------------------- /hakkun/src/hk/svc/api.armv7a.S: -------------------------------------------------------------------------------- 1 | .macro SVC_BEGIN name 2 | .section .text.\name, "ax", %progbits 3 | .global \name 4 | .type \name, %function 5 | .hidden \name 6 | .align 2 7 | .cfi_startproc 8 | \name: 9 | .endm 10 | 11 | .macro SVC_END 12 | .cfi_endproc 13 | .endm 14 | 15 | SVC_BEGIN _ZN2hk3svc11QueryMemoryEPNS0_10MemoryInfoEPjj 16 | str r1, [sp, #-4]! 17 | svc #0x6 18 | ldr r2, [sp] 19 | str r1, [r2] 20 | add sp, sp, #4 21 | bx lr 22 | SVC_END 23 | 24 | SVC_BEGIN _ZN2hk3svc13ArbitrateLockEjjj 25 | svc 0x1A 26 | bx lr 27 | SVC_END 28 | 29 | SVC_BEGIN _ZN2hk3svc15ArbitrateUnlockEj 30 | svc 0x1B 31 | bx lr 32 | SVC_END 33 | 34 | SVC_BEGIN _ZN2hk3svc5BreakENS0_11BreakReasonEPvj 35 | svc 0x26 36 | bx lr 37 | SVC_END 38 | 39 | SVC_BEGIN _ZN2hk3svc18hkBreakWithMessageENS0_11BreakReasonEPvjS2_S2_ 40 | ldr r4, [sp, #0x0] 41 | mov r5, #0x42 // Prevent showing message twice 42 | svc 0x26 43 | SVC_END 44 | 45 | SVC_BEGIN _ZN2hk3svc17OutputDebugStringEPKcj 46 | svc 0x27 47 | bx lr 48 | SVC_END 49 | 50 | SVC_BEGIN _ZN2hk3svc7GetInfoEPyNS0_8InfoTypeEjy 51 | str r0, [sp, #-4]! 52 | ldr r0, [sp, #4] 53 | ldr r3, [sp, #8] 54 | svc #0x29 55 | ldr r3, [sp] 56 | str r1, [r3] 57 | str r2, [r3, #4] 58 | add sp, sp, #4 59 | bx lr 60 | SVC_END 61 | 62 | // the abi for the following svcs is completely Wrong 63 | SVC_BEGIN _ZN2hk3svc26InvalidateProcessDataCacheEjjj 64 | stmdb sp!, {r3, r4, lr} 65 | mov r3, #0x0 66 | mov r4, #0x0 67 | svc 0x5d 68 | ldmia sp!, {r3, r4, pc} 69 | SVC_END 70 | 71 | SVC_BEGIN _ZN2hk3svc21FlushProcessDataCacheEjjj 72 | stmdb sp!, {r3, r4, lr} 73 | mov r3, #0x0 74 | mov r4, #0x0 75 | svc 0x5f 76 | ldmia sp!, {r3, r4, pc} 77 | SVC_END 78 | 79 | SVC_BEGIN _ZN2hk3svc16MapProcessMemoryEjjyj 80 | stmdb sp!, {r3, r4, lr} 81 | mov r3, #0x0 82 | ldr r4, [sp, #12] 83 | svc 0x74 84 | ldmia sp!, {r3, r4, pc} 85 | SVC_END 86 | -------------------------------------------------------------------------------- /hakkun/src/hk/util/Context.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/util/Context.h" 2 | 3 | namespace hk::util { 4 | 5 | struct StackFrame { 6 | StackFrame* fp; 7 | ptr lr; 8 | }; 9 | 10 | ptr getReturnAddress(int n) { 11 | StackFrame* fp; 12 | asm volatile("mov %0, fp" : "=r"(fp)); 13 | 14 | for (int i = 0; i < n; i++) { 15 | if (fp == nullptr || ptr(fp) % sizeof(ptr) != 0) 16 | return 0; 17 | 18 | fp = fp->fp; 19 | } 20 | 21 | return fp->lr; 22 | } 23 | 24 | } // namespace hk::util 25 | -------------------------------------------------------------------------------- /hakkun/src/rtld/DummyRtld.cpp: -------------------------------------------------------------------------------- 1 | #include "hk/diag/diag.h" 2 | #include "hk/ro/RoModule.h" 3 | #include "hk/svc/api.h" 4 | #include "hk/svc/types.h" 5 | 6 | section(.bss.rtldmodule) nn::ro::detail::RoModule hkRtldModule; 7 | 8 | #undef HK_ASSERT 9 | #define HK_ASSERT(_EXPR) \ 10 | { \ 11 | if ((_EXPR) == false) { \ 12 | *(int*)(0x1000 + __LINE__) = 0; \ 13 | __builtin_trap(); \ 14 | } \ 15 | } 16 | 17 | extern "C" void __module_entry__(void* x0, void* x1) { 18 | ptr addr; 19 | __asm("adr %[result], ." : [result] "=r"(addr)); 20 | hk::svc::MemoryInfo info; 21 | u32 page; 22 | 23 | // us 24 | HK_ASSERT(hk::svc::QueryMemory(&info, &page, addr).succeeded()); 25 | HK_ASSERT(info.permission & hk::svc::MemoryPermission_ReadExecute); 26 | addr = info.base_address + info.size; 27 | 28 | HK_ASSERT(hk::svc::QueryMemory(&info, &page, addr).succeeded()); 29 | HK_ASSERT(info.permission & hk::svc::MemoryPermission_Read); 30 | addr = info.base_address + info.size; 31 | 32 | HK_ASSERT(hk::svc::QueryMemory(&info, &page, addr).succeeded()); 33 | HK_ASSERT(info.permission & hk::svc::MemoryPermission_ReadWrite); 34 | addr = info.base_address + info.size; 35 | 36 | // main 37 | HK_ASSERT(hk::svc::QueryMemory(&info, &page, addr).succeeded()); 38 | HK_ASSERT(info.permission & hk::svc::MemoryPermission_ReadExecute); 39 | 40 | using Entry = void (*)(void*, void*); 41 | Entry entry = reinterpret_cast(info.base_address); 42 | entry(x0, x1); 43 | } 44 | -------------------------------------------------------------------------------- /tools/bake_hashes.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from elftools.elf.elffile import ELFFile 3 | import mmh3 4 | 5 | def bake_hashes(filename): 6 | with open(filename, 'rb') as f: 7 | elf_data = bytearray(f.read()) 8 | f.seek(0) 9 | elffile = ELFFile(f) 10 | 11 | dynsym = elffile.get_section_by_name('.dynsym') 12 | dynstr = elffile.get_section_by_name('.dynstr') 13 | 14 | if not dynsym or not dynstr: 15 | raise Exception("Sections not found") 16 | 17 | dynstr_offset = dynstr.header.sh_offset 18 | dynstr_data = bytearray(dynstr.data()) 19 | 20 | for symbol in dynsym.iter_symbols(): 21 | if symbol.entry.st_value != 0: 22 | continue 23 | 24 | name_offset = symbol.entry.st_name 25 | if name_offset == 0: 26 | continue 27 | 28 | name = symbol.name 29 | if not name: 30 | continue 31 | 32 | hash_value = mmh3.hash(name.encode(), seed=0) 33 | 34 | hash_bytes = struct.pack('") 54 | sys.exit(1) 55 | 56 | bake_hashes(sys.argv[1]) -------------------------------------------------------------------------------- /tools/deploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import shutil 4 | import ftplib 5 | 6 | build_dir = sys.argv[1] 7 | project_name = sys.argv[2] 8 | title_id = int(sys.argv[3].removeprefix('0x'), 16) 9 | module_binary = sys.argv[4] 10 | is_static = sys.argv[5] == 'TRUE' 11 | layeredfs_dir = f"atmosphere/contents/{title_id:016X}" 12 | exefs_dir = f"{layeredfs_dir}/exefs" 13 | sd_exefs_dir = f"{build_dir}/sd/{exefs_dir}" 14 | 15 | def deploy_sd(): 16 | print("-- Deploying to SD folder") 17 | try: 18 | shutil.rmtree(sd_exefs_dir) 19 | except FileNotFoundError: 20 | pass 21 | os.makedirs(sd_exefs_dir, exist_ok=True) 22 | shutil.copyfile(f"{build_dir}/main.npdm", f"{sd_exefs_dir}/main.npdm") 23 | shutil.copyfile(f"{build_dir}/{project_name}.nso", f"{sd_exefs_dir}/{module_binary}") 24 | if is_static: 25 | shutil.copyfile(f"{build_dir}/rtld.nso", f"{sd_exefs_dir}/rtld") 26 | 27 | def deploy_ftp(): 28 | ftp_ip = os.environ['HAKKUN_FTP_IP'] 29 | ftp_port = int(os.environ['HAKKUN_FTP_PORT']) if os.environ.get('HAKKUN_FTP_PORT') is not None else 5000 30 | ftp_user = os.environ.get('HAKKUN_FTP_USER') 31 | ftp_password = os.environ.get('HAKKUN_FTP_PASS') 32 | if (ftp_password is None): 33 | ftp_password = os.environ.get('HAKKUN_FTP_PASSWORD') 34 | print(f'-- Deploying to ftp://{ftp_ip}:{ftp_port}') 35 | 36 | ftp = ftplib.FTP() 37 | try: 38 | ftp.connect(ftp_ip, ftp_port) 39 | except OSError: 40 | print('-- Could not connect to FTP host') 41 | sys.exit(1) 42 | 43 | if (ftp_user is not None): 44 | ftp.login(ftp_user, ftp_password) 45 | 46 | def upload(file, path): 47 | print(f'-- Uploading {file} to ftp://{ftp_ip}:{ftp_port}/{path}') 48 | with open(file, 'rb') as io: 49 | ftp.storbinary(f'STOR {path}', io) 50 | 51 | upload(f"{build_dir}/main.npdm", f"{exefs_dir}/main.npdm") 52 | upload(f"{build_dir}/{project_name}.nso", f"{exefs_dir}/{module_binary}") 53 | if is_static: 54 | upload(f"{build_dir}/rtld.nso", f"{exefs_dir}/rtld") 55 | 56 | 57 | 58 | deploy_sd() 59 | if (os.environ.get('HAKKUN_FTP_IP') is not None): 60 | deploy_ftp() 61 | -------------------------------------------------------------------------------- /tools/elf2nso.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import struct 5 | import hashlib 6 | import lz4.block 7 | 8 | from nso import NsoHeader 9 | from elftools.elf.elffile import ELFFile 10 | from elftools.common.exceptions import ELFError, ELFParseError 11 | 12 | 13 | def fatal(msg): 14 | print(msg) 15 | sys.exit(1) 16 | 17 | 18 | def main(argc, argv): 19 | 20 | # Check arguments 21 | if argc < 3: 22 | fatal('Usage: %s [flags]\n -c compress\n -s check segment checksums' % argv[0]) 23 | 24 | do_compress = '-c' in argv[3:] 25 | do_checksums = '-s' in argv[3:] 26 | 27 | # Load ELF 28 | try: 29 | elf = ELFFile(open(argv[1], 'rb')) 30 | except IOError: 31 | fatal('Opening ELF for reading failed!') 32 | except (ELFError, ELFParseError): 33 | fatal('Reading ELF failed!') 34 | 35 | 36 | # Check architecture 37 | if elf.get_machine_arch() != 'ARM' and elf.get_machine_arch() != 'AArch64': 38 | fatal('Only ARM and AArch64 ELFs are supported! Input arch: %s' % elf.get_machine_arch()) 39 | 40 | 41 | # Check segments 42 | pt_load_segments = [] 43 | 44 | for section in elf.iter_segments(): 45 | if section.header.p_type == 'PT_LOAD': 46 | pt_load_segments.append(section) 47 | 48 | if len(pt_load_segments) != 3: 49 | fatal('Invalid ELF: Expected 3 loadable segments! Got %d' % len(pt_load_segments)) 50 | 51 | text_segment = pt_load_segments[0] 52 | rodata_segment = pt_load_segments[1] 53 | data_segment = pt_load_segments[2] 54 | 55 | 56 | # Open output file 57 | try: 58 | out = open(argv[2], 'wb') 59 | except IOError: 60 | fatal('Opening NSO for writing failed!') 61 | 62 | 63 | # Create NSO 64 | header = NsoHeader() 65 | if do_compress == True: 66 | header.flags |= 0b000111 67 | if do_checksums == True: 68 | header.flags |= 0b111000 69 | 70 | 71 | # Module is alyways a single zeroed byte directly after the header 72 | header.module_offset = header.size 73 | header.module_file_size = 1 74 | out.seek(header.size, 0) 75 | out.write(struct.pack('B', 0)) 76 | 77 | 78 | # text segment 79 | header.text_segment.file_offset = out.tell() 80 | header.text_segment.memory_offset = text_segment.header.p_vaddr 81 | header.text_segment.decompressed_size = text_segment.header.p_filesz 82 | 83 | elf.stream.seek(text_segment.header.p_offset) 84 | text_segment_data = elf.stream.read(text_segment.header.p_filesz) 85 | 86 | if do_checksums == True: 87 | header.text_section_hash = hashlib.sha256(text_segment_data).digest() 88 | 89 | if do_compress == True: 90 | text_segment_data = lz4.block.compress(text_segment_data, store_size=False) 91 | 92 | header.text_compressed_size = len(text_segment_data) 93 | out.write(text_segment_data) 94 | 95 | 96 | # rodata segment 97 | header.rodata_segment.file_offset = out.tell() 98 | header.rodata_segment.memory_offset = rodata_segment.header.p_vaddr 99 | header.rodata_segment.decompressed_size = rodata_segment.header.p_filesz 100 | 101 | elf.stream.seek(rodata_segment.header.p_offset) 102 | rodata_segment_data = elf.stream.read(rodata_segment.header.p_filesz) 103 | 104 | if do_checksums == True: 105 | header.rodata_section_hash = hashlib.sha256(rodata_segment_data).digest() 106 | 107 | if do_compress == True: 108 | rodata_segment_data = lz4.block.compress(rodata_segment_data, store_size=False) 109 | 110 | header.rodata_compressed_size = len(rodata_segment_data) 111 | out.write(rodata_segment_data) 112 | 113 | 114 | # data segment 115 | header.data_segment.file_offset = out.tell() 116 | header.data_segment.memory_offset = data_segment.header.p_vaddr 117 | header.data_segment.decompressed_size = data_segment.header.p_filesz 118 | 119 | elf.stream.seek(data_segment.header.p_offset) 120 | data_segment_data = elf.stream.read(data_segment.header.p_filesz) 121 | 122 | if do_checksums == True: 123 | header.data_section_hash = hashlib.sha256(data_segment_data).digest() 124 | 125 | if do_compress == True: 126 | data_segment_data = lz4.block.compress(data_segment_data, store_size=False) 127 | 128 | header.data_compressed_size = len(data_segment_data) 129 | out.write(data_segment_data) 130 | 131 | 132 | # bss size 133 | header.bss_size = data_segment.header.p_memsz - data_segment.header.p_filesz 134 | 135 | 136 | # Write Header 137 | out.seek(0, 0) 138 | out.write(header.save()) 139 | out.close() 140 | 141 | 142 | sys.exit(0) 143 | 144 | 145 | if __name__ == '__main__': 146 | main(len(sys.argv), sys.argv) 147 | -------------------------------------------------------------------------------- /tools/nso.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | class NsoSegment(struct.Struct): 4 | def __init__(self): 5 | super().__init__('<3I') 6 | 7 | self.file_offset = 0 8 | self.memory_offset = 0 9 | self.decompressed_size = 0 10 | 11 | def load(self, data, pos): 12 | (self.file_offset, 13 | self.memory_offset, 14 | self.decompressed_size) = self.unpack_from(data, pos) 15 | 16 | def save(self): 17 | return struct.pack( 18 | self.format, 19 | self.file_offset, 20 | self.memory_offset, 21 | self.decompressed_size, 22 | ) 23 | 24 | 25 | class NsoHeader(struct.Struct): 26 | def __init__(self): 27 | super().__init__('<4I12xI12xI12xI32s3I28s3Q32s32s32s') 28 | 29 | self.magic = 0x304F534E 30 | self.version = 0 31 | self._8 = 0 32 | self.flags = 0 33 | self.module_offset = 0 34 | self.module_file_size = 0 35 | self.bss_size = 0 36 | self.build_id = b'\0' * 32 37 | self.text_compressed_size = 0 38 | self.rodata_compressed_size = 0 39 | self.data_compressed_size = 0 40 | self._6C = b'\0' * 28 41 | self.api_info_extents = 0 42 | self.dynstr_extents = 0 43 | self.dynsym_extents = 0 44 | self.text_section_hash = b'\0' * 32 45 | self.rodata_section_hash = b'\0' * 32 46 | self.data_section_hash = b'\0' * 32 47 | 48 | self.text_segment = NsoSegment() 49 | self.rodata_segment = NsoSegment() 50 | self.data_segment = NsoSegment() 51 | 52 | def load(self, data, pos=0): 53 | (self.magic, 54 | self.version, 55 | self._8, 56 | self.flags, 57 | self.module_offset, 58 | self.module_file_size, 59 | self.bss_size, 60 | self.build_id, 61 | self.text_compressed_size, 62 | self.rodata_compressed_size, 63 | self.data_compressed_size, 64 | self._6C, 65 | self.api_info_extents, 66 | self.dynstr_extents, 67 | self.dynsym_extents, 68 | self.text_section_hash, 69 | self.rodata_section_hash, 70 | self.data_section_hash) = self.unpack_from(data, pos) 71 | 72 | self.text_segment.load(data, pos + 0x10) 73 | self.rodata_segment.load(data, pos + 0x20) 74 | self.data_segment.load(data, pos + 0x30) 75 | 76 | def save(self): 77 | 78 | outBuffer = bytearray(struct.pack( 79 | self.format, 80 | self.magic, 81 | self.version, 82 | self._8, 83 | self.flags, 84 | self.module_offset, 85 | self.module_file_size, 86 | self.bss_size, 87 | self.build_id, 88 | self.text_compressed_size, 89 | self.rodata_compressed_size, 90 | self.data_compressed_size, 91 | self._6C, 92 | self.api_info_extents, 93 | self.dynstr_extents, 94 | self.dynsym_extents, 95 | self.text_section_hash, 96 | self.rodata_section_hash, 97 | self.data_section_hash, 98 | )) 99 | 100 | outBuffer[0x10:0x10 + self.text_segment.size] = self.text_segment.save() 101 | outBuffer[0x20:0x20 + self.rodata_segment.size] = self.rodata_segment.save() 102 | outBuffer[0x30:0x30 + self.data_segment.size] = self.data_segment.save() 103 | 104 | return outBuffer 105 | -------------------------------------------------------------------------------- /tools/setup_libcxx_prepackaged.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | import tarfile 6 | import sys 7 | 8 | is_aarch32 = len(sys.argv) > 1 and sys.argv[1] == 'aarch32' 9 | 10 | prepackaged_source_tar_name = "stdlib-aarch32-19.1.0_clang_19.1.7.tar.xz" if is_aarch32 else "stdlib-19.1.0_clang_19.1.7.tar.xz" 11 | prepackaged_source = "https://github.com/fruityloops1/LibHakkun/releases/download/stdlib-19.1.0-3/" + prepackaged_source_tar_name 12 | 13 | root_dir = os.getcwd() 14 | 15 | def downloadAndCompilePrepackaged(): 16 | print(f"Downloading pre-packaged stdlib") 17 | 18 | subprocess.run(['curl', '-O', '-L', prepackaged_source]) 19 | 20 | print(f"Extracting") 21 | 22 | src_tar = tarfile.open(prepackaged_source_tar_name) 23 | src_tar.extractall('.') 24 | src_tar.close() 25 | 26 | os.remove(prepackaged_source_tar_name) 27 | 28 | downloadAndCompilePrepackaged() 29 | -------------------------------------------------------------------------------- /tools/setup_sail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import shutil 5 | import multiprocessing 6 | import subprocess 7 | 8 | root_dir = os.getcwd() 9 | build_dir = f'{root_dir}/sys/hakkun/sail/build' 10 | 11 | def compileSail(): 12 | subprocess.run(['cmake', '..']) 13 | subprocess.run(["make", "-j", f"{multiprocessing.cpu_count()}"]) 14 | 15 | try: 16 | shutil.rmtree(build_dir) 17 | except FileNotFoundError: 18 | pass 19 | os.makedirs(build_dir) 20 | os.chdir(build_dir) 21 | compileSail() 22 | --------------------------------------------------------------------------------