Compare commits
72 Commits
0.8.4
...
debugger_d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37025258c6 | ||
|
|
a38927ec04 | ||
|
|
fbddf090a4 | ||
|
|
b4b1208222 | ||
|
|
4e95397ed5 | ||
|
|
853a57e4d4 | ||
|
|
8c86074da2 | ||
|
|
88a6ef4cd7 | ||
|
|
7d2dd628ba | ||
|
|
7e93ca0977 | ||
|
|
eddbd7c072 | ||
|
|
e734a5412a | ||
|
|
7ddb0da5f6 | ||
|
|
862aa73783 | ||
|
|
34af93b72f | ||
|
|
5ef3ca9364 | ||
|
|
37d3577028 | ||
|
|
ef68881e5c | ||
|
|
e8a5aa81f4 | ||
|
|
f2f25dd5ed | ||
|
|
8d140d835a | ||
|
|
afae7eaa11 | ||
|
|
af70a4a3a3 | ||
|
|
bc6ad53018 | ||
|
|
a3fc2c95b8 | ||
|
|
68af2c1c2a | ||
|
|
2552c0327c | ||
|
|
f5ac895062 | ||
|
|
e4cc39c29b | ||
|
|
c80eb26135 | ||
|
|
964a698875 | ||
|
|
b57ec74ca3 | ||
|
|
66d5c9fe26 | ||
|
|
434f600f95 | ||
|
|
89503049b3 | ||
|
|
aaabb4bfc4 | ||
|
|
be772b40e1 | ||
|
|
3149b8a6fe | ||
|
|
c8e0028874 | ||
|
|
a8d929a343 | ||
|
|
618de9546a | ||
|
|
2673118478 | ||
|
|
6cc69fb3fc | ||
|
|
eefee8c7a8 | ||
|
|
dcf44e406e | ||
|
|
1970a52fc9 | ||
|
|
aca8f53050 | ||
|
|
cdb7ce3dec | ||
|
|
49d1e65496 | ||
|
|
29153af2bc | ||
|
|
f79f4d175b | ||
|
|
600ad660a6 | ||
|
|
efcce68a56 | ||
|
|
6b04c937e6 | ||
|
|
46c50f2cbe | ||
|
|
eb6ab2ba62 | ||
|
|
8a92a63a64 | ||
|
|
907f6fa72d | ||
|
|
94e527e763 | ||
|
|
588315f877 | ||
|
|
d1985fe77e | ||
|
|
61ad4e0991 | ||
|
|
24be9ffc57 | ||
|
|
d875d84d2d | ||
|
|
9fe8b22269 | ||
|
|
37e5a8544b | ||
|
|
bf7dc84893 | ||
|
|
bb48e33074 | ||
|
|
2572ae8378 | ||
|
|
46001263f8 | ||
|
|
ec8523af7c | ||
|
|
2708de3876 |
7
Makefile
7
Makefile
@@ -1,6 +1,7 @@
|
|||||||
TOPTARGETS := all clean dist
|
TOPTARGETS := all clean dist
|
||||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
AMSHASH := $(shell git rev-parse --short HEAD)
|
||||||
|
AMSREV := $(AMSBRANCH)-$(AMSHASH)
|
||||||
|
|
||||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||||
AMSREV := $(AMSREV)-dirty
|
AMSREV := $(AMSREV)-dirty
|
||||||
@@ -52,6 +53,8 @@ dist: all
|
|||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032
|
||||||
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000007
|
||||||
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D
|
||||||
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin
|
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin
|
||||||
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/atmosphere/fusee-secondary.bin
|
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/atmosphere/fusee-secondary.bin
|
||||||
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/sept/payload.bin
|
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/sept/payload.bin
|
||||||
@@ -68,6 +71,8 @@ dist: all
|
|||||||
cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro
|
cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro
|
||||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags
|
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags
|
||||||
touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags/boot2.flag
|
touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags/boot2.flag
|
||||||
|
cp stratosphere/tma/tma.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000007/exefs.nsp
|
||||||
|
cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D/exefs.nsp
|
||||||
cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../;
|
cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../;
|
||||||
rm -r atmosphere-$(AMSVER)
|
rm -r atmosphere-$(AMSVER)
|
||||||
mkdir out
|
mkdir out
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ In no particular order, we credit the following for their invaluable contributio
|
|||||||
|
|
||||||
* __switchbrew__ for the [libnx](https://github.com/switchbrew/libnx) project and the extensive [documentation, research and tool development](http://switchbrew.org) pertaining to the Nintendo Switch.
|
* __switchbrew__ for the [libnx](https://github.com/switchbrew/libnx) project and the extensive [documentation, research and tool development](http://switchbrew.org) pertaining to the Nintendo Switch.
|
||||||
* __devkitPro__ for the [devkitA64](https://devkitpro.org/) toolchain and libnx support.
|
* __devkitPro__ for the [devkitA64](https://devkitpro.org/) toolchain and libnx support.
|
||||||
* __ReSwitched Team__ for additional [documentation, research and tool development](https://reswitched.tech/) pertaining to the Nintendo Switch.
|
* __ReSwitched Team__ for additional [documentation, research and tool development](https://reswitched.team/) pertaining to the Nintendo Switch.
|
||||||
* __ChaN__ for the [FatFs](http://elm-chan.org/fsw/ff/00index_e.html) module.
|
* __ChaN__ for the [FatFs](http://elm-chan.org/fsw/ff/00index_e.html) module.
|
||||||
* __Marcus Geelnard__ for the [bcl-1.2.0](https://sourceforge.net/projects/bcl/files/bcl/bcl-1.2.0) library.
|
* __Marcus Geelnard__ for the [bcl-1.2.0](https://sourceforge.net/projects/bcl/files/bcl/bcl-1.2.0) library.
|
||||||
* __naehrwert__ and __st4rk__ for the original [hekate](https://github.com/nwert/hekate) project and its hwinit code base.
|
* __naehrwert__ and __st4rk__ for the original [hekate](https://github.com/nwert/hekate) project and its hwinit code base.
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
[config]
|
[hbl_config]
|
||||||
hbl_tid=010000000000100D
|
title_id=010000000000100D
|
||||||
hbl_path=atmosphere/hbl.nsp
|
path=atmosphere/hbl.nsp
|
||||||
override_key=!R
|
override_key=!R
|
||||||
|
|
||||||
|
[default_config]
|
||||||
|
override_key=!L
|
||||||
|
cheat_enable_key=!L
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
upload_enabled = u8!0x0
|
upload_enabled = u8!0x0
|
||||||
; Enable USB 3.0 superspeed for homebrew
|
; Enable USB 3.0 superspeed for homebrew
|
||||||
[usb]
|
[usb]
|
||||||
usb30_force_enabled = u8!0x1
|
usb30_force_enabled = u8!0x0
|
||||||
; Atmosphere custom settings
|
; Atmosphere custom settings
|
||||||
[atmosphere]
|
[atmosphere]
|
||||||
; Make the power menu's "reboot" button reboot to payload.
|
; Make the power menu's "reboot" button reboot to payload.
|
||||||
|
|||||||
@@ -21,4 +21,8 @@
|
|||||||
#define ATMOSPHERE_RELEASE_VERSION_MINOR 8
|
#define ATMOSPHERE_RELEASE_VERSION_MINOR 8
|
||||||
#define ATMOSPHERE_RELEASE_VERSION_MICRO 4
|
#define ATMOSPHERE_RELEASE_VERSION_MICRO 4
|
||||||
|
|
||||||
|
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR 7
|
||||||
|
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR 0
|
||||||
|
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO 1
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -55,7 +55,7 @@ When authoring patches, [hactool](https://github.com/SciresM/hactool) can be use
|
|||||||
|
|
||||||
Atmosphère can use the loader module in order to turn any game on your Switch's home menu into a launchpoint for the Homebrew Menu, rather than launching it through the album applet. This allows one to launch the Homebrew Menu with access to the ~3.2GB of RAM that the Switch reserves for games and applications, as opposed to the 442MB of RAM we are limited to when launching the Homebrew Menu from the album. This also means that it is no longer necessary to install homebrew as `.nsp` files on your Switch so long as you are using this method, as the only reason to do so is to allow the homebrew to access all of the Switch's available memory.
|
Atmosphère can use the loader module in order to turn any game on your Switch's home menu into a launchpoint for the Homebrew Menu, rather than launching it through the album applet. This allows one to launch the Homebrew Menu with access to the ~3.2GB of RAM that the Switch reserves for games and applications, as opposed to the 442MB of RAM we are limited to when launching the Homebrew Menu from the album. This also means that it is no longer necessary to install homebrew as `.nsp` files on your Switch so long as you are using this method, as the only reason to do so is to allow the homebrew to access all of the Switch's available memory.
|
||||||
|
|
||||||
In order to setup this method you will need the latest release of [hbmenu](https://github.com/switchbrew/nx-hbmenu/releases), and the latest release of [hbloader](https://github.com/switchbrew/nx-hbloader/releases). Place `hbmenu.nro` on the root of your Switch's SD Card, and place `hbl.nsp` in the atmosphere folder. From there, simply configure `loader.ini` in the atmosphere folder by replacing the Title ID in the ini (hbl_tid) (it is the Title ID for the album by default) with the Title ID of whatever game you wish to use to launch the Homebrew Menu. A list of Title IDs for Switch Games can be found [here](https://switchbrew.org/wiki/Title_list/Games). Afterwards you may reinsert your SD Card into your Switch and boot into Atmosphère as you normally would. You should now be able to boot into the Homebrew Menu by launching your designated game of choice.
|
In order to setup this method you will need the latest release of [hbmenu](https://github.com/switchbrew/nx-hbmenu/releases), and the latest release of [hbloader](https://github.com/switchbrew/nx-hbloader/releases). Place `hbmenu.nro` on the root of your Switch's SD Card, and place `hbl.nsp` in the atmosphere folder. From there, simply configure `loader.ini` in the atmosphere folder by replacing the Title ID in the ini (title_id in the [hbl_config] section) (it is the Title ID for the album by default) with the Title ID of whatever game you wish to use to launch the Homebrew Menu. A list of Title IDs for Switch Games can be found [here](https://switchbrew.org/wiki/Title_list/Games). Afterwards you may reinsert your SD Card into your Switch and boot into Atmosphère as you normally would. You should now be able to boot into the Homebrew Menu by launching your designated game of choice.
|
||||||
|
|
||||||
### Button Overrides
|
### Button Overrides
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ The SwIPC definition for this command follows.
|
|||||||
```
|
```
|
||||||
interface nn::pm::detail::IDebugMonitorInterface is pm:dmnt {
|
interface nn::pm::detail::IDebugMonitorInterface is pm:dmnt {
|
||||||
...
|
...
|
||||||
[65000] AtmosphereGetProcessHandle(u64 pid) -> handle<copy, process> process_handle;
|
[65000] AtmosphereGetProcessInfo(u64 pid) -> handle<copy, process> process_handle, u64 title_id, u64 storage_id;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ AMS := $(TOPDIR)/../../
|
|||||||
include $(DEVKITARM)/base_rules
|
include $(DEVKITARM)/base_rules
|
||||||
|
|
||||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
AMSHASH := $(shell git rev-parse --short HEAD)
|
||||||
|
AMSREV := $(AMSBRANCH)-$(AMSHASH)
|
||||||
|
|
||||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||||
AMSREV := $(AMSREV)-dirty
|
AMSREV := $(AMSREV)-dirty
|
||||||
@@ -43,7 +44,7 @@ INCLUDES := include ../../common/include
|
|||||||
# options for code generation
|
# options for code generation
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
ARCH := -march=armv4t -mtune=arm7tdmi -marm
|
ARCH := -march=armv4t -mtune=arm7tdmi -marm
|
||||||
DEFINES := -D__BPMP__ -DFUSEE_STAGE2_SRC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
DEFINES := -D__BPMP__ -DFUSEE_STAGE2_SRC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\" -DATMOSPHERE_GIT_HASH=$(AMSHASH)
|
||||||
|
|
||||||
CFLAGS := \
|
CFLAGS := \
|
||||||
-g \
|
-g \
|
||||||
@@ -61,7 +62,7 @@ CFLAGS += $(INCLUDE)
|
|||||||
|
|
||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
||||||
|
|
||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH) $(INCLUDE) $(DEFINES)
|
||||||
LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
LIBS :=
|
LIBS :=
|
||||||
@@ -159,6 +160,7 @@ clean:
|
|||||||
@$(MAKE) -C $(AMS)/exosphere clean
|
@$(MAKE) -C $(AMS)/exosphere clean
|
||||||
@$(MAKE) -C $(AMS)/thermosphere clean
|
@$(MAKE) -C $(AMS)/thermosphere clean
|
||||||
@$(MAKE) -C $(AMS)/stratosphere clean
|
@$(MAKE) -C $(AMS)/stratosphere clean
|
||||||
|
@$(MAKE) -C $(AMS)/sept clean
|
||||||
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).elf
|
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).elf
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ SECTIONS
|
|||||||
. = ALIGN(32);
|
. = ALIGN(32);
|
||||||
} >main
|
} >main
|
||||||
|
|
||||||
|
__data_end__ = ABSOLUTE(.);
|
||||||
|
PROVIDE (__total_size__ = (__data_end__ - __start__));
|
||||||
|
|
||||||
.bss (NOLOAD) :
|
.bss (NOLOAD) :
|
||||||
{
|
{
|
||||||
. = ALIGN(32);
|
. = ALIGN(32);
|
||||||
@@ -211,4 +214,36 @@ SECTIONS
|
|||||||
.debug_str 0 : { *(.debug_str) }
|
.debug_str 0 : { *(.debug_str) }
|
||||||
.debug_loc 0 : { *(.debug_loc) }
|
.debug_loc 0 : { *(.debug_loc) }
|
||||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
==== Embedded Data ====
|
||||||
|
======================= */
|
||||||
|
PROVIDE(__ams_mitm_kip_start__ = ams_mitm_kip - __start__);
|
||||||
|
PROVIDE(__ams_mitm_kip_size__ = ams_mitm_kip_end - ams_mitm_kip);
|
||||||
|
PROVIDE(__boot_100_kip_start__ = boot_100_kip - __start__);
|
||||||
|
PROVIDE(__boot_100_kip_size__ = boot_100_kip_end - boot_100_kip);
|
||||||
|
PROVIDE(__boot_200_kip_start__ = boot_200_kip - __start__);
|
||||||
|
PROVIDE(__boot_200_kip_size__ = boot_200_kip_end - boot_200_kip);
|
||||||
|
PROVIDE(__exosphere_bin_start__ = exosphere_bin - __start__);
|
||||||
|
PROVIDE(__exosphere_bin_size__ = exosphere_bin_end - exosphere_bin);
|
||||||
|
PROVIDE(__fusee_primary_bin_start__ = fusee_primary_bin - __start__);
|
||||||
|
PROVIDE(__fusee_primary_bin_size__ = fusee_primary_bin_end - fusee_primary_bin);
|
||||||
|
PROVIDE(__loader_kip_start__ = loader_kip - __start__);
|
||||||
|
PROVIDE(__loader_kip_size__ = loader_kip_end - loader_kip);
|
||||||
|
PROVIDE(__lp0fw_bin_start__ = lp0fw_bin - __start__);
|
||||||
|
PROVIDE(__lp0fw_bin_size__ = lp0fw_bin_end - lp0fw_bin);
|
||||||
|
PROVIDE(__pm_kip_start__ = pm_kip - __start__);
|
||||||
|
PROVIDE(__pm_kip_size__ = pm_kip_end - pm_kip);
|
||||||
|
PROVIDE(__rebootstub_bin_start__ = rebootstub_bin - __start__);
|
||||||
|
PROVIDE(__rebootstub_bin_size__ = rebootstub_bin_end - rebootstub_bin);
|
||||||
|
PROVIDE(__sept_primary_bin_start__ = sept_primary_bin - __start__);
|
||||||
|
PROVIDE(__sept_primary_bin_size__ = sept_primary_bin_end - sept_primary_bin);
|
||||||
|
PROVIDE(__sept_secondary_enc_start__ = sept_secondary_enc - __start__);
|
||||||
|
PROVIDE(__sept_secondary_enc_size__ = sept_secondary_enc_end - sept_secondary_enc);
|
||||||
|
PROVIDE(__sm_kip_start__ = sm_kip - __start__);
|
||||||
|
PROVIDE(__sm_kip_size__ = sm_kip_end - sm_kip);
|
||||||
|
PROVIDE(__splash_screen_bmp_start__ = splash_screen_bmp - __start__);
|
||||||
|
PROVIDE(__splash_screen_bmp_size__ = splash_screen_bmp_end - splash_screen_bmp);
|
||||||
|
PROVIDE(__thermosphere_bin_start__ = thermosphere_bin - __start__);
|
||||||
|
PROVIDE(__thermosphere_bin_size__ = thermosphere_bin_end - thermosphere_bin);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#include <atmosphere/version.h>
|
||||||
|
|
||||||
.macro CLEAR_GPR_REG_ITER
|
.macro CLEAR_GPR_REG_ITER
|
||||||
mov r\@, #0
|
mov r\@, #0
|
||||||
@@ -20,21 +21,27 @@
|
|||||||
|
|
||||||
.section .text.start, "ax", %progbits
|
.section .text.start, "ax", %progbits
|
||||||
.arm
|
.arm
|
||||||
|
|
||||||
.align 5
|
.align 5
|
||||||
.global _start
|
.global _start
|
||||||
.type _start, %function
|
.type _start, %function
|
||||||
_start:
|
_start:
|
||||||
|
b _crt0
|
||||||
|
|
||||||
|
.word (_metadata - _start)
|
||||||
|
|
||||||
|
_crt0:
|
||||||
/* Switch to system mode, mask all interrupts, clear all flags */
|
/* Switch to system mode, mask all interrupts, clear all flags */
|
||||||
msr cpsr_cxsf, #0xDF
|
msr cpsr_cxsf, #0xDF
|
||||||
|
|
||||||
/* Relocate ourselves if necessary */
|
/* Relocate ourselves if necessary */
|
||||||
ldr r2, =__start__
|
ldr r2, =_start
|
||||||
adr r3, _start
|
adr r3, _start
|
||||||
cmp r2, r3
|
cmp r2, r3
|
||||||
bne _relocation_loop_end
|
beq _relocation_loop_end
|
||||||
|
|
||||||
ldr r4, =__bss_start__
|
ldr r4, =__bss_start__
|
||||||
sub r4, r4, r2 /* size >= 32, obviously, and we've declared 32-byte-alignment */
|
sub r4, r4, r2 /* size >= 32, obviously, and weve declared 32-byte-alignment */
|
||||||
_relocation_loop:
|
_relocation_loop:
|
||||||
ldmia r3!, {r5-r12}
|
ldmia r3!, {r5-r12}
|
||||||
stmia r2!, {r5-r12}
|
stmia r2!, {r5-r12}
|
||||||
@@ -61,6 +68,129 @@ _start:
|
|||||||
ldr r1, [r1]
|
ldr r1, [r1]
|
||||||
b main
|
b main
|
||||||
|
|
||||||
|
/* Fusee-secondary header. */
|
||||||
|
.align 5
|
||||||
|
_metadata:
|
||||||
|
.ascii "FSS0"
|
||||||
|
.word __total_size__
|
||||||
|
.word (_crt0 - _start)
|
||||||
|
.word (_content_headers - _start)
|
||||||
|
.word (_content_headers_end - _content_headers) / 0x20 /* Number of content headers */
|
||||||
|
.word ((ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR << 24) | (ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR << 16) | (ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO << 8) | (0x0))
|
||||||
|
.word ((ATMOSPHERE_RELEASE_VERSION_MAJOR << 24) | (ATMOSPHERE_RELEASE_VERSION_MINOR << 16) | (ATMOSPHERE_RELEASE_VERSION_MICRO << 8) | (0x0))
|
||||||
|
#define TO_WORD(x) TO_WORD_(x)
|
||||||
|
#define TO_WORD_(x) 0x##x
|
||||||
|
#define AMS_GIT_REV_WORD TO_WORD(ATMOSPHERE_GIT_HASH)
|
||||||
|
.word AMS_GIT_REV_WORD
|
||||||
|
#undef TO_WORD_
|
||||||
|
#undef TO_WORD
|
||||||
|
|
||||||
|
_content_headers:
|
||||||
|
/* ams_mitm content header */
|
||||||
|
.word __ams_mitm_kip_start__
|
||||||
|
.word __ams_mitm_kip_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "ams_mitm"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* boot_100 content header */
|
||||||
|
.word __boot_100_kip_start__
|
||||||
|
.word __boot_100_kip_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "boot_100"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* boot_200 content header */
|
||||||
|
.word __boot_200_kip_start__
|
||||||
|
.word __boot_200_kip_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "boot_200"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* exosphere content header */
|
||||||
|
.word __exosphere_bin_start__
|
||||||
|
.word __exosphere_bin_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "exosphere"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* fusee_primary content header */
|
||||||
|
.word __fusee_primary_bin_start__
|
||||||
|
.word __fusee_primary_bin_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "fusee_primary"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* loader content header */
|
||||||
|
.word __loader_kip_start__
|
||||||
|
.word __loader_kip_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "loader"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* lp0fw content header */
|
||||||
|
.word __lp0fw_bin_start__
|
||||||
|
.word __lp0fw_bin_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "lp0fw"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* pm content header */
|
||||||
|
.word __pm_kip_start__
|
||||||
|
.word __pm_kip_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "pm"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* rebootstub content header */
|
||||||
|
.word __rebootstub_bin_start__
|
||||||
|
.word __rebootstub_bin_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "rebootstub"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* sept_primary content header */
|
||||||
|
.word __sept_primary_bin_start__
|
||||||
|
.word __sept_primary_bin_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "sept_primary"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* sept_secondary content header */
|
||||||
|
.word __sept_secondary_enc_start__
|
||||||
|
.word __sept_secondary_enc_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "sept_secondary"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* sm content header */
|
||||||
|
.word __sm_kip_start__
|
||||||
|
.word __sm_kip_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "sm"
|
||||||
|
.align 5
|
||||||
|
|
||||||
|
/* splash_screen content header */
|
||||||
|
.word __splash_screen_bmp_start__
|
||||||
|
.word __splash_screen_bmp_size__
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.word 0xCCCCCCCC
|
||||||
|
.asciz "splash_screen"
|
||||||
|
.align 5
|
||||||
|
_content_headers_end:
|
||||||
|
|
||||||
/* No need to include this in normal programs: */
|
/* No need to include this in normal programs: */
|
||||||
.section .chainloader.text.start, "ax", %progbits
|
.section .chainloader.text.start, "ax", %progbits
|
||||||
.arm
|
.arm
|
||||||
|
|||||||
@@ -120,14 +120,19 @@ check_rebootstub:
|
|||||||
@$(MAKE) -C $(AMS)/exosphere/rebootstub all
|
@$(MAKE) -C $(AMS)/exosphere/rebootstub all
|
||||||
|
|
||||||
$(BUILD):
|
$(BUILD):
|
||||||
|
ifeq ($(strip $(SEPT_ENC_PATH)),)
|
||||||
@[ -d $@ ] || mkdir -p $@
|
@[ -d $@ ] || mkdir -p $@
|
||||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
else
|
||||||
|
@touch $(TOPDIR)/$(TARGET).bin
|
||||||
|
@cp $(SEPT_ENC_PATH) $(TOPDIR)/$(TARGET).enc
|
||||||
|
endif
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
clean:
|
clean:
|
||||||
@echo clean ...
|
@echo clean ...
|
||||||
@$(MAKE) -C $(AMS)/exosphere/rebootstub clean
|
@$(MAKE) -C $(AMS)/exosphere/rebootstub clean
|
||||||
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).elf
|
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).enc $(TARGET).elf
|
||||||
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
MODULES := loader pm sm boot ams_mitm eclct.stub creport fatal
|
MODULES := loader pm sm boot ams_mitm eclct.stub creport fatal dmnt tma
|
||||||
|
|
||||||
SUBFOLDERS := libstratosphere $(MODULES)
|
SUBFOLDERS := libstratosphere $(MODULES)
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,26 @@ static std::vector<u64> g_disable_mitm_flagged_tids;
|
|||||||
static std::atomic_bool g_has_initialized = false;
|
static std::atomic_bool g_has_initialized = false;
|
||||||
static std::atomic_bool g_has_hid_session = false;
|
static std::atomic_bool g_has_hid_session = false;
|
||||||
|
|
||||||
static u64 g_override_key_combination = KEY_R;
|
/* Content override support variables/types */
|
||||||
static u64 g_override_hbl_tid = 0x010000000000100DULL;
|
static OverrideKey g_default_override_key = {
|
||||||
static bool g_override_any_app = false;
|
.key_combination = KEY_R,
|
||||||
static bool g_override_by_default = true;
|
.override_by_default = true
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HblOverrideConfig {
|
||||||
|
OverrideKey override_key;
|
||||||
|
u64 title_id;
|
||||||
|
bool override_any_app;
|
||||||
|
};
|
||||||
|
|
||||||
|
static HblOverrideConfig g_hbl_override_config = {
|
||||||
|
.override_key = {
|
||||||
|
.key_combination = KEY_L,
|
||||||
|
.override_by_default = true
|
||||||
|
},
|
||||||
|
.title_id = 0x010000000000100D,
|
||||||
|
.override_any_app = false
|
||||||
|
};
|
||||||
|
|
||||||
/* Static buffer for loader.ini contents at runtime. */
|
/* Static buffer for loader.ini contents at runtime. */
|
||||||
static char g_config_ini_data[0x800];
|
static char g_config_ini_data[0x800];
|
||||||
@@ -121,7 +137,7 @@ void Utils::InitializeThreadFunc(void *args) {
|
|||||||
|
|
||||||
if (!has_auto_backup) {
|
if (!has_auto_backup) {
|
||||||
fsFileSetSize(&g_cal0_file, ProdinfoSize);
|
fsFileSetSize(&g_cal0_file, ProdinfoSize);
|
||||||
fsFileWrite(&g_cal0_file, 0, g_cal0_backup, ProdinfoSize);
|
fsFileWrite(&g_cal0_file, 0, g_cal0_storage_backup, ProdinfoSize);
|
||||||
fsFileFlush(&g_cal0_file);
|
fsFileFlush(&g_cal0_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +223,6 @@ void Utils::InitializeThreadFunc(void *args) {
|
|||||||
|
|
||||||
hidExit();
|
hidExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
svcExitThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::IsSdInitialized() {
|
bool Utils::IsSdInitialized() {
|
||||||
@@ -368,7 +382,7 @@ Result Utils::SaveSdFileForAtmosphere(u64 title_id, const char *fn, void *data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::IsHblTid(u64 tid) {
|
bool Utils::IsHblTid(u64 tid) {
|
||||||
return (g_override_any_app && IsApplicationTid(tid)) || (!g_override_any_app && tid == g_override_hbl_tid);
|
return (g_hbl_override_config.override_any_app && IsApplicationTid(tid)) || (!g_hbl_override_config.override_any_app && tid == g_hbl_override_config.title_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::HasTitleFlag(u64 tid, const char *flag) {
|
bool Utils::HasTitleFlag(u64 tid, const char *flag) {
|
||||||
@@ -446,6 +460,13 @@ Result Utils::GetKeysDown(u64 *keys) {
|
|||||||
return 0x0;
|
return 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool HasOverrideKey(OverrideKey *cfg) {
|
||||||
|
u64 kDown = 0;
|
||||||
|
bool keys_triggered = (R_SUCCEEDED(Utils::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
||||||
|
return Utils::IsSdInitialized() && (cfg->override_by_default ^ keys_triggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Utils::HasOverrideButton(u64 tid) {
|
bool Utils::HasOverrideButton(u64 tid) {
|
||||||
if ((!IsApplicationTid(tid)) || (!IsSdInitialized())) {
|
if ((!IsApplicationTid(tid)) || (!IsSdInitialized())) {
|
||||||
/* Disable button override disable for non-applications. */
|
/* Disable button override disable for non-applications. */
|
||||||
@@ -455,71 +476,88 @@ bool Utils::HasOverrideButton(u64 tid) {
|
|||||||
/* Unconditionally refresh loader.ini contents. */
|
/* Unconditionally refresh loader.ini contents. */
|
||||||
RefreshConfiguration();
|
RefreshConfiguration();
|
||||||
|
|
||||||
u64 kDown = 0;
|
if (IsHblTid(tid) && HasOverrideKey(&g_hbl_override_config.override_key)) {
|
||||||
bool keys_triggered = (R_SUCCEEDED(GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0));
|
return true;
|
||||||
return IsSdInitialized() && (g_override_by_default ^ keys_triggered);
|
}
|
||||||
|
|
||||||
|
OverrideKey title_cfg = GetTitleOverrideKey(tid);
|
||||||
|
return HasOverrideKey(&title_cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int FsMitMIniHandler(void *user, const char *section, const char *name, const char *value) {
|
static OverrideKey ParseOverrideKey(const char *value) {
|
||||||
|
OverrideKey cfg;
|
||||||
|
|
||||||
|
/* Parse on by default. */
|
||||||
|
if (value[0] == '!') {
|
||||||
|
cfg.override_by_default = true;
|
||||||
|
value++;
|
||||||
|
} else {
|
||||||
|
cfg.override_by_default = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse key combination. */
|
||||||
|
if (strcasecmp(value, "A") == 0) {
|
||||||
|
cfg.key_combination = KEY_A;
|
||||||
|
} else if (strcasecmp(value, "B") == 0) {
|
||||||
|
cfg.key_combination = KEY_B;
|
||||||
|
} else if (strcasecmp(value, "X") == 0) {
|
||||||
|
cfg.key_combination = KEY_X;
|
||||||
|
} else if (strcasecmp(value, "Y") == 0) {
|
||||||
|
cfg.key_combination = KEY_Y;
|
||||||
|
} else if (strcasecmp(value, "LS") == 0) {
|
||||||
|
cfg.key_combination = KEY_LSTICK;
|
||||||
|
} else if (strcasecmp(value, "RS") == 0) {
|
||||||
|
cfg.key_combination = KEY_RSTICK;
|
||||||
|
} else if (strcasecmp(value, "L") == 0) {
|
||||||
|
cfg.key_combination = KEY_L;
|
||||||
|
} else if (strcasecmp(value, "R") == 0) {
|
||||||
|
cfg.key_combination = KEY_R;
|
||||||
|
} else if (strcasecmp(value, "ZL") == 0) {
|
||||||
|
cfg.key_combination = KEY_ZL;
|
||||||
|
} else if (strcasecmp(value, "ZR") == 0) {
|
||||||
|
cfg.key_combination = KEY_ZR;
|
||||||
|
} else if (strcasecmp(value, "PLUS") == 0) {
|
||||||
|
cfg.key_combination = KEY_PLUS;
|
||||||
|
} else if (strcasecmp(value, "MINUS") == 0) {
|
||||||
|
cfg.key_combination = KEY_MINUS;
|
||||||
|
} else if (strcasecmp(value, "DLEFT") == 0) {
|
||||||
|
cfg.key_combination = KEY_DLEFT;
|
||||||
|
} else if (strcasecmp(value, "DUP") == 0) {
|
||||||
|
cfg.key_combination = KEY_DUP;
|
||||||
|
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
||||||
|
cfg.key_combination = KEY_DRIGHT;
|
||||||
|
} else if (strcasecmp(value, "DDOWN") == 0) {
|
||||||
|
cfg.key_combination = KEY_DDOWN;
|
||||||
|
} else if (strcasecmp(value, "SL") == 0) {
|
||||||
|
cfg.key_combination = KEY_SL;
|
||||||
|
} else if (strcasecmp(value, "SR") == 0) {
|
||||||
|
cfg.key_combination = KEY_SR;
|
||||||
|
} else {
|
||||||
|
cfg.key_combination = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FsMitmIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||||
/* Taken and modified, with love, from Rajkosto's implementation. */
|
/* Taken and modified, with love, from Rajkosto's implementation. */
|
||||||
if (strcasecmp(section, "config") == 0) {
|
if (strcasecmp(section, "hbl_config") == 0) {
|
||||||
if (strcasecmp(name, "hbl_tid") == 0) {
|
if (strcasecmp(name, "title_id") == 0) {
|
||||||
if (strcasecmp(value, "app") == 0) {
|
if (strcasecmp(value, "app") == 0) {
|
||||||
g_override_any_app = true;
|
g_hbl_override_config.override_any_app = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
u64 override_tid = strtoul(value, NULL, 16);
|
u64 override_tid = strtoul(value, NULL, 16);
|
||||||
if (override_tid != 0) {
|
if (override_tid != 0) {
|
||||||
g_override_hbl_tid = override_tid;
|
g_hbl_override_config.title_id = override_tid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (strcasecmp(name, "override_key") == 0) {
|
} else if (strcasecmp(name, "override_key") == 0) {
|
||||||
if (value[0] == '!') {
|
g_hbl_override_config.override_key = ParseOverrideKey(value);
|
||||||
g_override_by_default = true;
|
|
||||||
value++;
|
|
||||||
} else {
|
|
||||||
g_override_by_default = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcasecmp(value, "A") == 0) {
|
|
||||||
g_override_key_combination = KEY_A;
|
|
||||||
} else if (strcasecmp(value, "B") == 0) {
|
|
||||||
g_override_key_combination = KEY_B;
|
|
||||||
} else if (strcasecmp(value, "X") == 0) {
|
|
||||||
g_override_key_combination = KEY_X;
|
|
||||||
} else if (strcasecmp(value, "Y") == 0) {
|
|
||||||
g_override_key_combination = KEY_Y;
|
|
||||||
} else if (strcasecmp(value, "LS") == 0) {
|
|
||||||
g_override_key_combination = KEY_LSTICK;
|
|
||||||
} else if (strcasecmp(value, "RS") == 0) {
|
|
||||||
g_override_key_combination = KEY_RSTICK;
|
|
||||||
} else if (strcasecmp(value, "L") == 0) {
|
|
||||||
g_override_key_combination = KEY_L;
|
|
||||||
} else if (strcasecmp(value, "R") == 0) {
|
|
||||||
g_override_key_combination = KEY_R;
|
|
||||||
} else if (strcasecmp(value, "ZL") == 0) {
|
|
||||||
g_override_key_combination = KEY_ZL;
|
|
||||||
} else if (strcasecmp(value, "ZR") == 0) {
|
|
||||||
g_override_key_combination = KEY_ZR;
|
|
||||||
} else if (strcasecmp(value, "PLUS") == 0) {
|
|
||||||
g_override_key_combination = KEY_PLUS;
|
|
||||||
} else if (strcasecmp(value, "MINUS") == 0) {
|
|
||||||
g_override_key_combination = KEY_MINUS;
|
|
||||||
} else if (strcasecmp(value, "DLEFT") == 0) {
|
|
||||||
g_override_key_combination = KEY_DLEFT;
|
|
||||||
} else if (strcasecmp(value, "DUP") == 0) {
|
|
||||||
g_override_key_combination = KEY_DUP;
|
|
||||||
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
|
||||||
g_override_key_combination = KEY_DRIGHT;
|
|
||||||
} else if (strcasecmp(value, "DDOWN") == 0) {
|
|
||||||
g_override_key_combination = KEY_DDOWN;
|
|
||||||
} else if (strcasecmp(value, "SL") == 0) {
|
|
||||||
g_override_key_combination = KEY_SL;
|
|
||||||
} else if (strcasecmp(value, "SR") == 0) {
|
|
||||||
g_override_key_combination = KEY_SR;
|
|
||||||
} else {
|
|
||||||
g_override_key_combination = 0;
|
|
||||||
}
|
}
|
||||||
|
} else if (strcasecmp(section, "default_config") == 0) {
|
||||||
|
if (strcasecmp(name, "override_key") == 0) {
|
||||||
|
g_default_override_key = ParseOverrideKey(value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -527,6 +565,46 @@ static int FsMitMIniHandler(void *user, const char *section, const char *name, c
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int FsMitmTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||||
|
/* We'll output an override key when relevant. */
|
||||||
|
OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
|
||||||
|
|
||||||
|
if (strcasecmp(section, "override_config") == 0) {
|
||||||
|
if (strcasecmp(name, "override_key") == 0) {
|
||||||
|
*user_cfg = ParseOverrideKey(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OverrideKey Utils::GetTitleOverrideKey(u64 tid) {
|
||||||
|
OverrideKey cfg = g_default_override_key;
|
||||||
|
char path[FS_MAX_PATH+1] = {0};
|
||||||
|
snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/config.ini", tid);
|
||||||
|
FsFile cfg_file;
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ, &cfg_file))) {
|
||||||
|
ON_SCOPE_EXIT { fsFileClose(&cfg_file); };
|
||||||
|
|
||||||
|
size_t config_file_size = 0x20000;
|
||||||
|
fsFileGetSize(&cfg_file, &config_file_size);
|
||||||
|
|
||||||
|
char *config_buf = reinterpret_cast<char *>(calloc(1, config_file_size + 1));
|
||||||
|
if (config_buf != NULL) {
|
||||||
|
ON_SCOPE_EXIT { free(config_buf); };
|
||||||
|
|
||||||
|
/* Read title ini contents. */
|
||||||
|
fsFileRead(&cfg_file, 0, config_buf, config_file_size, &config_file_size);
|
||||||
|
|
||||||
|
/* Parse title ini. */
|
||||||
|
ini_parse_string(config_buf, FsMitmTitleSpecificIniHandler, &cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
void Utils::RefreshConfiguration() {
|
void Utils::RefreshConfiguration() {
|
||||||
FsFile config_file;
|
FsFile config_file;
|
||||||
@@ -547,7 +625,7 @@ void Utils::RefreshConfiguration() {
|
|||||||
fsFileRead(&config_file, 0, g_config_ini_data, size, &r_s);
|
fsFileRead(&config_file, 0, g_config_ini_data, size, &r_s);
|
||||||
fsFileClose(&config_file);
|
fsFileClose(&config_file);
|
||||||
|
|
||||||
ini_parse_string(g_config_ini_data, FsMitMIniHandler, NULL);
|
ini_parse_string(g_config_ini_data, FsMitmIniHandler, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Utils::GetSettingsItemValueSize(const char *name, const char *key, u64 *out_size) {
|
Result Utils::GetSettingsItemValueSize(const char *name, const char *key, u64 *out_size) {
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ enum BisStorageId : u32 {
|
|||||||
BisStorageId_SystemProperPartition = 33,
|
BisStorageId_SystemProperPartition = 33,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OverrideKey {
|
||||||
|
u64 key_combination;
|
||||||
|
bool override_by_default;
|
||||||
|
};
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
public:
|
public:
|
||||||
static bool IsSdInitialized();
|
static bool IsSdInitialized();
|
||||||
@@ -72,6 +77,8 @@ class Utils {
|
|||||||
|
|
||||||
static bool IsHidAvailable();
|
static bool IsHidAvailable();
|
||||||
static Result GetKeysDown(u64 *keys);
|
static Result GetKeysDown(u64 *keys);
|
||||||
|
|
||||||
|
static OverrideKey GetTitleOverrideKey(u64 tid);
|
||||||
static bool HasOverrideButton(u64 tid);
|
static bool HasOverrideButton(u64 tid);
|
||||||
|
|
||||||
/* Settings! */
|
/* Settings! */
|
||||||
|
|||||||
166
stratosphere/dmnt/Makefile
Normal file
166
stratosphere/dmnt/Makefile
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
.SUFFIXES:
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ifeq ($(strip $(DEVKITPRO)),)
|
||||||
|
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||||
|
endif
|
||||||
|
|
||||||
|
TOPDIR ?= $(CURDIR)
|
||||||
|
include $(DEVKITPRO)/libnx/switch_rules
|
||||||
|
|
||||||
|
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||||
|
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||||
|
AMSREV := $(AMSREV)-dirty
|
||||||
|
endif
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# TARGET is the name of the output
|
||||||
|
# BUILD is the directory where object files & intermediate files will be placed
|
||||||
|
# SOURCES is a list of directories containing source code
|
||||||
|
# DATA is a list of directories containing data files
|
||||||
|
# INCLUDES is a list of directories containing header files
|
||||||
|
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
TARGET := $(notdir $(CURDIR))
|
||||||
|
BUILD := build
|
||||||
|
SOURCES := source
|
||||||
|
DATA := data
|
||||||
|
INCLUDES := include ../../common/include
|
||||||
|
EXEFS_SRC := exefs_src
|
||||||
|
|
||||||
|
DEFINES := -DDISABLE_IPC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# options for code generation
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
|
||||||
|
|
||||||
|
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||||
|
$(ARCH) $(DEFINES)
|
||||||
|
|
||||||
|
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||||
|
|
||||||
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
||||||
|
|
||||||
|
ASFLAGS := -g $(ARCH)
|
||||||
|
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
|
LIBS := -lstratosphere -lnx
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# list of directories containing libraries, this must be the top level containing
|
||||||
|
# include and lib
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# no real need to edit anything past this point unless you need to add additional
|
||||||
|
# rules for different file extensions
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
|
export TOPDIR := $(CURDIR)
|
||||||
|
|
||||||
|
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||||
|
|
||||||
|
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||||
|
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||||
|
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||||
|
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# use CXX for linking C++ projects, CC for standard C
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifeq ($(strip $(CPPFILES)),)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CC)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CXX)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OFILES := $(addsuffix .o,$(BINFILES)) \
|
||||||
|
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||||
|
|
||||||
|
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||||
|
-I$(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||||
|
|
||||||
|
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
|
||||||
|
|
||||||
|
ifeq ($(strip $(CONFIG_JSON)),)
|
||||||
|
jsons := $(wildcard *.json)
|
||||||
|
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||||
|
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||||
|
else
|
||||||
|
ifneq (,$(findstring config.json,$(jsons)))
|
||||||
|
export APP_JSON := $(TOPDIR)/config.json
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: $(BUILD) clean all
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
all: $(BUILD)
|
||||||
|
|
||||||
|
$(BUILD):
|
||||||
|
@[ -d $@ ] || mkdir -p $@
|
||||||
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
clean:
|
||||||
|
@echo clean ...
|
||||||
|
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
.PHONY: all
|
||||||
|
|
||||||
|
DEPENDS := $(OFILES:.o=.d)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# main targets
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
all : $(OUTPUT).nsp
|
||||||
|
|
||||||
|
ifeq ($(strip $(APP_JSON)),)
|
||||||
|
$(OUTPUT).nsp : $(OUTPUT).nso
|
||||||
|
else
|
||||||
|
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(OUTPUT).nso : $(OUTPUT).elf
|
||||||
|
|
||||||
|
$(OUTPUT).elf : $(OFILES)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# you need a rule like this for each extension you use as binary data
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
%.bin.o : %.bin
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
@echo $(notdir $<)
|
||||||
|
@$(bin2o)
|
||||||
|
|
||||||
|
-include $(DEPENDS)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
120
stratosphere/dmnt/dmnt.json
Normal file
120
stratosphere/dmnt/dmnt.json
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"name": "dmnt",
|
||||||
|
"title_id": "0x010000000000000d",
|
||||||
|
"title_id_range_min": "0x010000000000000d",
|
||||||
|
"title_id_range_max": "0x010000000000000d",
|
||||||
|
"main_thread_stack_size": "0x00004000",
|
||||||
|
"main_thread_priority": 39,
|
||||||
|
"default_cpu_id": 3,
|
||||||
|
"process_category": 0,
|
||||||
|
"is_retail": true,
|
||||||
|
"pool_partition": 2,
|
||||||
|
"is_64_bit": true,
|
||||||
|
"address_space_type": 1,
|
||||||
|
"filesystem_access": {
|
||||||
|
"permissions": "0xFFFFFFFFFFFFFFFF"
|
||||||
|
},
|
||||||
|
"service_access": [
|
||||||
|
"pm:dmnt",
|
||||||
|
"ldr:dmnt",
|
||||||
|
"ro:dmnt",
|
||||||
|
"ns:dev",
|
||||||
|
"spl:",
|
||||||
|
"lr",
|
||||||
|
"htc",
|
||||||
|
"bsd:s",
|
||||||
|
"sfdnsres",
|
||||||
|
"bsdcfg",
|
||||||
|
"set",
|
||||||
|
"fsp-srv",
|
||||||
|
"fatal:u",
|
||||||
|
"hid"
|
||||||
|
],
|
||||||
|
"service_host": [
|
||||||
|
"dmnt:-",
|
||||||
|
"dmnt:cht"
|
||||||
|
],
|
||||||
|
"kernel_capabilities": [{
|
||||||
|
"type": "kernel_flags",
|
||||||
|
"value": {
|
||||||
|
"highest_thread_priority": 63,
|
||||||
|
"lowest_thread_priority": 24,
|
||||||
|
"lowest_cpu_id": 0,
|
||||||
|
"highest_cpu_id": 3
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "syscalls",
|
||||||
|
"value": {
|
||||||
|
"svcSetHeapSize": "0x01",
|
||||||
|
"svcSetMemoryPermission": "0x02",
|
||||||
|
"svcSetMemoryAttribute": "0x03",
|
||||||
|
"svcMapMemory": "0x04",
|
||||||
|
"svcUnmapMemory": "0x05",
|
||||||
|
"svcQueryMemory": "0x06",
|
||||||
|
"svcExitProcess": "0x07",
|
||||||
|
"svcCreateThread": "0x08",
|
||||||
|
"svcStartThread": "0x09",
|
||||||
|
"svcExitThread": "0x0a",
|
||||||
|
"svcSleepThread": "0x0b",
|
||||||
|
"svcGetThreadPriority": "0x0c",
|
||||||
|
"svcSetThreadPriority": "0x0d",
|
||||||
|
"svcGetThreadCoreMask": "0x0e",
|
||||||
|
"svcSetThreadCoreMask": "0x0f",
|
||||||
|
"svcGetCurrentProcessorNumber": "0x10",
|
||||||
|
"svcSignalEvent": "0x11",
|
||||||
|
"svcClearEvent": "0x12",
|
||||||
|
"svcMapSharedMemory": "0x13",
|
||||||
|
"svcUnmapSharedMemory": "0x14",
|
||||||
|
"svcCreateTransferMemory": "0x15",
|
||||||
|
"svcCloseHandle": "0x16",
|
||||||
|
"svcResetSignal": "0x17",
|
||||||
|
"svcWaitSynchronization": "0x18",
|
||||||
|
"svcCancelSynchronization": "0x19",
|
||||||
|
"svcArbitrateLock": "0x1a",
|
||||||
|
"svcArbitrateUnlock": "0x1b",
|
||||||
|
"svcWaitProcessWideKeyAtomic": "0x1c",
|
||||||
|
"svcSignalProcessWideKey": "0x1d",
|
||||||
|
"svcGetSystemTick": "0x1e",
|
||||||
|
"svcConnectToNamedPort": "0x1f",
|
||||||
|
"svcSendSyncRequestLight": "0x20",
|
||||||
|
"svcSendSyncRequest": "0x21",
|
||||||
|
"svcSendSyncRequestWithUserBuffer": "0x22",
|
||||||
|
"svcSendAsyncRequestWithUserBuffer": "0x23",
|
||||||
|
"svcGetProcessId": "0x24",
|
||||||
|
"svcGetThreadId": "0x25",
|
||||||
|
"svcBreak": "0x26",
|
||||||
|
"svcOutputDebugString": "0x27",
|
||||||
|
"svcReturnFromException": "0x28",
|
||||||
|
"svcGetInfo": "0x29",
|
||||||
|
"svcWaitForAddress": "0x34",
|
||||||
|
"svcSignalToAddress": "0x35",
|
||||||
|
"svcCreateSession": "0x40",
|
||||||
|
"svcAcceptSession": "0x41",
|
||||||
|
"svcReplyAndReceiveLight": "0x42",
|
||||||
|
"svcReplyAndReceive": "0x43",
|
||||||
|
"svcReplyAndReceiveWithUserBuffer": "0x44",
|
||||||
|
"svcCreateEvent": "0x45",
|
||||||
|
"svcDebugActiveProcess": "0x60",
|
||||||
|
"svcBreakDebugProcess": "0x61",
|
||||||
|
"svcTerminateDebugProcess": "0x62",
|
||||||
|
"svcGetDebugEvent": "0x63",
|
||||||
|
"svcContinueDebugEvent": "0x64",
|
||||||
|
"svcGetProcessList": "0x65",
|
||||||
|
"svcGetThreadList": "0x66",
|
||||||
|
"svcGetDebugThreadContext": "0x67",
|
||||||
|
"svcSetDebugThreadContext": "0x68",
|
||||||
|
"svcQueryDebugProcessMemory": "0x69",
|
||||||
|
"svcReadDebugProcessMemory": "0x6a",
|
||||||
|
"svcWriteDebugProcessMemory": "0x6b",
|
||||||
|
"svcSetHardwareBreakPoint": "0x6c",
|
||||||
|
"svcGetDebugThreadParam": "0x6d",
|
||||||
|
"svcCallSecureMonitor": "0x7f"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "min_kernel_version",
|
||||||
|
"value": "0x0030"
|
||||||
|
}, {
|
||||||
|
"type": "handle_table_size",
|
||||||
|
"value": 0
|
||||||
|
}]
|
||||||
|
}
|
||||||
854
stratosphere/dmnt/source/dmnt_cheat_manager.cpp
Normal file
854
stratosphere/dmnt/source/dmnt_cheat_manager.cpp
Normal file
@@ -0,0 +1,854 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <switch.h>
|
||||||
|
#include "dmnt_cheat_manager.hpp"
|
||||||
|
#include "dmnt_cheat_vm.hpp"
|
||||||
|
#include "dmnt_config.hpp"
|
||||||
|
#include "pm_shim.h"
|
||||||
|
|
||||||
|
static HosMutex g_cheat_lock;
|
||||||
|
static HosThread g_detect_thread, g_vm_thread;
|
||||||
|
|
||||||
|
static IEvent *g_cheat_process_event;
|
||||||
|
static DmntCheatVm *g_cheat_vm;
|
||||||
|
|
||||||
|
static CheatProcessMetadata g_cheat_process_metadata = {0};
|
||||||
|
static Handle g_cheat_process_debug_hnd = 0;
|
||||||
|
|
||||||
|
/* Global cheat entry storage. */
|
||||||
|
static CheatEntry g_cheat_entries[DmntCheatManager::MaxCheatCount];
|
||||||
|
|
||||||
|
/* Global frozen address storage. */
|
||||||
|
static std::map<u64, FrozenAddressValue> g_frozen_addresses_map;
|
||||||
|
|
||||||
|
void DmntCheatManager::CloseActiveCheatProcess() {
|
||||||
|
if (g_cheat_process_debug_hnd != 0) {
|
||||||
|
/* Close process resources. */
|
||||||
|
svcCloseHandle(g_cheat_process_debug_hnd);
|
||||||
|
g_cheat_process_debug_hnd = 0;
|
||||||
|
g_cheat_process_metadata = (CheatProcessMetadata){0};
|
||||||
|
|
||||||
|
/* Clear cheat list. */
|
||||||
|
ResetAllCheatEntries();
|
||||||
|
|
||||||
|
/* Clear frozen addresses. */
|
||||||
|
ResetFrozenAddresses();
|
||||||
|
|
||||||
|
/* Signal to our fans. */
|
||||||
|
g_cheat_process_event->Signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntCheatManager::HasActiveCheatProcess() {
|
||||||
|
u64 tmp;
|
||||||
|
bool has_cheat_process = g_cheat_process_debug_hnd != 0;
|
||||||
|
|
||||||
|
if (has_cheat_process) {
|
||||||
|
has_cheat_process &= R_SUCCEEDED(svcGetProcessId(&tmp, g_cheat_process_debug_hnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_cheat_process) {
|
||||||
|
has_cheat_process &= R_SUCCEEDED(pmdmntGetApplicationPid(&tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_cheat_process) {
|
||||||
|
has_cheat_process &= tmp == g_cheat_process_metadata.process_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_cheat_process) {
|
||||||
|
CloseActiveCheatProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
return has_cheat_process;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::ContinueCheatProcess() {
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
/* Loop getting debug events. */
|
||||||
|
u64 debug_event_buf[0x50];
|
||||||
|
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)debug_event_buf, g_cheat_process_debug_hnd))) {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue the process. */
|
||||||
|
if (kernelAbove300()) {
|
||||||
|
svcContinueDebugEvent(g_cheat_process_debug_hnd, 5, nullptr, 0);
|
||||||
|
} else {
|
||||||
|
svcLegacyContinueDebugEvent(g_cheat_process_debug_hnd, 5, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size) {
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
return svcReadDebugProcessMemory(out_data, g_cheat_process_debug_hnd, proc_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size) {
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
return svcWriteDebugProcessMemory(g_cheat_process_debug_hnd, data, proc_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetCheatProcessMappingCount(u64 *out_count) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryInfo mem_info;
|
||||||
|
|
||||||
|
u64 address = 0;
|
||||||
|
*out_count = 0;
|
||||||
|
do {
|
||||||
|
mem_info.perm = 0;
|
||||||
|
u32 tmp;
|
||||||
|
if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &tmp, g_cheat_process_debug_hnd, address))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mem_info.perm != 0) {
|
||||||
|
*out_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
address = mem_info.addr + mem_info.size;
|
||||||
|
} while (address != 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryInfo mem_info;
|
||||||
|
u64 address = 0;
|
||||||
|
u64 count = 0;
|
||||||
|
*out_count = 0;
|
||||||
|
do {
|
||||||
|
mem_info.perm = 0;
|
||||||
|
u32 tmp;
|
||||||
|
if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &tmp, g_cheat_process_debug_hnd, address))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mem_info.perm != 0) {
|
||||||
|
count++;
|
||||||
|
if (count > offset) {
|
||||||
|
mappings[(*out_count)++] = mem_info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
address = mem_info.addr + mem_info.size;
|
||||||
|
} while (address != 0 && *out_count < max_count);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
return ReadCheatProcessMemoryForVm(proc_addr, out_data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
return WriteCheatProcessMemoryForVm(proc_addr, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::QueryCheatProcessMemory(MemoryInfo *mapping, u64 address) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
u32 tmp;
|
||||||
|
return svcQueryDebugProcessMemory(mapping, &tmp, g_cheat_process_debug_hnd, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::ResetFrozenAddresses() {
|
||||||
|
/* Just clear the map. */
|
||||||
|
g_frozen_addresses_map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::ResetCheatEntry(size_t i) {
|
||||||
|
if (i < DmntCheatManager::MaxCheatCount) {
|
||||||
|
g_cheat_entries[i].enabled = false;
|
||||||
|
g_cheat_entries[i].cheat_id = i;
|
||||||
|
g_cheat_entries[i].definition = {0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::ResetAllCheatEntries() {
|
||||||
|
for (size_t i = 0; i < DmntCheatManager::MaxCheatCount; i++) {
|
||||||
|
ResetCheatEntry(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatEntry *DmntCheatManager::GetFreeCheatEntry() {
|
||||||
|
/* Check all non-master cheats for availability. */
|
||||||
|
for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) {
|
||||||
|
if (g_cheat_entries[i].definition.num_opcodes == 0) {
|
||||||
|
return &g_cheat_entries[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatEntry *DmntCheatManager::GetCheatEntryById(size_t i) {
|
||||||
|
if (i < DmntCheatManager::MaxCheatCount) {
|
||||||
|
return &g_cheat_entries[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntCheatManager::ParseCheats(const char *s, size_t len) {
|
||||||
|
size_t i = 0;
|
||||||
|
CheatEntry *cur_entry = NULL;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
if (isspace(s[i])) {
|
||||||
|
/* Just ignore space. */
|
||||||
|
i++;
|
||||||
|
} else if (s[i] == '[') {
|
||||||
|
/* Parse a readable cheat name. */
|
||||||
|
cur_entry = GetFreeCheatEntry();
|
||||||
|
if (cur_entry == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract name bounds. */
|
||||||
|
size_t j = i + 1;
|
||||||
|
while (s[j] != ']') {
|
||||||
|
j++;
|
||||||
|
if (j >= len || (j - i - 1) >= sizeof(cur_entry->definition.readable_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* s[i+1:j] is cheat name. */
|
||||||
|
const size_t cheat_name_len = (j - i - 1);
|
||||||
|
memcpy(cur_entry->definition.readable_name, &s[i+1], cheat_name_len);
|
||||||
|
cur_entry->definition.readable_name[cheat_name_len] = 0;
|
||||||
|
|
||||||
|
/* Skip onwards. */
|
||||||
|
i = j + 1;
|
||||||
|
} else if (s[i] == '{') {
|
||||||
|
/* We're parsing a master cheat. */
|
||||||
|
cur_entry = &g_cheat_entries[0];
|
||||||
|
|
||||||
|
/* There can only be one master cheat. */
|
||||||
|
if (cur_entry->definition.num_opcodes > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract name bounds */
|
||||||
|
size_t j = i + 1;
|
||||||
|
while (s[j] != '}') {
|
||||||
|
j++;
|
||||||
|
if (j >= len || (j - i - 1) >= sizeof(cur_entry->definition.readable_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* s[i+1:j] is cheat name. */
|
||||||
|
const size_t cheat_name_len = (j - i - 1);
|
||||||
|
memcpy(cur_entry->definition.readable_name, &s[i+1], cheat_name_len);
|
||||||
|
cur_entry->definition.readable_name[cheat_name_len] = 0;
|
||||||
|
|
||||||
|
/* Skip onwards. */
|
||||||
|
i = j + 1;
|
||||||
|
} else if (isxdigit(s[i])) {
|
||||||
|
/* Make sure that we have a cheat open. */
|
||||||
|
if (cur_entry == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bounds check the opcode count. */
|
||||||
|
if (cur_entry->definition.num_opcodes >= sizeof(cur_entry->definition.opcodes)/sizeof(cur_entry->definition.opcodes[0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We're parsing an instruction, so validate it's 8 hex digits. */
|
||||||
|
for (size_t j = 1; j < 8; j++) {
|
||||||
|
/* Validate 8 hex chars. */
|
||||||
|
if (i + j >= len || !isxdigit(s[i+j])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the new opcode. */
|
||||||
|
char hex_str[9] = {0};
|
||||||
|
memcpy(hex_str, &s[i], 8);
|
||||||
|
cur_entry->definition.opcodes[cur_entry->definition.num_opcodes++] = strtoul(hex_str, NULL, 16);
|
||||||
|
|
||||||
|
|
||||||
|
/* Skip onwards. */
|
||||||
|
i += 8;
|
||||||
|
} else {
|
||||||
|
/* Unexpected character encountered. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable all entries we parsed. */
|
||||||
|
for (size_t i = 0; i < DmntCheatManager::MaxCheatCount; i++) {
|
||||||
|
if (g_cheat_entries[i].definition.num_opcodes > 0) {
|
||||||
|
g_cheat_entries[i].enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntCheatManager::LoadCheats(u64 title_id, const u8 *build_id) {
|
||||||
|
/* Reset existing entries. */
|
||||||
|
ResetAllCheatEntries();
|
||||||
|
|
||||||
|
FILE *f_cht = NULL;
|
||||||
|
/* Open the file for title/build_id. */
|
||||||
|
{
|
||||||
|
char path[FS_MAX_PATH+1] = {0};
|
||||||
|
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/%02x%02x%02x%02x%02x%02x%02x%02x.txt", title_id,
|
||||||
|
build_id[0], build_id[1], build_id[2], build_id[3], build_id[4], build_id[5], build_id[6], build_id[7]);
|
||||||
|
|
||||||
|
f_cht = fopen(path, "rb");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for NULL */
|
||||||
|
if (f_cht == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ON_SCOPE_EXIT { fclose(f_cht); };
|
||||||
|
|
||||||
|
/* Get file size. */
|
||||||
|
fseek(f_cht, 0L, SEEK_END);
|
||||||
|
const size_t cht_sz = ftell(f_cht);
|
||||||
|
fseek(f_cht, 0L, SEEK_SET);
|
||||||
|
|
||||||
|
/* Allocate cheat txt buffer. */
|
||||||
|
char *cht_txt = (char *)malloc(cht_sz + 1);
|
||||||
|
if (cht_txt == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ON_SCOPE_EXIT { free(cht_txt); };
|
||||||
|
|
||||||
|
/* Read cheats into buffer. */
|
||||||
|
if (fread(cht_txt, 1, cht_sz, f_cht) != cht_sz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cht_txt[cht_sz] = 0;
|
||||||
|
|
||||||
|
/* Parse cheat buffer. */
|
||||||
|
return ParseCheats(cht_txt, strlen(cht_txt));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetCheatCount(u64 *out_count) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_count = 0;
|
||||||
|
for (size_t i = 0; i < DmntCheatManager::MaxCheatCount; i++) {
|
||||||
|
if (g_cheat_entries[i].definition.num_opcodes > 0) {
|
||||||
|
*out_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 count = 0;
|
||||||
|
*out_count = 0;
|
||||||
|
for (size_t i = 0; i < DmntCheatManager::MaxCheatCount && (*out_count) < max_count; i++) {
|
||||||
|
if (g_cheat_entries[i].definition.num_opcodes > 0) {
|
||||||
|
count++;
|
||||||
|
if (count > offset) {
|
||||||
|
cheats[(*out_count)++] = g_cheat_entries[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetCheatById(CheatEntry *out_cheat, u32 cheat_id) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheatEntry *entry = GetCheatEntryById(cheat_id);
|
||||||
|
if (entry == nullptr || entry->definition.num_opcodes == 0) {
|
||||||
|
return ResultDmntCheatUnknownChtId;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_cheat = *entry;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::ToggleCheat(u32 cheat_id) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatEntry *entry = GetCheatEntryById(cheat_id);
|
||||||
|
if (entry == nullptr || entry->definition.num_opcodes == 0) {
|
||||||
|
return ResultDmntCheatUnknownChtId;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->enabled = !entry->enabled;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::AddCheat(u32 *out_id, CheatDefinition *def, bool enabled) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def->num_opcodes == 0 || def->num_opcodes > sizeof(def->opcodes)/sizeof(def->opcodes[0])) {
|
||||||
|
return ResultDmntCheatInvalidCheat;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatEntry *new_entry = GetFreeCheatEntry();
|
||||||
|
if (new_entry == nullptr) {
|
||||||
|
return ResultDmntCheatOutOfCheats;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entry->enabled = enabled;
|
||||||
|
new_entry->definition = *def;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::RemoveCheat(u32 cheat_id) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cheat_id >= DmntCheatManager::MaxCheatCount) {
|
||||||
|
return ResultDmntCheatUnknownChtId;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetCheatEntry(cheat_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetFrozenAddressCount(u64 *out_count) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_count = g_frozen_addresses_map.size();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 count = 0;
|
||||||
|
*out_count = 0;
|
||||||
|
for (auto const& [address, value] : g_frozen_addresses_map) {
|
||||||
|
if ((*out_count) >= max_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (count > offset) {
|
||||||
|
const u64 cur_ind = (*out_count)++;
|
||||||
|
frz_addrs[cur_ind].address = address;
|
||||||
|
frz_addrs[cur_ind].value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto it = g_frozen_addresses_map.find(address);
|
||||||
|
if (it == g_frozen_addresses_map.end()) {
|
||||||
|
return ResultDmntCheatAddressNotFrozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
frz_addr->address = it->first;
|
||||||
|
frz_addr->value = it->second;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::EnableFrozenAddress(u64 *out_value, u64 address, u64 width) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_frozen_addresses_map.size() >= DmntCheatManager::MaxFrozenAddressCount) {
|
||||||
|
return ResultDmntCheatTooManyFrozenAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto it = g_frozen_addresses_map.find(address);
|
||||||
|
if (it != g_frozen_addresses_map.end()) {
|
||||||
|
return ResultDmntCheatAddressAlreadyFrozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc;
|
||||||
|
FrozenAddressValue value = {0};
|
||||||
|
value.width = width;
|
||||||
|
if (R_FAILED((rc = ReadCheatProcessMemoryForVm(address, &value.value, width)))) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_frozen_addresses_map[address] = value;
|
||||||
|
*out_value = value.value;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::DisableFrozenAddress(u64 address) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (!HasActiveCheatProcess()) {
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto it = g_frozen_addresses_map.find(address);
|
||||||
|
if (it == g_frozen_addresses_map.end()) {
|
||||||
|
return ResultDmntCheatAddressNotFrozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_frozen_addresses_map.erase(address);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle DmntCheatManager::PrepareDebugNextApplication() {
|
||||||
|
Result rc;
|
||||||
|
Handle event_h;
|
||||||
|
if (R_FAILED((rc = pmdmntEnableDebugForApplication(&event_h)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PopulateMemoryExtents(MemoryRegionExtents *extents, Handle p_h, u64 id_base, u64 id_size) {
|
||||||
|
Result rc;
|
||||||
|
/* Get base extent. */
|
||||||
|
if (R_FAILED((rc = svcGetInfo(&extents->base, id_base, p_h, 0)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get size extent. */
|
||||||
|
if (R_FAILED((rc = svcGetInfo(&extents->size, id_size, p_h, 0)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StartDebugProcess(u64 pid) {
|
||||||
|
Result rc = pmdmntStartProcess(pid);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::ForceOpenCheatProcess() {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
Result rc;
|
||||||
|
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the current application process ID. */
|
||||||
|
if (R_FAILED((rc = pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)))) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
ON_SCOPE_EXIT { if (R_FAILED(rc)) { g_cheat_process_metadata.process_id = 0; } };
|
||||||
|
|
||||||
|
/* Get process handle, use it to learn memory extents. */
|
||||||
|
{
|
||||||
|
Handle proc_h = 0;
|
||||||
|
ON_SCOPE_EXIT { if (proc_h != 0) { svcCloseHandle(proc_h); } };
|
||||||
|
|
||||||
|
if (R_FAILED((rc = pmdmntAtmosphereGetProcessInfo(&proc_h, &g_cheat_process_metadata.title_id, nullptr, g_cheat_process_metadata.process_id)))) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get memory extents. */
|
||||||
|
PopulateMemoryExtents(&g_cheat_process_metadata.heap_extents, proc_h, 4, 5);
|
||||||
|
PopulateMemoryExtents(&g_cheat_process_metadata.alias_extents, proc_h, 2, 3);
|
||||||
|
if (kernelAbove200()) {
|
||||||
|
PopulateMemoryExtents(&g_cheat_process_metadata.address_space_extents, proc_h, 12, 13);
|
||||||
|
} else {
|
||||||
|
g_cheat_process_metadata.address_space_extents.base = 0x08000000UL;
|
||||||
|
g_cheat_process_metadata.address_space_extents.size = 0x78000000UL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get module information from Loader. */
|
||||||
|
{
|
||||||
|
LoaderModuleInfo proc_modules[2];
|
||||||
|
u32 num_modules;
|
||||||
|
if (R_FAILED((rc = ldrDmntGetModuleInfos(g_cheat_process_metadata.process_id, proc_modules, sizeof(proc_modules), &num_modules)))) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All applications must have two modules. */
|
||||||
|
/* However, this is a force-open, so we will accept one module. */
|
||||||
|
/* Poor HBL, I guess... */
|
||||||
|
LoaderModuleInfo *proc_module;
|
||||||
|
if (num_modules == 2) {
|
||||||
|
proc_module = &proc_modules[1];
|
||||||
|
} else if (num_modules == 1) {
|
||||||
|
proc_module = &proc_modules[0];
|
||||||
|
} else {
|
||||||
|
rc = ResultDmntCheatNotAttached;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cheat_process_metadata.main_nso_extents.base = proc_module->base_address;
|
||||||
|
g_cheat_process_metadata.main_nso_extents.size = proc_module->size;
|
||||||
|
memcpy(g_cheat_process_metadata.main_nso_build_id, proc_module->build_id, sizeof(g_cheat_process_metadata.main_nso_build_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read cheats off the SD. */
|
||||||
|
/* This is allowed to fail. We may not have any cheats. */
|
||||||
|
LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id);
|
||||||
|
|
||||||
|
/* Open a debug handle. */
|
||||||
|
if (R_FAILED((rc = svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)))) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue debug events, etc. */
|
||||||
|
ContinueCheatProcess();
|
||||||
|
|
||||||
|
/* Signal to our fans. */
|
||||||
|
g_cheat_process_event->Signal();
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::OnNewApplicationLaunch() {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
Result rc;
|
||||||
|
|
||||||
|
/* Close the current application, if it's open. */
|
||||||
|
CloseActiveCheatProcess();
|
||||||
|
|
||||||
|
/* Get the new application's process ID. */
|
||||||
|
if (R_FAILED((rc = pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get process handle, use it to learn memory extents. */
|
||||||
|
{
|
||||||
|
Handle proc_h = 0;
|
||||||
|
ON_SCOPE_EXIT { if (proc_h != 0) { svcCloseHandle(proc_h); } };
|
||||||
|
|
||||||
|
if (R_FAILED((rc = pmdmntAtmosphereGetProcessInfo(&proc_h, &g_cheat_process_metadata.title_id, nullptr, g_cheat_process_metadata.process_id)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get memory extents. */
|
||||||
|
PopulateMemoryExtents(&g_cheat_process_metadata.heap_extents, proc_h, 4, 5);
|
||||||
|
PopulateMemoryExtents(&g_cheat_process_metadata.alias_extents, proc_h, 2, 3);
|
||||||
|
if (kernelAbove200()) {
|
||||||
|
PopulateMemoryExtents(&g_cheat_process_metadata.address_space_extents, proc_h, 12, 13);
|
||||||
|
} else {
|
||||||
|
g_cheat_process_metadata.address_space_extents.base = 0x08000000UL;
|
||||||
|
g_cheat_process_metadata.address_space_extents.size = 0x78000000UL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we should skip based on keys down. */
|
||||||
|
if (!DmntConfigManager::HasCheatEnableButton(g_cheat_process_metadata.title_id)) {
|
||||||
|
StartDebugProcess(g_cheat_process_metadata.process_id);
|
||||||
|
g_cheat_process_metadata.process_id = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get module information from Loader. */
|
||||||
|
{
|
||||||
|
LoaderModuleInfo proc_modules[2];
|
||||||
|
u32 num_modules;
|
||||||
|
if (R_FAILED((rc = ldrDmntGetModuleInfos(g_cheat_process_metadata.process_id, proc_modules, sizeof(proc_modules), &num_modules)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All applications must have two modules. */
|
||||||
|
/* If we only have one, we must be e.g. mitming HBL. */
|
||||||
|
/* We don't want to fuck with HBL. */
|
||||||
|
if (num_modules != 2) {
|
||||||
|
StartDebugProcess(g_cheat_process_metadata.process_id);
|
||||||
|
g_cheat_process_metadata.process_id = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cheat_process_metadata.main_nso_extents.base = proc_modules[1].base_address;
|
||||||
|
g_cheat_process_metadata.main_nso_extents.size = proc_modules[1].size;
|
||||||
|
memcpy(g_cheat_process_metadata.main_nso_build_id, proc_modules[1].build_id, sizeof(g_cheat_process_metadata.main_nso_build_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read cheats off the SD. */
|
||||||
|
if (!LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id)) {
|
||||||
|
/* If we don't have cheats, or cheats are malformed, don't attach. */
|
||||||
|
StartDebugProcess(g_cheat_process_metadata.process_id);
|
||||||
|
g_cheat_process_metadata.process_id = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open a debug handle. */
|
||||||
|
if (R_FAILED((rc = svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start the process. */
|
||||||
|
StartDebugProcess(g_cheat_process_metadata.process_id);
|
||||||
|
|
||||||
|
/* Continue debug events, etc. */
|
||||||
|
ContinueCheatProcess();
|
||||||
|
|
||||||
|
/* Signal to our fans. */
|
||||||
|
g_cheat_process_event->Signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::DetectThread(void *arg) {
|
||||||
|
auto waiter = new WaitableManager(1);
|
||||||
|
waiter->AddWaitable(LoadReadOnlySystemEvent(PrepareDebugNextApplication(), [](u64 timeout) {
|
||||||
|
/* Process stuff for new application. */
|
||||||
|
DmntCheatManager::OnNewApplicationLaunch();
|
||||||
|
|
||||||
|
/* Setup detection for the next application, and close the duplicate handle. */
|
||||||
|
svcCloseHandle(PrepareDebugNextApplication());
|
||||||
|
|
||||||
|
return 0x0;
|
||||||
|
}, true));
|
||||||
|
|
||||||
|
waiter->Process();
|
||||||
|
delete waiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::VmThread(void *arg) {
|
||||||
|
while (true) {
|
||||||
|
/* Execute Cheat VM. */
|
||||||
|
{
|
||||||
|
/* Acquire lock. */
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
/* Handle any pending debug events. */
|
||||||
|
ContinueCheatProcess();
|
||||||
|
|
||||||
|
/* Execute VM. */
|
||||||
|
if (g_cheat_vm->LoadProgram(g_cheat_entries, DmntCheatManager::MaxCheatCount)) {
|
||||||
|
if (g_cheat_vm->GetProgramSize() != 0) {
|
||||||
|
g_cheat_vm->Execute(&g_cheat_process_metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply frozen addresses. */
|
||||||
|
for (auto const& [address, value] : g_frozen_addresses_map) {
|
||||||
|
WriteCheatProcessMemoryForVm(address, &value.value, value.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svcSleepThread(0x5000000ul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntCheatManager::GetHasActiveCheatProcess() {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
return HasActiveCheatProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle DmntCheatManager::GetCheatProcessEventHandle() {
|
||||||
|
return g_cheat_process_event->GetHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatManager::GetCheatProcessMetadata(CheatProcessMetadata *out) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||||
|
|
||||||
|
if (HasActiveCheatProcess()) {
|
||||||
|
*out = g_cheat_process_metadata;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatManager::InitializeCheatManager() {
|
||||||
|
/* Create cheat process detection event. */
|
||||||
|
g_cheat_process_event = CreateWriteOnlySystemEvent();
|
||||||
|
|
||||||
|
/* Create cheat vm. */
|
||||||
|
g_cheat_vm = new DmntCheatVm();
|
||||||
|
|
||||||
|
/* Spawn application detection thread, spawn cheat vm thread. */
|
||||||
|
if (R_FAILED(g_detect_thread.Initialize(&DmntCheatManager::DetectThread, nullptr, 0x4000, 28))) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (R_FAILED(g_vm_thread.Initialize(&DmntCheatManager::VmThread, nullptr, 0x4000, 28))) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start threads. */
|
||||||
|
if (R_FAILED(g_detect_thread.Start()) || R_FAILED(g_vm_thread.Start())) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
74
stratosphere/dmnt/source/dmnt_cheat_manager.hpp
Normal file
74
stratosphere/dmnt/source/dmnt_cheat_manager.hpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_cheat_types.hpp"
|
||||||
|
|
||||||
|
class DmntCheatManager {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MaxCheatCount = 0x80;
|
||||||
|
static constexpr size_t MaxFrozenAddressCount = 0x80;
|
||||||
|
private:
|
||||||
|
static Handle PrepareDebugNextApplication();
|
||||||
|
static void OnNewApplicationLaunch();
|
||||||
|
static void DetectThread(void *arg);
|
||||||
|
static void VmThread(void *arg);
|
||||||
|
|
||||||
|
static bool HasActiveCheatProcess();
|
||||||
|
static void CloseActiveCheatProcess();
|
||||||
|
static void ContinueCheatProcess();
|
||||||
|
|
||||||
|
static void ResetCheatEntry(size_t i);
|
||||||
|
static void ResetAllCheatEntries();
|
||||||
|
static CheatEntry *GetFreeCheatEntry();
|
||||||
|
static CheatEntry *GetCheatEntryById(size_t i);
|
||||||
|
static bool ParseCheats(const char *cht_txt, size_t len);
|
||||||
|
static bool LoadCheats(u64 title_id, const u8 *build_id);
|
||||||
|
|
||||||
|
static void ResetFrozenAddresses();
|
||||||
|
public:
|
||||||
|
static bool GetHasActiveCheatProcess();
|
||||||
|
static Handle GetCheatProcessEventHandle();
|
||||||
|
static Result GetCheatProcessMetadata(CheatProcessMetadata *out);
|
||||||
|
static Result ForceOpenCheatProcess();
|
||||||
|
|
||||||
|
static Result ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size);
|
||||||
|
static Result WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size);
|
||||||
|
|
||||||
|
static Result GetCheatProcessMappingCount(u64 *out_count);
|
||||||
|
static Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset);
|
||||||
|
static Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size);
|
||||||
|
static Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size);
|
||||||
|
static Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address);
|
||||||
|
|
||||||
|
static Result GetCheatCount(u64 *out_count);
|
||||||
|
static Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset);
|
||||||
|
static Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id);
|
||||||
|
static Result ToggleCheat(u32 cheat_id);
|
||||||
|
static Result AddCheat(u32 *out_id, CheatDefinition *def, bool enabled);
|
||||||
|
static Result RemoveCheat(u32 cheat_id);
|
||||||
|
|
||||||
|
static Result GetFrozenAddressCount(u64 *out_count);
|
||||||
|
static Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset);
|
||||||
|
static Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address);
|
||||||
|
static Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width);
|
||||||
|
static Result DisableFrozenAddress(u64 address);
|
||||||
|
|
||||||
|
static void InitializeCheatManager();
|
||||||
|
};
|
||||||
177
stratosphere/dmnt/source/dmnt_cheat_service.cpp
Normal file
177
stratosphere/dmnt/source/dmnt_cheat_service.cpp
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "dmnt_cheat_service.hpp"
|
||||||
|
#include "dmnt_cheat_manager.hpp"
|
||||||
|
|
||||||
|
/* ========================================================================================= */
|
||||||
|
/* ==================================== Meta Commands ==================================== */
|
||||||
|
/* ========================================================================================= */
|
||||||
|
|
||||||
|
void DmntCheatService::HasCheatProcess(Out<bool> out) {
|
||||||
|
out.SetValue(DmntCheatManager::GetHasActiveCheatProcess());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatService::GetCheatProcessEvent(Out<CopiedHandle> out_event) {
|
||||||
|
out_event.SetValue(DmntCheatManager::GetCheatProcessEventHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
|
||||||
|
return DmntCheatManager::GetCheatProcessMetadata(out_metadata.GetPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::ForceOpenCheatProcess() {
|
||||||
|
Result rc = DmntCheatManager::ForceOpenCheatProcess();
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
rc = ResultDmntCheatNotAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================================= */
|
||||||
|
/* =================================== Memory Commands =================================== */
|
||||||
|
/* ========================================================================================= */
|
||||||
|
|
||||||
|
Result DmntCheatService::GetCheatProcessMappingCount(Out<u64> out_count) {
|
||||||
|
return DmntCheatManager::GetCheatProcessMappingCount(out_count.GetPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::GetCheatProcessMappings(OutBuffer<MemoryInfo> mappings, Out<u64> out_count, u64 offset) {
|
||||||
|
if (mappings.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::GetCheatProcessMappings(mappings.buffer, mappings.num_elements, out_count.GetPointer(), offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::ReadCheatProcessMemory(OutBuffer<u8> buffer, u64 address, u64 out_size) {
|
||||||
|
if (buffer.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 sz = out_size;
|
||||||
|
if (buffer.num_elements < sz) {
|
||||||
|
sz = buffer.num_elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::ReadCheatProcessMemory(address, buffer.buffer, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::WriteCheatProcessMemory(InBuffer<u8> buffer, u64 address, u64 in_size) {
|
||||||
|
if (buffer.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 sz = in_size;
|
||||||
|
if (buffer.num_elements < sz) {
|
||||||
|
sz = buffer.num_elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::WriteCheatProcessMemory(address, buffer.buffer, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::QueryCheatProcessMemory(Out<MemoryInfo> mapping, u64 address) {
|
||||||
|
return DmntCheatManager::QueryCheatProcessMemory(mapping.GetPointer(), address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================================= */
|
||||||
|
/* =================================== Cheat Commands ==================================== */
|
||||||
|
/* ========================================================================================= */
|
||||||
|
|
||||||
|
Result DmntCheatService::GetCheatCount(Out<u64> out_count) {
|
||||||
|
return DmntCheatManager::GetCheatCount(out_count.GetPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::GetCheats(OutBuffer<CheatEntry> cheats, Out<u64> out_count, u64 offset) {
|
||||||
|
if (cheats.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::GetCheats(cheats.buffer, cheats.num_elements, out_count.GetPointer(), offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::GetCheatById(OutBuffer<CheatEntry> cheat, u32 cheat_id) {
|
||||||
|
if (cheat.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cheat.num_elements < 1) {
|
||||||
|
return ResultDmntCheatInvalidBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::GetCheatById(cheat.buffer, cheat_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::ToggleCheat(u32 cheat_id) {
|
||||||
|
return DmntCheatManager::ToggleCheat(cheat_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::AddCheat(InBuffer<CheatDefinition> cheat, Out<u32> out_cheat_id, bool enabled) {
|
||||||
|
if (cheat.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cheat.num_elements < 1) {
|
||||||
|
return ResultDmntCheatInvalidBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::AddCheat(out_cheat_id.GetPointer(), cheat.buffer, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::RemoveCheat(u32 cheat_id) {
|
||||||
|
return DmntCheatManager::RemoveCheat(cheat_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================================= */
|
||||||
|
/* =================================== Address Commands ================================== */
|
||||||
|
/* ========================================================================================= */
|
||||||
|
|
||||||
|
Result DmntCheatService::GetFrozenAddressCount(Out<u64> out_count) {
|
||||||
|
return DmntCheatManager::GetFrozenAddressCount(out_count.GetPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::GetFrozenAddresses(OutBuffer<FrozenAddressEntry> frz_addrs, Out<u64> out_count, u64 offset) {
|
||||||
|
if (frz_addrs.buffer == nullptr) {
|
||||||
|
return ResultDmntCheatNullBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::GetFrozenAddresses(frz_addrs.buffer, frz_addrs.num_elements, out_count.GetPointer(), offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::GetFrozenAddress(Out<FrozenAddressEntry> entry, u64 address) {
|
||||||
|
return DmntCheatManager::GetFrozenAddress(entry.GetPointer(), address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
|
||||||
|
switch (width) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return ResultDmntCheatInvalidFreezeWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DmntCheatManager::EnableFrozenAddress(out_value.GetPointer(), address, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DmntCheatService::DisableFrozenAddress(u64 address) {
|
||||||
|
return DmntCheatManager::DisableFrozenAddress(address);
|
||||||
|
}
|
||||||
105
stratosphere/dmnt/source/dmnt_cheat_service.hpp
Normal file
105
stratosphere/dmnt/source/dmnt_cheat_service.hpp
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_cheat_types.hpp"
|
||||||
|
|
||||||
|
enum DmntCheatCmd {
|
||||||
|
/* Meta */
|
||||||
|
DmntCheat_Cmd_HasCheatProcess = 65000,
|
||||||
|
DmntCheat_Cmd_GetCheatProcessEvent = 65001,
|
||||||
|
DmntCheat_Cmd_GetCheatProcessMetadata = 65002,
|
||||||
|
DmntCheat_Cmd_ForceOpenCheatProcess = 65003,
|
||||||
|
|
||||||
|
/* Interact with Memory */
|
||||||
|
DmntCheat_Cmd_GetCheatProcessMappingCount = 65100,
|
||||||
|
DmntCheat_Cmd_GetCheatProcessMappings = 65101,
|
||||||
|
DmntCheat_Cmd_ReadCheatProcessMemory = 65102,
|
||||||
|
DmntCheat_Cmd_WriteCheatProcessMemory = 65103,
|
||||||
|
DmntCheat_Cmd_QueryCheatProcessMemory = 65104,
|
||||||
|
|
||||||
|
/* Interact with Cheats */
|
||||||
|
DmntCheat_Cmd_GetCheatCount = 65200,
|
||||||
|
DmntCheat_Cmd_GetCheats = 65201,
|
||||||
|
DmntCheat_Cmd_GetCheatById = 65202,
|
||||||
|
DmntCheat_Cmd_ToggleCheat = 65203,
|
||||||
|
DmntCheat_Cmd_AddCheat = 65204,
|
||||||
|
DmntCheat_Cmd_RemoveCheat = 65205,
|
||||||
|
|
||||||
|
/* Interact with Frozen Addresses */
|
||||||
|
DmntCheat_Cmd_GetFrozenAddressCount = 65300,
|
||||||
|
DmntCheat_Cmd_GetFrozenAddresses = 65301,
|
||||||
|
DmntCheat_Cmd_GetFrozenAddress = 65302,
|
||||||
|
DmntCheat_Cmd_EnableFrozenAddress = 65303,
|
||||||
|
DmntCheat_Cmd_DisableFrozenAddress = 65304,
|
||||||
|
};
|
||||||
|
|
||||||
|
class DmntCheatService final : public IServiceObject {
|
||||||
|
private:
|
||||||
|
void HasCheatProcess(Out<bool> out);
|
||||||
|
void GetCheatProcessEvent(Out<CopiedHandle> out_event);
|
||||||
|
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
|
||||||
|
Result ForceOpenCheatProcess();
|
||||||
|
|
||||||
|
Result GetCheatProcessMappingCount(Out<u64> out_count);
|
||||||
|
Result GetCheatProcessMappings(OutBuffer<MemoryInfo> mappings, Out<u64> out_count, u64 offset);
|
||||||
|
Result ReadCheatProcessMemory(OutBuffer<u8> buffer, u64 address, u64 out_size);
|
||||||
|
Result WriteCheatProcessMemory(InBuffer<u8> buffer, u64 address, u64 in_size);
|
||||||
|
Result QueryCheatProcessMemory(Out<MemoryInfo> mapping, u64 address);
|
||||||
|
|
||||||
|
Result GetCheatCount(Out<u64> out_count);
|
||||||
|
Result GetCheats(OutBuffer<CheatEntry> cheats, Out<u64> out_count, u64 offset);
|
||||||
|
Result GetCheatById(OutBuffer<CheatEntry> cheat, u32 cheat_id);
|
||||||
|
Result ToggleCheat(u32 cheat_id);
|
||||||
|
Result AddCheat(InBuffer<CheatDefinition> cheat, Out<u32> out_cheat_id, bool enabled);
|
||||||
|
Result RemoveCheat(u32 cheat_id);
|
||||||
|
|
||||||
|
Result GetFrozenAddressCount(Out<u64> out_count);
|
||||||
|
Result GetFrozenAddresses(OutBuffer<FrozenAddressEntry> addresses, Out<u64> out_count, u64 offset);
|
||||||
|
Result GetFrozenAddress(Out<FrozenAddressEntry> entry, u64 address);
|
||||||
|
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
|
||||||
|
Result DisableFrozenAddress(u64 address);
|
||||||
|
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_HasCheatProcess, &DmntCheatService::HasCheatProcess>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheatProcessEvent, &DmntCheatService::GetCheatProcessEvent>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheatProcessMetadata, &DmntCheatService::GetCheatProcessMetadata>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_ForceOpenCheatProcess, &DmntCheatService::ForceOpenCheatProcess>(),
|
||||||
|
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheatProcessMappingCount, &DmntCheatService::GetCheatProcessMappingCount>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheatProcessMappings, &DmntCheatService::GetCheatProcessMappings>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_ReadCheatProcessMemory, &DmntCheatService::ReadCheatProcessMemory>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_WriteCheatProcessMemory, &DmntCheatService::WriteCheatProcessMemory>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_QueryCheatProcessMemory, &DmntCheatService::QueryCheatProcessMemory>(),
|
||||||
|
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheatCount, &DmntCheatService::GetCheatCount>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheats, &DmntCheatService::GetCheats>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetCheatById, &DmntCheatService::GetCheatById>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_ToggleCheat, &DmntCheatService::ToggleCheat>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_AddCheat, &DmntCheatService::AddCheat>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_RemoveCheat, &DmntCheatService::RemoveCheat>(),
|
||||||
|
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetFrozenAddressCount, &DmntCheatService::GetFrozenAddressCount>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetFrozenAddresses, &DmntCheatService::GetFrozenAddresses>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_GetFrozenAddress, &DmntCheatService::GetFrozenAddress>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_EnableFrozenAddress, &DmntCheatService::EnableFrozenAddress>(),
|
||||||
|
MakeServiceCommandMeta<DmntCheat_Cmd_DisableFrozenAddress, &DmntCheatService::DisableFrozenAddress>(),
|
||||||
|
};
|
||||||
|
};
|
||||||
58
stratosphere/dmnt/source/dmnt_cheat_types.hpp
Normal file
58
stratosphere/dmnt/source/dmnt_cheat_types.hpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_results.hpp"
|
||||||
|
|
||||||
|
struct MemoryRegionExtents {
|
||||||
|
u64 base;
|
||||||
|
u64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CheatProcessMetadata {
|
||||||
|
u64 process_id;
|
||||||
|
u64 title_id;
|
||||||
|
MemoryRegionExtents main_nso_extents;
|
||||||
|
MemoryRegionExtents heap_extents;
|
||||||
|
MemoryRegionExtents alias_extents;
|
||||||
|
MemoryRegionExtents address_space_extents;
|
||||||
|
u8 main_nso_build_id[0x20];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CheatDefinition {
|
||||||
|
char readable_name[0x40];
|
||||||
|
uint32_t num_opcodes;
|
||||||
|
uint32_t opcodes[0x100];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CheatEntry {
|
||||||
|
bool enabled;
|
||||||
|
uint32_t cheat_id;
|
||||||
|
CheatDefinition definition;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FrozenAddressValue {
|
||||||
|
u64 value;
|
||||||
|
u8 width;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FrozenAddressEntry {
|
||||||
|
u64 address;
|
||||||
|
FrozenAddressValue value;
|
||||||
|
};
|
||||||
761
stratosphere/dmnt/source/dmnt_cheat_vm.cpp
Normal file
761
stratosphere/dmnt/source/dmnt_cheat_vm.cpp
Normal file
@@ -0,0 +1,761 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "dmnt_cheat_types.hpp"
|
||||||
|
#include "dmnt_cheat_vm.hpp"
|
||||||
|
#include "dmnt_cheat_manager.hpp"
|
||||||
|
#include "dmnt_hid.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
void DmntCheatVm::OpenDebugLogFile() {
|
||||||
|
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
|
||||||
|
CloseDebugLogFile();
|
||||||
|
this->debug_log_file = fopen("cheat_vm_log.txt", "wb");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatVm::CloseDebugLogFile() {
|
||||||
|
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
|
||||||
|
if (this->debug_log_file != NULL) {
|
||||||
|
fclose(this->debug_log_file);
|
||||||
|
this->debug_log_file = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatVm::LogToDebugFile(const char *format, ...) {
|
||||||
|
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
|
||||||
|
if (this->debug_log_file != NULL) {
|
||||||
|
va_list arglist;
|
||||||
|
va_start(arglist, format);
|
||||||
|
vfprintf(this->debug_log_file, format, arglist);
|
||||||
|
va_end(arglist);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatVm::LogOpcode(const CheatVmOpcode *opcode) {
|
||||||
|
#ifndef DMNT_CHEAT_VM_DEBUG_LOG
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
switch (opcode->opcode) {
|
||||||
|
case CheatVmOpcodeType_StoreStatic:
|
||||||
|
this->LogToDebugFile("Opcode: Store Static\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->store_static.bit_width);
|
||||||
|
this->LogToDebugFile("Mem Type: %x\n", opcode->store_static.mem_type);
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->store_static.offset_register);
|
||||||
|
this->LogToDebugFile("Rel Addr: %lx\n", opcode->store_static.rel_address);
|
||||||
|
this->LogToDebugFile("Value: %lx\n", opcode->store_static.value.bit64);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_BeginConditionalBlock:
|
||||||
|
this->LogToDebugFile("Opcode: Begin Conditional\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->begin_cond.bit_width);
|
||||||
|
this->LogToDebugFile("Mem Type: %x\n", opcode->begin_cond.mem_type);
|
||||||
|
this->LogToDebugFile("Cond Type: %x\n", opcode->begin_cond.cond_type);
|
||||||
|
this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_cond.rel_address);
|
||||||
|
this->LogToDebugFile("Value: %lx\n", opcode->begin_cond.value.bit64);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_EndConditionalBlock:
|
||||||
|
this->LogToDebugFile("Opcode: End Conditional\n");
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_ControlLoop:
|
||||||
|
if (opcode->ctrl_loop.start_loop) {
|
||||||
|
this->LogToDebugFile("Opcode: Start Loop\n");
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index);
|
||||||
|
this->LogToDebugFile("Num Iters: %x\n", opcode->ctrl_loop.num_iters);
|
||||||
|
} else {
|
||||||
|
this->LogToDebugFile("Opcode: End Loop\n");
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_LoadRegisterStatic:
|
||||||
|
this->LogToDebugFile("Opcode: Load Register Static\n");
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_static.reg_index);
|
||||||
|
this->LogToDebugFile("Value: %lx\n", opcode->ldr_static.value);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_LoadRegisterMemory:
|
||||||
|
this->LogToDebugFile("Opcode: Load Register Memory\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->ldr_memory.bit_width);
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_memory.reg_index);
|
||||||
|
this->LogToDebugFile("Mem Type: %x\n", opcode->ldr_memory.mem_type);
|
||||||
|
this->LogToDebugFile("From Reg: %d\n", opcode->ldr_memory.load_from_reg);
|
||||||
|
this->LogToDebugFile("Rel Addr: %lx\n", opcode->ldr_memory.rel_address);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_StoreStaticToAddress:
|
||||||
|
this->LogToDebugFile("Opcode: Store Static to Address\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->str_static.bit_width);
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->str_static.reg_index);
|
||||||
|
if (opcode->str_static.add_offset_reg) {
|
||||||
|
this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_static.offset_reg_index);
|
||||||
|
}
|
||||||
|
this->LogToDebugFile("Incr Reg: %d\n", opcode->str_static.increment_reg);
|
||||||
|
this->LogToDebugFile("Value: %lx\n", opcode->str_static.value);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_PerformArithmeticStatic:
|
||||||
|
this->LogToDebugFile("Opcode: Perform Static Arithmetic\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_static.bit_width);
|
||||||
|
this->LogToDebugFile("Reg Idx: %x\n", opcode->perform_math_static.reg_index);
|
||||||
|
this->LogToDebugFile("Math Type: %x\n", opcode->perform_math_static.math_type);
|
||||||
|
this->LogToDebugFile("Value: %lx\n", opcode->perform_math_static.value);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_BeginKeypressConditionalBlock:
|
||||||
|
this->LogToDebugFile("Opcode: Begin Keypress Conditional\n");
|
||||||
|
this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask);
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_PerformArithmeticRegister:
|
||||||
|
this->LogToDebugFile("Opcode: Perform Register Arithmetic\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width);
|
||||||
|
this->LogToDebugFile("Dst Idx: %x\n", opcode->perform_math_reg.dst_reg_index);
|
||||||
|
this->LogToDebugFile("Src1 Idx: %x\n", opcode->perform_math_reg.src_reg_1_index);
|
||||||
|
if (opcode->perform_math_reg.has_immediate) {
|
||||||
|
this->LogToDebugFile("Value: %lx\n", opcode->perform_math_reg.value.bit64);
|
||||||
|
} else {
|
||||||
|
this->LogToDebugFile("Src2 Idx: %x\n", opcode->perform_math_reg.src_reg_2_index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_StoreRegisterToAddress:
|
||||||
|
this->LogToDebugFile("Opcode: Store Register to Address\n");
|
||||||
|
this->LogToDebugFile("Bit Width: %x\n", opcode->str_register.bit_width);
|
||||||
|
this->LogToDebugFile("S Reg Idx: %x\n", opcode->str_register.str_reg_index);
|
||||||
|
this->LogToDebugFile("A Reg Idx: %x\n", opcode->str_register.addr_reg_index);
|
||||||
|
this->LogToDebugFile("Incr Reg: %d\n", opcode->str_register.increment_reg);
|
||||||
|
switch (opcode->str_register.ofs_type) {
|
||||||
|
case StoreRegisterOffsetType_None:
|
||||||
|
break;
|
||||||
|
case StoreRegisterOffsetType_Reg:
|
||||||
|
this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_register.ofs_reg_index);
|
||||||
|
break;
|
||||||
|
case StoreRegisterOffsetType_Imm:
|
||||||
|
this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this->LogToDebugFile("Unknown opcode: %x\n", opcode->opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) {
|
||||||
|
/* If we've ever seen a decode failure, return false. */
|
||||||
|
bool valid = this->decode_success;
|
||||||
|
CheatVmOpcode opcode = {};
|
||||||
|
ON_SCOPE_EXIT {
|
||||||
|
this->decode_success &= valid;
|
||||||
|
if (valid) {
|
||||||
|
*out = opcode;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Helper function for getting instruction dwords. */
|
||||||
|
auto GetNextDword = [&]() {
|
||||||
|
if (this->instruction_ptr >= this->num_opcodes) {
|
||||||
|
valid = false;
|
||||||
|
return static_cast<u32>(0);
|
||||||
|
}
|
||||||
|
return this->program[this->instruction_ptr++];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Helper function for parsing a VmInt. */
|
||||||
|
auto GetNextVmInt = [&](const u32 bit_width) {
|
||||||
|
VmInt val = {0};
|
||||||
|
|
||||||
|
const u32 first_dword = GetNextDword();
|
||||||
|
switch (bit_width) {
|
||||||
|
case 1:
|
||||||
|
val.bit8 = (u8)first_dword;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
val.bit16 = (u16)first_dword;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
val.bit32 = first_dword;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Read opcode. */
|
||||||
|
const u32 first_dword = GetNextDword();
|
||||||
|
if (!valid) {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF));
|
||||||
|
if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) {
|
||||||
|
opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* detect condition start. */
|
||||||
|
switch (opcode.opcode) {
|
||||||
|
case CheatVmOpcodeType_BeginConditionalBlock:
|
||||||
|
case CheatVmOpcodeType_BeginKeypressConditionalBlock:
|
||||||
|
opcode.begin_conditional_block = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
opcode.begin_conditional_block = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (opcode.opcode) {
|
||||||
|
case CheatVmOpcodeType_StoreStatic:
|
||||||
|
{
|
||||||
|
/* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */
|
||||||
|
/* Read additional words. */
|
||||||
|
const u32 second_dword = GetNextDword();
|
||||||
|
opcode.store_static.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.store_static.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
|
||||||
|
opcode.store_static.offset_register = ((first_dword >> 16) & 0xF);
|
||||||
|
opcode.store_static.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
|
||||||
|
opcode.store_static.value = GetNextVmInt(opcode.store_static.bit_width);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_BeginConditionalBlock:
|
||||||
|
{
|
||||||
|
/* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */
|
||||||
|
/* Read additional words. */
|
||||||
|
const u32 second_dword = GetNextDword();
|
||||||
|
opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
|
||||||
|
opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF);
|
||||||
|
opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
|
||||||
|
opcode.begin_cond.value = GetNextVmInt(opcode.store_static.bit_width);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_EndConditionalBlock:
|
||||||
|
{
|
||||||
|
/* 20000000 */
|
||||||
|
/* There's actually nothing left to process here! */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_ControlLoop:
|
||||||
|
{
|
||||||
|
/* 300R0000 VVVVVVVV */
|
||||||
|
/* 310R0000 */
|
||||||
|
/* Parse register, whether loop start or loop end. */
|
||||||
|
opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
|
||||||
|
opcode.ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
|
||||||
|
|
||||||
|
/* Read number of iters if loop start. */
|
||||||
|
if (opcode.ctrl_loop.start_loop) {
|
||||||
|
opcode.ctrl_loop.num_iters = GetNextDword();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_LoadRegisterStatic:
|
||||||
|
{
|
||||||
|
/* 400R0000 VVVVVVVV VVVVVVVV */
|
||||||
|
/* Read additional words. */
|
||||||
|
opcode.ldr_static.reg_index = ((first_dword >> 20) & 0xF);
|
||||||
|
opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_LoadRegisterMemory:
|
||||||
|
{
|
||||||
|
/* 5TMRI0AA AAAAAAAA */
|
||||||
|
/* Read additional words. */
|
||||||
|
const u32 second_dword = GetNextDword();
|
||||||
|
opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
|
||||||
|
opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
|
||||||
|
opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
|
||||||
|
opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_StoreStaticToAddress:
|
||||||
|
{
|
||||||
|
/* 6T0RIor0 VVVVVVVV VVVVVVVV */
|
||||||
|
/* Read additional words. */
|
||||||
|
opcode.str_static.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.str_static.reg_index = ((first_dword >> 16) & 0xF);
|
||||||
|
opcode.str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
|
||||||
|
opcode.str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
|
||||||
|
opcode.str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
|
||||||
|
opcode.str_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_PerformArithmeticStatic:
|
||||||
|
{
|
||||||
|
/* 7T0RC000 VVVVVVVV */
|
||||||
|
/* Read additional words. */
|
||||||
|
opcode.perform_math_static.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
|
||||||
|
opcode.perform_math_static.math_type = (RegisterArithmeticType)((first_dword >> 12) & 0xF);
|
||||||
|
opcode.perform_math_static.value = GetNextDword();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_BeginKeypressConditionalBlock:
|
||||||
|
{
|
||||||
|
/* 8kkkkkkk */
|
||||||
|
/* Just parse the mask. */
|
||||||
|
opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_PerformArithmeticRegister:
|
||||||
|
{
|
||||||
|
/* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */
|
||||||
|
opcode.perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.perform_math_reg.math_type = (RegisterArithmeticType)((first_dword >> 20) & 0xF);
|
||||||
|
opcode.perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
|
||||||
|
opcode.perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
|
||||||
|
opcode.perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
|
||||||
|
if (opcode.perform_math_reg.has_immediate) {
|
||||||
|
opcode.perform_math_reg.src_reg_2_index = 0;
|
||||||
|
opcode.perform_math_reg.value = GetNextVmInt(opcode.perform_math_reg.bit_width);
|
||||||
|
} else {
|
||||||
|
opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_StoreRegisterToAddress:
|
||||||
|
{
|
||||||
|
/* ATSRIOra (aaaaaaaa) */
|
||||||
|
/* A = opcode 10 */
|
||||||
|
/* T = bit width */
|
||||||
|
/* S = src register index */
|
||||||
|
/* R = address register index */
|
||||||
|
/* I = 1 if increment address register, 0 if not increment address register */
|
||||||
|
/* O = offset type, 0 = None, 1 = Register, 2 = Immediate */
|
||||||
|
/* r = offset register (for offset type 1) */
|
||||||
|
/* a = relative address (for offset type 2) */
|
||||||
|
opcode.str_register.bit_width = (first_dword >> 24) & 0xF;
|
||||||
|
opcode.str_register.str_reg_index = ((first_dword >> 20) & 0xF);
|
||||||
|
opcode.str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
|
||||||
|
opcode.str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
|
||||||
|
opcode.str_register.ofs_type = (StoreRegisterOffsetType)(((first_dword >> 8) & 0xF));
|
||||||
|
opcode.str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
|
||||||
|
switch (opcode.str_register.ofs_type) {
|
||||||
|
case StoreRegisterOffsetType_None:
|
||||||
|
case StoreRegisterOffsetType_Reg:
|
||||||
|
/* Nothing more to do */
|
||||||
|
break;
|
||||||
|
case StoreRegisterOffsetType_Imm:
|
||||||
|
opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
opcode.str_register.ofs_type = StoreRegisterOffsetType_None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_ExtendedWidth:
|
||||||
|
default:
|
||||||
|
/* Unrecognized instruction cannot be decoded. */
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End decoding. */
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatVm::SkipConditionalBlock() {
|
||||||
|
if (this->condition_depth > 0) {
|
||||||
|
/* We want to continue until we're out of the current block. */
|
||||||
|
const size_t desired_depth = this->condition_depth - 1;
|
||||||
|
|
||||||
|
CheatVmOpcode skip_opcode;
|
||||||
|
while (this->DecodeNextOpcode(&skip_opcode) && this->condition_depth > desired_depth) {
|
||||||
|
/* Decode instructions until we see end of the current conditional block. */
|
||||||
|
/* NOTE: This is broken in gateway's implementation. */
|
||||||
|
/* Gateway currently checks for "0x2" instead of "0x20000000" */
|
||||||
|
/* In addition, they do a linear scan instead of correctly decoding opcodes. */
|
||||||
|
/* This causes issues if "0x2" appears as an immediate in the conditional block... */
|
||||||
|
|
||||||
|
/* We also support nesting of conditional blocks, and Gateway does not. */
|
||||||
|
if (skip_opcode.begin_conditional_block) {
|
||||||
|
this->condition_depth++;
|
||||||
|
} else if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) {
|
||||||
|
this->condition_depth--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Skipping, but this->condition_depth = 0. */
|
||||||
|
/* This is an error condition. */
|
||||||
|
/* However, I don't actually believe it is possible for this to happen. */
|
||||||
|
/* I guess we'll throw a fatal error here, so as to encourage me to fix the VM */
|
||||||
|
/* in the event that someone triggers it? I don't know how you'd do that. */
|
||||||
|
fatalSimple(ResultDmntCheatVmInvalidCondDepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
|
||||||
|
switch (bit_width) {
|
||||||
|
case 1:
|
||||||
|
return value.bit8;
|
||||||
|
case 2:
|
||||||
|
return value.bit16;
|
||||||
|
case 4:
|
||||||
|
return value.bit32;
|
||||||
|
case 8:
|
||||||
|
return value.bit64;
|
||||||
|
default:
|
||||||
|
/* Invalid bit width -> return 0. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address) {
|
||||||
|
switch (mem_type) {
|
||||||
|
case MemoryAccessType_MainNso:
|
||||||
|
default:
|
||||||
|
return metadata->main_nso_extents.base + rel_address;
|
||||||
|
case MemoryAccessType_Heap:
|
||||||
|
return metadata->heap_extents.base + rel_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatVm::ResetState() {
|
||||||
|
for (size_t i = 0; i < DmntCheatVm::NumRegisters; i++) {
|
||||||
|
this->registers[i] = 0;
|
||||||
|
this->loop_tops[i] = 0;
|
||||||
|
}
|
||||||
|
this->instruction_ptr = 0;
|
||||||
|
this->condition_depth = 0;
|
||||||
|
this->decode_success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntCheatVm::LoadProgram(const CheatEntry *cheats, size_t num_cheats) {
|
||||||
|
/* Reset opcode count. */
|
||||||
|
this->num_opcodes = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_cheats; i++) {
|
||||||
|
if (cheats[i].enabled) {
|
||||||
|
/* Bounds check. */
|
||||||
|
if (cheats[i].definition.num_opcodes + this->num_opcodes > MaximumProgramOpcodeCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t n = 0; n < cheats[i].definition.num_opcodes; n++) {
|
||||||
|
this->program[this->num_opcodes++] = cheats[i].definition.opcodes[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) {
|
||||||
|
CheatVmOpcode cur_opcode;
|
||||||
|
u64 kDown = 0;
|
||||||
|
|
||||||
|
/* Get Keys down. */
|
||||||
|
HidManagement::GetKeysDown(&kDown);
|
||||||
|
|
||||||
|
this->OpenDebugLogFile();
|
||||||
|
ON_SCOPE_EXIT { this->CloseDebugLogFile(); };
|
||||||
|
|
||||||
|
this->LogToDebugFile("Started VM execution.\n");
|
||||||
|
this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base);
|
||||||
|
this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base);
|
||||||
|
this->LogToDebugFile("Keys Down: %08x\n", (u32)(kDown & 0x0FFFFFFF));
|
||||||
|
|
||||||
|
/* Clear VM state. */
|
||||||
|
this->ResetState();
|
||||||
|
|
||||||
|
/* Loop until program finishes. */
|
||||||
|
while (this->DecodeNextOpcode(&cur_opcode)) {
|
||||||
|
this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < NumRegisters; i++) {
|
||||||
|
this->LogToDebugFile("Registers[%02x]: %016lx\n", i, this->registers[i]);
|
||||||
|
}
|
||||||
|
this->LogOpcode(&cur_opcode);
|
||||||
|
|
||||||
|
/* Increment conditional depth, if relevant. */
|
||||||
|
if (cur_opcode.begin_conditional_block) {
|
||||||
|
this->condition_depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cur_opcode.opcode) {
|
||||||
|
case CheatVmOpcodeType_StoreStatic:
|
||||||
|
{
|
||||||
|
/* Calculate address, write value to memory. */
|
||||||
|
u64 dst_address = GetCheatProcessAddress(metadata, cur_opcode.store_static.mem_type, cur_opcode.store_static.rel_address + this->registers[cur_opcode.store_static.offset_register]);
|
||||||
|
u64 dst_value = GetVmInt(cur_opcode.store_static.value, cur_opcode.store_static.bit_width);
|
||||||
|
switch (cur_opcode.store_static.bit_width) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.store_static.bit_width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_BeginConditionalBlock:
|
||||||
|
{
|
||||||
|
/* Read value from memory. */
|
||||||
|
u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, cur_opcode.begin_cond.rel_address);
|
||||||
|
u64 src_value = 0;
|
||||||
|
switch (cur_opcode.store_static.bit_width) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
DmntCheatManager::ReadCheatProcessMemoryForVm(src_address, &src_value, cur_opcode.begin_cond.bit_width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Check against condition. */
|
||||||
|
u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width);
|
||||||
|
bool cond_met = false;
|
||||||
|
switch (cur_opcode.begin_cond.cond_type) {
|
||||||
|
case ConditionalComparisonType_GT:
|
||||||
|
cond_met = src_value > cond_value;
|
||||||
|
break;
|
||||||
|
case ConditionalComparisonType_GE:
|
||||||
|
cond_met = src_value >= cond_value;
|
||||||
|
break;
|
||||||
|
case ConditionalComparisonType_LT:
|
||||||
|
cond_met = src_value < cond_value;
|
||||||
|
break;
|
||||||
|
case ConditionalComparisonType_LE:
|
||||||
|
cond_met = src_value <= cond_value;
|
||||||
|
break;
|
||||||
|
case ConditionalComparisonType_EQ:
|
||||||
|
cond_met = src_value == cond_value;
|
||||||
|
break;
|
||||||
|
case ConditionalComparisonType_NE:
|
||||||
|
cond_met = src_value != cond_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Skip conditional block if condition not met. */
|
||||||
|
if (!cond_met) {
|
||||||
|
this->SkipConditionalBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_EndConditionalBlock:
|
||||||
|
/* Decrement the condition depth. */
|
||||||
|
/* We will assume, graciously, that mismatched conditional block ends are a nop. */
|
||||||
|
if (this->condition_depth > 0) {
|
||||||
|
this->condition_depth--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_ControlLoop:
|
||||||
|
if (cur_opcode.ctrl_loop.start_loop) {
|
||||||
|
/* Start a loop. */
|
||||||
|
this->registers[cur_opcode.ctrl_loop.reg_index] = cur_opcode.ctrl_loop.num_iters;
|
||||||
|
this->loop_tops[cur_opcode.ctrl_loop.reg_index] = this->instruction_ptr;
|
||||||
|
} else {
|
||||||
|
/* End a loop. */
|
||||||
|
this->registers[cur_opcode.ctrl_loop.reg_index]--;
|
||||||
|
if (this->registers[cur_opcode.ctrl_loop.reg_index] != 0) {
|
||||||
|
this->instruction_ptr = this->loop_tops[cur_opcode.ctrl_loop.reg_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_LoadRegisterStatic:
|
||||||
|
/* Set a register to a static value. */
|
||||||
|
this->registers[cur_opcode.ldr_static.reg_index] = cur_opcode.ldr_static.value;
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_LoadRegisterMemory:
|
||||||
|
{
|
||||||
|
/* Choose source address. */
|
||||||
|
u64 src_address;
|
||||||
|
if (cur_opcode.ldr_memory.load_from_reg) {
|
||||||
|
src_address = this->registers[cur_opcode.ldr_memory.reg_index] + cur_opcode.ldr_memory.rel_address;
|
||||||
|
} else {
|
||||||
|
src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, cur_opcode.ldr_memory.rel_address);
|
||||||
|
}
|
||||||
|
/* Read into register. Gateway only reads on valid bitwidth. */
|
||||||
|
switch (cur_opcode.ldr_memory.bit_width) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
DmntCheatManager::ReadCheatProcessMemoryForVm(src_address, &this->registers[cur_opcode.ldr_memory.reg_index], cur_opcode.ldr_memory.bit_width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_StoreStaticToAddress:
|
||||||
|
{
|
||||||
|
/* Calculate address. */
|
||||||
|
u64 dst_address = this->registers[cur_opcode.str_static.reg_index];
|
||||||
|
u64 dst_value = cur_opcode.str_static.value;
|
||||||
|
if (cur_opcode.str_static.add_offset_reg) {
|
||||||
|
dst_address += this->registers[cur_opcode.str_static.offset_reg_index];
|
||||||
|
}
|
||||||
|
/* Write value to memory. Gateway only writes on valid bitwidth. */
|
||||||
|
switch (cur_opcode.str_static.bit_width) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.str_static.bit_width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Increment register if relevant. */
|
||||||
|
if (cur_opcode.str_static.increment_reg) {
|
||||||
|
this->registers[cur_opcode.str_static.reg_index] += cur_opcode.str_static.bit_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_PerformArithmeticStatic:
|
||||||
|
{
|
||||||
|
/* Do requested math. */
|
||||||
|
switch (cur_opcode.perform_math_static.math_type) {
|
||||||
|
case RegisterArithmeticType_Addition:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] += (u64)cur_opcode.perform_math_static.value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_Subtraction:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] -= (u64)cur_opcode.perform_math_static.value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_Multiplication:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] *= (u64)cur_opcode.perform_math_static.value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_LeftShift:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] <<= (u64)cur_opcode.perform_math_static.value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_RightShift:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] >>= (u64)cur_opcode.perform_math_static.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Do not handle extensions here. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Apply bit width. */
|
||||||
|
switch (cur_opcode.perform_math_static.bit_width) {
|
||||||
|
case 1:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] = static_cast<u8>(this->registers[cur_opcode.perform_math_static.reg_index]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] = static_cast<u16>(this->registers[cur_opcode.perform_math_static.reg_index]);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] = static_cast<u32>(this->registers[cur_opcode.perform_math_static.reg_index]);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
this->registers[cur_opcode.perform_math_static.reg_index] = static_cast<u64>(this->registers[cur_opcode.perform_math_static.reg_index]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_BeginKeypressConditionalBlock:
|
||||||
|
/* Check for keypress. */
|
||||||
|
if ((cur_opcode.begin_keypress_cond.key_mask & kDown) != cur_opcode.begin_keypress_cond.key_mask) {
|
||||||
|
/* Keys not pressed. Skip conditional block. */
|
||||||
|
this->SkipConditionalBlock();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_PerformArithmeticRegister:
|
||||||
|
{
|
||||||
|
const u64 operand_1_value = this->registers[cur_opcode.perform_math_reg.src_reg_1_index];
|
||||||
|
const u64 operand_2_value = cur_opcode.perform_math_reg.has_immediate ?
|
||||||
|
GetVmInt(cur_opcode.perform_math_reg.value, cur_opcode.perform_math_reg.bit_width) :
|
||||||
|
this->registers[cur_opcode.perform_math_reg.src_reg_2_index];
|
||||||
|
|
||||||
|
u64 res_val = 0;
|
||||||
|
/* Do requested math. */
|
||||||
|
switch (cur_opcode.perform_math_reg.math_type) {
|
||||||
|
case RegisterArithmeticType_Addition:
|
||||||
|
res_val = operand_1_value + operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_Subtraction:
|
||||||
|
res_val = operand_1_value - operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_Multiplication:
|
||||||
|
res_val = operand_1_value * operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_LeftShift:
|
||||||
|
res_val = operand_1_value << operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_RightShift:
|
||||||
|
res_val = operand_1_value >> operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_LogicalAnd:
|
||||||
|
res_val = operand_1_value & operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_LogicalOr:
|
||||||
|
res_val = operand_1_value | operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_LogicalNot:
|
||||||
|
res_val = ~operand_1_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_LogicalXor:
|
||||||
|
res_val = operand_1_value ^ operand_2_value;
|
||||||
|
break;
|
||||||
|
case RegisterArithmeticType_None:
|
||||||
|
res_val = operand_1_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Apply bit width. */
|
||||||
|
switch (cur_opcode.perform_math_reg.bit_width) {
|
||||||
|
case 1:
|
||||||
|
res_val = static_cast<u8>(res_val);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
res_val = static_cast<u16>(res_val);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
res_val = static_cast<u32>(res_val);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
res_val = static_cast<u64>(res_val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save to register. */
|
||||||
|
this->registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CheatVmOpcodeType_StoreRegisterToAddress:
|
||||||
|
{
|
||||||
|
/* Calculate address. */
|
||||||
|
u64 dst_value = this->registers[cur_opcode.str_register.str_reg_index];
|
||||||
|
u64 dst_address = this->registers[cur_opcode.str_register.addr_reg_index];
|
||||||
|
switch (cur_opcode.str_register.ofs_type) {
|
||||||
|
case StoreRegisterOffsetType_None:
|
||||||
|
/* Nothing more to do */
|
||||||
|
break;
|
||||||
|
case StoreRegisterOffsetType_Reg:
|
||||||
|
dst_address += this->registers[cur_opcode.str_register.ofs_reg_index];
|
||||||
|
break;
|
||||||
|
case StoreRegisterOffsetType_Imm:
|
||||||
|
dst_address += cur_opcode.str_register.rel_address;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write value to memory. Write only on valid bitwidth. */
|
||||||
|
switch (cur_opcode.str_register.bit_width) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.str_register.bit_width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increment register if relevant. */
|
||||||
|
if (cur_opcode.str_register.increment_reg) {
|
||||||
|
this->registers[cur_opcode.str_register.addr_reg_index] += cur_opcode.str_register.bit_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* By default, we do a no-op. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
221
stratosphere/dmnt/source/dmnt_cheat_vm.hpp
Normal file
221
stratosphere/dmnt/source/dmnt_cheat_vm.hpp
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_cheat_types.hpp"
|
||||||
|
|
||||||
|
enum CheatVmOpcodeType : u32 {
|
||||||
|
CheatVmOpcodeType_StoreStatic = 0,
|
||||||
|
CheatVmOpcodeType_BeginConditionalBlock = 1,
|
||||||
|
CheatVmOpcodeType_EndConditionalBlock = 2,
|
||||||
|
CheatVmOpcodeType_ControlLoop = 3,
|
||||||
|
CheatVmOpcodeType_LoadRegisterStatic = 4,
|
||||||
|
CheatVmOpcodeType_LoadRegisterMemory = 5,
|
||||||
|
CheatVmOpcodeType_StoreStaticToAddress = 6,
|
||||||
|
CheatVmOpcodeType_PerformArithmeticStatic = 7,
|
||||||
|
CheatVmOpcodeType_BeginKeypressConditionalBlock = 8,
|
||||||
|
|
||||||
|
/* These are not implemented by Gateway's VM. */
|
||||||
|
CheatVmOpcodeType_PerformArithmeticRegister = 9,
|
||||||
|
CheatVmOpcodeType_StoreRegisterToAddress = 10,
|
||||||
|
|
||||||
|
/* This is a meta entry, and not a real opcode. */
|
||||||
|
/* This is to facilitate multi-nybble instruction decoding in the future. */
|
||||||
|
CheatVmOpcodeType_ExtendedWidth = 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MemoryAccessType : u32 {
|
||||||
|
MemoryAccessType_MainNso = 0,
|
||||||
|
MemoryAccessType_Heap = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ConditionalComparisonType : u32 {
|
||||||
|
ConditionalComparisonType_GT = 1,
|
||||||
|
ConditionalComparisonType_GE = 2,
|
||||||
|
ConditionalComparisonType_LT = 3,
|
||||||
|
ConditionalComparisonType_LE = 4,
|
||||||
|
ConditionalComparisonType_EQ = 5,
|
||||||
|
ConditionalComparisonType_NE = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum RegisterArithmeticType : u32 {
|
||||||
|
RegisterArithmeticType_Addition = 0,
|
||||||
|
RegisterArithmeticType_Subtraction = 1,
|
||||||
|
RegisterArithmeticType_Multiplication = 2,
|
||||||
|
RegisterArithmeticType_LeftShift = 3,
|
||||||
|
RegisterArithmeticType_RightShift = 4,
|
||||||
|
|
||||||
|
/* These are not supported by Gateway's VM. */
|
||||||
|
RegisterArithmeticType_LogicalAnd = 5,
|
||||||
|
RegisterArithmeticType_LogicalOr = 6,
|
||||||
|
RegisterArithmeticType_LogicalNot = 7,
|
||||||
|
RegisterArithmeticType_LogicalXor = 8,
|
||||||
|
|
||||||
|
RegisterArithmeticType_None = 9,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum StoreRegisterOffsetType : u32 {
|
||||||
|
StoreRegisterOffsetType_None = 0,
|
||||||
|
StoreRegisterOffsetType_Reg = 1,
|
||||||
|
StoreRegisterOffsetType_Imm = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
union VmInt {
|
||||||
|
u8 bit8;
|
||||||
|
u16 bit16;
|
||||||
|
u32 bit32;
|
||||||
|
u64 bit64;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoreStaticOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
MemoryAccessType mem_type;
|
||||||
|
u32 offset_register;
|
||||||
|
u64 rel_address;
|
||||||
|
VmInt value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BeginConditionalOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
MemoryAccessType mem_type;
|
||||||
|
ConditionalComparisonType cond_type;
|
||||||
|
u64 rel_address;
|
||||||
|
VmInt value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EndConditionalOpcode {};
|
||||||
|
|
||||||
|
struct ControlLoopOpcode {
|
||||||
|
bool start_loop;
|
||||||
|
u32 reg_index;
|
||||||
|
u32 num_iters;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadRegisterStaticOpcode {
|
||||||
|
u32 reg_index;
|
||||||
|
u64 value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadRegisterMemoryOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
MemoryAccessType mem_type;
|
||||||
|
u32 reg_index;
|
||||||
|
bool load_from_reg;
|
||||||
|
u64 rel_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoreStaticToAddressOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
u32 reg_index;
|
||||||
|
bool increment_reg;
|
||||||
|
bool add_offset_reg;
|
||||||
|
u32 offset_reg_index;
|
||||||
|
u64 value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PerformArithmeticStaticOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
u32 reg_index;
|
||||||
|
RegisterArithmeticType math_type;
|
||||||
|
u32 value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BeginKeypressConditionalOpcode {
|
||||||
|
u32 key_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PerformArithmeticRegisterOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
RegisterArithmeticType math_type;
|
||||||
|
u32 dst_reg_index;
|
||||||
|
u32 src_reg_1_index;
|
||||||
|
u32 src_reg_2_index;
|
||||||
|
bool has_immediate;
|
||||||
|
VmInt value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoreRegisterToAddressOpcode {
|
||||||
|
u32 bit_width;
|
||||||
|
u32 str_reg_index;
|
||||||
|
u32 addr_reg_index;
|
||||||
|
bool increment_reg;
|
||||||
|
StoreRegisterOffsetType ofs_type;
|
||||||
|
u32 ofs_reg_index;
|
||||||
|
u64 rel_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct CheatVmOpcode {
|
||||||
|
CheatVmOpcodeType opcode;
|
||||||
|
bool begin_conditional_block;
|
||||||
|
union {
|
||||||
|
StoreStaticOpcode store_static;
|
||||||
|
BeginConditionalOpcode begin_cond;
|
||||||
|
EndConditionalOpcode end_cond;
|
||||||
|
ControlLoopOpcode ctrl_loop;
|
||||||
|
LoadRegisterStaticOpcode ldr_static;
|
||||||
|
LoadRegisterMemoryOpcode ldr_memory;
|
||||||
|
StoreStaticToAddressOpcode str_static;
|
||||||
|
PerformArithmeticStaticOpcode perform_math_static;
|
||||||
|
BeginKeypressConditionalOpcode begin_keypress_cond;
|
||||||
|
PerformArithmeticRegisterOpcode perform_math_reg;
|
||||||
|
StoreRegisterToAddressOpcode str_register;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class DmntCheatVm {
|
||||||
|
public:
|
||||||
|
constexpr static size_t MaximumProgramOpcodeCount = 0x400;
|
||||||
|
constexpr static size_t NumRegisters = 0x10;
|
||||||
|
private:
|
||||||
|
size_t num_opcodes = 0;
|
||||||
|
size_t instruction_ptr = 0;
|
||||||
|
size_t condition_depth = 0;
|
||||||
|
bool decode_success = false;
|
||||||
|
u32 program[MaximumProgramOpcodeCount] = {0};
|
||||||
|
u64 registers[NumRegisters] = {0};
|
||||||
|
size_t loop_tops[NumRegisters] = {0};
|
||||||
|
private:
|
||||||
|
bool DecodeNextOpcode(CheatVmOpcode *out);
|
||||||
|
void SkipConditionalBlock();
|
||||||
|
void ResetState();
|
||||||
|
|
||||||
|
/* For debugging. These will be IFDEF'd out normally. */
|
||||||
|
void OpenDebugLogFile();
|
||||||
|
void CloseDebugLogFile();
|
||||||
|
void LogToDebugFile(const char *format, ...);
|
||||||
|
void LogOpcode(const CheatVmOpcode *opcode);
|
||||||
|
|
||||||
|
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||||
|
static u64 GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address);
|
||||||
|
public:
|
||||||
|
DmntCheatVm() { }
|
||||||
|
|
||||||
|
size_t GetProgramSize() {
|
||||||
|
return this->num_opcodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadProgram(const CheatEntry *cheats, size_t num_cheats);
|
||||||
|
void Execute(const CheatProcessMetadata *metadata);
|
||||||
|
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
|
||||||
|
private:
|
||||||
|
FILE *debug_log_file = NULL;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
157
stratosphere/dmnt/source/dmnt_config.cpp
Normal file
157
stratosphere/dmnt/source/dmnt_config.cpp
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_hid.hpp"
|
||||||
|
#include "dmnt_config.hpp"
|
||||||
|
#include "ini.h"
|
||||||
|
|
||||||
|
/* Support variables. */
|
||||||
|
static OverrideKey g_default_cheat_enable_key = {
|
||||||
|
.key_combination = KEY_L,
|
||||||
|
.override_by_default = true
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Static buffer for loader.ini contents at runtime. */
|
||||||
|
static char g_config_ini_data[0x800];
|
||||||
|
|
||||||
|
static OverrideKey ParseOverrideKey(const char *value) {
|
||||||
|
OverrideKey cfg;
|
||||||
|
|
||||||
|
/* Parse on by default. */
|
||||||
|
if (value[0] == '!') {
|
||||||
|
cfg.override_by_default = true;
|
||||||
|
value++;
|
||||||
|
} else {
|
||||||
|
cfg.override_by_default = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse key combination. */
|
||||||
|
if (strcasecmp(value, "A") == 0) {
|
||||||
|
cfg.key_combination = KEY_A;
|
||||||
|
} else if (strcasecmp(value, "B") == 0) {
|
||||||
|
cfg.key_combination = KEY_B;
|
||||||
|
} else if (strcasecmp(value, "X") == 0) {
|
||||||
|
cfg.key_combination = KEY_X;
|
||||||
|
} else if (strcasecmp(value, "Y") == 0) {
|
||||||
|
cfg.key_combination = KEY_Y;
|
||||||
|
} else if (strcasecmp(value, "LS") == 0) {
|
||||||
|
cfg.key_combination = KEY_LSTICK;
|
||||||
|
} else if (strcasecmp(value, "RS") == 0) {
|
||||||
|
cfg.key_combination = KEY_RSTICK;
|
||||||
|
} else if (strcasecmp(value, "L") == 0) {
|
||||||
|
cfg.key_combination = KEY_L;
|
||||||
|
} else if (strcasecmp(value, "R") == 0) {
|
||||||
|
cfg.key_combination = KEY_R;
|
||||||
|
} else if (strcasecmp(value, "ZL") == 0) {
|
||||||
|
cfg.key_combination = KEY_ZL;
|
||||||
|
} else if (strcasecmp(value, "ZR") == 0) {
|
||||||
|
cfg.key_combination = KEY_ZR;
|
||||||
|
} else if (strcasecmp(value, "PLUS") == 0) {
|
||||||
|
cfg.key_combination = KEY_PLUS;
|
||||||
|
} else if (strcasecmp(value, "MINUS") == 0) {
|
||||||
|
cfg.key_combination = KEY_MINUS;
|
||||||
|
} else if (strcasecmp(value, "DLEFT") == 0) {
|
||||||
|
cfg.key_combination = KEY_DLEFT;
|
||||||
|
} else if (strcasecmp(value, "DUP") == 0) {
|
||||||
|
cfg.key_combination = KEY_DUP;
|
||||||
|
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
||||||
|
cfg.key_combination = KEY_DRIGHT;
|
||||||
|
} else if (strcasecmp(value, "DDOWN") == 0) {
|
||||||
|
cfg.key_combination = KEY_DDOWN;
|
||||||
|
} else if (strcasecmp(value, "SL") == 0) {
|
||||||
|
cfg.key_combination = KEY_SL;
|
||||||
|
} else if (strcasecmp(value, "SR") == 0) {
|
||||||
|
cfg.key_combination = KEY_SR;
|
||||||
|
} else {
|
||||||
|
cfg.key_combination = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int DmntIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||||
|
/* Taken and modified, with love, from Rajkosto's implementation. */
|
||||||
|
if (strcasecmp(section, "default_config") == 0) {
|
||||||
|
if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||||
|
g_default_cheat_enable_key = ParseOverrideKey(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int DmntTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||||
|
/* We'll output an override key when relevant. */
|
||||||
|
OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
|
||||||
|
|
||||||
|
if (strcasecmp(section, "override_config") == 0) {
|
||||||
|
if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||||
|
*user_cfg = ParseOverrideKey(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DmntConfigManager::RefreshConfiguration() {
|
||||||
|
FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r");
|
||||||
|
if (config == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(g_config_ini_data, 0, sizeof(g_config_ini_data));
|
||||||
|
fread(g_config_ini_data, 1, sizeof(g_config_ini_data) - 1, config);
|
||||||
|
fclose(config);
|
||||||
|
|
||||||
|
ini_parse_string(g_config_ini_data, DmntIniHandler, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
OverrideKey DmntConfigManager::GetTitleCheatEnableKey(u64 tid) {
|
||||||
|
OverrideKey cfg = g_default_cheat_enable_key;
|
||||||
|
char path[FS_MAX_PATH+1] = {0};
|
||||||
|
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/config.ini", tid);
|
||||||
|
|
||||||
|
|
||||||
|
FILE *config = fopen(path, "r");
|
||||||
|
if (config != NULL) {
|
||||||
|
ON_SCOPE_EXIT { fclose(config); };
|
||||||
|
|
||||||
|
/* Parse current title ini. */
|
||||||
|
ini_parse_file(config, DmntTitleSpecificIniHandler, &cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HasOverrideKey(OverrideKey *cfg) {
|
||||||
|
u64 kDown = 0;
|
||||||
|
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
||||||
|
return (cfg->override_by_default ^ keys_triggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DmntConfigManager::HasCheatEnableButton(u64 tid) {
|
||||||
|
/* Unconditionally refresh loader.ini contents. */
|
||||||
|
RefreshConfiguration();
|
||||||
|
|
||||||
|
OverrideKey title_cfg = GetTitleCheatEnableKey(tid);
|
||||||
|
return HasOverrideKey(&title_cfg);
|
||||||
|
}
|
||||||
31
stratosphere/dmnt/source/dmnt_config.hpp
Normal file
31
stratosphere/dmnt/source/dmnt_config.hpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
struct OverrideKey {
|
||||||
|
u64 key_combination;
|
||||||
|
bool override_by_default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DmntConfigManager {
|
||||||
|
public:
|
||||||
|
static void RefreshConfiguration();
|
||||||
|
|
||||||
|
static OverrideKey GetTitleCheatEnableKey(u64 tid);
|
||||||
|
static bool HasCheatEnableButton(u64 tid);
|
||||||
|
};
|
||||||
32
stratosphere/dmnt/source/dmnt_hid.cpp
Normal file
32
stratosphere/dmnt/source/dmnt_hid.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_hid.hpp"
|
||||||
|
|
||||||
|
static HosMutex g_hid_keys_down_lock;
|
||||||
|
|
||||||
|
Result HidManagement::GetKeysDown(u64 *keys) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_hid_keys_down_lock);
|
||||||
|
|
||||||
|
hidScanInput();
|
||||||
|
*keys = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||||
|
|
||||||
|
return 0x0;
|
||||||
|
}
|
||||||
23
stratosphere/dmnt/source/dmnt_hid.hpp
Normal file
23
stratosphere/dmnt/source/dmnt_hid.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
class HidManagement {
|
||||||
|
public:
|
||||||
|
static Result GetKeysDown(u64 *keys);
|
||||||
|
};
|
||||||
162
stratosphere/dmnt/source/dmnt_main.cpp
Normal file
162
stratosphere/dmnt/source/dmnt_main.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <atmosphere.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "dmnt_service.hpp"
|
||||||
|
#include "dmnt_cheat_service.hpp"
|
||||||
|
#include "dmnt_cheat_manager.hpp"
|
||||||
|
#include "dmnt_config.hpp"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
extern u32 __start__;
|
||||||
|
|
||||||
|
u32 __nx_applet_type = AppletType_None;
|
||||||
|
|
||||||
|
#define INNER_HEAP_SIZE 0x80000
|
||||||
|
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
|
||||||
|
char nx_inner_heap[INNER_HEAP_SIZE];
|
||||||
|
|
||||||
|
void __libnx_initheap(void);
|
||||||
|
void __appInit(void);
|
||||||
|
void __appExit(void);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void __libnx_initheap(void) {
|
||||||
|
void* addr = nx_inner_heap;
|
||||||
|
size_t size = nx_inner_heap_size;
|
||||||
|
|
||||||
|
/* Newlib */
|
||||||
|
extern char* fake_heap_start;
|
||||||
|
extern char* fake_heap_end;
|
||||||
|
|
||||||
|
fake_heap_start = (char*)addr;
|
||||||
|
fake_heap_end = (char*)addr + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __appInit(void) {
|
||||||
|
Result rc;
|
||||||
|
|
||||||
|
SetFirmwareVersionForLibnx();
|
||||||
|
|
||||||
|
rc = smInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pmdmntInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = ldrDmntInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (kernelAbove300()) {
|
||||||
|
rc = roDmntInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
rc = nsdevInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = lrInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = setInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = hidInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = fsInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = fsdevMountSdmc();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __appExit(void) {
|
||||||
|
/* Cleanup services. */
|
||||||
|
fsdevUnmountAll();
|
||||||
|
fsExit();
|
||||||
|
hidExit();
|
||||||
|
setExit();
|
||||||
|
lrExit();
|
||||||
|
nsdevExit();
|
||||||
|
/* if (kernelAbove300()) { roDmntExit(); } */
|
||||||
|
ldrDmntExit();
|
||||||
|
pmdmntExit();
|
||||||
|
smExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
consoleDebugInit(debugDevice_SVC);
|
||||||
|
|
||||||
|
/* Initialize configuration manager. */
|
||||||
|
DmntConfigManager::RefreshConfiguration();
|
||||||
|
|
||||||
|
/* Start cheat manager. */
|
||||||
|
DmntCheatManager::InitializeCheatManager();
|
||||||
|
|
||||||
|
/* Nintendo uses four threads. Add a fifth for our cheat service. */
|
||||||
|
auto server_manager = new WaitableManager(5);
|
||||||
|
|
||||||
|
/* Create services. */
|
||||||
|
|
||||||
|
/* TODO: Implement rest of dmnt:- in ams.tma development branch. */
|
||||||
|
/* server_manager->AddWaitable(new ServiceServer<DebugMonitorService>("dmnt:-", 4)); */
|
||||||
|
|
||||||
|
|
||||||
|
server_manager->AddWaitable(new ServiceServer<DmntCheatService>("dmnt:cht", 1));
|
||||||
|
|
||||||
|
/* Loop forever, servicing our services. */
|
||||||
|
server_manager->Process();
|
||||||
|
|
||||||
|
delete server_manager;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
38
stratosphere/dmnt/source/dmnt_results.hpp
Normal file
38
stratosphere/dmnt/source/dmnt_results.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
static constexpr u32 Module_Dmnt = 13;
|
||||||
|
|
||||||
|
static constexpr Result ResultDmntUnknown = MAKERESULT(Module_Dmnt, 1);
|
||||||
|
static constexpr Result ResultDmntDebuggingDisabled = MAKERESULT(Module_Dmnt, 2);
|
||||||
|
|
||||||
|
static constexpr Result ResultDmntCheatNotAttached = MAKERESULT(Module_Dmnt, 6500);
|
||||||
|
static constexpr Result ResultDmntCheatNullBuffer = MAKERESULT(Module_Dmnt, 6501);
|
||||||
|
static constexpr Result ResultDmntCheatInvalidBuffer = MAKERESULT(Module_Dmnt, 6502);
|
||||||
|
static constexpr Result ResultDmntCheatUnknownChtId = MAKERESULT(Module_Dmnt, 6503);
|
||||||
|
static constexpr Result ResultDmntCheatOutOfCheats = MAKERESULT(Module_Dmnt, 6504);
|
||||||
|
static constexpr Result ResultDmntCheatInvalidCheat = MAKERESULT(Module_Dmnt, 6505);
|
||||||
|
|
||||||
|
static constexpr Result ResultDmntCheatInvalidFreezeWidth = MAKERESULT(Module_Dmnt, 6600);
|
||||||
|
static constexpr Result ResultDmntCheatAddressAlreadyFrozen = MAKERESULT(Module_Dmnt, 6601);
|
||||||
|
static constexpr Result ResultDmntCheatAddressNotFrozen = MAKERESULT(Module_Dmnt, 6602);
|
||||||
|
static constexpr Result ResultDmntCheatTooManyFrozenAddresses = MAKERESULT(Module_Dmnt, 6603);
|
||||||
|
|
||||||
|
static constexpr Result ResultDmntCheatVmInvalidCondDepth = MAKERESULT(Module_Dmnt, 6700);
|
||||||
150
stratosphere/dmnt/source/dmnt_service.hpp
Normal file
150
stratosphere/dmnt/source/dmnt_service.hpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
enum DmntCmd {
|
||||||
|
DebugMonitor_Cmd_BreakDebugProcess = 0,
|
||||||
|
DebugMonitor_Cmd_TerminateDebugProcess = 1,
|
||||||
|
DebugMonitor_Cmd_CloseHandle = 2,
|
||||||
|
DebugMonitor_Cmd_LoadImage = 3,
|
||||||
|
DebugMonitor_Cmd_GetProcessId = 4,
|
||||||
|
DebugMonitor_Cmd_GetProcessHandle = 5,
|
||||||
|
DebugMonitor_Cmd_WaitSynchronization = 6,
|
||||||
|
DebugMonitor_Cmd_GetDebugEvent = 7,
|
||||||
|
DebugMonitor_Cmd_GetProcessModuleInfo = 8,
|
||||||
|
DebugMonitor_Cmd_GetProcessList = 9,
|
||||||
|
DebugMonitor_Cmd_GetThreadList = 10,
|
||||||
|
DebugMonitor_Cmd_GetDebugThreadContext = 11,
|
||||||
|
DebugMonitor_Cmd_ContinueDebugEvent = 12,
|
||||||
|
DebugMonitor_Cmd_ReadDebugProcessMemory = 13,
|
||||||
|
DebugMonitor_Cmd_WriteDebugProcessMemory = 14,
|
||||||
|
DebugMonitor_Cmd_SetDebugThreadContext = 15,
|
||||||
|
DebugMonitor_Cmd_GetDebugThreadParam = 16,
|
||||||
|
DebugMonitor_Cmd_InitializeThreadInfo = 17,
|
||||||
|
DebugMonitor_Cmd_SetHardwareBreakPoint = 18,
|
||||||
|
DebugMonitor_Cmd_QueryDebugProcessMemory = 19,
|
||||||
|
DebugMonitor_Cmd_GetProcessMemoryDetails = 20,
|
||||||
|
DebugMonitor_Cmd_AttachByProgramId = 21,
|
||||||
|
DebugMonitor_Cmd_AttachOnLaunch = 22,
|
||||||
|
DebugMonitor_Cmd_GetDebugMonitorProcessId = 23,
|
||||||
|
DebugMonitor_Cmd_GetJitDebugProcessList = 25,
|
||||||
|
DebugMonitor_Cmd_CreateCoreDump = 26,
|
||||||
|
DebugMonitor_Cmd_GetAllDebugThreadInfo = 27,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileOpen = 29,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileClose = 30,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileRead = 31,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileWrite = 32,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileSetAttributes = 33,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileGetInformation = 34,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileSetTime = 35,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileSetSize = 36,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileDelete = 37,
|
||||||
|
DebugMonitor_Cmd_TargetIO_FileMove = 38,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryCreate = 39,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryDelete = 40,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryRename = 41,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryGetCount = 42,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryOpen = 43,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryGetNext = 44,
|
||||||
|
DebugMonitor_Cmd_TargetIO_DirectoryClose = 45,
|
||||||
|
DebugMonitor_Cmd_TargetIO_GetFreeSpace = 46,
|
||||||
|
DebugMonitor_Cmd_TargetIO_GetVolumeInformation = 47,
|
||||||
|
DebugMonitor_Cmd_InitiateCoreDump = 48,
|
||||||
|
DebugMonitor_Cmd_ContinueCoreDump = 49,
|
||||||
|
DebugMonitor_Cmd_AddTTYToCoreDump = 50,
|
||||||
|
DebugMonitor_Cmd_AddImageToCoreDump = 51,
|
||||||
|
DebugMonitor_Cmd_CloseCoreDump = 52,
|
||||||
|
DebugMonitor_Cmd_CancelAttach = 53,
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugMonitorService final : public IServiceObject {
|
||||||
|
private:
|
||||||
|
Result BreakDebugProcess(Handle debug_hnd);
|
||||||
|
Result TerminateDebugProcess(Handle debug_hnd);
|
||||||
|
Result CloseHandle(Handle debug_hnd);
|
||||||
|
Result GetProcessId(Out<u64> out_pid, Handle hnd);
|
||||||
|
Result GetProcessHandle(Out<Handle> out_hnd, u64 pid);
|
||||||
|
Result WaitSynchronization(Handle hnd, u64 ns);
|
||||||
|
|
||||||
|
Result TargetIO_FileOpen(OutBuffer<u64> out_hnd, InBuffer<char> path, int open_mode, u32 create_mode);
|
||||||
|
Result TargetIO_FileClose(InBuffer<u64> hnd);
|
||||||
|
Result TargetIO_FileRead(InBuffer<u64> hnd, OutBuffer<u8, BufferType_Type1> out_data, Out<u32> out_read, u64 offset);
|
||||||
|
Result TargetIO_FileWrite(InBuffer<u64> hnd, InBuffer<u8, BufferType_Type1> data, Out<u32> out_written, u64 offset);
|
||||||
|
Result TargetIO_FileSetAttributes(InBuffer<char> path, InBuffer<u8> attributes);
|
||||||
|
Result TargetIO_FileGetInformation(InBuffer<char> path, OutBuffer<u64> out_info, Out<int> is_directory);
|
||||||
|
Result TargetIO_FileSetTime(InBuffer<char> path, u64 create, u64 access, u64 modify);
|
||||||
|
Result TargetIO_FileSetSize(InBuffer<char> path, u64 size);
|
||||||
|
Result TargetIO_FileDelete(InBuffer<char> path);
|
||||||
|
Result TargetIO_FileMove(InBuffer<char> path0, InBuffer<char> path1);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_BreakDebugProcess, &DebugMonitorService::BreakDebugProcess>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TerminateDebugProcess, &DebugMonitorService::TerminateDebugProcess>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_CloseHandle, &DebugMonitorService::CloseHandle>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_LoadImage, &DebugMonitorService::LoadImage>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_GetProcessId, &DebugMonitorService::GetProcessId>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_GetProcessHandle, &DebugMonitorService::GetProcessHandle>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_WaitSynchronization, &DebugMonitorService::WaitSynchronization>(),
|
||||||
|
//MakeServiceCommandMeta<DebugMonitor_Cmd_GetDebugEvent, &DebugMonitorService::GetDebugEvent>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetProcessModuleInfo, &DebugMonitorService::GetProcessModuleInfo>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetProcessList, &DebugMonitorService::GetProcessList>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetThreadList, &DebugMonitorService::GetThreadList>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetDebugThreadContext, &DebugMonitorService::GetDebugThreadContext>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_ContinueDebugEvent, &DebugMonitorService::ContinueDebugEvent>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_ReadDebugProcessMemory, &DebugMonitorService::ReadDebugProcessMemory>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_WriteDebugProcessMemory, &DebugMonitorService::WriteDebugProcessMemory>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_SetDebugThreadContext, &DebugMonitorService::SetDebugThreadContext>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetDebugThreadParam, &DebugMonitorService::GetDebugThreadParam>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_InitializeThreadInfo, &DebugMonitorService::InitializeThreadInfo>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_SetHardwareBreakPoint, &DebugMonitorService::SetHardwareBreakPoint>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_QueryDebugProcessMemory, &DebugMonitorService::QueryDebugProcessMemory>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetProcessMemoryDetails, &DebugMonitorService::GetProcessMemoryDetails>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_AttachByProgramId, &DebugMonitorService::AttachByProgramId>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_AttachOnLaunch, &DebugMonitorService::AttachOnLaunch>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetDebugMonitorProcessId, &DebugMonitorService::GetDebugMonitorProcessId>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetJitDebugProcessList, &DebugMonitorService::GetJitDebugProcessList>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_CreateCoreDump, &DebugMonitorService::CreateCoreDump>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_GetAllDebugThreadInfo, &DebugMonitorService::GetAllDebugThreadInfo>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileOpen, &DebugMonitorService::TargetIO_FileOpen>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileClose, &DebugMonitorService::TargetIO_FileClose>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileRead, &DebugMonitorService::TargetIO_FileRead>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileWrite, &DebugMonitorService::TargetIO_FileWrite>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileSetAttributes, &DebugMonitorService::TargetIO_FileSetAttributes>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileGetInformation, &DebugMonitorService::TargetIO_FileGetInformation>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileSetTime, &DebugMonitorService::TargetIO_FileSetTime>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileSetSize, &DebugMonitorService::TargetIO_FileSetSize>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileDelete, &DebugMonitorService::TargetIO_FileDelete>(),
|
||||||
|
MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_FileMove, &DebugMonitorService::TargetIO_FileMove>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryCreate, &DebugMonitorService::TargetIO_DirectoryCreate>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryDelete, &DebugMonitorService::TargetIO_DirectoryDelete>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryRename, &DebugMonitorService::TargetIO_DirectoryRename>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryGetCount, &DebugMonitorService::TargetIO_DirectoryGetCount>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryOpen, &DebugMonitorService::TargetIO_DirectoryOpen>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryGetNext, &DebugMonitorService::TargetIO_DirectoryGetNext>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_DirectoryClose, &DebugMonitorService::TargetIO_DirectoryClose>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_GetFreeSpace, &DebugMonitorService::TargetIO_GetFreeSpace>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_TargetIO_GetVolumeInformation, &DebugMonitorService::TargetIO_GetVolumeInformation>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_InitiateCoreDump, &DebugMonitorService::InitiateCoreDump>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_ContinueCoreDump, &DebugMonitorService::ContinueCoreDump>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_AddTTYToCoreDump, &DebugMonitorService::AddTTYToCoreDump>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_AddImageToCoreDump, &DebugMonitorService::AddImageToCoreDump>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_CloseCoreDump, &DebugMonitorService::CloseCoreDump>(),
|
||||||
|
// MakeServiceCommandMeta<DebugMonitor_Cmd_CancelAttach, &DebugMonitorService::CancelAttach>(),
|
||||||
|
};
|
||||||
|
};
|
||||||
52
stratosphere/dmnt/source/dmnt_service_debug.cpp
Normal file
52
stratosphere/dmnt/source/dmnt_service_debug.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "dmnt_service.hpp"
|
||||||
|
|
||||||
|
Result DebugMonitorService::BreakDebugProcess(Handle debug_hnd) {
|
||||||
|
/* Nintendo discards the output of this command, but we will return it. */
|
||||||
|
return svcBreakDebugProcess(debug_hnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TerminateDebugProcess(Handle debug_hnd) {
|
||||||
|
/* Nintendo discards the output of this command, but we will return it. */
|
||||||
|
return svcTerminateDebugProcess(debug_hnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::CloseHandle(Handle debug_hnd) {
|
||||||
|
/* Nintendo discards the output of this command, but we will return it. */
|
||||||
|
/* This command is, entertainingly, also pretty unsafe in general... */
|
||||||
|
return svcCloseHandle(debug_hnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::GetProcessId(Out<u64> out_pid, Handle hnd) {
|
||||||
|
/* Nintendo discards the output of this command, but we will return it. */
|
||||||
|
return svcGetProcessId(out_pid.GetPointer(), hnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::GetProcessHandle(Out<Handle> out_hnd, u64 pid) {
|
||||||
|
Result rc = svcDebugActiveProcess(out_hnd.GetPointer(), pid);
|
||||||
|
if (rc == 0xF401) {
|
||||||
|
rc = 0x4B7;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::WaitSynchronization(Handle hnd, u64 ns) {
|
||||||
|
/* Nintendo discards the output of this command, but we will return it. */
|
||||||
|
return svcWaitSynchronizationSingle(hnd, ns);
|
||||||
|
}
|
||||||
299
stratosphere/dmnt/source/dmnt_service_target_io.cpp
Normal file
299
stratosphere/dmnt/source/dmnt_service_target_io.cpp
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <switch.h>
|
||||||
|
#include "dmnt_service.hpp"
|
||||||
|
|
||||||
|
enum TIOCreateOption : u32 {
|
||||||
|
TIOCreateOption_CreateNew = 1,
|
||||||
|
TIOCreateOption_CreateAlways = 2,
|
||||||
|
TIOCreateOption_OpenExisting = 3,
|
||||||
|
TIOCreateOption_OpenAlways = 4,
|
||||||
|
TIOCreateOption_ResetSize = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Nintendo uses actual pointers as file handles. We'll add a layer of indirection... */
|
||||||
|
static bool g_sd_initialized = false;
|
||||||
|
static HosMutex g_sd_lock;
|
||||||
|
static FsFileSystem g_sd_fs;
|
||||||
|
|
||||||
|
static HosMutex g_file_handle_lock;
|
||||||
|
static u64 g_cur_fd = 0;
|
||||||
|
static std::unordered_map<u64, FsFile> g_file_handles;
|
||||||
|
|
||||||
|
static Result EnsureSdInitialized() {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_sd_lock);
|
||||||
|
if (g_sd_initialized) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = fsMountSdcard(&g_sd_fs);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
g_sd_initialized = true;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 GetFileHandle(FsFile f) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||||
|
|
||||||
|
u64 fd = g_cur_fd++;
|
||||||
|
g_file_handles[fd] = f;
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result GetFileByHandle(FsFile *out, u64 handle) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||||
|
if (g_file_handles.find(handle) != g_file_handles.end()) {
|
||||||
|
*out = g_file_handles[handle];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0x2EE202;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result CloseFileByHandle(u64 handle) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||||
|
if (g_file_handles.find(handle) != g_file_handles.end()) {
|
||||||
|
fsFileClose(&g_file_handles[handle]);
|
||||||
|
g_file_handles.erase(handle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0x2EE202;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FixPath(char *dst, size_t dst_size, InBuffer<char> &path) {
|
||||||
|
dst[dst_size - 1] = 0;
|
||||||
|
strncpy(dst, "/", dst_size - 1);
|
||||||
|
|
||||||
|
const char *src = path.buffer;
|
||||||
|
size_t src_idx = 0;
|
||||||
|
size_t dst_idx = 1;
|
||||||
|
while (src_idx < path.num_elements && (src[src_idx] == '/' || src[src_idx] == '\\')) {
|
||||||
|
src_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (src_idx < path.num_elements && dst_idx < dst_size - 1 && src[src_idx] != 0) {
|
||||||
|
if (src[src_idx] == '\\') {
|
||||||
|
dst[dst_idx] = '/';
|
||||||
|
} else {
|
||||||
|
dst[dst_idx] = src[src_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
src_idx++;
|
||||||
|
dst_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst_idx < dst_size) {
|
||||||
|
dst[dst_idx] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileOpen(OutBuffer<u64> out_hnd, InBuffer<char> path, int open_mode, u32 create_mode) {
|
||||||
|
if (out_hnd.num_elements != 1) {
|
||||||
|
return 0xF601;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = EnsureSdInitialized();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
char fs_path[FS_MAX_PATH];
|
||||||
|
FixPath(fs_path, sizeof(fs_path), path);
|
||||||
|
|
||||||
|
if (create_mode == TIOCreateOption_CreateAlways) {
|
||||||
|
fsFsDeleteFile(&g_sd_fs, fs_path);
|
||||||
|
rc = fsFsCreateFile(&g_sd_fs, fs_path, 0, 0);
|
||||||
|
} else if (create_mode == TIOCreateOption_CreateNew) {
|
||||||
|
rc = fsFsCreateFile(&g_sd_fs, fs_path, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
FsFile f;
|
||||||
|
rc = fsFsOpenFile(&g_sd_fs, fs_path, open_mode, &f);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
if (create_mode == TIOCreateOption_OpenAlways) {
|
||||||
|
fsFsCreateFile(&g_sd_fs, fs_path, 0, 0);
|
||||||
|
rc = fsFsOpenFile(&g_sd_fs, fs_path, open_mode, &f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (create_mode == TIOCreateOption_ResetSize) {
|
||||||
|
rc = fsFileSetSize(&f, 0);
|
||||||
|
}
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
out_hnd[0] = GetFileHandle(f);
|
||||||
|
} else {
|
||||||
|
fsFileClose(&f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileClose(InBuffer<u64> hnd) {
|
||||||
|
if (hnd.num_elements != 1) {
|
||||||
|
return 0xF601;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CloseFileByHandle(hnd[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileRead(InBuffer<u64> hnd, OutBuffer<u8, BufferType_Type1> out_data, Out<u32> out_read, u64 offset) {
|
||||||
|
if (hnd.num_elements != 1) {
|
||||||
|
return 0xF601;
|
||||||
|
}
|
||||||
|
|
||||||
|
FsFile f;
|
||||||
|
Result rc = GetFileByHandle(&f, hnd[0]);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read = 0;
|
||||||
|
rc = fsFileRead(&f, offset, out_data.buffer, out_data.num_elements, &read);
|
||||||
|
out_read.SetValue(static_cast<u32>(read));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileWrite(InBuffer<u64> hnd, InBuffer<u8, BufferType_Type1> data, Out<u32> out_written, u64 offset) {
|
||||||
|
if (hnd.num_elements != 1) {
|
||||||
|
return 0xF601;
|
||||||
|
}
|
||||||
|
|
||||||
|
FsFile f;
|
||||||
|
Result rc = GetFileByHandle(&f, hnd[0]);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = fsFileWrite(&f, offset, data.buffer, data.num_elements);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
out_written.SetValue(data.num_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileSetAttributes(InBuffer<char> path, InBuffer<u8> attributes) {
|
||||||
|
/* I don't really know why this command exists, Horizon doesn't allow you to set any attributes. */
|
||||||
|
/* N just returns 0x0 unconditionally here. */
|
||||||
|
return 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileGetInformation(InBuffer<char> path, OutBuffer<u64> out_info, Out<int> is_directory) {
|
||||||
|
if (out_info.num_elements != 4) {
|
||||||
|
return 0xF601;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = EnsureSdInitialized();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
char fs_path[FS_MAX_PATH];
|
||||||
|
FixPath(fs_path, sizeof(fs_path), path);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < out_info.num_elements; i++) {
|
||||||
|
out_info[i] = 0;
|
||||||
|
}
|
||||||
|
is_directory.SetValue(0);
|
||||||
|
|
||||||
|
FsFile f;
|
||||||
|
rc = fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_READ, &f);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
ON_SCOPE_EXIT { fsFileClose(&f); };
|
||||||
|
|
||||||
|
/* N doesn't check this return code. */
|
||||||
|
fsFileGetSize(&f, &out_info[0]);
|
||||||
|
|
||||||
|
/* TODO: N does not call fsFsGetFileTimestampRaw here, but we possibly could. */
|
||||||
|
} else {
|
||||||
|
FsDir dir;
|
||||||
|
rc = fsFsOpenDirectory(&g_sd_fs, fs_path, FS_DIROPEN_FILE | FS_DIROPEN_DIRECTORY, &dir);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
fsDirClose(&dir);
|
||||||
|
is_directory.SetValue(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileSetTime(InBuffer<char> path, u64 create, u64 access, u64 modify) {
|
||||||
|
/* This is another function that doesn't really need to exist, because Horizon doesn't let you set anything. */
|
||||||
|
return 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileSetSize(InBuffer<char> input, u64 size) {
|
||||||
|
/* Why does this function take in a path and not a file handle? */
|
||||||
|
|
||||||
|
/* We will try to be better than N, here. N only treats input as a path. */
|
||||||
|
if (input.num_elements == sizeof(u64)) {
|
||||||
|
FsFile f;
|
||||||
|
if (R_SUCCEEDED(GetFileByHandle(&f, reinterpret_cast<u64 *>(input.buffer)[0]))) {
|
||||||
|
return fsFileSetSize(&f, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = EnsureSdInitialized();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
char fs_path[FS_MAX_PATH];
|
||||||
|
FixPath(fs_path, sizeof(fs_path), input);
|
||||||
|
|
||||||
|
FsFile f;
|
||||||
|
rc = fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_WRITE, &f);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
rc = fsFileSetSize(&f, size);
|
||||||
|
fsFileClose(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileDelete(InBuffer<char> path) {
|
||||||
|
Result rc = EnsureSdInitialized();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
char fs_path[FS_MAX_PATH];
|
||||||
|
FixPath(fs_path, sizeof(fs_path), path);
|
||||||
|
|
||||||
|
return fsFsDeleteFile(&g_sd_fs, fs_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DebugMonitorService::TargetIO_FileMove(InBuffer<char> path0, InBuffer<char> path1) {
|
||||||
|
Result rc = EnsureSdInitialized();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
char fs_path0[FS_MAX_PATH];
|
||||||
|
char fs_path1[FS_MAX_PATH];
|
||||||
|
FixPath(fs_path0, sizeof(fs_path0), path0);
|
||||||
|
FixPath(fs_path1, sizeof(fs_path1), path1);
|
||||||
|
|
||||||
|
return fsFsRenameFile(&g_sd_fs, fs_path0, fs_path1);
|
||||||
|
}
|
||||||
269
stratosphere/dmnt/source/ini.c
Normal file
269
stratosphere/dmnt/source/ini.c
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
https://github.com/benhoyt/inih
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ini.h"
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_SECTION 50
|
||||||
|
#define MAX_NAME 50
|
||||||
|
|
||||||
|
/* Used by ini_parse_string() to keep track of string parsing state. */
|
||||||
|
typedef struct {
|
||||||
|
const char* ptr;
|
||||||
|
size_t num_left;
|
||||||
|
} ini_parse_string_ctx;
|
||||||
|
|
||||||
|
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||||
|
static char* rstrip(char* s)
|
||||||
|
{
|
||||||
|
char* p = s + strlen(s);
|
||||||
|
while (p > s && isspace((unsigned char)(*--p)))
|
||||||
|
*p = '\0';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first non-whitespace char in given string. */
|
||||||
|
static char* lskip(const char* s)
|
||||||
|
{
|
||||||
|
while (*s && isspace((unsigned char)(*s)))
|
||||||
|
s++;
|
||||||
|
return (char*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||||
|
or pointer to null at end of string if neither found. Inline comment must
|
||||||
|
be prefixed by a whitespace character to register as a comment. */
|
||||||
|
static char* find_chars_or_comment(const char* s, const char* chars)
|
||||||
|
{
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
int was_space = 0;
|
||||||
|
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||||
|
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||||
|
was_space = isspace((unsigned char)(*s));
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
while (*s && (!chars || !strchr(chars, *s))) {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return (char*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||||
|
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||||
|
{
|
||||||
|
strncpy(dest, src, size - 1);
|
||||||
|
dest[size - 1] = '\0';
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||||
|
void* user)
|
||||||
|
{
|
||||||
|
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||||
|
#if INI_USE_STACK
|
||||||
|
char line[INI_MAX_LINE];
|
||||||
|
int max_line = INI_MAX_LINE;
|
||||||
|
#else
|
||||||
|
char* line;
|
||||||
|
int max_line = INI_INITIAL_ALLOC;
|
||||||
|
#endif
|
||||||
|
#if INI_ALLOW_REALLOC
|
||||||
|
char* new_line;
|
||||||
|
int offset;
|
||||||
|
#endif
|
||||||
|
char section[MAX_SECTION] = "";
|
||||||
|
char prev_name[MAX_NAME] = "";
|
||||||
|
|
||||||
|
char* start;
|
||||||
|
char* end;
|
||||||
|
char* name;
|
||||||
|
char* value;
|
||||||
|
int lineno = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
line = (char*)malloc(INI_INITIAL_ALLOC);
|
||||||
|
if (!line) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if INI_HANDLER_LINENO
|
||||||
|
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||||
|
#else
|
||||||
|
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Scan through stream line by line */
|
||||||
|
while (reader(line, max_line, stream) != NULL) {
|
||||||
|
#if INI_ALLOW_REALLOC
|
||||||
|
offset = strlen(line);
|
||||||
|
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||||
|
max_line *= 2;
|
||||||
|
if (max_line > INI_MAX_LINE)
|
||||||
|
max_line = INI_MAX_LINE;
|
||||||
|
new_line = realloc(line, max_line);
|
||||||
|
if (!new_line) {
|
||||||
|
free(line);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
line = new_line;
|
||||||
|
if (reader(line + offset, max_line - offset, stream) == NULL)
|
||||||
|
break;
|
||||||
|
if (max_line >= INI_MAX_LINE)
|
||||||
|
break;
|
||||||
|
offset += strlen(line + offset);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
lineno++;
|
||||||
|
|
||||||
|
start = line;
|
||||||
|
#if INI_ALLOW_BOM
|
||||||
|
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||||
|
(unsigned char)start[1] == 0xBB &&
|
||||||
|
(unsigned char)start[2] == 0xBF) {
|
||||||
|
start += 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
start = lskip(rstrip(start));
|
||||||
|
|
||||||
|
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
||||||
|
/* Start-of-line comment */
|
||||||
|
}
|
||||||
|
#if INI_ALLOW_MULTILINE
|
||||||
|
else if (*prev_name && *start && start > line) {
|
||||||
|
/* Non-blank line with leading whitespace, treat as continuation
|
||||||
|
of previous name's value (as per Python configparser). */
|
||||||
|
if (!HANDLER(user, section, prev_name, start) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (*start == '[') {
|
||||||
|
/* A "[section]" line */
|
||||||
|
end = find_chars_or_comment(start + 1, "]");
|
||||||
|
if (*end == ']') {
|
||||||
|
*end = '\0';
|
||||||
|
strncpy0(section, start + 1, sizeof(section));
|
||||||
|
*prev_name = '\0';
|
||||||
|
}
|
||||||
|
else if (!error) {
|
||||||
|
/* No ']' found on section line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*start) {
|
||||||
|
/* Not a comment, must be a name[=:]value pair */
|
||||||
|
end = find_chars_or_comment(start, "=:");
|
||||||
|
if (*end == '=' || *end == ':') {
|
||||||
|
*end = '\0';
|
||||||
|
name = rstrip(start);
|
||||||
|
value = end + 1;
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
end = find_chars_or_comment(value, NULL);
|
||||||
|
if (*end)
|
||||||
|
*end = '\0';
|
||||||
|
#endif
|
||||||
|
value = lskip(value);
|
||||||
|
rstrip(value);
|
||||||
|
|
||||||
|
/* Valid name[=:]value pair found, call handler */
|
||||||
|
strncpy0(prev_name, name, sizeof(prev_name));
|
||||||
|
if (!HANDLER(user, section, name, value) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
else if (!error) {
|
||||||
|
/* No '=' or ':' found on name[=:]value line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if INI_STOP_ON_FIRST_ERROR
|
||||||
|
if (error)
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
free(line);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||||
|
{
|
||||||
|
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||||
|
{
|
||||||
|
FILE* file;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
file = fopen(filename, "r");
|
||||||
|
if (!file)
|
||||||
|
return -1;
|
||||||
|
error = ini_parse_file(file, handler, user);
|
||||||
|
fclose(file);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An ini_reader function to read the next line from a string buffer. This
|
||||||
|
is the fgets() equivalent used by ini_parse_string(). */
|
||||||
|
static char* ini_reader_string(char* str, int num, void* stream) {
|
||||||
|
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
|
||||||
|
const char* ctx_ptr = ctx->ptr;
|
||||||
|
size_t ctx_num_left = ctx->num_left;
|
||||||
|
char* strp = str;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
if (ctx_num_left == 0 || num < 2)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while (num > 1 && ctx_num_left != 0) {
|
||||||
|
c = *ctx_ptr++;
|
||||||
|
ctx_num_left--;
|
||||||
|
*strp++ = c;
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
num--;
|
||||||
|
}
|
||||||
|
|
||||||
|
*strp = '\0';
|
||||||
|
ctx->ptr = ctx_ptr;
|
||||||
|
ctx->num_left = ctx_num_left;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_string(const char* string, ini_handler handler, void* user) {
|
||||||
|
ini_parse_string_ctx ctx;
|
||||||
|
|
||||||
|
ctx.ptr = string;
|
||||||
|
ctx.num_left = strlen(string);
|
||||||
|
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
|
||||||
|
user);
|
||||||
|
}
|
||||||
130
stratosphere/dmnt/source/ini.h
Normal file
130
stratosphere/dmnt/source/ini.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
https://github.com/benhoyt/inih
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __INI_H__
|
||||||
|
#define __INI_H__
|
||||||
|
|
||||||
|
/* Make this header file easier to include in C++ code */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
||||||
|
#ifndef INI_HANDLER_LINENO
|
||||||
|
#define INI_HANDLER_LINENO 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Typedef for prototype of handler function. */
|
||||||
|
#if INI_HANDLER_LINENO
|
||||||
|
typedef int (*ini_handler)(void* user, const char* section,
|
||||||
|
const char* name, const char* value,
|
||||||
|
int lineno);
|
||||||
|
#else
|
||||||
|
typedef int (*ini_handler)(void* user, const char* section,
|
||||||
|
const char* name, const char* value);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Typedef for prototype of fgets-style reader function. */
|
||||||
|
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||||
|
|
||||||
|
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||||
|
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||||
|
is "" if name=value pair parsed before any section heading. name:value
|
||||||
|
pairs are also supported as a concession to Python's configparser.
|
||||||
|
|
||||||
|
For each name=value pair parsed, call handler function with given user
|
||||||
|
pointer as well as section, name, and value (data only valid for duration
|
||||||
|
of handler call). Handler should return nonzero on success, zero on error.
|
||||||
|
|
||||||
|
Returns 0 on success, line number of first error on parse error (doesn't
|
||||||
|
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||||
|
error (only when INI_USE_STACK is zero).
|
||||||
|
*/
|
||||||
|
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||||
|
close the file when it's finished -- the caller must do that. */
|
||||||
|
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||||
|
filename. Used for implementing custom or string-based I/O (see also
|
||||||
|
ini_parse_string). */
|
||||||
|
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||||
|
void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
||||||
|
instead of a file. Useful for parsing INI data from a network socket or
|
||||||
|
already in memory. */
|
||||||
|
int ini_parse_string(const char* string, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||||
|
configparser. If allowed, ini_parse() will call the handler with the same
|
||||||
|
name for each subsequent line parsed. */
|
||||||
|
#ifndef INI_ALLOW_MULTILINE
|
||||||
|
#define INI_ALLOW_MULTILINE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||||
|
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||||
|
#ifndef INI_ALLOW_BOM
|
||||||
|
#define INI_ALLOW_BOM 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
||||||
|
both ; and # comments at the start of a line by default. */
|
||||||
|
#ifndef INI_START_COMMENT_PREFIXES
|
||||||
|
#define INI_START_COMMENT_PREFIXES ";#"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||||
|
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||||
|
Python 3.2+ configparser behaviour. */
|
||||||
|
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||||
|
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||||
|
#endif
|
||||||
|
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||||
|
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
||||||
|
#ifndef INI_USE_STACK
|
||||||
|
#define INI_USE_STACK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum line length for any line in INI file (stack or heap). Note that
|
||||||
|
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
||||||
|
#ifndef INI_MAX_LINE
|
||||||
|
#define INI_MAX_LINE 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
|
||||||
|
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
|
||||||
|
zero. */
|
||||||
|
#ifndef INI_ALLOW_REALLOC
|
||||||
|
#define INI_ALLOW_REALLOC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
|
||||||
|
is zero. */
|
||||||
|
#ifndef INI_INITIAL_ALLOC
|
||||||
|
#define INI_INITIAL_ALLOC 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Stop parsing on first error (default is to keep parsing). */
|
||||||
|
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||||
|
#define INI_STOP_ON_FIRST_ERROR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __INI_H__ */
|
||||||
66
stratosphere/dmnt/source/pm_shim.c
Normal file
66
stratosphere/dmnt/source/pm_shim.c
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "pm_shim.h"
|
||||||
|
|
||||||
|
/* Atmosphere extension commands. */
|
||||||
|
Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
Service *s = pmdmntGetServiceSession();
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
u64 pid;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 65000;
|
||||||
|
raw->pid = pid;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(s);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
u64 title_id;
|
||||||
|
FsStorageId storage_id;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(s, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (out) {
|
||||||
|
*out = r.Handles[0];
|
||||||
|
} else {
|
||||||
|
svcCloseHandle(r.Handles[0]);
|
||||||
|
}
|
||||||
|
if (tid_out) *tid_out = resp->title_id;
|
||||||
|
if (sid_out) *sid_out = resp->storage_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
19
stratosphere/dmnt/source/pm_shim.h
Normal file
19
stratosphere/dmnt/source/pm_shim.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @file pm_shim.h
|
||||||
|
* @brief Process Management (pm) IPC wrapper.
|
||||||
|
* @author SciresM
|
||||||
|
* @copyright libnx Authors
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Atmosphere extension commands. */
|
||||||
|
Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -39,7 +39,6 @@ static void RunTaskThreadFunc(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Finish. */
|
/* Finish. */
|
||||||
svcExitThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RunTask(IFatalTask *task, u32 stack_size = 0x4000) {
|
static void RunTask(IFatalTask *task, u32 stack_size = 0x4000) {
|
||||||
|
|||||||
Submodule stratosphere/libstratosphere updated: 9ce1dce440...fa37b70b0e
@@ -38,10 +38,26 @@ static bool g_has_initialized_fs_dev = false;
|
|||||||
/* Default to Key R, hold disables override, HBL at atmosphere/hbl.nsp. */
|
/* Default to Key R, hold disables override, HBL at atmosphere/hbl.nsp. */
|
||||||
static bool g_mounted_hbl_nsp = false;
|
static bool g_mounted_hbl_nsp = false;
|
||||||
static char g_hbl_sd_path[FS_MAX_PATH+1] = "@Sdcard:/atmosphere/hbl.nsp\x00";
|
static char g_hbl_sd_path[FS_MAX_PATH+1] = "@Sdcard:/atmosphere/hbl.nsp\x00";
|
||||||
static u64 g_override_key_combination = KEY_R;
|
|
||||||
static bool g_override_by_default = true;
|
static OverrideKey g_default_override_key = {
|
||||||
static u64 g_override_hbl_tid = 0x010000000000100D;
|
.key_combination = KEY_L,
|
||||||
static bool g_override_any_app = false;
|
.override_by_default = true
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HblOverrideConfig {
|
||||||
|
OverrideKey override_key;
|
||||||
|
u64 title_id;
|
||||||
|
bool override_any_app;
|
||||||
|
};
|
||||||
|
|
||||||
|
static HblOverrideConfig g_hbl_override_config = {
|
||||||
|
.override_key = {
|
||||||
|
.key_combination = KEY_R,
|
||||||
|
.override_by_default = true
|
||||||
|
},
|
||||||
|
.title_id = 0x010000000000100D,
|
||||||
|
.override_any_app = false
|
||||||
|
};
|
||||||
|
|
||||||
/* Static buffer for loader.ini contents at runtime. */
|
/* Static buffer for loader.ini contents at runtime. */
|
||||||
static char g_config_ini_data[0x800];
|
static char g_config_ini_data[0x800];
|
||||||
@@ -62,7 +78,7 @@ Result ContentManagement::MountCode(u64 tid, FsStorageId sid) {
|
|||||||
RefreshConfigurationData();
|
RefreshConfigurationData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldOverrideContents(tid) && R_SUCCEEDED(MountCodeNspOnSd(tid))) {
|
if (ShouldOverrideContentsWithSD(tid) && R_SUCCEEDED(MountCodeNspOnSd(tid))) {
|
||||||
return 0x0;
|
return 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,72 +220,100 @@ void ContentManagement::SetCreatedTitle(u64 tid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static OverrideKey ParseOverrideKey(const char *value) {
|
||||||
|
OverrideKey cfg;
|
||||||
|
|
||||||
|
/* Parse on by default. */
|
||||||
|
if (value[0] == '!') {
|
||||||
|
cfg.override_by_default = true;
|
||||||
|
value++;
|
||||||
|
} else {
|
||||||
|
cfg.override_by_default = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse key combination. */
|
||||||
|
if (strcasecmp(value, "A") == 0) {
|
||||||
|
cfg.key_combination = KEY_A;
|
||||||
|
} else if (strcasecmp(value, "B") == 0) {
|
||||||
|
cfg.key_combination = KEY_B;
|
||||||
|
} else if (strcasecmp(value, "X") == 0) {
|
||||||
|
cfg.key_combination = KEY_X;
|
||||||
|
} else if (strcasecmp(value, "Y") == 0) {
|
||||||
|
cfg.key_combination = KEY_Y;
|
||||||
|
} else if (strcasecmp(value, "LS") == 0) {
|
||||||
|
cfg.key_combination = KEY_LSTICK;
|
||||||
|
} else if (strcasecmp(value, "RS") == 0) {
|
||||||
|
cfg.key_combination = KEY_RSTICK;
|
||||||
|
} else if (strcasecmp(value, "L") == 0) {
|
||||||
|
cfg.key_combination = KEY_L;
|
||||||
|
} else if (strcasecmp(value, "R") == 0) {
|
||||||
|
cfg.key_combination = KEY_R;
|
||||||
|
} else if (strcasecmp(value, "ZL") == 0) {
|
||||||
|
cfg.key_combination = KEY_ZL;
|
||||||
|
} else if (strcasecmp(value, "ZR") == 0) {
|
||||||
|
cfg.key_combination = KEY_ZR;
|
||||||
|
} else if (strcasecmp(value, "PLUS") == 0) {
|
||||||
|
cfg.key_combination = KEY_PLUS;
|
||||||
|
} else if (strcasecmp(value, "MINUS") == 0) {
|
||||||
|
cfg.key_combination = KEY_MINUS;
|
||||||
|
} else if (strcasecmp(value, "DLEFT") == 0) {
|
||||||
|
cfg.key_combination = KEY_DLEFT;
|
||||||
|
} else if (strcasecmp(value, "DUP") == 0) {
|
||||||
|
cfg.key_combination = KEY_DUP;
|
||||||
|
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
||||||
|
cfg.key_combination = KEY_DRIGHT;
|
||||||
|
} else if (strcasecmp(value, "DDOWN") == 0) {
|
||||||
|
cfg.key_combination = KEY_DDOWN;
|
||||||
|
} else if (strcasecmp(value, "SL") == 0) {
|
||||||
|
cfg.key_combination = KEY_SL;
|
||||||
|
} else if (strcasecmp(value, "SR") == 0) {
|
||||||
|
cfg.key_combination = KEY_SR;
|
||||||
|
} else {
|
||||||
|
cfg.key_combination = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
static int LoaderIniHandler(void *user, const char *section, const char *name, const char *value) {
|
static int LoaderIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||||
/* Taken and modified, with love, from Rajkosto's implementation. */
|
/* Taken and modified, with love, from Rajkosto's implementation. */
|
||||||
if (strcasecmp(section, "config") == 0) {
|
if (strcasecmp(section, "hbl_config") == 0) {
|
||||||
if (strcasecmp(name, "hbl_tid") == 0) {
|
if (strcasecmp(name, "title_id") == 0) {
|
||||||
if (strcasecmp(value, "app") == 0) {
|
if (strcasecmp(value, "app") == 0) {
|
||||||
g_override_any_app = true;
|
g_hbl_override_config.override_any_app = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
u64 override_tid = strtoul(value, NULL, 16);
|
u64 override_tid = strtoul(value, NULL, 16);
|
||||||
if (override_tid != 0) {
|
if (override_tid != 0) {
|
||||||
g_override_hbl_tid = override_tid;
|
g_hbl_override_config.title_id = override_tid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (strcasecmp(name, "hbl_path") == 0) {
|
} else if (strcasecmp(name, "path") == 0) {
|
||||||
while (*value == '/' || *value == '\\') {
|
while (*value == '/' || *value == '\\') {
|
||||||
value++;
|
value++;
|
||||||
}
|
}
|
||||||
snprintf(g_hbl_sd_path, FS_MAX_PATH, "@Sdcard:/%s", value);
|
snprintf(g_hbl_sd_path, FS_MAX_PATH, "@Sdcard:/%s", value);
|
||||||
g_hbl_sd_path[FS_MAX_PATH] = 0;
|
g_hbl_sd_path[FS_MAX_PATH] = 0;
|
||||||
} else if (strcasecmp(name, "override_key") == 0) {
|
} else if (strcasecmp(name, "override_key") == 0) {
|
||||||
if (value[0] == '!') {
|
g_hbl_override_config.override_key = ParseOverrideKey(value);
|
||||||
g_override_by_default = true;
|
|
||||||
value++;
|
|
||||||
} else {
|
|
||||||
g_override_by_default = false;
|
|
||||||
}
|
}
|
||||||
|
} else if (strcasecmp(section, "default_config") == 0) {
|
||||||
|
if (strcasecmp(name, "override_key") == 0) {
|
||||||
|
g_default_override_key = ParseOverrideKey(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcasecmp(value, "A") == 0) {
|
static int LoaderTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||||
g_override_key_combination = KEY_A;
|
/* We'll output an override key when relevant. */
|
||||||
} else if (strcasecmp(value, "B") == 0) {
|
OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
|
||||||
g_override_key_combination = KEY_B;
|
|
||||||
} else if (strcasecmp(value, "X") == 0) {
|
if (strcasecmp(section, "override_config") == 0) {
|
||||||
g_override_key_combination = KEY_X;
|
if (strcasecmp(name, "override_key") == 0) {
|
||||||
} else if (strcasecmp(value, "Y") == 0) {
|
*user_cfg = ParseOverrideKey(value);
|
||||||
g_override_key_combination = KEY_Y;
|
|
||||||
} else if (strcasecmp(value, "LS") == 0) {
|
|
||||||
g_override_key_combination = KEY_LSTICK;
|
|
||||||
} else if (strcasecmp(value, "RS") == 0) {
|
|
||||||
g_override_key_combination = KEY_RSTICK;
|
|
||||||
} else if (strcasecmp(value, "L") == 0) {
|
|
||||||
g_override_key_combination = KEY_L;
|
|
||||||
} else if (strcasecmp(value, "R") == 0) {
|
|
||||||
g_override_key_combination = KEY_R;
|
|
||||||
} else if (strcasecmp(value, "ZL") == 0) {
|
|
||||||
g_override_key_combination = KEY_ZL;
|
|
||||||
} else if (strcasecmp(value, "ZR") == 0) {
|
|
||||||
g_override_key_combination = KEY_ZR;
|
|
||||||
} else if (strcasecmp(value, "PLUS") == 0) {
|
|
||||||
g_override_key_combination = KEY_PLUS;
|
|
||||||
} else if (strcasecmp(value, "MINUS") == 0) {
|
|
||||||
g_override_key_combination = KEY_MINUS;
|
|
||||||
} else if (strcasecmp(value, "DLEFT") == 0) {
|
|
||||||
g_override_key_combination = KEY_DLEFT;
|
|
||||||
} else if (strcasecmp(value, "DUP") == 0) {
|
|
||||||
g_override_key_combination = KEY_DUP;
|
|
||||||
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
|
||||||
g_override_key_combination = KEY_DRIGHT;
|
|
||||||
} else if (strcasecmp(value, "DDOWN") == 0) {
|
|
||||||
g_override_key_combination = KEY_DDOWN;
|
|
||||||
} else if (strcasecmp(value, "SL") == 0) {
|
|
||||||
g_override_key_combination = KEY_SL;
|
|
||||||
} else if (strcasecmp(value, "SR") == 0) {
|
|
||||||
g_override_key_combination = KEY_SR;
|
|
||||||
} else {
|
|
||||||
g_override_key_combination = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -309,18 +353,56 @@ void ContentManagement::TryMountSdCard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContentManagement::ShouldReplaceWithHBL(u64 tid) {
|
static bool IsHBLTitleId(u64 tid) {
|
||||||
return g_mounted_hbl_nsp && ((g_override_any_app && IsApplicationTid(tid)) || (!g_override_any_app && tid == g_override_hbl_tid));
|
return ((g_hbl_override_config.override_any_app && IsApplicationTid(tid)) || (!g_hbl_override_config.override_any_app && tid == g_hbl_override_config.title_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContentManagement::ShouldOverrideContents(u64 tid) {
|
OverrideKey ContentManagement::GetTitleOverrideKey(u64 tid) {
|
||||||
if (tid >= 0x0100000000001000 && HasCreatedTitle(0x0100000000001000)) {
|
OverrideKey cfg = g_default_override_key;
|
||||||
|
char path[FS_MAX_PATH+1] = {0};
|
||||||
|
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/config.ini", tid);
|
||||||
|
|
||||||
|
|
||||||
|
FILE *config = fopen(path, "r");
|
||||||
|
if (config != NULL) {
|
||||||
|
ON_SCOPE_EXIT { fclose(config); };
|
||||||
|
|
||||||
|
/* Parse current title ini. */
|
||||||
|
ini_parse_file(config, LoaderTitleSpecificIniHandler, &cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ShouldOverrideContents(OverrideKey *cfg) {
|
||||||
u64 kDown = 0;
|
u64 kDown = 0;
|
||||||
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0));
|
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
||||||
return g_has_initialized_fs_dev && (g_override_by_default ^ keys_triggered);
|
return g_has_initialized_fs_dev && (cfg->override_by_default ^ keys_triggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContentManagement::ShouldOverrideContentsWithHBL(u64 tid) {
|
||||||
|
if (g_mounted_hbl_nsp && tid >= 0x0100000000001000 && HasCreatedTitle(0x0100000000001000)) {
|
||||||
|
/* Return whether we should override contents with HBL. */
|
||||||
|
return IsHBLTitleId(tid) && ShouldOverrideContents(&g_hbl_override_config.override_key);
|
||||||
|
} else {
|
||||||
|
/* Don't override if we failed to mount HBL or haven't launched qlaunch. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContentManagement::ShouldOverrideContentsWithSD(u64 tid) {
|
||||||
|
if (g_has_initialized_fs_dev) {
|
||||||
|
if (tid >= 0x0100000000001000 && HasCreatedTitle(0x0100000000001000)) {
|
||||||
|
/* Check whether we should override with non-HBL. */
|
||||||
|
OverrideKey title_cfg = GetTitleOverrideKey(tid);
|
||||||
|
return ShouldOverrideContents(&title_cfg);
|
||||||
} else {
|
} else {
|
||||||
/* Always redirect before qlaunch. */
|
/* Always redirect before qlaunch. */
|
||||||
return g_has_initialized_fs_dev;
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Never redirect before we can do so. */
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
#include "ldr_registration.hpp"
|
#include "ldr_registration.hpp"
|
||||||
|
|
||||||
|
struct OverrideKey {
|
||||||
|
u64 key_combination;
|
||||||
|
bool override_by_default;
|
||||||
|
};
|
||||||
|
|
||||||
class ContentManagement {
|
class ContentManagement {
|
||||||
public:
|
public:
|
||||||
static Result MountCode(u64 tid, FsStorageId sid);
|
static Result MountCode(u64 tid, FsStorageId sid);
|
||||||
@@ -37,8 +42,9 @@ class ContentManagement {
|
|||||||
static void RefreshConfigurationData();
|
static void RefreshConfigurationData();
|
||||||
static void TryMountSdCard();
|
static void TryMountSdCard();
|
||||||
|
|
||||||
static bool ShouldReplaceWithHBL(u64 tid);
|
static OverrideKey GetTitleOverrideKey(u64 tid);
|
||||||
static bool ShouldOverrideContents(u64 tid);
|
static bool ShouldOverrideContentsWithSD(u64 tid);
|
||||||
|
static bool ShouldOverrideContentsWithHBL(u64 tid);
|
||||||
|
|
||||||
/* SetExternalContentSource extension */
|
/* SetExternalContentSource extension */
|
||||||
class ExternalContentSource {
|
class ExternalContentSource {
|
||||||
|
|||||||
@@ -64,15 +64,20 @@ FILE *NpdmUtils::OpenNpdm(u64 title_id) {
|
|||||||
return OpenNpdmFromECS(ecs);
|
return OpenNpdmFromECS(ecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContentManagement::ShouldOverrideContents(title_id)) {
|
/* First, check HBL. */
|
||||||
if (ContentManagement::ShouldReplaceWithHBL(title_id)) {
|
if (ContentManagement::ShouldOverrideContentsWithHBL(title_id)) {
|
||||||
return OpenNpdmFromHBL();
|
return OpenNpdmFromHBL();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Next, check other override. */
|
||||||
|
if (ContentManagement::ShouldOverrideContentsWithSD(title_id)) {
|
||||||
FILE *f_out = OpenNpdmFromSdCard(title_id);
|
FILE *f_out = OpenNpdmFromSdCard(title_id);
|
||||||
if (f_out != NULL) {
|
if (f_out != NULL) {
|
||||||
return f_out;
|
return f_out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Last resort: real exefs. */
|
||||||
return OpenNpdmFromExeFS();
|
return OpenNpdmFromExeFS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +198,7 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) {
|
|||||||
info->acid->title_id_range_max = tid;
|
info->acid->title_id_range_max = tid;
|
||||||
info->aci0->title_id = tid;
|
info->aci0->title_id = tid;
|
||||||
|
|
||||||
if (ContentManagement::ShouldOverrideContents(tid) && ContentManagement::ShouldReplaceWithHBL(tid)
|
if (ContentManagement::ShouldOverrideContentsWithHBL(tid) && R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) {
|
||||||
&& R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) {
|
|
||||||
NpdmInfo *original_info = &g_original_npdm_cache.info;
|
NpdmInfo *original_info = &g_original_npdm_cache.info;
|
||||||
/* Fix pool partition. */
|
/* Fix pool partition. */
|
||||||
if (kernelAbove500()) {
|
if (kernelAbove500()) {
|
||||||
|
|||||||
@@ -72,10 +72,13 @@ FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) {
|
|||||||
return OpenNsoFromECS(index, ecs);
|
return OpenNsoFromECS(index, ecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContentManagement::ShouldOverrideContents(title_id)) {
|
/* First, check HBL. */
|
||||||
if (ContentManagement::ShouldReplaceWithHBL(title_id)) {
|
if (ContentManagement::ShouldOverrideContentsWithHBL(title_id)) {
|
||||||
return OpenNsoFromHBL(index);
|
return OpenNsoFromHBL(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Next, check secondary override. */
|
||||||
|
if (ContentManagement::ShouldOverrideContentsWithSD(title_id)) {
|
||||||
FILE *f_out = OpenNsoFromSdCard(index, title_id);
|
FILE *f_out = OpenNsoFromSdCard(index, title_id);
|
||||||
if (f_out != NULL) {
|
if (f_out != NULL) {
|
||||||
return f_out;
|
return f_out;
|
||||||
@@ -83,6 +86,8 @@ FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Finally, default to exefs. */
|
||||||
return OpenNsoFromExeFS(index);
|
return OpenNsoFromExeFS(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,19 +27,7 @@
|
|||||||
#include "pm_registration.hpp"
|
#include "pm_registration.hpp"
|
||||||
#include "pm_boot_mode.hpp"
|
#include "pm_boot_mode.hpp"
|
||||||
|
|
||||||
static std::vector<Boot2KnownTitleId> g_boot2_titles;
|
static std::vector<Boot2KnownTitleId> g_launched_titles;
|
||||||
|
|
||||||
static void ClearLaunchedTitles() {
|
|
||||||
g_boot2_titles.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SetLaunchedTitle(Boot2KnownTitleId title_id) {
|
|
||||||
g_boot2_titles.push_back(title_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool HasLaunchedTitle(Boot2KnownTitleId title_id) {
|
|
||||||
return std::find(g_boot2_titles.begin(), g_boot2_titles.end(), title_id) != g_boot2_titles.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsHexadecimal(const char *str) {
|
static bool IsHexadecimal(const char *str) {
|
||||||
while (*str) {
|
while (*str) {
|
||||||
@@ -52,8 +40,20 @@ static bool IsHexadecimal(const char *str) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool HasLaunchedTitle(Boot2KnownTitleId title_id) {
|
||||||
|
return std::find(g_launched_titles.begin(), g_launched_titles.end(), title_id) != g_launched_titles.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetLaunchedTitle(Boot2KnownTitleId title_id) {
|
||||||
|
g_launched_titles.push_back(title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ClearLaunchedTitles() {
|
||||||
|
g_launched_titles.clear();
|
||||||
|
}
|
||||||
|
|
||||||
static void LaunchTitle(Boot2KnownTitleId title_id, FsStorageId storage_id, u32 launch_flags, u64 *pid) {
|
static void LaunchTitle(Boot2KnownTitleId title_id, FsStorageId storage_id, u32 launch_flags, u64 *pid) {
|
||||||
u64 local_pid;
|
u64 local_pid = 0;
|
||||||
|
|
||||||
/* Don't launch a title twice during boot2. */
|
/* Don't launch a title twice during boot2. */
|
||||||
if (HasLaunchedTitle(title_id)) {
|
if (HasLaunchedTitle(title_id)) {
|
||||||
@@ -64,15 +64,15 @@ static void LaunchTitle(Boot2KnownTitleId title_id, FsStorageId storage_id, u32
|
|||||||
switch (rc) {
|
switch (rc) {
|
||||||
case 0xCE01:
|
case 0xCE01:
|
||||||
/* Out of resource! */
|
/* Out of resource! */
|
||||||
/* TODO: Panic(). */
|
std::abort();
|
||||||
break;
|
break;
|
||||||
case 0xDE01:
|
case 0xDE01:
|
||||||
/* Out of memory! */
|
/* Out of memory! */
|
||||||
/* TODO: Panic(). */
|
std::abort();
|
||||||
break;
|
break;
|
||||||
case 0xD001:
|
case 0xD001:
|
||||||
/* Limit Reached! */
|
/* Limit Reached! */
|
||||||
/* TODO: Panic(). */
|
std::abort();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* We don't care about other issues. */
|
/* We don't care about other issues. */
|
||||||
@@ -235,9 +235,13 @@ void EmbeddedBoot2::Main() {
|
|||||||
|
|
||||||
/* Launch usb. */
|
/* Launch usb. */
|
||||||
LaunchTitle(Boot2KnownTitleId::usb, FsStorageId_NandSystem, 0, NULL);
|
LaunchTitle(Boot2KnownTitleId::usb, FsStorageId_NandSystem, 0, NULL);
|
||||||
|
|
||||||
/* Launch tma. */
|
/* Launch tma. */
|
||||||
LaunchTitle(Boot2KnownTitleId::tma, FsStorageId_NandSystem, 0, NULL);
|
LaunchTitle(Boot2KnownTitleId::tma, FsStorageId_NandSystem, 0, NULL);
|
||||||
|
|
||||||
|
/* Launch Atmosphere dmnt, using FsStorageId_None to force SD card boot. */
|
||||||
|
LaunchTitle(Boot2KnownTitleId::dmnt, FsStorageId_None, 0, NULL);
|
||||||
|
|
||||||
/* Launch default programs. */
|
/* Launch default programs. */
|
||||||
for (auto &launch_program : g_additional_launch_programs) {
|
for (auto &launch_program : g_additional_launch_programs) {
|
||||||
if (!maintenance || std::get<bool>(launch_program)) {
|
if (!maintenance || std::get<bool>(launch_program)) {
|
||||||
@@ -251,7 +255,10 @@ void EmbeddedBoot2::Main() {
|
|||||||
if (titles_dir != NULL) {
|
if (titles_dir != NULL) {
|
||||||
while ((ent = readdir(titles_dir)) != NULL) {
|
while ((ent = readdir(titles_dir)) != NULL) {
|
||||||
if (strlen(ent->d_name) == 0x10 && IsHexadecimal(ent->d_name)) {
|
if (strlen(ent->d_name) == 0x10 && IsHexadecimal(ent->d_name)) {
|
||||||
u64 title_id = strtoul(ent->d_name, NULL, 16);
|
Boot2KnownTitleId title_id = (Boot2KnownTitleId)strtoul(ent->d_name, NULL, 16);
|
||||||
|
if (HasLaunchedTitle(title_id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
char title_path[FS_MAX_PATH] = {0};
|
char title_path[FS_MAX_PATH] = {0};
|
||||||
strcpy(title_path, "sdmc:/atmosphere/titles/");
|
strcpy(title_path, "sdmc:/atmosphere/titles/");
|
||||||
strcat(title_path, ent->d_name);
|
strcat(title_path, ent->d_name);
|
||||||
@@ -259,7 +266,7 @@ void EmbeddedBoot2::Main() {
|
|||||||
FILE *f_flag = fopen(title_path, "rb");
|
FILE *f_flag = fopen(title_path, "rb");
|
||||||
if (f_flag != NULL) {
|
if (f_flag != NULL) {
|
||||||
fclose(f_flag);
|
fclose(f_flag);
|
||||||
LaunchTitle((Boot2KnownTitleId)title_id, FsStorageId_None, 0, NULL);
|
LaunchTitle(title_id, FsStorageId_None, 0, NULL);
|
||||||
} else {
|
} else {
|
||||||
/* TODO: Deprecate this in the future. */
|
/* TODO: Deprecate this in the future. */
|
||||||
memset(title_path, 0, FS_MAX_PATH);
|
memset(title_path, 0, FS_MAX_PATH);
|
||||||
@@ -269,7 +276,7 @@ void EmbeddedBoot2::Main() {
|
|||||||
f_flag = fopen(title_path, "rb");
|
f_flag = fopen(title_path, "rb");
|
||||||
if (f_flag != NULL) {
|
if (f_flag != NULL) {
|
||||||
fclose(f_flag);
|
fclose(f_flag);
|
||||||
LaunchTitle((Boot2KnownTitleId)title_id, FsStorageId_None, 0, NULL);
|
LaunchTitle(title_id, FsStorageId_None, 0, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +284,9 @@ void EmbeddedBoot2::Main() {
|
|||||||
closedir(titles_dir);
|
closedir(titles_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Free the memory used to track what boot2 launches. */
|
||||||
|
ClearLaunchedTitles();
|
||||||
|
|
||||||
/* We no longer need the SD card. */
|
/* We no longer need the SD card. */
|
||||||
fsdevUnmountAll();
|
fsdevUnmountAll();
|
||||||
|
|
||||||
|
|||||||
@@ -76,10 +76,11 @@ Result DebugMonitorService::DisableDebug(u32 which) {
|
|||||||
return Registration::DisableDebug(which);
|
return Registration::DisableDebug(which);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result DebugMonitorService::AtmosphereGetProcessHandle(Out<CopiedHandle> proc_hand, u64 pid) {
|
Result DebugMonitorService::AtmosphereGetProcessInfo(Out<CopiedHandle> proc_hand, Out<Registration::TidSid> tid_sid, u64 pid) {
|
||||||
auto proc = Registration::GetProcess(pid);
|
auto proc = Registration::GetProcess(pid);
|
||||||
if(proc != nullptr) {
|
if (proc != nullptr) {
|
||||||
proc_hand.SetValue(proc->handle);
|
proc_hand.SetValue(proc->handle);
|
||||||
|
tid_sid.SetValue(proc->tid_sid);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0x20F;
|
return 0x20F;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ enum DmntCmd {
|
|||||||
|
|
||||||
Dmnt_Cmd_6X_DisableDebug = 6,
|
Dmnt_Cmd_6X_DisableDebug = 6,
|
||||||
|
|
||||||
Dmnt_Cmd_AtmosphereGetProcessHandle = 65000,
|
Dmnt_Cmd_AtmosphereGetProcessInfo = 65000,
|
||||||
Dmnt_Cmd_AtmosphereGetCurrentLimitInfo = 65001,
|
Dmnt_Cmd_AtmosphereGetCurrentLimitInfo = 65001,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class DebugMonitorService final : public IServiceObject {
|
|||||||
Result DisableDebug(u32 which);
|
Result DisableDebug(u32 which);
|
||||||
|
|
||||||
/* Atmosphere commands. */
|
/* Atmosphere commands. */
|
||||||
Result AtmosphereGetProcessHandle(Out<CopiedHandle> proc_hand, u64 pid);
|
Result AtmosphereGetProcessInfo(Out<CopiedHandle> proc_hand, Out<Registration::TidSid> tid_sid, u64 pid);
|
||||||
Result AtmosphereGetCurrentLimitInfo(Out<u64> cur_val, Out<u64> lim_val, u32 category, u32 resource);
|
Result AtmosphereGetCurrentLimitInfo(Out<u64> cur_val, Out<u64> lim_val, u32 category, u32 resource);
|
||||||
public:
|
public:
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
@@ -80,7 +80,7 @@ class DebugMonitorService final : public IServiceObject {
|
|||||||
MakeServiceCommandMeta<Dmnt_Cmd_6X_DisableDebug, &DebugMonitorService::DisableDebug, FirmwareVersion_600>(),
|
MakeServiceCommandMeta<Dmnt_Cmd_6X_DisableDebug, &DebugMonitorService::DisableDebug, FirmwareVersion_600>(),
|
||||||
|
|
||||||
/* Atmosphere extensions. */
|
/* Atmosphere extensions. */
|
||||||
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetProcessHandle, &DebugMonitorService::AtmosphereGetProcessHandle>(),
|
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetProcessInfo, &DebugMonitorService::AtmosphereGetProcessInfo>(),
|
||||||
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetCurrentLimitInfo, &DebugMonitorService::AtmosphereGetCurrentLimitInfo>(),
|
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetCurrentLimitInfo, &DebugMonitorService::AtmosphereGetCurrentLimitInfo>(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,5 +28,4 @@ void ProcessTracking::MainLoop(void *arg) {
|
|||||||
process_waiter->Process();
|
process_waiter->Process();
|
||||||
|
|
||||||
delete process_waiter;
|
delete process_waiter;
|
||||||
svcExitThread();
|
|
||||||
}
|
}
|
||||||
159
stratosphere/tma/Makefile
Normal file
159
stratosphere/tma/Makefile
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
.SUFFIXES:
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ifeq ($(strip $(DEVKITPRO)),)
|
||||||
|
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||||
|
endif
|
||||||
|
|
||||||
|
TOPDIR ?= $(CURDIR)
|
||||||
|
include $(DEVKITPRO)/libnx/switch_rules
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# TARGET is the name of the output
|
||||||
|
# BUILD is the directory where object files & intermediate files will be placed
|
||||||
|
# SOURCES is a list of directories containing source code
|
||||||
|
# DATA is a list of directories containing data files
|
||||||
|
# INCLUDES is a list of directories containing header files
|
||||||
|
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
TARGET := $(notdir $(CURDIR))
|
||||||
|
BUILD := build
|
||||||
|
SOURCES := source source/test source/settings source/target_io
|
||||||
|
DATA := data
|
||||||
|
INCLUDES := include ../../common/include
|
||||||
|
EXEFS_SRC := exefs_src
|
||||||
|
|
||||||
|
DEFINES := -DDISABLE_IPC
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# options for code generation
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||||
|
|
||||||
|
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||||
|
$(ARCH) $(DEFINES)
|
||||||
|
|
||||||
|
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||||
|
|
||||||
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
||||||
|
|
||||||
|
ASFLAGS := -g $(ARCH)
|
||||||
|
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
|
LIBS := -lstratosphere -lnx
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# list of directories containing libraries, this must be the top level containing
|
||||||
|
# include and lib
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# no real need to edit anything past this point unless you need to add additional
|
||||||
|
# rules for different file extensions
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
|
export TOPDIR := $(CURDIR)
|
||||||
|
|
||||||
|
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||||
|
|
||||||
|
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||||
|
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||||
|
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||||
|
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# use CXX for linking C++ projects, CC for standard C
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifeq ($(strip $(CPPFILES)),)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CC)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CXX)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OFILES := $(addsuffix .o,$(BINFILES)) \
|
||||||
|
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||||
|
|
||||||
|
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||||
|
-I$(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||||
|
|
||||||
|
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
|
||||||
|
|
||||||
|
ifeq ($(strip $(CONFIG_JSON)),)
|
||||||
|
jsons := $(wildcard *.json)
|
||||||
|
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||||
|
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||||
|
else
|
||||||
|
ifneq (,$(findstring config.json,$(jsons)))
|
||||||
|
export APP_JSON := $(TOPDIR)/config.json
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: $(BUILD) clean all
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
all: $(BUILD)
|
||||||
|
|
||||||
|
$(BUILD):
|
||||||
|
@[ -d $@ ] || mkdir -p $@
|
||||||
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
clean:
|
||||||
|
@echo clean ...
|
||||||
|
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
.PHONY: all
|
||||||
|
|
||||||
|
DEPENDS := $(OFILES:.o=.d)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# main targets
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
all : $(OUTPUT).nsp
|
||||||
|
|
||||||
|
ifeq ($(strip $(APP_JSON)),)
|
||||||
|
$(OUTPUT).nsp : $(OUTPUT).nso
|
||||||
|
else
|
||||||
|
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(OUTPUT).nso : $(OUTPUT).elf
|
||||||
|
|
||||||
|
$(OUTPUT).elf : $(OFILES)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# you need a rule like this for each extension you use as binary data
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
%.bin.o : %.bin
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
@echo $(notdir $<)
|
||||||
|
@$(bin2o)
|
||||||
|
|
||||||
|
-include $(DEPENDS)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
35
stratosphere/tma/client/Main.py
Normal file
35
stratosphere/tma/client/Main.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Copyright (c) 2018 Atmosphere-NX
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms and conditions of the GNU General Public License,
|
||||||
|
# version 2, as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
from UsbConnection import UsbConnection
|
||||||
|
import sys, time
|
||||||
|
from Packet import Packet
|
||||||
|
import ServiceId
|
||||||
|
|
||||||
|
def main(argc, argv):
|
||||||
|
with UsbConnection(None) as c:
|
||||||
|
print 'Waiting for connection...'
|
||||||
|
c.wait_connected()
|
||||||
|
print 'Connected!'
|
||||||
|
print 'Reading atmosphere/BCT.ini...'
|
||||||
|
c.intf.send_packet(Packet().set_service(ServiceId.TARGETIO_SERVICE).set_task(0x01000000).set_cmd(2).write_str('atmosphere/BCT.ini').write_u64(0x109).write_u64(0))
|
||||||
|
resp = c.intf.read_packet()
|
||||||
|
res_packet = c.intf.read_packet()
|
||||||
|
read_res, size_read = resp.read_u32(), resp.read_u32()
|
||||||
|
print 'Final Result: 0x%x' % res_packet.read_u32()
|
||||||
|
print 'Size Read: 0x%x' % size_read
|
||||||
|
print 'Data:\n%s' % resp.body[resp.offset:]
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(len(sys.argv), sys.argv))
|
||||||
120
stratosphere/tma/client/Packet.py
Normal file
120
stratosphere/tma/client/Packet.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Copyright (c) 2018 Atmosphere-NX
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms and conditions of the GNU General Public License,
|
||||||
|
# version 2, as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
import zlib
|
||||||
|
import ServiceId
|
||||||
|
from struct import unpack as up, pack as pk
|
||||||
|
|
||||||
|
HEADER_SIZE = 0x28
|
||||||
|
|
||||||
|
def crc32(s):
|
||||||
|
return zlib.crc32(s) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
class Packet():
|
||||||
|
def __init__(self):
|
||||||
|
self.service = 0
|
||||||
|
self.task = 0
|
||||||
|
self.cmd = 0
|
||||||
|
self.continuation = 0
|
||||||
|
self.version = 0
|
||||||
|
self.body_len = 0
|
||||||
|
self.body = ''
|
||||||
|
self.offset = 0
|
||||||
|
def load_header(self, header):
|
||||||
|
assert len(header) == HEADER_SIZE
|
||||||
|
self.service, self.task, self.cmd, self.continuation, self.version, self.body_len, \
|
||||||
|
_, self.body_chk, self.hdr_chk = up('<IIHBBI16sII', header)
|
||||||
|
if crc32(header[:-4]) != self.hdr_chk:
|
||||||
|
raise ValueError('Invalid header checksum in received packet!')
|
||||||
|
def load_body(self, body):
|
||||||
|
assert len(body) == self.body_len
|
||||||
|
if crc32(body) != self.body_chk:
|
||||||
|
raise ValueError('Invalid body checksum in received packet!')
|
||||||
|
self.body = body
|
||||||
|
def get_data(self):
|
||||||
|
assert len(self.body) == self.body_len and self.body_len <= 0xE000
|
||||||
|
self.body_chk = crc32(self.body)
|
||||||
|
hdr = pk('<IIHBBIIIIII', self.service, self.task, self.cmd, self.continuation, self.version, self.body_len, 0, 0, 0, 0, self.body_chk)
|
||||||
|
self.hdr_chk = crc32(hdr)
|
||||||
|
hdr += pk('<I', self.hdr_chk)
|
||||||
|
return hdr + self.body
|
||||||
|
def set_service(self, srv):
|
||||||
|
if type(srv) is str:
|
||||||
|
self.service = ServiceId.hash(srv)
|
||||||
|
else:
|
||||||
|
self.service = srv
|
||||||
|
return self
|
||||||
|
def set_task(self, t):
|
||||||
|
self.task = t
|
||||||
|
return self
|
||||||
|
def set_cmd(self, x):
|
||||||
|
self.cmd = x
|
||||||
|
return self
|
||||||
|
def set_continuation(self, c):
|
||||||
|
self.continuation = c
|
||||||
|
return self
|
||||||
|
def set_version(self, v):
|
||||||
|
self.version = v
|
||||||
|
return self
|
||||||
|
def reset_offset(self):
|
||||||
|
self.offset = 0
|
||||||
|
return self
|
||||||
|
def write_str(self, s):
|
||||||
|
if s[-1] != '\x00':
|
||||||
|
s += '\x00'
|
||||||
|
self.body += s
|
||||||
|
self.body_len += len(s)
|
||||||
|
return self
|
||||||
|
def write_u8(self, x):
|
||||||
|
self.body += pk('<B', x & 0xFF)
|
||||||
|
self.body_len += 1
|
||||||
|
return self
|
||||||
|
def write_u16(self, x):
|
||||||
|
self.body += pk('<H', x & 0xFFFF)
|
||||||
|
self.body_len += 2
|
||||||
|
return self
|
||||||
|
def write_u32(self, x):
|
||||||
|
self.body += pk('<I', x & 0xFFFFFFFF)
|
||||||
|
self.body_len += 4
|
||||||
|
return self
|
||||||
|
def write_u64(self, x):
|
||||||
|
self.body += pk('<Q', x & 0xFFFFFFFFFFFFFFFF)
|
||||||
|
self.body_len += 8
|
||||||
|
return self
|
||||||
|
def read_str(self):
|
||||||
|
s = ''
|
||||||
|
while self.body[self.offset] != '\x00' and self.offset < self.body_len:
|
||||||
|
s += self.body[self.offset]
|
||||||
|
self.offset += 1
|
||||||
|
if self.offset < self.body_len and self.body[self.offset] == '\x00':
|
||||||
|
self.offset += 1
|
||||||
|
def read_u8(self):
|
||||||
|
x, = up('<B', self.body[self.offset:self.offset+1])
|
||||||
|
self.offset += 1
|
||||||
|
return x
|
||||||
|
def read_u16(self):
|
||||||
|
x, = up('<H', self.body[self.offset:self.offset+2])
|
||||||
|
self.offset += 2
|
||||||
|
return x
|
||||||
|
def read_u32(self):
|
||||||
|
x, = up('<I', self.body[self.offset:self.offset+4])
|
||||||
|
self.offset += 4
|
||||||
|
return x
|
||||||
|
def read_u64(self):
|
||||||
|
x, = up('<Q', self.body[self.offset:self.offset+8])
|
||||||
|
self.offset += 8
|
||||||
|
return x
|
||||||
|
def read_struct(self, format, sz):
|
||||||
|
x = up(format, self.body[self.offset:self.offset+sz])
|
||||||
|
self.offset += sz
|
||||||
|
return x
|
||||||
30
stratosphere/tma/client/ServiceId.py
Normal file
30
stratosphere/tma/client/ServiceId.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2018 Atmosphere-NX
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms and conditions of the GNU General Public License,
|
||||||
|
# version 2, as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
def hash(s):
|
||||||
|
h = ord(s[0]) & 0xFFFFFFFF
|
||||||
|
for c in s:
|
||||||
|
h = ((1000003 * h) ^ ord(c)) & 0xFFFFFFFF
|
||||||
|
h ^= len(s)
|
||||||
|
return h
|
||||||
|
|
||||||
|
USB_QUERY_TARGET = hash("USBQueryTarget")
|
||||||
|
USB_SEND_HOST_INFO = hash("USBSendHostInfo")
|
||||||
|
USB_CONNECT = hash("USBConnect")
|
||||||
|
USB_DISCONNECT = hash("USBDisconnect")
|
||||||
|
|
||||||
|
ATMOSPHERE_TEST_SERVICE = hash("AtmosphereTestService")
|
||||||
|
SETTINGS_SERVICE = hash("SettingsService")
|
||||||
|
TARGETIO_SERVICE = hash("TIOService")
|
||||||
|
|
||||||
134
stratosphere/tma/client/UsbConnection.py
Normal file
134
stratosphere/tma/client/UsbConnection.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Copyright (c) 2018 Atmosphere-NX
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms and conditions of the GNU General Public License,
|
||||||
|
# version 2, as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from UsbInterface import UsbInterface
|
||||||
|
from threading import Thread, Condition
|
||||||
|
from collections import deque
|
||||||
|
import time
|
||||||
|
import ServiceId
|
||||||
|
from Packet import Packet
|
||||||
|
|
||||||
|
class UsbConnection(UsbInterface):
|
||||||
|
# Auto connect thread func.
|
||||||
|
def auto_connect(connection):
|
||||||
|
while not connection.is_connected():
|
||||||
|
try:
|
||||||
|
connection.connect(UsbInterface())
|
||||||
|
except ValueError as e:
|
||||||
|
continue
|
||||||
|
def recv_thread(connection):
|
||||||
|
while connection.is_connected():
|
||||||
|
try:
|
||||||
|
connection.recv_packet()
|
||||||
|
except Exception as e:
|
||||||
|
print 'An exception occurred:'
|
||||||
|
print 'Type: '+e.__class__.__name__
|
||||||
|
print 'Msg: '+str(e)
|
||||||
|
connection.disconnect()
|
||||||
|
connection.send_packet(None)
|
||||||
|
def send_thread(connection):
|
||||||
|
while connection.is_connected():
|
||||||
|
try:
|
||||||
|
next_packet = connection.get_next_send_packet()
|
||||||
|
if next_packet is not None:
|
||||||
|
connection.intf.send_packet(next_packet)
|
||||||
|
else:
|
||||||
|
connection.disconnect()
|
||||||
|
except Exception as e:
|
||||||
|
print 'An exception occurred:'
|
||||||
|
print 'Type: '+e.__class__.__name__
|
||||||
|
print 'Msg: '+str(e)
|
||||||
|
connection.disconnect()
|
||||||
|
def __init__(self, manager):
|
||||||
|
self.manager = manager
|
||||||
|
self.connected = False
|
||||||
|
self.intf = None
|
||||||
|
self.conn_lock, self.send_lock = Condition(), Condition()
|
||||||
|
self.send_queue = deque()
|
||||||
|
def __enter__(self):
|
||||||
|
self.conn_thrd = Thread(target=UsbConnection.auto_connect, args=(self,))
|
||||||
|
self.conn_thrd.daemon = True
|
||||||
|
self.conn_thrd.start()
|
||||||
|
return self
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.disconnect()
|
||||||
|
time.sleep(1)
|
||||||
|
print 'Closing!'
|
||||||
|
time.sleep(1)
|
||||||
|
def wait_connected(self):
|
||||||
|
self.conn_lock.acquire()
|
||||||
|
if not self.is_connected():
|
||||||
|
self.conn_lock.wait()
|
||||||
|
self.conn_lock.release()
|
||||||
|
def is_connected(self):
|
||||||
|
return self.connected
|
||||||
|
def connect(self, intf):
|
||||||
|
# This indicates we have a connection.
|
||||||
|
self.conn_lock.acquire()
|
||||||
|
assert not self.connected
|
||||||
|
self.intf = intf
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Perform Query + Connection handshake
|
||||||
|
print 'Performing handshake...'
|
||||||
|
self.intf.send_packet(Packet().set_service(ServiceId.USB_QUERY_TARGET))
|
||||||
|
query_resp = self.intf.read_packet()
|
||||||
|
print 'Found Switch, Protocol version 0x%x' % query_resp.read_u32()
|
||||||
|
|
||||||
|
self.intf.send_packet(Packet().set_service(ServiceId.USB_SEND_HOST_INFO).write_u32(0).write_u32(0))
|
||||||
|
|
||||||
|
self.intf.send_packet(Packet().set_service(ServiceId.USB_CONNECT))
|
||||||
|
resp = self.intf.read_packet()
|
||||||
|
|
||||||
|
# Spawn threads
|
||||||
|
self.recv_thrd = Thread(target=UsbConnection.recv_thread, args=(self,))
|
||||||
|
self.send_thrd = Thread(target=UsbConnection.send_thread, args=(self,))
|
||||||
|
self.recv_thrd.daemon = True
|
||||||
|
self.send_thrd.daemon = True
|
||||||
|
self.recv_thrd.start()
|
||||||
|
self.send_thrd.start()
|
||||||
|
self.connected = True
|
||||||
|
finally:
|
||||||
|
# Finish connection.
|
||||||
|
self.conn_lock.notify()
|
||||||
|
self.conn_lock.release()
|
||||||
|
def disconnect(self):
|
||||||
|
self.conn_lock.acquire()
|
||||||
|
if self.connected:
|
||||||
|
self.connected = False
|
||||||
|
self.intf.send_packet(Packet().set_service(ServiceId.USB_DISCONNECT))
|
||||||
|
self.conn_lock.release()
|
||||||
|
def recv_packet(self):
|
||||||
|
packet = self.intf.read_packet()
|
||||||
|
assert type(packet) is Packet
|
||||||
|
dat = packet.read_u64()
|
||||||
|
print('Got Packet: %08x' % dat)
|
||||||
|
def send_packet(self, packet):
|
||||||
|
assert type(packet) is Packet
|
||||||
|
self.send_lock.acquire()
|
||||||
|
if len(self.send_queue) == 0x40:
|
||||||
|
self.send_lock.wait()
|
||||||
|
self.send_queue.append(packet)
|
||||||
|
if len(self.send_queue) == 1:
|
||||||
|
self.send_lock.notify()
|
||||||
|
self.send_lock.release()
|
||||||
|
def get_next_send_packet(self):
|
||||||
|
self.send_lock.acquire()
|
||||||
|
if len(self.send_queue) == 0:
|
||||||
|
self.send_lock.wait()
|
||||||
|
packet = self.send_queue.popleft()
|
||||||
|
if len(self.send_queue) == 0x3F:
|
||||||
|
self.send_lock.notify()
|
||||||
|
self.send_lock.release()
|
||||||
|
return packet
|
||||||
|
|
||||||
62
stratosphere/tma/client/UsbInterface.py
Normal file
62
stratosphere/tma/client/UsbInterface.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Copyright (c) 2018 Atmosphere-NX
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms and conditions of the GNU General Public License,
|
||||||
|
# version 2, as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
import usb
|
||||||
|
import Packet
|
||||||
|
|
||||||
|
class UsbInterface():
|
||||||
|
def __init__(self):
|
||||||
|
self.dev = usb.core.find(idVendor=0x057e, idProduct=0x3000)
|
||||||
|
if self.dev is None:
|
||||||
|
raise ValueError('Device not found')
|
||||||
|
|
||||||
|
self.dev.set_configuration()
|
||||||
|
self.cfg = self.dev.get_active_configuration()
|
||||||
|
self.intf = usb.util.find_descriptor(self.cfg, bInterfaceClass=0xff, bInterfaceSubClass=0xff, bInterfaceProtocol=0xfc)
|
||||||
|
assert self.intf is not None
|
||||||
|
|
||||||
|
self.ep_in = usb.util.find_descriptor(
|
||||||
|
self.intf,
|
||||||
|
custom_match = \
|
||||||
|
lambda e: \
|
||||||
|
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
||||||
|
usb.util.ENDPOINT_IN)
|
||||||
|
assert self.ep_in is not None
|
||||||
|
|
||||||
|
self.ep_out = usb.util.find_descriptor(
|
||||||
|
self.intf,
|
||||||
|
custom_match = \
|
||||||
|
lambda e: \
|
||||||
|
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
||||||
|
usb.util.ENDPOINT_OUT)
|
||||||
|
assert self.ep_out is not None
|
||||||
|
def close(self):
|
||||||
|
usb.util.dispose_resources(self.dev)
|
||||||
|
def blocking_read(self, size):
|
||||||
|
return ''.join(chr(x) for x in self.ep_in.read(size, 0xFFFFFFFFFFFFFFFF))
|
||||||
|
def blocking_write(self, data):
|
||||||
|
self.ep_out.write(data, 0xFFFFFFFFFFFFFFFF)
|
||||||
|
def read_packet(self):
|
||||||
|
packet = Packet.Packet()
|
||||||
|
hdr = self.blocking_read(Packet.HEADER_SIZE)
|
||||||
|
packet.load_header(hdr)
|
||||||
|
if packet.body_len:
|
||||||
|
packet.load_body(self.blocking_read(packet.body_len))
|
||||||
|
return packet
|
||||||
|
def send_packet(self, packet):
|
||||||
|
data = packet.get_data()
|
||||||
|
self.blocking_write(data[:Packet.HEADER_SIZE])
|
||||||
|
if (len(data) > Packet.HEADER_SIZE):
|
||||||
|
self.blocking_write(data[Packet.HEADER_SIZE:])
|
||||||
|
|
||||||
|
|
||||||
93
stratosphere/tma/source/crc.h
Normal file
93
stratosphere/tma/source/crc.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Code taken from Yazen Ghannam <yazen.ghannam@linaro.org>, licensed GPLv2. */
|
||||||
|
|
||||||
|
#define CRC32X(crc, value) __asm__("crc32x %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32W(crc, value) __asm__("crc32w %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32H(crc, value) __asm__("crc32h %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32B(crc, value) __asm__("crc32b %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||||
|
|
||||||
|
static inline uint16_t __get_unaligned_le16(const uint8_t *p)
|
||||||
|
{
|
||||||
|
return p[0] | p[1] << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t __get_unaligned_le32(const uint8_t *p)
|
||||||
|
{
|
||||||
|
return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t __get_unaligned_le64(const uint8_t *p)
|
||||||
|
{
|
||||||
|
return (uint64_t)__get_unaligned_le32(p + 4) << 32 |
|
||||||
|
__get_unaligned_le32(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t get_unaligned_le16(const void *p)
|
||||||
|
{
|
||||||
|
return __get_unaligned_le16((const uint8_t *)p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t get_unaligned_le32(const void *p)
|
||||||
|
{
|
||||||
|
return __get_unaligned_le32((const uint8_t *)p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t get_unaligned_le64(const void *p)
|
||||||
|
{
|
||||||
|
return __get_unaligned_le64((const uint8_t *)p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static u32 crc32_arm64_le_hw(const u8 *p, unsigned int len) {
|
||||||
|
u32 crc = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
s64 length = len;
|
||||||
|
|
||||||
|
while ((length -= sizeof(u64)) >= 0) {
|
||||||
|
CRC32X(crc, get_unaligned_le64(p));
|
||||||
|
p += sizeof(u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following is more efficient than the straight loop */
|
||||||
|
if (length & sizeof(u32)) {
|
||||||
|
CRC32W(crc, get_unaligned_le32(p));
|
||||||
|
p += sizeof(u32);
|
||||||
|
}
|
||||||
|
if (length & sizeof(u16)) {
|
||||||
|
CRC32H(crc, get_unaligned_le16(p));
|
||||||
|
p += sizeof(u16);
|
||||||
|
}
|
||||||
|
if (length & sizeof(u8))
|
||||||
|
CRC32B(crc, *p);
|
||||||
|
|
||||||
|
return crc ^ 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
286
stratosphere/tma/source/dmnt.c
Normal file
286
stratosphere/tma/source/dmnt.c
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "dmnt.h"
|
||||||
|
|
||||||
|
static Service g_dmntSrv;
|
||||||
|
static u64 g_refCnt;
|
||||||
|
|
||||||
|
Result dmntInitialize(void) {
|
||||||
|
atomicIncrement64(&g_refCnt);
|
||||||
|
|
||||||
|
if (serviceIsActive(&g_dmntSrv))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return smGetService(&g_dmntSrv, "dmnt:-");
|
||||||
|
}
|
||||||
|
|
||||||
|
void dmntExit(void) {
|
||||||
|
if (atomicDecrement64(&g_refCnt) == 0)
|
||||||
|
serviceClose(&g_dmntSrv);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileOpen(DmntFile *out, const char *path, int flags, DmntTIOCreateOption create_option) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
ipcAddSendBuffer(&c, path, FS_MAX_PATH, BufferType_Normal);
|
||||||
|
ipcAddRecvBuffer(&c, out, sizeof(*out), BufferType_Normal);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
int flags;
|
||||||
|
u32 create_option;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 29;
|
||||||
|
raw->flags = flags;
|
||||||
|
raw->create_option = create_option;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileClose(DmntFile *f) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 30;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileRead(DmntFile *f, u64 off, void* buf, size_t len, size_t *out_read) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
|
||||||
|
ipcAddRecvBuffer(&c, buf, len, BufferType_Type1);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
u64 offset;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 31;
|
||||||
|
raw->offset = off;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
u32 out_read;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc) && out_read) {
|
||||||
|
*out_read = resp->out_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileWrite(DmntFile *f, u64 off, const void* buf, size_t len, size_t *out_written) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
|
||||||
|
ipcAddSendBuffer(&c, buf, len, BufferType_Type1);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
u64 offset;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 32;
|
||||||
|
raw->offset = off;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
u32 out_written;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc) && out_written) {
|
||||||
|
*out_written = resp->out_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileGetInformation(const char *path, bool *out_is_dir, DmntFileInformation *out_info) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
ipcAddSendBuffer(&c, path, FS_MAX_PATH, BufferType_Normal);
|
||||||
|
ipcAddRecvBuffer(&c, out_info, sizeof(*out_info), BufferType_Normal);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 34;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
int is_dir;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
*out_is_dir = resp->is_dir != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileGetSize(const char *path, u64 *out_size) {
|
||||||
|
DmntFileInformation info;
|
||||||
|
bool is_dir;
|
||||||
|
Result rc = dmntTargetIOFileGetInformation(path, &is_dir, &info);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (is_dir) {
|
||||||
|
/* TODO: error code? */
|
||||||
|
rc = 0x202;
|
||||||
|
} else {
|
||||||
|
*out_size = info.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result _dmntTargetIOFileSetSize(const void *arg, size_t arg_size, u64 size) {
|
||||||
|
IpcCommand c;
|
||||||
|
ipcInitialize(&c);
|
||||||
|
ipcAddSendBuffer(&c, arg, arg_size, BufferType_Normal);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 cmd_id;
|
||||||
|
u64 size;
|
||||||
|
} *raw;
|
||||||
|
|
||||||
|
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||||
|
|
||||||
|
raw->magic = SFCI_MAGIC;
|
||||||
|
raw->cmd_id = 36;
|
||||||
|
raw->size = size;
|
||||||
|
|
||||||
|
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
IpcParsedCommand r;
|
||||||
|
struct {
|
||||||
|
u64 magic;
|
||||||
|
u64 result;
|
||||||
|
} *resp;
|
||||||
|
|
||||||
|
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||||
|
resp = r.Raw;
|
||||||
|
|
||||||
|
rc = resp->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileSetSize(const char *path, u64 size) {
|
||||||
|
return _dmntTargetIOFileSetSize(path, FS_MAX_PATH, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result dmntTargetIOFileSetOpenFileSize(DmntFile *f, u64 size) {
|
||||||
|
/* Atmosphere extension */
|
||||||
|
return _dmntTargetIOFileSetSize(f, sizeof(*f), size);
|
||||||
|
}
|
||||||
57
stratosphere/tma/source/dmnt.h
Normal file
57
stratosphere/tma/source/dmnt.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 handle;
|
||||||
|
} DmntFile;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DmntTIOCreateOption_CreateNew = 1,
|
||||||
|
DmntTIOCreateOption_CreateAlways = 2,
|
||||||
|
DmntTIOCreateOption_OpenExisting = 3,
|
||||||
|
DmntTIOCreateOption_OpenAlways = 4,
|
||||||
|
DmntTIOCreateOption_ResetSize = 5,
|
||||||
|
} DmntTIOCreateOption;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 size;
|
||||||
|
u64 create_time;
|
||||||
|
u64 access_time;
|
||||||
|
u64 modify_time;
|
||||||
|
} DmntFileInformation;
|
||||||
|
|
||||||
|
Result dmntInitialize(void);
|
||||||
|
void dmntExit(void);
|
||||||
|
|
||||||
|
Result dmntTargetIOFileOpen(DmntFile *out, const char *path, int flags, DmntTIOCreateOption create_option);
|
||||||
|
Result dmntTargetIOFileClose(DmntFile *f);
|
||||||
|
Result dmntTargetIOFileRead(DmntFile *f, u64 off, void* buf, size_t len, size_t* out_read);
|
||||||
|
Result dmntTargetIOFileWrite(DmntFile *f, u64 off, const void* buf, size_t len, size_t* out_written);
|
||||||
|
Result dmntTargetIOFileGetInformation(const char *path, bool *out_is_dir, DmntFileInformation *out_info);
|
||||||
|
Result dmntTargetIOFileGetSize(const char *path, u64 *out_size);
|
||||||
|
Result dmntTargetIOFileSetSize(const char *path, u64 size);
|
||||||
|
Result dmntTargetIOFileSetOpenFileSize(DmntFile *f, u64 size); /* Atmosphere extension */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
43
stratosphere/tma/source/settings/settings_service.cpp
Normal file
43
stratosphere/tma/source/settings/settings_service.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "settings_service.hpp"
|
||||||
|
#include "settings_task.hpp"
|
||||||
|
|
||||||
|
TmaTask *SettingsService::NewTask(TmaPacket *packet) {
|
||||||
|
TmaTask *new_task = nullptr;
|
||||||
|
switch (packet->GetCommand()) {
|
||||||
|
case SettingsServiceCmd_GetSetting:
|
||||||
|
{
|
||||||
|
new_task = new GetSettingTask(this->manager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
new_task = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (new_task != nullptr) {
|
||||||
|
new_task->SetServiceId(this->GetServiceId());
|
||||||
|
new_task->SetTaskId(packet->GetTaskId());
|
||||||
|
new_task->OnStart(packet);
|
||||||
|
new_task->SetNeedsPackets(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_task;
|
||||||
|
}
|
||||||
34
stratosphere/tma/source/settings/settings_service.hpp
Normal file
34
stratosphere/tma/source/settings/settings_service.hpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "../tma_conn_service_ids.hpp"
|
||||||
|
#include "../tma_service.hpp"
|
||||||
|
|
||||||
|
enum SettingsServiceCmd : u32 {
|
||||||
|
SettingsServiceCmd_GetSetting = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
class SettingsService : public TmaService {
|
||||||
|
public:
|
||||||
|
SettingsService(TmaServiceManager *m) : TmaService(m, "SettingsService") { }
|
||||||
|
virtual ~SettingsService() { }
|
||||||
|
|
||||||
|
virtual TmaTask *NewTask(TmaPacket *packet) override;
|
||||||
|
};
|
||||||
48
stratosphere/tma/source/settings/settings_task.cpp
Normal file
48
stratosphere/tma/source/settings/settings_task.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "settings_task.hpp"
|
||||||
|
|
||||||
|
void GetSettingTask::OnStart(TmaPacket *packet) {
|
||||||
|
size_t length;
|
||||||
|
packet->ReadString(this->name, sizeof(this->name), &length);
|
||||||
|
packet->ReadString(this->item_key, sizeof(this->item_key), &length);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(setsysGetSettingsItemValueSize(this->name, this->item_key, &this->value_size))) {
|
||||||
|
if (this->value_size <= sizeof(this->value)) {
|
||||||
|
if (R_SUCCEEDED(setsysGetSettingsItemValue(this->name, this->item_key, this->value, this->value_size))) {
|
||||||
|
this->succeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetSettingTask::OnReceivePacket(TmaPacket *packet) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetSettingTask::OnSendPacket(TmaPacket *packet) {
|
||||||
|
packet->Write<u8>((u8)this->succeeded);
|
||||||
|
if (this->succeeded) {
|
||||||
|
packet->Write<u32>((u32)this->value_size);
|
||||||
|
packet->Write(this->value, this->value_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->Complete();
|
||||||
|
}
|
||||||
38
stratosphere/tma/source/settings/settings_task.hpp
Normal file
38
stratosphere/tma/source/settings/settings_task.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "../tma_task.hpp"
|
||||||
|
|
||||||
|
class GetSettingTask : public TmaTask {
|
||||||
|
private:
|
||||||
|
char name[0x40] = {0};
|
||||||
|
char item_key[0x40] = {0};
|
||||||
|
u8 value[0x40] = {0};
|
||||||
|
u64 value_size = 0;
|
||||||
|
bool succeeded = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GetSettingTask(TmaServiceManager *m) : TmaTask(m) { }
|
||||||
|
virtual ~GetSettingTask() { }
|
||||||
|
|
||||||
|
virtual void OnStart(TmaPacket *packet) override;
|
||||||
|
virtual void OnReceivePacket(TmaPacket *packet) override;
|
||||||
|
virtual void OnSendPacket(TmaPacket *packet) override;
|
||||||
|
};
|
||||||
47
stratosphere/tma/source/target_io/tio_service.cpp
Normal file
47
stratosphere/tma/source/target_io/tio_service.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tio_service.hpp"
|
||||||
|
#include "tio_task.hpp"
|
||||||
|
|
||||||
|
TmaTask *TIOService::NewTask(TmaPacket *packet) {
|
||||||
|
TmaTask *new_task = nullptr;
|
||||||
|
switch (packet->GetCommand()) {
|
||||||
|
case TIOServiceCmd_FileRead:
|
||||||
|
{
|
||||||
|
new_task = new TIOFileReadTask(this->manager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TIOServiceCmd_FileWrite:
|
||||||
|
{
|
||||||
|
new_task = new TIOFileWriteTask(this->manager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
new_task = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (new_task != nullptr) {
|
||||||
|
new_task->SetServiceId(this->GetServiceId());
|
||||||
|
new_task->SetTaskId(packet->GetTaskId());
|
||||||
|
new_task->OnStart(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_task;
|
||||||
|
}
|
||||||
35
stratosphere/tma/source/target_io/tio_service.hpp
Normal file
35
stratosphere/tma/source/target_io/tio_service.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "../tma_conn_service_ids.hpp"
|
||||||
|
#include "../tma_service.hpp"
|
||||||
|
|
||||||
|
enum TIOServiceCmd : u32 {
|
||||||
|
TIOServiceCmd_FileRead = 2,
|
||||||
|
TIOServiceCmd_FileWrite = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TIOService : public TmaService {
|
||||||
|
public:
|
||||||
|
TIOService(TmaServiceManager *m) : TmaService(m, "TIOService") { }
|
||||||
|
virtual ~TIOService() { }
|
||||||
|
|
||||||
|
virtual TmaTask *NewTask(TmaPacket *packet) override;
|
||||||
|
};
|
||||||
173
stratosphere/tma/source/target_io/tio_task.cpp
Normal file
173
stratosphere/tma/source/target_io/tio_task.cpp
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tio_task.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
void TIOFileReadTask::OnStart(TmaPacket *packet) {
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
|
||||||
|
packet->ReadString(path, sizeof(path), nullptr);
|
||||||
|
packet->Read<u64>(this->size_remaining);
|
||||||
|
packet->Read<u64>(this->cur_offset);
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
if (strlen(path) == 0) {
|
||||||
|
rc = 0x202;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
u64 file_size;
|
||||||
|
rc = dmntTargetIOFileGetSize(path, &file_size);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (file_size < this->cur_offset + this->size_remaining) {
|
||||||
|
this->size_remaining = file_size - this->cur_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = dmntTargetIOFileOpen(&this->handle, path, FS_OPEN_READ, DmntTIOCreateOption_OpenExisting);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
this->SendResult(rc);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
auto packet = this->AllocateSendPacket();
|
||||||
|
rc = this->ProcessPacket(packet);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
this->manager->SendPacket(packet);
|
||||||
|
if (this->size_remaining) {
|
||||||
|
this->SetNeedsPackets(true);
|
||||||
|
} else {
|
||||||
|
this->SendResult(rc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->manager->FreePacket(packet);
|
||||||
|
this->SendResult(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TIOFileReadTask::OnSendPacket(TmaPacket *packet) {
|
||||||
|
Result rc = this->ProcessPacket(packet);
|
||||||
|
|
||||||
|
if (this->size_remaining == 0 || R_FAILED(rc)) {
|
||||||
|
this->SendResult(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TIOFileReadTask::SendResult(Result rc) {
|
||||||
|
dmntTargetIOFileClose(&this->handle);
|
||||||
|
this->SetNeedsPackets(false);
|
||||||
|
|
||||||
|
auto packet = this->AllocateSendPacket();
|
||||||
|
packet->Write<Result>(rc);
|
||||||
|
this->manager->SendPacket(packet);
|
||||||
|
Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result TIOFileReadTask::ProcessPacket(TmaPacket *packet) {
|
||||||
|
Result rc = 0x196002;
|
||||||
|
|
||||||
|
size_t cur_read = static_cast<u32>((this->size_remaining > MaxDataSize) ? MaxDataSize : this->size_remaining);
|
||||||
|
|
||||||
|
u8 *buf = new u8[cur_read];
|
||||||
|
if (buf != nullptr) {
|
||||||
|
size_t actual_read = 0;
|
||||||
|
rc = dmntTargetIOFileRead(&this->handle, this->cur_offset, buf, cur_read, &actual_read);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
packet->Write<Result>(rc);
|
||||||
|
packet->Write<u32>(actual_read);
|
||||||
|
packet->Write(buf, actual_read);
|
||||||
|
this->cur_offset += actual_read;
|
||||||
|
this->size_remaining -= actual_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TIOFileWriteTask::OnStart(TmaPacket *packet) {
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
|
||||||
|
packet->ReadString(path, sizeof(path), nullptr);
|
||||||
|
packet->Read<u64>(this->size_remaining);
|
||||||
|
packet->Read<u64>(this->cur_offset);
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
if (strlen(path) == 0) {
|
||||||
|
rc = 0x202;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
u64 file_size;
|
||||||
|
rc = dmntTargetIOFileGetSize(path, &file_size);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (file_size < this->cur_offset + this->size_remaining) {
|
||||||
|
this->size_remaining = file_size - this->cur_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = dmntTargetIOFileOpen(&this->handle, path, FS_OPEN_READ, DmntTIOCreateOption_OpenExisting);
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
this->SendResult(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TIOFileWriteTask::OnReceivePacket(TmaPacket *packet) {
|
||||||
|
Result rc = this->ProcessPacket(packet);
|
||||||
|
|
||||||
|
if (this->size_remaining == 0 || R_FAILED(rc)) {
|
||||||
|
this->SendResult(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TIOFileWriteTask::SendResult(Result rc) {
|
||||||
|
dmntTargetIOFileClose(&this->handle);
|
||||||
|
|
||||||
|
auto packet = this->AllocateSendPacket();
|
||||||
|
packet->Write<Result>(rc);
|
||||||
|
this->manager->SendPacket(packet);
|
||||||
|
Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result TIOFileWriteTask::ProcessPacket(TmaPacket *packet) {
|
||||||
|
Result rc = 0x196002;
|
||||||
|
|
||||||
|
/* Note: N does not bounds check this. We do. */
|
||||||
|
u32 cur_write = 0;
|
||||||
|
packet->Read<u32>(cur_write);
|
||||||
|
|
||||||
|
size_t actual_written = 0;
|
||||||
|
if (cur_write < MaxDataSize) {
|
||||||
|
if (cur_write > this->size_remaining) {
|
||||||
|
cur_write = this->size_remaining;
|
||||||
|
}
|
||||||
|
rc = dmntTargetIOFileWrite(&this->handle, this->cur_offset, packet->GetCurrentBodyPtr(), cur_write, &actual_written);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
this->size_remaining -= actual_written;
|
||||||
|
this->cur_offset += actual_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
85
stratosphere/tma/source/target_io/tio_task.hpp
Normal file
85
stratosphere/tma/source/target_io/tio_task.hpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "../tma_task.hpp"
|
||||||
|
#include "../tma_service_manager.hpp"
|
||||||
|
#include "../dmnt.h"
|
||||||
|
|
||||||
|
class TIOTask : public TmaTask {
|
||||||
|
public:
|
||||||
|
TIOTask(TmaServiceManager *m) : TmaTask(m) { }
|
||||||
|
virtual ~TIOTask() { }
|
||||||
|
|
||||||
|
virtual void SendResult(Result rc) {
|
||||||
|
TmaPacket *packet = this->AllocateSendPacket();
|
||||||
|
packet->Write<Result>(rc);
|
||||||
|
this->manager->SendPacket(packet);
|
||||||
|
this->Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnStart(TmaPacket *packet) = 0;
|
||||||
|
|
||||||
|
virtual void OnReceivePacket(TmaPacket *packet) override {
|
||||||
|
this->Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnSendPacket(TmaPacket *packet) override {
|
||||||
|
this->Complete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TIOFileReadTask : public TIOTask {
|
||||||
|
private:
|
||||||
|
static constexpr size_t HeaderSize = sizeof(Result) + sizeof(u32);
|
||||||
|
static constexpr size_t MaxDataSize = TmaPacket::MaxBodySize - HeaderSize;
|
||||||
|
private:
|
||||||
|
DmntFile handle = {0};
|
||||||
|
u64 size_remaining = 0;
|
||||||
|
u64 cur_offset = 0;
|
||||||
|
public:
|
||||||
|
TIOFileReadTask(TmaServiceManager *m) : TIOTask(m) { }
|
||||||
|
virtual ~TIOFileReadTask() { }
|
||||||
|
|
||||||
|
virtual void OnStart(TmaPacket *packet) override;
|
||||||
|
virtual void OnSendPacket(TmaPacket *packet) override;
|
||||||
|
virtual void SendResult(Result rc) override;
|
||||||
|
|
||||||
|
Result ProcessPacket(TmaPacket *packet);
|
||||||
|
};
|
||||||
|
|
||||||
|
class TIOFileWriteTask : public TIOTask {
|
||||||
|
private:
|
||||||
|
static constexpr size_t HeaderSize = sizeof(u32);
|
||||||
|
static constexpr size_t MaxDataSize = TmaPacket::MaxBodySize - HeaderSize;
|
||||||
|
private:
|
||||||
|
DmntFile handle = {0};
|
||||||
|
u64 size_remaining = 0;
|
||||||
|
u64 cur_offset = 0;
|
||||||
|
public:
|
||||||
|
TIOFileWriteTask(TmaServiceManager *m) : TIOTask(m) { }
|
||||||
|
virtual ~TIOFileWriteTask() { }
|
||||||
|
|
||||||
|
virtual void OnStart(TmaPacket *packet) override;
|
||||||
|
virtual void OnReceivePacket(TmaPacket *packet) override;
|
||||||
|
virtual void SendResult(Result rc) override;
|
||||||
|
|
||||||
|
Result ProcessPacket(TmaPacket *packet);
|
||||||
|
};
|
||||||
|
|
||||||
31
stratosphere/tma/source/test/atmosphere_test_service.cpp
Normal file
31
stratosphere/tma/source/test/atmosphere_test_service.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "atmosphere_test_service.hpp"
|
||||||
|
#include "atmosphere_test_task.hpp"
|
||||||
|
|
||||||
|
TmaTask *AtmosphereTestService::NewTask(TmaPacket *packet) {
|
||||||
|
auto new_task = new AtmosphereTestTask(this->manager);
|
||||||
|
new_task->SetServiceId(this->GetServiceId());
|
||||||
|
new_task->SetTaskId(packet->GetTaskId());
|
||||||
|
new_task->OnStart(packet);
|
||||||
|
new_task->SetNeedsPackets(true);
|
||||||
|
|
||||||
|
return new_task;
|
||||||
|
}
|
||||||
30
stratosphere/tma/source/test/atmosphere_test_service.hpp
Normal file
30
stratosphere/tma/source/test/atmosphere_test_service.hpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "../tma_conn_service_ids.hpp"
|
||||||
|
#include "../tma_service.hpp"
|
||||||
|
|
||||||
|
class AtmosphereTestService : public TmaService {
|
||||||
|
public:
|
||||||
|
AtmosphereTestService(TmaServiceManager *m) : TmaService(m, "AtmosphereTestService") { }
|
||||||
|
virtual ~AtmosphereTestService() { }
|
||||||
|
|
||||||
|
virtual TmaTask *NewTask(TmaPacket *packet) override;
|
||||||
|
};
|
||||||
36
stratosphere/tma/source/test/atmosphere_test_task.cpp
Normal file
36
stratosphere/tma/source/test/atmosphere_test_task.cpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "atmosphere_test_task.hpp"
|
||||||
|
|
||||||
|
void AtmosphereTestTask::OnStart(TmaPacket *packet) {
|
||||||
|
packet->Read<u32>(this->arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AtmosphereTestTask::OnReceivePacket(TmaPacket *packet) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AtmosphereTestTask::OnSendPacket(TmaPacket *packet) {
|
||||||
|
for (size_t i = 0; i < this->arg && i < 0x100; i++) {
|
||||||
|
packet->Write<u8>('A');
|
||||||
|
}
|
||||||
|
|
||||||
|
this->Complete();
|
||||||
|
}
|
||||||
33
stratosphere/tma/source/test/atmosphere_test_task.hpp
Normal file
33
stratosphere/tma/source/test/atmosphere_test_task.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "../tma_task.hpp"
|
||||||
|
|
||||||
|
class AtmosphereTestTask : public TmaTask {
|
||||||
|
private:
|
||||||
|
u32 arg;
|
||||||
|
public:
|
||||||
|
AtmosphereTestTask(TmaServiceManager *m) : TmaTask(m) { }
|
||||||
|
virtual ~AtmosphereTestTask() { }
|
||||||
|
|
||||||
|
virtual void OnStart(TmaPacket *packet) override;
|
||||||
|
virtual void OnReceivePacket(TmaPacket *packet) override;
|
||||||
|
virtual void OnSendPacket(TmaPacket *packet) override;
|
||||||
|
};
|
||||||
64
stratosphere/tma/source/tma_conn_connection.cpp
Normal file
64
stratosphere/tma/source/tma_conn_connection.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_conn_connection.hpp"
|
||||||
|
#include "tma_service_manager.hpp"
|
||||||
|
|
||||||
|
/* Packet management. */
|
||||||
|
TmaPacket *TmaConnection::AllocateSendPacket() {
|
||||||
|
return this->service_manager->AllocateSendPacket();
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaPacket *TmaConnection::AllocateRecvPacket() {
|
||||||
|
return this->service_manager->AllocateRecvPacket();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaConnection::FreePacket(TmaPacket *packet) {
|
||||||
|
this->service_manager->FreePacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaConnection::OnReceivePacket(TmaPacket *packet) {
|
||||||
|
this->service_manager->OnReceivePacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaConnection::OnDisconnected() {
|
||||||
|
if (!this->is_initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->service_manager != nullptr) {
|
||||||
|
this->service_manager->OnDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->has_woken_up = false;
|
||||||
|
this->OnConnectionEvent(ConnectionEvent::Disconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaConnection::OnConnectionEvent(ConnectionEvent event) {
|
||||||
|
if (this->connection_event_callback != nullptr) {
|
||||||
|
this->connection_event_callback(this->connection_event_arg, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaConnection::CancelTasks() {
|
||||||
|
this->service_manager->CancelTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaConnection::Tick() {
|
||||||
|
this->service_manager->Tick();
|
||||||
|
}
|
||||||
90
stratosphere/tma/source/tma_conn_connection.hpp
Normal file
90
stratosphere/tma/source/tma_conn_connection.hpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_result.hpp"
|
||||||
|
#include "tma_conn_packet.hpp"
|
||||||
|
|
||||||
|
enum class ConnectionEvent : u32 {
|
||||||
|
Connected,
|
||||||
|
Disconnected
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TmaServiceManager;
|
||||||
|
|
||||||
|
class TmaConnection {
|
||||||
|
protected:
|
||||||
|
HosMutex lock;
|
||||||
|
void (*connection_event_callback)(void *, ConnectionEvent) = nullptr;
|
||||||
|
void *connection_event_arg = nullptr;
|
||||||
|
bool has_woken_up = false;
|
||||||
|
bool is_initialized = false;
|
||||||
|
TmaServiceManager *service_manager = nullptr;
|
||||||
|
protected:
|
||||||
|
void OnReceivePacket(TmaPacket *packet);
|
||||||
|
void OnDisconnected();
|
||||||
|
void OnConnectionEvent(ConnectionEvent event);
|
||||||
|
void CancelTasks();
|
||||||
|
void Tick();
|
||||||
|
public:
|
||||||
|
/* Setup */
|
||||||
|
TmaConnection() { }
|
||||||
|
virtual ~TmaConnection() { }
|
||||||
|
|
||||||
|
void Initialize() {
|
||||||
|
if (this->is_initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
this->is_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetConnectionEventCallback(void (*callback)(void *, ConnectionEvent), void *arg) {
|
||||||
|
this->connection_event_callback = callback;
|
||||||
|
this->connection_event_arg = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finalize() {
|
||||||
|
if (this->is_initialized) {
|
||||||
|
this->StopListening();
|
||||||
|
if (this->IsConnected()) {
|
||||||
|
this->Disconnect();
|
||||||
|
}
|
||||||
|
this->is_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetServiceManager(TmaServiceManager *manager) { this->service_manager = manager; }
|
||||||
|
|
||||||
|
/* Packet management. */
|
||||||
|
TmaPacket *AllocateSendPacket();
|
||||||
|
TmaPacket *AllocateRecvPacket();
|
||||||
|
void FreePacket(TmaPacket *packet);
|
||||||
|
|
||||||
|
/* Sleep management. */
|
||||||
|
bool HasWokenUp() const { return this->has_woken_up; }
|
||||||
|
void SetWokenUp(bool woke) { this->has_woken_up = woke; }
|
||||||
|
|
||||||
|
/* For sub-interfaces to implement, connection management. */
|
||||||
|
virtual void StartListening() { }
|
||||||
|
virtual void StopListening() { }
|
||||||
|
virtual bool IsConnected() = 0;
|
||||||
|
virtual TmaConnResult Disconnect() = 0;
|
||||||
|
virtual TmaConnResult SendPacket(TmaPacket *packet) = 0;
|
||||||
|
};
|
||||||
279
stratosphere/tma/source/tma_conn_packet.hpp
Normal file
279
stratosphere/tma/source/tma_conn_packet.hpp
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include "tma_conn_result.hpp"
|
||||||
|
#include "tma_conn_service_ids.hpp"
|
||||||
|
#include "crc.h"
|
||||||
|
|
||||||
|
class TmaPacket {
|
||||||
|
public:
|
||||||
|
struct Header {
|
||||||
|
u32 service_id;
|
||||||
|
u32 task_id;
|
||||||
|
u16 command;
|
||||||
|
u8 is_continuation;
|
||||||
|
u8 version;
|
||||||
|
u32 body_len;
|
||||||
|
u32 reserved[4]; /* This is where N's header ends. */
|
||||||
|
u32 body_checksum;
|
||||||
|
u32 header_checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Header) == 0x28, "Packet::Header definition!");
|
||||||
|
|
||||||
|
static constexpr u32 MaxBodySize = 0xE000;
|
||||||
|
static constexpr u32 MaxPacketSize = MaxBodySize + sizeof(Header);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<u8[]> buffer = std::make_unique<u8[]>(MaxPacketSize);
|
||||||
|
u32 offset = 0;
|
||||||
|
HosMessageQueue *free_queue = nullptr;
|
||||||
|
|
||||||
|
Header *GetHeader() const {
|
||||||
|
return reinterpret_cast<Header *>(buffer.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 *GetBody(u32 ofs) const {
|
||||||
|
return reinterpret_cast<u8 *>(buffer.get() + sizeof(Header) + ofs);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
TmaPacket() {
|
||||||
|
memset(buffer.get(), 0, MaxPacketSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implicit ~TmaPacket() */
|
||||||
|
|
||||||
|
/* These allow reading a packet in. */
|
||||||
|
void CopyHeaderFrom(Header *hdr) {
|
||||||
|
*GetHeader() = *hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult CopyBodyFrom(void *body, size_t size) {
|
||||||
|
if (size >= MaxBodySize) {
|
||||||
|
return TmaConnResult::PacketOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(GetBody(0), body, size);
|
||||||
|
|
||||||
|
return TmaConnResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyHeaderTo(void *out) {
|
||||||
|
memcpy(out, buffer.get(), sizeof(Header));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyBodyTo(void *out) const {
|
||||||
|
memcpy(out, buffer.get() + sizeof(Header), GetBodyLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHeaderValid() {
|
||||||
|
Header *hdr = GetHeader();
|
||||||
|
return crc32_arm64_le_hw(reinterpret_cast<const u8 *>(hdr), sizeof(*hdr) - sizeof(hdr->header_checksum)) == hdr->header_checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsBodyValid() const {
|
||||||
|
const u32 body_len = GetHeader()->body_len;
|
||||||
|
if (body_len == 0) {
|
||||||
|
return GetHeader()->body_checksum == 0;
|
||||||
|
} else {
|
||||||
|
return crc32_arm64_le_hw(GetBody(0), body_len) == GetHeader()->body_checksum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HosMessageQueue *GetFreeQueue() const {
|
||||||
|
return this->free_queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFreeQueue(HosMessageQueue *queue) {
|
||||||
|
this->free_queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetChecksums() {
|
||||||
|
Header *hdr = GetHeader();
|
||||||
|
if (hdr->body_len) {
|
||||||
|
hdr->body_checksum = crc32_arm64_le_hw(GetBody(0), hdr->body_len);
|
||||||
|
} else {
|
||||||
|
hdr->body_checksum = 0;
|
||||||
|
}
|
||||||
|
hdr->header_checksum = crc32_arm64_le_hw(reinterpret_cast<const u8 *>(hdr), sizeof(*hdr) - sizeof(hdr->header_checksum));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetBodyLength() const {
|
||||||
|
return GetHeader()->body_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetLength() const {
|
||||||
|
return GetBodyLength() + sizeof(Header);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetBodyAvailableLength() const {
|
||||||
|
return MaxPacketSize - this->offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetServiceId(TmaServiceId srv) {
|
||||||
|
GetHeader()->service_id = static_cast<u32>(srv);
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaServiceId GetServiceId() const {
|
||||||
|
return static_cast<TmaServiceId>(GetHeader()->service_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTaskId(u32 id) {
|
||||||
|
GetHeader()->task_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetTaskId() const {
|
||||||
|
return GetHeader()->task_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCommand(u16 cmd) {
|
||||||
|
GetHeader()->command = cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 GetCommand() const {
|
||||||
|
return GetHeader()->command;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetContinuation(bool c) {
|
||||||
|
GetHeader()->is_continuation = c ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetContinuation() const {
|
||||||
|
return GetHeader()->is_continuation == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetVersion(u8 v) {
|
||||||
|
GetHeader()->version = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 GetVersion() const {
|
||||||
|
return GetHeader()->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 *GetCurrentBodyPtr() {
|
||||||
|
return GetBody(this->offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearOffset() {
|
||||||
|
this->offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetBodyLength() {
|
||||||
|
GetHeader()->body_len = this->offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult Write(const void *data, size_t size) {
|
||||||
|
if (size > GetBodyAvailableLength()) {
|
||||||
|
return TmaConnResult::PacketOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(GetBody(this->offset), data, size);
|
||||||
|
this->offset += size;
|
||||||
|
GetHeader()->body_len = this->offset;
|
||||||
|
|
||||||
|
return TmaConnResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult Read(void *data, size_t size) {
|
||||||
|
if (size > GetBodyAvailableLength()) {
|
||||||
|
return TmaConnResult::PacketOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data, GetBody(this->offset), size);
|
||||||
|
this->offset += size;
|
||||||
|
|
||||||
|
return TmaConnResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
TmaConnResult Write(const T &t) {
|
||||||
|
return Write(&t, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
TmaConnResult Read(T &t) {
|
||||||
|
return Read(&t, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult WriteString(const char *s) {
|
||||||
|
return Write(s, strlen(s) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t WriteFormat(const char *format, ...) {
|
||||||
|
va_list va_arg;
|
||||||
|
va_start(va_arg, format);
|
||||||
|
const size_t available = GetBodyAvailableLength();
|
||||||
|
const int written = vsnprintf(reinterpret_cast<char *>(GetBody(this->offset)), available, format, va_arg);
|
||||||
|
|
||||||
|
size_t total_written;
|
||||||
|
if (static_cast<size_t>(written) < available) {
|
||||||
|
this->offset += written;
|
||||||
|
*GetBody(this->offset++) = 0;
|
||||||
|
total_written = written + 1;
|
||||||
|
} else {
|
||||||
|
this->offset += available;
|
||||||
|
total_written = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetHeader()->body_len = this->offset;
|
||||||
|
return total_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult ReadString(char *buf, size_t buf_size, size_t *out_size) {
|
||||||
|
TmaConnResult res = TmaConnResult::Success;
|
||||||
|
|
||||||
|
size_t available = GetBodyAvailableLength();
|
||||||
|
size_t ofs = 0;
|
||||||
|
while (ofs < buf_size) {
|
||||||
|
if (ofs >= available) {
|
||||||
|
res = TmaConnResult::PacketOverflow;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ofs == buf_size) {
|
||||||
|
res = TmaConnResult::BufferOverflow;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[ofs] = static_cast<char>(*GetBody(this->offset++));
|
||||||
|
|
||||||
|
if (buf[ofs++] == '\x00') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finish reading the string if the user buffer is too small. */
|
||||||
|
if (res == TmaConnResult::BufferOverflow) {
|
||||||
|
u8 cur = *GetBody(this->offset);
|
||||||
|
while (cur != 0) {
|
||||||
|
if (ofs >= available) {
|
||||||
|
res = TmaConnResult::PacketOverflow;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cur = *GetBody(this->offset++);
|
||||||
|
ofs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_size != nullptr) {
|
||||||
|
*out_size = ofs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
40
stratosphere/tma/source/tma_conn_result.hpp
Normal file
40
stratosphere/tma/source/tma_conn_result.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
enum class TmaConnResult : u32 {
|
||||||
|
Success = 0,
|
||||||
|
NotImplemented,
|
||||||
|
GeneralFailure,
|
||||||
|
ConnectionFailure,
|
||||||
|
AlreadyConnected,
|
||||||
|
WrongConnectionVersion,
|
||||||
|
PacketOverflow,
|
||||||
|
BufferOverflow,
|
||||||
|
Disconnected,
|
||||||
|
ServiceAlreadyRegistered,
|
||||||
|
ServiceUnknown,
|
||||||
|
Timeout,
|
||||||
|
NotInitialized,
|
||||||
|
};
|
||||||
53
stratosphere/tma/source/tma_conn_service_ids.hpp
Normal file
53
stratosphere/tma/source/tma_conn_service_ids.hpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tma_conn_result.hpp"
|
||||||
|
|
||||||
|
/* This is just python's hash function, but official TMA code uses it. */
|
||||||
|
static constexpr u32 HashServiceName(const char *name) {
|
||||||
|
u32 h = *name;
|
||||||
|
u32 len = 0;
|
||||||
|
|
||||||
|
while (*name) {
|
||||||
|
h = (1000003 * h) ^ *name;
|
||||||
|
name++;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return h ^ len;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TmaServiceId : u32 {
|
||||||
|
Invalid = 0,
|
||||||
|
|
||||||
|
/* Special nodes, for facilitating connection over USB. */
|
||||||
|
UsbQueryTarget = HashServiceName("USBQueryTarget"),
|
||||||
|
UsbSendHostInfo = HashServiceName("USBSendHostInfo"),
|
||||||
|
UsbConnect = HashServiceName("USBConnect"),
|
||||||
|
UsbDisconnect = HashServiceName("USBDisconnect"),
|
||||||
|
|
||||||
|
|
||||||
|
TestService = HashServiceName("AtmosphereTestService"), /* Temporary service, will be used to debug communications. */
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr bool IsMetaService(TmaServiceId id) {
|
||||||
|
return id == TmaServiceId::UsbQueryTarget ||
|
||||||
|
id == TmaServiceId::UsbSendHostInfo ||
|
||||||
|
id == TmaServiceId::UsbConnect ||
|
||||||
|
id == TmaServiceId::UsbDisconnect;
|
||||||
|
}
|
||||||
204
stratosphere/tma/source/tma_conn_usb_connection.cpp
Normal file
204
stratosphere/tma/source/tma_conn_usb_connection.cpp
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_conn_usb_connection.hpp"
|
||||||
|
#include "tma_usb_comms.hpp"
|
||||||
|
|
||||||
|
static HosThread g_SendThread, g_RecvThread;
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbConnection::InitializeComms() {
|
||||||
|
return TmaUsbComms::Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbConnection::FinalizeComms() {
|
||||||
|
return TmaUsbComms::Finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbConnection::ClearSendQueue() {
|
||||||
|
uintptr_t _packet;
|
||||||
|
while (this->send_queue.TryReceive(&_packet)) {
|
||||||
|
TmaPacket *packet = reinterpret_cast<TmaPacket *>(_packet);
|
||||||
|
if (packet != nullptr) {
|
||||||
|
this->FreePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbConnection::SendThreadFunc(void *arg) {
|
||||||
|
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
|
||||||
|
TmaConnResult res = TmaConnResult::Success;
|
||||||
|
TmaPacket *packet = nullptr;
|
||||||
|
|
||||||
|
while (res == TmaConnResult::Success) {
|
||||||
|
/* Receive a packet from the send queue. */
|
||||||
|
{
|
||||||
|
uintptr_t _packet;
|
||||||
|
this_ptr->send_queue.Receive(&_packet);
|
||||||
|
packet = reinterpret_cast<TmaPacket *>(_packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet != nullptr) {
|
||||||
|
/* Send the packet if we're connected. */
|
||||||
|
if (this_ptr->IsConnected()) {
|
||||||
|
res = TmaUsbComms::SendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
this_ptr->FreePacket(packet);
|
||||||
|
this_ptr->Tick();
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::Disconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this_ptr->SetConnected(false);
|
||||||
|
this_ptr->OnDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbConnection::RecvThreadFunc(void *arg) {
|
||||||
|
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
|
||||||
|
TmaConnResult res = TmaConnResult::Success;
|
||||||
|
this_ptr->SetConnected(true);
|
||||||
|
|
||||||
|
while (res == TmaConnResult::Success) {
|
||||||
|
TmaPacket *packet = this_ptr->AllocateRecvPacket();
|
||||||
|
if (packet == nullptr) { std::abort(); }
|
||||||
|
|
||||||
|
res = TmaUsbComms::ReceivePacket(packet);
|
||||||
|
|
||||||
|
if (res == TmaConnResult::Success) {
|
||||||
|
if (!IsMetaService(packet->GetServiceId())) {
|
||||||
|
this_ptr->OnReceivePacket(packet);
|
||||||
|
} else {
|
||||||
|
switch (packet->GetServiceId()) {
|
||||||
|
case TmaServiceId::UsbQueryTarget: {
|
||||||
|
this_ptr->SetConnected(false);
|
||||||
|
|
||||||
|
res = this_ptr->SendQueryReply(packet);
|
||||||
|
|
||||||
|
if (!this_ptr->has_woken_up) {
|
||||||
|
this_ptr->CancelTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TmaServiceId::UsbSendHostInfo: {
|
||||||
|
struct {
|
||||||
|
u32 version;
|
||||||
|
u32 sleeping;
|
||||||
|
} host_info;
|
||||||
|
packet->Read<decltype(host_info)>(host_info);
|
||||||
|
|
||||||
|
if (!this_ptr->has_woken_up || !host_info.sleeping) {
|
||||||
|
this_ptr->CancelTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TmaServiceId::UsbConnect: {
|
||||||
|
res = this_ptr->SendQueryReply(packet);
|
||||||
|
|
||||||
|
if (res == TmaConnResult::Success) {
|
||||||
|
this_ptr->SetConnected(true);
|
||||||
|
this_ptr->OnConnectionEvent(ConnectionEvent::Connected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TmaServiceId::UsbDisconnect: {
|
||||||
|
this_ptr->SetConnected(false);
|
||||||
|
this_ptr->OnDisconnected();
|
||||||
|
|
||||||
|
this_ptr->CancelTasks();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this_ptr->FreePacket(packet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this_ptr->FreePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this_ptr->SetConnected(false);
|
||||||
|
this_ptr->send_queue.Send(reinterpret_cast<uintptr_t>(nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbConnection::OnUsbStateChange(void *arg, u32 state) {
|
||||||
|
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
|
||||||
|
switch (state) {
|
||||||
|
case 0:
|
||||||
|
case 6:
|
||||||
|
this_ptr->StopThreads();
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this_ptr->StartThreads();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbConnection::StartThreads() {
|
||||||
|
g_SendThread.Join();
|
||||||
|
g_RecvThread.Join();
|
||||||
|
|
||||||
|
g_SendThread.Initialize(&TmaUsbConnection::SendThreadFunc, this, 0x4000, 38);
|
||||||
|
g_RecvThread.Initialize(&TmaUsbConnection::RecvThreadFunc, this, 0x4000, 38);
|
||||||
|
|
||||||
|
this->ClearSendQueue();
|
||||||
|
g_SendThread.Start();
|
||||||
|
g_RecvThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbConnection::StopThreads() {
|
||||||
|
TmaUsbComms::CancelComms();
|
||||||
|
g_SendThread.Join();
|
||||||
|
g_RecvThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TmaUsbConnection::IsConnected() {
|
||||||
|
return this->is_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbConnection::Disconnect() {
|
||||||
|
TmaUsbComms::SetStateChangeCallback(nullptr, nullptr);
|
||||||
|
|
||||||
|
this->StopThreads();
|
||||||
|
|
||||||
|
return TmaConnResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbConnection::SendPacket(TmaPacket *packet) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
if (this->IsConnected()) {
|
||||||
|
this->send_queue.Send(reinterpret_cast<uintptr_t>(packet));
|
||||||
|
return TmaConnResult::Success;
|
||||||
|
} else {
|
||||||
|
this->FreePacket(packet);
|
||||||
|
this->Tick();
|
||||||
|
return TmaConnResult::Disconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbConnection::SendQueryReply(TmaPacket *packet) {
|
||||||
|
packet->ClearOffset();
|
||||||
|
struct {
|
||||||
|
u32 version;
|
||||||
|
} target_info;
|
||||||
|
target_info.version = 0;
|
||||||
|
packet->Write<decltype(target_info)>(target_info);
|
||||||
|
return TmaUsbComms::SendPacket(packet);
|
||||||
|
}
|
||||||
52
stratosphere/tma/source/tma_conn_usb_connection.hpp
Normal file
52
stratosphere/tma/source/tma_conn_usb_connection.hpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_connection.hpp"
|
||||||
|
#include "tma_usb_comms.hpp"
|
||||||
|
|
||||||
|
class TmaUsbConnection : public TmaConnection {
|
||||||
|
private:
|
||||||
|
HosMessageQueue send_queue = HosMessageQueue(64);
|
||||||
|
std::atomic<bool> is_connected = false;
|
||||||
|
private:
|
||||||
|
static void SendThreadFunc(void *arg);
|
||||||
|
static void RecvThreadFunc(void *arg);
|
||||||
|
static void OnUsbStateChange(void *this_ptr, u32 state);
|
||||||
|
TmaConnResult SendQueryReply(TmaPacket *packet);
|
||||||
|
void ClearSendQueue();
|
||||||
|
void StartThreads();
|
||||||
|
void StopThreads();
|
||||||
|
void SetConnected(bool c) { this->is_connected = c; }
|
||||||
|
public:
|
||||||
|
static TmaConnResult InitializeComms();
|
||||||
|
static TmaConnResult FinalizeComms();
|
||||||
|
|
||||||
|
TmaUsbConnection() {
|
||||||
|
TmaUsbComms::SetStateChangeCallback(&TmaUsbConnection::OnUsbStateChange, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TmaUsbConnection() {
|
||||||
|
this->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool IsConnected() override;
|
||||||
|
virtual TmaConnResult Disconnect() override;
|
||||||
|
virtual TmaConnResult SendPacket(TmaPacket *packet) override;
|
||||||
|
};
|
||||||
106
stratosphere/tma/source/tma_main.cpp
Normal file
106
stratosphere/tma/source/tma_main.cpp
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <atmosphere.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_target.hpp"
|
||||||
|
|
||||||
|
#include "dmnt.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
extern u32 __start__;
|
||||||
|
|
||||||
|
u32 __nx_applet_type = AppletType_None;
|
||||||
|
|
||||||
|
#define INNER_HEAP_SIZE 0x400000
|
||||||
|
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
|
||||||
|
char nx_inner_heap[INNER_HEAP_SIZE];
|
||||||
|
|
||||||
|
void __libnx_initheap(void);
|
||||||
|
void __appInit(void);
|
||||||
|
void __appExit(void);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void __libnx_initheap(void) {
|
||||||
|
void* addr = nx_inner_heap;
|
||||||
|
size_t size = nx_inner_heap_size;
|
||||||
|
|
||||||
|
/* Newlib */
|
||||||
|
extern char* fake_heap_start;
|
||||||
|
extern char* fake_heap_end;
|
||||||
|
|
||||||
|
fake_heap_start = (char*)addr;
|
||||||
|
fake_heap_end = (char*)addr + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __appInit(void) {
|
||||||
|
Result rc;
|
||||||
|
|
||||||
|
rc = smInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pscInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = setsysInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = dmntInitialize();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __appExit(void) {
|
||||||
|
/* Cleanup services. */
|
||||||
|
dmntExit();
|
||||||
|
setsysExit();
|
||||||
|
pscExit();
|
||||||
|
smExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
consoleDebugInit(debugDevice_SVC);
|
||||||
|
|
||||||
|
/* This will initialize the target. */
|
||||||
|
TmaTarget::Initialize();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
svcSleepThread(10000000UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaTarget::Finalize();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
68
stratosphere/tma/source/tma_power_manager.cpp
Normal file
68
stratosphere/tma/source/tma_power_manager.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_power_manager.hpp"
|
||||||
|
|
||||||
|
static constexpr u16 PscPmModuleId_Usb = 0x04;
|
||||||
|
static constexpr u16 PscPmModuleId_Pcie = 0x13;
|
||||||
|
static constexpr u16 PscPmModuleId_Tma = 0x1E;
|
||||||
|
|
||||||
|
static const u16 g_tma_pm_dependencies[] = {
|
||||||
|
PscPmModuleId_Usb,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void (*g_pm_callback)(PscPmState, u32) = nullptr;
|
||||||
|
static HosThread g_pm_thread;
|
||||||
|
|
||||||
|
static void PowerManagerThread(void *arg) {
|
||||||
|
/* Setup psc module. */
|
||||||
|
Result rc;
|
||||||
|
PscPmModule tma_module = {0};
|
||||||
|
if (R_FAILED((rc = pscGetPmModule(&tma_module, PscPmModuleId_Tma, g_tma_pm_dependencies, sizeof(g_tma_pm_dependencies)/sizeof(u16), true)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For now, just do what dummy tma does -- loop forever, acknowledging everything. */
|
||||||
|
while (true) {
|
||||||
|
if (R_FAILED((rc = eventWait(&tma_module.event, U64_MAX)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
PscPmState state;
|
||||||
|
u32 flags;
|
||||||
|
if (R_FAILED((rc = pscPmModuleGetRequest(&tma_module, &state, &flags)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_pm_callback(state, flags);
|
||||||
|
|
||||||
|
if (R_FAILED((rc = pscPmModuleAcknowledge(&tma_module, state)))) {
|
||||||
|
fatalSimple(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaPowerManager::Initialize(void (*callback)(PscPmState, u32)) {
|
||||||
|
g_pm_callback = callback;
|
||||||
|
g_pm_thread.Initialize(PowerManagerThread, nullptr, 0x4000, 0x26);
|
||||||
|
g_pm_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaPowerManager::Finalize() {
|
||||||
|
/* TODO */
|
||||||
|
}
|
||||||
25
stratosphere/tma/source/tma_power_manager.hpp
Normal file
25
stratosphere/tma/source/tma_power_manager.hpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
class TmaPowerManager {
|
||||||
|
public:
|
||||||
|
static void Initialize(void (*callback)(PscPmState, u32));
|
||||||
|
static void Finalize();
|
||||||
|
};
|
||||||
32
stratosphere/tma/source/tma_service.cpp
Normal file
32
stratosphere/tma/source/tma_service.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_service.hpp"
|
||||||
|
#include "tma_service_manager.hpp"
|
||||||
|
|
||||||
|
u32 TmaService::GetNextTaskId() {
|
||||||
|
return this->manager->GetNextTaskId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaService::OnSleep() {
|
||||||
|
/* Default service does nothing here. */
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaService::OnWake() {
|
||||||
|
/* Default service does nothing here. */
|
||||||
|
}
|
||||||
43
stratosphere/tma/source/tma_service.hpp
Normal file
43
stratosphere/tma/source/tma_service.hpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_service_ids.hpp"
|
||||||
|
#include "tma_conn_packet.hpp"
|
||||||
|
#include "tma_task.hpp"
|
||||||
|
|
||||||
|
class TmaServiceManager;
|
||||||
|
|
||||||
|
class TmaService {
|
||||||
|
protected:
|
||||||
|
TmaServiceManager *manager;
|
||||||
|
const char *service_name;
|
||||||
|
const TmaServiceId id;
|
||||||
|
protected:
|
||||||
|
u32 GetNextTaskId();
|
||||||
|
public:
|
||||||
|
TmaService(TmaServiceManager *m, const char *n) : manager(m), service_name(n), id(static_cast<TmaServiceId>(HashServiceName(this->service_name))) { }
|
||||||
|
virtual ~TmaService() { }
|
||||||
|
|
||||||
|
TmaServiceId GetServiceId() const { return this->id; }
|
||||||
|
|
||||||
|
virtual TmaTask *NewTask(TmaPacket *packet) = 0;
|
||||||
|
virtual void OnSleep();
|
||||||
|
virtual void OnWake();
|
||||||
|
};
|
||||||
406
stratosphere/tma/source/tma_service_manager.cpp
Normal file
406
stratosphere/tma/source/tma_service_manager.cpp
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_service_manager.hpp"
|
||||||
|
|
||||||
|
TmaServiceManager::TmaServiceManager() {
|
||||||
|
/* Set up queues */
|
||||||
|
for (size_t i = 0; i < TmaServiceManager::PacketQueueDepth; i++) {
|
||||||
|
TmaPacket *packet = nullptr;
|
||||||
|
|
||||||
|
packet = new TmaPacket();
|
||||||
|
packet->SetFreeQueue(&this->free_send_packet_queue);
|
||||||
|
this->free_send_packet_queue.Send(reinterpret_cast<uintptr_t>(packet));
|
||||||
|
packet = nullptr;
|
||||||
|
|
||||||
|
packet = new TmaPacket();
|
||||||
|
packet->SetFreeQueue(&this->free_recv_packet_queue);
|
||||||
|
this->free_recv_packet_queue.Send(reinterpret_cast<uintptr_t>(packet));
|
||||||
|
packet = nullptr;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < TmaServiceManager::WorkQueueDepth; i++) {
|
||||||
|
this->free_work_queue.Send(reinterpret_cast<uintptr_t>(new TmaWorkItem()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaServiceManager::~TmaServiceManager() {
|
||||||
|
/* Destroy queues. */
|
||||||
|
TmaPacket *packet = nullptr;
|
||||||
|
while (this->free_send_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
|
||||||
|
delete packet;
|
||||||
|
packet = nullptr;
|
||||||
|
}
|
||||||
|
while (this->free_recv_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
|
||||||
|
delete packet;
|
||||||
|
packet = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaWorkItem *work_item = nullptr;
|
||||||
|
while (this->free_work_queue.TryReceive(reinterpret_cast<uintptr_t *>(&work_item))) {
|
||||||
|
delete work_item;
|
||||||
|
work_item = nullptr;
|
||||||
|
}
|
||||||
|
while (this->work_queue.TryReceive(reinterpret_cast<uintptr_t *>(&work_item))) {
|
||||||
|
delete work_item;
|
||||||
|
work_item = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::Initialize() {
|
||||||
|
this->initialized = true;
|
||||||
|
this->work_thread.Initialize(TmaServiceManager::WorkThread, this, 0x4000, 0x26);
|
||||||
|
this->work_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::Finalize() {
|
||||||
|
if (this->initialized) {
|
||||||
|
this->initialized = false;
|
||||||
|
if (this->connection && this->connection->IsConnected()) {
|
||||||
|
this->connection->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signal to work thread to end. */
|
||||||
|
this->work_queue.Send(reinterpret_cast<uintptr_t>(nullptr));
|
||||||
|
this->work_thread.Join();
|
||||||
|
|
||||||
|
/* TODO: N tells services that they have no manager here. Do we want to do that? */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet) {
|
||||||
|
if (!this->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaWorkItem *work_item = nullptr;
|
||||||
|
this->free_work_queue.Receive(reinterpret_cast<uintptr_t *>(&work_item));
|
||||||
|
|
||||||
|
work_item->task = task;
|
||||||
|
work_item->packet = packet;
|
||||||
|
work_item->work_type = type;
|
||||||
|
this->work_queue.Send(reinterpret_cast<uintptr_t>(work_item));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Packet management. */
|
||||||
|
TmaConnResult TmaServiceManager::SendPacket(TmaPacket *packet) {
|
||||||
|
TmaConnResult res = TmaConnResult::Disconnected;
|
||||||
|
|
||||||
|
if (this->connection != nullptr) {
|
||||||
|
res = this->connection->SendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TmaServiceManager::OnReceivePacket(TmaPacket *packet) {
|
||||||
|
this->AddWork(TmaWorkType::ReceivePacket, nullptr, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaPacket *TmaServiceManager::AllocateSendPacket() {
|
||||||
|
if (!this->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaPacket *packet = nullptr;
|
||||||
|
this->free_send_packet_queue.Receive(reinterpret_cast<uintptr_t *>(&packet));
|
||||||
|
|
||||||
|
packet->ClearOffset();
|
||||||
|
packet->SetBodyLength();
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaPacket *TmaServiceManager::AllocateRecvPacket() {
|
||||||
|
if (!this->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaPacket *packet = nullptr;
|
||||||
|
this->free_recv_packet_queue.Receive(reinterpret_cast<uintptr_t *>(&packet));
|
||||||
|
|
||||||
|
packet->ClearOffset();
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::FreePacket(TmaPacket *packet) {
|
||||||
|
if (!this->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet != nullptr) {
|
||||||
|
packet->GetFreeQueue()->Send(reinterpret_cast<uintptr_t>(packet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Service/task management. */
|
||||||
|
TmaService *TmaServiceManager::GetServiceById(TmaServiceId id) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
for (auto srv : this->services) {
|
||||||
|
if (srv->GetServiceId() == id) {
|
||||||
|
return srv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::AddService(TmaService *service) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
this->services.push_back(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::AddTask(TmaTask *task, TmaPacket *packet) {
|
||||||
|
this->AddWork(TmaWorkType::NewTask, task, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::FreeTask(TmaTask *task) {
|
||||||
|
this->AddWork(TmaWorkType::FreeTask, task, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::CancelTask(u32 task_id) {
|
||||||
|
if (this->initialized) {
|
||||||
|
this->task_list.Cancel(task_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::CancelTasks() {
|
||||||
|
if (this->initialized) {
|
||||||
|
this->task_list.CancelAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 TmaServiceManager::GetNextTaskId() {
|
||||||
|
while (true) {
|
||||||
|
u32 id;
|
||||||
|
{
|
||||||
|
/* N only uses 16 bits for the task id. We'll use 24. */
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
id = (this->next_task_id++) & 0xFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->task_list.IsIdFree(id)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connection management. */
|
||||||
|
void TmaServiceManager::Tick() {
|
||||||
|
this->AddWork(TmaWorkType::Tick, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::SetConnection(TmaConnection *conn) {
|
||||||
|
this->connection = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::OnDisconnect() {
|
||||||
|
if (!this->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->GetAsleep()) {
|
||||||
|
this->disconnect_signal.Reset();
|
||||||
|
|
||||||
|
this->AddWork(TmaWorkType::Disconnect, nullptr, nullptr);
|
||||||
|
|
||||||
|
/* TODO: why does N wait with a timeout of zero here? */
|
||||||
|
this->disconnect_signal.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::Sleep() {
|
||||||
|
if (!this->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->GetAsleep()) {
|
||||||
|
this->wake_signal.Reset();
|
||||||
|
this->sleep_signal.Reset();
|
||||||
|
|
||||||
|
/* Tell the work thread to stall, wait for ACK. */
|
||||||
|
this->AddWork(TmaWorkType::Sleep, nullptr, nullptr);
|
||||||
|
this->sleep_signal.Wait();
|
||||||
|
|
||||||
|
this->SetAsleep(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::Wake(TmaConnection *conn) {
|
||||||
|
if (this->connection != nullptr) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (this->GetAsleep()) {
|
||||||
|
this->connection = conn;
|
||||||
|
this->connection->SetWokenUp(true);
|
||||||
|
this->connection->SetServiceManager(this);
|
||||||
|
/* Tell the work thread to resume. */
|
||||||
|
this->wake_signal.Signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TmaServiceManager::GetConnected() const {
|
||||||
|
return this->connection != nullptr && this->connection->IsConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Work thread. */
|
||||||
|
void TmaServiceManager::WorkThread(void *_this) {
|
||||||
|
TmaServiceManager *this_ptr = reinterpret_cast<TmaServiceManager *>(_this);
|
||||||
|
if (!this_ptr->initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
/* Receive a work item. */
|
||||||
|
TmaWorkItem *work_item = nullptr;
|
||||||
|
this_ptr->work_queue.Receive(reinterpret_cast<uintptr_t *>(&work_item));
|
||||||
|
|
||||||
|
if (work_item == nullptr) {
|
||||||
|
/* We're done. */
|
||||||
|
this_ptr->task_list.CancelAll();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (work_item->work_type) {
|
||||||
|
case TmaWorkType::Tick:
|
||||||
|
/* HandleTickWork called unconditionally. */
|
||||||
|
break;
|
||||||
|
case TmaWorkType::NewTask:
|
||||||
|
this_ptr->HandleNewTaskWork(work_item);
|
||||||
|
break;
|
||||||
|
case TmaWorkType::FreeTask:
|
||||||
|
this_ptr->HandleFreeTaskWork(work_item);
|
||||||
|
break;
|
||||||
|
case TmaWorkType::ReceivePacket:
|
||||||
|
this_ptr->HandleReceivePacketWork(work_item);
|
||||||
|
break;
|
||||||
|
case TmaWorkType::Disconnect:
|
||||||
|
this_ptr->HandleDisconnectWork();
|
||||||
|
break;
|
||||||
|
case TmaWorkType::Sleep:
|
||||||
|
this_ptr->HandleSleepWork();
|
||||||
|
break;
|
||||||
|
case TmaWorkType::None:
|
||||||
|
default:
|
||||||
|
std::abort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this_ptr->free_work_queue.Send(reinterpret_cast<uintptr_t>(work_item));
|
||||||
|
this_ptr->HandleTickWork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::HandleNewTaskWork(TmaWorkItem *work_item) {
|
||||||
|
this->task_list.Add(work_item->task);
|
||||||
|
if (this->GetConnected()) {
|
||||||
|
if (work_item->packet != nullptr) {
|
||||||
|
this->SendPacket(work_item->packet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
work_item->task->Cancel();
|
||||||
|
this->FreePacket(work_item->packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::HandleFreeTaskWork(TmaWorkItem *work_item) {
|
||||||
|
delete work_item->task;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::HandleReceivePacketWork(TmaWorkItem *work_item) {
|
||||||
|
ON_SCOPE_EXIT { this->FreePacket(work_item->packet); };
|
||||||
|
|
||||||
|
/* Handle continuation packets. */
|
||||||
|
if (work_item->packet->GetContinuation()) {
|
||||||
|
this->task_list.ReceivePacket(work_item->packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make a new task for the packet. */
|
||||||
|
TmaService *srv = this->GetServiceById(work_item->packet->GetServiceId());
|
||||||
|
if (srv != nullptr) {
|
||||||
|
TmaTask *task = srv->NewTask(work_item->packet);
|
||||||
|
if (task != nullptr) {
|
||||||
|
this->task_list.Add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::HandleTickWork() {
|
||||||
|
if (this->connection == nullptr) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* N does this kind of manual cleanup if send isn't called. */
|
||||||
|
/* It's pretty gross, but in lieu of a better idea... */
|
||||||
|
bool needs_manual_cleanup = true;
|
||||||
|
|
||||||
|
TmaPacket *packet = nullptr;
|
||||||
|
|
||||||
|
while (this->connection != nullptr && this->free_send_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
|
||||||
|
needs_manual_cleanup = false;
|
||||||
|
|
||||||
|
if (this->task_list.SendPacket(this->GetConnected(), packet)) {
|
||||||
|
if (this->SendPacket(packet) != TmaConnResult::Success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->FreePacket(packet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_manual_cleanup) {
|
||||||
|
this->task_list.CleanupDoneTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::HandleDisconnectWork() {
|
||||||
|
this->task_list.CancelAll();
|
||||||
|
this->disconnect_signal.Signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaServiceManager::HandleSleepWork() {
|
||||||
|
/* Put the task list to sleep. */
|
||||||
|
this->task_list.Sleep();
|
||||||
|
|
||||||
|
/* Put services to sleep. */
|
||||||
|
for (auto srv : this->services) {
|
||||||
|
srv->OnSleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signal to main thread that we're sleeping. */
|
||||||
|
this->sleep_signal.Signal();
|
||||||
|
/* Wait for us to wake up. */
|
||||||
|
this->wake_signal.Wait();
|
||||||
|
|
||||||
|
/* We're awake now... */
|
||||||
|
this->SetAsleep(false);
|
||||||
|
|
||||||
|
/* Wake up services. */
|
||||||
|
for (auto srv : this->services) {
|
||||||
|
srv->OnWake();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wake up the task list. */
|
||||||
|
this->task_list.Wake();
|
||||||
|
}
|
||||||
109
stratosphere/tma/source/tma_service_manager.hpp
Normal file
109
stratosphere/tma/source/tma_service_manager.hpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_service_ids.hpp"
|
||||||
|
#include "tma_conn_packet.hpp"
|
||||||
|
#include "tma_task.hpp"
|
||||||
|
#include "tma_service.hpp"
|
||||||
|
#include "tma_task_list.hpp"
|
||||||
|
#include "tma_conn_connection.hpp"
|
||||||
|
|
||||||
|
enum class TmaWorkType : u32 {
|
||||||
|
None,
|
||||||
|
NewTask,
|
||||||
|
FreeTask,
|
||||||
|
ReceivePacket,
|
||||||
|
Tick,
|
||||||
|
Disconnect,
|
||||||
|
Sleep,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TmaWorkItem {
|
||||||
|
TmaTask *task;
|
||||||
|
TmaPacket *packet;
|
||||||
|
TmaWorkType work_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TmaServiceManager {
|
||||||
|
public:
|
||||||
|
static constexpr size_t PacketQueueDepth = 0x8;
|
||||||
|
static constexpr size_t WorkQueueDepth = 0x80;
|
||||||
|
private:
|
||||||
|
HosMutex lock;
|
||||||
|
bool initialized = false;
|
||||||
|
TmaTaskList task_list;
|
||||||
|
HosThread work_thread;
|
||||||
|
std::vector<TmaService *> services;
|
||||||
|
TmaConnection *connection = nullptr;
|
||||||
|
u32 next_task_id = 0;
|
||||||
|
|
||||||
|
/* Work queues. */
|
||||||
|
HosMessageQueue free_send_packet_queue = HosMessageQueue(PacketQueueDepth);
|
||||||
|
HosMessageQueue free_recv_packet_queue = HosMessageQueue(PacketQueueDepth);
|
||||||
|
HosMessageQueue work_queue = HosMessageQueue(WorkQueueDepth);
|
||||||
|
HosMessageQueue free_work_queue = HosMessageQueue(WorkQueueDepth);
|
||||||
|
|
||||||
|
/* Sleep management. */
|
||||||
|
HosSignal disconnect_signal;
|
||||||
|
HosSignal wake_signal;
|
||||||
|
HosSignal sleep_signal;
|
||||||
|
bool asleep = false;
|
||||||
|
private:
|
||||||
|
static void WorkThread(void *arg);
|
||||||
|
void AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet);
|
||||||
|
void HandleNewTaskWork(TmaWorkItem *work_item);
|
||||||
|
void HandleFreeTaskWork(TmaWorkItem *work_item);
|
||||||
|
void HandleReceivePacketWork(TmaWorkItem *work_item);
|
||||||
|
void HandleTickWork();
|
||||||
|
void HandleDisconnectWork();
|
||||||
|
void HandleSleepWork();
|
||||||
|
|
||||||
|
void SetAsleep(bool s) { this->asleep = s; }
|
||||||
|
public:
|
||||||
|
TmaServiceManager();
|
||||||
|
virtual ~TmaServiceManager();
|
||||||
|
void Initialize();
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
|
/* Packet management. */
|
||||||
|
TmaConnResult SendPacket(TmaPacket *packet);
|
||||||
|
void OnReceivePacket(TmaPacket *packet);
|
||||||
|
TmaPacket *AllocateSendPacket();
|
||||||
|
TmaPacket *AllocateRecvPacket();
|
||||||
|
void FreePacket(TmaPacket *packet);
|
||||||
|
|
||||||
|
/* Service/task management. */
|
||||||
|
TmaService *GetServiceById(TmaServiceId id);
|
||||||
|
void AddService(TmaService *service);
|
||||||
|
void AddTask(TmaTask *task, TmaPacket *packet);
|
||||||
|
void FreeTask(TmaTask *task);
|
||||||
|
void CancelTask(u32 task_id);
|
||||||
|
void CancelTasks();
|
||||||
|
u32 GetNextTaskId();
|
||||||
|
|
||||||
|
/* Connection management. */
|
||||||
|
void Tick();
|
||||||
|
void SetConnection(TmaConnection *conn);
|
||||||
|
void OnDisconnect();
|
||||||
|
void Sleep();
|
||||||
|
void Wake(TmaConnection *conn);
|
||||||
|
bool GetAsleep() const { return this->asleep; }
|
||||||
|
bool GetConnected() const;
|
||||||
|
};
|
||||||
234
stratosphere/tma/source/tma_target.cpp
Normal file
234
stratosphere/tma/source/tma_target.cpp
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_connection.hpp"
|
||||||
|
#include "tma_conn_usb_connection.hpp"
|
||||||
|
|
||||||
|
#include "tma_service_manager.hpp"
|
||||||
|
#include "tma_power_manager.hpp"
|
||||||
|
|
||||||
|
#include "tma_target.hpp"
|
||||||
|
|
||||||
|
#include "test/atmosphere_test_service.hpp"
|
||||||
|
#include "settings/settings_service.hpp"
|
||||||
|
#include "target_io/tio_service.hpp"
|
||||||
|
|
||||||
|
struct TmaTargetConfig {
|
||||||
|
char configuration_id1[0x80];
|
||||||
|
char serial_number[0x80];
|
||||||
|
};
|
||||||
|
|
||||||
|
static TmaConnection *g_active_connection = nullptr;
|
||||||
|
static TmaServiceManager *g_service_manager = nullptr;
|
||||||
|
static HosMutex g_connection_event_mutex;
|
||||||
|
static bool g_has_woken_up = false;
|
||||||
|
static bool g_connected_before_sleep = false;
|
||||||
|
static bool g_signal_on_disconnect = false;
|
||||||
|
|
||||||
|
static TmaUsbConnection *g_usb_connection = nullptr;
|
||||||
|
|
||||||
|
static TmaTargetConfig g_target_config = {
|
||||||
|
"Unknown",
|
||||||
|
"SerialNumber",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void RefreshTargetConfig() {
|
||||||
|
setsysInitialize();
|
||||||
|
|
||||||
|
/* TODO: setsysGetConfigurationId1(&g_target_config.configuration_id1); */
|
||||||
|
|
||||||
|
g_target_config.serial_number[0] = 0;
|
||||||
|
setsysGetSerialNumber(g_target_config.serial_number);
|
||||||
|
|
||||||
|
setsysExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializeServices() {
|
||||||
|
g_service_manager->Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FinalizeServices() {
|
||||||
|
g_service_manager->Finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetActiveConnection(TmaConnection *connection) {
|
||||||
|
if (g_active_connection != connection) {
|
||||||
|
if (g_active_connection != nullptr) {
|
||||||
|
FinalizeServices();
|
||||||
|
g_service_manager->SetConnection(nullptr);
|
||||||
|
g_active_connection->Disconnect();
|
||||||
|
g_active_connection = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection != nullptr) {
|
||||||
|
g_active_connection = connection;
|
||||||
|
InitializeServices();
|
||||||
|
g_service_manager->SetConnection(g_active_connection);
|
||||||
|
g_active_connection->SetServiceManager(g_service_manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnConnectionEvent(void *arg, ConnectionEvent evt) {
|
||||||
|
std::scoped_lock<HosMutex> lk(g_connection_event_mutex);
|
||||||
|
|
||||||
|
switch (evt) {
|
||||||
|
case ConnectionEvent::Connected:
|
||||||
|
{
|
||||||
|
bool has_active_connection = false;
|
||||||
|
g_has_woken_up = false;
|
||||||
|
|
||||||
|
if (arg == g_usb_connection) {
|
||||||
|
SetActiveConnection(g_usb_connection);
|
||||||
|
has_active_connection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_active_connection) {
|
||||||
|
/* TODO: Signal connected */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ConnectionEvent::Disconnected:
|
||||||
|
if (g_signal_on_disconnect) {
|
||||||
|
/* TODO: Signal disconnected */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::abort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Wake() {
|
||||||
|
if (g_service_manager->GetAsleep()) {
|
||||||
|
g_has_woken_up = true;
|
||||||
|
|
||||||
|
/* N checks what kind of connection to use here. For now, we only use USB. */
|
||||||
|
TmaUsbConnection::InitializeComms();
|
||||||
|
g_usb_connection = new TmaUsbConnection();
|
||||||
|
g_usb_connection->SetConnectionEventCallback(OnConnectionEvent, g_usb_connection);
|
||||||
|
g_usb_connection->Initialize();
|
||||||
|
g_active_connection = g_usb_connection;
|
||||||
|
|
||||||
|
g_service_manager->Wake(g_active_connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Sleep() {
|
||||||
|
if (!g_service_manager->GetAsleep()) {
|
||||||
|
if (g_active_connection->IsConnected()) {
|
||||||
|
g_connected_before_sleep = true;
|
||||||
|
|
||||||
|
/* TODO: Send a packet saying we're going to sleep. */
|
||||||
|
} else {
|
||||||
|
g_connected_before_sleep = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_service_manager->Sleep();
|
||||||
|
g_service_manager->SetConnection(nullptr);
|
||||||
|
g_active_connection->Disconnect();
|
||||||
|
g_active_connection = nullptr;
|
||||||
|
g_service_manager->CancelTasks();
|
||||||
|
|
||||||
|
if (g_usb_connection != nullptr) {
|
||||||
|
g_usb_connection->Finalize();
|
||||||
|
delete g_usb_connection;
|
||||||
|
g_usb_connection = nullptr;
|
||||||
|
TmaUsbConnection::FinalizeComms();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnPowerManagementEvent(PscPmState state, u32 flags) {
|
||||||
|
switch (state) {
|
||||||
|
case PscPmState_Awake:
|
||||||
|
{
|
||||||
|
Wake();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PscPmState_ReadyAwaken:
|
||||||
|
{
|
||||||
|
if (g_service_manager->GetAsleep()) {
|
||||||
|
Wake();
|
||||||
|
if (g_connected_before_sleep)
|
||||||
|
{
|
||||||
|
/* Try to restore a connection. */
|
||||||
|
bool connected = g_service_manager->GetConnected();
|
||||||
|
|
||||||
|
/* N uses a seven-second timeout, here. */
|
||||||
|
TimeoutHelper timeout_helper(7000000000ULL);
|
||||||
|
while (!connected && !timeout_helper.TimedOut()) {
|
||||||
|
connected = g_service_manager->GetConnected();
|
||||||
|
if (!connected) {
|
||||||
|
/* Sleep for 1ms. */
|
||||||
|
svcSleepThread(1000000ULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected) {
|
||||||
|
/* TODO: Signal disconnected */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PscPmState_ReadySleep:
|
||||||
|
{
|
||||||
|
Sleep();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Don't handle ReadySleepCritical/ReadyAwakenCritical/ReadyShutdown */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTarget::Initialize() {
|
||||||
|
/* Get current thread priority. */
|
||||||
|
u32 cur_prio;
|
||||||
|
if (R_FAILED(svcGetThreadPriority(&cur_prio, CUR_THREAD_HANDLE))) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_active_connection = nullptr;
|
||||||
|
g_service_manager = new TmaServiceManager();
|
||||||
|
/* TODO: Make this better. */
|
||||||
|
g_service_manager->AddService(new AtmosphereTestService(g_service_manager));
|
||||||
|
g_service_manager->AddService(new SettingsService(g_service_manager));
|
||||||
|
g_service_manager->AddService(new TIOService(g_service_manager));
|
||||||
|
|
||||||
|
RefreshTargetConfig();
|
||||||
|
|
||||||
|
/* N checks what kind of connection to use here. For now, we only use USB. */
|
||||||
|
TmaUsbConnection::InitializeComms();
|
||||||
|
g_usb_connection = new TmaUsbConnection();
|
||||||
|
g_usb_connection->SetConnectionEventCallback(OnConnectionEvent, g_usb_connection);
|
||||||
|
g_usb_connection->Initialize();
|
||||||
|
SetActiveConnection(g_usb_connection);
|
||||||
|
|
||||||
|
/* TODO: Initialize connection events */
|
||||||
|
|
||||||
|
/* TODO: Initialize IPC services */
|
||||||
|
|
||||||
|
TmaPowerManager::Initialize(OnPowerManagementEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTarget::Finalize() {
|
||||||
|
/* TODO: Is implementing this actually worthwhile? It will never be called in practice... */
|
||||||
|
}
|
||||||
25
stratosphere/tma/source/tma_target.hpp
Normal file
25
stratosphere/tma/source/tma_target.hpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
class TmaTarget {
|
||||||
|
public:
|
||||||
|
static void Initialize();
|
||||||
|
static void Finalize();
|
||||||
|
};
|
||||||
52
stratosphere/tma/source/tma_task.cpp
Normal file
52
stratosphere/tma/source/tma_task.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_task.hpp"
|
||||||
|
#include "tma_service_manager.hpp"
|
||||||
|
|
||||||
|
void TmaTask::SetNeedsPackets(bool n) {
|
||||||
|
this->needs_packets = n;
|
||||||
|
this->manager->Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaPacket *TmaTask::AllocateSendPacket(bool continuation) {
|
||||||
|
auto packet = this->manager->AllocateSendPacket();
|
||||||
|
packet->SetServiceId(this->service_id);
|
||||||
|
packet->SetTaskId(this->task_id);
|
||||||
|
packet->SetCommand(this->command);
|
||||||
|
packet->SetContinuation(continuation);
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TmaTask::FreePacket(TmaPacket *packet) {
|
||||||
|
this->manager->FreePacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTask::Complete() {
|
||||||
|
SetNeedsPackets(false);
|
||||||
|
this->state = TmaTaskState::Complete;
|
||||||
|
this->manager->Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTask::Cancel() {
|
||||||
|
SetNeedsPackets(false);
|
||||||
|
this->state = TmaTaskState::Canceled;
|
||||||
|
this->manager->Tick();
|
||||||
|
}
|
||||||
80
stratosphere/tma/source/tma_task.hpp
Normal file
80
stratosphere/tma/source/tma_task.hpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_service_ids.hpp"
|
||||||
|
#include "tma_conn_packet.hpp"
|
||||||
|
|
||||||
|
enum class TmaTaskState : u32 {
|
||||||
|
InProgress,
|
||||||
|
Complete,
|
||||||
|
Canceled,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TmaServiceManager;
|
||||||
|
|
||||||
|
class TmaTask {
|
||||||
|
public:
|
||||||
|
static constexpr u32 MaxPriority = 15;
|
||||||
|
static constexpr u32 NumPriorities = MaxPriority + 1;
|
||||||
|
protected:
|
||||||
|
TmaServiceManager *manager;
|
||||||
|
u32 priority = 0;
|
||||||
|
TmaServiceId service_id = TmaServiceId::Invalid;
|
||||||
|
u32 task_id = 0;
|
||||||
|
u32 command = 0;
|
||||||
|
TmaTaskState state = TmaTaskState::InProgress;
|
||||||
|
HosSignal signal;
|
||||||
|
bool owned_by_task_list = true;
|
||||||
|
bool sleep_allowed = true;
|
||||||
|
bool needs_packets = false;
|
||||||
|
public:
|
||||||
|
TmaTask(TmaServiceManager *m) : manager(m) { }
|
||||||
|
virtual ~TmaTask() { }
|
||||||
|
|
||||||
|
u32 GetPriority() const { return this->priority; }
|
||||||
|
TmaServiceId GetServiceId() const { return this->service_id; }
|
||||||
|
u32 GetTaskId() const { return this->task_id; }
|
||||||
|
u32 GetCommand() const { return this->command; }
|
||||||
|
TmaTaskState GetState() const { return this->state; }
|
||||||
|
bool GetOwnedByTaskList() const { return this->owned_by_task_list; }
|
||||||
|
bool GetSleepAllowed() const { return this->sleep_allowed; }
|
||||||
|
bool GetNeedsPackets() const { return this->needs_packets; }
|
||||||
|
|
||||||
|
void SetPriority(u32 p) { this->priority = p; }
|
||||||
|
void SetServiceId(TmaServiceId s) { this->service_id = s; }
|
||||||
|
void SetTaskId(u32 i) { this->task_id = i; }
|
||||||
|
void SetCommand(u32 c) { this->command = c; }
|
||||||
|
void SetOwnedByTaskList(bool o) { this->owned_by_task_list = o; }
|
||||||
|
void SetSleepAllowed(bool a) { this->sleep_allowed = a; }
|
||||||
|
void SetNeedsPackets(bool n);
|
||||||
|
|
||||||
|
void Signal() { this->signal.Signal(); }
|
||||||
|
void ResetSignal() { this->signal.Reset(); }
|
||||||
|
|
||||||
|
TmaPacket *AllocateSendPacket(bool continuation = true);
|
||||||
|
void FreePacket(TmaPacket *packet);
|
||||||
|
|
||||||
|
void Complete();
|
||||||
|
void Cancel();
|
||||||
|
|
||||||
|
virtual void OnStart(TmaPacket *packet) = 0;
|
||||||
|
virtual void OnReceivePacket(TmaPacket *packet) = 0;
|
||||||
|
virtual void OnSendPacket(TmaPacket *packet) = 0;
|
||||||
|
};
|
||||||
218
stratosphere/tma/source/tma_task_list.cpp
Normal file
218
stratosphere/tma/source/tma_task_list.cpp
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include "tma_task_list.hpp"
|
||||||
|
|
||||||
|
TmaTask *TmaTaskList::GetById(u32 task_id) const {
|
||||||
|
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||||
|
for (auto task : this->tasks[i]) {
|
||||||
|
if (task->GetTaskId() == task_id) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 TmaTaskList::GetNumTasks() const {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
u32 count = 0;
|
||||||
|
|
||||||
|
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||||
|
count += this->tasks[i].size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 TmaTaskList::GetNumSleepingTasks() const {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
u32 count = 0;
|
||||||
|
|
||||||
|
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||||
|
count += this->sleeping_tasks[i].size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TmaTaskList::IsIdFree(u32 task_id) const {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
return GetById(task_id) == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TmaTaskList::SendPacket(bool connected, TmaPacket *packet) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
TmaTask *target_task = nullptr;
|
||||||
|
|
||||||
|
/* This loop both finds a target task, and cleans up finished tasks. */
|
||||||
|
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||||
|
auto it = this->tasks[i].begin();
|
||||||
|
while (it != this->tasks[i].end()) {
|
||||||
|
auto task = *it;
|
||||||
|
switch (task->GetState()) {
|
||||||
|
case TmaTaskState::InProgress:
|
||||||
|
it++;
|
||||||
|
if (target_task == nullptr && task->GetNeedsPackets()) {
|
||||||
|
if (connected || IsMetaService(task->GetServiceId())) {
|
||||||
|
target_task = task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TmaTaskState::Complete:
|
||||||
|
case TmaTaskState::Canceled:
|
||||||
|
it = this->tasks[i].erase(it);
|
||||||
|
if (task->GetOwnedByTaskList()) {
|
||||||
|
delete task;
|
||||||
|
} else {
|
||||||
|
task->Signal();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* TODO: Panic to fatal? */
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_task) {
|
||||||
|
/* Setup packet. */
|
||||||
|
packet->SetContinuation(true);
|
||||||
|
packet->SetServiceId(target_task->GetServiceId());
|
||||||
|
packet->SetTaskId(target_task->GetTaskId());
|
||||||
|
packet->SetCommand(target_task->GetCommand());
|
||||||
|
packet->ClearOffset();
|
||||||
|
|
||||||
|
/* Actually handle packet send. */
|
||||||
|
target_task->OnSendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target_task != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TmaTaskList::ReceivePacket(TmaPacket *packet) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
auto task = this->GetById(packet->GetTaskId());
|
||||||
|
if (task != nullptr) {
|
||||||
|
task->OnReceivePacket(packet);
|
||||||
|
}
|
||||||
|
return task != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TmaTaskList::CleanupDoneTasks() {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
/* Clean up all tasks in Complete/Canceled state. */
|
||||||
|
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||||
|
auto it = this->tasks[i].begin();
|
||||||
|
while (it != this->tasks[i].end()) {
|
||||||
|
auto task = *it;
|
||||||
|
switch (task->GetState()) {
|
||||||
|
case TmaTaskState::InProgress:
|
||||||
|
it++;
|
||||||
|
break;
|
||||||
|
case TmaTaskState::Complete:
|
||||||
|
case TmaTaskState::Canceled:
|
||||||
|
it = this->tasks[i].erase(it);
|
||||||
|
if (task->GetOwnedByTaskList()) {
|
||||||
|
delete task;
|
||||||
|
} else {
|
||||||
|
task->Signal();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* TODO: Panic to fatal? */
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTaskList::Add(TmaTask *task) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
this->tasks[task->GetPriority()].push_back(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTaskList::Remove(TmaTask *task) {
|
||||||
|
const auto priority = task->GetPriority();
|
||||||
|
|
||||||
|
/* Nintendo iterates over all lists instead of just the correct one. */
|
||||||
|
/* TODO: Is there actually any reason to do that? */
|
||||||
|
auto ind = std::find(this->tasks[priority].begin(), this->tasks[priority].end(), task);
|
||||||
|
if (ind != this->tasks[priority].end()) {
|
||||||
|
this->tasks[priority].erase(ind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Panic to fatal? */
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTaskList::Cancel(u32 task_id) {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
auto task = this->GetById(task_id);
|
||||||
|
if (task != nullptr) {
|
||||||
|
task->Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTaskList::CancelAll() {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||||
|
for (auto task : this->tasks[i]) {
|
||||||
|
task->Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTaskList::Sleep() {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||||
|
auto it = this->tasks[i].begin();
|
||||||
|
while (it != this->tasks[i].end()) {
|
||||||
|
auto task = *it;
|
||||||
|
if (task->GetSleepAllowed()) {
|
||||||
|
it = this->tasks[i].erase(it);
|
||||||
|
this->sleeping_tasks[i].push_back(task);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaTaskList::Wake() {
|
||||||
|
std::scoped_lock<HosMutex> lk(this->lock);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||||
|
auto it = this->sleeping_tasks[i].begin();
|
||||||
|
while (it != this->sleeping_tasks[i].end()) {
|
||||||
|
auto task = *it;
|
||||||
|
it = this->sleeping_tasks[i].erase(it);
|
||||||
|
this->tasks[i].push_back(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
stratosphere/tma/source/tma_task_list.hpp
Normal file
50
stratosphere/tma/source/tma_task_list.hpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "tma_conn_service_ids.hpp"
|
||||||
|
#include "tma_task.hpp"
|
||||||
|
|
||||||
|
class TmaTaskList {
|
||||||
|
private:
|
||||||
|
mutable HosMutex lock;
|
||||||
|
std::vector<TmaTask *> tasks[TmaTask::NumPriorities];
|
||||||
|
std::vector<TmaTask *> sleeping_tasks[TmaTask::NumPriorities];
|
||||||
|
private:
|
||||||
|
void Remove(TmaTask *task);
|
||||||
|
TmaTask *GetById(u32 task_id) const;
|
||||||
|
public:
|
||||||
|
TmaTaskList() { }
|
||||||
|
virtual ~TmaTaskList() { }
|
||||||
|
|
||||||
|
u32 GetNumTasks() const;
|
||||||
|
u32 GetNumSleepingTasks() const;
|
||||||
|
bool IsIdFree(u32 task_id) const;
|
||||||
|
|
||||||
|
bool SendPacket(bool connected, TmaPacket *packet);
|
||||||
|
bool ReceivePacket(TmaPacket *packet);
|
||||||
|
void CleanupDoneTasks();
|
||||||
|
void Add(TmaTask *task);
|
||||||
|
void Cancel(u32 task_id);
|
||||||
|
void CancelAll();
|
||||||
|
|
||||||
|
void Sleep();
|
||||||
|
void Wake();
|
||||||
|
};
|
||||||
484
stratosphere/tma/source/tma_usb_comms.cpp
Normal file
484
stratosphere/tma/source/tma_usb_comms.cpp
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tma_usb_comms.hpp"
|
||||||
|
|
||||||
|
/* TODO: Is this actually allowed? */
|
||||||
|
#define ATMOSPHERE_INTERFACE_PROTOCOL 0xFC
|
||||||
|
|
||||||
|
static std::atomic<bool> g_initialized = false;
|
||||||
|
static UsbDsInterface *g_interface;
|
||||||
|
static UsbDsEndpoint *g_endpoint_in, *g_endpoint_out;
|
||||||
|
|
||||||
|
/* USB State Change Tracking. */
|
||||||
|
static HosThread g_state_change_thread;
|
||||||
|
static WaitableManagerBase *g_state_change_manager = nullptr;
|
||||||
|
static void (*g_state_change_callback)(void *arg, u32 state);
|
||||||
|
static void *g_state_change_arg;
|
||||||
|
|
||||||
|
/* USB Send/Receive mutexes. */
|
||||||
|
static HosMutex g_send_mutex;
|
||||||
|
static HosMutex g_recv_mutex;
|
||||||
|
|
||||||
|
/* Static arrays to do USB DMA into. */
|
||||||
|
static constexpr size_t DmaBufferAlign = 0x1000;
|
||||||
|
static constexpr size_t HeaderBufferSize = DmaBufferAlign;
|
||||||
|
static constexpr size_t DataBufferSize = 0x18000;
|
||||||
|
static __attribute__((aligned(DmaBufferAlign))) u8 g_header_buffer[HeaderBufferSize];
|
||||||
|
static __attribute__((aligned(DmaBufferAlign))) u8 g_recv_data_buf[DataBufferSize];
|
||||||
|
static __attribute__((aligned(DmaBufferAlign))) u8 g_send_data_buf[DataBufferSize];
|
||||||
|
|
||||||
|
/* Taken from libnx usb comms. */
|
||||||
|
static Result _usbCommsInterfaceInit1x()
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
struct usb_interface_descriptor interface_descriptor = {
|
||||||
|
.bLength = USB_DT_INTERFACE_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_INTERFACE,
|
||||||
|
.bInterfaceNumber = 4,
|
||||||
|
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||||
|
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
|
||||||
|
.bInterfaceProtocol = ATMOSPHERE_INTERFACE_PROTOCOL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||||
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_ENDPOINT,
|
||||||
|
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||||
|
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||||
|
.wMaxPacketSize = 0x200,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||||
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_ENDPOINT,
|
||||||
|
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||||
|
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||||
|
.wMaxPacketSize = 0x200,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
//Setup interface.
|
||||||
|
rc = usbDsGetDsInterface(&g_interface, &interface_descriptor, "usb");
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
//Setup endpoints.
|
||||||
|
rc = usbDsInterface_GetDsEndpoint(g_interface, &g_endpoint_in, &endpoint_descriptor_in);//device->host
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
rc = usbDsInterface_GetDsEndpoint(g_interface, &g_endpoint_out, &endpoint_descriptor_out);//host->device
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result _usbCommsInterfaceInit5x() {
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
u8 iManufacturer, iProduct, iSerialNumber;
|
||||||
|
static const u16 supported_langs[1] = {0x0409};
|
||||||
|
// Send language descriptor
|
||||||
|
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16));
|
||||||
|
// Send manufacturer
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Nintendo");
|
||||||
|
// Send product
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "Nintendo Switch");
|
||||||
|
// Send serial number
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber");
|
||||||
|
|
||||||
|
// Send device descriptors
|
||||||
|
struct usb_device_descriptor device_descriptor = {
|
||||||
|
.bLength = USB_DT_DEVICE_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_DEVICE,
|
||||||
|
.bcdUSB = 0x0110,
|
||||||
|
.bDeviceClass = 0x00,
|
||||||
|
.bDeviceSubClass = 0x00,
|
||||||
|
.bDeviceProtocol = 0x00,
|
||||||
|
.bMaxPacketSize0 = 0x40,
|
||||||
|
.idVendor = 0x057e,
|
||||||
|
.idProduct = 0x3000,
|
||||||
|
.bcdDevice = 0x0100,
|
||||||
|
.iManufacturer = iManufacturer,
|
||||||
|
.iProduct = iProduct,
|
||||||
|
.iSerialNumber = iSerialNumber,
|
||||||
|
.bNumConfigurations = 0x01
|
||||||
|
};
|
||||||
|
// Full Speed is USB 1.1
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor);
|
||||||
|
|
||||||
|
// High Speed is USB 2.0
|
||||||
|
device_descriptor.bcdUSB = 0x0200;
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor);
|
||||||
|
|
||||||
|
// Super Speed is USB 3.0
|
||||||
|
device_descriptor.bcdUSB = 0x0300;
|
||||||
|
// Upgrade packet size to 512
|
||||||
|
device_descriptor.bMaxPacketSize0 = 0x09;
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor);
|
||||||
|
|
||||||
|
// Define Binary Object Store
|
||||||
|
u8 bos[0x16] = {
|
||||||
|
0x05, // .bLength
|
||||||
|
USB_DT_BOS, // .bDescriptorType
|
||||||
|
0x16, 0x00, // .wTotalLength
|
||||||
|
0x02, // .bNumDeviceCaps
|
||||||
|
|
||||||
|
// USB 2.0
|
||||||
|
0x07, // .bLength
|
||||||
|
USB_DT_DEVICE_CAPABILITY, // .bDescriptorType
|
||||||
|
0x02, // .bDevCapabilityType
|
||||||
|
0x02, 0x00, 0x00, 0x00, // dev_capability_data
|
||||||
|
|
||||||
|
// USB 3.0
|
||||||
|
0x0A, // .bLength
|
||||||
|
USB_DT_DEVICE_CAPABILITY, // .bDescriptorType
|
||||||
|
0x03, // .bDevCapabilityType
|
||||||
|
0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos));
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
struct usb_interface_descriptor interface_descriptor = {
|
||||||
|
.bLength = USB_DT_INTERFACE_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_INTERFACE,
|
||||||
|
.bInterfaceNumber = 4,
|
||||||
|
.bNumEndpoints = 2,
|
||||||
|
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||||
|
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
|
||||||
|
.bInterfaceProtocol = ATMOSPHERE_INTERFACE_PROTOCOL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||||
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_ENDPOINT,
|
||||||
|
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||||
|
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||||
|
.wMaxPacketSize = 0x40,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||||
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||||
|
.bDescriptorType = USB_DT_ENDPOINT,
|
||||||
|
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||||
|
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||||
|
.wMaxPacketSize = 0x40,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
|
||||||
|
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||||
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||||
|
.bMaxBurst = 0x0F,
|
||||||
|
.bmAttributes = 0x00,
|
||||||
|
.wBytesPerInterval = 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc = usbDsRegisterInterface(&g_interface);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
interface_descriptor.bInterfaceNumber = g_interface->interface_index;
|
||||||
|
endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||||
|
endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||||
|
|
||||||
|
// Full Speed Config
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
// High Speed Config
|
||||||
|
endpoint_descriptor_in.wMaxPacketSize = 0x200;
|
||||||
|
endpoint_descriptor_out.wMaxPacketSize = 0x200;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
// Super Speed Config
|
||||||
|
endpoint_descriptor_in.wMaxPacketSize = 0x400;
|
||||||
|
endpoint_descriptor_out.wMaxPacketSize = 0x400;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
//Setup endpoints.
|
||||||
|
rc = usbDsInterface_RegisterEndpoint(g_interface, &g_endpoint_in, endpoint_descriptor_in.bEndpointAddress);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
rc = usbDsInterface_RegisterEndpoint(g_interface, &g_endpoint_out, endpoint_descriptor_out.bEndpointAddress);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Actual function implementations. */
|
||||||
|
TmaConnResult TmaUsbComms::Initialize() {
|
||||||
|
TmaConnResult res = TmaConnResult::Success;
|
||||||
|
|
||||||
|
if (g_initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = usbDsInitialize();
|
||||||
|
|
||||||
|
/* Perform interface setup. */
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||||
|
rc = _usbCommsInterfaceInit5x();
|
||||||
|
} else {
|
||||||
|
rc = _usbCommsInterfaceInit1x();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start the state change thread. */
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
rc = g_state_change_thread.Initialize(&TmaUsbComms::UsbStateChangeThreadFunc, nullptr, 0x4000, 38);
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
rc = g_state_change_thread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable USB communication. */
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
rc = usbDsInterface_EnableInterface(g_interface);
|
||||||
|
}
|
||||||
|
if (R_SUCCEEDED(rc) && GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||||
|
rc = usbDsEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
/* TODO: Should I not abort here? */
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_initialized = true;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbComms::Finalize() {
|
||||||
|
Result rc = 0;
|
||||||
|
/* We must have initialized before calling finalize. */
|
||||||
|
if (!g_initialized) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kill the state change thread. */
|
||||||
|
g_state_change_manager->RequestStop();
|
||||||
|
if (R_FAILED(g_state_change_thread.Join())) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
CancelComms();
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
usbDsExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state_change_callback = nullptr;
|
||||||
|
g_interface = nullptr;
|
||||||
|
g_endpoint_in = nullptr;
|
||||||
|
g_endpoint_out = nullptr;
|
||||||
|
g_initialized = false;
|
||||||
|
|
||||||
|
return R_SUCCEEDED(rc) ? TmaConnResult::Success : TmaConnResult::ConnectionFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbComms::CancelComms() {
|
||||||
|
if (!g_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
usbDsEndpoint_Cancel(g_endpoint_in);
|
||||||
|
usbDsEndpoint_Cancel(g_endpoint_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbComms::SetStateChangeCallback(void (*callback)(void *, u32), void *arg) {
|
||||||
|
g_state_change_callback = callback;
|
||||||
|
g_state_change_arg = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result TmaUsbComms::UsbXfer(UsbDsEndpoint *ep, size_t *out_xferd, void *buf, size_t size) {
|
||||||
|
Result rc = 0;
|
||||||
|
u32 urbId = 0;
|
||||||
|
u32 total_xferd = 0;
|
||||||
|
UsbDsReportData reportdata;
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
/* Start transfer. */
|
||||||
|
rc = usbDsEndpoint_PostBufferAsync(ep, buf, size, &urbId);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
/* Wait for transfer to complete. */
|
||||||
|
eventWait(&ep->CompletionEvent, U64_MAX);
|
||||||
|
eventClear(&ep->CompletionEvent);
|
||||||
|
|
||||||
|
rc = usbDsEndpoint_GetReportData(ep, &reportdata);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
|
||||||
|
rc = usbDsParseReportData(&reportdata, urbId, NULL, &total_xferd);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_xferd) *out_xferd = total_xferd;
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbComms::ReceivePacket(TmaPacket *packet) {
|
||||||
|
std::scoped_lock<HosMutex> lk{g_recv_mutex};
|
||||||
|
TmaConnResult res = TmaConnResult::Success;
|
||||||
|
|
||||||
|
if (!g_initialized || packet == nullptr) {
|
||||||
|
return TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the header. */
|
||||||
|
size_t read = 0;
|
||||||
|
if (R_SUCCEEDED(UsbXfer(g_endpoint_out, &read, g_header_buffer, sizeof(TmaPacket::Header)))) {
|
||||||
|
packet->CopyHeaderFrom(reinterpret_cast<TmaPacket::Header *>(g_header_buffer));
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate the read header data. */
|
||||||
|
if (res == TmaConnResult::Success) {
|
||||||
|
if (read != sizeof(TmaPacket::Header) || !packet->IsHeaderValid()) {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the body! */
|
||||||
|
if (res == TmaConnResult::Success) {
|
||||||
|
const u32 body_len = packet->GetBodyLength();
|
||||||
|
if (0 < body_len) {
|
||||||
|
if (body_len <= sizeof(g_recv_data_buf)) {
|
||||||
|
if (R_SUCCEEDED(UsbXfer(g_endpoint_out, &read, g_recv_data_buf, body_len))) {
|
||||||
|
if (read == body_len) {
|
||||||
|
res = packet->CopyBodyFrom(g_recv_data_buf, body_len);
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate the body. */
|
||||||
|
if (res == TmaConnResult::Success) {
|
||||||
|
if (!packet->IsBodyValid()) {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == TmaConnResult::Success) {
|
||||||
|
packet->ClearOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
TmaConnResult TmaUsbComms::SendPacket(TmaPacket *packet) {
|
||||||
|
std::scoped_lock<HosMutex> lk{g_send_mutex};
|
||||||
|
TmaConnResult res = TmaConnResult::Success;
|
||||||
|
|
||||||
|
if (!g_initialized || packet == nullptr) {
|
||||||
|
return TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure our packets have the correct checksums. */
|
||||||
|
packet->SetChecksums();
|
||||||
|
|
||||||
|
/* Send the packet. */
|
||||||
|
size_t written = 0;
|
||||||
|
const u32 body_len = packet->GetBodyLength();
|
||||||
|
if (body_len <= sizeof(g_send_data_buf)) {
|
||||||
|
/* Copy header to send buffer. */
|
||||||
|
packet->CopyHeaderTo(g_send_data_buf);
|
||||||
|
|
||||||
|
/* Send the packet header. */
|
||||||
|
if (R_SUCCEEDED(UsbXfer(g_endpoint_in, &written, g_send_data_buf, sizeof(TmaPacket::Header)))) {
|
||||||
|
if (written == sizeof(TmaPacket::Header)) {
|
||||||
|
res = TmaConnResult::Success;
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == TmaConnResult::Success && 0 < body_len) {
|
||||||
|
/* Copy body to send buffer. */
|
||||||
|
packet->CopyBodyTo(g_send_data_buf);
|
||||||
|
|
||||||
|
|
||||||
|
/* Send the packet body. */
|
||||||
|
if (R_SUCCEEDED(UsbXfer(g_endpoint_in, &written, g_send_data_buf, body_len))) {
|
||||||
|
if (written == body_len) {
|
||||||
|
res = TmaConnResult::Success;
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = TmaConnResult::GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TmaUsbComms::UsbStateChangeThreadFunc(void *arg) {
|
||||||
|
u32 state;
|
||||||
|
g_state_change_manager = new WaitableManager(1);
|
||||||
|
|
||||||
|
auto state_change_event = LoadReadOnlySystemEvent(usbDsGetStateChangeEvent()->revent, [&](u64 timeout) {
|
||||||
|
if (R_SUCCEEDED(usbDsGetState(&state))) {
|
||||||
|
if (g_state_change_callback != nullptr) {
|
||||||
|
g_state_change_callback(g_state_change_arg, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
g_state_change_manager->AddWaitable(state_change_event);
|
||||||
|
g_state_change_manager->Process();
|
||||||
|
|
||||||
|
/* If we get here, we're exiting. */
|
||||||
|
state_change_event->r_h = 0;
|
||||||
|
delete g_state_change_manager;
|
||||||
|
g_state_change_manager = nullptr;
|
||||||
|
|
||||||
|
svcExitThread();
|
||||||
|
}
|
||||||
36
stratosphere/tma/source/tma_usb_comms.hpp
Normal file
36
stratosphere/tma/source/tma_usb_comms.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
#include "tma_conn_result.hpp"
|
||||||
|
#include "tma_conn_packet.hpp"
|
||||||
|
|
||||||
|
class TmaUsbComms {
|
||||||
|
private:
|
||||||
|
static void UsbStateChangeThreadFunc(void *arg);
|
||||||
|
static Result UsbXfer(UsbDsEndpoint *ep, size_t *out_xferd, void *buf, size_t size);
|
||||||
|
public:
|
||||||
|
static TmaConnResult Initialize();
|
||||||
|
static TmaConnResult Finalize();
|
||||||
|
static void CancelComms();
|
||||||
|
static TmaConnResult ReceivePacket(TmaPacket *packet);
|
||||||
|
static TmaConnResult SendPacket(TmaPacket *packet);
|
||||||
|
|
||||||
|
static void SetStateChangeCallback(void (*callback)(void *, u32), void *arg);
|
||||||
|
};
|
||||||
147
stratosphere/tma/tma.json
Normal file
147
stratosphere/tma/tma.json
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
{
|
||||||
|
"name": "tma",
|
||||||
|
"title_id": "0x0100000000000007",
|
||||||
|
"title_id_range_min": "0x0100000000000007",
|
||||||
|
"title_id_range_max": "0x0100000000000007",
|
||||||
|
"main_thread_stack_size": "0x00004000",
|
||||||
|
"main_thread_priority": 38,
|
||||||
|
"default_cpu_id": 3,
|
||||||
|
"process_category": 0,
|
||||||
|
"is_retail": true,
|
||||||
|
"pool_partition": 2,
|
||||||
|
"is_64_bit": true,
|
||||||
|
"address_space_type": 1,
|
||||||
|
"filesystem_access": {
|
||||||
|
"permissions": "0xFFFFFFFFFFFFFFFF"
|
||||||
|
},
|
||||||
|
"service_access": [
|
||||||
|
"bsd:s",
|
||||||
|
"bsdcfg",
|
||||||
|
"dmnt:-",
|
||||||
|
"fatal:u",
|
||||||
|
"i2c",
|
||||||
|
"pcie",
|
||||||
|
"psc:m",
|
||||||
|
"set:cal",
|
||||||
|
"set:fd",
|
||||||
|
"set:sys",
|
||||||
|
"sfdnsres",
|
||||||
|
"spl:",
|
||||||
|
"usb:ds"
|
||||||
|
],
|
||||||
|
"service_host": [
|
||||||
|
"file_io",
|
||||||
|
"gds",
|
||||||
|
"htc",
|
||||||
|
"htcs",
|
||||||
|
"tma_log",
|
||||||
|
"tmagent"
|
||||||
|
],
|
||||||
|
"kernel_capabilities": [{
|
||||||
|
"type": "kernel_flags",
|
||||||
|
"value": {
|
||||||
|
"highest_thread_priority": 63,
|
||||||
|
"lowest_thread_priority": 24,
|
||||||
|
"lowest_cpu_id": 0,
|
||||||
|
"highest_cpu_id": 3
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "syscalls",
|
||||||
|
"value": {
|
||||||
|
"svcSetHeapSize": "0x01",
|
||||||
|
"svcSetMemoryPermission": "0x02",
|
||||||
|
"svcSetMemoryAttribute": "0x03",
|
||||||
|
"svcMapMemory": "0x04",
|
||||||
|
"svcUnmapMemory": "0x05",
|
||||||
|
"svcQueryMemory": "0x06",
|
||||||
|
"svcExitProcess": "0x07",
|
||||||
|
"svcCreateThread": "0x08",
|
||||||
|
"svcStartThread": "0x09",
|
||||||
|
"svcExitThread": "0x0a",
|
||||||
|
"svcSleepThread": "0x0b",
|
||||||
|
"svcGetThreadPriority": "0x0c",
|
||||||
|
"svcSetThreadPriority": "0x0d",
|
||||||
|
"svcGetThreadCoreMask": "0x0e",
|
||||||
|
"svcSetThreadCoreMask": "0x0f",
|
||||||
|
"svcGetCurrentProcessorNumber": "0x10",
|
||||||
|
"svcSignalEvent": "0x11",
|
||||||
|
"svcClearEvent": "0x12",
|
||||||
|
"svcMapSharedMemory": "0x13",
|
||||||
|
"svcUnmapSharedMemory": "0x14",
|
||||||
|
"svcCreateTransferMemory": "0x15",
|
||||||
|
"svcCloseHandle": "0x16",
|
||||||
|
"svcResetSignal": "0x17",
|
||||||
|
"svcWaitSynchronization": "0x18",
|
||||||
|
"svcCancelSynchronization": "0x19",
|
||||||
|
"svcArbitrateLock": "0x1a",
|
||||||
|
"svcArbitrateUnlock": "0x1b",
|
||||||
|
"svcWaitProcessWideKeyAtomic": "0x1c",
|
||||||
|
"svcSignalProcessWideKey": "0x1d",
|
||||||
|
"svcGetSystemTick": "0x1e",
|
||||||
|
"svcConnectToNamedPort": "0x1f",
|
||||||
|
"svcSendSyncRequestLight": "0x20",
|
||||||
|
"svcSendSyncRequest": "0x21",
|
||||||
|
"svcSendSyncRequestWithUserBuffer": "0x22",
|
||||||
|
"svcSendAsyncRequestWithUserBuffer": "0x23",
|
||||||
|
"svcGetProcessId": "0x24",
|
||||||
|
"svcGetThreadId": "0x25",
|
||||||
|
"svcBreak": "0x26",
|
||||||
|
"svcOutputDebugString": "0x27",
|
||||||
|
"svcReturnFromException": "0x28",
|
||||||
|
"svcGetInfo": "0x29",
|
||||||
|
"svcWaitForAddress": "0x34",
|
||||||
|
"svcSignalToAddress": "0x35",
|
||||||
|
"svcCreateSession": "0x40",
|
||||||
|
"svcAcceptSession": "0x41",
|
||||||
|
"svcReplyAndReceiveLight": "0x42",
|
||||||
|
"svcReplyAndReceive": "0x43",
|
||||||
|
"svcReplyAndReceiveWithUserBuffer": "0x44",
|
||||||
|
"svcCreateEvent": "0x45",
|
||||||
|
"svcReadWriteRegister": "0x4E",
|
||||||
|
"svcCreateSharedMemory": "0x50",
|
||||||
|
"svcMapTransferMemory": "0x51",
|
||||||
|
"svcUnmapTransferMemory": "0x52",
|
||||||
|
"svcCreateInterruptEvent": "0x53",
|
||||||
|
"svcQueryIoMapping": "0x55",
|
||||||
|
"svcCreateDeviceAddressSpace": "0x56",
|
||||||
|
"svcAttachDeviceAddressSpace": "0x57",
|
||||||
|
"svcDetachDeviceAddressSpace": "0x58",
|
||||||
|
"svcMapDeviceAddressSpaceByForce": "0x59",
|
||||||
|
"svcMapDeviceAddressSpaceAligned": "0x5a",
|
||||||
|
"svcMapDeviceAddressSpace": "0x5b",
|
||||||
|
"svcUnmapDeviceAddressSpace": "0x5c",
|
||||||
|
"svcInvalidateProcessDataCache": "0x5d",
|
||||||
|
"svcStoreProcessDataCache": "0x5e",
|
||||||
|
"svcFlushProcessDataCache": "0x5f",
|
||||||
|
"svcCallSecureMonitor": "0x7f"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "map",
|
||||||
|
"value": {
|
||||||
|
"address": "0x02000000",
|
||||||
|
"is_ro": false,
|
||||||
|
"size": "0x05000000",
|
||||||
|
"is_io": true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "map",
|
||||||
|
"value": {
|
||||||
|
"address": "0x10000000",
|
||||||
|
"is_ro": false,
|
||||||
|
"size": "0x04000000",
|
||||||
|
"is_io": true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "irq_pair",
|
||||||
|
"value": [130, null]
|
||||||
|
}, {
|
||||||
|
"type": "irq_pair",
|
||||||
|
"value": [131, 132]
|
||||||
|
}, {
|
||||||
|
"type": "min_kernel_version",
|
||||||
|
"value": "0x0030"
|
||||||
|
}, {
|
||||||
|
"type": "handle_table_size",
|
||||||
|
"value": 256
|
||||||
|
}]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user