Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d27dab6ab | ||
|
|
325c71b1b4 | ||
|
|
f9c9c1048e | ||
|
|
4ccb39a228 | ||
|
|
ad8acaefec | ||
|
|
106ae81614 | ||
|
|
93745dc40c | ||
|
|
3316820f86 | ||
|
|
f4950ff26e | ||
|
|
20ba6432b9 | ||
|
|
a611027eeb | ||
|
|
1a82b407a4 | ||
|
|
dc1db0dc72 | ||
|
|
047f3b653e | ||
|
|
79244078a6 | ||
|
|
fce2099d7d | ||
|
|
22d4de27f1 | ||
|
|
9d5ca47ac8 | ||
|
|
c1588d0300 | ||
|
|
b62014554c | ||
|
|
afcaf20020 | ||
|
|
bcf20b4441 | ||
|
|
c8a4a6dc58 | ||
|
|
634ce933be | ||
|
|
ca42a9daec | ||
|
|
9e1e9ff8c0 | ||
|
|
761c383958 | ||
|
|
48e4688c13 | ||
|
|
7ddaad615b | ||
|
|
a5854afd2f | ||
|
|
db3c5cf20f | ||
|
|
e7f941fa3d | ||
|
|
51fa778fb2 | ||
|
|
35167da6dd | ||
|
|
2a973b9e16 | ||
|
|
83626923cf | ||
|
|
7551bebb88 | ||
|
|
433b01aaf8 | ||
|
|
da664b49ad | ||
|
|
5d79952bdd | ||
|
|
e5ecd243f2 | ||
|
|
274035edd6 | ||
|
|
60776e8111 | ||
|
|
3fcad4bc65 | ||
|
|
991fe78740 | ||
|
|
aac64b1ded | ||
|
|
b6d3df3335 | ||
|
|
66560d0a7b | ||
|
|
b42d16cf1c | ||
|
|
bcdf32b214 | ||
|
|
9c3728c8f2 | ||
|
|
23fff1e8fd | ||
|
|
a553fbcdd1 | ||
|
|
e62606d276 | ||
|
|
e25d83f701 | ||
|
|
ed37c149d1 | ||
|
|
7c8d126f23 | ||
|
|
c9b88f0404 | ||
|
|
e24d7a1fd2 | ||
|
|
d756f2fc0d | ||
|
|
4d0ab41e6e | ||
|
|
512dbc3a24 | ||
|
|
a3d44e37b5 | ||
|
|
18e4d80073 | ||
|
|
e5bedd52ac | ||
|
|
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 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -43,7 +43,7 @@ X.X.X</br>
|
||||
|
||||
- What bootloader (fusèe, hekate, etc) was Atmosphère launched by:
|
||||
- Official release or unofficial build:
|
||||
- [ Offical release version x.x.x (or) unofficial build ]
|
||||
- [ Official release version x.x.x (or) unofficial build ]
|
||||
- [ If using an unofficial build, include details on where/how you acquired the build. ]
|
||||
- [ Ex: Self-compilation ]
|
||||
- [ Ex: Kosmos' distribution of Atmosphère ]
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -70,6 +70,7 @@ dkms.conf
|
||||
*.id1
|
||||
*.id2
|
||||
*.idb
|
||||
*.i64
|
||||
*.nam
|
||||
*.til
|
||||
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,6 +1,7 @@
|
||||
TOPTARGETS := all clean dist
|
||||
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)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
@@ -53,6 +54,7 @@ dist: all
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032
|
||||
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D
|
||||
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 sept/sept-primary/sept-primary.bin atmosphere-$(AMSVER)/sept/sept-primary.bin
|
||||
@@ -62,12 +64,14 @@ dist: all
|
||||
cp common/defaults/loader.ini atmosphere-$(AMSVER)/atmosphere/loader.ini
|
||||
cp common/defaults/system_settings.ini atmosphere-$(AMSVER)/atmosphere/system_settings.ini
|
||||
cp -r common/defaults/kip_patches atmosphere-$(AMSVER)/atmosphere/kip_patches
|
||||
cp -r common/defaults/hbl_html atmosphere-$(AMSVER)/atmosphere/hbl_html
|
||||
cp stratosphere/creport/creport.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036/exefs.nsp
|
||||
cp stratosphere/fatal/fatal.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034/exefs.nsp
|
||||
cp stratosphere/eclct.stub/eclct.stub.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/exefs.nsp
|
||||
cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags
|
||||
touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags/boot2.flag
|
||||
cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D/exefs.nsp
|
||||
cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../;
|
||||
rm -r atmosphere-$(AMSVER)
|
||||
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.
|
||||
* __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.
|
||||
* __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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
^http*
|
||||
@@ -1,4 +1,9 @@
|
||||
[config]
|
||||
hbl_tid=010000000000100D
|
||||
hbl_path=atmosphere/hbl.nsp
|
||||
override_key=!R
|
||||
[hbl_config]
|
||||
title_id=010000000000100D
|
||||
override_any_app=true
|
||||
path=atmosphere/hbl.nsp
|
||||
override_key=R
|
||||
|
||||
[default_config]
|
||||
override_key=!L
|
||||
cheat_enable_key=!L
|
||||
@@ -3,9 +3,16 @@
|
||||
upload_enabled = u8!0x0
|
||||
; Enable USB 3.0 superspeed for homebrew
|
||||
[usb]
|
||||
usb30_force_enabled = u8!0x1
|
||||
usb30_force_enabled = u8!0x0
|
||||
; Atmosphere custom settings
|
||||
[atmosphere]
|
||||
; Make the power menu's "reboot" button reboot to payload.
|
||||
; Set to "normal" for normal reboot, "rcm" for rcm reboot.
|
||||
power_menu_reboot_function = str!payload
|
||||
power_menu_reboot_function = str!payload
|
||||
; Controls whether dmnt cheats should be toggled on or off by
|
||||
; default. 1 = toggled on by default, 0 = toggled off by default.
|
||||
dmnt_cheats_enabled_by_default = u8!0x1
|
||||
; Controls whether dmnt should always save cheat toggle state
|
||||
; for restoration on new game launch. 1 = always save toggles,
|
||||
; 0 = only save toggles if toggle file exists.
|
||||
dmnt_always_save_cheat_toggles = u8!0x0
|
||||
@@ -19,6 +19,10 @@
|
||||
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MAJOR 0
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MINOR 8
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MICRO 4
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MICRO 6
|
||||
|
||||
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR 7
|
||||
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR 0
|
||||
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO 1
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,62 @@
|
||||
# Changelog
|
||||
## 0.8.6
|
||||
+ A number of bugs were fixed, including:
|
||||
+ A case of inverted logic was fixed in fs.mitm which prevented the flags system from working correctly.
|
||||
+ Time service access was corrected in both creport/fatal.
|
||||
+ This fixes the timestamps used in fatal/crash report filenames.
|
||||
+ A coherency issue was fixed in exosphère's Security Engine driver.
|
||||
+ This fixes some instability issues encountered when overclocking the CPU.
|
||||
+ Loader now unmaps NROs correctly, when ldr:ro is used.
|
||||
+ This fixes a crash when repeatedly launching the web applet on < 3.0.0.
|
||||
+ Usage of hidKeysDown was corrected to hidKeysHeld in several modules.
|
||||
+ This fixes a rare issue where keypresses may have been incorrectly detected.
|
||||
+ An issue with code filesystem unmounting was fixed in loader.
|
||||
+ This issue could occasionally cause a fatal error 0x1015 to be thrown on boot.
|
||||
+ Two bugs were fixed in the implementations of dmnt's cheat virtual machine.
|
||||
+ These could cause cheats to work incorrectly under certain circumstances.
|
||||
+ PM now uses a static buffer instead of a dynamically allocated one during process launch.
|
||||
+ This fixes a memory exhaustion problem when building with gcc 8.3.0.
|
||||
+ A workaround for a deadlock bug in Horizon's kernel on >= 6.0.0 was added in dmnt.
|
||||
+ This prevents a system hang when booting certain titles with cheats enabled (ex: Mario Kart 8 Deluxe).
|
||||
+ set.mitm now reads the system firmware version directly from the system version archive, instead of calling into set:sys.
|
||||
+ This fixes compatibility with 1.0.0, which now successfully boots again.
|
||||
+ dmnt's cheat virtual machine had some instruction set changes.
|
||||
+ A new opcode was added for beginning conditional blocks based on register contents.
|
||||
+ More addressing modes were added to the StoreRegisterToAddress opcode.
|
||||
+ These should allow for more complex cheats to be implemented.
|
||||
+ A new system for saving the state of cheat toggles between game boots was added.
|
||||
+ Toggles are now saved to `atmosphere/titles/<title id>/cheats/toggles.txt` when either toggles were successfully loaded from that file or the system setting `atmosphere!dmnt_always_save_cheat_toggles` is non-zero.
|
||||
+ This removes the need for manually setting cheats from all-on or all-off to the desired state on each game boot.
|
||||
+ The default behavior for loader's HBL support was changed.
|
||||
+ Instead of launching HBL when album is launched without R held, loader now launches HBL when album or any game is launched with R held.
|
||||
+ Loader will now override any app in addition to a specific title id when `hbl_config!override_any_app` is true in `loader.ini`.
|
||||
+ Accordingly, the `hbl_config!title_id=app` setting was deprecated. Support will be removed in Atmosphère 0.9.0.
|
||||
+ First-class support was added to loader and fs.mitm for enabling homebrew to launch web applets.
|
||||
+ Loader will now cause the "HtmlDocument" NCA path to resolve for whatever title HBL is taking over, even if it would not normally do so.
|
||||
+ fs.mitm will also now cause requests to mount the HtmlDocument content for HBL's title to open the `sdmc:/atmosphere/hbl_html` folder.
|
||||
+ By default, this just contains a URL whitelist.
|
||||
+ General system stability improvements to enhance the user's experience.
|
||||
## 0.8.5
|
||||
+ Support was added for overriding content on a per-title basis, separate from HBL override.
|
||||
+ This allows for using mods on the same title that one uses to launch HBL.
|
||||
+ By default, `!L` is used for title content override (this is configurable by editing `default_config!override_key` in `loader.ini`)
|
||||
+ This key combination can be set on a per-title basis by creating a `atmosphere/titles/<title id>/config.ini`, and editing `override_config!override_key`.
|
||||
+ Content headers were added for the embedded files inside of fusee-secondary.
|
||||
+ This will allow non-fusee bootloaders (like `hekate`) to extract the components bundled inside release binaries.
|
||||
+ This should greatly simplify the update process in the future, for users who do not launch Atmosphère using fusee.
|
||||
+ Support for cheat codes was added.
|
||||
+ These are handled by a new `dmnt` sysmodule, which will also reimplement Nintendo's Debug Monitor in the future.
|
||||
+ Cheat codes can be enabled/disabled at application launch via a per-title key combination.
|
||||
+ For details, please see the [cheat loading documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/cheats.md#cheat-loating-process).
|
||||
+ Cheat codes are fully backwards compatible with the pre-existing format, although a number of bugs have been fixed and some new features have been added.
|
||||
+ For details, please see [the compatibility documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/cheats.md#cheat-code-compatibility).
|
||||
+ An HIPC service API was added (`dmnt:cht`), that will allow user homebrew to interface with and control Atmosphère's cheat manager.
|
||||
+ Please see [the relevant documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/modules/dmnt.md).
|
||||
+ Full client code can be found in [libstratosphere](https://github.com/Atmosphere-NX/libstratosphere/blob/master/include/stratosphere/services/dmntcht.h).
|
||||
+ Users interested in interfacing should see [EdiZon](https://github.com/WerWolv/EdiZon), which should have support for interfacing with Atmosphère's API shortly after 0.8.5 releases.
|
||||
+ A bug was fixed that would cause Atmosphère's fatal screen to not show on 1.0.0-2.3.0.
|
||||
+ A bug was fixed that caused Atmosphère's automatic ProdInfo backups to be corrupt.
|
||||
+ General system stability improvements to enhance the user's experience.
|
||||
## 0.8.4
|
||||
+ Support for 7.0.0/7.0.1 was added.
|
||||
+ This is facilitated through a new payload, `sept`, which can be signed, encrypted, and then loaded by Nintendo's TSEC firmware.
|
||||
|
||||
365
docs/cheats.md
Normal file
365
docs/cheats.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# Cheats
|
||||
Atmosphère supports Action-Replay style cheat codes, with cheats loaded off of the SD card.
|
||||
|
||||
## Cheat Loading Process
|
||||
By default, Atmosphère will do the following when deciding whether to attach to a new application process:
|
||||
|
||||
+ Retrieve information about the new application process from `pm` and `loader`.
|
||||
+ Check whether a user-defined key combination is held, and stop if not.
|
||||
+ This defaults to "L is not held", and can be configured the same way as `fs.mitm` override keys.
|
||||
+ The ini key to configure this is `cheat_enable_key`.
|
||||
+ Check whether the process is a real application, and stop if not.
|
||||
+ This guards against applying cheat codes to the homebrew loader.
|
||||
+ Attempt to load cheats from `atmosphere/titles/<title_id>/cheats/<build_id>.txt`, where `build_id` is the hexadecimal representation of the first 8 bytes of the application's main executable's build id.
|
||||
+ If no cheats are found, then the cheat manager will stop.
|
||||
+ Open a kernel debug session for the new application process.
|
||||
+ Signal to a system event that a new cheat process has been attached to.
|
||||
|
||||
This behavior ensures that cheat codes are only loaded when the user would want them to.
|
||||
|
||||
In cases where dmnt has not activated the cheat manager, but the user wants to make it do so anyway, the cheat manager's service API provides a `ForceOpenCheatProcess` command that homebrew can use. This command will cause the cheat manager to try to force itself to attach to the process.
|
||||
|
||||
By default, all cheat codes listed in the loaded .txt file will be toggled on. This is configurable by the user, and the default can be set to toggled off by editing the `atmosphere!dmnt_cheats_enabled_by_default` entry to 0 instead of 1.
|
||||
|
||||
Users may use homebrew programs to toggle cheats on and off at runtime via the cheat manager's service API.
|
||||
|
||||
## Cheat Code Compatibility
|
||||
|
||||
Atmosphère manages cheat code through the execution of a small, custom virtual machine. Care has been taken to ensure that Atmosphère's cheat code format is fully backwards compatible with the pre-existing cheat code format, though new features have been added and bugs in the pre-existing cheat code applier have been fixed. Here is a short summary of the changes from the pre-existing format:
|
||||
|
||||
+ A number of bugs were fixed in the processing of conditional instructions.
|
||||
+ The pre-existing implementation was fundamentally broken, and checked for the wrong value when detecting the end of a conditional block.
|
||||
+ The pre-existing implementation also did not properly decode instructions, and instead linearly scanned for the terminator value. This caused problems if an instruction happened to encode a terminator inside its immediate values.
|
||||
+ The pre-existing implementation did not bounds check, and thus certain conditional cheat codes could cause it to read out-of-bounds memory, and potentially crash due to a data abort.
|
||||
+ Support was added for nesting conditional blocks.
|
||||
+ An instruction was added to perform much more complex arbitrary arithmetic on two registers.
|
||||
+ An instruction was added to allow writing the contents of register to a memory address specified by another register.
|
||||
+ The pre-existing implementation did not correctly synchronize with the application process, and thus would cause heavy lag under certain circumstances (especially around loading screens). This has been fixed in Atmosphère's implementation.
|
||||
|
||||
## Cheat Code Format
|
||||
|
||||
The following provides documentation of the instruction format for the virtual machine used to manage cheat codes.
|
||||
|
||||
Typically, instruction type is encoded in the upper nybble of the first instruction u32.
|
||||
|
||||
### Code Type 0: Store Static Value to Memory
|
||||
|
||||
Code type 0 allows writing a static value to a memory address.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ R: Register to use as an offset from memory region base.
|
||||
+ A: Immediate offset to use from memory region base.
|
||||
+ V: Value to write.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 1: Begin Conditional Block
|
||||
|
||||
Code type 1 performs a comparison of the contents of memory to a static value.
|
||||
|
||||
If the condition is not met, all instructions until the appropriate conditional block terminator are skipped.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ C: Condition to use, see below.
|
||||
+ A: Immediate offset to use from memory region base.
|
||||
+ V: Value to compare to.
|
||||
|
||||
#### Conditions
|
||||
|
||||
+ 1: >
|
||||
+ 2: >=
|
||||
+ 3: <
|
||||
+ 4: <=
|
||||
+ 5: ==
|
||||
+ 6: !=
|
||||
|
||||
---
|
||||
|
||||
### Code Type 2: End Conditional Block
|
||||
|
||||
Code type 2 marks the end of a conditional block (started by Code Type 1 or Code Type 8).
|
||||
|
||||
#### Encoding
|
||||
|
||||
`20000000`
|
||||
|
||||
---
|
||||
|
||||
### Code Type 3: Start/End Loop
|
||||
|
||||
Code type 3 allows for iterating in a loop a fixed number of times.
|
||||
|
||||
#### Start Loop Encoding
|
||||
|
||||
`300R0000 VVVVVVVV`
|
||||
|
||||
+ R: Register to use as loop counter.
|
||||
+ V: Number of iterations to loop.
|
||||
|
||||
#### End Loop Encoding
|
||||
|
||||
`310R0000`
|
||||
|
||||
+ R: Register to use as loop counter.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 4: Load Register with Static Value
|
||||
|
||||
Code type 4 allows setting a register to a constant value.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`400R0000 VVVVVVVV VVVVVVVV`
|
||||
|
||||
+ R: Register to use.
|
||||
+ V: Value to load.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 5: Load Register with Memory Value
|
||||
|
||||
Code type 5 allows loading a value from memory into a register, either using a fixed address or by dereferencing the destination register.
|
||||
|
||||
#### Load From Fixed Address Encoding
|
||||
|
||||
`5TMR00AA AAAAAAAA`
|
||||
|
||||
+ T: width of memory read (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ R: Register to load value into.
|
||||
+ A: Immediate offset to use from memory region base.
|
||||
|
||||
#### Load from Register Address Encoding
|
||||
|
||||
`5TMR10AA AAAAAAAA`
|
||||
|
||||
+ T: width of memory read (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ R: Register to load value into.
|
||||
+ A: Immediate offset to use from register R.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 6: Store Static Value to Register Memory Address
|
||||
|
||||
Code type 6 allows writing a fixed value to a memory address specified by a register.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`6T0RIor0 VVVVVVVV VVVVVVVV`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ R: Register used as base memory address.
|
||||
+ I: Increment register flag (0 = do not increment R, 1 = increment R by T).
|
||||
+ o: Offset register enable flag (0 = do not add r to address, 1 = add r to address).
|
||||
+ r: Register used as offset when o is 1.
|
||||
+ V: Value to write to memory.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 7: Legacy Arithmetic
|
||||
|
||||
Code type 7 allows performing arithmetic on registers.
|
||||
|
||||
However, it has been deprecated by Code type 9, and is only kept for backwards compatibility.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`7T0RC000 VVVVVVVV`
|
||||
|
||||
+ T: width of arithmetic operation (1, 2, 4, or 8 bytes)
|
||||
+ R: Register to apply arithmetic to.
|
||||
+ C: Arithmetic operation to apply, see below.
|
||||
+ V: Value to use for arithmetic operation.
|
||||
|
||||
#### Arithmetic Types
|
||||
|
||||
+ 0: Addition
|
||||
+ 1: Subtraction
|
||||
+ 2: Multiplication
|
||||
+ 3: Left Shift
|
||||
+ 4: Right Shift
|
||||
|
||||
---
|
||||
|
||||
### Code Type 8: Begin Keypress Conditional Block
|
||||
|
||||
Code type 8 enters or skips a conditional block based on whether a key combination is pressed.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`8kkkkkkk`
|
||||
|
||||
+ k: Keypad mask to check against, see below.
|
||||
|
||||
Note that for multiple button combinations, the bitmasks should be ORd together.
|
||||
|
||||
#### Keypad Values
|
||||
|
||||
Note: This is the direct output of `hidKeysDown()`.
|
||||
|
||||
+ 0000001: A
|
||||
+ 0000002: B
|
||||
+ 0000004: X
|
||||
+ 0000008: Y
|
||||
+ 0000010: Left Stick Pressed
|
||||
+ 0000020: Right Stick Pressed
|
||||
+ 0000040: L
|
||||
+ 0000080: R
|
||||
+ 0000100: ZL
|
||||
+ 0000200: ZR
|
||||
+ 0000400: Plus
|
||||
+ 0000800: Minus
|
||||
+ 0001000: Left
|
||||
+ 0002000: Up
|
||||
+ 0004000: Right
|
||||
+ 0008000: Down
|
||||
+ 0010000: Left Stick Left
|
||||
+ 0020000: Left Stick Up
|
||||
+ 0040000: Left Stick Right
|
||||
+ 0080000: Left Stick Down
|
||||
+ 0100000: Right Stick Left
|
||||
+ 0200000: Right Stick Up
|
||||
+ 0400000: Right Stick Right
|
||||
+ 0800000: Right Stick Down
|
||||
+ 1000000: SL
|
||||
+ 2000000: SR
|
||||
|
||||
---
|
||||
|
||||
### Code Type 9: Perform Arithmetic
|
||||
|
||||
Code type 9 allows performing arithmetic on registers.
|
||||
|
||||
#### Register Arithmetic Encoding
|
||||
|
||||
`9TCRS0s0`
|
||||
|
||||
+ T: width of arithmetic operation (1, 2, 4, or 8 bytes)
|
||||
+ C: Arithmetic operation to apply, see below.
|
||||
+ R: Register to store result in.
|
||||
+ S: Register to use as left-hand operand.
|
||||
+ s: Register to use as right-hand operand.
|
||||
|
||||
#### Immediate Value Arithmetic Encoding
|
||||
|
||||
`9TCRS100 VVVVVVVV (VVVVVVVV)`
|
||||
|
||||
+ T: width of arithmetic operation (1, 2, 4, or 8 bytes)
|
||||
+ C: Arithmetic operation to apply, see below.
|
||||
+ R: Register to store result in.
|
||||
+ S: Register to use as left-hand operand.
|
||||
+ V: Value to use as right-hand operand.
|
||||
|
||||
#### Arithmetic Types
|
||||
|
||||
+ 0: Addition
|
||||
+ 1: Subtraction
|
||||
+ 2: Multiplication
|
||||
+ 3: Left Shift
|
||||
+ 4: Right Shift
|
||||
+ 5: Logical And
|
||||
+ 6: Logical Or
|
||||
+ 7: Logical Not (discards right-hand operand)
|
||||
+ 8: Logical Xor
|
||||
+ 9: None/Move (discards right-hand operand)
|
||||
|
||||
---
|
||||
|
||||
### Code Type 10: Store Register to Memory Address
|
||||
|
||||
Code type 10 allows writing a register to memory.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`ATSRIOxa (aaaaaaaa)`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ S: Register to write to memory.
|
||||
+ R: Register to use as base address.
|
||||
+ I: Increment register flag (0 = do not increment R, 1 = increment R by T).
|
||||
+ O: Offset type, see below.
|
||||
+ x: Register used as offset when O is 1, Memory type when O is 3, 4 or 5.
|
||||
+ a: Value used as offset when O is 2, 4 or 5.
|
||||
|
||||
#### Offset Types
|
||||
|
||||
+ 0: No Offset
|
||||
+ 1: Use Offset Register
|
||||
+ 2: Use Fixed Offset
|
||||
+ 3: Memory Region + Base Register
|
||||
+ 4: Memory Region + Relative Address (ignore address register)
|
||||
+ 5: Memory Region + Relative Address + Offset Register
|
||||
---
|
||||
|
||||
### Code Type 11: Reserved
|
||||
|
||||
Code Type 11 is currently reserved for future use.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 12-15: Extended-Width Instruction
|
||||
|
||||
Code Types 12-15 signal to the VM to treat the upper two nybbles of the first dword as instruction type, instead of just the upper nybble.
|
||||
|
||||
This reserves an additional 64 opcodes for future use.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 0xC0: Begin Register Conditional Block
|
||||
|
||||
Code type 0xC0 performs a comparison of the contents of a register and another value. This code support multiple operand types, see below.
|
||||
|
||||
If the condition is not met, all instructions until the appropriate conditional block terminator are skipped.
|
||||
|
||||
#### Encoding
|
||||
|
||||
```
|
||||
C0TcSX##
|
||||
C0TcS0Ma aaaaaaaa
|
||||
C0TcS1Mr
|
||||
C0TcS2Ra aaaaaaaa
|
||||
C0TcS3Rr
|
||||
C0TcS400 VVVVVVVV (VVVVVVVV)
|
||||
C0TcS5X0
|
||||
```
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ c: Condition to use, see below.
|
||||
+ S: Source Register
|
||||
+ X: Operand Type, see below.
|
||||
+ M: Memory Type (operand types 0 and 1)
|
||||
+ R: Address Register (operand types 2 and 3)
|
||||
+ a: Relative Address (operand types 0 and 2)
|
||||
+ r: Offset Register (operand types 1 and 3)
|
||||
+ X: Other Register (used for operand type 5)
|
||||
+ V: Value to compare to (operand type 4)
|
||||
|
||||
#### Operand Type
|
||||
|
||||
+ 0: Memory Base + Relative Offset
|
||||
+ 1: Memory Base + Offset Register
|
||||
+ 2: Register + Relative Offset
|
||||
+ 3: Register + Offset Register
|
||||
+ 4: Static Value
|
||||
+ 5: Other Register
|
||||
|
||||
#### Conditions
|
||||
|
||||
+ 1: >
|
||||
+ 2: >=
|
||||
+ 3: <
|
||||
+ 4: <=
|
||||
+ 5: ==
|
||||
+ 6: !=
|
||||
@@ -1,19 +1,51 @@
|
||||
# BCT.ini
|
||||
BCT.ini is the configuration file used by fusée-primary and fusée-secondary. It is read by fusee-primary.bin to setup and boot fusee-secondary.bin and is also read by fusee-secondary.bin to configure Exosphère or to specify the environment it should boot.
|
||||
BCT.ini is the configuration file used by fusée-primary and fusée-secondary. It is read by fusee-primary.bin to setup and boot fusee-secondary.bin and is also read by fusee-secondary.bin to configure Exosphère, specify the environment it should boot, or configure other miscellaneous options such as setting a custom boot splashscreen.
|
||||
|
||||
## Configuration
|
||||
This file is located at the root of your SD.
|
||||
This file is located in the `atmosphere` folder on your SD card. The default configuration file will look similar to this.
|
||||
```
|
||||
BCT0
|
||||
[stage1]
|
||||
stage2_path = fusee-secondary.bin
|
||||
stage2_path = atmosphere/fusee-secondary.bin
|
||||
stage2_addr = 0xF0000000
|
||||
stage2_entrypoint = 0xF0000000
|
||||
|
||||
[exosphere]
|
||||
; Note: Disabling debugmode will cause parts of ams.tma to not work, in the future.
|
||||
debugmode = 1
|
||||
debugmode_user = 0
|
||||
|
||||
[stratosphere]
|
||||
; To force-enable nogc, add nogc = 1
|
||||
; To force-disable nogc, add nogc = 0
|
||||
```
|
||||
Add the following lines and replace the `X` according to the following list if you have trouble booting past the firmware version detection.
|
||||
|
||||
## Adding a Custom Boot Splashscreen
|
||||
Add the following lines to BCT.ini and change the value of `custom_splash` to the actual path and filename of your boot splashscreen.
|
||||
```
|
||||
[stage2]
|
||||
custom_splash = /path/to/your/bootlogo.bmp
|
||||
```
|
||||
|
||||
The boot splashscreen must be a BMP file, it must be 720x1280 (1280x720 rotated 90 degrees left/counterclockwise/anti-clockwise) resolution, and be in 32-bit ARGB format. You can use image editing software such as GIMP or Photoshop to export the image in this format.
|
||||
|
||||
## Configuring "nogc" Protection
|
||||
Nogc is a feature provided by fusée-secondary which disables the Nintendo Switch's Game Card reader. Its purpose is to prevent the reader from being updated when the console has been updated without burning fuses from a firmware lower than 4.0.0, to a newer firmware that is at least 4.0.0 or higher. By default, Atmosphere will protect the Game Card reader automatically, but you are free to change it.
|
||||
|
||||
To change its functionality, add the following line to the `stratosphere` section and change the value of `X` according to the following list.
|
||||
```
|
||||
nogc = X
|
||||
```
|
||||
```
|
||||
1 = force-enable nogc, so Atmosphere will always disable the Game Card reader.
|
||||
0 = force-disable nogc, so Atmosphere will always enable the Game Card reader.
|
||||
```
|
||||
|
||||
|
||||
## Changing Target Firmware
|
||||
Add the following line to the `exosphere` section and replace the `X` according to the following list if you have trouble booting past the firmware version detection.
|
||||
`target_firmware` is the OFW major version.
|
||||
```
|
||||
[exosphere]
|
||||
target_firmware = X
|
||||
```
|
||||
```
|
||||
@@ -22,5 +54,20 @@ target_firmware = X
|
||||
3.X.X = 3
|
||||
4.X.X = 4
|
||||
5.X.X = 5
|
||||
6.0.0 = 6
|
||||
6.X.X = 6
|
||||
6.2.0 = 7
|
||||
7.X.X = 8
|
||||
```
|
||||
|
||||
Note that 6.X.X indicates 6.0.0 through 6.1.0.
|
||||
|
||||
## Configuring Debugging Modes
|
||||
By default, Atmosphere signals to the Horizon kernel that debugging is enabled while leaving usermode debugging disabled, since this can cause undesirable side-effects. If you wish to change these behaviours, go to the `exosphere` section and change the value of `X` according to the following list.
|
||||
```
|
||||
debugmode = X
|
||||
debugmode_user = X
|
||||
```
|
||||
```
|
||||
1 = enable
|
||||
0 = disable
|
||||
```
|
||||
|
||||
38
docs/modules/dmnt.md
Normal file
38
docs/modules/dmnt.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# dmnt
|
||||
|
||||
dmnt is a reimplementation of Nintendo's debug monitor. It provides Atmosphère a rich set of debugging functionality, so that users can easily analyze the behaviors of programs. In addition, Atmosphère implements an extension in dmnt to provide cheat code functionality.
|
||||
|
||||
## Atmosphère Cheat Extension
|
||||
|
||||
In addition to the functionality provided by Nintendo's debug monitor, Atmosphère's dmnt has an extension for providing cheat code functionality. A HIPC Service API is provided for interacting with the cheat code manager, through the service `dmnt:cht`.
|
||||
|
||||
Those looking for more information on the cheat code functionality may wish to read `cheats.md`.
|
||||
|
||||
The SwIPC definition for `dmnt:cht` follows.
|
||||
```
|
||||
interface DmntCheatService is dmnt:cht {
|
||||
[65000] HasCheatProcess() -> bool;
|
||||
[65001] GetCheatProcessEvent() -> KObject;
|
||||
[65002] GetCheatProcessMetadata() -> CheatProcessMetadata;
|
||||
[65003] ForceOpenCheatProcess();
|
||||
|
||||
[65100] GetCheatProcessMappingCount() -> u64;
|
||||
[65101] GetCheatProcessMappings(u64 offset) -> buffer<MemoryInfo, 6>, u64 count;
|
||||
[65102] ReadCheatProcessMemory(u64 address, u64 size) -> buffer<u8, 6> data;
|
||||
[65103] WriteCheatProcessMemory(u64 address, u64 size, buffer<u8, 5> data);
|
||||
[65104] QueryCheatProcessMemory(u64 address) -> MemoryInfo;
|
||||
|
||||
[65200] GetCheatCount() -> u64;
|
||||
[65201] GetCheats(u64 offset) -> buffer<CheatEntry, 6>, u64 count;
|
||||
[65202] GetCheatById(u32 cheat_id) -> buffer<CheatEntry, 6> cheat;
|
||||
[65203] ToggleCheat(u32 cheat_id);
|
||||
[65204] AddCheat(buffer<CheatDefinition, 5> cheat, bool enabled) -> u32 cheat_id;
|
||||
[65203] RemoveCheat(u32 cheat_id);
|
||||
|
||||
[65300] GetFrozenAddressCount() -> u64;
|
||||
[65301] GetFrozenAddresses(u64 offset) -> buffer<FrozenAddressEntry, 6>, u64 count;
|
||||
[65302] GetFrozenAddress(u64 address) -> FrozenAddressEntry;
|
||||
[65303] EnableFrozenAddress(u64 address, u64 width) -> u64 value;
|
||||
[65304] DisableFrozenAddress(u64 address);
|
||||
}
|
||||
```
|
||||
@@ -1,2 +1,2 @@
|
||||
# fs_mitm
|
||||
fs_mitm is a sysmodule that enables intercepting file system operations. This module can log, deny, delay, replace, or redirect any request made to the filesystem. It enables LayeredFS to function, which allows for game mods.
|
||||
fs_mitm is a sysmodule that enables intercepting file system operations. This module can log, deny, delay, replace, or redirect any request made to the filesystem. It enables LayeredFS to function, which allows for replacement of game assets.
|
||||
@@ -55,13 +55,38 @@ 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.
|
||||
|
||||
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 launch any title while holding the button specified in `loader.ini`.
|
||||
|
||||
In addition, loader has extensions to enable homebrew to launch web applets. This normally requires the application launching the applet have HTML Manual content inside an installed NCA; Atmosphère's loader will automatically ensure that the commands used to check this succeed, and will (in tandem with `fs.mitm`) redirect the relevant filesystem to the `sdmc:/atmosphere/hbl_html/` subdirectory.
|
||||
|
||||
### Button Overrides
|
||||
|
||||
By default `loader.ini` is configured to launch the Homebrew Menu when launching the game normally, and launching the game when selecting the game while holding down R. If you wish to change this, you can modify the override_key section of `loader.ini`. Placing an exclamation point in front of whatever button you wish to use will make it so that you will only launch the actual game while holding down that button, otherwise you will go into the Homebrew Menu. Removing the exclamation point will reverse this, meaning that you will boot into the Homebrew Menu only while holding down the assigned button when launching the game.
|
||||
By default `loader.ini` is configured to launch the Homebrew Menu when launching any game while holding down the override key (defaults to R). If you wish to change this, you can modify the override_key section of `loader.ini`. Alternatively, if you would like to only allow hbmenu on a specific app, 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, and set override_any_app to false. A list of Title IDs for Switch Games can be found [here](https://switchbrew.org/wiki/Title_list/Games).
|
||||
|
||||
For example, `override_key=!R` will run the game only while holding down R when launching it, otherwise it will boot into the Homebrew Menu. `override_key=R` will only boot into the Homebrew Menu while holding down R when launching the game, otherwise it will launch the game as normal.
|
||||
To invert the behaviour of the override key, place an exclamation point in front of whatever button you wish to use. It will launch the actual game while holding down that button, instead of going into the Homebrew Menu. For example, `override_key=!R` will run the game only while holding down R when launching it, otherwise it will boot into the Homebrew Menu. 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 title of choice.
|
||||
|
||||
A list of valid buttons can be found here:
|
||||
|
||||
| Formal Name | .ini Name |
|
||||
| ----------- | --------- |
|
||||
| A Button | A |
|
||||
| B Button | B |
|
||||
| X Button | X |
|
||||
| Y Button | Y |
|
||||
| Left Stick | LS |
|
||||
| Right Stick | RS |
|
||||
| L Button | L |
|
||||
| R Button | R |
|
||||
| ZL Button | ZL |
|
||||
| ZR Button | ZR |
|
||||
| + Button | PLUS |
|
||||
| - Button | MINUS |
|
||||
| Left Dpad | DLEFT |
|
||||
| Up Dpad | DUP |
|
||||
| Right Dpad | DRIGHT |
|
||||
| Down Dpad | DDOWN |
|
||||
| SL Button | SL |
|
||||
| SR Button | SR |
|
||||
|
||||
### SM MITM Integration
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ The SwIPC definition for this command follows.
|
||||
```
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -50,5 +50,30 @@ upload_enabled = u8!0x0
|
||||
|
||||
### Atmosphère Custom Settings
|
||||
|
||||
At present, Atmosphère implements no custom settings. However, this is subject to change in the future, and any\
|
||||
custom settings will be documented here as they are added.
|
||||
At the time of writing, Atmosphère implements two custom settings, found in the `atmosphere` section.\
|
||||
|
||||
While not used for set_mitm, `power_menu_reboot_function` is loaded and controls the reboot behaviour of the console. By default, this value\
|
||||
is "payload", where the console will automatically reboot into the RCM payload stored in `sdmc:/atmosphere/reboot_payload.bin`.\
|
||||
(This payload is also used for fatal, upon a serious crash.) Setting the value to "rcm" reboots directly into RCM, and setting the value\
|
||||
to "normal" skips these behaviours.
|
||||
|
||||
```
|
||||
[atmosphere]
|
||||
power_menu_reboot_function = str!payload
|
||||
```
|
||||
|
||||
`dmnt_cheats_enabled_by_default` controls the behaviour of dmnt's cheat functionality. By default, this value is "0x1", enabling any cheats\
|
||||
defined by the user. Check [cheats](../cheats.md) for more information about Atmosphère's cheat functionality.
|
||||
|
||||
```
|
||||
[atmosphere]
|
||||
dmnt_cheats_enabled_by_default = u8!0x1
|
||||
```
|
||||
|
||||
`dmnt_always_save_cheat_toggles` controls the behaviour of dmnt's cheat toggle functionality. By default, this value is "0x0", causing toggles to\
|
||||
only be saved on game quit if a toggle file existed on game boot. Check [cheats](../cheats.md) for more information about Atmosphère's cheat functionality.
|
||||
|
||||
```
|
||||
[atmosphere]
|
||||
dmnt_always_save_cheat_toggles = u8!0x0
|
||||
```
|
||||
|
||||
@@ -315,6 +315,7 @@ void se_aes_crypt_insecure_internal(unsigned int keyslot, uint32_t out_ll_paddr,
|
||||
se->ERR_STATUS_REG = se->ERR_STATUS_REG;
|
||||
se->INT_STATUS_REG = se->INT_STATUS_REG;
|
||||
se->OPERATION_REG = 1;
|
||||
(void)(se->OPERATION_REG);
|
||||
|
||||
/* Ensure writes go through. */
|
||||
__dsb_ish();
|
||||
@@ -477,6 +478,7 @@ void trigger_se_rsa_op(void *buf, size_t size) {
|
||||
se->ERR_STATUS_REG = se->ERR_STATUS_REG;
|
||||
se->INT_STATUS_REG = se->INT_STATUS_REG;
|
||||
se->OPERATION_REG = 1;
|
||||
(void)(se->OPERATION_REG);
|
||||
|
||||
/* Ensure writes go through. */
|
||||
__dsb_ish();
|
||||
@@ -500,6 +502,9 @@ void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const v
|
||||
se->ERR_STATUS_REG = se->ERR_STATUS_REG;
|
||||
se->INT_STATUS_REG = se->INT_STATUS_REG;
|
||||
se->OPERATION_REG = op;
|
||||
(void)(se->OPERATION_REG);
|
||||
|
||||
__dsb_ish();
|
||||
|
||||
while (!(se->INT_STATUS_REG & 0x10)) { /* Wait a while */ }
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ AMS := $(TOPDIR)/../../
|
||||
include $(DEVKITARM)/base_rules
|
||||
|
||||
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)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
@@ -43,7 +44,7 @@ INCLUDES := include ../../common/include
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
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 := \
|
||||
-g \
|
||||
@@ -61,7 +62,7 @@ CFLAGS += $(INCLUDE)
|
||||
|
||||
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)
|
||||
|
||||
LIBS :=
|
||||
@@ -159,6 +160,7 @@ clean:
|
||||
@$(MAKE) -C $(AMS)/exosphere clean
|
||||
@$(MAKE) -C $(AMS)/thermosphere clean
|
||||
@$(MAKE) -C $(AMS)/stratosphere clean
|
||||
@$(MAKE) -C $(AMS)/sept clean
|
||||
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).elf
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
@@ -157,6 +157,9 @@ SECTIONS
|
||||
CONSTRUCTORS
|
||||
. = ALIGN(32);
|
||||
} >main
|
||||
|
||||
__data_end__ = ABSOLUTE(.);
|
||||
PROVIDE (__total_size__ = (__data_end__ - __start__));
|
||||
|
||||
.bss (NOLOAD) :
|
||||
{
|
||||
@@ -211,4 +214,36 @@ SECTIONS
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.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);
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ int nxfs_mount_all(void) {
|
||||
model = g_mmc_devpart_template;
|
||||
model.device_struct = &g_emmc_user_mmcpart;
|
||||
model.start_sector = 0;
|
||||
model.num_sectors = (32ull << 30) / model.sector_size;
|
||||
model.num_sectors = (256ull << 30) / model.sector_size;
|
||||
|
||||
rc = rawdev_mount_device("rawnand", &model, false);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* 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 <atmosphere/version.h>
|
||||
|
||||
.macro CLEAR_GPR_REG_ITER
|
||||
mov r\@, #0
|
||||
@@ -20,21 +21,27 @@
|
||||
|
||||
.section .text.start, "ax", %progbits
|
||||
.arm
|
||||
|
||||
.align 5
|
||||
.global _start
|
||||
.type _start, %function
|
||||
_start:
|
||||
b _crt0
|
||||
|
||||
.word (_metadata - _start)
|
||||
|
||||
_crt0:
|
||||
/* Switch to system mode, mask all interrupts, clear all flags */
|
||||
msr cpsr_cxsf, #0xDF
|
||||
|
||||
/* Relocate ourselves if necessary */
|
||||
ldr r2, =__start__
|
||||
ldr r2, =_start
|
||||
adr r3, _start
|
||||
cmp r2, r3
|
||||
bne _relocation_loop_end
|
||||
beq _relocation_loop_end
|
||||
|
||||
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:
|
||||
ldmia r3!, {r5-r12}
|
||||
stmia r2!, {r5-r12}
|
||||
@@ -60,6 +67,138 @@ _start:
|
||||
ldr r0, [r0]
|
||||
ldr r1, [r1]
|
||||
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
|
||||
|
||||
#define CONTENT_TYPE_FSP 0
|
||||
#define CONTENT_TYPE_EXO 1
|
||||
#define CONTENT_TYPE_WBT 2
|
||||
#define CONTENT_TYPE_RBT 3
|
||||
#define CONTENT_TYPE_SP1 4
|
||||
#define CONTENT_TYPE_SP2 5
|
||||
#define CONTENT_TYPE_KIP 6
|
||||
#define CONTENT_TYPE_BMP 7
|
||||
|
||||
_content_headers:
|
||||
/* ams_mitm content header */
|
||||
.word __ams_mitm_kip_start__
|
||||
.word __ams_mitm_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "ams_mitm"
|
||||
.align 5
|
||||
|
||||
/* boot_100 content header */
|
||||
.word __boot_100_kip_start__
|
||||
.word __boot_100_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "boot_100"
|
||||
.align 5
|
||||
|
||||
/* boot_200 content header */
|
||||
.word __boot_200_kip_start__
|
||||
.word __boot_200_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "boot_200"
|
||||
.align 5
|
||||
|
||||
/* exosphere content header */
|
||||
.word __exosphere_bin_start__
|
||||
.word __exosphere_bin_size__
|
||||
.word CONTENT_TYPE_EXO
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "exosphere"
|
||||
.align 5
|
||||
|
||||
/* fusee_primary content header */
|
||||
.word __fusee_primary_bin_start__
|
||||
.word __fusee_primary_bin_size__
|
||||
.word CONTENT_TYPE_FSP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "fusee_primary"
|
||||
.align 5
|
||||
|
||||
/* loader content header */
|
||||
.word __loader_kip_start__
|
||||
.word __loader_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "loader"
|
||||
.align 5
|
||||
|
||||
/* lp0fw content header */
|
||||
.word __lp0fw_bin_start__
|
||||
.word __lp0fw_bin_size__
|
||||
.word CONTENT_TYPE_WBT
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "lp0fw"
|
||||
.align 5
|
||||
|
||||
/* pm content header */
|
||||
.word __pm_kip_start__
|
||||
.word __pm_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "pm"
|
||||
.align 5
|
||||
|
||||
/* rebootstub content header */
|
||||
.word __rebootstub_bin_start__
|
||||
.word __rebootstub_bin_size__
|
||||
.word CONTENT_TYPE_RBT
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "rebootstub"
|
||||
.align 5
|
||||
|
||||
/* sept_primary content header */
|
||||
.word __sept_primary_bin_start__
|
||||
.word __sept_primary_bin_size__
|
||||
.word CONTENT_TYPE_SP1
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "sept_primary"
|
||||
.align 5
|
||||
|
||||
/* sept_secondary content header */
|
||||
.word __sept_secondary_enc_start__
|
||||
.word __sept_secondary_enc_size__
|
||||
.word CONTENT_TYPE_SP2
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "sept_secondary"
|
||||
.align 5
|
||||
|
||||
/* sm content header */
|
||||
.word __sm_kip_start__
|
||||
.word __sm_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "sm"
|
||||
.align 5
|
||||
|
||||
/* splash_screen content header */
|
||||
.word __splash_screen_bmp_start__
|
||||
.word __splash_screen_bmp_size__
|
||||
.word CONTENT_TYPE_BMP
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "splash_screen"
|
||||
.align 5
|
||||
_content_headers_end:
|
||||
|
||||
/* No need to include this in normal programs: */
|
||||
.section .chainloader.text.start, "ax", %progbits
|
||||
|
||||
@@ -120,14 +120,19 @@ check_rebootstub:
|
||||
@$(MAKE) -C $(AMS)/exosphere/rebootstub all
|
||||
|
||||
$(BUILD):
|
||||
ifeq ($(strip $(SEPT_ENC_PATH)),)
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
else
|
||||
@touch $(TOPDIR)/$(TARGET).bin
|
||||
@cp $(SEPT_ENC_PATH) $(TOPDIR)/$(TARGET).enc
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@$(MAKE) -C $(AMS)/exosphere/rebootstub clean
|
||||
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).elf
|
||||
@rm -fr $(BUILD) $(TARGET).bin $(TARGET).enc $(TARGET).elf
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
@@ -32,8 +32,8 @@ def get_last_block_for_desired_mac(key, data, desired_mac):
|
||||
k1 = shift_left_xor_rb(AES.new(key, AES.MODE_ECB).encrypt(bytearray(0x10)))
|
||||
if len(data) & 0xF:
|
||||
k1 = shift_left_xor_rb(k1)
|
||||
data += b'\x80'
|
||||
data += bytearray((0x10 - (len(data) & 0xF)) & 0xF)
|
||||
data = data + b'\x80'
|
||||
data = data + bytearray((0x10 - (len(data) & 0xF)) & 0xF)
|
||||
num_blocks = (len(data) + 0xF) >> 4
|
||||
last_block = sxor(bytearray(AES.new(key, AES.MODE_ECB).decrypt(desired_mac)), bytearray(k1))
|
||||
if len(data) > 0x0:
|
||||
@@ -43,20 +43,20 @@ def get_last_block_for_desired_mac(key, data, desired_mac):
|
||||
|
||||
def sign_encrypt_code(code, sig_key, enc_key, iv, desired_mac):
|
||||
# Pad with 0x20 of zeroes.
|
||||
code += bytearray(0x20)
|
||||
code = code + bytearray(0x20)
|
||||
code_len = len(code)
|
||||
code_len += 0xFFF
|
||||
code_len &= ~0xFFF
|
||||
code += bytearray(code_len - len(code))
|
||||
code = code + bytearray(code_len - len(code))
|
||||
|
||||
# Add empty trustzone, warmboot segments.
|
||||
code += bytearray(0x1FE0 - 0x10)
|
||||
code = code + bytearray(0x1FE0 - 0x10)
|
||||
pk11_hdr = b'PK11' + pk('<IIIIIII', 0x1000, 0, 0, code_len - 0x20, 0, 0x1000, 0)
|
||||
pk11 = pk11_hdr + code
|
||||
enc_pk11 = AES.new(enc_key, AES.MODE_CBC, iv).encrypt(pk11)
|
||||
enc_pk11 = pk('<IIII', len(pk11) + 0x10, 0, 0, 0) + iv + enc_pk11
|
||||
enc_pk11 += get_last_block_for_desired_mac(sig_key, enc_pk11, desired_mac)
|
||||
enc_pk11 += CMAC.new(sig_key, enc_pk11, AES).digest()
|
||||
enc_pk11 = enc_pk11 + get_last_block_for_desired_mac(sig_key, enc_pk11, desired_mac)
|
||||
enc_pk11 = enc_pk11 + CMAC.new(sig_key, enc_pk11, AES).digest()
|
||||
return enc_pk11
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ def main(argc, argv):
|
||||
with open(argv[1], 'rb') as f:
|
||||
code = f.read()
|
||||
if len(code) & 0xF:
|
||||
code += bytearray(0x10 - (len(code) & 0xF))
|
||||
code = code + bytearray(0x10 - (len(code) & 0xF))
|
||||
# TODO: Support dev unit crypto
|
||||
with open(argv[2], 'wb') as f:
|
||||
f.write(sign_encrypt_code(code, KEYS.HOVI_SIG_KEY_PRD, KEYS.HOVI_ENC_KEY_PRD, KEYS.IV, b'THANKS_NVIDIA_<3'))
|
||||
|
||||
@@ -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
|
||||
|
||||
SUBFOLDERS := libstratosphere $(MODULES)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ endif
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source source/fs_mitm source/set_mitm source/bpc_mitm
|
||||
SOURCES := source source/fs_mitm source/set_mitm source/bpc_mitm source/ns_mitm
|
||||
DATA := data
|
||||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "fs_mitm/fsmitm_main.hpp"
|
||||
#include "set_mitm/setmitm_main.hpp"
|
||||
#include "bpc_mitm/bpcmitm_main.hpp"
|
||||
#include "ns_mitm/nsmitm_main.hpp"
|
||||
|
||||
static HosThread g_module_threads[MitmModuleId_Count];
|
||||
|
||||
@@ -35,6 +36,7 @@ static const struct {
|
||||
{ &FsMitmMain, FsMitmPriority, FsMitmStackSize }, /* FsMitm */
|
||||
{ &SetMitmMain, SetMitmPriority, SetMitmStackSize }, /* SetMitm */
|
||||
{ &BpcMitmMain, BpcMitmPriority, BpcMitmStackSize }, /* BpcMitm */
|
||||
{ &NsMitmMain, NsMitmPriority, NsMitmStackSize }, /* NsMitm */
|
||||
};
|
||||
|
||||
void LaunchAllMitmModules() {
|
||||
|
||||
@@ -20,6 +20,7 @@ enum MitmModuleId : u32 {
|
||||
MitmModuleId_FsMitm = 0,
|
||||
MitmModuleId_SetMitm = 1,
|
||||
MitmModuleId_BpcMitm = 2,
|
||||
MitmModuleId_NsMitm = 3,
|
||||
|
||||
/* Always keep this at the end. */
|
||||
MitmModuleId_Count,
|
||||
|
||||
38
stratosphere/ams_mitm/source/fs_mitm/fs_filesystem_types.hpp
Normal file
38
stratosphere/ams_mitm/source/fs_mitm/fs_filesystem_types.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
|
||||
|
||||
enum OpenMode {
|
||||
OpenMode_Read = (1 << 0),
|
||||
OpenMode_Write = (1 << 1),
|
||||
OpenMode_Append = (1 << 2),
|
||||
|
||||
OpenMode_ReadWrite = OpenMode_Read | OpenMode_Write,
|
||||
OpenMode_All = OpenMode_ReadWrite | OpenMode_Append,
|
||||
};
|
||||
|
||||
enum DirectoryOpenMode {
|
||||
DirectoryOpenMode_Directories = (1 << 0),
|
||||
DirectoryOpenMode_Files = (1 << 1),
|
||||
|
||||
DirectoryOpenMode_All = (DirectoryOpenMode_Directories | DirectoryOpenMode_Files),
|
||||
};
|
||||
|
||||
enum DirectoryEntryType {
|
||||
DirectoryEntryType_Directory,
|
||||
DirectoryEntryType_File,
|
||||
};
|
||||
120
stratosphere/ams_mitm/source/fs_mitm/fs_idirectory.hpp
Normal file
120
stratosphere/ams_mitm/source/fs_mitm/fs_idirectory.hpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 "fs_results.hpp"
|
||||
|
||||
enum FsIDirectoryCmd : u32 {
|
||||
FsIDirectoryCmd_Read = 0,
|
||||
FsIDirectoryCmd_GetEntryCount = 1,
|
||||
};
|
||||
|
||||
class IDirectory {
|
||||
public:
|
||||
virtual ~IDirectory() {}
|
||||
|
||||
Result Read(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) {
|
||||
if (out_count == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
if (max_entries == 0) {
|
||||
*out_count = 0;
|
||||
return 0;
|
||||
}
|
||||
if (out_entries == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return ReadImpl(out_count, out_entries, max_entries);
|
||||
}
|
||||
|
||||
Result GetEntryCount(uint64_t *count) {
|
||||
if (count == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetEntryCountImpl(count);
|
||||
}
|
||||
|
||||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result ReadImpl(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) = 0;
|
||||
virtual Result GetEntryCountImpl(uint64_t *count) = 0;
|
||||
};
|
||||
|
||||
class IDirectoryInterface : public IServiceObject {
|
||||
private:
|
||||
std::unique_ptr<IDirectory> base_dir;
|
||||
public:
|
||||
IDirectoryInterface(IDirectory *d) : base_dir(d) {
|
||||
/* ... */
|
||||
};
|
||||
IDirectoryInterface(std::unique_ptr<IDirectory> d) : base_dir(std::move(d)) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result Read(OutBuffer<FsDirectoryEntry> buffer, Out<u64> out_count) final {
|
||||
return this->base_dir->Read(out_count.GetPointer(), buffer.buffer, buffer.num_elements);
|
||||
};
|
||||
virtual Result GetEntryCount(Out<u64> out_count) final {
|
||||
return this->base_dir->GetEntryCount(out_count.GetPointer());
|
||||
};
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MakeServiceCommandMeta<FsIDirectoryCmd_Read, &IDirectoryInterface::Read>(),
|
||||
MakeServiceCommandMeta<FsIDirectoryCmd_GetEntryCount, &IDirectoryInterface::GetEntryCount>(),
|
||||
};
|
||||
};
|
||||
|
||||
class ProxyDirectory : public IDirectory {
|
||||
private:
|
||||
std::unique_ptr<FsDir> base_dir;
|
||||
public:
|
||||
ProxyDirectory(FsDir *d) : base_dir(d) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyDirectory(std::unique_ptr<FsDir> d) : base_dir(std::move(d)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyDirectory(FsDir d) {
|
||||
this->base_dir = std::make_unique<FsDir>(d);
|
||||
}
|
||||
|
||||
virtual ~ProxyDirectory() {
|
||||
fsDirClose(this->base_dir.get());
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) {
|
||||
size_t count;
|
||||
|
||||
Result rc = fsDirRead(this->base_dir.get(), 0, &count, max_entries, out_entries);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out_count = count;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
virtual Result GetEntryCountImpl(uint64_t *count) {
|
||||
return fsDirGetEntryCount(this->base_dir.get(), count);
|
||||
}
|
||||
};
|
||||
195
stratosphere/ams_mitm/source/fs_mitm/fs_ifile.hpp
Normal file
195
stratosphere/ams_mitm/source/fs_mitm/fs_ifile.hpp
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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 "fs_results.hpp"
|
||||
#include "fs_shim.h"
|
||||
|
||||
enum FsIFileCmd : u32 {
|
||||
FsIFileCmd_Read = 0,
|
||||
FsIFileCmd_Write = 1,
|
||||
FsIFileCmd_Flush = 2,
|
||||
FsIFileCmd_SetSize = 3,
|
||||
FsIFileCmd_GetSize = 4,
|
||||
FsIFileCmd_OperateRange = 5,
|
||||
};
|
||||
|
||||
class IFile {
|
||||
public:
|
||||
virtual ~IFile() {}
|
||||
|
||||
Result Read(uint64_t *out, uint64_t offset, void *buffer, uint64_t size, uint32_t flags) {
|
||||
(void)(flags);
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
if (size == 0) {
|
||||
*out = 0;
|
||||
return 0;
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return ReadImpl(out, offset, buffer, size);
|
||||
}
|
||||
|
||||
Result GetSize(uint64_t *out) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetSizeImpl(out);
|
||||
}
|
||||
|
||||
Result Flush() {
|
||||
return FlushImpl();
|
||||
}
|
||||
|
||||
Result Write(uint64_t offset, void *buffer, uint64_t size, uint32_t flags) {
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
const bool flush = (flags & 1) != 0;
|
||||
return WriteImpl(offset, buffer, size, flush);
|
||||
}
|
||||
|
||||
Result Write(uint64_t offset, void *buffer, uint64_t size) {
|
||||
return WriteImpl(offset, buffer, size, false);
|
||||
}
|
||||
|
||||
Result SetSize(uint64_t size) {
|
||||
return SetSizeImpl(size);
|
||||
}
|
||||
|
||||
Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
||||
if (operation_type == 3) {
|
||||
return OperateRangeImpl(operation_type, offset, size, out_range_info);
|
||||
}
|
||||
return ResultFsUnsupportedOperation;
|
||||
}
|
||||
|
||||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) = 0;
|
||||
virtual Result GetSizeImpl(u64 *out) = 0;
|
||||
virtual Result FlushImpl() = 0;
|
||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, bool flush) = 0;
|
||||
virtual Result SetSizeImpl(u64 size) = 0;
|
||||
virtual Result OperateRangeImpl(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
||||
};
|
||||
|
||||
class IFileInterface : public IServiceObject {
|
||||
private:
|
||||
std::unique_ptr<IFile> base_file;
|
||||
public:
|
||||
IFileInterface(IFile *f) : base_file(f) {
|
||||
/* ... */
|
||||
};
|
||||
IFileInterface(std::unique_ptr<IFile> f) : base_file(std::move(f)) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result Read(OutBuffer<u8, BufferType_Type1> buffer, Out<u64> out_read, u64 offset, u64 size, u32 read_flags) final {
|
||||
return this->base_file->Read(out_read.GetPointer(), offset, buffer.buffer, std::min(buffer.num_elements, size), read_flags);
|
||||
};
|
||||
virtual Result Write(InBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size, u32 write_flags) final {
|
||||
return this->base_file->Write(offset, buffer.buffer, std::min(buffer.num_elements, size), write_flags);
|
||||
};
|
||||
virtual Result Flush() final {
|
||||
return this->base_file->Flush();
|
||||
};
|
||||
virtual Result SetSize(u64 size) final {
|
||||
return this->base_file->SetSize(size);
|
||||
};
|
||||
virtual Result GetSize(Out<u64> size) final {
|
||||
return this->base_file->GetSize(size.GetPointer());
|
||||
};
|
||||
virtual Result OperateRange(Out<FsRangeInfo> range_info, u32 operation_type, u64 offset, u64 size) final {
|
||||
return this->base_file->OperateRange(operation_type, offset, size, range_info.GetPointer());
|
||||
};
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MakeServiceCommandMeta<FsIFileCmd_Read, &IFileInterface::Read>(),
|
||||
MakeServiceCommandMeta<FsIFileCmd_Write, &IFileInterface::Write>(),
|
||||
MakeServiceCommandMeta<FsIFileCmd_Flush, &IFileInterface::Flush>(),
|
||||
MakeServiceCommandMeta<FsIFileCmd_SetSize, &IFileInterface::SetSize>(),
|
||||
MakeServiceCommandMeta<FsIFileCmd_GetSize, &IFileInterface::GetSize>(),
|
||||
|
||||
/* 4.0.0- */
|
||||
MakeServiceCommandMeta<FsIFileCmd_OperateRange, &IFileInterface::OperateRange, FirmwareVersion_400>(),
|
||||
};
|
||||
};
|
||||
|
||||
class ProxyFile : public IFile {
|
||||
private:
|
||||
std::unique_ptr<FsFile> base_file;
|
||||
public:
|
||||
ProxyFile(FsFile *f) : base_file(f) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFile(std::unique_ptr<FsFile> f) : base_file(std::move(f)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFile(FsFile f) {
|
||||
this->base_file = std::make_unique<FsFile>(f);
|
||||
}
|
||||
|
||||
virtual ~ProxyFile() {
|
||||
fsFileClose(this->base_file.get());
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) {
|
||||
size_t out_sz;
|
||||
|
||||
Result rc = fsFileRead(this->base_file.get(), offset, buffer, size, &out_sz);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = out_sz;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
virtual Result GetSizeImpl(u64 *out) {
|
||||
return fsFileGetSize(this->base_file.get(), out);
|
||||
}
|
||||
virtual Result FlushImpl() {
|
||||
return fsFileFlush(this->base_file.get());
|
||||
}
|
||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, bool flush) {
|
||||
Result rc = fsFileWrite(this->base_file.get(), offset, buffer, size);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
/* libnx doesn't allow passing the flush flag. */
|
||||
rc = fsFileFlush(this->base_file.get());
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
virtual Result SetSizeImpl(u64 size) {
|
||||
return fsFileSetSize(this->base_file.get(), size);
|
||||
}
|
||||
virtual Result OperateRangeImpl(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
||||
return fsFileOperateRange(this->base_file.get(), operation_type, offset, size, out_range_info);
|
||||
}
|
||||
};
|
||||
535
stratosphere/ams_mitm/source/fs_mitm/fs_ifilesystem.hpp
Normal file
535
stratosphere/ams_mitm/source/fs_mitm/fs_ifilesystem.hpp
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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 "../utils.hpp"
|
||||
|
||||
#include "fs_results.hpp"
|
||||
#include "fs_filesystem_types.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
#include "fs_ifile.hpp"
|
||||
#include "fs_idirectory.hpp"
|
||||
|
||||
enum FsIFileSystemCmd : u32 {
|
||||
/* 1.0.0+ */
|
||||
FsIFileSystemCmd_CreateFile = 0,
|
||||
FsIFileSystemCmd_DeleteFile = 1,
|
||||
FsIFileSystemCmd_CreateDirectory = 2,
|
||||
FsIFileSystemCmd_DeleteDirectory = 3,
|
||||
FsIFileSystemCmd_DeleteDirectoryRecursively = 4,
|
||||
FsIFileSystemCmd_RenameFile = 5,
|
||||
FsIFileSystemCmd_RenameDirectory = 6,
|
||||
FsIFileSystemCmd_GetEntryType = 7,
|
||||
FsIFileSystemCmd_OpenFile = 8,
|
||||
FsIFileSystemCmd_OpenDirectory = 9,
|
||||
FsIFileSystemCmd_Commit = 10,
|
||||
FsIFileSystemCmd_GetFreeSpaceSize = 11,
|
||||
FsIFileSystemCmd_GetTotalSpaceSize = 12,
|
||||
|
||||
/* 3.0.0+ */
|
||||
FsIFileSystemCmd_CleanDirectoryRecursively = 13,
|
||||
FsIFileSystemCmd_GetFileTimeStampRaw = 14,
|
||||
|
||||
/* 4.0.0+ */
|
||||
FsIFileSystemCmd_QueryEntry = 15,
|
||||
};
|
||||
|
||||
class IFile;
|
||||
class IDirectory;
|
||||
|
||||
class IFileSystem {
|
||||
public:
|
||||
virtual ~IFileSystem() {}
|
||||
|
||||
Result CreateFile(FsPath &path, uint64_t size, int flags) {
|
||||
return CreateFileImpl(path, size, flags);
|
||||
}
|
||||
|
||||
Result DeleteFile(FsPath &path) {
|
||||
return DeleteFileImpl(path);
|
||||
}
|
||||
|
||||
Result CreateDirectory(FsPath &path) {
|
||||
return CreateDirectoryImpl(path);
|
||||
}
|
||||
|
||||
Result DeleteDirectory(FsPath &path) {
|
||||
return DeleteDirectoryImpl(path);
|
||||
}
|
||||
|
||||
Result DeleteDirectoryRecursively(FsPath &path) {
|
||||
return DeleteDirectoryRecursivelyImpl(path);
|
||||
}
|
||||
|
||||
Result RenameFile(FsPath &old_path, FsPath &new_path) {
|
||||
return RenameFileImpl(old_path, new_path);
|
||||
}
|
||||
|
||||
Result RenameDirectory(FsPath &old_path, FsPath &new_path) {
|
||||
return RenameDirectoryImpl(old_path, new_path);
|
||||
}
|
||||
|
||||
Result GetEntryType(DirectoryEntryType *out, FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetEntryTypeImpl(out, path);
|
||||
}
|
||||
|
||||
Result OpenFile(std::unique_ptr<IFile> &out_file, FsPath &path, OpenMode mode) {
|
||||
if (!(mode & OpenMode_ReadWrite)) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
if (mode & ~OpenMode_All) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
return OpenFileImpl(out_file, path, mode);
|
||||
}
|
||||
|
||||
Result OpenDirectory(std::unique_ptr<IDirectory> &out_dir, FsPath &path, DirectoryOpenMode mode) {
|
||||
if (!(mode & DirectoryOpenMode_All)) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
if (mode & ~DirectoryOpenMode_All) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
return OpenDirectoryImpl(out_dir, path, mode);
|
||||
}
|
||||
|
||||
Result Commit() {
|
||||
return CommitImpl();
|
||||
}
|
||||
|
||||
Result GetFreeSpaceSize(uint64_t *out, FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetFreeSpaceSizeImpl(out, path);
|
||||
}
|
||||
|
||||
Result GetTotalSpaceSize(uint64_t *out, FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetTotalSpaceSizeImpl(out, path);
|
||||
}
|
||||
|
||||
Result CleanDirectoryRecursively(FsPath &path) {
|
||||
return CleanDirectoryRecursivelyImpl(path);
|
||||
}
|
||||
|
||||
Result GetFileTimeStampRaw(FsTimeStampRaw *out, FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetFileTimeStampRawImpl(out, path);
|
||||
}
|
||||
|
||||
Result QueryEntry(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) {
|
||||
return QueryEntryImpl(out, out_size, in, in_size, query, path);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result CreateFileImpl(FsPath &path, uint64_t size, int flags) = 0;
|
||||
virtual Result DeleteFileImpl(FsPath &path) = 0;
|
||||
virtual Result CreateDirectoryImpl(FsPath &path) = 0;
|
||||
virtual Result DeleteDirectoryImpl(FsPath &path) = 0;
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(FsPath &path) = 0;
|
||||
virtual Result RenameFileImpl(FsPath &old_path, FsPath &new_path) = 0;
|
||||
virtual Result RenameDirectoryImpl(FsPath &old_path, FsPath &new_path) = 0;
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, FsPath &path) = 0;
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, FsPath &path, OpenMode mode) = 0;
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, FsPath &path, DirectoryOpenMode mode) = 0;
|
||||
virtual Result CommitImpl() = 0;
|
||||
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
virtual Result CleanDirectoryRecursivelyImpl(FsPath &path) = 0;
|
||||
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(out_size);
|
||||
(void)(in);
|
||||
(void)(in_size);
|
||||
(void)(query);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
};
|
||||
|
||||
class IFileSystemInterface : public IServiceObject {
|
||||
private:
|
||||
std::unique_ptr<IFileSystem> base_fs;
|
||||
public:
|
||||
IFileSystemInterface(IFileSystem *fs) : base_fs(fs) {
|
||||
/* ... */
|
||||
};
|
||||
IFileSystemInterface(std::unique_ptr<IFileSystem> fs) : base_fs(std::move(fs)) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result CreateFile(InPointer<char> in_path, uint64_t size, int flags) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->CreateFile(path, size, flags);
|
||||
}
|
||||
|
||||
virtual Result DeleteFile(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->DeleteFile(path);
|
||||
}
|
||||
|
||||
virtual Result CreateDirectory(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->CreateDirectory(path);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectory(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->DeleteDirectory(path);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectoryRecursively(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
virtual Result RenameFile(InPointer<char> in_old_path, InPointer<char> in_new_path) final {
|
||||
FsPath old_path;
|
||||
FsPath new_path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&old_path, in_old_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&new_path, in_new_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->RenameFile(old_path, new_path);
|
||||
}
|
||||
|
||||
virtual Result RenameDirectory(InPointer<char> in_old_path, InPointer<char> in_new_path) final {
|
||||
FsPath old_path;
|
||||
FsPath new_path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&old_path, in_old_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&new_path, in_new_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->RenameDirectory(old_path, new_path);
|
||||
}
|
||||
|
||||
|
||||
virtual Result GetEntryType(Out<u32> out_type, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
DirectoryEntryType type;
|
||||
rc = this->base_fs->GetEntryType(&type, path);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_type.SetValue(type);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtual Result OpenFile(Out<std::shared_ptr<IFileInterface>> out_intf, InPointer<char> in_path, uint32_t mode) final {
|
||||
FsPath path;
|
||||
std::unique_ptr<IFile> out_file;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = this->base_fs->OpenFile(out_file, path, static_cast<OpenMode>(mode));
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_intf.SetValue(std::make_shared<IFileInterface>(std::move(out_file)));
|
||||
/* TODO: Nintendo checks allocation success here, should we?. */
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtual Result OpenDirectory(Out<std::shared_ptr<IDirectoryInterface>> out_intf, InPointer<char> in_path, uint32_t mode) final {
|
||||
FsPath path;
|
||||
std::unique_ptr<IDirectory> out_dir;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = this->base_fs->OpenDirectory(out_dir, path, static_cast<DirectoryOpenMode>(mode));
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_intf.SetValue(std::make_shared<IDirectoryInterface>(std::move(out_dir)));
|
||||
/* TODO: Nintendo checks allocation success here, should we?. */
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtual Result Commit() final {
|
||||
return this->base_fs->Commit();
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSize(Out<uint64_t> out_size, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetFreeSpaceSize(out_size.GetPointer(), path);
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSize(Out<uint64_t> out_size, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetTotalSpaceSize(out_size.GetPointer(), path);
|
||||
}
|
||||
|
||||
virtual Result CleanDirectoryRecursively(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
virtual Result GetFileTimeStampRaw(Out<FsTimeStampRaw> out_timestamp, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetFileTimeStampRaw(out_timestamp.GetPointer(), path);
|
||||
}
|
||||
|
||||
virtual Result QueryEntry(OutBuffer<char, BufferType_Type1> out_buffer, InBuffer<char, BufferType_Type1> in_buffer, int query, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED((rc = FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->QueryEntry(out_buffer.buffer, out_buffer.num_elements, in_buffer.buffer, in_buffer.num_elements, query, path);
|
||||
}
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_CreateFile, &IFileSystemInterface::CreateFile>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_DeleteFile, &IFileSystemInterface::DeleteFile>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_CreateDirectory, &IFileSystemInterface::CreateDirectory>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_DeleteDirectory, &IFileSystemInterface::DeleteDirectory>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_DeleteDirectoryRecursively, &IFileSystemInterface::DeleteDirectoryRecursively>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_RenameFile, &IFileSystemInterface::RenameFile>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_RenameDirectory, &IFileSystemInterface::RenameDirectory>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_GetEntryType, &IFileSystemInterface::GetEntryType>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_OpenFile, &IFileSystemInterface::OpenFile>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_OpenDirectory, &IFileSystemInterface::OpenDirectory>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_Commit, &IFileSystemInterface::Commit>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_GetFreeSpaceSize, &IFileSystemInterface::GetFreeSpaceSize>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_GetTotalSpaceSize, &IFileSystemInterface::GetTotalSpaceSize>(),
|
||||
|
||||
/* 3.0.0- */
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_CleanDirectoryRecursively, &IFileSystemInterface::CleanDirectoryRecursively, FirmwareVersion_300>(),
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_GetFileTimeStampRaw, &IFileSystemInterface::GetFileTimeStampRaw, FirmwareVersion_300>(),
|
||||
|
||||
/* 4.0.0- */
|
||||
MakeServiceCommandMeta<FsIFileSystemCmd_QueryEntry, &IFileSystemInterface::QueryEntry, FirmwareVersion_400>(),
|
||||
};
|
||||
};
|
||||
|
||||
class ProxyFileSystem : public IFileSystem {
|
||||
private:
|
||||
std::unique_ptr<FsFileSystem> base_fs;
|
||||
public:
|
||||
ProxyFileSystem(FsFileSystem *fs) : base_fs(fs) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFileSystem(std::unique_ptr<FsFileSystem> fs) : base_fs(std::move(fs)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFileSystem(FsFileSystem fs) {
|
||||
this->base_fs = std::make_unique<FsFileSystem>(fs);
|
||||
}
|
||||
|
||||
virtual ~ProxyFileSystem() {
|
||||
fsFsClose(this->base_fs.get());
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Result CreateFileImpl(FsPath &path, uint64_t size, int flags) {
|
||||
return fsFsCreateFile(this->base_fs.get(), path.str, size, flags);
|
||||
}
|
||||
|
||||
virtual Result DeleteFileImpl(FsPath &path) {
|
||||
return fsFsDeleteFile(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result CreateDirectoryImpl(FsPath &path) {
|
||||
return fsFsCreateDirectory(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectoryImpl(FsPath &path) {
|
||||
return fsFsDeleteDirectory(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(FsPath &path) {
|
||||
return fsFsDeleteDirectoryRecursively(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result RenameFileImpl(FsPath &old_path, FsPath &new_path) {
|
||||
return fsFsRenameFile(this->base_fs.get(), old_path.str, new_path.str);
|
||||
}
|
||||
|
||||
virtual Result RenameDirectoryImpl(FsPath &old_path, FsPath &new_path) {
|
||||
return fsFsRenameDirectory(this->base_fs.get(), old_path.str, new_path.str);
|
||||
}
|
||||
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, FsPath &path) {
|
||||
FsEntryType type;
|
||||
|
||||
Result rc = fsFsGetEntryType(this->base_fs.get(), path.str, &type);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = static_cast<DirectoryEntryType>(static_cast<u32>(type));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, FsPath &path, OpenMode mode) {
|
||||
FsFile f;
|
||||
|
||||
Result rc = fsFsOpenFile(this->base_fs.get(), path.str, static_cast<int>(mode), &f);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_file = std::make_unique<ProxyFile>(f);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, FsPath &path, DirectoryOpenMode mode) {
|
||||
FsDir d;
|
||||
|
||||
Result rc = fsFsOpenDirectory(this->base_fs.get(), path.str, static_cast<int>(mode), &d);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_dir = std::make_unique<ProxyDirectory>(d);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtual Result CommitImpl() {
|
||||
return fsFsCommit(this->base_fs.get());
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, FsPath &path) {
|
||||
return fsFsGetFreeSpace(this->base_fs.get(), path.str, out);
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, FsPath &path) {
|
||||
return fsFsGetTotalSpace(this->base_fs.get(), path.str, out);
|
||||
}
|
||||
|
||||
virtual Result CleanDirectoryRecursivelyImpl(FsPath &path) {
|
||||
return fsFsCleanDirectoryRecursively(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, FsPath &path) {
|
||||
return fsFsGetFileTimeStampRaw(this->base_fs.get(), path.str, out);
|
||||
}
|
||||
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) {
|
||||
return fsFsQueryEntry(this->base_fs.get(), out, out_size, in, in_size, path.str,static_cast<FsFileSystemQueryType>(query));
|
||||
}
|
||||
};
|
||||
@@ -19,6 +19,8 @@
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_shim.h"
|
||||
|
||||
#include "fs_results.hpp"
|
||||
|
||||
#include "../debug.hpp"
|
||||
|
||||
enum FsIStorageCmd : u32 {
|
||||
@@ -33,13 +35,17 @@ enum FsIStorageCmd : u32 {
|
||||
class IStorage {
|
||||
public:
|
||||
virtual ~IStorage();
|
||||
|
||||
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
|
||||
virtual Result Write(void *buffer, size_t size, u64 offset) = 0;
|
||||
virtual Result Flush() = 0;
|
||||
virtual Result SetSize(u64 size) = 0;
|
||||
virtual Result GetSize(u64 *out_size) = 0;
|
||||
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
||||
|
||||
static inline bool IsRangeValid(uint64_t offset, uint64_t size, uint64_t total_size) {
|
||||
return size <= total_size && offset <= total_size - size;
|
||||
}
|
||||
};
|
||||
|
||||
class IStorageInterface : public IServiceObject {
|
||||
@@ -49,7 +55,7 @@ class IStorageInterface : public IServiceObject {
|
||||
IStorageInterface(IStorage *s) : base_storage(s) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
|
||||
~IStorageInterface() {
|
||||
delete base_storage;
|
||||
};
|
||||
@@ -95,14 +101,14 @@ class IROStorage : public IStorage {
|
||||
(void)(buffer);
|
||||
(void)(offset);
|
||||
(void)(size);
|
||||
return 0x313802;
|
||||
return ResultFsUnsupportedOperation;
|
||||
};
|
||||
virtual Result Flush() final {
|
||||
return 0x0;
|
||||
};
|
||||
virtual Result SetSize(u64 size) final {
|
||||
(void)(size);
|
||||
return 0x313802;
|
||||
return ResultFsUnsupportedOperation;
|
||||
};
|
||||
virtual Result GetSize(u64 *out_size) = 0;
|
||||
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
||||
|
||||
267
stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.cpp
Normal file
267
stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <switch.h>
|
||||
#include "fs_path_utils.hpp"
|
||||
#include "fs_results.hpp"
|
||||
|
||||
Result FsPathUtils::VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) {
|
||||
const char *cur = path;
|
||||
size_t name_len = 0;
|
||||
|
||||
for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) {
|
||||
const char c = *(cur++);
|
||||
/* If terminated, we're done. */
|
||||
if (c == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
|
||||
/* We should do this. */
|
||||
|
||||
/* Banned characters: :*?<>| */
|
||||
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|') {
|
||||
return ResultFsInvalidCharacter;
|
||||
}
|
||||
|
||||
name_len++;
|
||||
/* Check for separator. */
|
||||
if (c == '/' || c == '\\') {
|
||||
name_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
Result FsPathUtils::ConvertPathForServiceObject(FsPath *out, const char *path) {
|
||||
/* Check for nullptr. */
|
||||
if (out == nullptr || path == nullptr) {
|
||||
return ResultFsInvalidPath;
|
||||
}
|
||||
|
||||
/* Copy string, NULL terminate. */
|
||||
/* NOTE: Nintendo adds an extra char at 0x301 for NULL terminator */
|
||||
/* But then forces 0x300 to NULL anyway... */
|
||||
std::strncpy(out->str, path, sizeof(out->str) - 1);
|
||||
out->str[sizeof(out->str)-1] = 0;
|
||||
|
||||
/* Replace any instances of \ with / */
|
||||
for (size_t i = 0; i < sizeof(out->str); i++) {
|
||||
if (out->str[i] == '\\') {
|
||||
out->str[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
/* Nintendo allows some liberties if the path is a windows path. Who knows why... */
|
||||
const auto prefix_len = FsPathUtils::IsWindowsAbsolutePath(path) ? 2 : 0;
|
||||
const size_t max_len = (FS_MAX_PATH-1) - prefix_len;
|
||||
return FsPathUtils::VerifyPath(out->str + prefix_len, max_len, max_len);
|
||||
}
|
||||
|
||||
Result FsPathUtils::IsNormalized(bool *out, const char *path) {
|
||||
/* Nintendo uses a state machine here. */
|
||||
enum class PathState {
|
||||
Start,
|
||||
Normal,
|
||||
FirstSeparator,
|
||||
Separator,
|
||||
CurrentDir,
|
||||
ParentDir,
|
||||
WindowsDriveLetter,
|
||||
};
|
||||
|
||||
PathState state = PathState::Start;
|
||||
|
||||
for (const char *cur = path; *cur != 0; cur++) {
|
||||
const char c = *cur;
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
if (IsWindowsDriveLetter(c)) {
|
||||
state = PathState::WindowsDriveLetter;
|
||||
} else if (c == '/') {
|
||||
state = PathState::FirstSeparator;
|
||||
} else {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
break;
|
||||
case PathState::Normal:
|
||||
if (c == '/') {
|
||||
state = PathState::Separator;
|
||||
}
|
||||
break;
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Separator:
|
||||
/* It is unclear why first separator and separator are separate states... */
|
||||
if (c == '/') {
|
||||
*out = false;
|
||||
return 0;
|
||||
} else if (c == '.') {
|
||||
state = PathState::CurrentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
if (c == '/') {
|
||||
*out = false;
|
||||
return 0;
|
||||
} else if (c == '.') {
|
||||
state = PathState::ParentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::ParentDir:
|
||||
if (c == '/') {
|
||||
*out = false;
|
||||
return 0;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::WindowsDriveLetter:
|
||||
if (c == ':') {
|
||||
*out = true;
|
||||
return 0;
|
||||
} else {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
case PathState::WindowsDriveLetter:
|
||||
return ResultFsInvalidPathFormat;
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Normal:
|
||||
*out = true;
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
case PathState::ParentDir:
|
||||
case PathState::Separator:
|
||||
*out = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result FsPathUtils::Normalize(char *out, size_t max_out_size, const char *src, size_t *out_len) {
|
||||
/* Paths must start with / */
|
||||
if (src[0] != '/') {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
|
||||
bool skip_next_sep = false;
|
||||
size_t i = 0;
|
||||
size_t len = 0;
|
||||
|
||||
while (src[i] != 0) {
|
||||
if (src[i] == '/') {
|
||||
/* Swallow separators. */
|
||||
while (src[++i] == '/') { }
|
||||
if (src[i] == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle skip if needed */
|
||||
if (!skip_next_sep) {
|
||||
if (len + 1 == max_out_size) {
|
||||
out[len] = 0;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
out[len++] = '/';
|
||||
|
||||
/* TODO: N has some weird windows support stuff here under a bool. */
|
||||
/* Boolean is normally false though? */
|
||||
}
|
||||
skip_next_sep = false;
|
||||
}
|
||||
|
||||
/* See length of current dir. */
|
||||
size_t dir_len = 0;
|
||||
while (src[i+dir_len] != '/' && src[i+dir_len] != 0) {
|
||||
dir_len++;
|
||||
}
|
||||
|
||||
if (FsPathUtils::IsCurrentDirectory(&src[i])) {
|
||||
skip_next_sep = true;
|
||||
} else if (FsPathUtils::IsParentDirectory(&src[i])) {
|
||||
if (len == 1) {
|
||||
return ResultFsDirectoryUnobtainable;
|
||||
}
|
||||
|
||||
/* Walk up a directory. */
|
||||
len -= 2;
|
||||
while (out[len] != '/') {
|
||||
len--;
|
||||
}
|
||||
} else {
|
||||
/* Copy, possibly truncating. */
|
||||
if (len + dir_len + 1 <= max_out_size) {
|
||||
for (size_t j = 0; j < dir_len; j++) {
|
||||
out[len++] = src[i+j];
|
||||
}
|
||||
} else {
|
||||
const size_t copy_len = max_out_size - 1 - len;
|
||||
for (size_t j = 0; j < copy_len; j++) {
|
||||
out[len++] = src[i+j];
|
||||
}
|
||||
out[len] = 0;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
}
|
||||
|
||||
i += dir_len;
|
||||
}
|
||||
|
||||
if (skip_next_sep) {
|
||||
len--;
|
||||
}
|
||||
|
||||
if (len == 0 && max_out_size) {
|
||||
out[len++] = '/';
|
||||
}
|
||||
|
||||
if (max_out_size < len - 1) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* NULL terminate. */
|
||||
out[len] = 0;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
|
||||
/* Assert normalized. */
|
||||
bool normalized = false;
|
||||
if (R_FAILED(FsPathUtils::IsNormalized(&normalized, out)) || !normalized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
48
stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.hpp
Normal file
48
stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.hpp
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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
struct FsPath {
|
||||
char str[FS_MAX_PATH];
|
||||
};
|
||||
|
||||
class FsPathUtils {
|
||||
public:
|
||||
static Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len);
|
||||
static Result ConvertPathForServiceObject(FsPath *out, const char *path);
|
||||
|
||||
static Result IsNormalized(bool *out, const char *path);
|
||||
static Result Normalize(char *out, size_t max_out_size, const char *src, size_t *out_size);
|
||||
|
||||
static bool IsWindowsDriveLetter(const char c) {
|
||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||
}
|
||||
|
||||
static bool IsCurrentDirectory(const char *path) {
|
||||
return path[0] == '.' && (path[1] == 0 || path[1] == '/');
|
||||
}
|
||||
|
||||
static bool IsParentDirectory(const char *path) {
|
||||
return path[0] == '.' && path[1] == '.' && (path[2] == 0 || path[2] == '/');
|
||||
}
|
||||
|
||||
static bool IsWindowsAbsolutePath(const char *path) {
|
||||
/* Nintendo uses this in path comparisons... */
|
||||
return IsWindowsDriveLetter(path[0]) && path[1] == ':';
|
||||
}
|
||||
};
|
||||
39
stratosphere/ams_mitm/source/fs_mitm/fs_results.hpp
Normal file
39
stratosphere/ams_mitm/source/fs_mitm/fs_results.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
static constexpr u32 Module_Fs = 2;
|
||||
|
||||
static constexpr Result ResultFsNotImplemented = MAKERESULT(Module_Fs, 3001);
|
||||
static constexpr Result ResultFsOutOfRange = MAKERESULT(Module_Fs, 3005);
|
||||
|
||||
static constexpr Result ResultFsAllocationFailureInSubDirectoryFileSystem = MAKERESULT(Module_Fs, 3355);
|
||||
|
||||
static constexpr Result ResultFsInvalidArgument = MAKERESULT(Module_Fs, 6001);
|
||||
static constexpr Result ResultFsInvalidPath = MAKERESULT(Module_Fs, 6002);
|
||||
static constexpr Result ResultFsTooLongPath = MAKERESULT(Module_Fs, 6003);
|
||||
static constexpr Result ResultFsInvalidCharacter = MAKERESULT(Module_Fs, 6004);
|
||||
static constexpr Result ResultFsInvalidPathFormat = MAKERESULT(Module_Fs, 6005);
|
||||
static constexpr Result ResultFsDirectoryUnobtainable = MAKERESULT(Module_Fs, 6006);
|
||||
static constexpr Result ResultFsNotNormalized = MAKERESULT(Module_Fs, 6007);
|
||||
|
||||
static constexpr Result ResultFsInvalidOffset = MAKERESULT(Module_Fs, 6061);
|
||||
static constexpr Result ResultFsInvalidSize = MAKERESULT(Module_Fs, 6062);
|
||||
static constexpr Result ResultFsNullptrArgument = MAKERESULT(Module_Fs, 6063);
|
||||
|
||||
static constexpr Result ResultFsUnsupportedOperation = MAKERESULT(Module_Fs, 6300);
|
||||
@@ -132,6 +132,98 @@ Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType) {
|
||||
if (hosversionBefore(2, 0, 0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 fsType;
|
||||
u64 titleId;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 7;
|
||||
raw->fsType = fsType;
|
||||
raw->titleId = titleId;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath) {
|
||||
if (hosversionBefore(2, 0, 0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
char sendStr[FS_MAX_PATH] = {0};
|
||||
strncpy(sendStr, contentPath, sizeof(sendStr)-1);
|
||||
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendStatic(&c, sendStr, sizeof(sendStr), 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 fsType;
|
||||
u64 titleId;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 8;
|
||||
raw->fsType = fsType;
|
||||
raw->titleId = titleId;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Missing FS File commands. */
|
||||
Result fsFileOperateRange(FsFile* f, u32 op_id, u64 off, u64 len, FsRangeInfo *out) {
|
||||
IpcCommand c;
|
||||
|
||||
@@ -20,6 +20,8 @@ typedef struct {
|
||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, u32 PartitionId);
|
||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
|
||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out);
|
||||
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType);
|
||||
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath);
|
||||
|
||||
/* Missing FS File commands. */
|
||||
Result fsFileOperateRange(FsFile* f, u32 op_id, u64 off, u64 len, FsRangeInfo *out);
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
#include "fs_subdirectory_filesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
Result SubDirectoryFileSystem::Initialize(const char *bp) {
|
||||
if (strnlen(bp, FS_MAX_PATH) >= FS_MAX_PATH) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* Normalize the path. */
|
||||
char normal_path[FS_MAX_PATH + 1];
|
||||
size_t normal_path_len;
|
||||
Result rc = FsPathUtils::Normalize(normal_path, sizeof(normal_path), bp, &normal_path_len);
|
||||
if (R_FAILED(rc)) {
|
||||
/* N calls svcBreak here. */
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Ensure terminating '/' */
|
||||
if (normal_path[normal_path_len-1] != '/') {
|
||||
if (normal_path_len + 2 > sizeof(normal_path)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
strncat(normal_path, "/", 2);
|
||||
normal_path[sizeof(normal_path)-1] = 0;
|
||||
normal_path_len++;
|
||||
}
|
||||
|
||||
this->base_path_len = normal_path_len + 1;
|
||||
this->base_path = reinterpret_cast<char *>(malloc(this->base_path_len));
|
||||
if (this->base_path == nullptr) {
|
||||
return ResultFsAllocationFailureInSubDirectoryFileSystem;
|
||||
}
|
||||
|
||||
std::strncpy(this->base_path, normal_path, this->base_path_len);
|
||||
this->base_path[this->base_path_len-1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
if (this->base_path_len + strnlen(relative_path, FS_MAX_PATH) > out_size) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* Copy base path. */
|
||||
std::strncpy(out, this->base_path, out_size);
|
||||
out[out_size-1] = 0;
|
||||
|
||||
/* Normalize it. */
|
||||
return FsPathUtils::Normalize(out + this->base_path_len - 2, out_size - (this->base_path_len - 2), relative_path, nullptr);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CreateFileImpl(FsPath &path, uint64_t size, int flags) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->CreateFile(full_path, size, flags);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::DeleteFileImpl(FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->DeleteFile(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CreateDirectoryImpl(FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->CreateDirectory(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::DeleteDirectoryImpl(FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::DeleteDirectoryRecursivelyImpl(FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::RenameFileImpl(FsPath &old_path, FsPath &new_path) {
|
||||
Result rc;
|
||||
FsPath full_old_path, full_new_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_old_path, old_path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_new_path, new_path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->RenameFile(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::RenameDirectoryImpl(FsPath &old_path, FsPath &new_path) {
|
||||
Result rc;
|
||||
FsPath full_old_path, full_new_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_old_path, old_path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_new_path, new_path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->RenameDirectory(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetEntryType(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, FsPath &path, OpenMode mode) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->OpenFile(out_file, full_path, mode);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, FsPath &path, DirectoryOpenMode mode) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CommitImpl() {
|
||||
return this->base_fs->Commit();
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CleanDirectoryRecursivelyImpl(FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->CleanDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) {
|
||||
Result rc;
|
||||
FsPath full_path;
|
||||
|
||||
if (R_FAILED((rc = GetFullPath(full_path, path)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return this->base_fs->QueryEntry(out, out_size, in, in_size, query, full_path);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 "fs_ifilesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
class SubDirectoryFileSystem : public IFileSystem {
|
||||
private:
|
||||
std::shared_ptr<IFileSystem> base_fs;
|
||||
char *base_path = nullptr;
|
||||
size_t base_path_len = 0;
|
||||
|
||||
public:
|
||||
SubDirectoryFileSystem(IFileSystem *fs, const char *bp) : base_fs(fs) {
|
||||
Result rc = this->Initialize(bp);
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
}
|
||||
|
||||
SubDirectoryFileSystem(std::shared_ptr<IFileSystem> fs, const char *bp) : base_fs(fs) {
|
||||
Result rc = this->Initialize(bp);
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual ~SubDirectoryFileSystem() {
|
||||
if (this->base_path != nullptr) {
|
||||
free(this->base_path);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Result Initialize(const char *bp);
|
||||
protected:
|
||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
Result GetFullPath(FsPath &full_path, FsPath &relative_path) {
|
||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Result CreateFileImpl(FsPath &path, uint64_t size, int flags) override;
|
||||
virtual Result DeleteFileImpl(FsPath &path) override;
|
||||
virtual Result CreateDirectoryImpl(FsPath &path) override;
|
||||
virtual Result DeleteDirectoryImpl(FsPath &path) override;
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(FsPath &path) override;
|
||||
virtual Result RenameFileImpl(FsPath &old_path, FsPath &new_path) override;
|
||||
virtual Result RenameDirectoryImpl(FsPath &old_path, FsPath &new_path) override;
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, FsPath &path) override;
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, FsPath &path, OpenMode mode) override;
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, FsPath &path, DirectoryOpenMode mode) override;
|
||||
virtual Result CommitImpl() override;
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, FsPath &path) override;
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, FsPath &path) override;
|
||||
virtual Result CleanDirectoryRecursivelyImpl(FsPath &path) override;
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, FsPath &path) override;
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) override;
|
||||
};
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_results.hpp"
|
||||
|
||||
#include "fsmitm_layeredrom.hpp"
|
||||
#include "../utils.hpp"
|
||||
#include "../debug.hpp"
|
||||
@@ -54,7 +56,7 @@ Result LayeredRomFS::Read(void *buffer, size_t size, u64 offset) {
|
||||
/* Validate size. */
|
||||
u64 virt_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
|
||||
if (offset >= virt_size) {
|
||||
return 0x2F5A02;
|
||||
return ResultFsInvalidOffset;
|
||||
}
|
||||
if (virt_size - offset < size) {
|
||||
size = virt_size - offset;
|
||||
|
||||
@@ -49,7 +49,7 @@ void RomFSBuildContext::VisitDirectory(FsFileSystem *filesys, RomFSBuildDirector
|
||||
strcat(child->path + parent->path_len, this->dir_entry.name);
|
||||
|
||||
if (!this->AddDirectory(parent, child, NULL)) {
|
||||
delete child->path;
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
} else {
|
||||
child_dirs.push_back(child);
|
||||
@@ -72,7 +72,7 @@ void RomFSBuildContext::VisitDirectory(FsFileSystem *filesys, RomFSBuildDirector
|
||||
child->size = this->dir_entry.fileSize;
|
||||
|
||||
if (!this->AddFile(parent, child)) {
|
||||
delete child->path;
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
}
|
||||
} else {
|
||||
@@ -125,7 +125,7 @@ void RomFSBuildContext::VisitDirectory(RomFSBuildDirectoryContext *parent, u32 p
|
||||
child->source = this->cur_source_type;
|
||||
child->orig_offset = cur_file->offset;
|
||||
if (!this->AddFile(parent, child)) {
|
||||
delete child->path;
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
}
|
||||
if (cur_file->sibling == ROMFS_ENTRY_EMPTY) {
|
||||
@@ -153,7 +153,7 @@ void RomFSBuildContext::VisitDirectory(RomFSBuildDirectoryContext *parent, u32 p
|
||||
|
||||
RomFSBuildDirectoryContext *real = NULL;
|
||||
if (!this->AddDirectory(parent, child, &real)) {
|
||||
delete child->path;
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
}
|
||||
if (real == NULL) {
|
||||
@@ -246,8 +246,10 @@ void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
|
||||
this->file_hash_table_size = 4 * file_hash_table_entry_count;
|
||||
|
||||
/* Assign metadata pointers */
|
||||
RomFSHeader *header = new RomFSHeader({0});
|
||||
u8 *metadata = new u8[this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size];
|
||||
auto *header = reinterpret_cast<RomFSHeader*>(std::malloc(sizeof(RomFSHeader)));
|
||||
*header = {};
|
||||
const size_t metadata_size = this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size;
|
||||
u8 *metadata = reinterpret_cast<u8*>(std::malloc(metadata_size));
|
||||
u32 *dir_hash_table = (u32 *)((uintptr_t)metadata);
|
||||
RomFSDirectoryEntry *dir_table = (RomFSDirectoryEntry *)((uintptr_t)dir_hash_table + this->dir_hash_table_size);
|
||||
u32 *file_hash_table = (u32 *)((uintptr_t)dir_table + this->dir_table_size);
|
||||
@@ -372,7 +374,7 @@ void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
|
||||
/* Delete directories. */
|
||||
for (const auto &it : this->directories) {
|
||||
cur_dir = it.second;
|
||||
delete cur_dir->path;
|
||||
delete[] cur_dir->path;
|
||||
delete cur_dir;
|
||||
}
|
||||
this->root = NULL;
|
||||
@@ -381,7 +383,7 @@ void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
|
||||
/* Delete files. */
|
||||
for (const auto &it : this->files) {
|
||||
cur_file = it.second;
|
||||
delete cur_file->path;
|
||||
delete[] cur_file->path;
|
||||
delete cur_file;
|
||||
}
|
||||
this->files.clear();
|
||||
@@ -397,13 +399,11 @@ void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
|
||||
header->dir_table_ofs = header->dir_hash_table_ofs + header->dir_hash_table_size;
|
||||
header->file_hash_table_ofs = header->dir_table_ofs + header->dir_table_size;
|
||||
header->file_table_ofs = header->file_hash_table_ofs + header->file_hash_table_size;
|
||||
|
||||
const size_t metadata_size = this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size;
|
||||
|
||||
|
||||
/* Try to save metadata to the SD card, to save on memory space. */
|
||||
if (R_SUCCEEDED(Utils::SaveSdFileForAtmosphere(this->title_id, ROMFS_METADATA_FILE_PATH, metadata, metadata_size))) {
|
||||
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, RomFSDataSource::MetaData);
|
||||
delete metadata;
|
||||
std::free(metadata);
|
||||
} else {
|
||||
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, metadata, RomFSDataSource::Memory);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <switch.h>
|
||||
#include <map>
|
||||
|
||||
@@ -120,10 +121,10 @@ struct RomFSSourceInfo {
|
||||
case RomFSDataSource::MetaData:
|
||||
break;
|
||||
case RomFSDataSource::LooseFile:
|
||||
delete this->loose_source_info.path;
|
||||
delete[] this->loose_source_info.path;
|
||||
break;
|
||||
case RomFSDataSource::Memory:
|
||||
delete this->memory_source_info.data;
|
||||
std::free((void*)this->memory_source_info.data);
|
||||
break;
|
||||
default:
|
||||
fatalSimple(0xF601);
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "fsmitm_romstorage.hpp"
|
||||
#include "fsmitm_layeredrom.hpp"
|
||||
|
||||
#include "fs_subdirectory_filesystem.hpp"
|
||||
|
||||
#include "../debug.hpp"
|
||||
|
||||
static HosMutex g_StorageCacheLock;
|
||||
@@ -80,6 +82,81 @@ void FsMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx
|
||||
}
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenHblWebContentFileSystem(Out<std::shared_ptr<IFileSystemInterface>> &out_fs) {
|
||||
std::shared_ptr<IFileSystemInterface> fs = nullptr;
|
||||
u32 out_domain_id = 0;
|
||||
Result rc = 0;
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_fs.SetValue(std::move(fs));
|
||||
if (out_fs.IsDomain()) {
|
||||
out_fs.ChangeObjectId(out_domain_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Mount the SD card using fs.mitm's session. */
|
||||
FsFileSystem sd_fs;
|
||||
rc = fsMountSdcard(&sd_fs);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
fs = std::make_shared<IFileSystemInterface>(std::make_unique<SubDirectoryFileSystem>(std::make_shared<ProxyFileSystem>(sd_fs), AtmosphereHblWebContentDir));
|
||||
if (out_fs.IsDomain()) {
|
||||
out_domain_id = sd_fs.s.object_id;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out_fs, u64 title_id, u32 filesystem_type) {
|
||||
/* Check for eligibility. */
|
||||
{
|
||||
FsDir d;
|
||||
if (!Utils::IsWebAppletTid(this->title_id) || filesystem_type != FsFileSystemType_ContentManual || !Utils::IsHblTid(title_id) ||
|
||||
R_FAILED(Utils::OpenSdDir(AtmosphereHblWebContentDir, &d))) {
|
||||
return RESULT_FORWARD_TO_SESSION;
|
||||
}
|
||||
fsDirClose(&d);
|
||||
}
|
||||
|
||||
/* If there's an existing filesystem, don't override. */
|
||||
/* TODO: Multiplex, overriding existing content with HBL content. */
|
||||
{
|
||||
FsFileSystem fs;
|
||||
if (R_SUCCEEDED(fsOpenFileSystemWithPatchFwd(this->forward_service.get(), &fs, title_id, static_cast<FsFileSystemType>(filesystem_type)))) {
|
||||
fsFsClose(&fs);
|
||||
return RESULT_FORWARD_TO_SESSION;
|
||||
}
|
||||
}
|
||||
|
||||
return this->OpenHblWebContentFileSystem(out_fs);
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out_fs, InPointer<char> path, u64 title_id, u32 filesystem_type) {
|
||||
/* Check for eligibility. */
|
||||
{
|
||||
FsDir d;
|
||||
if (!Utils::IsWebAppletTid(this->title_id) || filesystem_type != FsFileSystemType_ContentManual || !Utils::IsHblTid(title_id) ||
|
||||
R_FAILED(Utils::OpenSdDir(AtmosphereHblWebContentDir, &d))) {
|
||||
return RESULT_FORWARD_TO_SESSION;
|
||||
}
|
||||
fsDirClose(&d);
|
||||
}
|
||||
|
||||
/* If there's an existing filesystem, don't override. */
|
||||
/* TODO: Multiplex, overriding existing content with HBL content. */
|
||||
{
|
||||
FsFileSystem fs;
|
||||
if (R_SUCCEEDED(fsOpenFileSystemWithIdFwd(this->forward_service.get(), &fs, title_id, static_cast<FsFileSystemType>(filesystem_type), path.pointer))) {
|
||||
fsFsClose(&fs);
|
||||
return RESULT_FORWARD_TO_SESSION;
|
||||
}
|
||||
}
|
||||
|
||||
return this->OpenHblWebContentFileSystem(out_fs);
|
||||
}
|
||||
|
||||
/* Gate access to the BIS partitions. */
|
||||
Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out_storage, u32 bis_partition_id) {
|
||||
std::shared_ptr<IStorageInterface> storage = nullptr;
|
||||
@@ -99,7 +176,7 @@ Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out
|
||||
FsStorage bis_storage;
|
||||
rc = fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
const bool is_sysmodule = this->title_id < 0x0100000000001000;
|
||||
const bool is_sysmodule = this->title_id < 0x0100000000001000ul;
|
||||
const bool has_bis_write_flag = Utils::HasFlag(this->title_id, "bis_write");
|
||||
const bool has_cal0_read_flag = Utils::HasFlag(this->title_id, "cal_read");
|
||||
if (bis_partition_id == BisStorageId_Boot0) {
|
||||
@@ -117,6 +194,12 @@ Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out
|
||||
if (is_sysmodule || has_bis_write_flag) {
|
||||
/* Sysmodules should still be allowed to read and write. */
|
||||
storage = std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage));
|
||||
} else if (Utils::IsHblTid(this->title_id) &&
|
||||
((BisStorageId_BcPkg2_1 <= bis_partition_id && bis_partition_id <= BisStorageId_BcPkg2_6) || bis_partition_id == BisStorageId_Boot1)) {
|
||||
/* Allow HBL to write to boot1 (safe firm) + package2. */
|
||||
/* This is needed to not break compatibility with ChoiDujourNX, which does not check for write access before beginning an update. */
|
||||
/* TODO: get fixed so that this can be turned off without causing bricks :/ */
|
||||
storage = std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage));
|
||||
} else {
|
||||
/* Non-sysmodules should be allowed to read. */
|
||||
storage = std::make_shared<IStorageInterface>(new ROProxyStorage(bis_storage));
|
||||
|
||||
@@ -18,16 +18,25 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_istorage.hpp"
|
||||
#include "fs_ifilesystem.hpp"
|
||||
#include "../utils.hpp"
|
||||
|
||||
enum FspSrvCmd : u32 {
|
||||
FspSrvCmd_OpenFileSystemDeprecated = 0,
|
||||
|
||||
FspSrvCmd_SetCurrentProcess = 1,
|
||||
|
||||
FspSrvCmd_OpenFileSystemWithPatch = 7,
|
||||
FspSrvCmd_OpenFileSystemWithId = 8,
|
||||
|
||||
FspSrvCmd_OpenBisStorage = 12,
|
||||
FspSrvCmd_OpenDataStorageByCurrentProcess = 200,
|
||||
FspSrvCmd_OpenDataStorageByDataId = 202,
|
||||
};
|
||||
|
||||
class FsMitmService : public IMitmServiceObject {
|
||||
private:
|
||||
static constexpr const char *AtmosphereHblWebContentDir = "/atmosphere/hbl_html";
|
||||
private:
|
||||
bool has_initialized = false;
|
||||
bool should_override_contents;
|
||||
@@ -58,14 +67,20 @@ class FsMitmService : public IMitmServiceObject {
|
||||
}
|
||||
|
||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
||||
|
||||
private:
|
||||
Result OpenHblWebContentFileSystem(Out<std::shared_ptr<IFileSystemInterface>> &out);
|
||||
protected:
|
||||
/* Overridden commands. */
|
||||
Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 title_id, u32 filesystem_type);
|
||||
Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 title_id, u32 filesystem_type);
|
||||
Result OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
|
||||
Result OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out);
|
||||
Result OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out, u64 data_id, u8 storage_id);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* TODO MakeServiceCommandMeta<FspSrvCmd_OpenFileSystemDeprecated, &FsMitmService::OpenFileSystemDeprecated>(), */
|
||||
MakeServiceCommandMeta<FspSrvCmd_OpenFileSystemWithPatch, &FsMitmService::OpenFileSystemWithPatch, FirmwareVersion_200>(),
|
||||
MakeServiceCommandMeta<FspSrvCmd_OpenFileSystemWithId, &FsMitmService::OpenFileSystemWithId, FirmwareVersion_200>(),
|
||||
MakeServiceCommandMeta<FspSrvCmd_OpenBisStorage, &FsMitmService::OpenBisStorage>(),
|
||||
MakeServiceCommandMeta<FspSrvCmd_OpenDataStorageByCurrentProcess, &FsMitmService::OpenDataStorageByCurrentProcess>(),
|
||||
MakeServiceCommandMeta<FspSrvCmd_OpenDataStorageByDataId, &FsMitmService::OpenDataStorageByDataId>(),
|
||||
|
||||
189
stratosphere/ams_mitm/source/ns_mitm/ns_shim.c
Normal file
189
stratosphere/ams_mitm/source/ns_mitm/ns_shim.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
#include <switch.h>
|
||||
#include "ns_shim.h"
|
||||
|
||||
/* Command forwarders. */
|
||||
Result nsGetDocumentInterfaceFwd(Service* s, NsDocumentInterface* out) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 7999;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result nsamGetApplicationContentPathFwd(Service* s, void* out, size_t out_size, u64 app_id, FsStorageId storage_id) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddRecvBuffer(&c, out, out_size, 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u8 storage_id;
|
||||
u64 app_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 21;
|
||||
raw->storage_id = storage_id;
|
||||
raw->app_id = app_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result nsamResolveApplicationContentPathFwd(Service* s, u64 title_id, FsStorageId storage_id) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u8 storage_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 23;
|
||||
raw->storage_id = storage_id;
|
||||
raw->title_id = title_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result nsamGetRunningApplicationProgramIdFwd(Service* s, u64* out_tid, u64 app_id) {
|
||||
if (hosversionBefore(6, 0, 0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 app_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 92;
|
||||
raw->app_id = app_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 title_id;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out_tid) {
|
||||
*out_tid = resp->title_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Web forwarders */
|
||||
Result nswebGetApplicationContentPath(NsDocumentInterface* doc, void* out, size_t out_size, u64 app_id, FsStorageId storage_id) {
|
||||
return nsamGetApplicationContentPathFwd(&doc->s, out, out_size, app_id, storage_id);
|
||||
}
|
||||
|
||||
Result nswebResolveApplicationContentPath(NsDocumentInterface* doc, u64 title_id, FsStorageId storage_id) {
|
||||
return nsamResolveApplicationContentPathFwd(&doc->s, title_id, storage_id);
|
||||
}
|
||||
|
||||
Result nswebGetRunningApplicationProgramId(NsDocumentInterface* doc, u64* out_tid, u64 app_id) {
|
||||
return nsamGetRunningApplicationProgramIdFwd(&doc->s, out_tid, app_id);
|
||||
}
|
||||
31
stratosphere/ams_mitm/source/ns_mitm/ns_shim.h
Normal file
31
stratosphere/ams_mitm/source/ns_mitm/ns_shim.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file ns_shim.h
|
||||
* @brief Nintendo Shell Services (ns) IPC wrapper.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
Service s;
|
||||
} NsDocumentInterface;
|
||||
|
||||
/* Command forwarders. */
|
||||
Result nsGetDocumentInterfaceFwd(Service* s, NsDocumentInterface* out);
|
||||
|
||||
Result nsamGetApplicationContentPathFwd(Service* s, void* out, size_t out_size, u64 app_id, FsStorageId storage_id);
|
||||
Result nsamResolveApplicationContentPathFwd(Service* s, u64 title_id, FsStorageId storage_id);
|
||||
Result nsamGetRunningApplicationProgramIdFwd(Service* s, u64* out_tid, u64 app_id);
|
||||
|
||||
Result nswebGetApplicationContentPath(NsDocumentInterface* doc, void* out, size_t out_size, u64 app_id, FsStorageId storage_id);
|
||||
Result nswebResolveApplicationContentPath(NsDocumentInterface* doc, u64 title_id, FsStorageId storage_id);
|
||||
Result nswebGetRunningApplicationProgramId(NsDocumentInterface* doc, u64* out_tid, u64 app_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
40
stratosphere/ams_mitm/source/ns_mitm/nsmitm_am_service.cpp
Normal file
40
stratosphere/ams_mitm/source/ns_mitm/nsmitm_am_service.cpp
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/>.
|
||||
*/
|
||||
|
||||
#include <mutex>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "nsmitm_am_service.hpp"
|
||||
#include "ns_shim.h"
|
||||
|
||||
void NsAmMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
||||
/* Nothing to do here */
|
||||
}
|
||||
|
||||
Result NsAmMitmService::GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type) {
|
||||
return nsamGetApplicationContentPathFwd(this->forward_service.get(), out_path.buffer, out_path.num_elements, app_id, static_cast<FsStorageId>(storage_type));
|
||||
}
|
||||
|
||||
Result NsAmMitmService::ResolveApplicationContentPath(u64 title_id, u8 storage_type) {
|
||||
Result rc = nsamResolveApplicationContentPathFwd(this->forward_service.get(), title_id, static_cast<FsStorageId>(storage_type));
|
||||
|
||||
/* Always succeed for web applet asking about HBL. */
|
||||
return (Utils::IsWebAppletTid(this->title_id) && Utils::IsHblTid(title_id)) ? 0 : rc;
|
||||
}
|
||||
|
||||
Result NsAmMitmService::GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id) {
|
||||
return nsamGetRunningApplicationProgramIdFwd(this->forward_service.get(), out_tid.GetPointer(), app_id);
|
||||
}
|
||||
51
stratosphere/ams_mitm/source/ns_mitm/nsmitm_am_service.hpp
Normal file
51
stratosphere/ams_mitm/source/ns_mitm/nsmitm_am_service.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 "../utils.hpp"
|
||||
|
||||
#include "nsmitm_service_common.hpp"
|
||||
|
||||
class NsAmMitmService : public IMitmServiceObject {
|
||||
public:
|
||||
NsAmMitmService(std::shared_ptr<Service> s, u64 pid) : IMitmServiceObject(s, pid) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
static bool ShouldMitm(u64 pid, u64 tid) {
|
||||
/* We will mitm:
|
||||
* - web applets, to facilitate hbl web browser launching.
|
||||
*/
|
||||
return Utils::IsWebAppletTid(tid);
|
||||
}
|
||||
|
||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
||||
|
||||
protected:
|
||||
/* Overridden commands. */
|
||||
Result GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type);
|
||||
Result ResolveApplicationContentPath(u64 title_id, u8 storage_type);
|
||||
Result GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<NsSrvCmd_GetApplicationContentPath, &NsAmMitmService::GetApplicationContentPath>(),
|
||||
MakeServiceCommandMeta<NsSrvCmd_ResolveApplicationContentPath, &NsAmMitmService::ResolveApplicationContentPath>(),
|
||||
MakeServiceCommandMeta<NsSrvCmd_GetRunningApplicationProgramId, &NsAmMitmService::GetRunningApplicationProgramId, FirmwareVersion_600>(),
|
||||
};
|
||||
};
|
||||
61
stratosphere/ams_mitm/source/ns_mitm/nsmitm_main.cpp
Normal file
61
stratosphere/ams_mitm/source/ns_mitm/nsmitm_main.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 "../utils.hpp"
|
||||
|
||||
#include "nsmitm_main.hpp"
|
||||
|
||||
#include "nsmitm_am_service.hpp"
|
||||
#include "nsmitm_web_service.hpp"
|
||||
|
||||
void NsMitmMain(void *arg) {
|
||||
/* Wait for initialization to occur */
|
||||
Utils::WaitSdInitialized();
|
||||
|
||||
/* Ensure we can talk to NS. */
|
||||
{
|
||||
if (R_FAILED(nsInitialize())) {
|
||||
std::abort();
|
||||
}
|
||||
nsExit();
|
||||
}
|
||||
|
||||
/* Create server manager */
|
||||
auto server_manager = new WaitableManager(1);
|
||||
|
||||
/* Create ns mitm. */
|
||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
|
||||
AddMitmServerToManager<NsAmMitmService>(server_manager, "ns:am", 5);
|
||||
} else {
|
||||
AddMitmServerToManager<NsWebMitmService>(server_manager, "ns:web", 5);
|
||||
}
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
server_manager->Process();
|
||||
|
||||
delete server_manager;
|
||||
|
||||
}
|
||||
|
||||
29
stratosphere/ams_mitm/source/ns_mitm/nsmitm_main.hpp
Normal file
29
stratosphere/ams_mitm/source/ns_mitm/nsmitm_main.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
constexpr u32 NsMitmPriority = 48;
|
||||
constexpr u32 NsMitmStackSize = 0x4000;
|
||||
|
||||
void NsMitmMain(void *arg);
|
||||
@@ -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>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
|
||||
enum NsGetterCmd : u32 {
|
||||
NsGetterCmd_GetDocumentInterface = 7999,
|
||||
};
|
||||
|
||||
enum NsSrvCmd : u32 {
|
||||
NsSrvCmd_GetApplicationContentPath = 21,
|
||||
NsSrvCmd_ResolveApplicationContentPath = 23,
|
||||
NsSrvCmd_GetRunningApplicationProgramId = 92,
|
||||
};
|
||||
66
stratosphere/ams_mitm/source/ns_mitm/nsmitm_web_service.cpp
Normal file
66
stratosphere/ams_mitm/source/ns_mitm/nsmitm_web_service.cpp
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 <mutex>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "nsmitm_web_service.hpp"
|
||||
|
||||
void NsWebMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
||||
/* Nothing to do here */
|
||||
}
|
||||
|
||||
Result NsWebMitmService::GetDocumentInterface(Out<std::shared_ptr<NsDocumentService>> out_intf) {
|
||||
std::shared_ptr<NsDocumentService> intf = nullptr;
|
||||
u32 out_domain_id = 0;
|
||||
Result rc = 0;
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
out_intf.SetValue(std::move(intf));
|
||||
if (out_intf.IsDomain()) {
|
||||
out_intf.ChangeObjectId(out_domain_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Open a document interface. */
|
||||
NsDocumentInterface doc;
|
||||
rc = nsGetDocumentInterfaceFwd(this->forward_service.get(), &doc);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
intf = std::make_shared<NsDocumentService>(this->title_id, doc);
|
||||
if (out_intf.IsDomain()) {
|
||||
out_domain_id = doc.s.object_id;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result NsDocumentService::GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type) {
|
||||
return nswebGetApplicationContentPath(this->srv.get(), out_path.buffer, out_path.num_elements, app_id, static_cast<FsStorageId>(storage_type));
|
||||
}
|
||||
|
||||
Result NsDocumentService::ResolveApplicationContentPath(u64 title_id, u8 storage_type) {
|
||||
Result rc = nswebResolveApplicationContentPath(this->srv.get(), title_id, static_cast<FsStorageId>(storage_type));
|
||||
|
||||
/* Always succeed for web applet asking about HBL. */
|
||||
return (Utils::IsWebAppletTid(this->title_id) && Utils::IsHblTid(title_id)) ? 0 : rc;
|
||||
}
|
||||
|
||||
Result NsDocumentService::GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id) {
|
||||
return nswebGetRunningApplicationProgramId(this->srv.get(), out_tid.GetPointer(), app_id);
|
||||
}
|
||||
81
stratosphere/ams_mitm/source/ns_mitm/nsmitm_web_service.hpp
Normal file
81
stratosphere/ams_mitm/source/ns_mitm/nsmitm_web_service.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 "../utils.hpp"
|
||||
|
||||
#include "nsmitm_service_common.hpp"
|
||||
#include "ns_shim.h"
|
||||
|
||||
class NsDocumentService : public IServiceObject {
|
||||
private:
|
||||
u64 title_id;
|
||||
std::unique_ptr<NsDocumentInterface> srv;
|
||||
public:
|
||||
NsDocumentService(u64 t, NsDocumentInterface *s) : title_id(t), srv(s) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
NsDocumentService(u64 t, std::unique_ptr<NsDocumentInterface> s) : title_id(t), srv(std::move(s)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
NsDocumentService(u64 t, NsDocumentInterface s) : title_id(t) {
|
||||
srv = std::make_unique<NsDocumentInterface>(s);
|
||||
}
|
||||
|
||||
virtual ~NsDocumentService() {
|
||||
serviceClose(&srv->s);
|
||||
}
|
||||
private:
|
||||
/* Actual command API. */
|
||||
Result GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type);
|
||||
Result ResolveApplicationContentPath(u64 title_id, u8 storage_type);
|
||||
Result GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<NsSrvCmd_GetApplicationContentPath, &NsDocumentService::GetApplicationContentPath>(),
|
||||
MakeServiceCommandMeta<NsSrvCmd_ResolveApplicationContentPath, &NsDocumentService::ResolveApplicationContentPath>(),
|
||||
MakeServiceCommandMeta<NsSrvCmd_GetRunningApplicationProgramId, &NsDocumentService::GetRunningApplicationProgramId, FirmwareVersion_600>(),
|
||||
};
|
||||
};
|
||||
|
||||
class NsWebMitmService : public IMitmServiceObject {
|
||||
public:
|
||||
NsWebMitmService(std::shared_ptr<Service> s, u64 pid) : IMitmServiceObject(s, pid) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
static bool ShouldMitm(u64 pid, u64 tid) {
|
||||
/* We will mitm:
|
||||
* - web applets, to facilitate hbl web browser launching.
|
||||
*/
|
||||
return Utils::IsWebAppletTid(tid);
|
||||
}
|
||||
|
||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
||||
|
||||
protected:
|
||||
/* Overridden commands. */
|
||||
Result GetDocumentInterface(Out<std::shared_ptr<NsDocumentService>> out_intf);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<NsGetterCmd_GetDocumentInterface, &NsWebMitmService::GetDocumentInterface, FirmwareVersion_300>(),
|
||||
};
|
||||
};
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "setmitm_main.hpp"
|
||||
#include "setsys_mitm_service.hpp"
|
||||
#include "setsys_settings_items.hpp"
|
||||
#include "setsys_firmware_version.hpp"
|
||||
|
||||
#include "../utils.hpp"
|
||||
|
||||
@@ -40,6 +41,9 @@ using SetMitmManager = WaitableManager<SetSysManagerOptions>;
|
||||
void SetMitmMain(void *arg) {
|
||||
/* Wait for SD to initialize. */
|
||||
Utils::WaitSdInitialized();
|
||||
|
||||
/* Initialize version manager. */
|
||||
VersionManager::Initialize();
|
||||
|
||||
/* Create server manager */
|
||||
auto server_manager = new SetMitmManager(3);
|
||||
|
||||
@@ -21,35 +21,65 @@
|
||||
|
||||
static HosMutex g_version_mutex;
|
||||
static bool g_got_version = false;
|
||||
static SetSysFirmwareVersion g_ams_fw_version = {0};
|
||||
static SetSysFirmwareVersion g_fw_version = {0};
|
||||
|
||||
Result VersionManager::GetFirmwareVersion(u64 title_id, SetSysFirmwareVersion *out) {
|
||||
void VersionManager::Initialize() {
|
||||
std::scoped_lock<HosMutex> lock(g_version_mutex);
|
||||
if (!g_got_version) {
|
||||
Result rc = setsysGetFirmwareVersion(&g_fw_version);
|
||||
if (R_FAILED(rc)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Modify the output firmware version. */
|
||||
{
|
||||
u32 major, minor, micro;
|
||||
char display_version[sizeof(g_fw_version.display_version)] = {0};
|
||||
|
||||
GetAtmosphereApiVersion(&major, &minor, µ, nullptr, nullptr);
|
||||
snprintf(display_version, sizeof(display_version), "%s (AMS %u.%u.%u)", g_fw_version.display_version, major, minor, micro);
|
||||
|
||||
memcpy(g_fw_version.display_version, display_version, sizeof(g_fw_version.display_version));
|
||||
}
|
||||
|
||||
g_got_version = true;
|
||||
|
||||
if (g_got_version) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mount firmware version data archive. */
|
||||
if (R_SUCCEEDED(romfsMountFromDataArchive(0x0100000000000809ul, FsStorageId_NandSystem, "809"))) {
|
||||
ON_SCOPE_EXIT { romfsUnmount("809"); };
|
||||
|
||||
SetSysFirmwareVersion fw_ver;
|
||||
|
||||
/* Firmware version file must exist. */
|
||||
FILE *f = fopen("809:/file", "rb");
|
||||
if (f == NULL) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Must be possible to read firmware version from file. */
|
||||
if (fread(&fw_ver, sizeof(fw_ver), 1, f) != 1) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
g_fw_version = fw_ver;
|
||||
g_ams_fw_version = fw_ver;
|
||||
} else {
|
||||
/* Failure to open data archive is an abort. */
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Modify the output firmware version. */
|
||||
{
|
||||
u32 major, minor, micro;
|
||||
char display_version[sizeof(g_ams_fw_version.display_version)] = {0};
|
||||
|
||||
GetAtmosphereApiVersion(&major, &minor, µ, nullptr, nullptr);
|
||||
snprintf(display_version, sizeof(display_version), "%s (AMS %u.%u.%u)", g_ams_fw_version.display_version, major, minor, micro);
|
||||
|
||||
memcpy(g_ams_fw_version.display_version, display_version, sizeof(g_ams_fw_version.display_version));
|
||||
}
|
||||
|
||||
g_got_version = true;
|
||||
}
|
||||
|
||||
Result VersionManager::GetFirmwareVersion(u64 title_id, SetSysFirmwareVersion *out) {
|
||||
VersionManager::Initialize();
|
||||
|
||||
/* Report atmosphere string to qlaunch, maintenance and nothing else. */
|
||||
if (title_id == 0x0100000000001000ULL || title_id == 0x0100000000001015ULL) {
|
||||
*out = g_fw_version;
|
||||
return 0;
|
||||
*out = g_ams_fw_version;
|
||||
} else {
|
||||
return setsysGetFirmwareVersion(out);
|
||||
*out = g_fw_version;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
|
||||
class VersionManager {
|
||||
public:
|
||||
static void Initialize();
|
||||
static Result GetFirmwareVersion(u64 title_id, SetSysFirmwareVersion *out);
|
||||
};
|
||||
|
||||
@@ -257,7 +257,7 @@ void SettingsItemManager::LoadConfiguration() {
|
||||
char *config_buf = new char[0x10000];
|
||||
std::memset(config_buf, 0, 0x10000);
|
||||
ON_SCOPE_EXIT {
|
||||
delete config_buf;
|
||||
delete[] config_buf;
|
||||
};
|
||||
|
||||
/* Read from file. */
|
||||
|
||||
@@ -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_hid_session = false;
|
||||
|
||||
static u64 g_override_key_combination = KEY_R;
|
||||
static u64 g_override_hbl_tid = 0x010000000000100DULL;
|
||||
static bool g_override_any_app = false;
|
||||
static bool g_override_by_default = true;
|
||||
/* Content override support variables/types */
|
||||
static OverrideKey g_default_override_key = {
|
||||
.key_combination = KEY_R,
|
||||
.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 char g_config_ini_data[0x800];
|
||||
@@ -121,7 +137,7 @@ void Utils::InitializeThreadFunc(void *args) {
|
||||
|
||||
if (!has_auto_backup) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -204,11 +220,7 @@ void Utils::InitializeThreadFunc(void *args) {
|
||||
}
|
||||
|
||||
g_has_hid_session = true;
|
||||
|
||||
hidExit();
|
||||
}
|
||||
|
||||
svcExitThread();
|
||||
}
|
||||
|
||||
bool Utils::IsSdInitialized() {
|
||||
@@ -368,7 +380,11 @@ Result Utils::SaveSdFileForAtmosphere(u64 title_id, const char *fn, void *data,
|
||||
}
|
||||
|
||||
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)) || (tid == g_hbl_override_config.title_id);
|
||||
}
|
||||
|
||||
bool Utils::IsWebAppletTid(u64 tid) {
|
||||
return tid == 0x010000000000100Aul || tid == 0x010000000000100Ful || tid == 0x0100000000001010ul || tid == 0x0100000000001011ul;
|
||||
}
|
||||
|
||||
bool Utils::HasTitleFlag(u64 tid, const char *flag) {
|
||||
@@ -378,14 +394,14 @@ bool Utils::HasTitleFlag(u64 tid, const char *flag) {
|
||||
|
||||
memset(flag_path, 0, sizeof(flag_path));
|
||||
snprintf(flag_path, sizeof(flag_path) - 1, "flags/%s.flag", flag);
|
||||
if (OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f)) {
|
||||
if (R_SUCCEEDED(OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f))) {
|
||||
fsFileClose(&f);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO: Deprecate. */
|
||||
snprintf(flag_path, sizeof(flag_path) - 1, "%s.flag", flag);
|
||||
if (OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f)) {
|
||||
if (R_SUCCEEDED(OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f))) {
|
||||
fsFileClose(&f);
|
||||
return true;
|
||||
}
|
||||
@@ -398,7 +414,7 @@ bool Utils::HasGlobalFlag(const char *flag) {
|
||||
FsFile f;
|
||||
char flag_path[FS_MAX_PATH] = {0};
|
||||
snprintf(flag_path, sizeof(flag_path), "/atmosphere/flags/%s.flag", flag);
|
||||
if (fsFsOpenFile(&g_sd_filesystem, flag_path, FS_OPEN_READ, &f)) {
|
||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, flag_path, FS_OPEN_READ, &f))) {
|
||||
fsFileClose(&f);
|
||||
return true;
|
||||
}
|
||||
@@ -434,18 +450,24 @@ bool Utils::HasSdDisableMitMFlag(u64 tid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Result Utils::GetKeysDown(u64 *keys) {
|
||||
if (!Utils::IsHidAvailable() || R_FAILED(hidInitialize())) {
|
||||
Result Utils::GetKeysHeld(u64 *keys) {
|
||||
if (!Utils::IsHidAvailable()) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
||||
}
|
||||
|
||||
hidScanInput();
|
||||
*keys = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
*keys = hidKeysHeld(CONTROLLER_P1_AUTO);
|
||||
|
||||
hidExit();
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
static bool HasOverrideKey(OverrideKey *cfg) {
|
||||
u64 kDown = 0;
|
||||
bool keys_triggered = (R_SUCCEEDED(Utils::GetKeysHeld(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
||||
return Utils::IsSdInitialized() && (cfg->override_by_default ^ keys_triggered);
|
||||
}
|
||||
|
||||
|
||||
bool Utils::HasOverrideButton(u64 tid) {
|
||||
if ((!IsApplicationTid(tid)) || (!IsSdInitialized())) {
|
||||
/* Disable button override disable for non-applications. */
|
||||
@@ -455,78 +477,144 @@ bool Utils::HasOverrideButton(u64 tid) {
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshConfiguration();
|
||||
|
||||
u64 kDown = 0;
|
||||
bool keys_triggered = (R_SUCCEEDED(GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0));
|
||||
return IsSdInitialized() && (g_override_by_default ^ keys_triggered);
|
||||
if (IsHblTid(tid) && HasOverrideKey(&g_hbl_override_config.override_key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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. */
|
||||
if (strcasecmp(section, "config") == 0) {
|
||||
if (strcasecmp(name, "hbl_tid") == 0) {
|
||||
if (strcasecmp(section, "hbl_config") == 0) {
|
||||
if (strcasecmp(name, "title_id") == 0) {
|
||||
if (strcasecmp(value, "app") == 0) {
|
||||
g_override_any_app = true;
|
||||
}
|
||||
else {
|
||||
/* DEPRECATED */
|
||||
g_hbl_override_config.override_any_app = true;
|
||||
g_hbl_override_config.title_id = 0;
|
||||
} else {
|
||||
u64 override_tid = strtoul(value, NULL, 16);
|
||||
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) {
|
||||
if (value[0] == '!') {
|
||||
g_override_by_default = true;
|
||||
value++;
|
||||
g_hbl_override_config.override_key = ParseOverrideKey(value);
|
||||
} else if (strcasecmp(name, "override_any_app") == 0) {
|
||||
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
|
||||
g_hbl_override_config.override_any_app = true;
|
||||
} else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
|
||||
g_hbl_override_config.override_any_app = false;
|
||||
} 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;
|
||||
/* I guess we default to not changing the value? */
|
||||
}
|
||||
}
|
||||
} else if (strcasecmp(section, "default_config") == 0) {
|
||||
if (strcasecmp(name, "override_key") == 0) {
|
||||
g_default_override_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
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() {
|
||||
FsFile config_file;
|
||||
@@ -547,7 +635,7 @@ void Utils::RefreshConfiguration() {
|
||||
fsFileRead(&config_file, 0, g_config_ini_data, size, &r_s);
|
||||
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) {
|
||||
|
||||
@@ -37,6 +37,11 @@ enum BisStorageId : u32 {
|
||||
BisStorageId_SystemProperPartition = 33,
|
||||
};
|
||||
|
||||
struct OverrideKey {
|
||||
u64 key_combination;
|
||||
bool override_by_default;
|
||||
};
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static bool IsSdInitialized();
|
||||
@@ -60,6 +65,7 @@ class Utils {
|
||||
static void InitializeThreadFunc(void *args);
|
||||
|
||||
static bool IsHblTid(u64 tid);
|
||||
static bool IsWebAppletTid(u64 tid);
|
||||
|
||||
static bool HasTitleFlag(u64 tid, const char *flag);
|
||||
static bool HasHblFlag(const char *flag);
|
||||
@@ -71,7 +77,9 @@ class Utils {
|
||||
|
||||
|
||||
static bool IsHidAvailable();
|
||||
static Result GetKeysDown(u64 *keys);
|
||||
static Result GetKeysHeld(u64 *keys);
|
||||
|
||||
static OverrideKey GetTitleOverrideKey(u64 tid);
|
||||
static bool HasOverrideButton(u64 tid);
|
||||
|
||||
/* Settings! */
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"erpt:c",
|
||||
"fatal:u",
|
||||
"ns:dev",
|
||||
"time:*",
|
||||
"fsp-srv"
|
||||
],
|
||||
"kernel_capabilities": [
|
||||
|
||||
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
|
||||
#---------------------------------------------------------------------------------------
|
||||
121
stratosphere/dmnt/dmnt.json
Normal file
121
stratosphere/dmnt/dmnt.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"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",
|
||||
"set:sys",
|
||||
"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
|
||||
}]
|
||||
}
|
||||
102
stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp
Normal file
102
stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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_config.hpp"
|
||||
#include "dmnt_cheat_debug_events_manager.hpp"
|
||||
|
||||
|
||||
/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */
|
||||
|
||||
static HosThread g_per_core_threads[DmntCheatDebugEventsManager::NumCores];
|
||||
static HosMessageQueue *g_per_core_queues[DmntCheatDebugEventsManager::NumCores];
|
||||
static HosSignal g_continued_signal;
|
||||
|
||||
void DmntCheatDebugEventsManager::PerCoreThreadFunc(void *arg) {
|
||||
/* This thread will simply wait on the appropriate message queue. */
|
||||
size_t current_core = reinterpret_cast<size_t>(arg);
|
||||
while (true) {
|
||||
Handle debug_handle = 0;
|
||||
/* Get the debug handle. */
|
||||
{
|
||||
uintptr_t x = 0;
|
||||
g_per_core_queues[current_core]->Receive(&x);
|
||||
debug_handle = static_cast<Handle>(x);
|
||||
}
|
||||
|
||||
/* Continue the process, if needed. */
|
||||
if (kernelAbove300()) {
|
||||
svcContinueDebugEvent(debug_handle, 5, nullptr, 0);
|
||||
} else {
|
||||
svcLegacyContinueDebugEvent(debug_handle, 5, 0);
|
||||
}
|
||||
|
||||
g_continued_signal.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
void DmntCheatDebugEventsManager::ContinueCheatProcess(Handle cheat_dbg_hnd) {
|
||||
/* Loop getting debug events. */
|
||||
DebugEventInfo dbg_event;
|
||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&dbg_event, cheat_dbg_hnd))) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
size_t target_core = DmntCheatDebugEventsManager::NumCores - 1;
|
||||
/* Retrieve correct core for new thread event. */
|
||||
if (dbg_event.type == DebugEventType::AttachThread) {
|
||||
u64 out64;
|
||||
u32 out32;
|
||||
Result rc = svcGetDebugThreadParam(&out64, &out32, cheat_dbg_hnd, dbg_event.info.attach_thread.thread_id, DebugThreadParam_CurrentCore);
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
|
||||
target_core = out32;
|
||||
}
|
||||
|
||||
/* Make appropriate thread continue. */
|
||||
g_per_core_queues[target_core]->Send(static_cast<uintptr_t>(cheat_dbg_hnd));
|
||||
|
||||
/* Wait. */
|
||||
g_continued_signal.Wait();
|
||||
g_continued_signal.Reset();
|
||||
}
|
||||
|
||||
void DmntCheatDebugEventsManager::Initialize() {
|
||||
/* Spawn per core resources. */
|
||||
for (size_t i = 0; i < DmntCheatDebugEventsManager::NumCores; i++) {
|
||||
/* Create queue. */
|
||||
g_per_core_queues[i] = new HosMessageQueue(1);
|
||||
|
||||
/* Create thread. */
|
||||
if (R_FAILED(g_per_core_threads[i].Initialize(&DmntCheatDebugEventsManager::PerCoreThreadFunc, reinterpret_cast<void *>(i), 0x1000, 24, i))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Set core mask. */
|
||||
if (R_FAILED(svcSetThreadCoreMask(g_per_core_threads[i].GetHandle(), i, (1u << i)))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Start thread. */
|
||||
if (R_FAILED(g_per_core_threads[i].Start())) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
131
stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp
Normal file
131
stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
struct StackFrame {
|
||||
u64 fp;
|
||||
u64 lr;
|
||||
};
|
||||
|
||||
struct AttachProcessInfo {
|
||||
u64 title_id;
|
||||
u64 process_id;
|
||||
char name[0xC];
|
||||
u32 flags;
|
||||
u64 user_exception_context_address; /* 5.0.0+ */
|
||||
};
|
||||
|
||||
struct AttachThreadInfo {
|
||||
u64 thread_id;
|
||||
u64 tls_address;
|
||||
u64 entrypoint;
|
||||
};
|
||||
|
||||
/* TODO: ExitProcessInfo */
|
||||
/* TODO: ExitThreadInfo */
|
||||
|
||||
enum class DebugExceptionType : u32 {
|
||||
UndefinedInstruction = 0,
|
||||
InstructionAbort = 1,
|
||||
DataAbort = 2,
|
||||
AlignmentFault = 3,
|
||||
DebuggerAttached = 4,
|
||||
BreakPoint = 5,
|
||||
UserBreak = 6,
|
||||
DebuggerBreak = 7,
|
||||
BadSvc = 8,
|
||||
UnknownNine = 9,
|
||||
};
|
||||
|
||||
struct UndefinedInstructionInfo {
|
||||
u32 insn;
|
||||
};
|
||||
|
||||
struct DataAbortInfo {
|
||||
u64 address;
|
||||
};
|
||||
|
||||
struct AlignmentFaultInfo {
|
||||
u64 address;
|
||||
};
|
||||
|
||||
struct UserBreakInfo {
|
||||
u64 break_reason;
|
||||
u64 address;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct BadSvcInfo {
|
||||
u32 id;
|
||||
};
|
||||
|
||||
union SpecificExceptionInfo {
|
||||
UndefinedInstructionInfo undefined_instruction;
|
||||
DataAbortInfo data_abort;
|
||||
AlignmentFaultInfo alignment_fault;
|
||||
UserBreakInfo user_break;
|
||||
BadSvcInfo bad_svc;
|
||||
u64 raw;
|
||||
};
|
||||
|
||||
struct ExceptionInfo {
|
||||
DebugExceptionType type;
|
||||
u64 address;
|
||||
SpecificExceptionInfo specific;
|
||||
};
|
||||
|
||||
|
||||
enum class DebugEventType : u32 {
|
||||
AttachProcess = 0,
|
||||
AttachThread = 1,
|
||||
ExitProcess = 2,
|
||||
ExitThread = 3,
|
||||
Exception = 4
|
||||
};
|
||||
|
||||
union DebugInfo {
|
||||
AttachProcessInfo attach_process;
|
||||
AttachThreadInfo attach_thread;
|
||||
ExceptionInfo exception;
|
||||
};
|
||||
|
||||
struct DebugEventInfo {
|
||||
DebugEventType type;
|
||||
u32 flags;
|
||||
u64 thread_id;
|
||||
union {
|
||||
DebugInfo info;
|
||||
u64 _[0x40/sizeof(u64)];
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!");
|
||||
|
||||
class DmntCheatDebugEventsManager {
|
||||
public:
|
||||
static constexpr size_t NumCores = 4;
|
||||
private:
|
||||
static void PerCoreThreadFunc(void *arg);
|
||||
public:
|
||||
static void ContinueCheatProcess(Handle cheat_dbg_hnd);
|
||||
|
||||
static void Initialize();
|
||||
};
|
||||
1143
stratosphere/dmnt/source/dmnt_cheat_manager.cpp
Normal file
1143
stratosphere/dmnt/source/dmnt_cheat_manager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
83
stratosphere/dmnt/source/dmnt_cheat_manager.hpp
Normal file
83
stratosphere/dmnt/source/dmnt_cheat_manager.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 void DebugEventsThread(void *arg);
|
||||
|
||||
static void StartDebugEventsThread();
|
||||
static void WaitDebugEventsThread();
|
||||
|
||||
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 CheatEntry *GetCheatEntryByReadableName(const char *readable_name);
|
||||
static bool ParseCheats(const char *cht_txt, size_t len);
|
||||
static bool LoadCheats(u64 title_id, const u8 *build_id);
|
||||
|
||||
static bool ParseCheatToggles(const char *s, size_t len);
|
||||
static bool LoadCheatToggles(u64 title_id);
|
||||
static void SaveCheatToggles(u64 title_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;
|
||||
};
|
||||
972
stratosphere/dmnt/source/dmnt_cheat_vm.cpp
Normal file
972
stratosphere/dmnt/source/dmnt_cheat_vm.cpp
Normal file
@@ -0,0 +1,972 @@
|
||||
/*
|
||||
* 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", "ab");
|
||||
#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;
|
||||
case StoreRegisterOffsetType_MemReg:
|
||||
this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type);
|
||||
break;
|
||||
case StoreRegisterOffsetType_MemImm:
|
||||
case StoreRegisterOffsetType_MemImmReg:
|
||||
this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type);
|
||||
this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CheatVmOpcodeType_BeginRegisterConditionalBlock:
|
||||
this->LogToDebugFile("Opcode: Begin Register Conditional\n");
|
||||
this->LogToDebugFile("Bit Width: %x\n", opcode->begin_reg_cond.bit_width);
|
||||
this->LogToDebugFile("Cond Type: %x\n", opcode->begin_reg_cond.cond_type);
|
||||
this->LogToDebugFile("V Reg Idx: %x\n", opcode->begin_reg_cond.val_reg_index);
|
||||
switch (opcode->begin_reg_cond.comp_type) {
|
||||
case CompareRegisterValueType_StaticValue:
|
||||
this->LogToDebugFile("Comp Type: Static Value\n");
|
||||
this->LogToDebugFile("Value: %lx\n", opcode->begin_reg_cond.value.bit64);
|
||||
break;
|
||||
case CompareRegisterValueType_OtherRegister:
|
||||
this->LogToDebugFile("Comp Type: Other Register\n");
|
||||
this->LogToDebugFile("X Reg Idx: %x\n", opcode->begin_reg_cond.other_reg_index);
|
||||
break;
|
||||
case CompareRegisterValueType_MemoryRelAddr:
|
||||
this->LogToDebugFile("Comp Type: Memory Relative Address\n");
|
||||
this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type);
|
||||
this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address);
|
||||
break;
|
||||
case CompareRegisterValueType_MemoryOfsReg:
|
||||
this->LogToDebugFile("Comp Type: Memory Offset Register\n");
|
||||
this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type);
|
||||
this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index);
|
||||
break;
|
||||
case CompareRegisterValueType_RegisterRelAddr:
|
||||
this->LogToDebugFile("Comp Type: Register Relative Address\n");
|
||||
this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index);
|
||||
this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address);
|
||||
break;
|
||||
case CompareRegisterValueType_RegisterOfsReg:
|
||||
this->LogToDebugFile("Comp Type: Register Offset Register\n");
|
||||
this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index);
|
||||
this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index);
|
||||
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:
|
||||
case CheatVmOpcodeType_BeginRegisterConditionalBlock:
|
||||
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 >> 16) & 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:
|
||||
{
|
||||
/* ATSRIOxa (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, 3 = Memory Region,
|
||||
4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + Relative Address */
|
||||
/* x = offset register (for offset type 1), memory type (for offset type 3) */
|
||||
/* a = relative address (for offset type 2+3) */
|
||||
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;
|
||||
case StoreRegisterOffsetType_MemReg:
|
||||
opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
|
||||
break;
|
||||
case StoreRegisterOffsetType_MemImm:
|
||||
case StoreRegisterOffsetType_MemImmReg:
|
||||
opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
|
||||
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_BeginRegisterConditionalBlock:
|
||||
{
|
||||
/* C0TcSX## */
|
||||
/* C0TcS0Ma aaaaaaaa */
|
||||
/* C0TcS1Mr */
|
||||
/* C0TcS2Ra aaaaaaaa */
|
||||
/* C0TcS3Rr */
|
||||
/* C0TcS400 VVVVVVVV (VVVVVVVV) */
|
||||
/* C0TcS5X0 */
|
||||
/* C0 = opcode 0xC0 */
|
||||
/* T = bit width */
|
||||
/* c = condition type. */
|
||||
/* S = source register. */
|
||||
/* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */
|
||||
/* 2 = register with relative offset, 3 = register with offset register, 4 = static value, 5 = other register. */
|
||||
/* M = memory type. */
|
||||
/* R = address register. */
|
||||
/* a = relative address. */
|
||||
/* r = offset register. */
|
||||
/* X = other register. */
|
||||
/* V = value. */
|
||||
opcode.begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
|
||||
opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF);
|
||||
opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
|
||||
opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF);
|
||||
|
||||
switch (opcode.begin_reg_cond.comp_type) {
|
||||
case CompareRegisterValueType_StaticValue:
|
||||
opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width);
|
||||
break;
|
||||
case CompareRegisterValueType_OtherRegister:
|
||||
opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF);
|
||||
break;
|
||||
case CompareRegisterValueType_MemoryRelAddr:
|
||||
opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
|
||||
opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
|
||||
break;
|
||||
case CompareRegisterValueType_MemoryOfsReg:
|
||||
opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
|
||||
opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
|
||||
break;
|
||||
case CompareRegisterValueType_RegisterRelAddr:
|
||||
opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
|
||||
opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
|
||||
break;
|
||||
case CompareRegisterValueType_RegisterOfsReg:
|
||||
opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
|
||||
opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
|
||||
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->condition_depth > desired_depth && this->DecodeNextOpcode(&skip_opcode)) {
|
||||
/* 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) {
|
||||
this->num_opcodes = 0;
|
||||
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;
|
||||
case StoreRegisterOffsetType_MemReg:
|
||||
dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index]);
|
||||
break;
|
||||
case StoreRegisterOffsetType_MemImm:
|
||||
dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, cur_opcode.str_register.rel_address);
|
||||
break;
|
||||
case StoreRegisterOffsetType_MemImmReg:
|
||||
dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index] + 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;
|
||||
case CheatVmOpcodeType_BeginRegisterConditionalBlock:
|
||||
{
|
||||
/* Get value from register. */
|
||||
u64 src_value = 0;
|
||||
switch (cur_opcode.begin_reg_cond.bit_width) {
|
||||
case 1:
|
||||
src_value = static_cast<u8>(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul);
|
||||
break;
|
||||
case 2:
|
||||
src_value = static_cast<u16>(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFul);
|
||||
break;
|
||||
case 4:
|
||||
src_value = static_cast<u32>(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFul);
|
||||
break;
|
||||
case 8:
|
||||
src_value = static_cast<u64>(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFFFFFFFFFul);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read value from memory. */
|
||||
u64 cond_value = 0;
|
||||
if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) {
|
||||
cond_value = GetVmInt(cur_opcode.begin_reg_cond.value, cur_opcode.begin_reg_cond.bit_width);
|
||||
} else if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_OtherRegister) {
|
||||
switch (cur_opcode.begin_reg_cond.bit_width) {
|
||||
case 1:
|
||||
cond_value = static_cast<u8>(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul);
|
||||
break;
|
||||
case 2:
|
||||
cond_value = static_cast<u16>(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFul);
|
||||
break;
|
||||
case 4:
|
||||
cond_value = static_cast<u32>(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFul);
|
||||
break;
|
||||
case 8:
|
||||
cond_value = static_cast<u64>(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFFFFFFFFFul);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
u64 cond_address = 0;
|
||||
switch (cur_opcode.begin_reg_cond.comp_type) {
|
||||
case CompareRegisterValueType_MemoryRelAddr:
|
||||
cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, cur_opcode.begin_reg_cond.rel_address);
|
||||
break;
|
||||
case CompareRegisterValueType_MemoryOfsReg:
|
||||
cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]);
|
||||
break;
|
||||
case CompareRegisterValueType_RegisterRelAddr:
|
||||
cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + cur_opcode.begin_reg_cond.rel_address;
|
||||
break;
|
||||
case CompareRegisterValueType_RegisterOfsReg:
|
||||
cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + this->registers[cur_opcode.begin_reg_cond.ofs_reg_index];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (cur_opcode.begin_reg_cond.bit_width) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
DmntCheatManager::ReadCheatProcessMemoryForVm(cond_address, &cond_value, cur_opcode.begin_reg_cond.bit_width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check against condition. */
|
||||
bool cond_met = false;
|
||||
switch (cur_opcode.begin_reg_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;
|
||||
default:
|
||||
/* By default, we do a no-op. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
252
stratosphere/dmnt/source/dmnt_cheat_vm.hpp
Normal file
252
stratosphere/dmnt/source/dmnt_cheat_vm.hpp
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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,
|
||||
CheatVmOpcodeType_Reserved11 = 11,
|
||||
|
||||
/* This is a meta entry, and not a real opcode. */
|
||||
/* This is to facilitate multi-nybble instruction decoding. */
|
||||
CheatVmOpcodeType_ExtendedWidth = 12,
|
||||
|
||||
/* Extended width opcodes. */
|
||||
CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0,
|
||||
};
|
||||
|
||||
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,
|
||||
StoreRegisterOffsetType_MemReg = 3,
|
||||
StoreRegisterOffsetType_MemImm = 4,
|
||||
StoreRegisterOffsetType_MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum CompareRegisterValueType : u32 {
|
||||
CompareRegisterValueType_MemoryRelAddr = 0,
|
||||
CompareRegisterValueType_MemoryOfsReg = 1,
|
||||
CompareRegisterValueType_RegisterRelAddr = 2,
|
||||
CompareRegisterValueType_RegisterOfsReg = 3,
|
||||
CompareRegisterValueType_StaticValue = 4,
|
||||
CompareRegisterValueType_OtherRegister = 5,
|
||||
};
|
||||
|
||||
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;
|
||||
MemoryAccessType mem_type;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width;
|
||||
ConditionalComparisonType cond_type;
|
||||
u32 val_reg_index;
|
||||
CompareRegisterValueType comp_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 addr_reg_index;
|
||||
u32 other_reg_index;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
BeginRegisterConditionalOpcode begin_reg_cond;
|
||||
};
|
||||
};
|
||||
|
||||
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 = hidKeysHeld(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);
|
||||
};
|
||||
168
stratosphere/dmnt/source/dmnt_main.cpp
Normal file
168
stratosphere/dmnt/source/dmnt_main.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 = setsysInitialize();
|
||||
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();
|
||||
setsysExit();
|
||||
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;
|
||||
}
|
||||
|
||||
39
stratosphere/dmnt/source/dmnt_results.hpp
Normal file
39
stratosphere/dmnt/source/dmnt_results.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 ResultDmntCheatCannotDisableMasterCheat = 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
|
||||
@@ -14,7 +14,7 @@
|
||||
"filesystem_access": {
|
||||
"permissions": "0xFFFFFFFFFFFFFFFF"
|
||||
},
|
||||
"service_access": ["bpc", "bpc:c", "erpt:c", "fsp-srv", "gpio", "i2c", "lbl", "lm", "nvdrv:s", "pcv", "pl:u", "pm:info", "psm", "set", "set:sys", "spsm", "spl:", "vi:m", "vi:s"],
|
||||
"service_access": ["bpc", "bpc:c", "erpt:c", "fsp-srv", "gpio", "i2c", "lbl", "lm", "nvdrv:s", "pcv", "pl:u", "pm:info", "psm", "set", "set:sys", "spsm", "spl:", "time:*", "vi:m", "vi:s"],
|
||||
"service_host": ["fatal:p", "fatal:u", "time:s"],
|
||||
"kernel_capabilities": [{
|
||||
"type": "kernel_flags",
|
||||
|
||||
@@ -238,30 +238,46 @@ void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse the starting address. */
|
||||
{
|
||||
u64 guess = thread_ctx.pc.x;
|
||||
|
||||
/* Helper to guess start address. */
|
||||
auto TryGuessStartAddress = [&](u64 guess) {
|
||||
MemoryInfo mi;
|
||||
u32 pi;
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess)) || mi.perm != Perm_Rx) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Iterate backwards until we find the memory before the code region. */
|
||||
while (mi.addr > 0) {
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mi.type == MemType_Unmapped) {
|
||||
/* Code region will be at the end of the unmapped region preceding it. */
|
||||
ctx->cpu_ctx.aarch64_ctx.start_address = mi.addr + mi.size;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
guess -= 4;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/* Parse the starting address. */
|
||||
{
|
||||
if (TryGuessStartAddress(thread_ctx.pc.x)) {
|
||||
return;
|
||||
}
|
||||
if (TryGuessStartAddress(thread_ctx.lr)) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
||||
if (TryGuessStartAddress(ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ static void RunTaskThreadFunc(void *arg) {
|
||||
}
|
||||
|
||||
/* Finish. */
|
||||
svcExitThread();
|
||||
}
|
||||
|
||||
static void RunTask(IFatalTask *task, u32 stack_size = 0x4000) {
|
||||
|
||||
@@ -117,8 +117,12 @@ Result ShowFatalTask::PrepareScreenForDrawing() {
|
||||
if (R_FAILED((rc = viGetDisplayLogicalResolution(&this->display, &display_width, &display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height)))) {
|
||||
return rc;
|
||||
|
||||
/* viSetDisplayMagnification was added in 3.0.0. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
||||
if (R_FAILED((rc = viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create layer to draw to. */
|
||||
|
||||
Submodule stratosphere/libstratosphere updated: 9ce1dce440...31c1dc1a82
@@ -13,7 +13,7 @@
|
||||
* 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 <cstring>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
@@ -38,10 +38,26 @@ static bool g_has_initialized_fs_dev = false;
|
||||
/* Default to Key R, hold disables override, HBL at atmosphere/hbl.nsp. */
|
||||
static bool g_mounted_hbl_nsp = false;
|
||||
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 u64 g_override_hbl_tid = 0x010000000000100D;
|
||||
static bool g_override_any_app = false;
|
||||
|
||||
static OverrideKey g_default_override_key = {
|
||||
.key_combination = KEY_L,
|
||||
.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 char g_config_ini_data[0x800];
|
||||
@@ -52,44 +68,44 @@ static std::map<u64, ContentManagement::ExternalContentSource> g_external_conten
|
||||
Result ContentManagement::MountCode(u64 tid, FsStorageId sid) {
|
||||
char path[FS_MAX_PATH] = {0};
|
||||
Result rc;
|
||||
|
||||
|
||||
/* We defer SD card mounting, so if relevant ensure it is mounted. */
|
||||
if (!g_has_initialized_fs_dev) {
|
||||
if (!g_has_initialized_fs_dev) {
|
||||
TryMountSdCard();
|
||||
}
|
||||
|
||||
|
||||
if (g_has_initialized_fs_dev) {
|
||||
RefreshConfigurationData();
|
||||
}
|
||||
|
||||
if (ShouldOverrideContents(tid) && R_SUCCEEDED(MountCodeNspOnSd(tid))) {
|
||||
|
||||
if (ShouldOverrideContentsWithSD(tid) && R_SUCCEEDED(MountCodeNspOnSd(tid))) {
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
|
||||
if (R_FAILED(rc = ResolveContentPath(path, tid, sid))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* Fix up path. */
|
||||
for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) {
|
||||
if (path[i] == '\\') {
|
||||
path[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Always re-initialize fsp-ldr, in case it's closed */
|
||||
if (R_FAILED(rc = fsldrInitialize())) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
if (R_FAILED(rc = fsldrOpenCodeFileSystem(tid, path, &g_CodeFileSystem))) {
|
||||
fsldrExit();
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
fsdevMountDevice("code", g_CodeFileSystem);
|
||||
TryMountHblNspOnSd();
|
||||
|
||||
|
||||
fsldrExit();
|
||||
return rc;
|
||||
}
|
||||
@@ -105,14 +121,15 @@ Result ContentManagement::UnmountCode() {
|
||||
|
||||
|
||||
void ContentManagement::TryMountHblNspOnSd() {
|
||||
char path[FS_MAX_PATH + 1] = {0};
|
||||
char path[FS_MAX_PATH + 1];
|
||||
strncpy(path, g_hbl_sd_path, FS_MAX_PATH);
|
||||
path[FS_MAX_PATH] = 0;
|
||||
for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) {
|
||||
if (path[i] == '\\') {
|
||||
path[i] = '/';
|
||||
}
|
||||
}
|
||||
if (g_has_initialized_fs_dev && !g_mounted_hbl_nsp && R_SUCCEEDED(fsOpenFileSystemWithId(&g_HblFileSystem, 0, FsFileSystemType_ApplicationPackage, path))) {
|
||||
if (g_has_initialized_fs_dev && !g_mounted_hbl_nsp && R_SUCCEEDED(fsOpenFileSystemWithId(&g_HblFileSystem, 0, FsFileSystemType_ApplicationPackage, path))) {
|
||||
fsdevMountDevice("hbl", g_HblFileSystem);
|
||||
g_mounted_hbl_nsp = true;
|
||||
}
|
||||
@@ -120,14 +137,14 @@ void ContentManagement::TryMountHblNspOnSd() {
|
||||
|
||||
Result ContentManagement::MountCodeNspOnSd(u64 tid) {
|
||||
char path[FS_MAX_PATH+1] = {0};
|
||||
snprintf(path, FS_MAX_PATH, "@Sdcard:/atmosphere/titles/%016lx/exefs.nsp", tid);
|
||||
snprintf(path, FS_MAX_PATH, "@Sdcard:/atmosphere/titles/%016lx/exefs.nsp", tid);
|
||||
Result rc = fsOpenFileSystemWithId(&g_CodeFileSystem, 0, FsFileSystemType_ApplicationPackage, path);
|
||||
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
fsdevMountDevice("code", g_CodeFileSystem);
|
||||
TryMountHblNspOnSd();
|
||||
}
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -140,34 +157,34 @@ Result ContentManagement::ResolveContentPath(char *out_path, u64 tid, FsStorageI
|
||||
LrRegisteredLocationResolver reg;
|
||||
LrLocationResolver lr;
|
||||
char path[FS_MAX_PATH] = {0};
|
||||
|
||||
|
||||
/* Try to get the path from the registered resolver. */
|
||||
if (R_FAILED(rc = lrOpenRegisteredLocationResolver(®))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
if (R_SUCCEEDED(rc = lrRegLrResolveProgramPath(®, tid, path))) {
|
||||
strncpy(out_path, path, FS_MAX_PATH);
|
||||
} else if (rc != 0x408) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
serviceClose(®.s);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* If getting the path from the registered resolver fails, fall back to the normal resolver. */
|
||||
if (R_FAILED(rc = lrOpenLocationResolver(sid, &lr))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
if (R_SUCCEEDED(rc = lrLrResolveProgramPath(&lr, tid, path))) {
|
||||
strncpy(out_path, path, FS_MAX_PATH);
|
||||
}
|
||||
|
||||
|
||||
serviceClose(&lr.s);
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -178,15 +195,15 @@ Result ContentManagement::ResolveContentPathForTidSid(char *out_path, Registrati
|
||||
Result ContentManagement::RedirectContentPath(const char *path, u64 tid, FsStorageId sid) {
|
||||
Result rc;
|
||||
LrLocationResolver lr;
|
||||
|
||||
|
||||
if (R_FAILED(rc = lrOpenLocationResolver(sid, &lr))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
rc = lrLrRedirectProgramPath(&lr, tid, path);
|
||||
|
||||
|
||||
serviceClose(&lr.s);
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -194,6 +211,31 @@ Result ContentManagement::RedirectContentPathForTidSid(const char *path, Registr
|
||||
return RedirectContentPath(path, tid_sid->title_id, tid_sid->storage_id);
|
||||
}
|
||||
|
||||
void ContentManagement::RedirectHtmlDocumentPathForHbl(u64 tid, FsStorageId sid) {
|
||||
LrLocationResolver lr;
|
||||
char path[FS_MAX_PATH] = {0};
|
||||
|
||||
/* Open resolver. */
|
||||
if (R_FAILED(lrOpenLocationResolver(sid, &lr))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ensure close on exit. */
|
||||
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
||||
|
||||
/* Only redirect the HTML document path if there is not one already. */
|
||||
if (R_SUCCEEDED(lrLrResolveApplicationHtmlDocumentPath(&lr, tid, path))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We just need to set this to any valid NCA path. Let's use the executable path. */
|
||||
if (R_FAILED(lrLrResolveProgramPath(&lr, tid, path))) {
|
||||
return;
|
||||
}
|
||||
|
||||
lrLrRedirectApplicationHtmlDocumentPath(&lr, tid, path);
|
||||
}
|
||||
|
||||
bool ContentManagement::HasCreatedTitle(u64 tid) {
|
||||
return std::find(g_created_titles.begin(), g_created_titles.end(), tid) != g_created_titles.end();
|
||||
}
|
||||
@@ -204,89 +246,126 @@ 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) {
|
||||
/* Taken and modified, with love, from Rajkosto's implementation. */
|
||||
if (strcasecmp(section, "config") == 0) {
|
||||
if (strcasecmp(name, "hbl_tid") == 0) {
|
||||
if (strcasecmp(section, "hbl_config") == 0) {
|
||||
if (strcasecmp(name, "title_id") == 0) {
|
||||
if (strcasecmp(value, "app") == 0) {
|
||||
g_override_any_app = true;
|
||||
}
|
||||
else {
|
||||
/* DEPRECATED */
|
||||
g_hbl_override_config.override_any_app = true;
|
||||
g_hbl_override_config.title_id = 0;
|
||||
} else {
|
||||
u64 override_tid = strtoul(value, NULL, 16);
|
||||
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 == '\\') {
|
||||
value++;
|
||||
}
|
||||
snprintf(g_hbl_sd_path, FS_MAX_PATH, "@Sdcard:/%s", value);
|
||||
g_hbl_sd_path[FS_MAX_PATH] = 0;
|
||||
} else if (strcasecmp(name, "override_key") == 0) {
|
||||
if (value[0] == '!') {
|
||||
g_override_by_default = true;
|
||||
value++;
|
||||
g_hbl_override_config.override_key = ParseOverrideKey(value);
|
||||
} else if (strcasecmp(name, "override_any_app") == 0) {
|
||||
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
|
||||
g_hbl_override_config.override_any_app = true;
|
||||
} else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
|
||||
g_hbl_override_config.override_any_app = false;
|
||||
} 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;
|
||||
/* I guess we default to not changing the value? */
|
||||
}
|
||||
}
|
||||
} else if (strcasecmp(section, "default_config") == 0) {
|
||||
if (strcasecmp(name, "override_key") == 0) {
|
||||
g_default_override_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ContentManagement::RefreshConfigurationData() {
|
||||
static int LoaderTitleSpecificIniHandler(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;
|
||||
}
|
||||
|
||||
void ContentManagement::RefreshConfigurationData() {
|
||||
FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r");
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::fill(g_config_ini_data, g_config_ini_data + 0x800, 0);
|
||||
fread(g_config_ini_data, 1, 0x7FF, config);
|
||||
fclose(config);
|
||||
|
||||
|
||||
ini_parse_string(g_config_ini_data, LoaderIniHandler, NULL);
|
||||
}
|
||||
|
||||
@@ -299,28 +378,66 @@ void ContentManagement::TryMountSdCard() {
|
||||
if (R_FAILED(smGetServiceOriginal(&tmp_hnd, smEncodeName(required_active_services[i])))) {
|
||||
return;
|
||||
} else {
|
||||
svcCloseHandle(tmp_hnd);
|
||||
svcCloseHandle(tmp_hnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (R_SUCCEEDED(fsdevMountSdmc())) {
|
||||
g_has_initialized_fs_dev = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ContentManagement::ShouldReplaceWithHBL(u64 tid) {
|
||||
return g_mounted_hbl_nsp && ((g_override_any_app && IsApplicationTid(tid)) || (!g_override_any_app && tid == g_override_hbl_tid));
|
||||
static bool IsHBLTitleId(u64 tid) {
|
||||
return ((g_hbl_override_config.override_any_app && IsApplicationTid(tid)) || (tid == g_hbl_override_config.title_id));
|
||||
}
|
||||
|
||||
bool ContentManagement::ShouldOverrideContents(u64 tid) {
|
||||
if (tid >= 0x0100000000001000 && HasCreatedTitle(0x0100000000001000)) {
|
||||
u64 kDown = 0;
|
||||
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0));
|
||||
return g_has_initialized_fs_dev && (g_override_by_default ^ keys_triggered);
|
||||
OverrideKey ContentManagement::GetTitleOverrideKey(u64 tid) {
|
||||
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;
|
||||
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysHeld(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
||||
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 {
|
||||
/* Always redirect before qlaunch. */
|
||||
return g_has_initialized_fs_dev;
|
||||
/* 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 {
|
||||
/* Always redirect before qlaunch. */
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
/* Never redirect before we can do so. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
#include "ldr_registration.hpp"
|
||||
|
||||
struct OverrideKey {
|
||||
u64 key_combination;
|
||||
bool override_by_default;
|
||||
};
|
||||
|
||||
class ContentManagement {
|
||||
public:
|
||||
static Result MountCode(u64 tid, FsStorageId sid);
|
||||
@@ -31,14 +36,17 @@ class ContentManagement {
|
||||
static Result RedirectContentPath(const char *path, u64 tid, FsStorageId sid);
|
||||
static Result ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid);
|
||||
static Result RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid);
|
||||
|
||||
|
||||
static void RedirectHtmlDocumentPathForHbl(u64 tid, FsStorageId sid);
|
||||
|
||||
static bool HasCreatedTitle(u64 tid);
|
||||
static void SetCreatedTitle(u64 tid);
|
||||
static void RefreshConfigurationData();
|
||||
static void TryMountSdCard();
|
||||
|
||||
static bool ShouldReplaceWithHBL(u64 tid);
|
||||
static bool ShouldOverrideContents(u64 tid);
|
||||
|
||||
static OverrideKey GetTitleOverrideKey(u64 tid);
|
||||
static bool ShouldOverrideContentsWithSD(u64 tid);
|
||||
static bool ShouldOverrideContentsWithHBL(u64 tid);
|
||||
|
||||
/* SetExternalContentSource extension */
|
||||
class ExternalContentSource {
|
||||
|
||||
@@ -20,14 +20,17 @@
|
||||
#include "ldr_content_management.hpp"
|
||||
#include "ldr_hid.hpp"
|
||||
|
||||
Result HidManagement::GetKeysDown(u64 *keys) {
|
||||
if (!ContentManagement::HasCreatedTitle(0x0100000000000013) || R_FAILED(hidInitialize())) {
|
||||
Result HidManagement::GetKeysHeld(u64 *keys) {
|
||||
if (!ContentManagement::HasCreatedTitle(0x0100000000000013)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
||||
}
|
||||
|
||||
if (!serviceIsActive(hidGetSessionService()) && R_FAILED(hidInitialize())) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
||||
}
|
||||
|
||||
hidScanInput();
|
||||
*keys = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
*keys = hidKeysHeld(CONTROLLER_P1_AUTO);
|
||||
|
||||
hidExit();
|
||||
return 0x0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
|
||||
class HidManagement {
|
||||
public:
|
||||
static Result GetKeysDown(u64 *keys);
|
||||
static Result GetKeysHeld(u64 *keys);
|
||||
};
|
||||
|
||||
@@ -131,7 +131,7 @@ Result MapUtils::MapCodeMemoryForProcessModern(Handle process_h, u64 base_addres
|
||||
AddressSpaceInfo address_space = {0};
|
||||
Result rc;
|
||||
|
||||
if (R_FAILED((rc = GetAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE)))) {
|
||||
if (R_FAILED((rc = GetAddressSpaceInfo(&address_space, process_h)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -193,22 +193,22 @@ Result MapUtils::MapCodeMemoryForProcessDeprecated(Handle process_h, bool is_64_
|
||||
|
||||
Result MapUtils::GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) {
|
||||
Result rc;
|
||||
if (R_FAILED((rc = svcGetInfo(&out->heap_base, 4, CUR_PROCESS_HANDLE, 0)))) {
|
||||
if (R_FAILED((rc = svcGetInfo(&out->heap_base, 4, process_h, 0)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = svcGetInfo(&out->heap_size, 5, CUR_PROCESS_HANDLE, 0)))) {
|
||||
if (R_FAILED((rc = svcGetInfo(&out->heap_size, 5, process_h, 0)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = svcGetInfo(&out->map_base, 2, CUR_PROCESS_HANDLE, 0)))) {
|
||||
if (R_FAILED((rc = svcGetInfo(&out->map_base, 2, process_h, 0)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = svcGetInfo(&out->map_size, 3, CUR_PROCESS_HANDLE, 0)))) {
|
||||
if (R_FAILED((rc = svcGetInfo(&out->map_size, 3, process_h, 0)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = svcGetInfo(&out->addspace_base, 12, CUR_PROCESS_HANDLE, 0)))) {
|
||||
if (R_FAILED((rc = svcGetInfo(&out->addspace_base, 12, process_h, 0)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = svcGetInfo(&out->addspace_size, 13, CUR_PROCESS_HANDLE, 0)))) {
|
||||
if (R_FAILED((rc = svcGetInfo(&out->addspace_size, 13, process_h, 0)))) {
|
||||
return rc;
|
||||
}
|
||||
out->heap_end = out->heap_base + out->heap_size;
|
||||
|
||||
@@ -151,13 +151,15 @@ struct MappedCodeMemory {
|
||||
return rc;
|
||||
}
|
||||
|
||||
void Unmap() {
|
||||
Result Unmap() {
|
||||
Result rc = 0;
|
||||
if (this->IsMapped()) {
|
||||
if (R_FAILED(svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->code_memory_address, this->size))) {
|
||||
if (R_FAILED((rc = svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->code_memory_address, this->size)))) {
|
||||
/* TODO: panic(). */
|
||||
}
|
||||
}
|
||||
this->mapped_address = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
|
||||
@@ -63,16 +63,21 @@ FILE *NpdmUtils::OpenNpdm(u64 title_id) {
|
||||
if ((ecs = ContentManagement::GetExternalContentSource(title_id)) != nullptr) {
|
||||
return OpenNpdmFromECS(ecs);
|
||||
}
|
||||
|
||||
if (ContentManagement::ShouldOverrideContents(title_id)) {
|
||||
if (ContentManagement::ShouldReplaceWithHBL(title_id)) {
|
||||
return OpenNpdmFromHBL();
|
||||
}
|
||||
|
||||
/* First, check HBL. */
|
||||
if (ContentManagement::ShouldOverrideContentsWithHBL(title_id)) {
|
||||
return OpenNpdmFromHBL();
|
||||
}
|
||||
|
||||
/* Next, check other override. */
|
||||
if (ContentManagement::ShouldOverrideContentsWithSD(title_id)) {
|
||||
FILE *f_out = OpenNpdmFromSdCard(title_id);
|
||||
if (f_out != NULL) {
|
||||
return f_out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Last resort: real exefs. */
|
||||
return OpenNpdmFromExeFS();
|
||||
}
|
||||
|
||||
@@ -193,8 +198,7 @@ Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) {
|
||||
info->acid->title_id_range_max = tid;
|
||||
info->aci0->title_id = tid;
|
||||
|
||||
if (ContentManagement::ShouldOverrideContents(tid) && ContentManagement::ShouldReplaceWithHBL(tid)
|
||||
&& R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) {
|
||||
if (ContentManagement::ShouldOverrideContentsWithHBL(tid) && R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) {
|
||||
NpdmInfo *original_info = &g_original_npdm_cache.info;
|
||||
/* Fix pool partition. */
|
||||
if (kernelAbove500()) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
@@ -46,16 +47,26 @@ Result NroUtils::ValidateNrrHeader(NrrHeader *header, u64 size, u64 title_id_min
|
||||
}
|
||||
|
||||
Result NroUtils::LoadNro(Registration::Process *target_proc, Handle process_h, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size, u64 *out_address) {
|
||||
NroHeader *nro;
|
||||
NroHeader nro_hdr = {0};
|
||||
MappedCodeMemory mcm_nro = {0};
|
||||
MappedCodeMemory mcm_bss = {0};
|
||||
unsigned int i;
|
||||
Result rc;
|
||||
Result rc = 0;
|
||||
u8 nro_hash[0x20];
|
||||
struct sha256_state sha_ctx;
|
||||
|
||||
/* Perform cleanup on failure. */
|
||||
ON_SCOPE_EXIT {
|
||||
if (R_FAILED(rc)) {
|
||||
mcm_nro.Close();
|
||||
mcm_bss.Close();
|
||||
}
|
||||
};
|
||||
|
||||
/* Ensure there is an available NRO slot. */
|
||||
if (std::all_of(target_proc->nro_infos.begin(), target_proc->nro_infos.end(), std::mem_fn(&Registration::NroInfo::in_use))) {
|
||||
return 0x6E09;
|
||||
rc = 0x6E09;
|
||||
return rc;
|
||||
}
|
||||
for (i = 0; i < 0x200; i++) {
|
||||
if (R_SUCCEEDED(mcm_nro.Open(process_h, target_proc->is_64_bit_addspace, nro_heap_address, nro_heap_size))) {
|
||||
@@ -67,68 +78,72 @@ Result NroUtils::LoadNro(Registration::Process *target_proc, Handle process_h, u
|
||||
}
|
||||
}
|
||||
if (i >= 0x200) {
|
||||
return 0x6609;
|
||||
rc = 0x6609;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Map the NRO. */
|
||||
if (R_FAILED((rc = mcm_nro.Map()))) {
|
||||
goto LOAD_NRO_END;
|
||||
return rc;
|
||||
}
|
||||
|
||||
nro = (NroHeader *)mcm_nro.mapped_address;
|
||||
if (nro->magic != MAGIC_NRO0) {
|
||||
rc = 0x6809;
|
||||
goto LOAD_NRO_END;
|
||||
/* Read data from NRO while it's mapped. */
|
||||
{
|
||||
nro_hdr = *((NroHeader *)mcm_nro.mapped_address);
|
||||
|
||||
if (nro_hdr.magic != MAGIC_NRO0) {
|
||||
rc = 0x6809;
|
||||
return rc;
|
||||
}
|
||||
if (nro_hdr.nro_size != nro_heap_size || nro_hdr.bss_size != bss_heap_size) {
|
||||
rc = 0x6809;
|
||||
return rc;
|
||||
}
|
||||
if ((nro_hdr.text_size & 0xFFF) || (nro_hdr.ro_size & 0xFFF) || (nro_hdr.rw_size & 0xFFF) || (nro_hdr.bss_size & 0xFFF)) {
|
||||
rc = 0x6809;
|
||||
return rc;
|
||||
}
|
||||
if (nro_hdr.text_offset != 0 || nro_hdr.text_offset + nro_hdr.text_size != nro_hdr.ro_offset || nro_hdr.ro_offset + nro_hdr.ro_size != nro_hdr.rw_offset || nro_hdr.rw_offset + nro_hdr.rw_size != nro_hdr.nro_size) {
|
||||
rc = 0x6809;
|
||||
return rc;
|
||||
}
|
||||
|
||||
sha256_init(&sha_ctx);
|
||||
sha256_update(&sha_ctx, (u8 *)mcm_nro.mapped_address, nro_hdr.nro_size);
|
||||
sha256_finalize(&sha_ctx);
|
||||
sha256_finish(&sha_ctx, nro_hash);
|
||||
}
|
||||
if (nro->nro_size != nro_heap_size || nro->bss_size != bss_heap_size) {
|
||||
rc = 0x6809;
|
||||
goto LOAD_NRO_END;
|
||||
|
||||
/* Unmap the NRO. */
|
||||
if (R_FAILED((rc = mcm_nro.Unmap()))) {
|
||||
return rc;
|
||||
}
|
||||
if ((nro->text_size & 0xFFF) || (nro->ro_size & 0xFFF) || (nro->rw_size & 0xFFF) || (nro->bss_size & 0xFFF)) {
|
||||
rc = 0x6809;
|
||||
goto LOAD_NRO_END;
|
||||
}
|
||||
if (nro->text_offset != 0 || nro->text_offset + nro->text_size != nro->ro_offset || nro->ro_offset + nro->ro_size != nro->rw_offset || nro->rw_offset + nro->rw_size != nro->nro_size) {
|
||||
rc = 0x6809;
|
||||
goto LOAD_NRO_END;
|
||||
}
|
||||
|
||||
|
||||
sha256_init(&sha_ctx);
|
||||
sha256_update(&sha_ctx, (u8 *)nro, nro->nro_size);
|
||||
sha256_finalize(&sha_ctx);
|
||||
sha256_finish(&sha_ctx, nro_hash);
|
||||
|
||||
if (!Registration::IsNroHashPresent(target_proc->index, nro_hash)) {
|
||||
rc = 0x6C09;
|
||||
goto LOAD_NRO_END;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (Registration::IsNroAlreadyLoaded(target_proc->index, nro_hash)) {
|
||||
rc = 0x7209;
|
||||
goto LOAD_NRO_END;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address, nro->text_size, 5)))) {
|
||||
goto LOAD_NRO_END;
|
||||
if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address, nro_hdr.text_size, 5)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address + nro->ro_offset, nro->ro_size, 1)))) {
|
||||
goto LOAD_NRO_END;
|
||||
if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address + nro_hdr.ro_offset, nro_hdr.ro_size, 1)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address + nro->rw_offset, nro->rw_size + nro->bss_size, 3)))) {
|
||||
goto LOAD_NRO_END;
|
||||
if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address + nro_hdr.rw_offset, nro_hdr.rw_size + nro_hdr.bss_size, 3)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
Registration::AddNroToProcess(target_proc->index, &mcm_nro, &mcm_bss, nro->text_size, nro->ro_size, nro->rw_size, nro->build_id);
|
||||
Registration::AddNroToProcess(target_proc->index, &mcm_nro, &mcm_bss, nro_hdr.text_size, nro_hdr.ro_size, nro_hdr.rw_size, nro_hdr.build_id);
|
||||
*out_address = mcm_nro.code_memory_address;
|
||||
mcm_nro.Unmap();
|
||||
mcm_bss.Unmap();
|
||||
rc = 0x0;
|
||||
|
||||
LOAD_NRO_END:
|
||||
if (R_FAILED(rc)) {
|
||||
mcm_nro.Close();
|
||||
mcm_bss.Close();
|
||||
}
|
||||
return 0x0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -71,11 +71,14 @@ FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) {
|
||||
if ((ecs = ContentManagement::GetExternalContentSource(title_id)) != nullptr) {
|
||||
return OpenNsoFromECS(index, ecs);
|
||||
}
|
||||
|
||||
/* First, check HBL. */
|
||||
if (ContentManagement::ShouldOverrideContentsWithHBL(title_id)) {
|
||||
return OpenNsoFromHBL(index);
|
||||
}
|
||||
|
||||
if (ContentManagement::ShouldOverrideContents(title_id)) {
|
||||
if (ContentManagement::ShouldReplaceWithHBL(title_id)) {
|
||||
return OpenNsoFromHBL(index);
|
||||
}
|
||||
/* Next, check secondary override. */
|
||||
if (ContentManagement::ShouldOverrideContentsWithSD(title_id)) {
|
||||
FILE *f_out = OpenNsoFromSdCard(index, title_id);
|
||||
if (f_out != NULL) {
|
||||
return f_out;
|
||||
@@ -83,6 +86,8 @@ FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, default to exefs. */
|
||||
return OpenNsoFromExeFS(index);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,10 +221,18 @@ Result ProcessCreation::CreateProcess(Handle *out_process_h, u64 index, char *nc
|
||||
/* Send the pid/tid pair to anyone interested in man-in-the-middle-attacking it. */
|
||||
Registration::AssociatePidTidForMitM(index);
|
||||
|
||||
rc = 0;
|
||||
rc = 0;
|
||||
|
||||
/* If HBL, override HTML document path. */
|
||||
if (ContentManagement::ShouldOverrideContentsWithHBL(target_process->tid_sid.title_id)) {
|
||||
ContentManagement::RedirectHtmlDocumentPathForHbl(target_process->tid_sid.title_id, target_process->tid_sid.storage_id);
|
||||
}
|
||||
|
||||
/* ECS is a one-shot operation, but we don't clear on failure. */
|
||||
ContentManagement::ClearExternalContentSource(target_process->tid_sid.title_id);
|
||||
|
||||
|
||||
CREATE_PROCESS_END:
|
||||
if (mounted_code) {
|
||||
if (R_SUCCEEDED(rc) && target_process->tid_sid.storage_id != FsStorageId_None) {
|
||||
rc = ContentManagement::UnmountCode();
|
||||
@@ -232,8 +240,7 @@ Result ProcessCreation::CreateProcess(Handle *out_process_h, u64 index, char *nc
|
||||
ContentManagement::UnmountCode();
|
||||
}
|
||||
}
|
||||
|
||||
CREATE_PROCESS_END:
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out_process_h = process_h;
|
||||
} else {
|
||||
|
||||
@@ -27,19 +27,7 @@
|
||||
#include "pm_registration.hpp"
|
||||
#include "pm_boot_mode.hpp"
|
||||
|
||||
static std::vector<Boot2KnownTitleId> g_boot2_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 std::vector<Boot2KnownTitleId> g_launched_titles;
|
||||
|
||||
static bool IsHexadecimal(const char *str) {
|
||||
while (*str) {
|
||||
@@ -52,8 +40,20 @@ static bool IsHexadecimal(const char *str) {
|
||||
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) {
|
||||
u64 local_pid;
|
||||
u64 local_pid = 0;
|
||||
|
||||
/* Don't launch a title twice during boot2. */
|
||||
if (HasLaunchedTitle(title_id)) {
|
||||
@@ -64,15 +64,15 @@ static void LaunchTitle(Boot2KnownTitleId title_id, FsStorageId storage_id, u32
|
||||
switch (rc) {
|
||||
case 0xCE01:
|
||||
/* Out of resource! */
|
||||
/* TODO: Panic(). */
|
||||
std::abort();
|
||||
break;
|
||||
case 0xDE01:
|
||||
/* Out of memory! */
|
||||
/* TODO: Panic(). */
|
||||
std::abort();
|
||||
break;
|
||||
case 0xD001:
|
||||
/* Limit Reached! */
|
||||
/* TODO: Panic(). */
|
||||
std::abort();
|
||||
break;
|
||||
default:
|
||||
/* We don't care about other issues. */
|
||||
@@ -235,8 +235,12 @@ void EmbeddedBoot2::Main() {
|
||||
|
||||
/* Launch usb. */
|
||||
LaunchTitle(Boot2KnownTitleId::usb, FsStorageId_NandSystem, 0, NULL);
|
||||
|
||||
/* Launch tma. */
|
||||
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. */
|
||||
for (auto &launch_program : g_additional_launch_programs) {
|
||||
@@ -251,7 +255,10 @@ void EmbeddedBoot2::Main() {
|
||||
if (titles_dir != NULL) {
|
||||
while ((ent = readdir(titles_dir)) != NULL) {
|
||||
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};
|
||||
strcpy(title_path, "sdmc:/atmosphere/titles/");
|
||||
strcat(title_path, ent->d_name);
|
||||
@@ -259,7 +266,7 @@ void EmbeddedBoot2::Main() {
|
||||
FILE *f_flag = fopen(title_path, "rb");
|
||||
if (f_flag != NULL) {
|
||||
fclose(f_flag);
|
||||
LaunchTitle((Boot2KnownTitleId)title_id, FsStorageId_None, 0, NULL);
|
||||
LaunchTitle(title_id, FsStorageId_None, 0, NULL);
|
||||
} else {
|
||||
/* TODO: Deprecate this in the future. */
|
||||
memset(title_path, 0, FS_MAX_PATH);
|
||||
@@ -269,7 +276,7 @@ void EmbeddedBoot2::Main() {
|
||||
f_flag = fopen(title_path, "rb");
|
||||
if (f_flag != NULL) {
|
||||
fclose(f_flag);
|
||||
LaunchTitle((Boot2KnownTitleId)title_id, FsStorageId_None, 0, NULL);
|
||||
LaunchTitle(title_id, FsStorageId_None, 0, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,6 +287,6 @@ void EmbeddedBoot2::Main() {
|
||||
/* We no longer need the SD card. */
|
||||
fsdevUnmountAll();
|
||||
|
||||
/* Clear titles. */
|
||||
/* Free the memory used to track what boot2 launches. */
|
||||
ClearLaunchedTitles();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user