├── LICENSE
├── Makefile
├── README.md
├── config.json
├── config.mk
├── exlaunch.sh
├── main.npdm
├── misc
├── link.ld
├── mk
│ ├── as_rtld.mk
│ ├── common.mk
│ ├── deploy.mk
│ ├── module.mk
│ └── npdm.mk
├── npdm-json
│ ├── application.json
│ └── qlaunch.json
├── scripts
│ ├── deploy-ftp.py
│ ├── deploy-ryu.sh
│ ├── deploy-sd.sh
│ ├── make-npdm-json.py
│ └── post-build.sh
└── specs
│ ├── as_rtld.specs
│ └── module.specs
├── patchNpdm.py
└── source
├── common.hpp
├── lib.hpp
├── lib
├── alloc.hpp
├── armv8.hpp
├── armv8
│ ├── instructions.hpp
│ ├── instructions
│ │ ├── base.hpp
│ │ ├── op100x
│ │ │ ├── add_subtract_immediate
│ │ │ │ ├── add_immediate.hpp
│ │ │ │ ├── adds_immediate.hpp
│ │ │ │ ├── base.hpp
│ │ │ │ ├── cmn_immediate.hpp
│ │ │ │ ├── cmp_immediate.hpp
│ │ │ │ ├── sub_immediate.hpp
│ │ │ │ └── subs_immediate.hpp
│ │ │ ├── base.hpp
│ │ │ ├── logical_immediate
│ │ │ │ ├── base.hpp
│ │ │ │ └── orr_immediate.hpp
│ │ │ ├── move_wide_immediate
│ │ │ │ ├── base.hpp
│ │ │ │ ├── movk.hpp
│ │ │ │ ├── movn.hpp
│ │ │ │ └── movz.hpp
│ │ │ └── pc_rel_addressing
│ │ │ │ ├── adr.hpp
│ │ │ │ ├── adrp.hpp
│ │ │ │ └── base.hpp
│ │ ├── op101x
│ │ │ ├── base.hpp
│ │ │ ├── hints
│ │ │ │ ├── base.hpp
│ │ │ │ └── nop.hpp
│ │ │ ├── unconditional_branch_immediate
│ │ │ │ ├── b.hpp
│ │ │ │ ├── base.hpp
│ │ │ │ └── bl.hpp
│ │ │ └── unconditional_branch_register
│ │ │ │ ├── base.hpp
│ │ │ │ ├── br.hpp
│ │ │ │ └── ret.hpp
│ │ ├── opx101
│ │ │ ├── base.hpp
│ │ │ └── logical_shifted_register
│ │ │ │ ├── base.hpp
│ │ │ │ ├── mov_register.hpp
│ │ │ │ └── orr_shifted_register.hpp
│ │ └── opx1x0
│ │ │ ├── base.hpp
│ │ │ ├── load_register_literal
│ │ │ ├── base.hpp
│ │ │ └── ldr_literal.hpp
│ │ │ ├── load_store_register_offset
│ │ │ ├── base.hpp
│ │ │ ├── ldr_register_offset.hpp
│ │ │ └── str_register_offset.hpp
│ │ │ ├── load_store_register_unscaled_immediate
│ │ │ ├── base.hpp
│ │ │ ├── ldur_unscaled_immediate.hpp
│ │ │ └── stur_unscaled_immediate.hpp
│ │ │ └── load_store_register_unsigned_immediate
│ │ │ ├── base.hpp
│ │ │ ├── ldr_register_immediate.hpp
│ │ │ └── str_register_immediate.hpp
│ └── register.hpp
├── diag
│ ├── abort.cpp
│ ├── abort.hpp
│ ├── assert.hpp
│ ├── reboot_to_payload.cpp
│ └── reboot_to_payload.hpp
├── hook
│ ├── base.hpp
│ ├── class.hpp
│ ├── deprecated.hpp
│ ├── inline.hpp
│ ├── nx64
│ │ ├── hook_impl.cpp
│ │ ├── impl.hpp
│ │ ├── inline_asm.s
│ │ ├── inline_impl.cpp
│ │ └── inline_impl.hpp
│ ├── replace.hpp
│ └── trampoline.hpp
├── init
│ ├── crt0.s
│ ├── header.s
│ └── init.cpp
├── libsetting.hpp
├── module.cpp
├── nx
│ ├── abort.h
│ ├── arm
│ │ ├── cache.h
│ │ ├── cache.s
│ │ └── tls.h
│ ├── kernel
│ │ ├── svc.h
│ │ ├── svc.s
│ │ ├── virtmem.c
│ │ └── virtmem.h
│ ├── nx.h
│ ├── result.h
│ ├── smc.c
│ ├── smc.h
│ └── types.h
├── patch
│ ├── code_patcher.hpp
│ ├── patcher_impl.hpp
│ ├── random_access_patcher.hpp
│ └── stream_patcher.hpp
├── reloc
│ ├── elf.cpp
│ ├── elf.hpp
│ ├── rtld.hpp
│ └── rtld
│ │ ├── LICENSE.txt
│ │ ├── ModuleHeader.hpp
│ │ ├── ModuleList.hpp
│ │ ├── ModuleObject.cpp
│ │ ├── ModuleObject.hpp
│ │ ├── dl_trampoline.s
│ │ ├── ld.cpp
│ │ ├── utils.cpp
│ │ └── utils.hpp
├── result.hpp
└── util
│ ├── func_ptrs.hpp
│ ├── math
│ ├── bitset.hpp
│ └── sign_extend.hpp
│ ├── modules.hpp
│ ├── ptr_path.hpp
│ ├── random.cpp
│ ├── random.hpp
│ ├── sys
│ ├── cur_proc_handle.cpp
│ ├── cur_proc_handle.hpp
│ ├── jit.hpp
│ ├── mem_layout.cpp
│ ├── mem_layout.hpp
│ ├── rw_pages.cpp
│ ├── rw_pages.hpp
│ └── soc.hpp
│ └── typed_storage.hpp
├── nn.hpp
├── nn
├── fs.hpp
├── fs
│ ├── fs_directories.hpp
│ ├── fs_files.hpp
│ ├── fs_mount.hpp
│ └── fs_types.hpp
├── nn_common.hpp
├── nnmusl.hpp
├── os.hpp
├── os
│ ├── impl
│ │ ├── os_internal_condition_variable.hpp
│ │ ├── os_internal_condition_variable_impl.os.horizon.hpp
│ │ ├── os_internal_critical_section.hpp
│ │ ├── os_internal_critical_section_impl.os.horizon.hpp
│ │ ├── os_resource_manager.hpp
│ │ ├── os_tick_manager_impl.hpp
│ │ ├── os_tick_manager_impl.os.horizon.hpp
│ │ ├── os_timeout_helper.hpp
│ │ └── os_timeout_helper_impl.os.horizon.hpp
│ ├── os_condition_variable_common.hpp
│ ├── os_event_api.hpp
│ ├── os_event_common.hpp
│ ├── os_event_types.hpp
│ ├── os_mutex_api.hpp
│ ├── os_mutex_common.hpp
│ ├── os_mutex_type.hpp
│ ├── os_thread_api.hpp
│ ├── os_thread_common.hpp
│ ├── os_thread_type.hpp
│ ├── os_tick.hpp
│ └── os_types.hpp
├── time.hpp
├── time
│ └── time_timespan.hpp
└── util
│ └── util_typed_storage.hpp
├── program
├── main.cpp
└── setting.hpp
└── types.h
/Makefile:
--------------------------------------------------------------------------------
1 | # Define paths.
2 | PWD := $(shell pwd)
3 | MISC_PATH := $(PWD)/misc
4 | MK_PATH := $(MISC_PATH)/mk
5 | SCRIPTS_PATH := $(MISC_PATH)/scripts
6 | SPECS_PATH := $(MISC_PATH)/specs
7 | NPDM_JSON_PATH := $(MISC_PATH)/npdm-json
8 |
9 | # To configure exlaunch, edit config.mk.
10 | include $(PWD)/config.mk
11 |
12 | # Define common variables.
13 | NAME := $(shell basename $(PWD))
14 | OUT := $(PWD)/deploy
15 | SD_OUT := atmosphere/contents/$(PROGRAM_ID)/exefs
16 |
17 |
18 | # Set load kind specific variables.
19 | ifeq ($(LOAD_KIND), Module)
20 | LOAD_KIND_ENUM := 2
21 | BINARY_NAME := subsdk9 # TODO: support subsdkX?
22 | SPECS_NAME := module.specs
23 | MK_NAME := module.mk
24 | else ifeq ($(LOAD_KIND), AsRtld)
25 | LOAD_KIND_ENUM := 1
26 | BINARY_NAME := rtld
27 | SPECS_NAME := as_rtld.specs
28 | MK_NAME := as_rtld.mk
29 | else
30 | $(error LOAD_KIND is invalid, please check config.mk)
31 | endif
32 |
33 | .PHONY: clean all
34 |
35 | # Built internal C flags variable.
36 | EXL_CFLAGS := $(C_FLAGS) -DEXL_LOAD_KIND=$(LOAD_KIND) -DEXL_LOAD_KIND_ENUM=$(LOAD_KIND_ENUM) -DEXL_PROGRAM_ID=0x$(PROGRAM_ID)
37 | EXL_CXXFLAGS := $(CLEAN) $(CXX_FLAGS)
38 |
39 | # Export all of our variables to sub-makes and sub-processes.
40 | export
41 |
42 | include $(MK_PATH)/$(MK_NAME)
43 | include $(MK_PATH)/common.mk
44 | include $(MK_PATH)/deploy.mk
45 | include $(MK_PATH)/npdm.mk
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Xenoblade2DynFPS
2 | exlaunch plugin for Xenoblade Chronicles 2 that is taking naive approach at adjusting game speed dynamically.
3 |
4 | It's adjusted for Western release, version 2.1.0
5 |
6 | Main points of using this plugin:
7 | - Gameplay speed is adjusted to framerate on frame-to-frame basis without any smoothing implemented
8 | - Cutscenes are automatically locked to 30 FPS
9 | - UI speed is adjusted to framerate
10 | - Foliage movement is adjusted to framerate
11 | - Gameplay dynamic resolution GPU rendering frametime was replaced by CPU frametime with some tweaks to match better dynamic resolution expectations. Cutscenes still rely on GPU frametime because at 30 FPS it gives better results.
12 |
13 | Known issues that probably will never be fixed:
14 | - Game has big issues with fluid asset streaming, so it can affect stability of speed calculation at higher framerates (> 50 FPS)
15 | - Billboards speed like mist and fire is still tied to framerate, this is probably calculated on shader side
16 |
17 | For **EMULATORS** it is recommended to use this release:
18 | https://github.com/masagrator/Xenoblade2DynFPS/releases/tag/1.0.0-CLEAN
19 | It has some skeleton to support 120 FPS game speed adjustments, but I guess they would need refining from somebody who knows how to achieve 120 FPS in Xenoblade Chronicles 2 without breaking game.
20 |
21 | Plugin also ties internal vSync with nvn interval, so at default game runs at 60 FPS with this plugin. You can use FPSLocker or emulator settings to lock it back to 30 FPS. Don't lower res in lib_nx.ini, it will make your experience worse since whenever CPU will have an issue with maintaining stable framepacing, it will drop res to lower possible until framepacing will stabilize. And in some sceneries it's not possible without big bump in RAM clocks. Lowering res in most cases doesn't help enough to justify doing this.
22 |
23 | If you are compiling this plugin, use `main.npdm` from root of repo instead from `deploy` folder.
24 |
25 | # How to install
26 | Copy folder `0100E95004038000` to `atmosphere/contents/` on sdcard. Run game
27 |
--------------------------------------------------------------------------------
/config.mk:
--------------------------------------------------------------------------------
1 | #----------------------------- User configuration -----------------------------
2 |
3 | # Common settings
4 | #------------------------
5 |
6 | # How you're loading your module. Used to determine how to find the target module. (AsRtld/Module/Kip)
7 | LOAD_KIND := Module
8 |
9 | # Program you're targetting. Used to determine where to deploy your files.
10 | PROGRAM_ID := 0100801011c3e000
11 |
12 | # Optional path to copy the final ELF to, for convenience.
13 | ELF_EXTRACT :=
14 |
15 | # Python command to use. Must be Python 3.4+.
16 | PYTHON := python3
17 |
18 | # JSON to use to make .npdm
19 | NPDM_JSON := qlaunch.json
20 |
21 | # Additional C/C++ flags to use.
22 | C_FLAGS :=
23 | CXX_FLAGS :=
24 |
25 | # AsRtld settings
26 | #------------------------
27 |
28 | # Path to the SD card. Used to mount and deploy files on SD, likely with hekate UMS.
29 | MOUNT_PATH := /mnt/k
30 |
31 | # Module settings
32 | #------------------------
33 |
34 | # Settings for deploying over FTP. Used by the deploy-ftp.py script.
35 | FTP_IP := 192.168.0.235
36 | FTP_PORT := 5000
37 | FTP_USERNAME := anonymous
38 | FTP_PASSWORD :=
39 |
40 | # Settings for deploying to Ryu. Used by the deploy-ryu.sh script.
41 | RYU_PATH := /mnt/c/Users/shado/AppData/Roaming/Ryujinx
42 |
--------------------------------------------------------------------------------
/exlaunch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # User configuration has moved to misc/mk/config.mk.
4 |
5 | # Make arguments.
6 | MAKE_ARGS="-j8 V=1"
7 |
8 | # Determine the make verb from the user action.
9 | if [ "$1" == "build" ]; then
10 | MAKE_VERB=""
11 | elif [ "$1" == "clean" ]; then
12 | MAKE_VERB="clean"
13 | elif [ "$1" == "deploy-sd" ]; then
14 | MAKE_VERB="deploy-sd"
15 | elif [ "$1" == "deploy-ftp" ]; then
16 | MAKE_VERB="deploy-ftp"
17 | elif [ "$1" == "deploy-ryu" ]; then
18 | MAKE_VERB="deploy-ryu"
19 | elif [ "$1" == "make-npdm-json" ]; then
20 | MAKE_VERB="npdm-json"
21 | else
22 | echo "Invalid arg. (build/clean/deploy-sd/deploy-ftp/deploy-ryu)"
23 | exit 1
24 | fi
25 |
26 | make $MAKE_ARGS $MAKE_VERB
27 |
--------------------------------------------------------------------------------
/main.npdm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/masagrator/Xenoblade2DynFPS/b55f63d5bef95d6a21d8f778f55b07391c2be500/main.npdm
--------------------------------------------------------------------------------
/misc/mk/as_rtld.mk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/masagrator/Xenoblade2DynFPS/b55f63d5bef95d6a21d8f778f55b07391c2be500/misc/mk/as_rtld.mk
--------------------------------------------------------------------------------
/misc/mk/deploy.mk:
--------------------------------------------------------------------------------
1 | .PHONY: deploy-sd deploy-ftp deploy-ryu
2 |
3 | deploy-sd:
4 | @$(SHELL) $(SCRIPTS_PATH)/deploy-sd.sh
5 |
6 | deploy-ftp:
7 | @$(PYTHON) $(SCRIPTS_PATH)/deploy-ftp.py
8 |
9 | deploy-ryu:
10 | @$(SHELL) $(SCRIPTS_PATH)/deploy-ryu.sh
11 |
--------------------------------------------------------------------------------
/misc/mk/module.mk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/masagrator/Xenoblade2DynFPS/b55f63d5bef95d6a21d8f778f55b07391c2be500/misc/mk/module.mk
--------------------------------------------------------------------------------
/misc/mk/npdm.mk:
--------------------------------------------------------------------------------
1 | NPDM_JSON := $(NPDM_JSON_PATH)/$(NPDM_JSON)
2 |
3 | .PHONY: npdm-json
4 |
5 | npdm-json:
6 | @$(PYTHON) $(SCRIPTS_PATH)/make-npdm-json.py
7 |
--------------------------------------------------------------------------------
/misc/npdm-json/application.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Application",
3 | "title_id": "0x0100801011c3e000",
4 | "title_id_range_min": "0x0100000000010000",
5 | "title_id_range_max": "0x01ffffffffffffff",
6 | "main_thread_stack_size": "0x00100000",
7 | "main_thread_priority": 44,
8 | "default_cpu_id": 0,
9 | "version": "0x00000000",
10 | "is_retail": true,
11 | "pool_partition": 0,
12 | "is_64_bit": true,
13 | "address_space_type": 3,
14 | "filesystem_access": {
15 | "permissions": "0x4000000000000000"
16 | },
17 | "service_access": ["acc:u0", "aoc:u", "apm", "appletOE", "audin:u", "audout:u", "audren:u", "banana", "bcat:u", "bsd:u", "bsdcfg", "caps:su", "caps:u", "csrng", "friend:u", "fsp-srv", "hid", "htc", "htc:tenv", "htcs", "hwopus", "irs", "ldn:u", "ldr:ro", "lm", "mii:u", "mm:u", "nfc:mf:u", "nfc:user", "nfp:user", "nifm:u", "nim:eca", "nsd:u", "ntc", "nvdrv", "pcm", "pctl", "pl:u", "prepo:u", "set", "sfdnsres", "ssl", "time:u", "vi:u"],
18 | "service_host": [],
19 | "kernel_capabilities": [{
20 | "type": "kernel_flags",
21 | "value": {
22 | "highest_thread_priority": 59,
23 | "lowest_thread_priority": 28,
24 | "lowest_cpu_id": 0,
25 | "highest_cpu_id": 2
26 | }
27 | }, {
28 | "type": "syscalls",
29 | "value": {
30 | "svcSetHeapSize": "0x01",
31 | "svcSetMemoryPermission": "0x02",
32 | "svcSetMemoryAttribute": "0x03",
33 | "svcMapMemory": "0x04",
34 | "svcUnmapMemory": "0x05",
35 | "svcQueryMemory": "0x06",
36 | "svcExitProcess": "0x07",
37 | "svcCreateThread": "0x08",
38 | "svcStartThread": "0x09",
39 | "svcExitThread": "0x0a",
40 | "svcSleepThread": "0x0b",
41 | "svcGetThreadPriority": "0x0c",
42 | "svcSetThreadPriority": "0x0d",
43 | "svcGetThreadCoreMask": "0x0e",
44 | "svcSetThreadCoreMask": "0x0f",
45 | "svcGetCurrentProcessorNumber": "0x10",
46 | "svcSignalEvent": "0x11",
47 | "svcClearEvent": "0x12",
48 | "svcMapSharedMemory": "0x13",
49 | "svcUnmapSharedMemory": "0x14",
50 | "svcCreateTransferMemory": "0x15",
51 | "svcCloseHandle": "0x16",
52 | "svcResetSignal": "0x17",
53 | "svcWaitSynchronization": "0x18",
54 | "svcCancelSynchronization": "0x19",
55 | "svcArbitrateLock": "0x1a",
56 | "svcArbitrateUnlock": "0x1b",
57 | "svcWaitProcessWideKeyAtomic": "0x1c",
58 | "svcSignalProcessWideKey": "0x1d",
59 | "svcGetSystemTick": "0x1e",
60 | "svcConnectToNamedPort": "0x1f",
61 | "svcSendSyncRequestLight": "0x20",
62 | "svcSendSyncRequest": "0x21",
63 | "svcSendSyncRequestWithUserBuffer": "0x22",
64 | "svcSendAsyncRequestWithUserBuffer": "0x23",
65 | "svcGetProcessId": "0x24",
66 | "svcGetThreadId": "0x25",
67 | "svcBreak": "0x26",
68 | "svcOutputDebugString": "0x27",
69 | "svcReturnFromException": "0x28",
70 | "svcGetInfo": "0x29",
71 | "svcMapPhysicalMemory": "0x2c",
72 | "svcUnmapPhysicalMemory": "0x2d",
73 | "svcGetLastThreadInfo": "0x2f",
74 | "svcSetThreadActivity": "0x32",
75 | "svcGetThreadContext3": "0x33",
76 | "svcWaitForAddress": "0x34",
77 | "svcSignalToAddress": "0x35",
78 | "svcSynchronizePreemptionState": "0x36",
79 | "svcFlushProcessDataCache": "0x5f"
80 | }
81 | }, {
82 | "type": "application_type",
83 | "value": 1
84 | }, {
85 | "type": "min_kernel_version",
86 | "value": "0x0091"
87 | }, {
88 | "type": "handle_table_size",
89 | "value": 512
90 | }, {
91 | "type": "debug_flags",
92 | "value": {
93 | "allow_debug": true,
94 | "force_debug": false
95 | }
96 | }]
97 | }
--------------------------------------------------------------------------------
/misc/npdm-json/qlaunch.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qlaunch",
3 | "title_id": "0x0100000000001000",
4 | "title_id_range_min": "0x0100000000001000",
5 | "title_id_range_max": "0x0100000000001000",
6 | "main_thread_stack_size": "0x00060000",
7 | "main_thread_priority": 40,
8 | "default_cpu_id": 3,
9 | "process_category": 738197504,
10 | "is_retail": true,
11 | "pool_partition": 1,
12 | "is_64_bit": true,
13 | "address_space_type": 3,
14 | "filesystem_access": {
15 | "permissions": "0x0000000000002048"
16 | },
17 | "service_access": ["acc:su", "appletAE", "audctl", "audin:u", "audout:u", "audren:u", "banana", "bcat:m", "bsd:u", "bsdcfg", "btm:sys", "capmtp", "caps:a", "erpt:c", "erpt:r", "eupld:r", "fatal:u", "friend:m", "fsp-srv", "hid", "hid:sys", "htc", "htc:tenv", "htcs", "hwopus", "irs", "lbl", "ldn:m", "ldn:s", "lm", "lp2p:m", "mig:usr", "mii:e", "miiimg", "nd:sys", "news:m", "nfc:sys", "nfp:sys", "ngct:s", "nifm:s", "nim:shp", "notif:s", "npns:s", "ns:am2", "ns:su", "nsd:u", "ntc", "nvdrv:a", "olsc:s", "ovln:snd", "pctl:s", "pdm:qry", "pl:u", "prepo:m", "psm", "set", "set:sys", "sfdnsres", "ssl", "time:a", "time:u", "vi:s"],
18 | "service_host": [],
19 | "kernel_capabilities": [{
20 | "type": "kernel_flags",
21 | "value": {
22 | "highest_thread_priority": 59,
23 | "lowest_thread_priority": 28,
24 | "lowest_cpu_id": 3,
25 | "highest_cpu_id": 3
26 | }
27 | }, {
28 | "type": "syscalls",
29 | "value": {
30 | "svcSetHeapSize": "0x01",
31 | "svcSetMemoryPermission": "0x02",
32 | "svcSetMemoryAttribute": "0x03",
33 | "svcMapMemory": "0x04",
34 | "svcUnmapMemory": "0x05",
35 | "svcQueryMemory": "0x06",
36 | "svcExitProcess": "0x07",
37 | "svcCreateThread": "0x08",
38 | "svcStartThread": "0x09",
39 | "svcExitThread": "0x0a",
40 | "svcSleepThread": "0x0b",
41 | "svcGetThreadPriority": "0x0c",
42 | "svcSetThreadPriority": "0x0d",
43 | "svcGetThreadCoreMask": "0x0e",
44 | "svcSetThreadCoreMask": "0x0f",
45 | "svcGetCurrentProcessorNumber": "0x10",
46 | "svcSignalEvent": "0x11",
47 | "svcClearEvent": "0x12",
48 | "svcMapSharedMemory": "0x13",
49 | "svcUnmapSharedMemory": "0x14",
50 | "svcCreateTransferMemory": "0x15",
51 | "svcCloseHandle": "0x16",
52 | "svcResetSignal": "0x17",
53 | "svcWaitSynchronization": "0x18",
54 | "svcCancelSynchronization": "0x19",
55 | "svcArbitrateLock": "0x1a",
56 | "svcArbitrateUnlock": "0x1b",
57 | "svcWaitProcessWideKeyAtomic": "0x1c",
58 | "svcSignalProcessWideKey": "0x1d",
59 | "svcGetSystemTick": "0x1e",
60 | "svcConnectToNamedPort": "0x1f",
61 | "svcSendSyncRequestLight": "0x20",
62 | "svcSendSyncRequest": "0x21",
63 | "svcSendSyncRequestWithUserBuffer": "0x22",
64 | "svcSendAsyncRequestWithUserBuffer": "0x23",
65 | "svcGetProcessId": "0x24",
66 | "svcGetThreadId": "0x25",
67 | "svcBreak": "0x26",
68 | "svcOutputDebugString": "0x27",
69 | "svcReturnFromException": "0x28",
70 | "svcGetInfo": "0x29",
71 | "svcGetLastThreadInfo": "0x2f",
72 | "svcWaitForAddress": "0x34",
73 | "svcSignalToAddress": "0x35",
74 | "svcMapTransferMemory": "0x51",
75 | "svcUnmapTransferMemory": "0x52",
76 | "svcFlushProcessDataCache": "0x5f"
77 | }
78 | }, {
79 | "type": "application_type",
80 | "value": 2
81 | }, {
82 | "type": "min_kernel_version",
83 | "value": "0x0091"
84 | }, {
85 | "type": "handle_table_size",
86 | "value": 512
87 | }, {
88 | "type": "debug_flags",
89 | "value": {
90 | "allow_debug": false,
91 | "force_debug": false
92 | }
93 | }]
94 | }
--------------------------------------------------------------------------------
/misc/scripts/deploy-ftp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import ftplib
4 | import os
5 | from types import NoneType
6 | from typing import Any, Dict, Optional, Self, Tuple, Union
7 |
8 | # https://ftputil.sschwarzer.net
9 | import ftputil
10 |
11 | # Wrapper to assert the enviroment variable exists.
12 | def getenv(key: str, default: Optional[str] = None, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> str:
13 | '''Wrapper for os.getenv.'''
14 |
15 | value: Union[None, str] = os.getenv(key)
16 | # Use the value if it exists.
17 | if value is not None:
18 | return value
19 | # If we're provided a default, fallback to that.
20 | elif default is not None:
21 | return default
22 | # Assert key is missing.
23 | else:
24 | raise KeyError(f'Missing enviroment variable {key}! Only run this script via make deploy-ftp.')
25 |
26 |
27 | # Get enviroment variables from build system.
28 | PROGRAM_ID: str = getenv('PROGRAM_ID')
29 | FTP_IP: str = getenv('FTP_IP')
30 | FTP_PORT: int = int(getenv('FTP_PORT'))
31 | FTP_USERNAME: str = getenv('FTP_USERNAME')
32 | OUT: str = getenv('OUT')
33 | SD_OUT: str = getenv('SD_OUT')
34 | # Password may be empty, so special case this.
35 | FTP_PASSWORD: str = getenv('FTP_PASSWORD', '')
36 |
37 |
38 | class SessionFactory(ftplib.FTP):
39 | '''Session factory for FTPHost.'''
40 |
41 | def __init__(self: Self, ftp_ip: str, ftp_port: int, ftp_username: str, ftp_password: str, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> NoneType:
42 | super().__init__()
43 |
44 | # Connect to FTP server.
45 | self.connect(ftp_ip, ftp_port)
46 | # Login with credentials.
47 | self.login(ftp_username, ftp_password)
48 |
49 |
50 | def main(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> NoneType:
51 | # Connect/login to console FTP server.
52 | with ftputil.FTPHost(FTP_IP, FTP_PORT, FTP_USERNAME, FTP_PASSWORD, session_factory=SessionFactory) as ftp_host:
53 | # Make output directory.
54 | ftp_host.makedirs(SD_OUT, exist_ok=True)
55 |
56 | # Iterate every file in the deploy directory.
57 | for name in os.listdir(OUT):
58 | # Ignore directories, for now.
59 | if not os.path.isfile(os.path.join(OUT, name)):
60 | continue
61 |
62 | # Upload file to server.
63 | ftp_host.upload(os.path.join(OUT, name), ftp_host.path.join(SD_OUT, name))
64 |
65 |
66 | if __name__ == '__main__':
67 | main()
68 |
--------------------------------------------------------------------------------
/misc/scripts/deploy-ryu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Verify the path is set.
5 | if [ -z "${RYU_PATH}" ]; then
6 | echo "RYU_PATH appears to not be set! Check your exlaunch.sh?"
7 | fi
8 |
9 | # Setup the path to the game's mods folder.
10 | export MODS_PATH=${RYU_PATH}/mods/contents/${PROGRAM_ID}/mods
11 |
12 | # Ensure directory exists.
13 | mkdir -p ${MODS_PATH}/exefs;
14 |
15 | # Copy over files.
16 | cp ${OUT}/* ${MODS_PATH}/exefs
--------------------------------------------------------------------------------
/misc/scripts/deploy-sd.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Mount drive.
5 | sudo mount ${MOUNT_PATH}
6 | # Copy files to drive.
7 | cp ${OUT}/* ${MOUNT_PATH}${SD_OUT}
8 | # Unmount drive.
9 | sudo umount ${MOUNT_PATH}
--------------------------------------------------------------------------------
/misc/scripts/make-npdm-json.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/masagrator/Xenoblade2DynFPS/b55f63d5bef95d6a21d8f778f55b07391c2be500/misc/scripts/make-npdm-json.py
--------------------------------------------------------------------------------
/misc/scripts/post-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | OUT_NSO=${OUT}/${BINARY_NAME}
5 | OUT_NPDM=${OUT}/main.npdm
6 |
7 | # Clear older build.
8 | rm -rf ${OUT}
9 |
10 | # Create out directory.
11 | mkdir ${OUT}
12 |
13 | # Copy build into out
14 | mv ${NAME}.nso ${OUT_NSO}
15 | mv ${NAME}.npdm ${OUT_NPDM}
16 |
17 | # Copy ELF to user path if defined.
18 | if [ ! -z $ELF_EXTRACT ]; then
19 | cp "$NAME.elf" "$ELF_EXTRACT"
20 | fi
21 |
--------------------------------------------------------------------------------
/misc/specs/as_rtld.specs:
--------------------------------------------------------------------------------
1 | %rename link old_link
2 |
3 | *link:
4 | %(old_link) -T ../misc/link.ld -pie --gc-sections -z text -z nodynamic-undefined-weak --build-id=sha1 --nx-module-name --exclude-libs=ALL
5 |
6 | *startfile:
7 | crti%O%s crtbegin%O%s
--------------------------------------------------------------------------------
/misc/specs/module.specs:
--------------------------------------------------------------------------------
1 | %rename link old_link
2 |
3 | *link:
4 | %(old_link) -T ../misc/link.ld --shared --export-dynamic --gc-sections --build-id=sha1 --nx-module-name -init=exl_module_init --exclude-libs=ALL
5 |
6 | *startfile:
7 | crti%O%s crtbegin%O%s
--------------------------------------------------------------------------------
/source/common.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "types.h"
4 |
5 | #include "lib/alloc.hpp"
6 | #include "lib/nx/nx.h"
7 | #include "lib/result.hpp"
8 | #include "lib/libsetting.hpp"
9 |
10 | #define APPEND_IMPL(x, y) x ## y
11 | #define APPEND(x, y) APPEND_IMPL(x, y)
12 |
13 | #define NON_COPYABLE(cls) \
14 | cls(const cls&) = delete; \
15 | cls& operator=(const cls&) = delete
16 |
17 | #define NON_MOVEABLE(cls) \
18 | cls(cls&&) = delete; \
19 | cls& operator=(cls&&) = delete
20 |
21 | #define NUM_ARGS_(_1, _2, _3, _4, _5, _6, TOTAL, ...) TOTAL
22 | #define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1)
23 | #define VA_MACRO(MACRO, ...) APPEND(MACRO, NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)
24 |
--------------------------------------------------------------------------------
/source/lib.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 |
5 | #include "lib/armv8.hpp"
6 |
7 | #include "lib/diag/abort.hpp"
8 | #include "lib/diag/assert.hpp"
9 |
10 | #include "lib/reloc/rtld.hpp"
11 |
12 | #include "lib/patch/code_patcher.hpp"
13 | #include "lib/patch/patcher_impl.hpp"
14 | #include "lib/patch/random_access_patcher.hpp"
15 | #include "lib/patch/stream_patcher.hpp"
16 |
17 | #include "lib/util/math/bitset.hpp"
18 | #include "lib/util/sys/cur_proc_handle.hpp"
19 | #include "lib/util/sys/jit.hpp"
20 | #include "lib/util/sys/mem_layout.hpp"
21 | #include "lib/util/sys/rw_pages.hpp"
22 | #include "lib/util/sys/soc.hpp"
23 | #include "lib/util/modules.hpp"
24 | #include "lib/util/ptr_path.hpp"
25 | #include "lib/util/typed_storage.hpp"
26 |
27 | #include "lib/hook/base.hpp"
28 | #include "lib/hook/class.hpp"
29 | #include "lib/hook/deprecated.hpp"
30 | #include "lib/hook/inline.hpp"
31 | #include "lib/hook/replace.hpp"
32 | #include "lib/hook/trampoline.hpp"
33 |
--------------------------------------------------------------------------------
/source/lib/alloc.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | /* Require an externally linked heap implementation to be provided if fake heap isn't used. */
4 | #ifndef EXL_USE_FAKEHEAP
5 |
6 | #include
7 |
8 | extern "C" {
9 |
10 | extern void *malloc(size_t size);
11 | extern void *aligned_alloc( size_t alignment, size_t size );
12 |
13 | };
14 |
15 | #endif
--------------------------------------------------------------------------------
/source/lib/armv8.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "util/math/bitset.hpp"
5 | #include "armv8/register.hpp"
6 |
7 | namespace exl::armv8 {
8 |
9 | using InstType = uint;
10 |
11 | template
12 | using InstMask = util::Mask;
13 |
14 | using InstBitSet = util::BitSet;
15 | }
16 |
17 | #include "armv8/instructions.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../armv8.hpp"
4 |
5 | #define _ACCESSOR_MASK_NAME(name) \
6 | APPEND(name, Mask)
7 | #define _ACCESSOR_GETTER_NAME(name) \
8 | Get##name
9 | #define _ACCESSOR_SETTER_NAME(name) \
10 | Set##name
11 |
12 |
13 | #define _ACCESSOR_BODY(name) \
14 | constexpr InstType _ACCESSOR_GETTER_NAME(name)() const { \
15 | return BitsOf<_ACCESSOR_MASK_NAME(name)>(); \
16 | } \
17 | constexpr void _ACCESSOR_SETTER_NAME(name)(InstType val) { \
18 | SetBits<_ACCESSOR_MASK_NAME(name)>(val); \
19 | }
20 |
21 | #define _ACCESSOR_2(name, low) \
22 | static constexpr auto _ACCESSOR_MASK_NAME(name) = InstMask(); \
23 | _ACCESSOR_BODY(name)
24 | #define _ACCESSOR_3(name, low, high) \
25 | static constexpr auto _ACCESSOR_MASK_NAME(name) = InstMask(); \
26 | _ACCESSOR_BODY(name)
27 |
28 | #define ACCESSOR(...) VA_MACRO(_ACCESSOR_, __VA_ARGS__)
29 |
30 | #include "instructions/base.hpp"
31 |
32 | #undef _ACCESSOR_MASK_NAME
33 | #undef _ACCESSOR_GETTER_NAME
34 | #undef _ACCESSOR_SETTER_NAME
35 | #undef _ACCESSOR_BODY
36 | #undef _ACCESSOR_2
37 | #undef _ACCESSOR_3
38 | #undef ACCESSOR
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Instruction : public InstBitSet {
8 |
9 | ACCESSOR(MainOp0, 25, 29);
10 |
11 | constexpr Instruction(u8 op0) {
12 | SetMainOp0(op0);
13 | }
14 | };
15 |
16 | enum ShiftType : u8 {
17 | ShiftType_LSL = 0b00,
18 | ShiftType_LSR = 0b01,
19 | ShiftType_ASR = 0b10,
20 | ShiftType_ROR = 0b11,
21 | };
22 |
23 | enum ExtendType : u8 {
24 | ExtendType_UXTB = 0b000,
25 | ExtendType_UXTH = 0b001,
26 | ExtendType_UXTW = 0b010,
27 | ExtendType_UXTX = 0b011,
28 | ExtendType_LSL = 0b011,
29 | ExtendType_SXTB = 0b100,
30 | ExtendType_SXTH = 0b101,
31 | ExtendType_SXTW = 0b110,
32 | ExtendType_SXTX = 0b111,
33 | };
34 |
35 | }
36 |
37 | #include "op100x/base.hpp"
38 | #include "op101x/base.hpp"
39 | #include "opx1x0/base.hpp"
40 | #include "opx101/base.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/add_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct AddImmediate : public impl::op100x::AddSubtractImmediate {
8 |
9 | static constexpr bool Op = 0b0;
10 | static constexpr bool S = 0b0;
11 |
12 | constexpr AddImmediate(reg::Register rd, reg::Register rn, u32 imm) : AddSubtractImmediate(rd.Is64(), Op, S) {
13 | /* static_assert(rd.Is64() == rn.Is64(), ""); */
14 | SetRd(rd.Index());
15 | SetRn(rn.Index());
16 | SetImm12(CalcImm(imm));
17 | SetSh(CalcSh(imm));
18 | }
19 | };
20 |
21 | static_assert(AddImmediate(reg::X0, reg::X1, 12).Value() == 0x91003020, "");
22 | static_assert(AddImmediate(reg::X2, reg::X3, 46).Value() == 0x9100B862, "");
23 | static_assert(AddImmediate(reg::X4, reg::X5, 0x1000).Value() == 0x914004A4, "");
24 | static_assert(AddImmediate(reg::W6, reg::W7, 0x57000).Value() == 0x11415CE6, "");
25 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/adds_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct AddsImmediate : public impl::op100x::AddSubtractImmediate {
8 |
9 | static constexpr bool Op = 0b0;
10 | static constexpr bool S = 0b1;
11 |
12 | constexpr AddsImmediate(reg::Register rd, reg::Register rn, u32 imm) : AddSubtractImmediate(rd.Is64(), Op, S) {
13 | /* static_assert(rd.Is64() == rn.Is64(), ""); */
14 | SetRd(rd.Index());
15 | SetRn(rn.Index());
16 | SetImm12(CalcImm(imm));
17 | SetSh(CalcSh(imm));
18 | }
19 | };
20 |
21 | static_assert(AddsImmediate(reg::X0, reg::X1, 12).Value() == 0xB1003020, "");
22 | static_assert(AddsImmediate(reg::X2, reg::X3, 46).Value() == 0xB100B862, "");
23 | static_assert(AddsImmediate(reg::X4, reg::X5, 0x1000).Value() == 0xB14004A4, "");
24 | static_assert(AddsImmediate(reg::W6, reg::W7, 0x57000).Value() == 0x31415CE6, "");
25 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op100x {
6 |
7 | struct AddSubtractImmediate : public Op100xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b010;
10 |
11 | static const uint ImmShift = 12;
12 | static const u32 MaskForImmShift = (1 << ImmShift) - 1;
13 |
14 | ACCESSOR(Sf, 31);
15 | ACCESSOR(Op, 30);
16 | ACCESSOR(S, 29);
17 | ACCESSOR(Sh, 22);
18 | ACCESSOR(Imm12, 10, 22);
19 | ACCESSOR(Rn, 5, 10);
20 | ACCESSOR(Rd, 0, 5);
21 |
22 | constexpr AddSubtractImmediate(bool sf, bool op, bool s) : Op100xInstruction(Op0) {
23 | SetSf(sf);
24 | SetOp(op);
25 | SetS(s);
26 | }
27 |
28 | static constexpr bool CalcSh(u32 imm) {
29 | return imm != 0 && (imm & MaskForImmShift) == 0;
30 | }
31 |
32 | static constexpr u16 CalcImm(u32 imm) {
33 | if(CalcSh(imm)) {
34 | imm >>= ImmShift;
35 | }
36 | return static_cast(imm);
37 | }
38 | };
39 | };
40 |
41 | #include "add_immediate.hpp"
42 | #include "adds_immediate.hpp"
43 | #include "sub_immediate.hpp"
44 | #include "subs_immediate.hpp"
45 |
46 | /* Alias. */
47 | #include "cmn_immediate.hpp"
48 | #include "cmp_immediate.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/cmn_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | /* Alias. */
8 | struct CmnImmediate : public AddsImmediate {
9 |
10 | static constexpr reg::Register GetRd(reg::Register reg) {
11 | if(reg.Is64()) {
12 | return reg::None64;
13 | }
14 | else {
15 | return reg::None32;
16 | }
17 | }
18 |
19 | constexpr CmnImmediate(reg::Register reg, u32 imm) : AddsImmediate(GetRd(reg), reg, imm) {}
20 |
21 | };
22 |
23 | static_assert(CmnImmediate(reg::X0, 45).Value() == 0xB100B41F, "");
24 | static_assert(CmnImmediate(reg::W1, 32).Value() == 0x3100803F, "");
25 | static_assert(CmnImmediate(reg::X2, 0x4000).Value() == 0xB140105F, "");
26 | static_assert(CmnImmediate(reg::X3, 0x54000).Value() == 0xB141507F, "");
27 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/cmp_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | /* Alias. */
8 | struct CmpImmediate : public SubsImmediate {
9 |
10 | static constexpr reg::Register GetRd(reg::Register reg) {
11 | if(reg.Is64()) {
12 | return reg::None64;
13 | }
14 | else {
15 | return reg::None32;
16 | }
17 | }
18 |
19 | constexpr CmpImmediate(reg::Register reg, u32 imm) : SubsImmediate(GetRd(reg), reg, imm) {}
20 |
21 | };
22 |
23 | static_assert(CmpImmediate(reg::X0, 45).Value() == 0xF100B41F, "");
24 | static_assert(CmpImmediate(reg::W1, 32).Value() == 0x7100803F, "");
25 | static_assert(CmpImmediate(reg::X2, 0x4000).Value() == 0xF140105F, "");
26 | static_assert(CmpImmediate(reg::X3, 0x54000).Value() == 0xF141507F, "");
27 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/sub_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct SubImmediate : public impl::op100x::AddSubtractImmediate {
8 |
9 | static constexpr bool Op = 0b1;
10 | static constexpr bool S = 0b0;
11 |
12 | constexpr SubImmediate(reg::Register rd, reg::Register rn, u32 imm) : AddSubtractImmediate(rd.Is64(), Op, S) {
13 | /* static_assert(rd.Is64() == rn.Is64(), ""); */
14 | SetRd(rd.Index());
15 | SetRn(rn.Index());
16 | SetImm12(CalcImm(imm));
17 | SetSh(CalcSh(imm));
18 | }
19 | };
20 |
21 | static_assert(SubImmediate(reg::X0, reg::X1, 12).Value() == 0xD1003020, "");
22 | static_assert(SubImmediate(reg::X2, reg::X3, 46).Value() == 0xD100B862, "");
23 | static_assert(SubImmediate(reg::X4, reg::X5, 0x1000).Value() == 0xD14004A4, "");
24 | static_assert(SubImmediate(reg::W6, reg::W7, 0x57000).Value() == 0x51415CE6, "");
25 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/add_subtract_immediate/subs_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct SubsImmediate : public impl::op100x::AddSubtractImmediate {
8 |
9 | static constexpr bool Op = 0b1;
10 | static constexpr bool S = 0b1;
11 |
12 | constexpr SubsImmediate(reg::Register rd, reg::Register rn, u32 imm) : AddSubtractImmediate(rd.Is64(), Op, S) {
13 | /* static_assert(rd.Is64() == rn.Is64(), ""); */
14 | SetRd(rd.Index());
15 | SetRn(rn.Index());
16 | SetImm12(CalcImm(imm));
17 | SetSh(CalcSh(imm));
18 | }
19 | };
20 |
21 | static_assert(SubsImmediate(reg::X0, reg::X1, 12).Value() == 0xF1003020, "");
22 | static_assert(SubsImmediate(reg::X2, reg::X3, 46).Value() == 0xF100B862, "");
23 | static_assert(SubsImmediate(reg::X4, reg::X5, 0x1000).Value() == 0xF14004A4, "");
24 | static_assert(SubsImmediate(reg::W6, reg::W7, 0x57000).Value() == 0x71415CE6, "");
25 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl {
6 |
7 | struct Op100xInstruction : public Instruction {
8 |
9 | ACCESSOR(Op0, 23, 26);
10 |
11 | constexpr Op100xInstruction(u8 op0) : Instruction(0b1000) {
12 | SetOp0(op0);
13 | }
14 | };
15 | }
16 |
17 | #include "add_subtract_immediate/base.hpp"
18 | #include "logical_immediate/base.hpp"
19 | #include "move_wide_immediate/base.hpp"
20 | #include "pc_rel_addressing/base.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/logical_immediate/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op100x {
6 |
7 | struct LogicalImmediate : public Op100xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b100;
10 |
11 | ACCESSOR(Sf, 31);
12 | ACCESSOR(Opc, 29, 31);
13 | ACCESSOR(N, 22);
14 | ACCESSOR(Immr, 16, 22);
15 | ACCESSOR(Imms, 10, 16);
16 | ACCESSOR(Rn, 5, 10);
17 | ACCESSOR(Rd, 0, 5);
18 |
19 | constexpr LogicalImmediate(u8 sf, u8 opc) : Op100xInstruction(Op0) {
20 | SetSf(sf);
21 | SetOpc(opc);
22 | }
23 | };
24 | };
25 |
26 | /* TODO: make this work well... */
27 | // #include "orr_immediate.hpp"
28 |
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/logical_immediate/orr_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | #include
6 | #include
7 |
8 | namespace exl::armv8::inst {
9 |
10 | struct OrrImmediate : public impl::op100x::LogicalImmediate {
11 |
12 | static constexpr u8 Opc = 0b01;
13 |
14 | static constexpr auto ImmBase = 10;
15 |
16 | static constexpr auto ImmsLow = 10;
17 | static constexpr auto ImmsHigh = 16;
18 |
19 | static constexpr auto ImmrLow = 16;
20 | static constexpr auto ImmrHigh = 20;
21 |
22 | static constexpr auto NLow = 22;
23 |
24 | ACCESSOR(N, NLow);
25 | ACCESSOR(Immr, ImmrLow, ImmrHigh);
26 | ACCESSOR(Imms, ImmsLow, ImmsHigh);
27 | ACCESSOR(Rn, 5, 10);
28 | ACCESSOR(Rd, 0, 5);
29 |
30 | using ImmType = u16;
31 |
32 | static constexpr auto RMaskForImm = util::Mask();
33 | static constexpr auto SMaskForImm = util::Mask();
34 | static constexpr auto NMaskForImm = util::Mask();
35 |
36 | static constexpr util::BitSet EncodeImmediate(ImmType imm) {
37 | /*
38 | static_assert(imm != 0);
39 | */
40 |
41 | /* Compute the needed rotations. */
42 | auto r = static_cast(std::countr_zero(imm));
43 | /* Compute the immediate to encode. */
44 | u32 immShift = imm >> r;
45 | /* Compute the amount of bits needed to encode the immediate. */
46 | ImmType width = std::bit_width(immShift);
47 | /* Round up the width to a power of 2. */
48 | ImmType widthCeil = std::clamp(std::bit_ceil(width), static_cast(2), static_cast(64));
49 |
50 | /* N should be 64 if the total value is 64 bits. */
51 | bool n = widthCeil == 64;
52 |
53 | /* Compute mask where the width will fit in. */
54 | auto widthMask = static_cast(std::bit_width(imm) << 1) - 1;
55 |
56 | /* Initialize s and carve out space for the width and extra 0 bit. */
57 | auto s = ((1u << ImmsMask.Count) - 1u) & ~widthMask;
58 | /* OR in the width. */
59 | s |= (width - 1) & widthMask;
60 |
61 | /* Build up BitSet to return. */
62 | auto b = util::BitSet();
63 | b.SetBits(r);
64 | b.SetBits(s);
65 | b.SetBits(n);
66 | return b;
67 | }
68 |
69 |
70 | constexpr OrrImmediate(reg::Register rd, reg::Register rn, ImmType imm) : LogicalImmediate(rd.Is64(), Opc) {
71 | auto immb = EncodeImmediate(imm);
72 |
73 | /* static_asert(rd.Is64() == rn.Is64(), ""); */
74 | /* static_assert(rd.Is32() != immb.BitsOf(), ""); */
75 |
76 | SetN(immb.BitsOf());
77 | SetImmr(immb.BitsOf());
78 | SetImms(immb.BitsOf());
79 | SetRn(rn.Index());
80 | SetRd(rd.Index());
81 | }
82 |
83 | };
84 |
85 | //static constexpr auto Test = OrrImmediate(reg::X0, reg::X1, 0b11111);
86 |
87 | //static_assert(Test.Value() == 0xB2401020, "");
88 | //static_assert(OrrImmediate(reg::W2, reg::W3, 0b111100).Value() == 0x321E0C62, "");
89 | //static_assert(OrrImmediate(reg::X4, reg::X5, 0b111).Value() == 0xB24008A4, "");
90 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/move_wide_immediate/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op100x {
6 |
7 | struct MoveWideImmediate : public Op100xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b101;
10 |
11 | ACCESSOR(Sf, 31);
12 | ACCESSOR(Opc, 29, 31);
13 | ACCESSOR(Hw, 21, 23);
14 | ACCESSOR(Imm16, 5, 21);
15 | ACCESSOR(Rd, 0, 5);
16 |
17 | constexpr MoveWideImmediate(reg::Register reg, u8 opc, u8 hw, u16 imm) : Op100xInstruction(Op0) {
18 | SetSf(reg.Is64());
19 | SetOpc(opc);
20 | SetHw(hw);
21 | SetImm16(imm);
22 | SetRd(reg.Index());
23 | }
24 | };
25 | };
26 |
27 | #include "movk.hpp"
28 | #include "movn.hpp"
29 | #include "movz.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/move_wide_immediate/movk.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Movk : public impl::op100x::MoveWideImmediate {
8 |
9 | static constexpr u8 Opc = 0b11;
10 | static constexpr u8 Hw = 0b00;
11 |
12 | constexpr Movk(reg::Register reg, u16 imm) : MoveWideImmediate(reg, Opc, Hw, imm) {}
13 |
14 | };
15 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/move_wide_immediate/movn.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Movn : public impl::op100x::MoveWideImmediate {
8 |
9 | static constexpr u8 Opc = 0b00;
10 | static constexpr u8 Hw = 0b00;
11 |
12 | constexpr Movn(reg::Register reg, u16 imm) : MoveWideImmediate(reg, Opc, Hw, imm) {}
13 |
14 | };
15 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/move_wide_immediate/movz.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Movz : public impl::op100x::MoveWideImmediate {
8 |
9 | static constexpr u8 Opc = 0b10;
10 | static constexpr u8 Hw = 0b00;
11 |
12 | constexpr Movz(reg::Register reg, u16 imm) : MoveWideImmediate(reg, Opc, Hw, imm) {}
13 |
14 | };
15 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/pc_rel_addressing/adr.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Adr : public impl::op100x::PcRelAddressing {
8 |
9 | static constexpr u8 Opc = 0b00;
10 | static constexpr u8 Hw = 0b00;
11 |
12 | constexpr Adr(reg::Register reg, u32 imm) : PcRelAddressing(reg, imm, Op_ADR) {}
13 |
14 | };
15 |
16 | static_assert(Adr(reg::X0, 0x1000u).Value() == 0x10008000, "");
17 | static_assert(Adr(reg::X1, 0xfff0u).Value() == 0x1007FF81, "");
18 | static_assert(Adr(reg::X2, 0x69669u).Value() == 0x3034B342, "");
19 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/pc_rel_addressing/adrp.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Adrp : public impl::op100x::PcRelAddressing {
8 |
9 | static constexpr u8 Opc = 0b00;
10 | static constexpr u8 Hw = 0b00;
11 |
12 | constexpr Adrp(reg::Register reg, u32 imm) : PcRelAddressing(reg, imm >> 12, Op_ADRP) {}
13 |
14 | };
15 |
16 | static_assert(Adrp(reg::X0, 0x1000).Value() == 0xB0000000, "");
17 | static_assert(Adrp(reg::X1, 0xfff000).Value() == 0xF0007FE1, "");
18 | static_assert(Adrp(reg::X2, 0x6969000).Value() == 0xB0034B42, "");
19 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op100x/pc_rel_addressing/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op100x {
6 |
7 | struct PcRelAddressing : public Op100xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b000;
10 |
11 | ACCESSOR(Op, 31);
12 | ACCESSOR(Immlo, 29, 31);
13 | ACCESSOR(Immhi, 5, 24);
14 | ACCESSOR(Rd, 0, 5);
15 |
16 | enum Op : u8 {
17 | Op_ADR = 0,
18 | Op_ADRP = 1,
19 | };
20 |
21 | constexpr PcRelAddressing(reg::Register reg, u32 imm, Op op) : Op100xInstruction(Op0) {
22 | /*
23 | static_assert(reg.Is64());
24 | */
25 | SetOp(op);
26 | SetImmlo(imm);
27 | SetImmhi(imm >> ImmloMask.Count);
28 | SetRd(reg.Index());
29 | }
30 | };
31 | };
32 |
33 | #include "adr.hpp"
34 | #include "adrp.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl {
6 |
7 | struct Op101xInstruction : public Instruction {
8 |
9 | ACCESSOR(Op0, 29, 32);
10 | ACCESSOR(Op1, 12, 26);
11 | ACCESSOR(Op2, 0, 5);
12 |
13 | constexpr Op101xInstruction(u8 op0) : Instruction(0b1010) {
14 | SetOp0(op0);
15 | }
16 | };
17 | }
18 |
19 | #include "hints/base.hpp"
20 | #include "unconditional_branch_immediate/base.hpp"
21 | #include "unconditional_branch_register/base.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/hints/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op101x {
6 |
7 | struct Hints : public Op101xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b110;
10 | static constexpr u16 Op1 = 0b01000000110010;
11 | static constexpr u8 Op2 = 0b11111;
12 |
13 | ACCESSOR(CRm, 8, 12);
14 | ACCESSOR(LocalOp2, 5, 8);
15 |
16 | constexpr Hints() : Op101xInstruction(Op0) {
17 | SetOp1(Op1);
18 | SetOp2(Op2);
19 | }
20 | };
21 | }
22 |
23 | #include "nop.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/hints/nop.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Nop : public impl::op101x::Hints {
8 |
9 | static constexpr u8 CRm = 0b0000;
10 | static constexpr u8 LocalOp2 = 0b000;
11 |
12 | constexpr Nop() : Hints() {
13 | SetCRm(CRm);
14 | SetLocalOp2(LocalOp2);
15 | }
16 | };
17 |
18 | static_assert(Nop().Value() == 0xD503201F, "");
19 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/unconditional_branch_immediate/b.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Branch : public impl::op101x::UnconditionalBranchImmediate {
8 |
9 | constexpr Branch(uint relative_address) : UnconditionalBranchImmediate(UnconditionalBranchImmediate::B, relative_address) {}
10 | };
11 |
12 | static_assert(Branch(0x4440).Value() == 0x14001110, "");
13 | static_assert(Branch(0x4200).Value() == 0x14001080, "");
14 | static_assert(Branch(0x6900).Value() == 0x14001A40, "");
15 | static_assert(Branch(0x0008).Value() == 0x14000002, "");
16 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/unconditional_branch_immediate/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op101x {
6 |
7 | struct UnconditionalBranchImmediate : public Op101xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b000;
10 |
11 | ACCESSOR(Op, 31);
12 | ACCESSOR(Imm26, 0, 26);
13 |
14 | enum Op {
15 | B = 0,
16 | BL = 1,
17 | };
18 |
19 | constexpr UnconditionalBranchImmediate(Op op, uint relative_address) : Op101xInstruction(Op0) {
20 | SetOp(op);
21 | SetImm26(relative_address / 4);
22 | }
23 | };
24 | }
25 |
26 | #include "b.hpp"
27 | #include "bl.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/unconditional_branch_immediate/bl.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct BranchLink : public impl::op101x::UnconditionalBranchImmediate {
8 |
9 | constexpr BranchLink(uint relative_address) : UnconditionalBranchImmediate(UnconditionalBranchImmediate::BL, relative_address) {}
10 | };
11 |
12 | static_assert(BranchLink(0x4440).Value() == 0x94001110, "");
13 | static_assert(BranchLink(0x4200).Value() == 0x94001080, "");
14 | static_assert(BranchLink(0x6900).Value() == 0x94001A40, "");
15 | static_assert(BranchLink(0x0008).Value() == 0x94000002, "");
16 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/unconditional_branch_register/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::op101x {
6 |
7 | struct UnconditionalBranchRegister : public Op101xInstruction {
8 |
9 | static constexpr u8 Op0 = 0b110;
10 | static constexpr u32 Op1 = 0b10000000000000;
11 |
12 | ACCESSOR(Opc, 21, 25);
13 | ACCESSOR(UBROp2,16, 21);
14 | ACCESSOR(Op3, 10, 16);
15 | ACCESSOR(Rn, 5, 10);
16 | ACCESSOR(Op4, 0, 5);
17 |
18 | constexpr UnconditionalBranchRegister(u8 opc, u8 op2) : Op101xInstruction(Op0) {
19 | SetOp1(Op1);
20 | SetOpc(opc);
21 | SetUBROp2(op2);
22 | }
23 | };
24 | }
25 |
26 | #include "br.hpp"
27 | #include "ret.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/unconditional_branch_register/br.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct BranchRegister : public impl::op101x::UnconditionalBranchRegister {
8 |
9 | static constexpr u8 Opc = 0b0000;
10 | static constexpr u8 Op2 = 0b11111;
11 | static constexpr u8 Op3 = 0b000000;
12 | static constexpr u8 Op4 = 0b00000;
13 |
14 | constexpr BranchRegister(reg::Register rn) : UnconditionalBranchRegister(Opc, Op2) {
15 | /*
16 | static_assert(rn.Is64());
17 | */
18 |
19 | SetOp3(Op3);
20 | SetRn(rn.Index());
21 | SetOp4(Op4);
22 | }
23 | };
24 |
25 | static_assert(BranchRegister(reg::X0).Value() == 0xD61F0000, "");
26 | static_assert(BranchRegister(reg::X1).Value() == 0xD61F0020, "");
27 | static_assert(BranchRegister(reg::X2).Value() == 0xD61F0040, "");
28 | static_assert(BranchRegister(reg::X3).Value() == 0xD61F0060, "");
29 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/op101x/unconditional_branch_register/ret.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct Ret : public impl::op101x::UnconditionalBranchRegister {
8 |
9 | static constexpr u8 Opc = 0b0010;
10 | static constexpr u8 Op2 = 0b11111;
11 | static constexpr u8 Op3 = 0b000000;
12 | static constexpr u8 Op4 = 0b00000;
13 |
14 | constexpr Ret(reg::Register rn = reg::X30) : UnconditionalBranchRegister(Opc, Op2) {
15 | /*
16 | static_assert(rn.Is64());
17 | */
18 |
19 | SetOp3(Op3);
20 | SetRn(rn.Index());
21 | SetOp4(Op4);
22 | }
23 | };
24 |
25 | static_assert(Ret(reg::X0).Value() == 0xD65F0000, "");
26 | static_assert(Ret(reg::X1).Value() == 0xD65F0020, "");
27 | static_assert(Ret(reg::X2).Value() == 0xD65F0040, "");
28 | static_assert(Ret(reg::X30).Value() == 0xD65F03C0, "");
29 | static_assert(Ret().Value() == 0xD65F03C0, "");
30 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx101/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl {
6 |
7 | struct Opx101Instruction : public Instruction {
8 |
9 | ACCESSOR(Op0, 30);
10 | ACCESSOR(Op1, 28);
11 | ACCESSOR(Op2, 20, 24);
12 | ACCESSOR(Op3, 10, 15);
13 |
14 | constexpr Opx101Instruction(u8 op0, u8 op1, u8 op2, u8 op3) : Instruction(0b0101) {
15 | SetOp0(op0);
16 | SetOp1(op1);
17 | SetOp2(op2);
18 | SetOp3(op3);
19 | }
20 | };
21 | }
22 |
23 | #include "logical_shifted_register/base.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx101/logical_shifted_register/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::opx101 {
6 |
7 | struct LogicalShiftedRegister : public Opx101Instruction {
8 |
9 | static constexpr u8 Op0 = 0b0;
10 | static constexpr u8 Op1 = 0b0;
11 | static constexpr u8 Op2 = 0b0000;
12 | static constexpr u8 Op3 = 0b000000;
13 |
14 | ACCESSOR(Sf, 31);
15 | ACCESSOR(Opc, 29, 31);
16 | ACCESSOR(N, 21);
17 | ACCESSOR(Immr, 16, 22);
18 | ACCESSOR(Imms, 10, 16);
19 | ACCESSOR(Rn, 5, 10);
20 | ACCESSOR(Rd, 0, 5);
21 |
22 | constexpr LogicalShiftedRegister(u8 sf, u8 opc) : Opx101Instruction(Op0, Op1, Op2, Op3) {
23 | SetSf(sf);
24 | SetOpc(opc);
25 | }
26 | };
27 | };
28 |
29 | #include "orr_shifted_register.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx101/logical_shifted_register/mov_register.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "orr_shifted_register.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | /* Alias. */
8 | struct MovRegister : public OrrShiftedRegister {
9 |
10 | static constexpr inline auto GetRn(reg::Register rd, reg::Register rm) {
11 | /* TODO: static_assert(rd.Is64() == rm.Is64() , ""); */
12 | return rd.Is64() ? reg::None64 : reg::None32;
13 | }
14 |
15 | constexpr MovRegister(reg::Register rd, reg::Register rm) : OrrShiftedRegister(rd, GetRn(rd, rm), rm) {}
16 |
17 | };
18 |
19 | static_assert(MovRegister(reg::X0, reg::X1).Value() == 0xAA0103E0, "");
20 | static_assert(MovRegister(reg::X2, reg::X3).Value() == 0xAA0303E2, "");
21 | static_assert(MovRegister(reg::X4, reg::X5).Value() == 0xAA0503E4, "");
22 | static_assert(MovRegister(reg::X6, reg::X7).Value() == 0xAA0703E6, "");
23 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx101/logical_shifted_register/orr_shifted_register.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct OrrShiftedRegister : public impl::opx101::LogicalShiftedRegister {
8 |
9 | static constexpr u8 Sf = 0b1;
10 | static constexpr u8 Opc = 0b01;
11 |
12 | ACCESSOR(Shift, 22, 24);
13 | ACCESSOR(Rm, 16, 21);
14 | ACCESSOR(Imm6, 10, 16);
15 |
16 | constexpr inline static auto GetSf(reg::Register rd, reg::Register rn, reg::Register rm) {
17 | /* TODO: static_assert(rd.Is64() == rn.Is64() && rn.Is64() == rm.Is64(), ""); */
18 | return rd.Is64();
19 | }
20 |
21 | constexpr OrrShiftedRegister(reg::Register rd, reg::Register rn, reg::Register rm, ShiftType shift = ShiftType_LSL, u16 amount = 0)
22 | : LogicalShiftedRegister(GetSf(rd, rn, rm), Opc) {
23 | SetShift(shift);
24 | SetN(0);
25 | SetRm(rm.Index());
26 | SetImm6(amount);
27 | SetRn(rn.Index());
28 | SetRd(rd.Index());
29 | }
30 |
31 | };
32 |
33 | static_assert(OrrShiftedRegister(reg::X0, reg::X1, reg::X2).Value() == 0xAA020020, "");
34 | static_assert(OrrShiftedRegister(reg::X3, reg::X4, reg::X5).Value() == 0xAA050083, "");
35 | static_assert(OrrShiftedRegister(reg::X6, reg::X7, reg::X8).Value() == 0xAA0800E6, "");
36 | static_assert(OrrShiftedRegister(reg::X9, reg::X10, reg::X11).Value() == 0xAA0B0149, "");
37 |
38 | static_assert(OrrShiftedRegister(reg::X0, reg::X1, reg::X2, ShiftType_LSR, 8).Value() == 0xAA422020, "");
39 | static_assert(OrrShiftedRegister(reg::X3, reg::X4, reg::X5, ShiftType_LSR, 8).Value() == 0xAA452083, "");
40 | static_assert(OrrShiftedRegister(reg::X6, reg::X7, reg::X8, ShiftType_LSR, 8).Value() == 0xAA4820E6, "");
41 | static_assert(OrrShiftedRegister(reg::X9, reg::X10, reg::X11, ShiftType_LSR, 8).Value() == 0xAA4B2149, "");
42 |
43 | static_assert(OrrShiftedRegister(reg::X0, reg::X1, reg::X2, ShiftType_ASR, 8).Value() == 0xAA822020, "");
44 | static_assert(OrrShiftedRegister(reg::X3, reg::X4, reg::X5, ShiftType_ASR, 8).Value() == 0xAA852083, "");
45 | static_assert(OrrShiftedRegister(reg::X6, reg::X7, reg::X8, ShiftType_ASR, 8).Value() == 0xAA8820E6, "");
46 | static_assert(OrrShiftedRegister(reg::X9, reg::X10, reg::X11, ShiftType_ASR, 8).Value() == 0xAA8B2149, "");
47 |
48 | static_assert(OrrShiftedRegister(reg::X0, reg::X1, reg::X2, ShiftType_ROR, 8).Value() == 0xAAC22020, "");
49 | static_assert(OrrShiftedRegister(reg::X3, reg::X4, reg::X5, ShiftType_ROR, 8).Value() == 0xAAC52083, "");
50 | static_assert(OrrShiftedRegister(reg::X6, reg::X7, reg::X8, ShiftType_ROR, 8).Value() == 0xAAC820E6, "");
51 | static_assert(OrrShiftedRegister(reg::X9, reg::X10, reg::X11, ShiftType_ROR, 8).Value() == 0xAACB2149, "");
52 |
53 | }
54 |
55 |
56 | #include "mov_register.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl {
6 |
7 | struct Opx1x0Instruction : public Instruction {
8 |
9 | ACCESSOR(Op0, 28, 32);
10 | ACCESSOR(Op1, 26);
11 | ACCESSOR(Op2, 23, 25);
12 | ACCESSOR(Op3, 16, 22);
13 | ACCESSOR(Op4, 10, 12);
14 |
15 | constexpr Opx1x0Instruction(u8 op0) : Instruction(0b0100) {
16 | SetOp0(op0);
17 | }
18 | };
19 | }
20 |
21 | #include "load_register_literal/base.hpp"
22 | #include "load_store_register_offset/base.hpp"
23 | #include "load_store_register_unscaled_immediate/base.hpp"
24 | #include "load_store_register_unsigned_immediate/base.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_register_literal/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "util/math/sign_extend.hpp"
5 |
6 | namespace exl::armv8::inst::impl::opx1x0 {
7 |
8 | struct LoadRegisterLiteral : public Opx1x0Instruction {
9 |
10 | static constexpr u8 Op0 = 0b0001;
11 | static constexpr u8 Op2 = 0b00;
12 |
13 | ACCESSOR(Opc, 30, 31);
14 | ACCESSOR(V, 26);
15 | ACCESSOR(Imm19, 5, 24);
16 | ACCESSOR(Rt, 0, 5);
17 |
18 | constexpr LoadRegisterLiteral(reg::Register rt, int imm19, u8 v, u8 opc) : Opx1x0Instruction(Op0) {
19 | SetOp0(Op0);
20 | SetOp2(Op2);
21 | SetOpc(opc);
22 | SetV(v);
23 | SetImm19(util::SignExtend(imm19));
24 | SetRt(rt.Index());
25 | }
26 | };
27 | }
28 |
29 | #include "ldr_literal.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_register_literal/ldr_literal.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct LdrLiteral : public impl::opx1x0::LoadRegisterLiteral {
8 |
9 | static constexpr u8 V = 0b0;
10 |
11 | static constexpr u8 GetOpc(reg::Register rt) {
12 | return 0b00 | rt.Is64();
13 | }
14 |
15 | constexpr LdrLiteral(reg::Register rt, uint relative_distance) : LoadRegisterLiteral(rt, relative_distance / 4, V, GetOpc(rt)) {}
16 | };
17 |
18 | static_assert(LdrLiteral(reg::X0, 0x08).Value() == 0x58000040, "");
19 | static_assert(LdrLiteral(reg::W1, 0x10).Value() == 0x18000081, "");
20 | static_assert(LdrLiteral(reg::X2, 0x18).Value() == 0x580000C2, "");
21 | static_assert(LdrLiteral(reg::W3, 0x20).Value() == 0x18000103, "");
22 | static_assert(LdrLiteral(reg::X4, 0x28).Value() == 0x58000144, "");
23 | static_assert(LdrLiteral(reg::W5, 0x30).Value() == 0x18000185, "");
24 | static_assert(LdrLiteral(reg::X6, 0x38).Value() == 0x580001C6, "");
25 | static_assert(LdrLiteral(reg::W7, 0x40).Value() == 0x18000207, "");
26 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_offset/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::inst::impl::opx1x0 {
6 |
7 | struct LoadStoreRegisterOffset : public Opx1x0Instruction {
8 |
9 | static constexpr u8 Op0 = 0b0011;
10 | static constexpr u16 Op1 = 0;
11 | static constexpr u8 Op2 = 0b00;
12 | static constexpr u8 Op3 = 0b100000;
13 | static constexpr u8 Op4 = 0b10;
14 |
15 | ACCESSOR(Size, 30, 32);
16 | ACCESSOR(V, 26);
17 | ACCESSOR(Opc, 22, 24);
18 | ACCESSOR(Rm, 16, 21);
19 | ACCESSOR(Option, 13, 16);
20 | ACCESSOR(S, 12);
21 | ACCESSOR(Rn, 5, 10);
22 | ACCESSOR(Rt, 0, 5);
23 |
24 | constexpr LoadStoreRegisterOffset(u8 size, u8 v, u8 opc) : Opx1x0Instruction(Op0) {
25 | SetSize(size);
26 | SetOpc(opc);
27 | SetOp1(Op1);
28 | SetOp2(Op2);
29 | SetOp3(Op3);
30 | SetOp4(Op4);
31 | SetV(v);
32 | }
33 | };
34 | }
35 |
36 | #include "ldr_register_offset.hpp"
37 | #include "str_register_offset.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_offset/ldr_register_offset.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct LdrRegisterOffset : public impl::opx1x0::LoadStoreRegisterOffset {
8 |
9 | static constexpr u8 V = 0b0;
10 | static constexpr u8 Opc = 0b01;
11 |
12 | ACCESSOR(S, 12);
13 | ACCESSOR(Option, 13, 16);
14 |
15 | static constexpr u8 GetSize(reg::Register rt) {
16 | return 0b10 | rt.Is64();
17 | }
18 |
19 | /* TODO: merge */
20 | static constexpr u8 CreateOption(ExtendType extend) {
21 | switch(extend) {
22 | case ExtendType_UXTW:
23 | return 0b010;
24 | case ExtendType_LSL:
25 | return 0b011;
26 | case ExtendType_SXTW:
27 | return 0b110;
28 | case ExtendType_SXTX:
29 | return 0b111;
30 | default:
31 | /* static_assert(false, ""); */
32 | return 0b000;
33 | };
34 | }
35 |
36 | static constexpr bool CreateS(reg::Register rt, u8 amount) {
37 | if(amount == 0)
38 | return false;
39 |
40 | if(rt.Is64() && amount == 3)
41 | return true;
42 |
43 | if(rt.Is32() && amount == 2)
44 | return true;
45 |
46 | /* static_assert(false, ""); */
47 | return false;
48 | }
49 |
50 | constexpr LdrRegisterOffset(reg::Register rt, reg::Register rn, reg::Register rm, ExtendType extend = ExtendType_LSL, u8 amount = 0) : LoadStoreRegisterOffset(GetSize(rt), V, Opc){
51 | /*
52 | static_assert(rt.Is64() == rm.Is64(), "");
53 | static_assert(extend & 0b010 != 0, "");
54 | static_assert(CreateOption(extend) == rm.Is64(), "");
55 |
56 | if(rt.Is64()) {
57 | static_assert(amount == 0 || amount == 3, "");
58 | } else {
59 | static_assert(amount == 0 || amount == 2, "");
60 | }
61 | */
62 |
63 | SetSize(GetSize(rt));
64 | SetRm(rm.Index());
65 | SetOption(CreateOption(extend));
66 | SetS(CreateS(rt, amount));
67 | SetRt(rt.Index());
68 | SetRn(rn.Index());
69 | }
70 |
71 | constexpr LdrRegisterOffset(reg::Register rt, reg::Register rn, reg::Register rm, u8 amount) : LdrRegisterOffset(rt, rn, rm, ExtendType_LSL, amount) {}
72 | };
73 |
74 | static_assert(LdrRegisterOffset(reg::X0, reg::X1, reg::X2).Value() == 0xF8626820, "");
75 | static_assert(LdrRegisterOffset(reg::X3, reg::X4, reg::X5).Value() == 0xF8656883, "");
76 | static_assert(LdrRegisterOffset(reg::X6, reg::X7, reg::X8).Value() == 0xF86868E6, "");
77 |
78 | static_assert(LdrRegisterOffset(reg::X0, reg::X1, reg::W2, ExtendType_UXTW, 3).Value() == 0xF8625820, "");
79 | static_assert(LdrRegisterOffset(reg::X3, reg::X4, reg::W5, ExtendType_UXTW, 3).Value() == 0xF8655883, "");
80 | static_assert(LdrRegisterOffset(reg::X6, reg::X7, reg::W8, ExtendType_UXTW, 3).Value() == 0xF86858E6, "");
81 |
82 | static_assert(LdrRegisterOffset(reg::X0, reg::X1, reg::X2, ExtendType_SXTX, 3).Value() == 0xF862F820, "");
83 | static_assert(LdrRegisterOffset(reg::X3, reg::X4, reg::X5, ExtendType_SXTX, 3).Value() == 0xF865F883, "");
84 | static_assert(LdrRegisterOffset(reg::X6, reg::X7, reg::X8, ExtendType_SXTX, 3).Value() == 0xF868F8E6, "");
85 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_offset/str_register_offset.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct StrRegisterOffset : public impl::opx1x0::LoadStoreRegisterOffset {
8 |
9 | static constexpr u8 V = 0b0;
10 | static constexpr u8 Opc = 0b00;
11 |
12 | ACCESSOR(S, 12);
13 | ACCESSOR(Option, 13, 16);
14 |
15 | static constexpr u8 GetSize(reg::Register rt) {
16 | return 0b10 | rt.Is64();
17 | }
18 |
19 | /* TODO: merge */
20 | static constexpr u8 CreateOption(ExtendType extend) {
21 | switch(extend) {
22 | case ExtendType_UXTW:
23 | return 0b010;
24 | case ExtendType_LSL:
25 | return 0b011;
26 | case ExtendType_SXTW:
27 | return 0b110;
28 | case ExtendType_SXTX:
29 | return 0b111;
30 | default:
31 | /* static_assert(false, ""); */
32 | return 0b000;
33 | };
34 | }
35 |
36 | static constexpr bool CreateS(reg::Register rt, u8 amount) {
37 | if(amount == 0)
38 | return false;
39 |
40 | if(rt.Is64() && amount == 3)
41 | return true;
42 |
43 | if(rt.Is32() && amount == 2)
44 | return true;
45 |
46 | /* static_assert(false, ""); */
47 | return false;
48 | }
49 |
50 | constexpr StrRegisterOffset(reg::Register rt, reg::Register rn, reg::Register rm, ExtendType extend = ExtendType_LSL, u8 amount = 0) : LoadStoreRegisterOffset(GetSize(rt), V, Opc){
51 | /*
52 | static_assert(rt.Is64() == rm.Is64(), "");
53 | static_assert(extend & 0b010 != 0, "");
54 | static_assert(CreateOption(extend) == rm.Is64(), "");
55 |
56 | if(rt.Is64()) {
57 | static_assert(amount == 0 || amount == 3, "");
58 | } else {
59 | static_assert(amount == 0 || amount == 2, "");
60 | }
61 | */
62 |
63 | SetSize(GetSize(rt));
64 | SetRm(rm.Index());
65 | SetOption(CreateOption(extend));
66 | SetS(CreateS(rt, amount));
67 | SetRt(rt.Index());
68 | SetRn(rn.Index());
69 | }
70 |
71 | constexpr StrRegisterOffset(reg::Register rt, reg::Register rn, reg::Register rm, u8 amount) : StrRegisterOffset(rt, rn, rm, ExtendType_LSL, amount) {}
72 | };
73 |
74 | static_assert(StrRegisterOffset(reg::X0, reg::X1, reg::X2).Value() == 0xF8226820, "");
75 | static_assert(StrRegisterOffset(reg::X3, reg::X4, reg::X5).Value() == 0xF8256883, "");
76 | static_assert(StrRegisterOffset(reg::X6, reg::X7, reg::X8).Value() == 0xF82868E6, "");
77 |
78 | static_assert(StrRegisterOffset(reg::X0, reg::X1, reg::W2, ExtendType_UXTW, 3).Value() == 0xF8225820, "");
79 | static_assert(StrRegisterOffset(reg::X3, reg::X4, reg::W5, ExtendType_UXTW, 3).Value() == 0xF8255883, "");
80 | static_assert(StrRegisterOffset(reg::X6, reg::X7, reg::W8, ExtendType_UXTW, 3).Value() == 0xF82858E6, "");
81 |
82 | static_assert(StrRegisterOffset(reg::X0, reg::X1, reg::X2, ExtendType_SXTX, 3).Value() == 0xF822F820, "");
83 | static_assert(StrRegisterOffset(reg::X3, reg::X4, reg::X5, ExtendType_SXTX, 3).Value() == 0xF825F883, "");
84 | static_assert(StrRegisterOffset(reg::X6, reg::X7, reg::X8, ExtendType_SXTX, 3).Value() == 0xF828F8E6, "");
85 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_unscaled_immediate/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "util/math/sign_extend.hpp"
5 |
6 | namespace exl::armv8::inst::impl::opx1x0 {
7 |
8 | struct LoadStoreRegisterUnscaledImmediate : public Opx1x0Instruction {
9 |
10 | static constexpr u8 Op0 = 0b0011;
11 | static constexpr u8 Op2 = 0b00;
12 | static constexpr u8 Op3 = 0b000000;
13 | static constexpr u8 Op4 = 0b00;
14 |
15 | ACCESSOR(Size, 30, 32);
16 | ACCESSOR(V, 26);
17 | ACCESSOR(Opc, 22, 24);
18 | ACCESSOR(Imm9, 12, 21);
19 | ACCESSOR(Rn, 5, 10);
20 | ACCESSOR(Rt, 0, 5);
21 |
22 | constexpr LoadStoreRegisterUnscaledImmediate(u8 size, u8 v, u8 opc, s16 imm9, reg::Register rn, reg::Register rt) : Opx1x0Instruction(Op0) {
23 | SetOp2(Op2);
24 | SetOp3(Op3);
25 | SetOp4(Op4);
26 | SetSize(size);
27 | SetV(v);
28 | SetOpc(opc);
29 | SetImm9(util::SignExtend(imm9));
30 | SetRn(rn.Index());
31 | SetRt(rt.Index());
32 | }
33 | };
34 | }
35 |
36 | #include "ldur_unscaled_immediate.hpp"
37 | #include "stur_unscaled_immediate.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_unscaled_immediate/ldur_unscaled_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct LdurUnscaledImmediate : public impl::opx1x0::LoadStoreRegisterUnscaledImmediate {
8 |
9 | static constexpr bool V = 0b0;
10 | static constexpr u8 Opc = 0b01;
11 |
12 | static constexpr u8 GetSize(reg::Register rt) {
13 | return 0b10 | rt.Is64();
14 | }
15 |
16 | constexpr LdurUnscaledImmediate(reg::Register rt, reg::Register rn, s16 imm9 = 0) : LoadStoreRegisterUnscaledImmediate(
17 | GetSize(rt), V, Opc, imm9, rn, rt
18 | ) {}
19 | };
20 |
21 | static_assert(LdurUnscaledImmediate(reg::X0, reg::X1, -1).Value() == 0xF85FF020, "");
22 | static_assert(LdurUnscaledImmediate(reg::X2, reg::X3, 1).Value() == 0xF8401062, "");
23 | static_assert(LdurUnscaledImmediate(reg::X4, reg::X5, -2).Value() == 0xF85FE0A4, "");
24 | static_assert(LdurUnscaledImmediate(reg::X6, reg::X7, 2).Value() == 0xF84020E6, "");
25 | static_assert(LdurUnscaledImmediate(reg::X8, reg::X9, -3).Value() == 0xF85FD128, "");
26 | static_assert(LdurUnscaledImmediate(reg::X10, reg::X11, -4).Value() == 0xF85FC16A, "");
27 | static_assert(LdurUnscaledImmediate(reg::X12, reg::X13, 2).Value() == 0xF84021AC, "");
28 | static_assert(LdurUnscaledImmediate(reg::X14, reg::X15, -4).Value() == 0xF85FC1EE, "");
29 | static_assert(LdurUnscaledImmediate(reg::W16, reg::X17, 20).Value() == 0xB8414230, "");
30 | static_assert(LdurUnscaledImmediate(reg::W18, reg::X19, -52).Value() == 0xB85CC272, "");
31 | static_assert(LdurUnscaledImmediate(reg::W20, reg::X21, 41).Value() == 0xB84292B4, "");
32 | static_assert(LdurUnscaledImmediate(reg::W22, reg::X23, -75).Value() == 0xB85B52F6, "");
33 | static_assert(LdurUnscaledImmediate(reg::W24, reg::X25, 95).Value() == 0xB845F338, "");
34 | static_assert(LdurUnscaledImmediate(reg::W26, reg::X27, -69).Value() == 0xB85BB37A, "");
35 | static_assert(LdurUnscaledImmediate(reg::W28, reg::X29, -1).Value() == 0xB85FF3BC, "");
36 | static_assert(LdurUnscaledImmediate(reg::X30, reg::SP, -5).Value() == 0xF85FB3FE, "");
37 | static_assert(LdurUnscaledImmediate(reg::LR, reg::SP, -5).Value() == 0xF85FB3FE, "");
38 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_unscaled_immediate/stur_unscaled_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct SturUnscaledImmediate : public impl::opx1x0::LoadStoreRegisterUnscaledImmediate {
8 |
9 | static constexpr bool V = 0b0;
10 | static constexpr u8 Opc = 0b00;
11 |
12 | static constexpr u8 GetSize(reg::Register rt) {
13 | return 0b10 | rt.Is64();
14 | }
15 |
16 | constexpr SturUnscaledImmediate(reg::Register rt, reg::Register rn, u16 imm12 = 0) : LoadStoreRegisterUnscaledImmediate(
17 | GetSize(rt), V, Opc, imm12, rn, rt
18 | ) {}
19 | };
20 |
21 | static_assert(SturUnscaledImmediate(reg::X0, reg::X1, -1).Value() == 0xF81FF020, "");
22 | static_assert(SturUnscaledImmediate(reg::X2, reg::X3, 1).Value() == 0xF8001062, "");
23 | static_assert(SturUnscaledImmediate(reg::X4, reg::X5, -2).Value() == 0xF81FE0A4, "");
24 | static_assert(SturUnscaledImmediate(reg::X6, reg::X7, 2).Value() == 0xF80020E6, "");
25 | static_assert(SturUnscaledImmediate(reg::X8, reg::X9, -3).Value() == 0xF81FD128, "");
26 | static_assert(SturUnscaledImmediate(reg::X10, reg::X11, -4).Value() == 0xF81FC16A, "");
27 | static_assert(SturUnscaledImmediate(reg::X12, reg::X13, 2).Value() == 0xF80021AC, "");
28 | static_assert(SturUnscaledImmediate(reg::X14, reg::X15, -4).Value() == 0xF81FC1EE, "");
29 | static_assert(SturUnscaledImmediate(reg::W16, reg::X17, 20).Value() == 0xB8014230, "");
30 | static_assert(SturUnscaledImmediate(reg::W18, reg::X19, -52).Value() == 0xB81CC272, "");
31 | static_assert(SturUnscaledImmediate(reg::W20, reg::X21, 41).Value() == 0xB80292B4, "");
32 | static_assert(SturUnscaledImmediate(reg::W22, reg::X23, -75).Value() == 0xB81B52F6, "");
33 | static_assert(SturUnscaledImmediate(reg::W24, reg::X25, 95).Value() == 0xB805F338, "");
34 | static_assert(SturUnscaledImmediate(reg::W26, reg::X27, -69).Value() == 0xB81BB37A, "");
35 | static_assert(SturUnscaledImmediate(reg::W28, reg::X29, -1).Value() == 0xB81FF3BC, "");
36 | static_assert(SturUnscaledImmediate(reg::X30, reg::SP, -5).Value() == 0xF81FB3FE, "");
37 | static_assert(SturUnscaledImmediate(reg::LR, reg::SP, -5).Value() == 0xF81FB3FE, "");
38 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_unsigned_immediate/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "util/math/sign_extend.hpp"
5 |
6 | namespace exl::armv8::inst::impl::opx1x0 {
7 |
8 | struct LoadStoreRegisterUnsignedImmediate : public Opx1x0Instruction {
9 |
10 | static constexpr u8 Op0 = 0b0011;
11 | static constexpr u8 Op2 = 0b10;
12 |
13 | ACCESSOR(Size, 30, 32);
14 | ACCESSOR(V, 26);
15 | ACCESSOR(Opc, 22, 24);
16 | ACCESSOR(Imm12, 10, 22);
17 | ACCESSOR(Rn, 5, 10);
18 | ACCESSOR(Rt, 0, 5);
19 |
20 | constexpr LoadStoreRegisterUnsignedImmediate(u8 size, u8 v, u8 opc, u16 imm12, reg::Register rn, reg::Register rt) : Opx1x0Instruction(Op0) {
21 | SetOp2(Op2);
22 | SetSize(size);
23 | SetV(v);
24 | SetOpc(opc);
25 | SetImm12(imm12);
26 | SetRn(rn.Index());
27 | SetRt(rt.Index());
28 | }
29 | };
30 | }
31 |
32 | #include "ldr_register_immediate.hpp"
33 | #include "str_register_immediate.hpp"
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_unsigned_immediate/ldr_register_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct LdrRegisterImmediate : public impl::opx1x0::LoadStoreRegisterUnsignedImmediate {
8 |
9 | static constexpr bool V = 0b0;
10 | static constexpr u8 Opc = 0b01;
11 |
12 | static constexpr u8 GetSize(reg::Register rt) {
13 | return 0b10 | rt.Is64();
14 | }
15 |
16 | constexpr LdrRegisterImmediate(reg::Register rt, reg::Register rn, u16 imm12 = 0) : LoadStoreRegisterUnsignedImmediate(
17 | GetSize(rt), V, Opc, imm12, rn, rt
18 | ) {}
19 | };
20 |
21 | static_assert(LdrRegisterImmediate(reg::X0, reg::X1).Value() == 0xF9400020, "");
22 | static_assert(LdrRegisterImmediate(reg::X2, reg::X3).Value() == 0xF9400062, "");
23 | static_assert(LdrRegisterImmediate(reg::X4, reg::X5).Value() == 0xF94000A4, "");
24 | static_assert(LdrRegisterImmediate(reg::X6, reg::X7).Value() == 0xF94000E6, "");
25 | static_assert(LdrRegisterImmediate(reg::X8, reg::X9).Value() == 0xF9400128, "");
26 | static_assert(LdrRegisterImmediate(reg::X10, reg::X11, 1).Value() == 0xF940056A, "");
27 | static_assert(LdrRegisterImmediate(reg::X12, reg::X13, 2).Value() == 0xF94009AC, "");
28 | static_assert(LdrRegisterImmediate(reg::X14, reg::X15, 4).Value() == 0xF94011EE, "");
29 | static_assert(LdrRegisterImmediate(reg::W16, reg::X17, 20).Value() == 0xB9405230, "");
30 | static_assert(LdrRegisterImmediate(reg::W18, reg::X19, 52).Value() == 0xB940D272, "");
31 | static_assert(LdrRegisterImmediate(reg::W20, reg::X21, 41).Value() == 0xB940A6B4, "");
32 | static_assert(LdrRegisterImmediate(reg::W22, reg::X23, 75).Value() == 0xB9412EF6, "");
33 | static_assert(LdrRegisterImmediate(reg::W24, reg::X25, 95).Value() == 0xB9417F38, "");
34 | static_assert(LdrRegisterImmediate(reg::W26, reg::X27, 69).Value() == 0xB941177A, "");
35 | static_assert(LdrRegisterImmediate(reg::W28, reg::X29).Value() == 0xB94003BC, "");
36 | static_assert(LdrRegisterImmediate(reg::X30, reg::SP).Value() == 0xF94003FE, "");
37 | static_assert(LdrRegisterImmediate(reg::LR, reg::SP).Value() == 0xF94003FE, "");
38 | }
--------------------------------------------------------------------------------
/source/lib/armv8/instructions/opx1x0/load_store_register_unsigned_immediate/str_register_immediate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 |
5 | namespace exl::armv8::inst {
6 |
7 | struct StrRegisterImmediate : public impl::opx1x0::LoadStoreRegisterUnsignedImmediate {
8 |
9 | static constexpr bool V = 0b0;
10 | static constexpr u8 Opc = 0b00;
11 |
12 | static constexpr u8 GetSize(reg::Register rt) {
13 | return 0b10 | rt.Is64();
14 | }
15 |
16 | constexpr StrRegisterImmediate(reg::Register rt, reg::Register rn, u16 imm12 = 0) : LoadStoreRegisterUnsignedImmediate(
17 | GetSize(rt), V, Opc, imm12, rn, rt
18 | ) {}
19 | };
20 |
21 | static_assert(StrRegisterImmediate(reg::X0, reg::X1).Value() == 0xF9000020, "");
22 | static_assert(StrRegisterImmediate(reg::X2, reg::X3).Value() == 0xF9000062, "");
23 | static_assert(StrRegisterImmediate(reg::X4, reg::X5).Value() == 0xF90000A4, "");
24 | static_assert(StrRegisterImmediate(reg::X6, reg::X7).Value() == 0xF90000E6, "");
25 | static_assert(StrRegisterImmediate(reg::X8, reg::X9).Value() == 0xF9000128, "");
26 | static_assert(StrRegisterImmediate(reg::X10, reg::X11, 1).Value() == 0xF900056A, "");
27 | static_assert(StrRegisterImmediate(reg::X12, reg::X13, 2).Value() == 0xF90009AC, "");
28 | static_assert(StrRegisterImmediate(reg::X14, reg::X15, 4).Value() == 0xF90011EE, "");
29 | static_assert(StrRegisterImmediate(reg::W16, reg::X17, 20).Value() == 0xB9005230, "");
30 | static_assert(StrRegisterImmediate(reg::W18, reg::X19, 52).Value() == 0xB900D272, "");
31 | static_assert(StrRegisterImmediate(reg::W20, reg::X21, 41).Value() == 0xB900A6B4, "");
32 | static_assert(StrRegisterImmediate(reg::W22, reg::X23, 75).Value() == 0xB9012EF6, "");
33 | static_assert(StrRegisterImmediate(reg::W24, reg::X25, 95).Value() == 0xB9017F38, "");
34 | static_assert(StrRegisterImmediate(reg::W26, reg::X27, 69).Value() == 0xB901177A, "");
35 | static_assert(StrRegisterImmediate(reg::W28, reg::X29).Value() == 0xB90003BC, "");
36 | static_assert(StrRegisterImmediate(reg::X30, reg::SP).Value() == 0xF90003FE, "");
37 | static_assert(StrRegisterImmediate(reg::LR, reg::SP).Value() == 0xF90003FE, "");
38 | }
--------------------------------------------------------------------------------
/source/lib/armv8/register.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::armv8::reg {
6 |
7 | enum class RegisterKind : bool {
8 | W, X
9 | };
10 |
11 | class Register {
12 | private:
13 | /* Pack members efficiently. */
14 | RegisterKind m_Kind : 1;
15 | char m_Index : 7;
16 |
17 | public:
18 | constexpr Register(RegisterKind kind, uchar index) : m_Kind(kind), m_Index(index) { }
19 |
20 | constexpr inline bool Is32() const { return m_Kind == RegisterKind::W; }
21 | constexpr inline bool Is64() const { return m_Kind == RegisterKind::X; }
22 | constexpr inline uchar Index() const { return m_Index; }
23 | };
24 | static_assert(sizeof(Register) == 1, "Register");
25 |
26 | #define REG(I) \
27 | constexpr inline Register W##I(RegisterKind::W, I); \
28 | constexpr inline Register X##I(RegisterKind::X, I);
29 |
30 | REG(0); REG(1); REG(2); REG(3); REG(4); REG(5); REG(6); REG(7); REG(8); REG(9);
31 | REG(10);REG(11);REG(12);REG(13);REG(14);REG(15);REG(16);REG(17);REG(18);REG(19);
32 | REG(20);REG(21);REG(22);REG(23);REG(24);REG(25);REG(26);REG(27);REG(28);REG(29);
33 | REG(30);
34 |
35 | constexpr inline auto LR = X30;
36 | constexpr inline auto SP = Register(RegisterKind::X, 31);
37 | constexpr inline auto None32 = Register(RegisterKind::W, -1);
38 | constexpr inline auto None64 = Register(RegisterKind::X, -1);
39 |
40 | #undef REG
41 | }
--------------------------------------------------------------------------------
/source/lib/diag/abort.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 "abort.hpp"
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | namespace exl::diag {
24 |
25 | void NORETURN NOINLINE AbortImpl(const AbortCtx & ctx) {
26 | #ifdef EXL_SUPPORTS_REBOOTPAYLOAD
27 | /* Ensure abort handler doesn't recursively abort. */
28 | static std::atomic recurse_guard;
29 | auto recursing = recurse_guard.exchange(true);
30 |
31 | if(!recursing && util::IsSocErista()) {
32 | /* Reboot to abort payload.*/
33 | AbortToPayload(ctx);
34 | } else
35 | #endif
36 | {
37 | /* We have no capability of chainloading payloads on mariko. */
38 | /* Don't have a great solution for this at the moment, just data abort. */
39 | /* TODO: maybe write to a file? custom fatal program? */
40 | register u64 addr __asm__("x27") = 0x6969696969696969;
41 | register u64 val __asm__("x28") = ctx.m_Result;
42 | while (true) {
43 | __asm__ __volatile__ (
44 | "str %[val], [%[addr]]"
45 | :
46 | : [val]"r"(val), [addr]"r"(addr)
47 | );
48 | }
49 | }
50 |
51 | UNREACHABLE;
52 | }
53 |
54 | #define ABORT_WITH_VALUE(v) \
55 | { \
56 | exl::diag::AbortCtx ctx {.m_Result = (Result)v}; \
57 | AbortImpl(ctx); \
58 | }
59 |
60 | /* TODO: better assert/abort support. */
61 | void NORETURN NOINLINE AssertionFailureImpl(const char *file, int line, const char *func, const char *expr, u64 value, const char *format, ...) ABORT_WITH_VALUE(value)
62 | void NORETURN NOINLINE AssertionFailureImpl(const char *file, int line, const char *func, const char *expr, u64 value) ABORT_WITH_VALUE(value)
63 | void NORETURN NOINLINE AbortImpl(const char *file, int line, const char *func, const char *expr, u64 value, const char *format, ...) ABORT_WITH_VALUE(value)
64 | void NORETURN NOINLINE AbortImpl(const char *file, int line, const char *func, const char *expr, u64 value) ABORT_WITH_VALUE(value)
65 |
66 | };
67 |
68 | /* C shim for libnx */
69 | extern "C" NORETURN void exl_abort(Result r)
70 | ABORT_WITH_VALUE(r)
--------------------------------------------------------------------------------
/source/lib/diag/abort.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 "common.hpp"
20 |
21 | namespace exl::diag {
22 |
23 | struct AbortCtx {
24 | Result m_Result;
25 | };
26 |
27 | void NORETURN NOINLINE AssertionFailureImpl(const char *file, int line, const char *func, const char *expr, u64 value, const char *format, ...) __attribute__((format(printf, 6, 7)));
28 | void NORETURN NOINLINE AssertionFailureImpl(const char *file, int line, const char *func, const char *expr, u64 value);
29 |
30 | void NORETURN NOINLINE AbortImpl(const char *file, int line, const char *func, const char *expr, u64 value, const char *format, ...) __attribute__((format(printf, 6, 7)));
31 | void NORETURN NOINLINE AbortImpl(const char *file, int line, const char *func, const char *expr, u64 value);
32 |
33 | void NORETURN NOINLINE AbortImpl(const AbortCtx&);
34 | };
--------------------------------------------------------------------------------
/source/lib/diag/assert.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 "abort.hpp"
20 | #include "program/setting.hpp"
21 |
22 | #ifdef EXL_DEBUG
23 |
24 | #define EXL_CALL_ASSERT_FAIL_IMPL(cond, ...) ::exl::diag::AssertionFailureImpl(__FILE__, __LINE__, __PRETTY_FUNCTION__, cond, 0, ## __VA_ARGS__)
25 | #define EXL_CALL_ABORT_IMPL(cond, value, ...) ::exl::diag::AbortImpl(__FILE__, __LINE__, __PRETTY_FUNCTION__, cond, value, ## __VA_ARGS__)
26 |
27 | #define EXL_UNREACHABLE_DEFAULT_CASE() default: EXL_CALL_ABORT_IMPL("Unreachable default case entered", 0)
28 | #define EXL_ABORT(value, ...) EXL_CALL_ABORT_IMPL("", value, ## __VA_ARGS__)
29 |
30 | #else
31 |
32 | #define EXL_CALL_ASSERT_FAIL_IMPL(cond, ...) ::exl::diag::AssertionFailureImpl(NULL, 0, NULL, NULL, 0, ## __VA_ARGS__)
33 | #define EXL_CALL_ABORT_IMPL(cond, value, ...) ::exl::diag::AbortImpl(NULL, 0, NULL, NULL, value, ## __VA_ARGS__)
34 |
35 | #define EXL_UNREACHABLE_DEFAULT_CASE() default: EXL_CALL_ABORT_IMPL(NULL, 0)
36 | #define EXL_ABORT(value, ...) EXL_CALL_ABORT_IMPL("", value, ## __VA_ARGS__)
37 |
38 | #endif
39 |
40 |
41 | #define EXL_ASSERT_IMPL(expr, ...) \
42 | ({ \
43 | if (!(static_cast(expr))) { \
44 | EXL_CALL_ASSERT_FAIL_IMPL(#expr, ## __VA_ARGS__); \
45 | } \
46 | })
47 |
48 | #define EXL_ASSERT(expr, ...) EXL_ASSERT_IMPL(expr, ## __VA_ARGS__)
--------------------------------------------------------------------------------
/source/lib/diag/reboot_to_payload.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "abort.hpp"
4 |
5 | namespace exl::diag {
6 | void NORETURN AbortToPayload(const AbortCtx&);
7 | };
--------------------------------------------------------------------------------
/source/lib/hook/base.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "util/func_ptrs.hpp"
4 | #include "util/modules.hpp"
5 |
6 | #include
7 | #include
8 |
9 | #include "nx64/impl.hpp"
10 |
11 | #define _HOOK_STATIC_CALLBACK_ASSERT() \
12 | static_assert(!std::is_member_function_pointer_v>, "Callback method must be static!")
13 |
14 | namespace exl::hook {
15 |
16 | /* TODO: 32-bit. */
17 | namespace arch = nx64;
18 |
19 | namespace {
20 | using Entrypoint = util::GenericFuncPtr;
21 | };
22 |
23 | inline void NORETURN CallTargetEntrypoint(void* x0, void* x1) {
24 | auto entrypoint = reinterpret_cast(util::modules::GetTargetStart());
25 | entrypoint(x0, x1);
26 | UNREACHABLE;
27 | }
28 |
29 | inline void Initialize() {
30 | arch::Initialize();
31 | }
32 |
33 | template
34 | CbFunc Hook(InFunc hook, CbFunc callback, bool do_trampoline = false) {
35 |
36 | /* Workaround for being unable to cast member functions. */
37 | /* Probably some horrible UB here? */
38 | uintptr_t hookp;
39 | uintptr_t callbackp;
40 | std::memcpy(&hookp, &hook, sizeof(hookp));
41 | std::memcpy(&callbackp, &callback, sizeof(callbackp));
42 |
43 | uintptr_t trampoline = arch::Hook(hookp, callbackp, do_trampoline);
44 |
45 | /* Workaround for being unable to cast member functions. */
46 | /* Probably some horrible UB here? */
47 | CbFunc ret;
48 | std::memcpy(&ret, &trampoline, sizeof(trampoline));
49 |
50 | return ret;
51 | }
52 |
53 | using InlineCtx = arch::InlineCtx;
54 | using InlineCallback = void (*)(InlineCtx*);
55 |
56 | inline void HookInline(uintptr_t hook, InlineCallback callback) {
57 | arch::HookInline(hook, reinterpret_cast(callback));
58 | }
59 | }
--------------------------------------------------------------------------------
/source/lib/hook/class.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "deprecated.hpp"
4 |
5 | #define _HOOK_CLASS_VALUE_SUFFIX ClassValue
6 | #define _HOOK_CLASS_SUFFIX Class
7 |
8 | #define _MAKE_CLASS_VALUE_NAME(name) \
9 | APPEND(name, _HOOK_CLASS_VALUE_SUFFIX)
10 |
11 | #define _MAKE_CLASS_NAME(name) \
12 | APPEND(name, _HOOK_CLASS_SUFFIX)
13 |
14 | #define _MAKE_CLASS_WRAP(type, name) \
15 | using _MAKE_CLASS_VALUE_NAME(name) = type; \
16 | class _MAKE_CLASS_NAME(name) : public _MAKE_CLASS_VALUE_NAME(name) {
17 |
18 | #define MAKE_CLASS_HOOK(ret, type, name, args, body) \
19 | _MAKE_CLASS_WRAP(type, name) \
20 | public: \
21 | _MAKE_HOOK_IMPL(ret, name, args, body) \
22 | }
23 |
24 |
25 | #define MAKE_CLASS_HOOK_T(ret, type, name, args, body) \
26 | _MAKE_CLASS_WRAP(type, name) \
27 | public: \
28 | using FuncValue = ret (type::*) args; \
29 | inline static FuncValue _HOOK_IMPL_NAME(name); \
30 | _MAKE_HOOK_T_IMPL(ret, name, args, body) \
31 | }
32 |
33 | #define _HOOK_CLASS_HOOK_NAME(name) \
34 | _MAKE_CLASS_NAME(name)::_HOOK_NAME(name)
35 |
36 | #define _HOOK_CLASS_FUNC(name, funcname) \
37 | _MAKE_CLASS_VALUE_NAME(name)::funcname
38 |
39 | #define _HOOK_CLASS_T_NAME(name) \
40 | _MAKE_CLASS_NAME(name)::_HOOK_IMPL_NAME(name)
41 |
42 | #define INJECT_CLASS_HOOK(funcname, name) \
43 | hook::Hook(&_HOOK_CLASS_FUNC(name, funcname), &_HOOK_CLASS_HOOK_NAME(name));
44 | #define INJECT_CLASS_HOOK_T(funcname, name) \
45 | _HOOK_CLASS_T_NAME(name) = hook::Hook(&_HOOK_CLASS_FUNC(name, funcname), &_HOOK_CLASS_HOOK_NAME(name), true);
46 |
47 | #define CPP_IMPL(...) \
48 | (this->*impl)(__VA_ARGS__)
49 |
--------------------------------------------------------------------------------
/source/lib/hook/deprecated.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #define _IMPL_SUFFIX Impl
4 | #define _HOOK_SUFFIX Hook
5 |
6 | #define _HOOK_IMPL_NAME(name) \
7 | APPEND(name, _IMPL_SUFFIX)
8 | #define _HOOK_NAME(name) \
9 | APPEND(name, _HOOK_SUFFIX)
10 |
11 | #define _MAKE_HOOK_IMPL(ret, name, args, body) \
12 | ret _HOOK_NAME(name) args { \
13 | body \
14 | }
15 |
16 | #define MAKE_HOOK(ret, name, args, body) \
17 | static _MAKE_HOOK_IMPL(ret, name, args, body)
18 |
19 | #define _MAKE_HOOK_T_IMPL(ret, name, args, body) \
20 | ret _HOOK_NAME(name) args { \
21 | const auto impl = _HOOK_IMPL_NAME(name); \
22 | body \
23 | }
24 |
25 | #define MAKE_HOOK_T(ret, name, args, body) \
26 | static ret (*_HOOK_IMPL_NAME(name)) args; \
27 | static _MAKE_HOOK_T_IMPL(ret, name, args, body)
28 |
29 | #define INJECT_HOOK(offset, name) \
30 | exl::hook::Hook(offset, _HOOK_NAME(name));
31 | #define INJECT_HOOK_T(offset, name) \
32 | _HOOK_IMPL_NAME(name) = exl::hook::Hook(offset, _HOOK_NAME(name), true);
33 |
--------------------------------------------------------------------------------
/source/lib/hook/inline.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "base.hpp"
6 |
7 | #define HOOK_DEFINE_INLINE(name) \
8 | struct name : public ::exl::hook::impl::InlineHook
9 |
10 | namespace exl::hook::impl {
11 |
12 | template
13 | struct InlineHook {
14 |
15 | template
16 | using CallbackFuncPtr = decltype(&T::Callback);
17 |
18 | static ALWAYS_INLINE void InstallAtOffset(ptrdiff_t address) {
19 | _HOOK_STATIC_CALLBACK_ASSERT();
20 |
21 | hook::HookInline(util::modules::GetTargetStart() + address, Derived::Callback);
22 | }
23 |
24 | static ALWAYS_INLINE void InstallAtPtr(uintptr_t ptr) {
25 | _HOOK_STATIC_CALLBACK_ASSERT();
26 |
27 | hook::HookInline(ptr, Derived::Callback);
28 | }
29 |
30 | };
31 | }
--------------------------------------------------------------------------------
/source/lib/hook/nx64/impl.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 | #include "inline_impl.hpp"
5 |
6 | namespace exl::hook::nx64 {
7 |
8 | void Initialize();
9 |
10 | uintptr_t Hook(uintptr_t hook, uintptr_t callback, bool do_trampoline = false);
11 | void HookInline(uintptr_t hook, uintptr_t callback);
12 | }
--------------------------------------------------------------------------------
/source/lib/hook/nx64/inline_asm.s:
--------------------------------------------------------------------------------
1 | .macro CODE_BEGIN name
2 | .section .text.\name, "ax", %progbits
3 | .global \name
4 | .type \name, %function
5 | .align 2
6 | .cfi_startproc
7 | \name:
8 | .endm
9 |
10 | .macro CODE_END
11 | .cfi_endproc
12 | .endm
13 |
14 | /* Size of stack to reserve for the context. Adjust this along with CtxStackSize in inline_impl.cpp */
15 | .set CTX_STACK_SIZE, 0x100
16 |
17 | /* For these macros, LR is deliberately not backed up as that's handled by the entry's entrypoint. */
18 | .macro armBackupRegisters
19 | sub sp, sp, CTX_STACK_SIZE
20 | stp x0, x1, [sp, #0x00]
21 | stp x2, x3, [sp, #0x10]
22 | stp x4, x5, [sp, #0x20]
23 | stp x6, x7, [sp, #0x30]
24 | stp x8, x9, [sp, #0x40]
25 | stp x10, x11, [sp, #0x50]
26 | stp x12, x13, [sp, #0x60]
27 | stp x14, x15, [sp, #0x70]
28 | stp x16, x17, [sp, #0x80]
29 | stp x18, x19, [sp, #0x90]
30 | stp x20, x21, [sp, #0xA0]
31 | stp x22, x23, [sp, #0xB0]
32 | stp x24, x25, [sp, #0xC0]
33 | stp x26, x27, [sp, #0xD0]
34 | stp x28, x29, [sp, #0xE0]
35 | .endm
36 |
37 | .macro armRecoverRegisters
38 | ldp x0, x1, [sp, #0x00]
39 | ldp x2, x3, [sp, #0x10]
40 | ldp x4, x5, [sp, #0x20]
41 | ldp x6, x7, [sp, #0x30]
42 | ldp x8, x9, [sp, #0x40]
43 | ldp x10, x11, [sp, #0x50]
44 | ldp x12, x13, [sp, #0x60]
45 | ldp x14, x15, [sp, #0x70]
46 | ldp x16, x17, [sp, #0x80]
47 | ldp x18, x19, [sp, #0x90]
48 | ldp x20, x21, [sp, #0xA0]
49 | ldp x22, x23, [sp, #0xB0]
50 | ldp x24, x25, [sp, #0xC0]
51 | ldp x26, x27, [sp, #0xD0]
52 | ldp x28, x29, [sp, #0xE0]
53 | add sp, sp, CTX_STACK_SIZE
54 | .endm
55 |
56 | CODE_BEGIN exl_inline_hook_impl
57 | armBackupRegisters
58 |
59 | /* LR contains a pointer to the called entry here. */
60 | mov x19, lr
61 |
62 | /* Load inline context for the first argument of the callback. */
63 | mov x0, sp
64 | /* Load then call callback. */
65 | ldr x20, [x19, #0x8]
66 | blr x20
67 |
68 | /* Keep a hold of entry pointer before restoring all the registers. */
69 | mov lr, x19
70 |
71 | armRecoverRegisters
72 |
73 | /* Return to entry. */
74 | ret
75 | CODE_END
--------------------------------------------------------------------------------
/source/lib/hook/nx64/inline_impl.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include
3 |
4 | #include
5 | #include
6 |
7 | #include "impl.hpp"
8 |
9 | namespace exl::hook::nx64 {
10 |
11 | /* Size of stack to reserve for the context. Adjust this along with CTX_STACK_SIZE in inline_asm.s */
12 | static constexpr int CtxStackSize = 0x100;
13 |
14 | namespace reg = exl::armv8::reg;
15 | namespace inst = exl::armv8::inst;
16 |
17 | struct Entry {
18 | std::array m_CbEntry;
19 | uintptr_t m_Callback;
20 | };
21 |
22 | static constexpr size_t InlinePoolCount = setting::InlinePoolSize / sizeof(Entry);
23 |
24 | JIT_CREATE(s_InlineHookJit, setting::InlinePoolSize);
25 | static size_t s_EntryIndex = 0;
26 |
27 | extern "C" {
28 | extern char exl_inline_hook_impl;
29 | }
30 |
31 | static uintptr_t GetImpl() {
32 | return reinterpret_cast(&exl_inline_hook_impl);
33 | }
34 |
35 | static const Entry* GetEntryRx() {
36 | return reinterpret_cast(s_InlineHookJit.GetRo());
37 | }
38 |
39 | static Entry* GetEntryRw() {
40 | return reinterpret_cast(s_InlineHookJit.GetRw());
41 | }
42 |
43 | void InitializeInline() {
44 | s_InlineHookJit.Initialize();
45 | }
46 |
47 | void HookInline(uintptr_t hook, uintptr_t callback) {
48 | /* Ensure enough space in the pool. */
49 | if(s_EntryIndex >= InlinePoolCount)
50 | EXL_ABORT(result::HookTrampolineAllocFail);
51 |
52 | /* Grab entry from pool. */
53 | auto entryRx = &GetEntryRx()[s_EntryIndex];
54 | auto entryRw = &GetEntryRw()[s_EntryIndex];
55 | s_EntryIndex++;
56 |
57 | /* Get pointer to entry's entrypoint. */
58 | uintptr_t entryCb = reinterpret_cast(&entryRx->m_CbEntry);
59 | /* Hook to call into the entry's entrypoint. Assign trampoline to be used by impl. */
60 | auto trampoline = Hook(hook, entryCb, true);
61 | /* Offset of LR before SP is moved. */
62 | static constexpr int lrBackupOffset = int(offsetof(InlineCtx, m_Gpr.m_Lr)) - CtxStackSize;
63 | static_assert(lrBackupOffset == -0x10, "InlineCtx is not ABI compatible.");
64 |
65 | /* Construct entrypoint instructions. */
66 | auto impl = GetImpl();
67 | entryRw->m_CbEntry = {
68 | /* Backup LR register to stack, as we are about to trash it. */
69 | inst::SturUnscaledImmediate(reg::LR, reg::SP, lrBackupOffset),
70 | /* Branch to implementation. */
71 | inst::BranchLink(impl - (entryCb + (1 * sizeof(armv8::InstType)))),
72 | /* Restore proper LR. */
73 | inst::LdurUnscaledImmediate(reg::LR, reg::SP, lrBackupOffset),
74 | /* Branch to trampoline. */
75 | inst::Branch(trampoline - (entryCb + (3 * sizeof(armv8::InstType))))
76 | };
77 | /* Assign callback to be called to be used by impl. */
78 | entryRw->m_Callback = callback;
79 |
80 | /* Finally, flush caches to have RX region to be consistent. */
81 | s_InlineHookJit.Flush();
82 | }
83 | }
--------------------------------------------------------------------------------
/source/lib/hook/nx64/inline_impl.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace exl::hook::nx64 {
4 |
5 | union GpRegister {
6 | u64 X;
7 | u32 W;
8 | };
9 |
10 | union GpRegisters {
11 | GpRegister m_Gp[31];
12 | struct {
13 | GpRegister _Gp[29];
14 | GpRegister m_Fp;
15 | GpRegister m_Lr;
16 | };
17 | };
18 |
19 | namespace impl {
20 | /* This type is only unioned with GpRegisters, so this is valid. */
21 | struct GpRegisterAccessorImpl {
22 | GpRegisters& Get() {
23 | return *reinterpret_cast(this);
24 | }
25 | };
26 |
27 | struct GpRegisterAccessor64 : public GpRegisterAccessorImpl {
28 | u64& operator[](int index)
29 | {
30 | return Get().m_Gp[index].X;
31 | }
32 | };
33 |
34 | struct GpRegisterAccessor32 : public GpRegisterAccessorImpl {
35 | u32& operator[](int index)
36 | {
37 | return Get().m_Gp[index].W;
38 | }
39 | };
40 | }
41 |
42 | struct InlineCtx {
43 | union {
44 | /* Accessors are union'd with the gprs so that they can be accessed directly. */
45 | impl::GpRegisterAccessor64 X;
46 | impl::GpRegisterAccessor32 W;
47 | GpRegisters m_Gpr;
48 | };
49 | };
50 |
51 | void InitializeInline();
52 | }
--------------------------------------------------------------------------------
/source/lib/hook/replace.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 | #include "util/func_ptrs.hpp"
5 |
6 | #define HOOK_DEFINE_REPLACE(name) \
7 | struct name : public ::exl::hook::impl::ReplaceHook
8 |
9 | namespace exl::hook::impl {
10 |
11 | template
12 | struct ReplaceHook {
13 |
14 | template
15 | using CallbackFuncPtr = decltype(&T::Callback);
16 |
17 | static ALWAYS_INLINE void InstallAtOffset(ptrdiff_t address) {
18 | _HOOK_STATIC_CALLBACK_ASSERT();
19 |
20 | hook::Hook(util::modules::GetTargetStart() + address, Derived::Callback);
21 | }
22 |
23 | template
24 | static ALWAYS_INLINE void InstallAtFuncPtr(util::GenericFuncPtr ptr) {
25 | _HOOK_STATIC_CALLBACK_ASSERT();
26 |
27 | using ArgFuncPtr = decltype(ptr);
28 | static_assert(std::is_same_v>, "Argument pointer type must match callback type!");
29 |
30 | hook::Hook(ptr, Derived::Callback);
31 | }
32 |
33 | static ALWAYS_INLINE void InstallAtPtr(uintptr_t ptr) {
34 | _HOOK_STATIC_CALLBACK_ASSERT();
35 |
36 | hook::Hook(ptr, Derived::Callback);
37 | }
38 | };
39 |
40 | }
--------------------------------------------------------------------------------
/source/lib/hook/trampoline.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "base.hpp"
4 | #include "util/func_ptrs.hpp"
5 | #include
6 |
7 | #define HOOK_DEFINE_TRAMPOLINE(name) \
8 | struct name : public ::exl::hook::impl::TrampolineHook
9 |
10 | namespace exl::hook::impl {
11 |
12 | template
13 | class TrampolineHook {
14 |
15 | template
16 | using CallbackFuncPtr = decltype(&T::Callback);
17 |
18 | static ALWAYS_INLINE auto& OrigRef() {
19 | _HOOK_STATIC_CALLBACK_ASSERT();
20 |
21 | static constinit CallbackFuncPtr<> s_FnPtr = nullptr;
22 |
23 | return s_FnPtr;
24 | }
25 |
26 | public:
27 | template
28 | static ALWAYS_INLINE decltype(auto) Orig(Args &&... args) {
29 | _HOOK_STATIC_CALLBACK_ASSERT();
30 |
31 | return OrigRef()(std::forward(args)...);
32 | }
33 |
34 | static ALWAYS_INLINE void InstallAtOffset(ptrdiff_t address) {
35 | _HOOK_STATIC_CALLBACK_ASSERT();
36 |
37 | OrigRef() = hook::Hook(util::modules::GetTargetStart() + address, Derived::Callback, true);
38 | }
39 |
40 | template
41 | static ALWAYS_INLINE void InstallAtFuncPtr(util::GenericFuncPtr ptr) {
42 | _HOOK_STATIC_CALLBACK_ASSERT();
43 | using ArgFuncPtr = decltype(ptr);
44 |
45 | static_assert(std::is_same_v>, "Argument pointer type must match callback type!");
46 |
47 | OrigRef() = hook::Hook(ptr, Derived::Callback, true);
48 | }
49 |
50 | static ALWAYS_INLINE void InstallAtPtr(uintptr_t ptr) {
51 | _HOOK_STATIC_CALLBACK_ASSERT();
52 |
53 | OrigRef() = hook::Hook(ptr, Derived::Callback, true);
54 | }
55 | };
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/source/lib/init/crt0.s:
--------------------------------------------------------------------------------
1 | .section ".text.crt0","ax"
2 |
3 | .macro FROM_MOD0 register_num, offset
4 | ldr w\register_num, [x24, #\offset]
5 | sxtw x\register_num, w\register_num
6 | add x\register_num, x\register_num, x24
7 | .endm
8 |
9 | .macro FUNC_RELATIVE_ASLR name, register_num, symbol
10 | .word \symbol - .
11 | \name:
12 | ldr w\register_num, [x30]
13 | sxtw x\register_num, w\register_num
14 | add x\register_num, x\register_num, x30
15 | .endm
16 |
17 | .global __module_start
18 | __module_start:
19 | b entrypoint
20 | .word __nx_mod0 - __module_start
21 |
22 | .align 4
23 | .ascii "~~exlaunch uwu~~"
24 |
25 | entrypoint:
26 | // Arguments on NSO entry:
27 | // x0=zero | x1=main thread handle
28 | // Arguments on NRO entry (homebrew ABI):
29 | // x0=ptr to env context | x1=UINT64_MAX (-1 aka 0xFFFFFFFFFFFFFFFF)
30 | // Arguments on user-mode exception entry:
31 | // x0=excpt type (non-zero) | x1=ptr to excpt context
32 |
33 | // Detect and handle user-mode exceptions first:
34 | // if (x0 != 0 && x1 != UINT64_MAX) exl_exception_entry();
35 | cmp x0, #0
36 | ccmn x1, #1, #4, ne // 4 = Z
37 | beq get_module_info
38 | b exl_exception_entry
39 |
40 | get_module_info:
41 | // Get start of our module
42 | bl __get_module_start_shim
43 | FUNC_RELATIVE_ASLR __get_module_start_shim, 23, __module_start
44 |
45 | // Get location of MOD0
46 | mov x24, xzr
47 | ldr w24, [x23, #4]
48 | add x24, x24, x23
49 |
50 | // Get BSS regions from MOD0
51 | FROM_MOD0 8, 0x8
52 | FROM_MOD0 9, 0xC
53 |
54 | bssclr_start:
55 | // Calculate BSS address/size
56 | sub x9, x9, x8 // calculate BSS size
57 | add x9, x9, #7 // round up to 8
58 | bic x9, x9, #7 // ^
59 |
60 | // Clear the BSS in 8-byte units
61 | bss_loop:
62 | subs w9, w9, #8
63 | str xzr, [x8], #8
64 | bne bss_loop
65 |
66 | // Preserve registers across function calls
67 | mov x25, x0 // entrypoint argument 0
68 | mov x26, x1 // entrypoint argument 1
69 |
70 | // Parse ELF .dynamic section (which applies relocations to our module)
71 | mov x0, x23
72 | FROM_MOD0 1, 0x4
73 | bl exl_dynamic
74 |
75 | mov x0, x25
76 | mov x1, x26
77 | b exl_entrypoint_init
78 |
79 | // failsafe
80 | .word 0xdeadbeef
81 |
82 |
83 | .section ".rodata.mod0","a"
84 |
85 | .hidden exl_nx_module_runtime
86 |
87 | .align 2
88 | __nx_mod0:
89 | .ascii "MOD0"
90 | .word __dynamic_start__ - __nx_mod0
91 | .word __bss_start__ - __nx_mod0
92 | .word __bss_end__ - __nx_mod0
93 | .word __eh_frame_hdr_start__ - __nx_mod0
94 | .word __eh_frame_hdr_end__ - __nx_mod0
95 | .word exl_nx_module_runtime - __nx_mod0
--------------------------------------------------------------------------------
/source/lib/init/header.s:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/masagrator/Xenoblade2DynFPS/b55f63d5bef95d6a21d8f778f55b07391c2be500/source/lib/init/header.s
--------------------------------------------------------------------------------
/source/lib/init/init.cpp:
--------------------------------------------------------------------------------
1 | #include "common.hpp"
2 |
3 | #include "program/setting.hpp"
4 |
5 | extern "C" {
6 | /* These magic symbols are provided by the linker. */
7 | extern void (*__preinit_array_start []) (void) __attribute__((weak));
8 | extern void (*__preinit_array_end []) (void) __attribute__((weak));
9 | extern void (*__init_array_start []) (void) __attribute__((weak));
10 | extern void (*__init_array_end []) (void) __attribute__((weak));
11 |
12 | /* Exported by program. */
13 | extern void exl_main(void*, void*);
14 | /* Optionally exported by program. */
15 | __attribute__((weak)) extern void exl_init();
16 |
17 | #ifdef EXL_USE_FAKEHEAP
18 |
19 | char __fake_heap[exl::setting::HeapSize];
20 |
21 | void __init_heap() {
22 | extern char * fake_heap_start;
23 | extern char * fake_heap_end;
24 |
25 | fake_heap_start = __fake_heap;
26 | fake_heap_end = __fake_heap + exl::setting::HeapSize;
27 | }
28 |
29 | #endif
30 |
31 | void __init_array(void) {
32 | size_t count;
33 | size_t i;
34 |
35 | count = __preinit_array_end - __preinit_array_start;
36 | for (i = 0; i < count; i++)
37 | __preinit_array_start[i] ();
38 |
39 | count = __init_array_end - __init_array_start;
40 | for (i = 0; i < count; i++)
41 | __init_array_start[i] ();
42 | }
43 |
44 | /* Called when loaded as a module with RTLD. */
45 | void exl_module_init() {
46 | #ifdef EXL_USE_FAKEHEAP
47 | __init_heap();
48 | #endif
49 | exl_init();
50 | __init_array();
51 | exl_main(NULL, NULL);
52 | }
53 |
54 | /* Called when loaded as the entrypoint of the process, like RTLD. */
55 | void exl_entrypoint_init(void* x0, void* x1) {
56 | #ifdef EXL_USE_FAKEHEAP
57 | __init_heap();
58 | #endif
59 | exl_init();
60 | __init_array();
61 | exl_main(x0, x1);
62 | }
63 |
64 | void exl_module_fini(void) {}
65 |
66 | }
67 |
68 | #include
69 | #include
70 | #include
71 |
72 | extern "C" void exl_init() {
73 | /* Getting the SOC type in an application context is more effort than it's worth. */
74 | #ifndef EXL_AS_MODULE
75 | exl::util::impl::InitSocType();
76 | #endif
77 | exl::util::impl::InitMemLayout();
78 | virtmemSetup();
79 | exl::patch::impl::InitPatcherImpl();
80 | }
--------------------------------------------------------------------------------
/source/lib/libsetting.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "types.h"
4 |
5 | #define EXL_LOAD_KIND_KIP 0
6 | #define EXL_LOAD_KIND_ASRTLD 1
7 | #define EXL_LOAD_KIND_MODULE 2
8 |
9 | namespace exl::setting {
10 |
11 | enum class LoadKind {
12 | Kip,
13 | AsRtld,
14 | Module,
15 | };
16 | /* Ensure consistency with preprocessor constants. */
17 | static_assert(EXL_LOAD_KIND_KIP == static_cast(LoadKind::Kip), "");
18 | static_assert(EXL_LOAD_KIND_ASRTLD == static_cast(LoadKind::AsRtld), "");
19 | static_assert(EXL_LOAD_KIND_MODULE == static_cast(LoadKind::Module), "");
20 |
21 | static constexpr LoadKind SelfLoadKind = LoadKind::EXL_LOAD_KIND;
22 | static constexpr u64 ProgramId = EXL_PROGRAM_ID;
23 | };
24 |
25 | #ifndef EXL_LOAD_KIND_ENUM
26 | #error "EXL_LOAD_KIND_ENUM not defined!"
27 | #elif EXL_LOAD_KIND_ENUM == EXL_LOAD_KIND_KIP
28 | #define EXL_AS_KIP
29 | #elif EXL_LOAD_KIND_ENUM == EXL_LOAD_KIND_ASRTLD
30 | #define EXL_AS_RTLD
31 | #elif EXL_LOAD_KIND_ENUM == EXL_LOAD_KIND_MODULE
32 | #define EXL_AS_MODULE
33 | #endif
--------------------------------------------------------------------------------
/source/lib/module.cpp:
--------------------------------------------------------------------------------
1 | #include "common.hpp"
2 |
3 | #include "program/setting.hpp"
4 |
5 | struct ModuleName {
6 | int unknown;
7 | int name_length;
8 | char name[EXL_MODULE_NAME_LEN + 1];
9 | };
10 |
11 | __attribute__((section(".nx-module-name")))
12 | const ModuleName s_ModuleName = {.unknown = 0, .name_length = EXL_MODULE_NAME_LEN, .name = EXL_MODULE_NAME};
--------------------------------------------------------------------------------
/source/lib/nx/abort.h:
--------------------------------------------------------------------------------
1 | // Provide a C shim for aborting in libnx
2 | #pragma once
3 |
4 | #include "types.h"
5 |
6 | extern void exl_abort(Result);
7 |
8 | #ifndef R_ABORT_UNLESS
9 | #define R_ABORT_UNLESS(r) \
10 | { \
11 | Result _tmp_r = r; \
12 | if(R_FAILED(_tmp_r)) \
13 | exl_abort(_tmp_r); \
14 | }
15 |
16 | #elif
17 | #error "Included abort.h outside of libnx!"
18 | #endif
--------------------------------------------------------------------------------
/source/lib/nx/arm/cache.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file cache.h
3 | * @brief AArch64 cache operations.
4 | * @author plutoo
5 | * @copyright libnx Authors
6 | */
7 | #pragma once
8 | #include "../types.h"
9 |
10 | /**
11 | * @brief Performs a data cache flush on the specified buffer.
12 | * @param addr Address of the buffer.
13 | * @param size Size of the buffer, in bytes.
14 | * @remarks Cache flush is defined as Clean + Invalidate.
15 | * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
16 | */
17 | void armDCacheFlush(void* addr, size_t size);
18 |
19 | /**
20 | * @brief Performs a data cache clean on the specified buffer.
21 | * @param addr Address of the buffer.
22 | * @param size Size of the buffer, in bytes.
23 | * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
24 | */
25 | void armDCacheClean(void* addr, size_t size);
26 |
27 | /**
28 | * @brief Performs an instruction cache invalidation clean on the specified buffer.
29 | * @param addr Address of the buffer.
30 | * @param size Size of the buffer, in bytes.
31 | * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
32 | */
33 | void armICacheInvalidate(void* addr, size_t size);
34 |
35 | /**
36 | * @brief Performs a data cache zeroing operation on the specified buffer.
37 | * @param addr Address of the buffer.
38 | * @param size Size of the buffer, in bytes.
39 | * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
40 | */
41 | void armDCacheZero(void* addr, size_t size);
42 |
--------------------------------------------------------------------------------
/source/lib/nx/arm/cache.s:
--------------------------------------------------------------------------------
1 | .macro CODE_BEGIN name
2 | .section .text.\name, "ax", %progbits
3 | .global \name
4 | .hidden \name
5 | .type \name, %function
6 | .align 2
7 | .cfi_startproc
8 | \name:
9 | .endm
10 |
11 | .macro CODE_END
12 | .cfi_endproc
13 | .endm
14 |
15 | CODE_BEGIN armDCacheFlush
16 | add x1, x1, x0
17 | mrs x8, CTR_EL0
18 | lsr x8, x8, #16
19 | and x8, x8, #0xf
20 | mov x9, #4
21 | lsl x9, x9, x8
22 | sub x10, x9, #1
23 | bic x8, x0, x10
24 | mov x10, x1
25 |
26 | armDCacheFlush_L0:
27 | dc civac, x8
28 | add x8, x8, x9
29 | cmp x8, x10
30 | bcc armDCacheFlush_L0
31 |
32 | dsb sy
33 | ret
34 | CODE_END
35 |
36 | CODE_BEGIN armDCacheClean
37 | add x1, x1, x0
38 | mrs x8, CTR_EL0
39 | lsr x8, x8, #16
40 | and x8, x8, #0xf
41 | mov x9, #4
42 | lsl x9, x9, x8
43 | sub x10, x9, #1
44 | bic x8, x0, x10
45 | mov x10, x1
46 |
47 | armDCacheClean_L0:
48 | dc cvac, x8
49 | add x8, x8, x9
50 | cmp x8, x10
51 | bcc armDCacheClean_L0
52 |
53 | dsb sy
54 | ret
55 | CODE_END
56 |
57 | CODE_BEGIN armICacheInvalidate
58 | add x1, x1, x0
59 | mrs x8, CTR_EL0
60 | and x8, x8, #0xf
61 | mov x9, #4
62 | lsl x9, x9, x8
63 | sub x10, x9, #1
64 | bic x8, x0, x10
65 | mov x10, x1
66 |
67 | armICacheInvalidate_L0:
68 | ic ivau, x8
69 | add x8, x8, x9
70 | cmp x8, x10
71 | bcc armICacheInvalidate_L0
72 |
73 | dsb sy
74 | ret
75 | CODE_END
76 |
77 | CODE_BEGIN armDCacheZero
78 | add x1, x1, x0
79 | mrs x8, CTR_EL0
80 | lsr x8, x8, #16
81 | and x8, x8, #0xf
82 | mov x9, #4
83 | lsl x9, x9, x8
84 | sub x10, x9, #1
85 | bic x8, x0, x10
86 | mov x10, x1
87 |
88 | armDCacheZero_L0:
89 | dc zva, x8
90 | add x8, x8, x9
91 | cmp x8, x10
92 | bcc armDCacheZero_L0
93 |
94 | dsb sy
95 | ret
96 | CODE_END
97 |
--------------------------------------------------------------------------------
/source/lib/nx/arm/tls.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file tls.h
3 | * @brief AArch64 thread local storage.
4 | * @author plutoo
5 | * @copyright libnx Authors
6 | */
7 | #pragma once
8 | #include "../types.h"
9 |
10 | /**
11 | * @brief Gets the thread local storage buffer.
12 | * @return The thread local storage buffer.
13 | */
14 | static inline void* armGetTls(void) {
15 | void* ret;
16 | __asm__ ("mrs %x[data], tpidrro_el0" : [data] "=r" (ret));
17 | return ret;
18 | }
--------------------------------------------------------------------------------
/source/lib/nx/kernel/virtmem.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file virtmem.h
3 | * @brief Virtual memory mapping utilities
4 | * @author plutoo
5 | * @copyright libnx Authors
6 | */
7 | #pragma once
8 | #include "../types.h"
9 |
10 | /// Address space reservation type (see \ref virtmemAddReservation)
11 | typedef struct VirtmemReservation VirtmemReservation;
12 |
13 | /// Locks the virtual memory manager mutex.
14 | void virtmemLock(void);
15 |
16 | /// Unlocks the virtual memory manager mutex.
17 | void virtmemUnlock(void);
18 |
19 | /**
20 | * @brief Finds a random slice of free general purpose address space.
21 | * @param size Desired size of the slice (rounded up to page alignment).
22 | * @param guard_size Desired size of the unmapped guard areas surrounding the slice (rounded up to page alignment).
23 | * @return Pointer to the slice of address space, or NULL on failure.
24 | * @note The virtual memory manager mutex must be held during the find-and-map process (see \ref virtmemLock and \ref virtmemUnlock).
25 | */
26 | void* virtmemFindAslr(size_t size, size_t guard_size);
27 |
28 | /**
29 | * @brief Finds a random slice of free stack address space.
30 | * @param size Desired size of the slice (rounded up to page alignment).
31 | * @param guard_size Desired size of the unmapped guard areas surrounding the slice (rounded up to page alignment).
32 | * @return Pointer to the slice of address space, or NULL on failure.
33 | * @note The virtual memory manager mutex must be held during the find-and-map process (see \ref virtmemLock and \ref virtmemUnlock).
34 | */
35 | void* virtmemFindStack(size_t size, size_t guard_size);
36 |
37 | /**
38 | * @brief Finds a random slice of free code memory address space.
39 | * @param size Desired size of the slice (rounded up to page alignment).
40 | * @param guard_size Desired size of the unmapped guard areas surrounding the slice (rounded up to page alignment).
41 | * @return Pointer to the slice of address space, or NULL on failure.
42 | * @note The virtual memory manager mutex must be held during the find-and-map process (see \ref virtmemLock and \ref virtmemUnlock).
43 | */
44 | void* virtmemFindCodeMemory(size_t size, size_t guard_size);
45 |
46 | /**
47 | * @brief Reserves a range of memory address space.
48 | * @param mem Pointer to the address space slice.
49 | * @param size Size of the slice.
50 | * @return Pointer to a reservation object, or NULL on failure.
51 | * @remark This function is intended to be used in lieu of a memory map operation when the memory won't be mapped straight away.
52 | * @note The virtual memory manager mutex must be held during the find-and-reserve process (see \ref virtmemLock and \ref virtmemUnlock).
53 | */
54 | VirtmemReservation* virtmemAddReservation(void* mem, size_t size);
55 |
56 | /**
57 | * @brief Releases a memory address space reservation.
58 | * @param rv Reservation to release.
59 | * @note The virtual memory manager mutex must be held before calling this function (see \ref virtmemLock and \ref virtmemUnlock).
60 | */
61 | void virtmemRemoveReservation(VirtmemReservation* rv);
62 |
63 | void virtmemSetup();
--------------------------------------------------------------------------------
/source/lib/nx/nx.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifdef __cplusplus
4 | extern "C" {
5 | #endif
6 |
7 | #include "result.h"
8 | #include "types.h"
9 | #include "smc.h"
10 |
11 | #include "arm/cache.h"
12 | #include "arm/tls.h"
13 |
14 | #include "kernel/svc.h"
15 | #include "kernel/virtmem.h"
16 |
17 | #ifdef __cplusplus
18 | }
19 | #endif
--------------------------------------------------------------------------------
/source/lib/nx/smc.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file smc.h
3 | * @brief Wrappers for secure monitor calls.
4 | * @copyright libnx Authors
5 | */
6 | #pragma once
7 | #include "types.h"
8 | #include "kernel/svc.h"
9 |
10 | typedef enum {
11 | SplConfigItem_DisableProgramVerification = 1,
12 | SplConfigItem_DramId = 2,
13 | SplConfigItem_SecurityEngineIrqNumber = 3,
14 | SplConfigItem_Version = 4,
15 | SplConfigItem_HardwareType = 5,
16 | SplConfigItem_IsRetail = 6,
17 | SplConfigItem_IsRecoveryBoot = 7,
18 | SplConfigItem_DeviceId = 8,
19 | SplConfigItem_BootReason = 9,
20 | SplConfigItem_MemoryArrange = 10,
21 | SplConfigItem_IsDebugMode = 11,
22 | SplConfigItem_KernelMemoryConfiguration = 12,
23 | SplConfigItem_IsChargerHiZModeEnabled = 13,
24 | SplConfigItem_IsKiosk = 14,
25 | SplConfigItem_NewHardwareType = 15,
26 | SplConfigItem_NewKeyGeneration = 16,
27 | SplConfigItem_Package2Hash = 17,
28 |
29 | SplConfigItem_ExosphereVersion = 65000,
30 | SplConfigItem_NeedsReboot = 65001,
31 | SplConfigItem_NeedsShutdown = 65002,
32 | SplConfigItem_ExosphereVerHash = 65003,
33 | SplConfigItem_HasRcmBugPatch = 65004,
34 | } SplConfigItem;
35 |
36 | typedef enum {
37 | SplHardwareType_Icosa = 0,
38 | SplHardwareType_Copper = 1,
39 | SplHardwareType_Hoag = 2,
40 | SplHardwareType_Iowa = 3,
41 | SplHardwareType_Calcio = 4,
42 | SplHardwareType_Aula = 5,
43 | } SplHardwareType;
44 |
45 | Result smcGetConfig(SplConfigItem config_item, u64 *out_config);
46 |
47 | void smcRebootToRcm(void);
48 | void smcRebootToIramPayload(void);
49 | void smcPerformShutdown(void);
50 |
51 | Result smcCopyToIram(uintptr_t iram_addr, const void *src_addr, u32 size);
52 | Result smcCopyFromIram(void *dst_addr, uintptr_t iram_addr, u32 size);
53 |
54 | Result smcReadWriteRegister(u32 phys_addr, u32 value, u32 mask);
55 |
56 | Result smcGenerateRandomBytes(void *dst, u32 size);
57 | Result smcGenerateRandomU64(u64* out);
--------------------------------------------------------------------------------
/source/lib/nx/types.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file switch/types.h
3 | * @brief Various system types.
4 | * @copyright libnx Authors
5 | */
6 | #pragma once
7 |
8 | #include "../../types.h"
9 |
10 | #include
11 | #include
12 |
13 | #include "result.h"
14 |
15 | /// The maximum value of a u64.
16 | #define U64_MAX UINT64_MAX
17 |
18 | #ifndef SSIZE_MAX
19 | #ifdef SIZE_MAX
20 | #define SSIZE_MAX ((SIZE_MAX) >> 1)
21 | #endif
22 | #endif
23 |
24 | typedef u32 Handle; ///< Kernel object handle.
25 | typedef u32 Result; ///< Function error code result type.
26 | typedef void (*ThreadFunc)(void *); ///< Thread entrypoint function.
27 | typedef void (*VoidFn)(void); ///< Function without arguments nor return value.
28 |
29 | /// Creates a bitmask from a bit number.
30 | #ifndef BIT
31 | #define BIT(n) (1U<<(n))
32 | #endif
33 |
34 | /// Packs a struct so that it won't include padding bytes.
35 | #ifndef PACKED
36 | #define PACKED __attribute__((packed))
37 | #endif
38 |
39 | /// Marks a function as not returning, for the purposes of compiler optimization.
40 | #ifndef NORETURN
41 | #define NORETURN __attribute__((noreturn))
42 | #endif
43 |
44 | /// Performs a dummy operation on the specified argument in order to silence compiler warnings about unused arguments.
45 | #ifndef IGNORE_ARG
46 | #define IGNORE_ARG(x) (void)(x)
47 | #endif
48 |
49 | /// Flags a function as deprecated.
50 | #ifndef DEPRECATED
51 | #ifndef LIBNX_NO_DEPRECATION
52 | #define DEPRECATED __attribute__ ((deprecated))
53 | #else
54 | #define DEPRECATED
55 | #endif
56 | #endif
57 |
58 | /// Flags a function as (always) inline.
59 | #define NX_INLINE __attribute__((always_inline)) static inline
60 |
61 | /// Flags a function as constexpr in C++14 and above; or as (always) inline otherwise.
62 | #if __cplusplus >= 201402L
63 | #define NX_CONSTEXPR NX_INLINE constexpr
64 | #else
65 | #define NX_CONSTEXPR NX_INLINE
66 | #endif
67 |
68 | /// Invalid handle.
69 | #define INVALID_HANDLE ((Handle) 0)
70 |
--------------------------------------------------------------------------------
/source/lib/patch/code_patcher.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../armv8.hpp"
4 | #include "stream_patcher.hpp"
5 |
6 | namespace exl::patch {
7 |
8 | namespace inst = armv8::inst;
9 |
10 | class CodePatcher : public StreamPatcher {
11 | private:
12 | using InstBitSet = armv8::InstBitSet;
13 |
14 | public:
15 | inline CodePatcher(uintptr_t start) : StreamPatcher(start) {}
16 |
17 | inline void WriteInst(InstBitSet inst) {
18 | Write(inst);
19 | }
20 |
21 | /* Special case branches as they are relative to the current position. */
22 | inline void BranchInstRel(ptrdiff_t address) {
23 | WriteInst(inst::Branch(address));
24 | }
25 | inline void BranchLinkInstRel(ptrdiff_t address) {
26 | WriteInst(inst::BranchLink(address));
27 | }
28 |
29 | /* Address relative to the base (Ro). */
30 | inline void BranchInst(uintptr_t address) {
31 | BranchInstRel(RelativeAddressFromBase(address));
32 | }
33 | inline void BranchLinkInst(uintptr_t address) {
34 | BranchLinkInstRel(RelativeAddressFromBase(address));
35 | }
36 | /* Absolute addresses. */
37 | inline void BranchInst(void* ptr) {
38 | BranchInstRel(RelativeAddressFromPointer(ptr));
39 | }
40 | inline void BranchLinkInst(void* ptr) {
41 | BranchLinkInstRel(RelativeAddressFromPointer(ptr));
42 | }
43 | };
44 | }
--------------------------------------------------------------------------------
/source/lib/patch/patcher_impl.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace exl::patch {
9 |
10 | namespace impl {
11 | inline util::TypedStorage s_Storage;
12 |
13 | inline const util::RwPages& GetRwPages() { return util::GetReference(s_Storage); }
14 |
15 | inline void InitPatcherImpl() {
16 | auto& mod = util::GetMainModuleInfo();
17 | auto start = mod.m_Total.m_Start;
18 | auto size = mod.m_Rodata.GetEnd() - start;
19 | util::ConstructAt(s_Storage, start, size);
20 | }
21 | };
22 |
23 | class PatcherImpl {
24 | protected:
25 | const util::RwPages& m_Pages;
26 |
27 | template
28 | inline T& At(const uintptr_t offset) {
29 | /* Get address for the object. */
30 | uintptr_t start = RwFromAddr(offset);
31 |
32 | auto ptr = reinterpret_cast(start);
33 | return *ptr;
34 | }
35 |
36 | public:
37 | inline PatcherImpl() : m_Pages(impl::GetRwPages()) {}
38 |
39 | inline uintptr_t AddrFromRo(uintptr_t ro) const { return ro - m_Pages.GetRo(); }
40 | inline uintptr_t AddrFromRw(uintptr_t rw) const { return rw - m_Pages.GetRw(); }
41 |
42 | inline uintptr_t RoFromAddr(uintptr_t addr) const { return m_Pages.GetRo() + addr; }
43 | inline uintptr_t RwFromAddr(uintptr_t addr) const { return m_Pages.GetRw() + addr; }
44 |
45 | inline ptrdiff_t AddrFromRoPointer(void* ptr) const { return AddrFromRo(reinterpret_cast(ptr)); }
46 | inline ptrdiff_t AddrFromRwPointer(void* ptr) const { return AddrFromRw(reinterpret_cast(ptr)); }
47 | };
48 | }
--------------------------------------------------------------------------------
/source/lib/patch/random_access_patcher.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::patch {
6 |
7 | class RandomAccessPatcher : public PatcherImpl {
8 | private:
9 | uintptr_t m_Lowest = UINTPTR_MAX;
10 | uintptr_t m_Highest = UINTPTR_MAX;
11 |
12 | inline bool IsUnset() {
13 | return m_Lowest == UINTPTR_MAX || m_Highest == UINTPTR_MAX;
14 | }
15 |
16 | inline void SetLowest(uintptr_t addr) {
17 | if(IsUnset())
18 | m_Lowest = addr;
19 | else
20 | m_Lowest = std::min(m_Lowest, addr);
21 | }
22 |
23 | inline void SetHighest(uintptr_t addr) {
24 | if(IsUnset())
25 | m_Highest = addr;
26 | else
27 | m_Highest = std::max(m_Highest, addr);
28 | }
29 |
30 | public:
31 | template
32 | void Write(const uintptr_t addr, T value) {
33 | /* Update low/high. */
34 | SetLowest(addr);
35 | SetHighest(addr + sizeof(T));
36 |
37 | /* Write value. */
38 | At(addr) = value;
39 | }
40 |
41 | template
42 | T& Read(const uintptr_t index) {
43 | return At(index);
44 | }
45 |
46 | inline void Flush() {
47 | /* Nothing to flush if there was nothing done. */
48 | if(IsUnset())
49 | return;
50 |
51 | /* Get actual pointers. */
52 | void* ro = (void*) RoFromAddr(m_Lowest);
53 | void* rw = (void*) RwFromAddr(m_Lowest);
54 | auto size = m_Highest - m_Lowest;
55 |
56 | /* Flush data/instructions. */
57 | armDCacheFlush(rw, size);
58 | armICacheInvalidate(ro, size);
59 | }
60 |
61 | inline ~RandomAccessPatcher() {
62 | /* Flush out any changes we made. */
63 | Flush();
64 | }
65 | };
66 | };
--------------------------------------------------------------------------------
/source/lib/patch/stream_patcher.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "patcher_impl.hpp"
4 | #include "armv8.hpp"
5 |
6 | namespace exl::patch {
7 |
8 | class StreamPatcher : public PatcherImpl {
9 | private:
10 | constexpr bool DoneNothing() const {
11 | return m_Start == m_Current;
12 | }
13 |
14 | inline void Flush() {
15 | /* Nothing to flush if there was nothing done. */
16 | if(DoneNothing())
17 | return;
18 |
19 | /* Get actual pointers. */
20 | void* ro = (void*) RoFromAddr(m_Start);
21 | void* rw = (void*) RwFromAddr(m_Start);
22 | auto size = m_Current - m_Start;
23 |
24 | /* Flush data/instructions. */
25 | armDCacheFlush(rw, size);
26 | armICacheInvalidate(ro, size);
27 |
28 | /* Reset start position. */
29 | m_Start += size;
30 | }
31 |
32 | protected:
33 | inline ptrdiff_t RelativeAddressFromBase(uintptr_t address) const {
34 | return address - m_Current;
35 | }
36 | inline ptrdiff_t RelativeAddressFromPointer(void* ptr) const {
37 | return AddrFromRoPointer(ptr) - m_Current;
38 | }
39 |
40 | uintptr_t m_Start;
41 | uintptr_t m_Current;
42 |
43 | public:
44 | inline StreamPatcher(uintptr_t start) : m_Start(start), m_Current(start) {}
45 |
46 | template
47 | inline void Write(T v) {
48 | At(m_Current) = v;
49 |
50 | m_Current += sizeof(T);
51 | }
52 |
53 | /* Flush current data then move to a new address. */
54 | inline void SeekRel(uintptr_t address) {
55 | /* Don't need to do anything if the address doesn't need to change. */
56 | if(address == 0)
57 | return;
58 |
59 | /* Get address relative to base. */
60 | address += m_Current;
61 |
62 | /* Flush what we've already done. */
63 | Flush();
64 |
65 | /* Reset to the position to the provided address. */
66 | m_Start = address;
67 | m_Current = address;
68 | }
69 |
70 | /* Address relative to the base (Ro). */
71 | inline void Seek(uintptr_t address) {
72 | SeekRel(RelativeAddressFromBase(address));
73 | }
74 | /* Absolute address. */
75 | inline void Seek(void* ptr) {
76 | SeekRel(RelativeAddressFromPointer(ptr));
77 | }
78 |
79 | inline ~StreamPatcher() {
80 | Flush();
81 | }
82 | };
83 | }
--------------------------------------------------------------------------------
/source/lib/reloc/elf.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | /* TODO: 32-bit support? */
6 | typedef Elf64_Addr Elf_Addr;
7 | typedef Elf64_Rel Elf_Rel;
8 | typedef Elf64_Rela Elf_Rela;
9 | typedef Elf64_Dyn Elf_Dyn;
10 | typedef Elf64_Sym Elf_Sym;
11 | typedef Elf64_Xword Elf_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_IS_REL_ABSOLUTE(type) \
22 | type == R_AARCH64_ABS32 || type == R_AARCH64_ABS64
23 |
--------------------------------------------------------------------------------
/source/lib/reloc/rtld.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 |
5 | #include "elf.hpp"
6 |
7 | #include "rtld/ModuleHeader.hpp"
8 | #include "rtld/ModuleList.hpp"
9 | #include "rtld/ModuleObject.hpp"
10 |
11 | using namespace rtld;
12 |
13 | typedef Elf_Addr (*lookup_global_t)(const char *);
14 |
15 | extern "C" void __rtld_runtime_resolve(void);
16 |
17 | namespace rtld {
18 | Elf_Addr lookup_global_auto(const char *name);
19 | }
20 |
21 | namespace nn::ro::detail {
22 | extern ModuleObjectList g_pManualLoadList;
23 | extern ModuleObjectList g_pAutoLoadList;
24 | extern bool g_RoDebugFlag;
25 | extern lookup_global_t g_LookupGlobalManualFunctionPointer;
26 | };
27 |
28 | namespace ro = nn::ro::detail;
29 |
30 | extern "C" {
31 | extern rtld::ModuleObject exl_nx_module_runtime;
32 | };
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2019 Thog
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6 |
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/ModuleHeader.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | const uint32_t MOD0_MAGIC = 0x30444F4D;
6 |
7 | namespace rtld {
8 | struct ModuleHeader {
9 | uint32_t magic;
10 | uint32_t dynamic_offset;
11 | uint32_t bss_start_offset;
12 | uint32_t bss_end_offset;
13 | uint32_t unwind_start_offset;
14 | uint32_t unwind_end_offset;
15 | uint32_t module_object_offset;
16 | };
17 | }
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/ModuleList.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ModuleObject.hpp"
4 |
5 | namespace rtld {
6 |
7 | struct ModuleObjectList {
8 | ModuleObject *front;
9 | ModuleObject *back;
10 |
11 | class Iterator;
12 |
13 | Iterator begin() { return Iterator(this->back, false); }
14 |
15 | Iterator end() { return Iterator((ModuleObject *)this, false); }
16 |
17 | Iterator rbegin() { return Iterator(this->front, true); }
18 |
19 | Iterator rend() { return Iterator((ModuleObject *)this, true); }
20 |
21 | class Iterator {
22 | public:
23 | Iterator(ModuleObject *pModule, bool reverted)
24 | : m_pCurrentModule(pModule), m_Reverted(reverted) {}
25 |
26 | Iterator &operator=(ModuleObject *pModule) {
27 | m_pCurrentModule = pModule;
28 | return *this;
29 | }
30 |
31 | Iterator &operator++() {
32 | if (m_pCurrentModule) {
33 | m_pCurrentModule = m_Reverted ? m_pCurrentModule->next
34 | : m_pCurrentModule->prev;
35 | }
36 | return *this;
37 | }
38 |
39 | bool operator!=(const Iterator &iterator) {
40 | return m_pCurrentModule != iterator.m_pCurrentModule;
41 | }
42 |
43 | ModuleObject *operator*() { return m_pCurrentModule; }
44 |
45 | private:
46 | ModuleObject *m_pCurrentModule;
47 | bool m_Reverted;
48 | };
49 | };
50 |
51 | #ifdef __aarch64__
52 | static_assert(sizeof(ModuleObjectList) == 0x10, "ModuleObjectList isn't valid");
53 | #elif __arm__
54 | static_assert(sizeof(ModuleObjectList) == 0x8, "ModuleObjectList isn't valid");
55 | #endif
56 |
57 | } // namespace rtld
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/ModuleObject.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "../elf.hpp"
9 |
10 | namespace rtld {
11 |
12 | struct ModuleObject {
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,
20 | bool do_lazy_got_init);
21 |
22 | public:
23 | struct ModuleObject *next;
24 | struct ModuleObject *prev;
25 | union {
26 | Elf_Rel *rel;
27 | Elf_Rela *rela;
28 | void *raw;
29 | } rela_or_rel_plt;
30 | union {
31 | Elf_Rel *rel;
32 | Elf_Rela *rela;
33 | } rela_or_rel;
34 | char *module_base;
35 | Elf_Dyn *dynamic;
36 | bool is_rela;
37 | Elf_Xword rela_or_rel_plt_size;
38 | void (*dt_init)(void);
39 | void (*dt_fini)(void);
40 | uint32_t *hash_bucket;
41 | uint32_t *hash_chain;
42 | char *dynstr;
43 | Elf_Sym *dynsym;
44 | Elf_Xword dynstr_size;
45 | void **got;
46 | Elf_Xword rela_dyn_size;
47 | Elf_Xword rel_dyn_size;
48 | Elf_Xword rel_count;
49 | Elf_Xword rela_count;
50 | Elf_Xword hash_nchain_value;
51 | Elf_Xword hash_nbucket_value;
52 | void *got_stub_ptr;
53 | #ifdef __RTLD_6XX__
54 | Elf_Xword soname_idx;
55 | size_t nro_size;
56 | bool cannot_revert_symbols;
57 | #endif
58 |
59 | void Initialize(char *aslr_base, Elf_Dyn *dynamic);
60 | void Relocate();
61 | Elf_Sym *GetSymbolByName(const char *name);
62 | void ResolveSymbols(bool do_lazy_got_init);
63 | bool TryResolveSymbol(Elf_Addr *target_symbol_address, Elf_Sym *symbol);
64 | };
65 |
66 | #ifdef __RTLD_6XX__
67 | #ifdef __aarch64__
68 | static_assert(sizeof(ModuleObject) == 0xD0, "ModuleObject size isn't valid");
69 | #elif __arm__
70 | static_assert(sizeof(ModuleObject) == 0x68, "ModuleObject size isn't valid");
71 | #endif
72 | #else
73 | #ifdef __aarch64__
74 | static_assert(sizeof(ModuleObject) == 0xB8, "ModuleObject size isn't valid");
75 | #elif __arm__
76 | static_assert(sizeof(ModuleObject) == 0x5C, "ModuleObject size isn't valid");
77 | #endif
78 | #endif
79 | } // namespace rtld
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/dl_trampoline.s:
--------------------------------------------------------------------------------
1 | .section ".text", "ax"
2 |
3 | #define ip0 x16
4 | #define ip1 x17
5 |
6 | .global __rtld_runtime_resolve
7 | .type __rtld_runtime_resolve, @function
8 | __rtld_runtime_resolve:
9 | /* AArch64 we get called with:
10 | ip0 (x16) &PLTGOT[2]
11 | ip1 (x17) temp(dl resolver entry point)
12 | [sp, #0] &PLTGOT[n]
13 |
14 | What we need:
15 | x0 = calling module (ip0[-1])
16 | x1 = .rel.plt index ((ip1 - ip0 - 8) / 8)
17 | */
18 | ldr x17, [sp]
19 | str x29, [sp]
20 | stp x8, x19, [sp, #-0x10]!
21 | stp x6, x7, [sp, #-0x10]!
22 | stp x4, x5, [sp, #-0x10]!
23 | stp x2, x3, [sp, #-0x10]!
24 | stp x0, x1, [sp, #-0x10]!
25 | stp q6, q7, [sp, #-0x20]!
26 | stp q4, q5, [sp, #-0x20]!
27 | stp q2, q3, [sp, #-0x20]!
28 | stp q0, q1, [sp, #-0x20]!
29 | mov x29, sp
30 | mov x19, x17
31 | sub x1, x17, x16
32 | sub x1, x1, #8
33 | lsr x1, x1, #3
34 | ldur x0, [x16, #-8]
35 | bl __rtld_lazy_bind_symbol
36 | str x0, [x19]
37 | mov x16, x0
38 | ldp q0, q1, [sp], #0x20
39 | ldp q2, q3, [sp], #0x20
40 | ldp q4, q5, [sp], #0x20
41 | ldp q6, q7, [sp], #0x20
42 | ldp x0, x1, [sp], #0x10
43 | ldp x2, x3, [sp], #0x10
44 | ldp x4, x5, [sp], #0x10
45 | ldp x6, x7, [sp], #0x10
46 | ldp x8, x19, [sp], #0x10
47 | ldp x29, x30, [sp], #0x10
48 | br x16
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/ld.cpp:
--------------------------------------------------------------------------------
1 | #include "lib/reloc/rtld.hpp"
2 | #include "utils.hpp"
3 |
4 | Elf_Addr rtld::lookup_global_auto(const char *name) {
5 | if (ro::g_pAutoLoadList.back == (ModuleObject *)&ro::g_pAutoLoadList) {
6 | return 0;
7 | }
8 |
9 | for (ModuleObject *module : ro::g_pAutoLoadList) {
10 | Elf_Sym *symbol = module->GetSymbolByName(name);
11 | if (symbol && ELF_ST_BIND(symbol->st_info)) {
12 | return (Elf_Addr)module->module_base + symbol->st_value;
13 | }
14 | }
15 | return 0;
16 | }
17 |
18 | extern "C" Elf_Addr __rtld_lazy_bind_symbol(ModuleObject *module,
19 | size_t index) {
20 | if (module->is_rela) {
21 | Elf_Rela *entry = &module->rela_or_rel_plt.rela[index];
22 | Elf_Sym *symbol = &module->dynsym[ELF_R_SYM(entry->r_info)];
23 |
24 | Elf_Addr target_symbol_address;
25 |
26 | if (module->TryResolveSymbol(&target_symbol_address, symbol)) {
27 | if (target_symbol_address == 0) {
28 | return 0;
29 | }
30 |
31 | return target_symbol_address + entry->r_addend;
32 | } else {
33 | print_unresolved_symbol(&module->dynstr[symbol->st_name]);
34 | }
35 | } else {
36 | Elf_Rel *entry = &module->rela_or_rel_plt.rel[index];
37 | Elf_Sym *symbol = &module->dynsym[ELF_R_SYM(entry->r_info)];
38 |
39 | Elf_Addr target_symbol_address;
40 |
41 | if (module->TryResolveSymbol(&target_symbol_address, symbol)) {
42 | return target_symbol_address;
43 | } else {
44 | print_unresolved_symbol(&module->dynstr[symbol->st_name]);
45 | }
46 | }
47 |
48 | return 0;
49 | }
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.hpp"
2 |
3 | extern "C" unsigned long __rtld_elf_hash(const char *name) {
4 | unsigned long h = 0;
5 | unsigned long g;
6 |
7 | while (*name) {
8 | h = (h << 4) + *name++;
9 | if ((g = h & 0xf0000000)) h ^= g >> 24;
10 | h &= ~g;
11 | }
12 | return h;
13 | }
--------------------------------------------------------------------------------
/source/lib/reloc/rtld/utils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | extern "C" unsigned long __rtld_elf_hash(const char *name);
4 |
5 | inline void print_unresolved_symbol(const char *name) {
6 | /* TODO */
7 | }
8 |
--------------------------------------------------------------------------------
/source/lib/result.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "types.h"
4 | #include "lib/diag/assert.hpp"
5 |
6 | #define R_ABORT_UNLESS(expr) \
7 | { \
8 | Result _tmp_r = expr; \
9 | if(R_FAILED(_tmp_r)) \
10 | EXL_ABORT(_tmp_r); \
11 | }
12 |
13 | namespace exl::result {
14 | using BaseType = u32;
15 | constexpr BaseType SuccessValue = BaseType();
16 | constexpr Result MakeResult(BaseType m, BaseType d) {
17 | return MAKERESULT(m, d);
18 | }
19 |
20 | constexpr BaseType ExlModule = 252;
21 |
22 | constexpr Result Success = MakeResult(0, SuccessValue);
23 |
24 | constexpr Result HookFailed = MakeResult(ExlModule, 1);
25 | constexpr Result HookTrampolineAllocFail = MakeResult(ExlModule, 2);
26 | constexpr Result HookFixingTooManyInstructions = MakeResult(ExlModule, 3);
27 | constexpr Result FailedToFindTarget = MakeResult(ExlModule, 4);
28 | constexpr Result TooManyStaticModules = MakeResult(ExlModule, 5);
29 |
30 | }
--------------------------------------------------------------------------------
/source/lib/util/func_ptrs.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "util/ptr_path.hpp"
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | namespace exl::util {
11 |
12 | template
13 | using GenericFuncPtr = R(*)(A...);
14 |
15 | namespace member_func {
16 |
17 | template
18 | using Ptr = R(T::*)(A...);
19 |
20 | template
21 | using ConstPtr = R(T::*)(A...) const;
22 |
23 | /* Itanium ABI. */
24 | template
25 | struct RichImpl {
26 | using Generic = GenericFuncPtr;
27 |
28 | uintptr_t m_Ptr;
29 | ptrdiff_t m_Adj;
30 |
31 | constexpr bool IsVirtual() const {
32 | return (m_Ptr & 1) == 1;
33 | }
34 |
35 | constexpr Generic GetPtr(const T* ptr) const {
36 | if(IsVirtual()) {
37 | /* Cast this pointer to something we can do arithmatic on. */
38 | auto _this = reinterpret_cast(ptr);
39 | /* m_Ptr is the vtable offset to call plus 1. */
40 | ptrdiff_t offset = m_Ptr - 1;
41 | /* Get the position of the vtable pointer within the structure. */
42 | auto vtblptr = _this + m_Adj;
43 | /* Dereference to get the vtable pointer. */
44 | auto vtbl = *reinterpret_cast(vtblptr);
45 | /* Add the offset and cast to Type */
46 | return *reinterpret_cast(vtbl + offset);
47 | } else {
48 | /* m_Ptr is just the function pointer. */
49 | return reinterpret_cast(m_Ptr);
50 | }
51 | }
52 |
53 | template
54 | ALWAYS_INLINE auto Call(T* _this, Args &&... args) const {
55 | return GetPtr(_this)(_this, std::forward(args)...);
56 | }
57 | };
58 |
59 | template
60 | struct Rich;
61 |
62 | template
63 | struct Rich> : public RichImpl {};
64 |
65 | template
66 | struct Rich> : public RichImpl {};
67 |
68 | template
69 | static constexpr auto Adapt(T in) {
70 | return std::bit_cast>(in);
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/source/lib/util/math/bitset.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | namespace exl::util {
7 |
8 | template<
9 | std::integral Underlying,
10 | Underlying _Low,
11 | Underlying _High = _Low + 1>
12 | struct Mask {
13 | static constexpr Underlying Low = _Low;
14 | static constexpr Underlying High = _High;
15 | static constexpr Underlying Count = _High - _Low;
16 | static constexpr Underlying Value() {
17 | auto base = (1 << Count) - 1;
18 | return base << Low;
19 | }
20 | };
21 |
22 | template
23 | class BitSet {
24 | private:
25 | Underlying m_Data;
26 |
27 | public:
28 | constexpr inline BitSet() : m_Data() {}
29 | constexpr inline BitSet(Underlying data) : m_Data(data) {}
30 |
31 | template
32 | constexpr Underlying inline BitsOf() const {
33 | /* Take out the bits we want. */
34 | auto value = m_Data & Mask.Value();
35 | /* Shift down the bits. */
36 | return value >> Mask.Low;
37 | }
38 |
39 | template
40 | constexpr void inline SetBits(Underlying value) {
41 | /* Carve out the bits not in the mask. */
42 | m_Data &= ~Mask.Value();
43 |
44 | /* Prepare value to be written. */
45 | auto v = value << Mask.Low;
46 | v &= Mask.Value();
47 |
48 | /* OR in the bits. */
49 | m_Data |= v;
50 | }
51 |
52 | /* Wrappers to construct masks. */
53 | template
54 | constexpr Underlying inline BitsOf() const {
55 | return BitsOf>();
56 | }
57 | template
58 | constexpr void inline SetBits(Underlying value) {
59 | SetBits>(value);
60 | }
61 |
62 | /* Conversion operators. */
63 | constexpr inline Underlying& operator=(const Underlying& value) { m_Data = value; return this; }
64 |
65 | constexpr inline Underlying Value() const { return m_Data; }
66 | };
67 | };
--------------------------------------------------------------------------------
/source/lib/util/math/sign_extend.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | namespace exl::util {
7 |
8 | namespace impl {
9 | template
10 | struct SignExtender {
11 | std::intmax_t m_Extended : Bits;
12 | };
13 | }
14 |
15 | template
16 | constexpr T SignExtend(T value) {
17 | impl::SignExtender extender {value};
18 | return extender.m_Extended & ((1 << Bits) - 1);
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/source/lib/util/modules.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "sys/mem_layout.hpp"
5 |
6 | namespace exl::util::modules {
7 |
8 | inline uintptr_t GetSelfStart() {
9 | return GetSelfModuleInfo().m_Total.m_Start;
10 | }
11 |
12 | inline uintptr_t GetTargetOffset(uintptr_t offset) {
13 | return GetMainModuleInfo().m_Total.m_Start + offset;
14 | }
15 |
16 | inline uintptr_t GetTargetStart() {
17 | return GetTargetOffset(0);
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/source/lib/util/random.cpp:
--------------------------------------------------------------------------------
1 | #include "random.hpp"
2 |
3 | #include
4 |
5 | namespace exl::util {
6 |
7 | u64 GetRandomU64() {
8 | std::mt19937_64 random { svcGetSystemTick() };
9 | return random();
10 | }
11 |
12 | extern "C" u64 exl_random() {
13 | return GetRandomU64();
14 | }
15 | }
--------------------------------------------------------------------------------
/source/lib/util/random.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace exl::util {
6 |
7 | u64 GetRandomU64();
8 |
9 | /* TODO: remove along with libnx. */
10 | extern "C" u64 exl_random();
11 | }
--------------------------------------------------------------------------------
/source/lib/util/sys/cur_proc_handle.cpp:
--------------------------------------------------------------------------------
1 | #include "cur_proc_handle.hpp"
2 |
3 | #include
4 |
5 | namespace exl::util::proc_handle {
6 |
7 | namespace {
8 |
9 | Handle s_Handle = INVALID_HANDLE;
10 |
11 | void ReceiveProcessHandleThreadMain(void *session_handle_ptr) {
12 | // Convert the argument to a handle we can use.
13 | Handle session_handle = (Handle)(uintptr_t)session_handle_ptr;
14 |
15 | // Receive the request from the client thread.
16 | memset(armGetTls(), 0, 0x10);
17 | s32 idx = 0;
18 | R_ABORT_UNLESS(svcReplyAndReceive(&idx, &session_handle, 1, INVALID_HANDLE, UINT64_MAX));
19 |
20 | // Set the process handle.
21 | s_Handle = ((u32 *)armGetTls())[3];
22 |
23 | // Close the session.
24 | svcCloseHandle(session_handle);
25 |
26 | // Terminate ourselves.
27 | svcExitThread();
28 |
29 | // This code will never execute.
30 | while (true);
31 | }
32 |
33 | void GetViaIpcTrick(void) {
34 | alignas(PAGE_SIZE) u8 temp_thread_stack[0x1000];
35 |
36 | // Create a new session to transfer our process handle to ourself
37 | Handle server_handle, client_handle;
38 | R_ABORT_UNLESS(svcCreateSession(&server_handle, &client_handle, 0, 0));
39 |
40 | // Create a new thread to receive our handle.
41 | Handle thread_handle;
42 | R_ABORT_UNLESS(svcCreateThread(&thread_handle, (void*) &ReceiveProcessHandleThreadMain, (void *)(uintptr_t)server_handle, temp_thread_stack + sizeof(temp_thread_stack), 0x20, 2));
43 |
44 | // Start the new thread.
45 | R_ABORT_UNLESS(svcStartThread(thread_handle));
46 |
47 | // Send the message.
48 | static const u32 SendProcessHandleMessage[4] = { 0x00000000, 0x80000000, 0x00000002, CUR_PROCESS_HANDLE };
49 | memcpy(armGetTls(), SendProcessHandleMessage, sizeof(SendProcessHandleMessage));
50 | svcSendSyncRequest(client_handle);
51 |
52 | // Close the session handle.
53 | svcCloseHandle(client_handle);
54 |
55 | // Wait for the thread to be done.
56 | R_ABORT_UNLESS(svcWaitSynchronizationSingle(thread_handle, UINT64_MAX));
57 |
58 | // Close the thread handle.
59 | svcCloseHandle(thread_handle);
60 | }
61 |
62 | Result GetViaMesosphere() {
63 | R_TRY(svcGetInfo((u64*)&s_Handle, InfoType_MesosphereCurrentProcess, INVALID_HANDLE, 0));
64 |
65 | return result::Success;
66 | }
67 | }
68 |
69 | Handle Get() {
70 | if(s_Handle == INVALID_HANDLE) {
71 | /* Try to ask mesosphere for our process handle. */
72 | Result r = GetViaMesosphere();
73 |
74 | /* Fallback to an IPC trick if mesosphere is old/not present. */
75 | if(R_FAILED(r))
76 | GetViaIpcTrick();
77 | }
78 | return s_Handle;
79 | }
80 | };
--------------------------------------------------------------------------------
/source/lib/util/sys/cur_proc_handle.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 |
5 | namespace exl::util::proc_handle {
6 | Handle Get();
7 | }
--------------------------------------------------------------------------------
/source/lib/util/sys/jit.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 | #include "lib/util/typed_storage.hpp"
5 | #include "rw_pages.hpp"
6 |
7 | #include
8 |
9 | #define JIT_CREATE(name, size) \
10 | namespace impl::name { \
11 | __attribute__((section(".text." #name))) \
12 | alignas(PAGE_SIZE) \
13 | static const std::array s_Area {}; \
14 | } \
15 | exl::util::Jit name(std::span(impl::name::s_Area));
16 |
17 | namespace exl::util {
18 |
19 | class Jit {
20 | std::span m_Rx;
21 | util::TypedStorage m_Pages;
22 |
23 | inline RwPages& GetPages() { return util::GetReference(m_Pages); }
24 |
25 | public:
26 | constexpr Jit(std::span rx) : m_Rx(rx) {}
27 | inline void Initialize() {
28 | util::ConstructAt(m_Pages, reinterpret_cast(m_Rx.data()), m_Rx.size());
29 | }
30 |
31 | inline void Flush() {
32 | GetPages().Flush();
33 | }
34 |
35 | inline uintptr_t GetRo() { return GetPages().GetRo(); }
36 | inline uintptr_t GetRw() { return GetPages().GetRw(); }
37 | inline uintptr_t GetSize() { return GetPages().GetSize(); }
38 | };
39 | }
--------------------------------------------------------------------------------
/source/lib/util/sys/mem_layout.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 | #include
5 |
6 | namespace exl::util {
7 |
8 | struct Range {
9 | uintptr_t m_Start;
10 | size_t m_Size;
11 |
12 | constexpr uintptr_t GetEnd() const { return m_Start + m_Size; }
13 | };
14 |
15 | struct ModuleInfo {
16 | Range m_Total;
17 | Range m_Text;
18 | Range m_Rodata;
19 | Range m_Data;
20 | /* TODO: bss? */
21 | };
22 |
23 | namespace mem_layout {
24 | static constexpr int s_MaxModules = 13;
25 | inline int s_ModuleCount = -1;
26 |
27 | inline Range s_Alias;
28 | inline Range s_Heap;
29 | inline Range s_Aslr;
30 | inline Range s_Stack;
31 | }
32 |
33 | namespace impl {
34 | void InitMemLayout();
35 |
36 | namespace mem_layout {
37 | inline std::array s_ModuleInfos;
38 | }
39 | }
40 |
41 | static inline const ModuleInfo& GetModuleInfo(int index) {
42 | EXL_ASSERT(index < mem_layout::s_ModuleCount);
43 | return impl::mem_layout::s_ModuleInfos.at(index);
44 | }
45 |
46 | namespace mem_layout {
47 |
48 | #if defined EXL_AS_KIP
49 | /* TODO */
50 | #error "Not implemented..."
51 | #elif defined EXL_AS_RTLD
52 | static constexpr int s_RtldModuleIdx = 0;
53 | static constexpr int s_MainModuleIdx = 1;
54 | static constexpr int s_SelfModuleIdx = s_RtldModuleIdx;
55 |
56 | }
57 |
58 | static inline const ModuleInfo& GetRtldModuleInfo() { return GetModuleInfo(mem_layout::s_RtldModuleIdx); }
59 | static inline const ModuleInfo& GetMainModuleInfo() { return GetModuleInfo(mem_layout::s_MainModuleIdx); }
60 | #elif defined EXL_AS_MODULE
61 | static constexpr int s_RtldModuleIdx = 0;
62 | static constexpr int s_MainModuleIdx = 1;
63 |
64 | /* Decided at runtime. */
65 | inline int s_SelfModuleIdx = -1;
66 |
67 | }
68 | static inline const ModuleInfo& GetRtldModuleInfo() { return GetModuleInfo(mem_layout::s_RtldModuleIdx); }
69 | static inline const ModuleInfo& GetMainModuleInfo() { return GetModuleInfo(mem_layout::s_MainModuleIdx); }
70 | #endif
71 |
72 |
73 | static inline const ModuleInfo& GetSelfModuleInfo() { return GetModuleInfo(mem_layout::s_SelfModuleIdx); }
74 | static inline const ModuleInfo& GetSdkModuleInfo() { return GetModuleInfo(mem_layout::s_ModuleCount - 1); }
75 | };
--------------------------------------------------------------------------------
/source/lib/util/sys/rw_pages.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace exl::util {
8 |
9 | class RwPages {
10 | NON_COPYABLE(RwPages);
11 | private:
12 | struct Claim {
13 | uintptr_t m_Ro = 0;
14 | uintptr_t m_Rw = 0;
15 | size_t m_Size = 0;
16 | VirtmemReservation* m_RwReserve = nullptr;
17 |
18 | constexpr uintptr_t GetAlignedRo() const {
19 | return ALIGN_DOWN(m_Ro, PAGE_SIZE);
20 | }
21 |
22 | constexpr uintptr_t GetAlignedRw() const {
23 | return ALIGN_DOWN(m_Rw, PAGE_SIZE);
24 | }
25 |
26 | constexpr size_t GetAlignedSize() const {
27 | return ALIGN_UP(m_Size, PAGE_SIZE);
28 | }
29 |
30 | constexpr ptrdiff_t RoToOffset(uintptr_t address) const {
31 | return m_Ro - address;
32 | }
33 |
34 | constexpr ptrdiff_t RwToOffset(uintptr_t address) const {
35 | return m_Rw - address;
36 | }
37 |
38 | constexpr uintptr_t RoToRw(uintptr_t address) const {
39 | return m_Rw + RoToOffset(address);
40 | }
41 |
42 | constexpr uintptr_t RwToRo(uintptr_t address) const {
43 | return RwToOffset(address) + m_Ro;
44 | }
45 |
46 | constexpr bool InRo(uintptr_t address) const {
47 | ptrdiff_t offset = RoToOffset(address);
48 |
49 | /* Is the address before the ro region? */
50 | if(offset < 0)
51 | return false;
52 |
53 | /* Is the address after the ro region? */
54 | if(m_Size <= static_cast(offset))
55 | return false;
56 |
57 | return true;
58 | }
59 |
60 | constexpr bool InRw(uintptr_t address) const {
61 | ptrdiff_t offset = RwToOffset(address);
62 |
63 | /* Is the address before the rw region? */
64 | if(offset < 0)
65 | return false;
66 |
67 | /* Is the address after the rw region? */
68 | if(m_Size <= static_cast(offset))
69 | return false;
70 |
71 | return true;
72 | }
73 | };
74 |
75 | Claim m_Claim;
76 | bool m_Owner = true;
77 |
78 | public:
79 | RwPages(uintptr_t ro, size_t size);
80 |
81 | /* Explicitly only allow moving. */
82 | RwPages(RwPages&& other)
83 | : m_Claim(std::exchange(other.m_Claim, {})),
84 | m_Owner(std::exchange(other.m_Owner, false)) {}
85 | RwPages& operator=(RwPages&& other) {
86 | m_Claim = std::exchange(other.m_Claim, {});
87 | other.m_Owner = false;
88 | return *this;
89 | }
90 |
91 | void Flush();
92 |
93 | inline const Claim& GetClaim() const { return m_Claim; }
94 |
95 | inline uintptr_t GetRo() const { return GetClaim().m_Ro; }
96 | inline uintptr_t GetRw() const { return GetClaim().m_Rw; }
97 | inline uintptr_t GetSize() const{ return GetClaim().m_Size; }
98 |
99 | ~RwPages();
100 | };
101 | };
--------------------------------------------------------------------------------
/source/lib/util/sys/soc.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 |
5 | namespace exl::util {
6 |
7 | enum class SocType {
8 | Erista,
9 | Mariko
10 | };
11 |
12 | namespace impl {
13 | inline SocType s_SocType;
14 |
15 | static inline void InitSocType() {
16 | SplHardwareType hwtype;
17 | R_ABORT_UNLESS(smcGetConfig(SplConfigItem_HardwareType, reinterpret_cast(&hwtype)));
18 |
19 | switch (hwtype) {
20 | case SplHardwareType_Icosa:
21 | case SplHardwareType_Copper:
22 | impl::s_SocType = SocType::Erista;
23 | return;
24 | case SplHardwareType_Hoag:
25 | case SplHardwareType_Iowa:
26 | case SplHardwareType_Calcio:
27 | case SplHardwareType_Aula:
28 | impl::s_SocType = SocType::Mariko;
29 | return;
30 |
31 | EXL_UNREACHABLE_DEFAULT_CASE();
32 | }
33 | }
34 | }
35 |
36 | static inline bool IsSocErista() { return impl::s_SocType == SocType::Erista; }
37 | static inline bool IsSocMariko() { return impl::s_SocType == SocType::Mariko; }
38 | }
--------------------------------------------------------------------------------
/source/lib/util/typed_storage.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
20 | #include
21 | #include
22 | #include
23 |
24 | namespace exl::util {
25 |
26 | template
27 | struct TypedStorage {
28 | typename std::aligned_storage::type _storage;
29 | };
30 |
31 | template
32 | static ALWAYS_INLINE T *GetPointer(TypedStorage &ts) {
33 | return std::launder(reinterpret_cast(std::addressof(ts._storage)));
34 | }
35 |
36 | template
37 | static ALWAYS_INLINE const T *GetPointer(const TypedStorage &ts) {
38 | return std::launder(reinterpret_cast(std::addressof(ts._storage)));
39 | }
40 |
41 | template
42 | static ALWAYS_INLINE T &GetReference(TypedStorage &ts) {
43 | return *GetPointer(ts);
44 | }
45 |
46 | template
47 | static ALWAYS_INLINE const T &GetReference(const TypedStorage &ts) {
48 | return *GetPointer(ts);
49 | }
50 |
51 | namespace impl {
52 |
53 | template
54 | static ALWAYS_INLINE T *GetPointerForConstructAt(TypedStorage &ts) {
55 | return reinterpret_cast(std::addressof(ts._storage));
56 | }
57 |
58 | }
59 |
60 | template
61 | static ALWAYS_INLINE T *ConstructAt(TypedStorage &ts, Args &&... args) {
62 | return std::construct_at(impl::GetPointerForConstructAt(ts), std::forward(args)...);
63 | }
64 |
65 | template
66 | static ALWAYS_INLINE void DestroyAt(TypedStorage &ts) {
67 | return std::destroy_at(GetPointer(ts));
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/source/nn.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | /* nnSdk is only available when loaded as a module. */
4 | #ifndef EXL_AS_MODULE
5 | #error "Cannot use nnSdk when not as a module!"
6 | #endif
7 |
8 | #include "nn/fs.hpp"
9 | #include "nn/os.hpp"
10 | #include "nn/time.hpp"
--------------------------------------------------------------------------------
/source/nn/fs.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "nn/fs/fs_types.hpp"
4 | #include "nn/fs/fs_directories.hpp"
5 | #include "nn/fs/fs_mount.hpp"
6 | #include "nn/fs/fs_files.hpp"
--------------------------------------------------------------------------------
/source/nn/fs/fs_directories.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "fs_types.hpp"
4 |
5 | namespace nn::fs {
6 | /*
7 | Open a directory given the path and open mode.
8 | handleOut: Pointer to write the handle into.
9 | path: Path to the directory to open.
10 | openMode: Mode to open the directory with, see nn::fs::OpenDirectoryMode.
11 | */
12 | Result OpenDirectory(DirectoryHandle* handleOut, char const* path, s32 openMode);
13 |
14 | /*
15 | Closes directory.
16 | handle: Handle of directory to close.
17 | */
18 | void CloseDirectory(DirectoryHandle handle);
19 |
20 | /*
21 | Read entries for a given opened directory into a provided buffer.
22 | entryCountOut: Pointer to write actual amount of entries read.
23 | entriesOut: Pointer to buffer containing (entryBufferLength) amount of DirectoryEntry
24 | handle: Handle of directory to be opened.
25 | entryBufferLength: How many entries provided in the entriesOut argument.
26 | */
27 | Result ReadDirectory(s64* entryCountOut, DirectoryEntry* entriesOut, DirectoryHandle handle, s64 entryBufferLength);
28 |
29 | /*
30 | Creates a directory at given path.
31 | path: Path to path to create.
32 | */
33 | Result CreateDirectory(char const* path);
34 |
35 | /*
36 | Get the amount of entries in a given opened directory.
37 | entryCountOut: Pointer to write the entry count.
38 | handle: Handle of the directory to count entries.
39 | */
40 | Result GetDirectoryEntryCount(s64* entryCountOut, DirectoryHandle handle);
41 |
42 | /*
43 | Delete a given directory path and all of it's subdirectories/folders.
44 | path: Path to the directory to delete.
45 | */
46 | Result DeleteDirectoryRecursively(char const* path);
47 |
48 | /*
49 | TODO: ?
50 | path: Path to the directory to be cleaned.
51 | */
52 | Result CleanDirectoryRecursively(char const* path);
53 |
54 | /*
55 | TODO: ?
56 | */
57 | Result RenameDirectory(char const*, char const*);
58 | }
--------------------------------------------------------------------------------
/source/nn/fs/fs_files.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "fs_types.hpp"
4 |
5 | namespace nn::fs {
6 |
7 | /*
8 | Create a file.
9 | path: Path where to create the path.
10 | size: Size of the file to create.
11 | */
12 | Result CreateFile(char const* path, s64 size);
13 |
14 | /*
15 | Open a file.
16 | handleOut: Output for handle representing opened file.
17 | path: File path to open.
18 | mode: Mode to open file. See OpenMode.
19 | */
20 | Result OpenFile(FileHandle* handleOut, char const* path, int mode);
21 |
22 | /*
23 | Close a file.
24 | handle: Handle to file to be closed.
25 | */
26 | void CloseFile(FileHandle handle);
27 |
28 | /*
29 | Read file at a location.
30 | handle: Handle representing file to be read.
31 | position: Position within the file to be read.
32 | size: How many bytes to read from file.
33 | */
34 | Result ReadFile(FileHandle handle, long position, void* buffer, ulong size);
35 |
36 | /*
37 | Read file at a location, with additional options.
38 | handle: Handle representing file to be read.
39 | position: Position within the file to be read.
40 | size: How many bytes to read from file.
41 | option: Additional options for reading, see ReadOption.
42 | */
43 | Result ReadFile(FileHandle handle, long position, void* buffer, const ReadOption& option);
44 |
45 | /*
46 | Read file at a location, with an output amount of bytes read.
47 | bytesRead: How many bytes were actually read.
48 | handle: Handle representing file to be read.
49 | position: Position within the file to be read.
50 | size: How many bytes to read from file.
51 | */
52 | Result ReadFile(ulong* bytesRead, FileHandle handle, long position, void* buffer);
53 |
54 |
55 | /*
56 | Read file at a location, with an output amount of bytes read, and additional options.
57 | bytesRead: How many bytes were actually read.
58 | handle: Handle representing file to be read.
59 | position: Position within the file to be read.
60 | size: How many bytes to read from file.
61 | option: Additional options for reading, see ReadOption.
62 | */
63 | Result ReadFile(ulong* bytesRead, FileHandle handle, long position, void* buffer, const ReadOption& option);
64 |
65 | /*
66 | Gets the size of the file.
67 | size: File size.
68 | handle: Handle representing file to check.
69 | */
70 | Result GetFileSize(long* size, nn::fs::FileHandle handle);
71 |
72 | /*
73 | Writes to a file.
74 | handle: Handle representing file to write to.
75 | position: Position within the file to write to.
76 | buffer: Pointer to the data to be written.
77 | size: Amount of data to write, from the pointer.
78 | option: Additional options for writing, like flushing.
79 | */
80 | Result WriteFile(FileHandle handle, s64 position, void const* buffer, u64 size, WriteOption const& option);
81 |
82 | /*
83 | Flush file.
84 | handle: Handle representing file to flush.
85 | */
86 | Result FlushFile(FileHandle handle);
87 | }
--------------------------------------------------------------------------------
/source/nn/fs/fs_mount.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "fs_types.hpp"
4 |
5 | namespace nn::fs {
6 |
7 | /*
8 | Mount SD card. Must have explicit permission.
9 | mount: drive to mount to.
10 | */
11 | Result MountSdCardForDebug(char const* mount);
12 | };
--------------------------------------------------------------------------------
/source/nn/fs/fs_types.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace nn::fs {
6 |
7 | /* Handle representing an opened file. */
8 | struct FileHandle {
9 | u64 _internal;
10 | };
11 |
12 | /* Handle representing an opened directory. */
13 | struct DirectoryHandle {
14 | u64 _internal;
15 | };
16 |
17 | /* Kinds of entries within a directory. */
18 | enum DirectoryEntryType {
19 | DirectoryEntryType_Directory,
20 | DirectoryEntryType_File,
21 | };
22 |
23 | /* Bitfield to define the kinds of entries to open from a directory. */
24 | enum OpenDirectoryMode : u8 {
25 | OpenDirectoryMode_Directory = BIT(0),
26 | OpenDirectoryMode_File = BIT(1),
27 |
28 | OpenDirectoryMode_All = OpenDirectoryMode_Directory | OpenDirectoryMode_File,
29 | };
30 |
31 | /* Maximum length a directory name can be. */
32 | constexpr inline size_t MaxDirectoryEntryNameSize = 0x300;
33 |
34 | /* Information about an entry within a directory. */
35 | struct DirectoryEntry {
36 | char m_Name[MaxDirectoryEntryNameSize + 1];
37 | OpenDirectoryMode m_Type;
38 | u32 field_304;
39 | long m_FileSize;
40 | };
41 |
42 | /* Mode for opening files. */
43 | enum OpenMode {
44 | OpenMode_Read = BIT(0),
45 | OpenMode_Write = BIT(1),
46 | OpenMode_Append = BIT(2),
47 |
48 | OpenMode_ReadWrite = OpenMode_Read | OpenMode_Write,
49 | };
50 |
51 | /* Options for reading. TODO: is this an enum? what for? */
52 | struct ReadOption {
53 | int _field_0;
54 |
55 | static inline ReadOption MakeOption(int value) {
56 | return {value};
57 | }
58 | };
59 |
60 | enum WriteOptionFlag {
61 | WriteOptionFlag_Flush = 1 << 0,
62 | };
63 |
64 | /* Options for writing. TODO: What else can this do? */
65 | struct WriteOption {
66 | int flags;
67 | static WriteOption CreateOption(int v) {
68 | return {
69 | .flags = v,
70 | };
71 | }
72 | };
73 | };
--------------------------------------------------------------------------------
/source/nn/nn_common.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "types.h"
4 |
5 | #include "util/util_typed_storage.hpp"
6 |
7 |
8 | typedef u32 Handle;
9 | typedef u32 Result;
10 | #define INVALID_HANDLE ((Handle) 0)
11 | #define BIT(n) (1U<<(n))
12 |
--------------------------------------------------------------------------------
/source/nn/nnmusl.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "nn_common.hpp"
4 |
5 | extern "C" int _nnmusl_errno_from_result(Result);
6 | extern "C" int __nnmusl_BroadcastConditionVariable(void*);
--------------------------------------------------------------------------------
/source/nn/os.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "os/os_types.hpp"
4 | #include "os/os_tick.hpp"
5 | #include "os/os_condition_variable_common.hpp"
6 | #include "os/os_event_common.hpp"
7 | #include "os/os_event_types.hpp"
8 | #include "os/os_event_api.hpp"
9 | #include "os/os_mutex_common.hpp"
10 | #include "os/os_mutex_api.hpp"
11 | #include "os/os_mutex_type.hpp"
12 | #include "os/os_thread_common.hpp"
13 | #include "os/os_thread_api.hpp"
14 | #include "os/os_thread_type.hpp"
--------------------------------------------------------------------------------
/source/nn/os/impl/os_internal_condition_variable.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 "nn/nn_common.hpp"
20 |
21 | #include "os_internal_critical_section.hpp"
22 |
23 | /* Assuming HOS. */
24 | #include "os_internal_condition_variable_impl.os.horizon.hpp"
25 |
26 | namespace nn::os::detail {
27 |
28 | class InternalConditionVariable {
29 | private:
30 | InternalConditionVariableImplByHorizon m_impl;
31 | public:
32 | constexpr InternalConditionVariable() : m_impl() { /* ... */ }
33 |
34 | constexpr void Initialize() {
35 | m_impl.Initialize();
36 | }
37 |
38 | void Signal() {
39 | m_impl.Signal();
40 | }
41 |
42 | void Broadcast() {
43 | m_impl.Broadcast();
44 | }
45 |
46 | void Wait(InternalCriticalSection *cs) {
47 | m_impl.Wait(cs);
48 | }
49 |
50 | ConditionVariableStatus TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper) {
51 | return m_impl.TimedWait(cs, timeout_helper);
52 | }
53 | };
54 |
55 | using InternalConditionVariableStorage = util::TypedStorage;
56 |
57 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_internal_condition_variable_impl.os.horizon.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 | #include "nn/nn_common.hpp"
19 |
20 | #include "os_timeout_helper.hpp"
21 |
22 | namespace nn::os::detail {
23 |
24 | class TimeoutHelper;
25 |
26 | class InternalConditionVariableImplByHorizon {
27 | private:
28 | u32 m_value;
29 | public:
30 | constexpr InternalConditionVariableImplByHorizon() : m_value(0) { /* ... */ }
31 |
32 | constexpr void Initialize() {
33 | m_value = 0;
34 | }
35 |
36 | void Signal();
37 | void Broadcast();
38 |
39 | void Wait(InternalCriticalSection *cs);
40 | ConditionVariableStatus TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper);
41 | };
42 |
43 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_internal_critical_section.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 "nn/nn_common.hpp"
20 | #include
21 |
22 | /* Assume HOS. */
23 | #include "os_internal_critical_section_impl.os.horizon.hpp"
24 |
25 | namespace nn::os::detail {
26 |
27 | class InternalCriticalSection {
28 | private:
29 | InternalCriticalSectionImplByHorizon m_impl;
30 | public:
31 | constexpr InternalCriticalSection() : m_impl() { /* ... */ }
32 |
33 | constexpr void Initialize() { m_impl.Initialize(); }
34 | constexpr void Finalize() { m_impl.Finalize(); }
35 |
36 | void Enter() { return m_impl.Enter(); }
37 | bool TryEnter() { return m_impl.TryEnter(); }
38 | void Leave() { return m_impl.Leave(); }
39 |
40 | bool IsLockedByCurrentThread() const { return m_impl.IsLockedByCurrentThread(); }
41 |
42 | ALWAYS_INLINE void Lock() { return this->Enter(); }
43 | ALWAYS_INLINE bool TryLock() { return this->TryEnter(); }
44 | ALWAYS_INLINE void Unlock() { return this->Leave(); }
45 |
46 | ALWAYS_INLINE void lock() { return this->Lock(); }
47 | ALWAYS_INLINE bool try_lock() { return this->TryLock(); }
48 | ALWAYS_INLINE void unlock() { return this->Unlock(); }
49 |
50 | InternalCriticalSectionImplByHorizon *Get() {
51 | return std::addressof(m_impl);
52 | }
53 |
54 | const InternalCriticalSectionImplByHorizon *Get() const {
55 | return std::addressof(m_impl);
56 | }
57 | };
58 |
59 | using InternalCriticalSectionStorage = util::TypedStorage;
60 |
61 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_internal_critical_section_impl.os.horizon.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 "nn/nn_common.hpp"
20 |
21 | namespace nn::os::detail {
22 |
23 | class InternalConditionVariableImplByHorizon;
24 |
25 | class InternalCriticalSectionImplByHorizon {
26 | private:
27 |
28 | friend class InternalConditionVariableImplByHorizon;
29 | private:
30 | u32 m_thread_handle;
31 | public:
32 | constexpr InternalCriticalSectionImplByHorizon() : m_thread_handle(INVALID_HANDLE) { /* ... */ }
33 |
34 | constexpr void Initialize() { m_thread_handle = INVALID_HANDLE; }
35 | constexpr void Finalize() { /* ... */ }
36 |
37 | void Enter();
38 | bool TryEnter();
39 | void Leave();
40 |
41 | bool IsLockedByCurrentThread() const;
42 |
43 | ALWAYS_INLINE void Lock() { return this->Enter(); }
44 | ALWAYS_INLINE bool TryLock() { return this->TryEnter(); }
45 | ALWAYS_INLINE void Unlock() { return this->Leave(); }
46 |
47 | ALWAYS_INLINE void lock() { return this->Lock(); }
48 | ALWAYS_INLINE bool try_lock() { return this->TryLock(); }
49 | ALWAYS_INLINE void unlock() { return this->Unlock(); }
50 | };
51 |
52 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_resource_manager.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 "nn/nn_common.hpp"
19 |
20 | #include "os_tick_manager_impl.hpp"
21 |
22 | namespace nn::os::detail {
23 |
24 | class OsResourceManager {
25 | private:
26 |
27 | TickManager m_tick_manager{};
28 |
29 | public:
30 | OsResourceManager() = default;
31 |
32 | constexpr ALWAYS_INLINE TickManager &GetTickManager() { return m_tick_manager; }
33 | };
34 |
35 | class ResourceManagerHolder {
36 | private:
37 | static util::TypedStorage s_resource_manager_storage;
38 | private:
39 | constexpr ResourceManagerHolder() { /* ... */ }
40 | public:
41 | static ALWAYS_INLINE void InitializeResourceManagerInstance() {
42 | /* Construct the resource manager instance. */
43 | util::ConstructAt(s_resource_manager_storage);
44 | }
45 |
46 | static ALWAYS_INLINE OsResourceManager &GetResourceManagerInstance() {
47 | return GetReference(s_resource_manager_storage);
48 | }
49 | };
50 |
51 | ALWAYS_INLINE OsResourceManager &GetResourceManager() {
52 | return ResourceManagerHolder::GetResourceManagerInstance();
53 | }
54 |
55 | ALWAYS_INLINE TickManager &GetTickManager() {
56 | return GetResourceManager().GetTickManager();
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_tick_manager_impl.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 "nn/nn_common.hpp"
20 | #include "nn/time.hpp"
21 |
22 | /* Assume HOS. */
23 | #include "os_tick_manager_impl.os.horizon.hpp"
24 |
25 | namespace nn::os::detail {
26 |
27 | /* Tick frequency must be less than INT64_MAX / 1 second. */
28 | static constexpr s64 MaxTickFrequency = (std::numeric_limits::max() / TimeSpan::FromSeconds(1).GetNanoSeconds()) - 1;
29 |
30 | class TickManager {
31 | private:
32 | TickManagerImpl m_impl;
33 | public:
34 | constexpr TickManager() : m_impl() { /* ... */ }
35 |
36 | ALWAYS_INLINE Tick GetTick() const {
37 | return m_impl.GetTick();
38 | }
39 |
40 | ALWAYS_INLINE Tick GetSystemTickOrdered() const {
41 | return m_impl.GetSystemTickOrdered();
42 | }
43 |
44 | ALWAYS_INLINE s64 GetTickFrequency() const {
45 | return m_impl.GetTickFrequency();
46 | }
47 |
48 | ALWAYS_INLINE s64 GetMaxTick() const {
49 | return m_impl.GetMaxTick();
50 | }
51 |
52 | ALWAYS_INLINE s64 GetMaxTimeSpanNs() const {
53 | return m_impl.GetMaxTimeSpanNs();
54 | }
55 |
56 | TimeSpan ConvertToTimeSpan(Tick tick) const;
57 | Tick ConvertToTick(TimeSpan ts) const;
58 | };
59 |
60 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_tick_manager_impl.os.horizon.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
19 | #include
20 |
21 | #include
22 |
23 | namespace nn::os::detail {
24 |
25 | class TickManagerImpl {
26 | public:
27 | constexpr TickManagerImpl() { /* ... */ }
28 |
29 | ALWAYS_INLINE Tick GetTick() const {
30 | s64 tick;
31 | __asm__ __volatile__("mrs %[tick], cntpct_el0" : [tick]"=&r"(tick) :: "memory");
32 | return Tick(tick);
33 | }
34 |
35 | ALWAYS_INLINE Tick GetSystemTickOrdered() const {
36 | s64 tick;
37 | __asm__ __volatile__("dsb ish\n"
38 | "isb\n"
39 | "mrs %[tick], cntpct_el0\n"
40 | "isb"
41 | : [tick]"=&r"(tick)
42 | :
43 | : "memory");
44 | return Tick(tick);
45 | }
46 |
47 | static constexpr inline const s64 TicksPerSecond = 19'200'000;
48 |
49 | static constexpr ALWAYS_INLINE s64 GetTickFrequency() {
50 | return static_cast(TicksPerSecond);
51 | }
52 |
53 | static constexpr ALWAYS_INLINE s64 GetMaxTick() {
54 | static_assert(GetTickFrequency() <= TimeSpan::FromSeconds(1).GetNanoSeconds());
55 | return (std::numeric_limits::max() / TimeSpan::FromSeconds(1).GetNanoSeconds()) * GetTickFrequency();
56 | }
57 |
58 | static constexpr ALWAYS_INLINE s64 GetMaxTimeSpanNs() {
59 | static_assert(GetTickFrequency() <= TimeSpan::FromSeconds(1).GetNanoSeconds());
60 | return TimeSpan::FromNanoSeconds(std::numeric_limits::max()).GetNanoSeconds();
61 | }
62 | };
63 |
64 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_timeout_helper.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 "nn/nn_common.hpp"
19 |
20 | #include "os_resource_manager.hpp"
21 |
22 | /* Assume HOS. */
23 | #include "os_timeout_helper_impl.os.horizon.hpp"
24 |
25 | namespace nn::os::detail {
26 |
27 | class TimeoutHelper {
28 | private:
29 | Tick m_absolute_end_tick;
30 | public:
31 | explicit TimeoutHelper(TimeSpan timeout) {
32 | if (timeout == TimeSpan::FromNanoSeconds(0)) {
33 | /* If timeout is zero, don't do relative tick calculations. */
34 | m_absolute_end_tick = Tick(0);
35 | } else {
36 | const auto &tick_manager = detail::GetTickManager();
37 |
38 | const u64 cur_tick = tick_manager.GetTick().GetInt64Value();
39 | const u64 timeout_tick = tick_manager.ConvertToTick(timeout).GetInt64Value();
40 | const u64 end_tick = cur_tick + timeout_tick + 1;
41 |
42 | m_absolute_end_tick = Tick(std::min(std::numeric_limits::max(), end_tick));
43 | }
44 | }
45 |
46 | static void Sleep(TimeSpan tm) {
47 | TimeoutHelperImpl::Sleep(tm);
48 | }
49 |
50 | bool TimedOut() const {
51 | if (m_absolute_end_tick.GetInt64Value() == 0) {
52 | return true;
53 | }
54 |
55 | const Tick cur_tick = detail::GetTickManager().GetTick();
56 |
57 | return cur_tick >= m_absolute_end_tick;
58 | }
59 |
60 | TargetTimeSpan GetTimeLeftOnTarget() const;
61 | };
62 |
63 | }
--------------------------------------------------------------------------------
/source/nn/os/impl/os_timeout_helper_impl.os.horizon.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 "nn/nn_common.hpp"
19 |
20 | namespace nn::os::detail {
21 |
22 | using TargetTimeSpan = ::nn::TimeSpan;
23 |
24 | class TimeoutHelperImpl {
25 | public:
26 | static TargetTimeSpan ConvertToImplTime(Tick tick) {
27 | return detail::GetTickManager().ConvertToTimeSpan(tick);
28 | }
29 |
30 | static void Sleep(TimeSpan tm);
31 | };
32 |
33 | }
--------------------------------------------------------------------------------
/source/nn/os/os_condition_variable_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 | #pragma once
17 | #include "nn/nn_common.hpp"
18 |
19 | namespace nn::os {
20 |
21 | enum class ConditionVariableStatus {
22 | TimedOut = 0,
23 | Success = 1,
24 | };
25 |
26 | }
--------------------------------------------------------------------------------
/source/nn/os/os_event_api.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 | #include
19 | #include "os_event_common.hpp"
20 |
21 | namespace nn::os {
22 |
23 | struct EventType;
24 | struct MultiWaitHolderType;
25 |
26 | void InitializeEvent(EventType *event, bool signaled, EventClearMode clear_mode);
27 | void FinalizeEvent(EventType *event);
28 |
29 | void SignalEvent(EventType *event);
30 | void WaitEvent(EventType *event);
31 | bool TryWaitEvent(EventType *event);
32 | bool TimedWaitEvent(EventType *event, TimeSpan timeout);
33 | void ClearEvent(EventType *event);
34 |
35 | void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, EventType *event);
36 |
37 | }
--------------------------------------------------------------------------------
/source/nn/os/os_event_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 | #pragma once
17 |
18 | namespace nn::os {
19 |
20 | enum EventClearMode {
21 | EventClearMode_ManualClear = 0,
22 | EventClearMode_AutoClear = 1,
23 | };
24 |
25 | }
--------------------------------------------------------------------------------
/source/nn/os/os_event_types.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 | #include "impl/os_internal_critical_section.hpp"
19 | #include "impl/os_internal_condition_variable.hpp"
20 |
21 | namespace nn::os {
22 |
23 | namespace impl {
24 |
25 | class MultiWaitObjectList;
26 |
27 | }
28 |
29 | struct EventType {
30 | enum State {
31 | State_NotInitialized = 0,
32 | State_Initialized = 1,
33 | };
34 |
35 | /* List stuff. */
36 | u8 _0[0x10];
37 |
38 | bool signaled;
39 | bool initially_signaled;
40 | u8 clear_mode;
41 | u8 state;
42 | u32 broadcast_counter_low;
43 | u32 broadcast_counter_high;
44 |
45 | detail::InternalCriticalSectionStorage cs_event;
46 | detail::InternalConditionVariableStorage cv_signaled;
47 | };
48 | static_assert(std::is_trivial::value);
49 |
50 | }
--------------------------------------------------------------------------------
/source/nn/os/os_mutex_api.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 "nn/nn_common.hpp"
20 |
21 | namespace nn::os {
22 |
23 | struct MutexType;
24 |
25 | void InitializeMutex(MutexType *mutex, bool recursive, int lock_level);
26 | void FinalizeMutex(MutexType *mutex);
27 |
28 | void LockMutex(MutexType *mutex);
29 | bool TryLockMutex(MutexType *mutex);
30 | void UnlockMutex(MutexType *mutex);
31 |
32 | bool IsMutexLockedByCurrentThread(const MutexType *mutex);
33 | };
--------------------------------------------------------------------------------
/source/nn/os/os_mutex_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 "nn/nn_common.hpp"
20 |
21 | namespace nn::os {
22 |
23 | constexpr inline s32 MutexLockLevelMin = 1;
24 | constexpr inline s32 MutexLockLevelMax = BITSIZEOF(s32) - 1;
25 | constexpr inline s32 MutexLockLevelInitial = 0;
26 |
27 | constexpr inline s32 MutexRecursiveLockCountMax = (1 << BITSIZEOF(u16)) - 1;
28 |
29 | }
--------------------------------------------------------------------------------
/source/nn/os/os_mutex_type.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 "nn/nn_common.hpp"
20 | #include "impl/os_internal_critical_section.hpp"
21 |
22 | namespace nn::os {
23 |
24 | struct ThreadType;
25 |
26 | struct MutexType {
27 | enum State : u8 {
28 | State_NotInitialized = 0,
29 | State_Initialized = 1,
30 | };
31 |
32 | State state;
33 | bool is_recursive;
34 | s32 lock_level;
35 | s32 nest_count;
36 | ThreadType *owner_thread;
37 | detail::InternalCriticalSectionStorage critical_section;
38 | };
39 | }
--------------------------------------------------------------------------------
/source/nn/os/os_thread_api.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
20 | #include
21 | #include "os_thread_common.hpp"
22 | #include "os_types.hpp"
23 |
24 | namespace nn::os {
25 |
26 | struct ThreadType;
27 | struct MultiWaitHolderType;
28 |
29 | Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority, s32 ideal_core);
30 | Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority);
31 | void DestroyThread(ThreadType *thread);
32 | void StartThread(ThreadType *thread);
33 |
34 | ThreadType *GetCurrentThread();
35 |
36 | void WaitThread(ThreadType *thread);
37 | bool TryWaitThread(ThreadType *thread);
38 |
39 | void YieldThread();
40 | void SleepThread(TimeSpan time);
41 |
42 | s32 SuspendThread(ThreadType *thread);
43 | s32 ResumeThread(ThreadType *thread);
44 | s32 GetThreadSuspendCount(const ThreadType *thread);
45 |
46 | void CancelThreadSynchronization(ThreadType *Thread);
47 |
48 | s32 ChangeThreadPriority(ThreadType *thread, s32 priority);
49 | s32 GetThreadPriority(const ThreadType *thread);
50 | s32 GetThreadCurrentPriority(const ThreadType *thread);
51 |
52 | void SetThreadName(ThreadType *thread, const char *name);
53 | void SetThreadNamePointer(ThreadType *thread, const char *name);
54 | const char *GetThreadNamePointer(const ThreadType *thread);
55 |
56 | s32 GetCurrentProcessorNumber();
57 | s32 GetCurrentCoreNumber();
58 |
59 | void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask);
60 | void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread);
61 |
62 | u64 GetThreadAvailableCoreMask();
63 |
64 | ThreadId GetThreadId(const ThreadType *thread);
65 |
66 | void InitializeMultiWaitHolder(MultiWaitHolderType *holder, ThreadType *thread);
67 |
68 | }
--------------------------------------------------------------------------------
/source/nn/os/os_thread_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 | #pragma once
17 |
18 | #include "nn/nn_common.hpp"
19 | #include "os_types.hpp"
20 |
21 | namespace nn::os {
22 |
23 | constexpr inline s32 ThreadSuspendCountMax = 127;
24 |
25 | constexpr inline s32 ThreadNameLengthMax = 0x20;
26 |
27 | constexpr inline s32 ThreadPriorityRangeSize = 32;
28 | constexpr inline s32 HighestThreadPriority = 0;
29 | constexpr inline s32 DefaultThreadPriority = ThreadPriorityRangeSize / 2;
30 | constexpr inline s32 LowestThreadPriority = ThreadPriorityRangeSize - 1;
31 |
32 | constexpr inline s32 InvalidThreadPriority = 127;
33 |
34 | constexpr inline s32 LowestSystemThreadPriority = 35;
35 | constexpr inline s32 HighestSystemThreadPriority = -12;
36 |
37 | constexpr inline size_t StackGuardAlignment = 4 * 1024;
38 | constexpr inline size_t ThreadStackAlignment = 4 * 1024;
39 |
40 | using ThreadFunction = void (*)(void *);
41 |
42 | }
--------------------------------------------------------------------------------
/source/nn/os/os_thread_type.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 "nn/nn_common.hpp"
20 | #include "os_thread_api.hpp"
21 | #include "os_types.hpp"
22 | #include "impl/os_internal_condition_variable.hpp"
23 | #include "impl/os_internal_critical_section.hpp"
24 |
25 | namespace nn::os {
26 |
27 | struct ThreadType {
28 |
29 | enum State : u8 {
30 | State_NotInitialized = 0,
31 | State_Initialized = 1,
32 | State_DestroyedBeforeStarted = 2,
33 | State_Started = 3,
34 | State_Terminated = 4,
35 | };
36 |
37 | /* List stuff. */
38 | u8 _0[0x40];
39 |
40 | State state;
41 | u8 _41;
42 | u8 _42;
43 | u8 suspend_count;
44 | s16 priority;
45 | void* user_stack;
46 | void* aliased_stack;
47 | size_t stack_size;
48 | void *argument;
49 | ThreadFunction function;
50 |
51 | /* Unknown? */
52 | u8 _88[0x110];
53 |
54 | char name_buffer[ThreadNameLengthMax];
55 | char* name_pointer;
56 |
57 | mutable detail::InternalCriticalSectionStorage cs_thread;
58 | mutable detail::InternalConditionVariableStorage cv_thread;
59 |
60 | Handle handle;
61 | ThreadId thread_id;
62 | };
63 | static_assert(sizeof(ThreadType) == 0x1C0, "");
64 |
65 | constexpr inline s32 IdealCoreDontCare = -1;
66 | constexpr inline s32 IdealCoreUseDefault = -2;
67 | constexpr inline s32 IdealCoreNoUpdate = -3;
68 |
69 | };
--------------------------------------------------------------------------------
/source/nn/os/os_tick.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
19 | #include
20 |
21 | namespace nn::os {
22 |
23 | class Tick;
24 |
25 | /* Tick API. */
26 | Tick GetSystemTick();
27 | Tick GetSystemTickOrdered();
28 | s64 GetSystemTickFrequency();
29 | TimeSpan ConvertToTimeSpan(Tick tick);
30 | Tick ConvertToTick(TimeSpan ts);
31 |
32 | class Tick {
33 | private:
34 | s64 m_tick;
35 | public:
36 | constexpr explicit Tick(s64 t = 0) : m_tick(t) { /* ... */ }
37 | Tick(TimeSpan ts) : m_tick(ConvertToTick(ts).GetInt64Value()) { /* ... */ }
38 | public:
39 | constexpr s64 GetInt64Value() const { return m_tick; }
40 | TimeSpan ToTimeSpan() const { return ConvertToTimeSpan(*this); }
41 |
42 | /* Tick arithmetic. */
43 | constexpr Tick &operator+=(Tick rhs) { m_tick += rhs.m_tick; return *this; }
44 | constexpr Tick &operator-=(Tick rhs) { m_tick -= rhs.m_tick; return *this; }
45 | constexpr Tick operator+(Tick rhs) const { Tick r(*this); return r += rhs; }
46 | constexpr Tick operator-(Tick rhs) const { Tick r(*this); return r -= rhs; }
47 |
48 | constexpr bool operator==(const Tick &rhs) const {
49 | return m_tick == rhs.m_tick;
50 | }
51 |
52 | constexpr bool operator!=(const Tick &rhs) const {
53 | return !(*this == rhs);
54 | }
55 |
56 | constexpr bool operator<(const Tick &rhs) const {
57 | return m_tick < rhs.m_tick;
58 | }
59 |
60 | constexpr bool operator>=(const Tick &rhs) const {
61 | return !(*this < rhs);
62 | }
63 |
64 | constexpr bool operator>(const Tick &rhs) const {
65 | return m_tick > rhs.m_tick;
66 | }
67 |
68 | constexpr bool operator<=(const Tick &rhs) const {
69 | return !(*this > rhs);
70 | }
71 | };
72 |
73 | }
--------------------------------------------------------------------------------
/source/nn/os/os_types.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 | namespace nn::os {
20 |
21 | using ThreadId = u64;
22 |
23 | };
--------------------------------------------------------------------------------
/source/nn/time.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "time/time_timespan.hpp"
--------------------------------------------------------------------------------
/source/nn/util/util_typed_storage.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
20 | #include
21 | #include
22 | #include
23 |
24 | namespace nn::util {
25 |
26 | template
27 | struct TypedStorage {
28 | typename std::aligned_storage::type _storage;
29 | };
30 |
31 | template
32 | static ALWAYS_INLINE T *GetPointer(TypedStorage &ts) {
33 | return std::launder(reinterpret_cast(std::addressof(ts._storage)));
34 | }
35 |
36 | template
37 | static ALWAYS_INLINE const T *GetPointer(const TypedStorage &ts) {
38 | return std::launder(reinterpret_cast(std::addressof(ts._storage)));
39 | }
40 |
41 | template
42 | static ALWAYS_INLINE T &GetReference(TypedStorage &ts) {
43 | return *GetPointer(ts);
44 | }
45 |
46 | template
47 | static ALWAYS_INLINE const T &GetReference(const TypedStorage &ts) {
48 | return *GetPointer(ts);
49 | }
50 |
51 | namespace impl {
52 |
53 | template
54 | static ALWAYS_INLINE T *GetPointerForConstructAt(TypedStorage &ts) {
55 | return reinterpret_cast(std::addressof(ts._storage));
56 | }
57 |
58 | }
59 |
60 | template
61 | static ALWAYS_INLINE T *ConstructAt(TypedStorage &ts, Args &&... args) {
62 | return std::construct_at(impl::GetPointerForConstructAt(ts), std::forward(args)...);
63 | }
64 |
65 | template
66 | static ALWAYS_INLINE void DestroyAt(TypedStorage &ts) {
67 | return std::destroy_at(GetPointer(ts));
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/source/program/setting.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "common.hpp"
4 |
5 | #define EXL_MODULE_NAME "exlaunch"
6 | #define EXL_MODULE_NAME_LEN 8
7 |
8 | #define EXL_DEBUG
9 | #define EXL_USE_FAKEHEAP
10 |
11 | /*
12 | #define EXL_SUPPORTS_REBOOTPAYLOAD
13 | */
14 |
15 | namespace exl::setting {
16 | /* How large the fake .bss heap will be. */
17 | constexpr size_t HeapSize = 0x5000;
18 |
19 | /* How large the JIT area will be for hooks. */
20 | constexpr size_t JitSize = 0x1000;
21 |
22 | /* How large the area will be inline hook pool. */
23 | constexpr size_t InlinePoolSize = 0x1000;
24 |
25 | /* Sanity checks. */
26 | static_assert(ALIGN_UP(JitSize, PAGE_SIZE) == JitSize, "");
27 | static_assert(ALIGN_UP(InlinePoolSize, PAGE_SIZE) == JitSize, "");
28 | }
--------------------------------------------------------------------------------
/source/types.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | typedef uint8_t u8; ///< 8-bit unsigned integer.
9 | typedef uint16_t u16; ///< 16-bit unsigned integer.
10 | typedef uint32_t u32; ///< 32-bit unsigned integer.
11 | typedef uint64_t u64; ///< 64-bit unsigned integer.
12 | typedef __uint128_t u128; ///< 128-bit unsigned integer.
13 |
14 | typedef int8_t s8; ///< 8-bit signed integer.
15 | typedef int16_t s16; ///< 16-bit signed integer.
16 | typedef int32_t s32; ///< 32-bit signed integer.
17 | typedef int64_t s64; ///< 64-bit signed integer.
18 | typedef __int128_t s128; ///< 128-bit unsigned integer.
19 |
20 | typedef volatile u8 vu8; ///< 8-bit volatile unsigned integer.
21 | typedef volatile u16 vu16; ///< 16-bit volatile unsigned integer.
22 | typedef volatile u32 vu32; ///< 32-bit volatile unsigned integer.
23 | typedef volatile u64 vu64; ///< 64-bit volatile unsigned integer.
24 | typedef volatile u128 vu128; ///< 128-bit volatile unsigned integer.
25 |
26 | typedef volatile s8 vs8; ///< 8-bit volatile signed integer.
27 | typedef volatile s16 vs16; ///< 16-bit volatile signed integer.
28 | typedef volatile s32 vs32; ///< 32-bit volatile signed integer.
29 | typedef volatile s64 vs64; ///< 64-bit volatile signed integer.
30 | typedef volatile s128 vs128; ///< 128-bit volatile signed integer.
31 |
32 | typedef unsigned char uchar;
33 | typedef unsigned short ushort;
34 | typedef unsigned int uint;
35 | typedef unsigned long ulong;
36 |
37 |
38 | #define ALIGN_UP(x, a) ((((uintptr_t)x) + (((uintptr_t)a)-1)) & ~(((uintptr_t)a)-1))
39 | #define ALIGN_DOWN(x, a) ((uintptr_t)(x) & ~(((uintptr_t)(a)) - 1))
40 | #define ALIGNED(a) __attribute__((aligned(a)))
41 | #define ON_INIT __attribute__((constructor))
42 | #define NOINLINE __attribute__((noinline))
43 | #define NORETURN __attribute__((noreturn))
44 | #define UNREACHABLE __builtin_unreachable()
45 | #define PAGE_SIZE (0x1000)
46 | #define ALWAYS_INLINE inline __attribute__((always_inline))
47 | #define BITSIZEOF(x) (sizeof(x) * CHAR_BIT)
--------------------------------------------------------------------------------