Compare commits

...

48 Commits
0.8.5 ... 0.8.6

Author SHA1 Message Date
Michael Scire
2d27dab6ab ams: bump version to 0.8.6 2019-03-26 12:03:55 -07:00
Michael Scire
325c71b1b4 ams_mitm: fix erroneous comment due to copy-paste. 2019-03-26 12:03:55 -07:00
Michael Scire
f9c9c1048e fs.mitm: prefer official web content to hbl_html 2019-03-26 11:53:30 -07:00
Michael Scire
4ccb39a228 ams_mitm: add ns:web mitm for HBL web support 2019-03-26 11:35:50 -07:00
Michael Scire
ad8acaefec fs.mitm: fix some filesystem issues 2019-03-26 09:05:19 -07:00
Michael Scire
106ae81614 fatal: improve start address detection 2019-03-26 09:04:32 -07:00
Michael Scire
93745dc40c docs: update, add 0.8.6 changelog ahead of release 2019-03-25 17:50:59 -07:00
Michael Scire
3316820f86 pm: use fixed-sized buf + scoped lock (gcc 8.3 compat) 2019-03-25 17:12:19 -07:00
Michael Scire
f4950ff26e dmnt-cheat: Add support for saving/restoring cheat toggle state 2019-03-25 10:35:08 -07:00
Michael Scire
20ba6432b9 set.mitm: fix 1.0.0 compat (closes #459) 2019-03-24 19:27:37 -07:00
BlockBuilder57
a611027eeb Add info to set_mitm docs, clean up loader docs (#488)
* Add info to set_mitm docs, clean up loader docs

Also add the list of buttons found in all cases of ParseOverrideKey

* Fix typo in fs_mitm.md
2019-03-24 15:39:16 -07:00
Michael Scire
1a82b407a4 sept-sign: remove += for compatibility 2019-03-24 08:26:14 -07:00
Michael Scire
dc1db0dc72 loader: Ensure code:/ unmounts if mounted. 2019-03-23 18:27:53 -07:00
Michael Scire
047f3b653e fs.mitm: also give choinx access to boot1 (closes #485) 2019-03-23 16:33:49 -07:00
Michael Scire
79244078a6 fs.mitm: make HBL web content work (verified on hw) 2019-03-22 16:54:37 -08:00
Michael Scire
fce2099d7d fs.mitm: fix error in FsPathUtils::VerifyPath 2019-03-22 15:06:50 -07:00
Michael Scire
22d4de27f1 fs.mitm: fix null deref 2019-03-22 14:49:07 -07:00
Michael Scire
9d5ca47ac8 fs.mitm: Add Hbl Web override support, also support choinx" 2019-03-22 12:41:51 -07:00
Michael Scire
c1588d0300 fs.mitm: Implement path normalization 2019-03-22 11:52:03 -07:00
Michael Scire
b62014554c fs.mitm: Implement SubDirectoryFileSystem 2019-03-22 11:28:09 -07:00
Michael Scire
afcaf20020 fs.mitm: ProxyFile, ProxyDirectory, ProxyFileSystem 2019-03-22 10:20:36 -07:00
Michael Scire
bcf20b4441 fs.mitm: implement ifilesystem wrapper 2019-03-22 09:51:47 -07:00
Michael Scire
c8a4a6dc58 fs.mitm: fix inverted ifile condition 2019-03-22 08:58:08 -07:00
Michael Scire
634ce933be fs.mitm: start implementation of IFileSystem api. 2019-03-22 08:49:10 -07:00
Michael Scire
ca42a9daec hbl: change default to every_app=true, change default override key 2019-03-22 07:13:49 -07:00
Michael Scire
9e1e9ff8c0 loader: separate override_any_app (deprecate =app) 2019-03-22 06:59:28 -07:00
zkitX
761c383958 Fix spelling mistake (#483) 2019-03-21 03:09:44 -07:00
Michael Scire
48e4688c13 loader: begin needed support for HBL to use web browser commands 2019-03-20 07:53:56 -07:00
TuxSH
7ddaad615b loader; suppress gcc warning 2019-03-19 21:29:31 +01:00
thedax
a5854afd2f Update BCT.md to bring it into the modern age. (#482)
* Bring BCT.md into the modern age. It was terribly outdated.

* Added notes on debugging modes
2019-03-19 02:46:12 -07:00
Michael Scire
db3c5cf20f loader: fix ldr:ro unmap semantics for < 3.0.0 2019-03-16 19:16:19 -07:00
tslater2006
e7f941fa3d Update cheat docs with latest opcodes (#479)
* Update cheat docs with latest opcodes

This covers changes made to Opcode 10 as well as the new 0xC0 opcode

* Corrected Opcode 10 documentation
2019-03-15 22:20:20 -07:00
Michael Scire
51fa778fb2 loader/ams_mitm: Change Down -> Held in API 2019-03-15 21:28:38 -07:00
Michael Scire
35167da6dd loader: Actually use hidKeysHeld 2019-03-15 19:51:00 -07:00
Michael Scire
2a973b9e16 dmnt-cheat: extend StoreRegisterToAddressOpcode some more 2019-03-15 19:24:23 -07:00
Michael Scire
83626923cf loader/ams_mitm: hidKeysHeld, not hidKeysDown (and remove workaround) 2019-03-15 18:34:55 -07:00
Michael Scire
7551bebb88 dmnt-cheat: Fix a few bugs in vm. 2019-03-15 18:29:43 -07:00
Michael Scire
433b01aaf8 dmnt-cheat: add other register source to new condition opcode 2019-03-15 13:52:11 -07:00
Michael Scire
da664b49ad dmnt-cheat: amend inline docs 2019-03-15 13:46:53 -07:00
Michael Scire
5d79952bdd dmnt-cheat: Add register conditional vm instruction 2019-03-15 13:45:35 -07:00
Michael Scire
e5ecd243f2 dmnt-cheat: Implement real workaround for 6.0.0+ kernel bug 2019-03-15 03:30:51 -07:00
Michael Scire
274035edd6 Add 64-bit IDA binaries to gitignore 2019-03-15 00:25:24 -07:00
Michael Scire
60776e8111 loader: fix ldr:ro mapping error on < 3.0.0 2019-03-15 00:25:09 -07:00
Michael Scire
3fcad4bc65 exo: fix SE driver coherency bug (closes #384) 2019-03-14 13:07:54 -07:00
Michael Scire
991fe78740 creport/fatal: fix time-retrieval functionality 2019-03-14 09:16:55 -07:00
Michael Scire
aac64b1ded dmnt-cheat: workaround for 6.0.0+ kernel bug. 2019-03-14 09:15:59 -07:00
Léo Lam
b6d3df3335 fs_mitm: Fix mismatched new[] / delete (#389)
* fs.mitm: Fix mismatched new[] / delete

Using delete instead of delete[] on a pointer given by new[] is
undefined behaviour.

For memory sources, malloc/free are used because cleaning up is tricky
when data can be either allocated with new (RomfsHeader) or new[]
(metadata).

* set.mitm: Fix mismatched new[] / delete
2019-03-08 07:25:33 -08:00
Michael Scire
66560d0a7b fs.mitm: fix two cases of inverted logic 2019-03-07 19:53:21 -08:00
76 changed files with 3831 additions and 482 deletions

View File

@@ -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
View File

@@ -70,6 +70,7 @@ dkms.conf
*.id1
*.id2
*.idb
*.i64
*.nam
*.til

View File

@@ -64,6 +64,7 @@ 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

View File

@@ -0,0 +1 @@
^http*

View File

@@ -1,7 +1,8 @@
[hbl_config]
title_id=010000000000100D
override_any_app=true
path=atmosphere/hbl.nsp
override_key=!R
override_key=R
[default_config]
override_key=!L

View File

@@ -11,4 +11,8 @@ usb30_force_enabled = u8!0x0
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
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

View File

@@ -19,7 +19,7 @@
#define ATMOSPHERE_RELEASE_VERSION_MAJOR 0
#define ATMOSPHERE_RELEASE_VERSION_MINOR 8
#define ATMOSPHERE_RELEASE_VERSION_MICRO 5
#define ATMOSPHERE_RELEASE_VERSION_MICRO 6
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR 7
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR 0

View File

@@ -1,4 +1,41 @@
# 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.

View File

@@ -283,22 +283,24 @@ Code type 10 allows writing a register to memory.
#### Encoding
`ATSRIOra (aaaaaaaa)`
`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.
+ r: Register used as offset when O is 1.
+ a: Value used as offset when O is 2.
+ 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
@@ -313,4 +315,51 @@ Code Types 12-15 signal to the VM to treat the upper two nybbles of the first dw
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: !=

View File

@@ -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
```

View File

@@ -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.

View File

@@ -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 (title_id in the [hbl_config] section) (it is the Title ID for the album by default) with the Title ID of whatever game you wish to use to launch the Homebrew Menu. A list of Title IDs for Switch Games can be found [here](https://switchbrew.org/wiki/Title_list/Games). Afterwards you may reinsert your SD Card into your Switch and boot into Atmosphère as you normally would. You should now be able to boot into the Homebrew Menu by launching your designated game of choice.
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

View File

@@ -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
```

View File

@@ -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 */ }

View File

@@ -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'))

View File

@@ -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

View File

@@ -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() {

View File

@@ -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,

View 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,
};

View 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);
}
};

View 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);
}
};

View 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));
}
};

View File

@@ -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;

View 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;
}

View 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] == ':';
}
};

View 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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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));

View File

@@ -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>(),

View 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);
}

View 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

View 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);
}

View 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>(),
};
};

View 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;
}

View 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);

View 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>
#include <stratosphere.hpp>
#include "../utils.hpp"
enum NsGetterCmd : u32 {
NsGetterCmd_GetDocumentInterface = 7999,
};
enum NsSrvCmd : u32 {
NsSrvCmd_GetApplicationContentPath = 21,
NsSrvCmd_ResolveApplicationContentPath = 23,
NsSrvCmd_GetRunningApplicationProgramId = 92,
};

View 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);
}

View 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>(),
};
};

View File

@@ -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);

View File

@@ -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, &micro, 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, &micro, 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;
}

View File

@@ -20,5 +20,6 @@
class VersionManager {
public:
static void Initialize();
static Result GetFirmwareVersion(u64 title_id, SetSysFirmwareVersion *out);
};

View File

@@ -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. */

View File

@@ -220,8 +220,6 @@ void Utils::InitializeThreadFunc(void *args) {
}
g_has_hid_session = true;
hidExit();
}
}
@@ -382,7 +380,11 @@ Result Utils::SaveSdFileForAtmosphere(u64 title_id, const char *fn, void *data,
}
bool Utils::IsHblTid(u64 tid) {
return (g_hbl_override_config.override_any_app && IsApplicationTid(tid)) || (!g_hbl_override_config.override_any_app && tid == g_hbl_override_config.title_id);
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) {
@@ -392,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;
}
@@ -412,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;
}
@@ -448,21 +450,20 @@ 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::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0));
bool keys_triggered = (R_SUCCEEDED(Utils::GetKeysHeld(&kDown)) && ((kDown & cfg->key_combination) != 0));
return Utils::IsSdInitialized() && (cfg->override_by_default ^ keys_triggered);
}
@@ -544,9 +545,10 @@ static int FsMitmIniHandler(void *user, const char *section, const char *name, c
if (strcasecmp(section, "hbl_config") == 0) {
if (strcasecmp(name, "title_id") == 0) {
if (strcasecmp(value, "app") == 0) {
/* DEPRECATED */
g_hbl_override_config.override_any_app = true;
}
else {
g_hbl_override_config.title_id = 0;
} else {
u64 override_tid = strtoul(value, NULL, 16);
if (override_tid != 0) {
g_hbl_override_config.title_id = override_tid;
@@ -554,6 +556,14 @@ static int FsMitmIniHandler(void *user, const char *section, const char *name, c
}
} else if (strcasecmp(name, "override_key") == 0) {
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 {
/* I guess we default to not changing the value? */
}
}
} else if (strcasecmp(section, "default_config") == 0) {
if (strcasecmp(name, "override_key") == 0) {

View File

@@ -65,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);
@@ -76,7 +77,7 @@ 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);

View File

@@ -23,6 +23,7 @@
"erpt:c",
"fatal:u",
"ns:dev",
"time:*",
"fsp-srv"
],
"kernel_capabilities": [

View 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();
}
}
}

View 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();
};

File diff suppressed because it is too large Load Diff

View File

@@ -42,8 +42,13 @@ class DmntCheatManager {
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:

View File

@@ -24,7 +24,7 @@
void DmntCheatVm::OpenDebugLogFile() {
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
CloseDebugLogFile();
this->debug_log_file = fopen("cheat_vm_log.txt", "wb");
this->debug_log_file = fopen("cheat_vm_log.txt", "ab");
#endif
}
@@ -142,8 +142,52 @@ void DmntCheatVm::LogOpcode(const CheatVmOpcode *opcode) {
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;
@@ -208,6 +252,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) {
switch (opcode.opcode) {
case CheatVmOpcodeType_BeginConditionalBlock:
case CheatVmOpcodeType_BeginKeypressConditionalBlock:
case CheatVmOpcodeType_BeginRegisterConditionalBlock:
opcode.begin_conditional_block = true;
break;
default:
@@ -264,7 +309,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) {
{
/* 400R0000 VVVVVVVV VVVVVVVV */
/* Read additional words. */
opcode.ldr_static.reg_index = ((first_dword >> 20) & 0xF);
opcode.ldr_static.reg_index = ((first_dword >> 16) & 0xF);
opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword());
}
break;
@@ -327,15 +372,16 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) {
break;
case CheatVmOpcodeType_StoreRegisterToAddress:
{
/* ATSRIOra (aaaaaaaa) */
/* 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 */
/* r = offset register (for offset type 1) */
/* a = relative address (for offset type 2) */
/* 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);
@@ -350,12 +396,72 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) {
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. */
@@ -373,7 +479,7 @@ void DmntCheatVm::SkipConditionalBlock() {
const size_t desired_depth = this->condition_depth - 1;
CheatVmOpcode skip_opcode;
while (this->DecodeNextOpcode(&skip_opcode) && this->condition_depth > desired_depth) {
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" */
@@ -736,6 +842,15 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) {
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. */
@@ -754,6 +869,101 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) {
}
}
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;

View File

@@ -35,10 +35,14 @@ enum CheatVmOpcodeType : u32 {
/* 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 in the future. */
/* This is to facilitate multi-nybble instruction decoding. */
CheatVmOpcodeType_ExtendedWidth = 12,
/* Extended width opcodes. */
CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0,
};
enum MemoryAccessType : u32 {
@@ -75,6 +79,18 @@ 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 {
@@ -157,10 +173,24 @@ struct StoreRegisterToAddressOpcode {
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;
@@ -177,6 +207,7 @@ struct CheatVmOpcode {
BeginKeypressConditionalOpcode begin_keypress_cond;
PerformArithmeticRegisterOpcode perform_math_reg;
StoreRegisterToAddressOpcode str_register;
BeginRegisterConditionalOpcode begin_reg_cond;
};
};

View File

@@ -26,7 +26,7 @@ Result HidManagement::GetKeysDown(u64 *keys) {
std::scoped_lock<HosMutex> lk(g_hid_keys_down_lock);
hidScanInput();
*keys = hidKeysDown(CONTROLLER_P1_AUTO);
*keys = hidKeysHeld(CONTROLLER_P1_AUTO);
return 0x0;
}

View File

@@ -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",

View File

@@ -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;
}
}
}
}
}

View File

@@ -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>
@@ -68,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 (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;
}
@@ -121,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;
}
@@ -136,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;
}
@@ -156,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(&reg))) {
return rc;
}
if (R_SUCCEEDED(rc = lrRegLrResolveProgramPath(&reg, tid, path))) {
strncpy(out_path, path, FS_MAX_PATH);
} else if (rc != 0x408) {
return rc;
}
serviceClose(&reg.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;
}
@@ -194,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;
}
@@ -210,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();
}
@@ -222,7 +248,7 @@ 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;
@@ -230,7 +256,7 @@ static OverrideKey ParseOverrideKey(const char *value) {
} else {
cfg.override_by_default = false;
}
/* Parse key combination. */
if (strcasecmp(value, "A") == 0) {
cfg.key_combination = KEY_A;
@@ -271,7 +297,7 @@ static OverrideKey ParseOverrideKey(const char *value) {
} else {
cfg.key_combination = 0;
}
return cfg;
}
@@ -280,9 +306,10 @@ static int LoaderIniHandler(void *user, const char *section, const char *name, c
if (strcasecmp(section, "hbl_config") == 0) {
if (strcasecmp(name, "title_id") == 0) {
if (strcasecmp(value, "app") == 0) {
/* DEPRECATED */
g_hbl_override_config.override_any_app = true;
}
else {
g_hbl_override_config.title_id = 0;
} else {
u64 override_tid = strtoul(value, NULL, 16);
if (override_tid != 0) {
g_hbl_override_config.title_id = override_tid;
@@ -296,6 +323,14 @@ static int LoaderIniHandler(void *user, const char *section, const char *name, c
g_hbl_sd_path[FS_MAX_PATH] = 0;
} else if (strcasecmp(name, "override_key") == 0) {
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 {
/* I guess we default to not changing the value? */
}
}
} else if (strcasecmp(section, "default_config") == 0) {
if (strcasecmp(name, "override_key") == 0) {
@@ -310,7 +345,7 @@ static int LoaderIniHandler(void *user, const char *section, const char *name, c
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);
@@ -326,11 +361,11 @@ void ContentManagement::RefreshConfigurationData() {
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);
}
@@ -343,10 +378,10 @@ 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;
}
@@ -354,15 +389,15 @@ void ContentManagement::TryMountSdCard() {
}
static bool IsHBLTitleId(u64 tid) {
return ((g_hbl_override_config.override_any_app && IsApplicationTid(tid)) || (!g_hbl_override_config.override_any_app && tid == g_hbl_override_config.title_id));
return ((g_hbl_override_config.override_any_app && IsApplicationTid(tid)) || (tid == g_hbl_override_config.title_id));
}
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);
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); };
@@ -370,13 +405,13 @@ OverrideKey ContentManagement::GetTitleOverrideKey(u64 tid) {
/* 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::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 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);
}

View File

@@ -36,12 +36,14 @@ 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 OverrideKey GetTitleOverrideKey(u64 tid);
static bool ShouldOverrideContentsWithSD(u64 tid);
static bool ShouldOverrideContentsWithHBL(u64 tid);

View File

@@ -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;
}
}

View File

@@ -19,5 +19,5 @@
class HidManagement {
public:
static Result GetKeysDown(u64 *keys);
static Result GetKeysHeld(u64 *keys);
};

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -283,13 +283,10 @@ void EmbeddedBoot2::Main() {
}
closedir(titles_dir);
}
/* Free the memory used to track what boot2 launches. */
ClearLaunchedTitles();
/* We no longer need the SD card. */
fsdevUnmountAll();
/* Clear titles. */
/* Free the memory used to track what boot2 launches. */
ClearLaunchedTitles();
}

View File

@@ -42,7 +42,7 @@ Result DebugMonitorService::LaunchDebugProcess(u64 pid) {
}
Result DebugMonitorService::GetTitleProcessId(Out<u64> pid, u64 tid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
std::shared_ptr<Registration::Process> proc = Registration::GetProcessByTitleId(tid);
if (proc != nullptr) {
@@ -57,7 +57,7 @@ Result DebugMonitorService::EnableDebugForTitleId(Out<CopiedHandle> event, u64 t
}
Result DebugMonitorService::GetApplicationProcessId(Out<u64> pid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
std::shared_ptr<Registration::Process> app_proc;
if (Registration::HasApplicationProcess(&app_proc)) {

View File

@@ -19,7 +19,7 @@
#include "pm_info.hpp"
Result InformationService::GetTitleId(Out<u64> tid, u64 pid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
std::shared_ptr<Registration::Process> proc = Registration::GetProcess(pid);
if (proc != NULL) {

View File

@@ -44,12 +44,24 @@ class ProcessWaiter final : public IWaitable {
class ProcessList final {
private:
HosRecursiveMutex mutex;
HosRecursiveMutex m;
HosRecursiveMutex *GetMutex() {
return &this->m;
}
public:
std::vector<std::shared_ptr<Registration::Process>> processes;
auto GetUniqueLock() {
return std::unique_lock{this->mutex};
void lock() {
GetMutex()->lock();
}
void unlock() {
GetMutex()->unlock();
}
void try_lock() {
GetMutex()->try_lock();
}
};

View File

@@ -13,14 +13,12 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include <atomic>
#include "pm_registration.hpp"
#include "pm_resource_limits.hpp"
#include "pm_process_wait.hpp"
static ProcessList g_process_list;
static ProcessList g_dead_process_list;
@@ -38,8 +36,10 @@ static IEvent *g_process_event = nullptr;
static IEvent *g_debug_title_event = nullptr;
static IEvent *g_debug_application_event = nullptr;
std::unique_lock<HosRecursiveMutex> Registration::GetProcessListUniqueLock() {
return g_process_list.GetUniqueLock();
static u8 g_ac_buf[4 * sizeof(LoaderProgramInfo)];
ProcessList &Registration::GetProcessList() {
return g_process_list;
}
void Registration::InitializeSystemResources() {
@@ -70,9 +70,8 @@ void Registration::HandleProcessLaunch() {
u64 *out_pid = g_process_launch_state.out_pid;
Process new_process = {0};
new_process.tid_sid = g_process_launch_state.tid_sid;
auto ac_buf = std::vector<u8>(4 * sizeof(LoaderProgramInfo));
std::fill(ac_buf.begin(), ac_buf.end(), 0xCC);
u8 *acid_sac = ac_buf.data(), *aci0_sac = acid_sac + sizeof(LoaderProgramInfo), *fac = aci0_sac + sizeof(LoaderProgramInfo), *fah = fac + sizeof(LoaderProgramInfo);
std::memset(g_ac_buf, 0xCC, sizeof(g_ac_buf));
u8 *acid_sac = g_ac_buf, *aci0_sac = acid_sac + sizeof(LoaderProgramInfo), *fac = aci0_sac + sizeof(LoaderProgramInfo), *fah = fac + sizeof(LoaderProgramInfo);
/* Check that this is a real program. */
if (R_FAILED((rc = ldrPmGetProgramInfo(new_process.tid_sid.title_id, new_process.tid_sid.storage_id, &program_info)))) {
@@ -185,7 +184,7 @@ HANDLE_PROCESS_LAUNCH_END:
Result Registration::LaunchDebugProcess(u64 pid) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
LoaderProgramInfo program_info = {0};
Result rc;
@@ -288,48 +287,56 @@ Result Registration::HandleSignaledProcess(std::shared_ptr<Registration::Process
}
void Registration::FinalizeExitedProcess(std::shared_ptr<Registration::Process> process) {
auto auto_lock = GetProcessListUniqueLock();
bool signal_debug_process_5x = kernelAbove500() && process->flags & PROCESSFLAGS_NOTIFYWHENEXITED;
/* Unregister with FS. */
if (R_FAILED(fsprUnregisterProgram(process->pid))) {
std::abort();
}
/* Unregister with SM. */
if (R_FAILED(smManagerUnregisterProcess(process->pid))) {
std::abort();
}
/* Unregister with LDR. */
if (R_FAILED(ldrPmUnregisterTitle(process->ldr_queue_index))) {
std::abort();
}
/* Close the process's handle. */
svcCloseHandle(process->handle);
process->handle = 0;
/* Insert into dead process list, if relevant. */
if (signal_debug_process_5x) {
auto lk = g_dead_process_list.GetUniqueLock();
g_dead_process_list.processes.push_back(process);
}
/* Remove NOTE: This probably frees process. */
RemoveProcessFromList(process->pid);
bool signal_debug_process_5x;
/* Scope to manage process list lock. */
{
std::scoped_lock<ProcessList &> lk(GetProcessList());
signal_debug_process_5x = kernelAbove500() && process->flags & PROCESSFLAGS_NOTIFYWHENEXITED;
/* Unregister with FS. */
if (R_FAILED(fsprUnregisterProgram(process->pid))) {
std::abort();
}
/* Unregister with SM. */
if (R_FAILED(smManagerUnregisterProcess(process->pid))) {
std::abort();
}
/* Unregister with LDR. */
if (R_FAILED(ldrPmUnregisterTitle(process->ldr_queue_index))) {
std::abort();
}
/* Close the process's handle. */
svcCloseHandle(process->handle);
process->handle = 0;
/* Insert into dead process list, if relevant. */
if (signal_debug_process_5x) {
std::scoped_lock<ProcessList &> dead_lk(g_dead_process_list);
g_dead_process_list.processes.push_back(process);
}
/* Remove NOTE: This probably frees process. */
RemoveProcessFromList(process->pid);
}
auto_lock.unlock();
if (signal_debug_process_5x) {
g_process_event->Signal();
}
}
void Registration::AddProcessToList(std::shared_ptr<Registration::Process> process) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
g_process_list.processes.push_back(process);
g_process_launch_start_event->GetManager()->AddWaitable(new ProcessWaiter(process));
}
void Registration::RemoveProcessFromList(u64 pid) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
/* Remove process from list. */
for (unsigned int i = 0; i < g_process_list.processes.size(); i++) {
@@ -344,7 +351,7 @@ void Registration::RemoveProcessFromList(u64 pid) {
}
void Registration::SetProcessState(u64 pid, ProcessState new_state) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
/* Set process state. */
for (auto &process : g_process_list.processes) {
@@ -356,7 +363,7 @@ void Registration::SetProcessState(u64 pid, ProcessState new_state) {
}
bool Registration::HasApplicationProcess(std::shared_ptr<Registration::Process> *out) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
for (auto &process : g_process_list.processes) {
if (process->flags & PROCESSFLAGS_APPLICATION) {
@@ -371,7 +378,7 @@ bool Registration::HasApplicationProcess(std::shared_ptr<Registration::Process>
}
std::shared_ptr<Registration::Process> Registration::GetProcess(u64 pid) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
for (auto &process : g_process_list.processes) {
if (process->pid == pid) {
@@ -383,7 +390,7 @@ std::shared_ptr<Registration::Process> Registration::GetProcess(u64 pid) {
}
std::shared_ptr<Registration::Process> Registration::GetProcessByTitleId(u64 tid) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
for (auto &process : g_process_list.processes) {
if (process->tid_sid.title_id == tid) {
@@ -396,7 +403,7 @@ std::shared_ptr<Registration::Process> Registration::GetProcessByTitleId(u64 tid
Result Registration::GetDebugProcessIds(u64 *out_pids, u32 max_out, u32 *num_out) {
auto auto_lock = GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(GetProcessList());
u32 num = 0;
@@ -415,42 +422,49 @@ Handle Registration::GetProcessEventHandle() {
}
void Registration::GetProcessEventType(u64 *out_pid, u64 *out_type) {
auto auto_lock = GetProcessListUniqueLock();
for (auto &p : g_process_list.processes) {
if (kernelAbove200() && p->state >= ProcessState_Running && p->flags & PROCESSFLAGS_DEBUGDETACHED) {
p->flags &= ~PROCESSFLAGS_DEBUGDETACHED;
*out_pid = p->pid;
*out_type = kernelAbove500() ? PROCESSEVENTTYPE_500_DEBUGDETACHED : PROCESSEVENTTYPE_DEBUGDETACHED;
return;
}
if (p->flags & PROCESSFLAGS_DEBUGEVENTPENDING) {
u64 old_flags = p->flags;
p->flags &= ~PROCESSFLAGS_DEBUGEVENTPENDING;
*out_pid = p->pid;
*out_type = kernelAbove500() ?
((old_flags & PROCESSFLAGS_DEBUGSUSPENDED) ?
PROCESSEVENTTYPE_500_SUSPENDED :
PROCESSEVENTTYPE_500_RUNNING) :
((old_flags & PROCESSFLAGS_DEBUGSUSPENDED) ?
PROCESSEVENTTYPE_SUSPENDED :
PROCESSEVENTTYPE_RUNNING);
return;
}
if (p->flags & PROCESSFLAGS_CRASHED) {
*out_pid = p->pid;
*out_type = kernelAbove500() ? PROCESSEVENTTYPE_500_CRASH : PROCESSEVENTTYPE_CRASH;
return;
}
if (!kernelAbove500() && p->flags & PROCESSFLAGS_NOTIFYWHENEXITED && p->state == ProcessState_Exited) {
*out_pid = p->pid;
*out_type = PROCESSEVENTTYPE_EXIT;
return;
/* Scope to manage process list lock. */
{
std::scoped_lock<ProcessList &> lk(GetProcessList());
for (auto &p : g_process_list.processes) {
if (kernelAbove200() && p->state >= ProcessState_Running && p->flags & PROCESSFLAGS_DEBUGDETACHED) {
p->flags &= ~PROCESSFLAGS_DEBUGDETACHED;
*out_pid = p->pid;
*out_type = kernelAbove500() ? PROCESSEVENTTYPE_500_DEBUGDETACHED : PROCESSEVENTTYPE_DEBUGDETACHED;
return;
}
if (p->flags & PROCESSFLAGS_DEBUGEVENTPENDING) {
u64 old_flags = p->flags;
p->flags &= ~PROCESSFLAGS_DEBUGEVENTPENDING;
*out_pid = p->pid;
*out_type = kernelAbove500() ?
((old_flags & PROCESSFLAGS_DEBUGSUSPENDED) ?
PROCESSEVENTTYPE_500_SUSPENDED :
PROCESSEVENTTYPE_500_RUNNING) :
((old_flags & PROCESSFLAGS_DEBUGSUSPENDED) ?
PROCESSEVENTTYPE_SUSPENDED :
PROCESSEVENTTYPE_RUNNING);
return;
}
if (p->flags & PROCESSFLAGS_CRASHED) {
*out_pid = p->pid;
*out_type = kernelAbove500() ? PROCESSEVENTTYPE_500_CRASH : PROCESSEVENTTYPE_CRASH;
return;
}
if (!kernelAbove500() && p->flags & PROCESSFLAGS_NOTIFYWHENEXITED && p->state == ProcessState_Exited) {
*out_pid = p->pid;
*out_type = PROCESSEVENTTYPE_EXIT;
return;
}
}
*out_pid = 0;
*out_type = 0;
}
if (kernelAbove500()) {
auto_lock.unlock();
auto dead_process_list_lock = g_dead_process_list.GetUniqueLock();
std::scoped_lock<ProcessList &> dead_lk(g_dead_process_list);
if (g_dead_process_list.processes.size()) {
std::shared_ptr<Registration::Process> process = g_dead_process_list.processes[0];
g_dead_process_list.processes.erase(g_dead_process_list.processes.begin());
@@ -459,8 +473,6 @@ void Registration::GetProcessEventType(u64 *out_pid, u64 *out_type) {
return;
}
}
*out_pid = 0;
*out_type = 0;
}

View File

@@ -20,6 +20,8 @@
#include <memory>
#include <mutex>
class ProcessList;
#define LAUNCHFLAGS_NOTIFYWHENEXITED(flags) (flags & 1)
#define LAUNCHFLAGS_STARTSUSPENDED(flags) (flags & (kernelAbove500() ? 0x10 : 0x2))
#define LAUNCHFLAGS_ARGLOW(flags) (kernelAbove500() ? ((flags & 0x14) != 0x10) : (kernelAbove200() ? ((flags & 0x6) != 0x2) : ((flags >> 2) & 1)))
@@ -171,7 +173,7 @@ class Registration {
static void InitializeSystemResources();
static IWaitable *GetProcessLaunchStartEvent();
static std::unique_lock<HosRecursiveMutex> GetProcessListUniqueLock();
static ProcessList &GetProcessList();
static Result ProcessLaunchStartCallback(u64 timeout);
static Result HandleSignaledProcess(std::shared_ptr<Process> process);
@@ -201,3 +203,4 @@ class Registration {
static bool HasApplicationProcess(std::shared_ptr<Process> *out);
};
#include "pm_process_wait.hpp"

View File

@@ -28,7 +28,7 @@ Result ShellService::LaunchProcess(Out<u64> pid, Registration::TidSid tid_sid, u
}
Result ShellService::TerminateProcessId(u64 pid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
auto proc = Registration::GetProcess(pid);
if (proc != nullptr) {
@@ -39,7 +39,7 @@ Result ShellService::TerminateProcessId(u64 pid) {
}
Result ShellService::TerminateTitleId(u64 tid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
auto proc = Registration::GetProcessByTitleId(tid);
if (proc != NULL) {
@@ -58,7 +58,7 @@ void ShellService::GetProcessEventType(Out<u64> type, Out<u64> pid) {
}
Result ShellService::FinalizeExitedProcess(u64 pid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
auto proc = Registration::GetProcess(pid);
if (proc == NULL) {
@@ -72,7 +72,7 @@ Result ShellService::FinalizeExitedProcess(u64 pid) {
}
Result ShellService::ClearProcessNotificationFlag(u64 pid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
auto proc = Registration::GetProcess(pid);
if (proc != NULL) {
@@ -91,7 +91,7 @@ void ShellService::NotifyBootFinished() {
}
Result ShellService::GetApplicationProcessId(Out<u64> pid) {
auto auto_lock = Registration::GetProcessListUniqueLock();
std::scoped_lock<ProcessList &> lk(Registration::GetProcessList());
std::shared_ptr<Registration::Process> app_proc;
if (Registration::HasApplicationProcess(&app_proc)) {