Compare commits
1 Commits
0.8.5
...
debugger_d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37025258c6 |
4
Makefile
4
Makefile
@@ -53,8 +53,9 @@ dist: all
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032
|
||||
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000007
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D
|
||||
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin
|
||||
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/atmosphere/fusee-secondary.bin
|
||||
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/sept/payload.bin
|
||||
cp sept/sept-primary/sept-primary.bin atmosphere-$(AMSVER)/sept/sept-primary.bin
|
||||
@@ -70,6 +71,7 @@ dist: all
|
||||
cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro
|
||||
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags
|
||||
touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags/boot2.flag
|
||||
cp stratosphere/tma/tma.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000007/exefs.nsp
|
||||
cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D/exefs.nsp
|
||||
cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../;
|
||||
rm -r atmosphere-$(AMSVER)
|
||||
|
||||
@@ -8,7 +8,4 @@ usb30_force_enabled = u8!0x0
|
||||
[atmosphere]
|
||||
; Make the power menu's "reboot" button reboot to payload.
|
||||
; Set to "normal" for normal reboot, "rcm" for rcm reboot.
|
||||
power_menu_reboot_function = str!payload
|
||||
; 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
|
||||
power_menu_reboot_function = str!payload
|
||||
@@ -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 4
|
||||
|
||||
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR 7
|
||||
#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR 0
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
# Changelog
|
||||
## 0.8.5
|
||||
+ Support was added for overriding content on a per-title basis, separate from HBL override.
|
||||
+ This allows for using mods on the same title that one uses to launch HBL.
|
||||
+ By default, `!L` is used for title content override (this is configurable by editing `default_config!override_key` in `loader.ini`)
|
||||
+ This key combination can be set on a per-title basis by creating a `atmosphere/titles/<title id>/config.ini`, and editing `override_config!override_key`.
|
||||
+ Content headers were added for the embedded files inside of fusee-secondary.
|
||||
+ This will allow non-fusee bootloaders (like `hekate`) to extract the components bundled inside release binaries.
|
||||
+ This should greatly simplify the update process in the future, for users who do not launch Atmosphère using fusee.
|
||||
+ Support for cheat codes was added.
|
||||
+ These are handled by a new `dmnt` sysmodule, which will also reimplement Nintendo's Debug Monitor in the future.
|
||||
+ Cheat codes can be enabled/disabled at application launch via a per-title key combination.
|
||||
+ For details, please see the [cheat loading documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/cheats.md#cheat-loating-process).
|
||||
+ Cheat codes are fully backwards compatible with the pre-existing format, although a number of bugs have been fixed and some new features have been added.
|
||||
+ For details, please see [the compatibility documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/cheats.md#cheat-code-compatibility).
|
||||
+ An HIPC service API was added (`dmnt:cht`), that will allow user homebrew to interface with and control Atmosphère's cheat manager.
|
||||
+ Please see [the relevant documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/modules/dmnt.md).
|
||||
+ Full client code can be found in [libstratosphere](https://github.com/Atmosphere-NX/libstratosphere/blob/master/include/stratosphere/services/dmntcht.h).
|
||||
+ Users interested in interfacing should see [EdiZon](https://github.com/WerWolv/EdiZon), which should have support for interfacing with Atmosphère's API shortly after 0.8.5 releases.
|
||||
+ A bug was fixed that would cause Atmosphère's fatal screen to not show on 1.0.0-2.3.0.
|
||||
+ A bug was fixed that caused Atmosphère's automatic ProdInfo backups to be corrupt.
|
||||
+ General system stability improvements to enhance the user's experience.
|
||||
## 0.8.4
|
||||
+ Support for 7.0.0/7.0.1 was added.
|
||||
+ This is facilitated through a new payload, `sept`, which can be signed, encrypted, and then loaded by Nintendo's TSEC firmware.
|
||||
|
||||
316
docs/cheats.md
316
docs/cheats.md
@@ -1,316 +0,0 @@
|
||||
# Cheats
|
||||
Atmosphère supports Action-Replay style cheat codes, with cheats loaded off of the SD card.
|
||||
|
||||
## Cheat Loading Process
|
||||
By default, Atmosphère will do the following when deciding whether to attach to a new application process:
|
||||
|
||||
+ Retrieve information about the new application process from `pm` and `loader`.
|
||||
+ Check whether a user-defined key combination is held, and stop if not.
|
||||
+ This defaults to "L is not held", and can be configured the same way as `fs.mitm` override keys.
|
||||
+ The ini key to configure this is `cheat_enable_key`.
|
||||
+ Check whether the process is a real application, and stop if not.
|
||||
+ This guards against applying cheat codes to the homebrew loader.
|
||||
+ Attempt to load cheats from `atmosphere/titles/<title_id>/cheats/<build_id>.txt`, where `build_id` is the hexadecimal representation of the first 8 bytes of the application's main executable's build id.
|
||||
+ If no cheats are found, then the cheat manager will stop.
|
||||
+ Open a kernel debug session for the new application process.
|
||||
+ Signal to a system event that a new cheat process has been attached to.
|
||||
|
||||
This behavior ensures that cheat codes are only loaded when the user would want them to.
|
||||
|
||||
In cases where dmnt has not activated the cheat manager, but the user wants to make it do so anyway, the cheat manager's service API provides a `ForceOpenCheatProcess` command that homebrew can use. This command will cause the cheat manager to try to force itself to attach to the process.
|
||||
|
||||
By default, all cheat codes listed in the loaded .txt file will be toggled on. This is configurable by the user, and the default can be set to toggled off by editing the `atmosphere!dmnt_cheats_enabled_by_default` entry to 0 instead of 1.
|
||||
|
||||
Users may use homebrew programs to toggle cheats on and off at runtime via the cheat manager's service API.
|
||||
|
||||
## Cheat Code Compatibility
|
||||
|
||||
Atmosphère manages cheat code through the execution of a small, custom virtual machine. Care has been taken to ensure that Atmosphère's cheat code format is fully backwards compatible with the pre-existing cheat code format, though new features have been added and bugs in the pre-existing cheat code applier have been fixed. Here is a short summary of the changes from the pre-existing format:
|
||||
|
||||
+ A number of bugs were fixed in the processing of conditional instructions.
|
||||
+ The pre-existing implementation was fundamentally broken, and checked for the wrong value when detecting the end of a conditional block.
|
||||
+ The pre-existing implementation also did not properly decode instructions, and instead linearly scanned for the terminator value. This caused problems if an instruction happened to encode a terminator inside its immediate values.
|
||||
+ The pre-existing implementation did not bounds check, and thus certain conditional cheat codes could cause it to read out-of-bounds memory, and potentially crash due to a data abort.
|
||||
+ Support was added for nesting conditional blocks.
|
||||
+ An instruction was added to perform much more complex arbitrary arithmetic on two registers.
|
||||
+ An instruction was added to allow writing the contents of register to a memory address specified by another register.
|
||||
+ The pre-existing implementation did not correctly synchronize with the application process, and thus would cause heavy lag under certain circumstances (especially around loading screens). This has been fixed in Atmosphère's implementation.
|
||||
|
||||
## Cheat Code Format
|
||||
|
||||
The following provides documentation of the instruction format for the virtual machine used to manage cheat codes.
|
||||
|
||||
Typically, instruction type is encoded in the upper nybble of the first instruction u32.
|
||||
|
||||
### Code Type 0: Store Static Value to Memory
|
||||
|
||||
Code type 0 allows writing a static value to a memory address.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ R: Register to use as an offset from memory region base.
|
||||
+ A: Immediate offset to use from memory region base.
|
||||
+ V: Value to write.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 1: Begin Conditional Block
|
||||
|
||||
Code type 1 performs a comparison of the contents of memory to a static value.
|
||||
|
||||
If the condition is not met, all instructions until the appropriate conditional block terminator are skipped.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ C: Condition to use, see below.
|
||||
+ A: Immediate offset to use from memory region base.
|
||||
+ V: Value to compare to.
|
||||
|
||||
#### Conditions
|
||||
|
||||
+ 1: >
|
||||
+ 2: >=
|
||||
+ 3: <
|
||||
+ 4: <=
|
||||
+ 5: ==
|
||||
+ 6: !=
|
||||
|
||||
---
|
||||
|
||||
### Code Type 2: End Conditional Block
|
||||
|
||||
Code type 2 marks the end of a conditional block (started by Code Type 1 or Code Type 8).
|
||||
|
||||
#### Encoding
|
||||
|
||||
`20000000`
|
||||
|
||||
---
|
||||
|
||||
### Code Type 3: Start/End Loop
|
||||
|
||||
Code type 3 allows for iterating in a loop a fixed number of times.
|
||||
|
||||
#### Start Loop Encoding
|
||||
|
||||
`300R0000 VVVVVVVV`
|
||||
|
||||
+ R: Register to use as loop counter.
|
||||
+ V: Number of iterations to loop.
|
||||
|
||||
#### End Loop Encoding
|
||||
|
||||
`310R0000`
|
||||
|
||||
+ R: Register to use as loop counter.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 4: Load Register with Static Value
|
||||
|
||||
Code type 4 allows setting a register to a constant value.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`400R0000 VVVVVVVV VVVVVVVV`
|
||||
|
||||
+ R: Register to use.
|
||||
+ V: Value to load.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 5: Load Register with Memory Value
|
||||
|
||||
Code type 5 allows loading a value from memory into a register, either using a fixed address or by dereferencing the destination register.
|
||||
|
||||
#### Load From Fixed Address Encoding
|
||||
|
||||
`5TMR00AA AAAAAAAA`
|
||||
|
||||
+ T: width of memory read (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ R: Register to load value into.
|
||||
+ A: Immediate offset to use from memory region base.
|
||||
|
||||
#### Load from Register Address Encoding
|
||||
|
||||
`5TMR10AA AAAAAAAA`
|
||||
|
||||
+ T: width of memory read (1, 2, 4, or 8 bytes)
|
||||
+ M: memory region to write to (0 = Main NSO, 1 = Heap)
|
||||
+ R: Register to load value into.
|
||||
+ A: Immediate offset to use from register R.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 6: Store Static Value to Register Memory Address
|
||||
|
||||
Code type 6 allows writing a fixed value to a memory address specified by a register.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`6T0RIor0 VVVVVVVV VVVVVVVV`
|
||||
|
||||
+ T: width of memory write (1, 2, 4, or 8 bytes)
|
||||
+ R: Register used as base memory address.
|
||||
+ I: Increment register flag (0 = do not increment R, 1 = increment R by T).
|
||||
+ o: Offset register enable flag (0 = do not add r to address, 1 = add r to address).
|
||||
+ r: Register used as offset when o is 1.
|
||||
+ V: Value to write to memory.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 7: Legacy Arithmetic
|
||||
|
||||
Code type 7 allows performing arithmetic on registers.
|
||||
|
||||
However, it has been deprecated by Code type 9, and is only kept for backwards compatibility.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`7T0RC000 VVVVVVVV`
|
||||
|
||||
+ T: width of arithmetic operation (1, 2, 4, or 8 bytes)
|
||||
+ R: Register to apply arithmetic to.
|
||||
+ C: Arithmetic operation to apply, see below.
|
||||
+ V: Value to use for arithmetic operation.
|
||||
|
||||
#### Arithmetic Types
|
||||
|
||||
+ 0: Addition
|
||||
+ 1: Subtraction
|
||||
+ 2: Multiplication
|
||||
+ 3: Left Shift
|
||||
+ 4: Right Shift
|
||||
|
||||
---
|
||||
|
||||
### Code Type 8: Begin Keypress Conditional Block
|
||||
|
||||
Code type 8 enters or skips a conditional block based on whether a key combination is pressed.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`8kkkkkkk`
|
||||
|
||||
+ k: Keypad mask to check against, see below.
|
||||
|
||||
Note that for multiple button combinations, the bitmasks should be ORd together.
|
||||
|
||||
#### Keypad Values
|
||||
|
||||
Note: This is the direct output of `hidKeysDown()`.
|
||||
|
||||
+ 0000001: A
|
||||
+ 0000002: B
|
||||
+ 0000004: X
|
||||
+ 0000008: Y
|
||||
+ 0000010: Left Stick Pressed
|
||||
+ 0000020: Right Stick Pressed
|
||||
+ 0000040: L
|
||||
+ 0000080: R
|
||||
+ 0000100: ZL
|
||||
+ 0000200: ZR
|
||||
+ 0000400: Plus
|
||||
+ 0000800: Minus
|
||||
+ 0001000: Left
|
||||
+ 0002000: Up
|
||||
+ 0004000: Right
|
||||
+ 0008000: Down
|
||||
+ 0010000: Left Stick Left
|
||||
+ 0020000: Left Stick Up
|
||||
+ 0040000: Left Stick Right
|
||||
+ 0080000: Left Stick Down
|
||||
+ 0100000: Right Stick Left
|
||||
+ 0200000: Right Stick Up
|
||||
+ 0400000: Right Stick Right
|
||||
+ 0800000: Right Stick Down
|
||||
+ 1000000: SL
|
||||
+ 2000000: SR
|
||||
|
||||
---
|
||||
|
||||
### Code Type 9: Perform Arithmetic
|
||||
|
||||
Code type 9 allows performing arithmetic on registers.
|
||||
|
||||
#### Register Arithmetic Encoding
|
||||
|
||||
`9TCRS0s0`
|
||||
|
||||
+ T: width of arithmetic operation (1, 2, 4, or 8 bytes)
|
||||
+ C: Arithmetic operation to apply, see below.
|
||||
+ R: Register to store result in.
|
||||
+ S: Register to use as left-hand operand.
|
||||
+ s: Register to use as right-hand operand.
|
||||
|
||||
#### Immediate Value Arithmetic Encoding
|
||||
|
||||
`9TCRS100 VVVVVVVV (VVVVVVVV)`
|
||||
|
||||
+ T: width of arithmetic operation (1, 2, 4, or 8 bytes)
|
||||
+ C: Arithmetic operation to apply, see below.
|
||||
+ R: Register to store result in.
|
||||
+ S: Register to use as left-hand operand.
|
||||
+ V: Value to use as right-hand operand.
|
||||
|
||||
#### Arithmetic Types
|
||||
|
||||
+ 0: Addition
|
||||
+ 1: Subtraction
|
||||
+ 2: Multiplication
|
||||
+ 3: Left Shift
|
||||
+ 4: Right Shift
|
||||
+ 5: Logical And
|
||||
+ 6: Logical Or
|
||||
+ 7: Logical Not (discards right-hand operand)
|
||||
+ 8: Logical Xor
|
||||
+ 9: None/Move (discards right-hand operand)
|
||||
|
||||
---
|
||||
|
||||
### Code Type 10: Store Register to Memory Address
|
||||
|
||||
Code type 10 allows writing a register to memory.
|
||||
|
||||
#### Encoding
|
||||
|
||||
`ATSRIOra (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.
|
||||
|
||||
#### Offset Types
|
||||
|
||||
+ 0: No Offset
|
||||
+ 1: Use Offset Register
|
||||
+ 2: Use Fixed Offset
|
||||
|
||||
---
|
||||
|
||||
### Code Type 11: Reserved
|
||||
|
||||
Code Type 11 is currently reserved for future use.
|
||||
|
||||
---
|
||||
|
||||
### Code Type 12-15: Extended-Width Instruction
|
||||
|
||||
Code Types 12-15 signal to the VM to treat the upper two nybbles of the first dword as instruction type, instead of just the upper nybble.
|
||||
|
||||
This reserves an additional 64 opcodes for future use.
|
||||
|
||||
---
|
||||
@@ -1,38 +0,0 @@
|
||||
# dmnt
|
||||
|
||||
dmnt is a reimplementation of Nintendo's debug monitor. It provides Atmosphère a rich set of debugging functionality, so that users can easily analyze the behaviors of programs. In addition, Atmosphère implements an extension in dmnt to provide cheat code functionality.
|
||||
|
||||
## Atmosphère Cheat Extension
|
||||
|
||||
In addition to the functionality provided by Nintendo's debug monitor, Atmosphère's dmnt has an extension for providing cheat code functionality. A HIPC Service API is provided for interacting with the cheat code manager, through the service `dmnt:cht`.
|
||||
|
||||
Those looking for more information on the cheat code functionality may wish to read `cheats.md`.
|
||||
|
||||
The SwIPC definition for `dmnt:cht` follows.
|
||||
```
|
||||
interface DmntCheatService is dmnt:cht {
|
||||
[65000] HasCheatProcess() -> bool;
|
||||
[65001] GetCheatProcessEvent() -> KObject;
|
||||
[65002] GetCheatProcessMetadata() -> CheatProcessMetadata;
|
||||
[65003] ForceOpenCheatProcess();
|
||||
|
||||
[65100] GetCheatProcessMappingCount() -> u64;
|
||||
[65101] GetCheatProcessMappings(u64 offset) -> buffer<MemoryInfo, 6>, u64 count;
|
||||
[65102] ReadCheatProcessMemory(u64 address, u64 size) -> buffer<u8, 6> data;
|
||||
[65103] WriteCheatProcessMemory(u64 address, u64 size, buffer<u8, 5> data);
|
||||
[65104] QueryCheatProcessMemory(u64 address) -> MemoryInfo;
|
||||
|
||||
[65200] GetCheatCount() -> u64;
|
||||
[65201] GetCheats(u64 offset) -> buffer<CheatEntry, 6>, u64 count;
|
||||
[65202] GetCheatById(u32 cheat_id) -> buffer<CheatEntry, 6> cheat;
|
||||
[65203] ToggleCheat(u32 cheat_id);
|
||||
[65204] AddCheat(buffer<CheatDefinition, 5> cheat, bool enabled) -> u32 cheat_id;
|
||||
[65203] RemoveCheat(u32 cheat_id);
|
||||
|
||||
[65300] GetFrozenAddressCount() -> u64;
|
||||
[65301] GetFrozenAddresses(u64 offset) -> buffer<FrozenAddressEntry, 6>, u64 count;
|
||||
[65302] GetFrozenAddress(u64 address) -> FrozenAddressEntry;
|
||||
[65303] EnableFrozenAddress(u64 address, u64 width) -> u64 value;
|
||||
[65304] DisableFrozenAddress(u64 address);
|
||||
}
|
||||
```
|
||||
@@ -316,7 +316,7 @@ int nxfs_mount_all(void) {
|
||||
model = g_mmc_devpart_template;
|
||||
model.device_struct = &g_emmc_user_mmcpart;
|
||||
model.start_sector = 0;
|
||||
model.num_sectors = (256ull << 30) / model.sector_size;
|
||||
model.num_sectors = (32ull << 30) / model.sector_size;
|
||||
|
||||
rc = rawdev_mount_device("rawnand", &model, false);
|
||||
|
||||
|
||||
@@ -85,20 +85,11 @@ _metadata:
|
||||
#undef TO_WORD_
|
||||
#undef TO_WORD
|
||||
|
||||
#define CONTENT_TYPE_FSP 0
|
||||
#define CONTENT_TYPE_EXO 1
|
||||
#define CONTENT_TYPE_WBT 2
|
||||
#define CONTENT_TYPE_RBT 3
|
||||
#define CONTENT_TYPE_SP1 4
|
||||
#define CONTENT_TYPE_SP2 5
|
||||
#define CONTENT_TYPE_KIP 6
|
||||
#define CONTENT_TYPE_BMP 7
|
||||
|
||||
_content_headers:
|
||||
/* ams_mitm content header */
|
||||
.word __ams_mitm_kip_start__
|
||||
.word __ams_mitm_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "ams_mitm"
|
||||
.align 5
|
||||
@@ -106,7 +97,7 @@ _content_headers:
|
||||
/* boot_100 content header */
|
||||
.word __boot_100_kip_start__
|
||||
.word __boot_100_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "boot_100"
|
||||
.align 5
|
||||
@@ -114,7 +105,7 @@ _content_headers:
|
||||
/* boot_200 content header */
|
||||
.word __boot_200_kip_start__
|
||||
.word __boot_200_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "boot_200"
|
||||
.align 5
|
||||
@@ -122,7 +113,7 @@ _content_headers:
|
||||
/* exosphere content header */
|
||||
.word __exosphere_bin_start__
|
||||
.word __exosphere_bin_size__
|
||||
.word CONTENT_TYPE_EXO
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "exosphere"
|
||||
.align 5
|
||||
@@ -130,7 +121,7 @@ _content_headers:
|
||||
/* fusee_primary content header */
|
||||
.word __fusee_primary_bin_start__
|
||||
.word __fusee_primary_bin_size__
|
||||
.word CONTENT_TYPE_FSP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "fusee_primary"
|
||||
.align 5
|
||||
@@ -138,7 +129,7 @@ _content_headers:
|
||||
/* loader content header */
|
||||
.word __loader_kip_start__
|
||||
.word __loader_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "loader"
|
||||
.align 5
|
||||
@@ -146,7 +137,7 @@ _content_headers:
|
||||
/* lp0fw content header */
|
||||
.word __lp0fw_bin_start__
|
||||
.word __lp0fw_bin_size__
|
||||
.word CONTENT_TYPE_WBT
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "lp0fw"
|
||||
.align 5
|
||||
@@ -154,7 +145,7 @@ _content_headers:
|
||||
/* pm content header */
|
||||
.word __pm_kip_start__
|
||||
.word __pm_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "pm"
|
||||
.align 5
|
||||
@@ -162,7 +153,7 @@ _content_headers:
|
||||
/* rebootstub content header */
|
||||
.word __rebootstub_bin_start__
|
||||
.word __rebootstub_bin_size__
|
||||
.word CONTENT_TYPE_RBT
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "rebootstub"
|
||||
.align 5
|
||||
@@ -170,7 +161,7 @@ _content_headers:
|
||||
/* sept_primary content header */
|
||||
.word __sept_primary_bin_start__
|
||||
.word __sept_primary_bin_size__
|
||||
.word CONTENT_TYPE_SP1
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "sept_primary"
|
||||
.align 5
|
||||
@@ -178,7 +169,7 @@ _content_headers:
|
||||
/* sept_secondary content header */
|
||||
.word __sept_secondary_enc_start__
|
||||
.word __sept_secondary_enc_size__
|
||||
.word CONTENT_TYPE_SP2
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "sept_secondary"
|
||||
.align 5
|
||||
@@ -186,7 +177,7 @@ _content_headers:
|
||||
/* sm content header */
|
||||
.word __sm_kip_start__
|
||||
.word __sm_kip_size__
|
||||
.word CONTENT_TYPE_KIP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "sm"
|
||||
.align 5
|
||||
@@ -194,7 +185,7 @@ _content_headers:
|
||||
/* splash_screen content header */
|
||||
.word __splash_screen_bmp_start__
|
||||
.word __splash_screen_bmp_size__
|
||||
.word CONTENT_TYPE_BMP
|
||||
.word 0xCCCCCCCC
|
||||
.word 0xCCCCCCCC
|
||||
.asciz "splash_screen"
|
||||
.align 5
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
MODULES := loader pm sm boot ams_mitm eclct.stub creport fatal dmnt
|
||||
MODULES := loader pm sm boot ams_mitm eclct.stub creport fatal dmnt tma
|
||||
|
||||
SUBFOLDERS := libstratosphere $(MODULES)
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"sfdnsres",
|
||||
"bsdcfg",
|
||||
"set",
|
||||
"set:sys",
|
||||
"fsp-srv",
|
||||
"fatal:u",
|
||||
"hid"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "pm_shim.h"
|
||||
|
||||
static HosMutex g_cheat_lock;
|
||||
static HosThread g_detect_thread, g_vm_thread, g_debug_events_thread;
|
||||
static HosThread g_detect_thread, g_vm_thread;
|
||||
|
||||
static IEvent *g_cheat_process_event;
|
||||
static DmntCheatVm *g_cheat_vm;
|
||||
@@ -30,53 +30,12 @@ static DmntCheatVm *g_cheat_vm;
|
||||
static CheatProcessMetadata g_cheat_process_metadata = {0};
|
||||
static Handle g_cheat_process_debug_hnd = 0;
|
||||
|
||||
/* Should we enable cheats by default? */
|
||||
static bool g_enable_cheats_by_default = true;
|
||||
|
||||
/* For debug event thread management. */
|
||||
static HosMutex g_debug_event_thread_lock;
|
||||
static bool g_has_debug_events_thread = false;
|
||||
|
||||
/* To save some copying. */
|
||||
static bool g_needs_reload_vm_program = false;
|
||||
|
||||
/* Global cheat entry storage. */
|
||||
static CheatEntry g_cheat_entries[DmntCheatManager::MaxCheatCount];
|
||||
|
||||
/* Global frozen address storage. */
|
||||
static std::map<u64, FrozenAddressValue> g_frozen_addresses_map;
|
||||
|
||||
void DmntCheatManager::StartDebugEventsThread() {
|
||||
std::scoped_lock<HosMutex> lk(g_debug_event_thread_lock);
|
||||
|
||||
/* Spawn the debug events thread. */
|
||||
if (!g_has_debug_events_thread) {
|
||||
Result rc;
|
||||
|
||||
if (R_FAILED((rc = g_debug_events_thread.Initialize(&DmntCheatManager::DebugEventsThread, nullptr, 0x4000, 48)))) {
|
||||
return fatalSimple(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED((rc = g_debug_events_thread.Start()))) {
|
||||
return fatalSimple(rc);
|
||||
}
|
||||
|
||||
g_has_debug_events_thread = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DmntCheatManager::WaitDebugEventsThread() {
|
||||
std::scoped_lock<HosMutex> lk(g_debug_event_thread_lock);
|
||||
|
||||
/* Wait for the thread to exit. */
|
||||
if (g_has_debug_events_thread) {
|
||||
g_debug_events_thread.CancelSynchronization();
|
||||
g_debug_events_thread.Join();
|
||||
|
||||
g_has_debug_events_thread = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DmntCheatManager::CloseActiveCheatProcess() {
|
||||
if (g_cheat_process_debug_hnd != 0) {
|
||||
/* Close process resources. */
|
||||
@@ -110,7 +69,7 @@ bool DmntCheatManager::HasActiveCheatProcess() {
|
||||
if (has_cheat_process) {
|
||||
has_cheat_process &= tmp == g_cheat_process_metadata.process_id;
|
||||
}
|
||||
|
||||
|
||||
if (!has_cheat_process) {
|
||||
CloseActiveCheatProcess();
|
||||
}
|
||||
@@ -125,7 +84,7 @@ void DmntCheatManager::ContinueCheatProcess() {
|
||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)debug_event_buf, g_cheat_process_debug_hnd))) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
|
||||
/* Continue the process. */
|
||||
if (kernelAbove300()) {
|
||||
svcContinueDebugEvent(g_cheat_process_debug_hnd, 5, nullptr, 0);
|
||||
@@ -145,26 +104,7 @@ Result DmntCheatManager::ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_da
|
||||
|
||||
Result DmntCheatManager::WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size) {
|
||||
if (HasActiveCheatProcess()) {
|
||||
Result rc = svcWriteDebugProcessMemory(g_cheat_process_debug_hnd, data, proc_addr, size);
|
||||
|
||||
/* We might have a frozen address. Update it if we do! */
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
for (auto & [address, value] : g_frozen_addresses_map) {
|
||||
/* Map is in order, so break here. */
|
||||
if (address >= proc_addr + size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if we need to write. */
|
||||
if (proc_addr <= address) {
|
||||
const size_t offset = (address - proc_addr);
|
||||
const size_t size_to_copy = size - offset;
|
||||
memcpy(&value.value, (void *)((uintptr_t)data + offset), size_to_copy < sizeof(value.value) ? size_to_copy : sizeof(value.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
return svcWriteDebugProcessMemory(g_cheat_process_debug_hnd, data, proc_addr, size);
|
||||
}
|
||||
|
||||
return ResultDmntCheatNotAttached;
|
||||
@@ -263,9 +203,6 @@ void DmntCheatManager::ResetCheatEntry(size_t i) {
|
||||
g_cheat_entries[i].enabled = false;
|
||||
g_cheat_entries[i].cheat_id = i;
|
||||
g_cheat_entries[i].definition = {0};
|
||||
|
||||
/* Trigger a VM reload. */
|
||||
g_needs_reload_vm_program = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,9 +234,6 @@ CheatEntry *DmntCheatManager::GetCheatEntryById(size_t i) {
|
||||
bool DmntCheatManager::ParseCheats(const char *s, size_t len) {
|
||||
size_t i = 0;
|
||||
CheatEntry *cur_entry = NULL;
|
||||
|
||||
/* Trigger a VM reload. */
|
||||
g_needs_reload_vm_program = true;
|
||||
|
||||
while (i < len) {
|
||||
if (isspace(s[i])) {
|
||||
@@ -386,15 +320,10 @@ bool DmntCheatManager::ParseCheats(const char *s, size_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Master cheat can't be disabled. */
|
||||
if (g_cheat_entries[0].definition.num_opcodes > 0) {
|
||||
g_cheat_entries[0].enabled = true;
|
||||
}
|
||||
|
||||
/* Enable all entries we parsed. */
|
||||
for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) {
|
||||
for (size_t i = 0; i < DmntCheatManager::MaxCheatCount; i++) {
|
||||
if (g_cheat_entries[i].definition.num_opcodes > 0) {
|
||||
g_cheat_entries[i].enabled = g_enable_cheats_by_default;
|
||||
g_cheat_entries[i].enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,14 +438,7 @@ Result DmntCheatManager::ToggleCheat(u32 cheat_id) {
|
||||
return ResultDmntCheatUnknownChtId;
|
||||
}
|
||||
|
||||
if (cheat_id == 0) {
|
||||
return ResultDmntCheatCannotDisableMasterCheat;
|
||||
}
|
||||
|
||||
entry->enabled = !entry->enabled;
|
||||
|
||||
/* Trigger a VM reload. */
|
||||
g_needs_reload_vm_program = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -538,9 +460,6 @@ Result DmntCheatManager::AddCheat(u32 *out_id, CheatDefinition *def, bool enable
|
||||
|
||||
new_entry->enabled = enabled;
|
||||
new_entry->definition = *def;
|
||||
|
||||
/* Trigger a VM reload. */
|
||||
g_needs_reload_vm_program = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -556,9 +475,6 @@ Result DmntCheatManager::RemoveCheat(u32 cheat_id) {
|
||||
}
|
||||
|
||||
ResetCheatEntry(cheat_id);
|
||||
|
||||
/* Trigger a VM reload. */
|
||||
g_needs_reload_vm_program = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -697,11 +613,6 @@ Result DmntCheatManager::ForceOpenCheatProcess() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Close the current application, if it's open. */
|
||||
CloseActiveCheatProcess();
|
||||
/* Wait to not have debug events thread. */
|
||||
WaitDebugEventsThread();
|
||||
|
||||
/* Get the current application process ID. */
|
||||
if (R_FAILED((rc = pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)))) {
|
||||
return rc;
|
||||
@@ -762,10 +673,10 @@ Result DmntCheatManager::ForceOpenCheatProcess() {
|
||||
if (R_FAILED((rc = svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Start debug events thread. */
|
||||
StartDebugEventsThread();
|
||||
|
||||
/* Continue debug events, etc. */
|
||||
ContinueCheatProcess();
|
||||
|
||||
/* Signal to our fans. */
|
||||
g_cheat_process_event->Signal();
|
||||
|
||||
@@ -779,9 +690,6 @@ void DmntCheatManager::OnNewApplicationLaunch() {
|
||||
/* Close the current application, if it's open. */
|
||||
CloseActiveCheatProcess();
|
||||
|
||||
/* Wait to not have debug events thread. */
|
||||
WaitDebugEventsThread();
|
||||
|
||||
/* Get the new application's process ID. */
|
||||
if (R_FAILED((rc = pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)))) {
|
||||
fatalSimple(rc);
|
||||
@@ -851,10 +759,10 @@ void DmntCheatManager::OnNewApplicationLaunch() {
|
||||
|
||||
/* Start the process. */
|
||||
StartDebugProcess(g_cheat_process_metadata.process_id);
|
||||
|
||||
/* Start debug events thread. */
|
||||
StartDebugEventsThread();
|
||||
|
||||
|
||||
/* Continue debug events, etc. */
|
||||
ContinueCheatProcess();
|
||||
|
||||
/* Signal to our fans. */
|
||||
g_cheat_process_event->Signal();
|
||||
}
|
||||
@@ -883,12 +791,11 @@ void DmntCheatManager::VmThread(void *arg) {
|
||||
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||
|
||||
if (HasActiveCheatProcess()) {
|
||||
/* Handle any pending debug events. */
|
||||
ContinueCheatProcess();
|
||||
|
||||
/* Execute VM. */
|
||||
if (!g_needs_reload_vm_program || (g_cheat_vm->LoadProgram(g_cheat_entries, DmntCheatManager::MaxCheatCount))) {
|
||||
/* Program: reloaded. */
|
||||
g_needs_reload_vm_program = false;
|
||||
|
||||
/* Execute program if it's present. */
|
||||
if (g_cheat_vm->LoadProgram(g_cheat_entries, DmntCheatManager::MaxCheatCount)) {
|
||||
if (g_cheat_vm->GetProgramSize() != 0) {
|
||||
g_cheat_vm->Execute(&g_cheat_process_metadata);
|
||||
}
|
||||
@@ -900,22 +807,7 @@ void DmntCheatManager::VmThread(void *arg) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr u64 ONE_SECOND = 1000000000ul;
|
||||
constexpr u64 NUM_TIMES = 12;
|
||||
constexpr u64 DELAY = ONE_SECOND / NUM_TIMES;
|
||||
svcSleepThread(DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void DmntCheatManager::DebugEventsThread(void *arg) {
|
||||
while (R_SUCCEEDED(svcWaitSynchronizationSingle(g_cheat_process_debug_hnd, U64_MAX))) {
|
||||
std::scoped_lock<HosMutex> lk(g_cheat_lock);
|
||||
|
||||
/* Handle any pending debug events. */
|
||||
if (HasActiveCheatProcess()) {
|
||||
ContinueCheatProcess();
|
||||
}
|
||||
svcSleepThread(0x5000000ul);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,20 +839,11 @@ void DmntCheatManager::InitializeCheatManager() {
|
||||
/* Create cheat vm. */
|
||||
g_cheat_vm = new DmntCheatVm();
|
||||
|
||||
/* Learn whether we should enable cheats by default. */
|
||||
{
|
||||
u8 en;
|
||||
if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_cheats_enabled_by_default", &en, sizeof(en)))) {
|
||||
g_enable_cheats_by_default = (en != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Spawn application detection thread, spawn cheat vm thread. */
|
||||
if (R_FAILED(g_detect_thread.Initialize(&DmntCheatManager::DetectThread, nullptr, 0x4000, 39))) {
|
||||
if (R_FAILED(g_detect_thread.Initialize(&DmntCheatManager::DetectThread, nullptr, 0x4000, 28))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (R_FAILED(g_vm_thread.Initialize(&DmntCheatManager::VmThread, nullptr, 0x4000, 48))) {
|
||||
if (R_FAILED(g_vm_thread.Initialize(&DmntCheatManager::VmThread, nullptr, 0x4000, 28))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ class DmntCheatManager {
|
||||
static void OnNewApplicationLaunch();
|
||||
static void DetectThread(void *arg);
|
||||
static void VmThread(void *arg);
|
||||
static void DebugEventsThread(void *arg);
|
||||
|
||||
static void StartDebugEventsThread();
|
||||
static void WaitDebugEventsThread();
|
||||
|
||||
static bool HasActiveCheatProcess();
|
||||
static void CloseActiveCheatProcess();
|
||||
|
||||
@@ -441,7 +441,6 @@ bool DmntCheatVm::LoadProgram(const CheatEntry *cheats, size_t num_cheats) {
|
||||
if (cheats[i].enabled) {
|
||||
/* Bounds check. */
|
||||
if (cheats[i].definition.num_opcodes + this->num_opcodes > MaximumProgramOpcodeCount) {
|
||||
this->num_opcodes = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,11 +99,6 @@ void __appInit(void) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
rc = setsysInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
rc = hidInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
@@ -127,7 +122,6 @@ void __appExit(void) {
|
||||
fsdevUnmountAll();
|
||||
fsExit();
|
||||
hidExit();
|
||||
setsysExit();
|
||||
setExit();
|
||||
lrExit();
|
||||
nsdevExit();
|
||||
|
||||
@@ -23,13 +23,12 @@ static constexpr u32 Module_Dmnt = 13;
|
||||
static constexpr Result ResultDmntUnknown = MAKERESULT(Module_Dmnt, 1);
|
||||
static constexpr Result ResultDmntDebuggingDisabled = MAKERESULT(Module_Dmnt, 2);
|
||||
|
||||
static constexpr Result ResultDmntCheatNotAttached = MAKERESULT(Module_Dmnt, 6500);
|
||||
static constexpr Result ResultDmntCheatNullBuffer = MAKERESULT(Module_Dmnt, 6501);
|
||||
static constexpr Result ResultDmntCheatInvalidBuffer = MAKERESULT(Module_Dmnt, 6502);
|
||||
static constexpr Result ResultDmntCheatUnknownChtId = MAKERESULT(Module_Dmnt, 6503);
|
||||
static constexpr Result ResultDmntCheatOutOfCheats = MAKERESULT(Module_Dmnt, 6504);
|
||||
static constexpr Result ResultDmntCheatInvalidCheat = MAKERESULT(Module_Dmnt, 6505);
|
||||
static constexpr Result ResultDmntCheatCannotDisableMasterCheat = MAKERESULT(Module_Dmnt, 6505);
|
||||
static constexpr Result ResultDmntCheatNotAttached = MAKERESULT(Module_Dmnt, 6500);
|
||||
static constexpr Result ResultDmntCheatNullBuffer = MAKERESULT(Module_Dmnt, 6501);
|
||||
static constexpr Result ResultDmntCheatInvalidBuffer = MAKERESULT(Module_Dmnt, 6502);
|
||||
static constexpr Result ResultDmntCheatUnknownChtId = MAKERESULT(Module_Dmnt, 6503);
|
||||
static constexpr Result ResultDmntCheatOutOfCheats = MAKERESULT(Module_Dmnt, 6504);
|
||||
static constexpr Result ResultDmntCheatInvalidCheat = MAKERESULT(Module_Dmnt, 6505);
|
||||
|
||||
static constexpr Result ResultDmntCheatInvalidFreezeWidth = MAKERESULT(Module_Dmnt, 6600);
|
||||
static constexpr Result ResultDmntCheatAddressAlreadyFrozen = MAKERESULT(Module_Dmnt, 6601);
|
||||
|
||||
@@ -117,12 +117,8 @@ Result ShowFatalTask::PrepareScreenForDrawing() {
|
||||
if (R_FAILED((rc = viGetDisplayLogicalResolution(&this->display, &display_width, &display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* viSetDisplayMagnification was added in 3.0.0. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
||||
if (R_FAILED((rc = viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
if (R_FAILED((rc = viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height)))) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Create layer to draw to. */
|
||||
|
||||
Submodule stratosphere/libstratosphere updated: 49d2188f6f...fa37b70b0e
159
stratosphere/tma/Makefile
Normal file
159
stratosphere/tma/Makefile
Normal file
@@ -0,0 +1,159 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source source/test source/settings source/target_io
|
||||
DATA := data
|
||||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
||||
DEFINES := -DDISABLE_IPC
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lstratosphere -lnx
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES := $(addsuffix .o,$(BINFILES)) \
|
||||
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
|
||||
|
||||
ifeq ($(strip $(CONFIG_JSON)),)
|
||||
jsons := $(wildcard *.json)
|
||||
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||
else
|
||||
ifneq (,$(findstring config.json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/config.json
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(BUILD)
|
||||
|
||||
$(BUILD):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
.PHONY: all
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
all : $(OUTPUT).nsp
|
||||
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
$(OUTPUT).nsp : $(OUTPUT).nso
|
||||
else
|
||||
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||
endif
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
||||
35
stratosphere/tma/client/Main.py
Normal file
35
stratosphere/tma/client/Main.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (c) 2018 Atmosphere-NX
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms and conditions of the GNU General Public License,
|
||||
# version 2, as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
from UsbConnection import UsbConnection
|
||||
import sys, time
|
||||
from Packet import Packet
|
||||
import ServiceId
|
||||
|
||||
def main(argc, argv):
|
||||
with UsbConnection(None) as c:
|
||||
print 'Waiting for connection...'
|
||||
c.wait_connected()
|
||||
print 'Connected!'
|
||||
print 'Reading atmosphere/BCT.ini...'
|
||||
c.intf.send_packet(Packet().set_service(ServiceId.TARGETIO_SERVICE).set_task(0x01000000).set_cmd(2).write_str('atmosphere/BCT.ini').write_u64(0x109).write_u64(0))
|
||||
resp = c.intf.read_packet()
|
||||
res_packet = c.intf.read_packet()
|
||||
read_res, size_read = resp.read_u32(), resp.read_u32()
|
||||
print 'Final Result: 0x%x' % res_packet.read_u32()
|
||||
print 'Size Read: 0x%x' % size_read
|
||||
print 'Data:\n%s' % resp.body[resp.offset:]
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(len(sys.argv), sys.argv))
|
||||
120
stratosphere/tma/client/Packet.py
Normal file
120
stratosphere/tma/client/Packet.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# Copyright (c) 2018 Atmosphere-NX
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms and conditions of the GNU General Public License,
|
||||
# version 2, as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import zlib
|
||||
import ServiceId
|
||||
from struct import unpack as up, pack as pk
|
||||
|
||||
HEADER_SIZE = 0x28
|
||||
|
||||
def crc32(s):
|
||||
return zlib.crc32(s) & 0xFFFFFFFF
|
||||
|
||||
class Packet():
|
||||
def __init__(self):
|
||||
self.service = 0
|
||||
self.task = 0
|
||||
self.cmd = 0
|
||||
self.continuation = 0
|
||||
self.version = 0
|
||||
self.body_len = 0
|
||||
self.body = ''
|
||||
self.offset = 0
|
||||
def load_header(self, header):
|
||||
assert len(header) == HEADER_SIZE
|
||||
self.service, self.task, self.cmd, self.continuation, self.version, self.body_len, \
|
||||
_, self.body_chk, self.hdr_chk = up('<IIHBBI16sII', header)
|
||||
if crc32(header[:-4]) != self.hdr_chk:
|
||||
raise ValueError('Invalid header checksum in received packet!')
|
||||
def load_body(self, body):
|
||||
assert len(body) == self.body_len
|
||||
if crc32(body) != self.body_chk:
|
||||
raise ValueError('Invalid body checksum in received packet!')
|
||||
self.body = body
|
||||
def get_data(self):
|
||||
assert len(self.body) == self.body_len and self.body_len <= 0xE000
|
||||
self.body_chk = crc32(self.body)
|
||||
hdr = pk('<IIHBBIIIIII', self.service, self.task, self.cmd, self.continuation, self.version, self.body_len, 0, 0, 0, 0, self.body_chk)
|
||||
self.hdr_chk = crc32(hdr)
|
||||
hdr += pk('<I', self.hdr_chk)
|
||||
return hdr + self.body
|
||||
def set_service(self, srv):
|
||||
if type(srv) is str:
|
||||
self.service = ServiceId.hash(srv)
|
||||
else:
|
||||
self.service = srv
|
||||
return self
|
||||
def set_task(self, t):
|
||||
self.task = t
|
||||
return self
|
||||
def set_cmd(self, x):
|
||||
self.cmd = x
|
||||
return self
|
||||
def set_continuation(self, c):
|
||||
self.continuation = c
|
||||
return self
|
||||
def set_version(self, v):
|
||||
self.version = v
|
||||
return self
|
||||
def reset_offset(self):
|
||||
self.offset = 0
|
||||
return self
|
||||
def write_str(self, s):
|
||||
if s[-1] != '\x00':
|
||||
s += '\x00'
|
||||
self.body += s
|
||||
self.body_len += len(s)
|
||||
return self
|
||||
def write_u8(self, x):
|
||||
self.body += pk('<B', x & 0xFF)
|
||||
self.body_len += 1
|
||||
return self
|
||||
def write_u16(self, x):
|
||||
self.body += pk('<H', x & 0xFFFF)
|
||||
self.body_len += 2
|
||||
return self
|
||||
def write_u32(self, x):
|
||||
self.body += pk('<I', x & 0xFFFFFFFF)
|
||||
self.body_len += 4
|
||||
return self
|
||||
def write_u64(self, x):
|
||||
self.body += pk('<Q', x & 0xFFFFFFFFFFFFFFFF)
|
||||
self.body_len += 8
|
||||
return self
|
||||
def read_str(self):
|
||||
s = ''
|
||||
while self.body[self.offset] != '\x00' and self.offset < self.body_len:
|
||||
s += self.body[self.offset]
|
||||
self.offset += 1
|
||||
if self.offset < self.body_len and self.body[self.offset] == '\x00':
|
||||
self.offset += 1
|
||||
def read_u8(self):
|
||||
x, = up('<B', self.body[self.offset:self.offset+1])
|
||||
self.offset += 1
|
||||
return x
|
||||
def read_u16(self):
|
||||
x, = up('<H', self.body[self.offset:self.offset+2])
|
||||
self.offset += 2
|
||||
return x
|
||||
def read_u32(self):
|
||||
x, = up('<I', self.body[self.offset:self.offset+4])
|
||||
self.offset += 4
|
||||
return x
|
||||
def read_u64(self):
|
||||
x, = up('<Q', self.body[self.offset:self.offset+8])
|
||||
self.offset += 8
|
||||
return x
|
||||
def read_struct(self, format, sz):
|
||||
x = up(format, self.body[self.offset:self.offset+sz])
|
||||
self.offset += sz
|
||||
return x
|
||||
30
stratosphere/tma/client/ServiceId.py
Normal file
30
stratosphere/tma/client/ServiceId.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2018 Atmosphere-NX
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms and conditions of the GNU General Public License,
|
||||
# version 2, as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
def hash(s):
|
||||
h = ord(s[0]) & 0xFFFFFFFF
|
||||
for c in s:
|
||||
h = ((1000003 * h) ^ ord(c)) & 0xFFFFFFFF
|
||||
h ^= len(s)
|
||||
return h
|
||||
|
||||
USB_QUERY_TARGET = hash("USBQueryTarget")
|
||||
USB_SEND_HOST_INFO = hash("USBSendHostInfo")
|
||||
USB_CONNECT = hash("USBConnect")
|
||||
USB_DISCONNECT = hash("USBDisconnect")
|
||||
|
||||
ATMOSPHERE_TEST_SERVICE = hash("AtmosphereTestService")
|
||||
SETTINGS_SERVICE = hash("SettingsService")
|
||||
TARGETIO_SERVICE = hash("TIOService")
|
||||
|
||||
134
stratosphere/tma/client/UsbConnection.py
Normal file
134
stratosphere/tma/client/UsbConnection.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# Copyright (c) 2018 Atmosphere-NX
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms and conditions of the GNU General Public License,
|
||||
# version 2, as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
from UsbInterface import UsbInterface
|
||||
from threading import Thread, Condition
|
||||
from collections import deque
|
||||
import time
|
||||
import ServiceId
|
||||
from Packet import Packet
|
||||
|
||||
class UsbConnection(UsbInterface):
|
||||
# Auto connect thread func.
|
||||
def auto_connect(connection):
|
||||
while not connection.is_connected():
|
||||
try:
|
||||
connection.connect(UsbInterface())
|
||||
except ValueError as e:
|
||||
continue
|
||||
def recv_thread(connection):
|
||||
while connection.is_connected():
|
||||
try:
|
||||
connection.recv_packet()
|
||||
except Exception as e:
|
||||
print 'An exception occurred:'
|
||||
print 'Type: '+e.__class__.__name__
|
||||
print 'Msg: '+str(e)
|
||||
connection.disconnect()
|
||||
connection.send_packet(None)
|
||||
def send_thread(connection):
|
||||
while connection.is_connected():
|
||||
try:
|
||||
next_packet = connection.get_next_send_packet()
|
||||
if next_packet is not None:
|
||||
connection.intf.send_packet(next_packet)
|
||||
else:
|
||||
connection.disconnect()
|
||||
except Exception as e:
|
||||
print 'An exception occurred:'
|
||||
print 'Type: '+e.__class__.__name__
|
||||
print 'Msg: '+str(e)
|
||||
connection.disconnect()
|
||||
def __init__(self, manager):
|
||||
self.manager = manager
|
||||
self.connected = False
|
||||
self.intf = None
|
||||
self.conn_lock, self.send_lock = Condition(), Condition()
|
||||
self.send_queue = deque()
|
||||
def __enter__(self):
|
||||
self.conn_thrd = Thread(target=UsbConnection.auto_connect, args=(self,))
|
||||
self.conn_thrd.daemon = True
|
||||
self.conn_thrd.start()
|
||||
return self
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.disconnect()
|
||||
time.sleep(1)
|
||||
print 'Closing!'
|
||||
time.sleep(1)
|
||||
def wait_connected(self):
|
||||
self.conn_lock.acquire()
|
||||
if not self.is_connected():
|
||||
self.conn_lock.wait()
|
||||
self.conn_lock.release()
|
||||
def is_connected(self):
|
||||
return self.connected
|
||||
def connect(self, intf):
|
||||
# This indicates we have a connection.
|
||||
self.conn_lock.acquire()
|
||||
assert not self.connected
|
||||
self.intf = intf
|
||||
|
||||
try:
|
||||
# Perform Query + Connection handshake
|
||||
print 'Performing handshake...'
|
||||
self.intf.send_packet(Packet().set_service(ServiceId.USB_QUERY_TARGET))
|
||||
query_resp = self.intf.read_packet()
|
||||
print 'Found Switch, Protocol version 0x%x' % query_resp.read_u32()
|
||||
|
||||
self.intf.send_packet(Packet().set_service(ServiceId.USB_SEND_HOST_INFO).write_u32(0).write_u32(0))
|
||||
|
||||
self.intf.send_packet(Packet().set_service(ServiceId.USB_CONNECT))
|
||||
resp = self.intf.read_packet()
|
||||
|
||||
# Spawn threads
|
||||
self.recv_thrd = Thread(target=UsbConnection.recv_thread, args=(self,))
|
||||
self.send_thrd = Thread(target=UsbConnection.send_thread, args=(self,))
|
||||
self.recv_thrd.daemon = True
|
||||
self.send_thrd.daemon = True
|
||||
self.recv_thrd.start()
|
||||
self.send_thrd.start()
|
||||
self.connected = True
|
||||
finally:
|
||||
# Finish connection.
|
||||
self.conn_lock.notify()
|
||||
self.conn_lock.release()
|
||||
def disconnect(self):
|
||||
self.conn_lock.acquire()
|
||||
if self.connected:
|
||||
self.connected = False
|
||||
self.intf.send_packet(Packet().set_service(ServiceId.USB_DISCONNECT))
|
||||
self.conn_lock.release()
|
||||
def recv_packet(self):
|
||||
packet = self.intf.read_packet()
|
||||
assert type(packet) is Packet
|
||||
dat = packet.read_u64()
|
||||
print('Got Packet: %08x' % dat)
|
||||
def send_packet(self, packet):
|
||||
assert type(packet) is Packet
|
||||
self.send_lock.acquire()
|
||||
if len(self.send_queue) == 0x40:
|
||||
self.send_lock.wait()
|
||||
self.send_queue.append(packet)
|
||||
if len(self.send_queue) == 1:
|
||||
self.send_lock.notify()
|
||||
self.send_lock.release()
|
||||
def get_next_send_packet(self):
|
||||
self.send_lock.acquire()
|
||||
if len(self.send_queue) == 0:
|
||||
self.send_lock.wait()
|
||||
packet = self.send_queue.popleft()
|
||||
if len(self.send_queue) == 0x3F:
|
||||
self.send_lock.notify()
|
||||
self.send_lock.release()
|
||||
return packet
|
||||
|
||||
62
stratosphere/tma/client/UsbInterface.py
Normal file
62
stratosphere/tma/client/UsbInterface.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2018 Atmosphere-NX
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms and conditions of the GNU General Public License,
|
||||
# version 2, as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import usb
|
||||
import Packet
|
||||
|
||||
class UsbInterface():
|
||||
def __init__(self):
|
||||
self.dev = usb.core.find(idVendor=0x057e, idProduct=0x3000)
|
||||
if self.dev is None:
|
||||
raise ValueError('Device not found')
|
||||
|
||||
self.dev.set_configuration()
|
||||
self.cfg = self.dev.get_active_configuration()
|
||||
self.intf = usb.util.find_descriptor(self.cfg, bInterfaceClass=0xff, bInterfaceSubClass=0xff, bInterfaceProtocol=0xfc)
|
||||
assert self.intf is not None
|
||||
|
||||
self.ep_in = usb.util.find_descriptor(
|
||||
self.intf,
|
||||
custom_match = \
|
||||
lambda e: \
|
||||
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
||||
usb.util.ENDPOINT_IN)
|
||||
assert self.ep_in is not None
|
||||
|
||||
self.ep_out = usb.util.find_descriptor(
|
||||
self.intf,
|
||||
custom_match = \
|
||||
lambda e: \
|
||||
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
||||
usb.util.ENDPOINT_OUT)
|
||||
assert self.ep_out is not None
|
||||
def close(self):
|
||||
usb.util.dispose_resources(self.dev)
|
||||
def blocking_read(self, size):
|
||||
return ''.join(chr(x) for x in self.ep_in.read(size, 0xFFFFFFFFFFFFFFFF))
|
||||
def blocking_write(self, data):
|
||||
self.ep_out.write(data, 0xFFFFFFFFFFFFFFFF)
|
||||
def read_packet(self):
|
||||
packet = Packet.Packet()
|
||||
hdr = self.blocking_read(Packet.HEADER_SIZE)
|
||||
packet.load_header(hdr)
|
||||
if packet.body_len:
|
||||
packet.load_body(self.blocking_read(packet.body_len))
|
||||
return packet
|
||||
def send_packet(self, packet):
|
||||
data = packet.get_data()
|
||||
self.blocking_write(data[:Packet.HEADER_SIZE])
|
||||
if (len(data) > Packet.HEADER_SIZE):
|
||||
self.blocking_write(data[Packet.HEADER_SIZE:])
|
||||
|
||||
|
||||
93
stratosphere/tma/source/crc.h
Normal file
93
stratosphere/tma/source/crc.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Code taken from Yazen Ghannam <yazen.ghannam@linaro.org>, licensed GPLv2. */
|
||||
|
||||
#define CRC32X(crc, value) __asm__("crc32x %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32W(crc, value) __asm__("crc32w %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32H(crc, value) __asm__("crc32h %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32B(crc, value) __asm__("crc32b %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
|
||||
|
||||
static inline uint16_t __get_unaligned_le16(const uint8_t *p)
|
||||
{
|
||||
return p[0] | p[1] << 8;
|
||||
}
|
||||
|
||||
static inline uint32_t __get_unaligned_le32(const uint8_t *p)
|
||||
{
|
||||
return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
|
||||
}
|
||||
|
||||
static inline uint64_t __get_unaligned_le64(const uint8_t *p)
|
||||
{
|
||||
return (uint64_t)__get_unaligned_le32(p + 4) << 32 |
|
||||
__get_unaligned_le32(p);
|
||||
}
|
||||
|
||||
static inline uint16_t get_unaligned_le16(const void *p)
|
||||
{
|
||||
return __get_unaligned_le16((const uint8_t *)p);
|
||||
}
|
||||
|
||||
static inline uint32_t get_unaligned_le32(const void *p)
|
||||
{
|
||||
return __get_unaligned_le32((const uint8_t *)p);
|
||||
}
|
||||
|
||||
static inline uint64_t get_unaligned_le64(const void *p)
|
||||
{
|
||||
return __get_unaligned_le64((const uint8_t *)p);
|
||||
}
|
||||
|
||||
|
||||
static u32 crc32_arm64_le_hw(const u8 *p, unsigned int len) {
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
|
||||
s64 length = len;
|
||||
|
||||
while ((length -= sizeof(u64)) >= 0) {
|
||||
CRC32X(crc, get_unaligned_le64(p));
|
||||
p += sizeof(u64);
|
||||
}
|
||||
|
||||
/* The following is more efficient than the straight loop */
|
||||
if (length & sizeof(u32)) {
|
||||
CRC32W(crc, get_unaligned_le32(p));
|
||||
p += sizeof(u32);
|
||||
}
|
||||
if (length & sizeof(u16)) {
|
||||
CRC32H(crc, get_unaligned_le16(p));
|
||||
p += sizeof(u16);
|
||||
}
|
||||
if (length & sizeof(u8))
|
||||
CRC32B(crc, *p);
|
||||
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
286
stratosphere/tma/source/dmnt.c
Normal file
286
stratosphere/tma/source/dmnt.c
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "dmnt.h"
|
||||
|
||||
static Service g_dmntSrv;
|
||||
static u64 g_refCnt;
|
||||
|
||||
Result dmntInitialize(void) {
|
||||
atomicIncrement64(&g_refCnt);
|
||||
|
||||
if (serviceIsActive(&g_dmntSrv))
|
||||
return 0;
|
||||
|
||||
return smGetService(&g_dmntSrv, "dmnt:-");
|
||||
}
|
||||
|
||||
void dmntExit(void) {
|
||||
if (atomicDecrement64(&g_refCnt) == 0)
|
||||
serviceClose(&g_dmntSrv);
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileOpen(DmntFile *out, const char *path, int flags, DmntTIOCreateOption create_option) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, path, FS_MAX_PATH, BufferType_Normal);
|
||||
ipcAddRecvBuffer(&c, out, sizeof(*out), BufferType_Normal);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
int flags;
|
||||
u32 create_option;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 29;
|
||||
raw->flags = flags;
|
||||
raw->create_option = create_option;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileClose(DmntFile *f) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 30;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileRead(DmntFile *f, u64 off, void* buf, size_t len, size_t *out_read) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
|
||||
ipcAddRecvBuffer(&c, buf, len, BufferType_Type1);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 offset;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 31;
|
||||
raw->offset = off;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u32 out_read;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc) && out_read) {
|
||||
*out_read = resp->out_read;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileWrite(DmntFile *f, u64 off, const void* buf, size_t len, size_t *out_written) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
|
||||
ipcAddSendBuffer(&c, buf, len, BufferType_Type1);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 offset;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 32;
|
||||
raw->offset = off;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u32 out_written;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc) && out_written) {
|
||||
*out_written = resp->out_written;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileGetInformation(const char *path, bool *out_is_dir, DmntFileInformation *out_info) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, path, FS_MAX_PATH, BufferType_Normal);
|
||||
ipcAddRecvBuffer(&c, out_info, sizeof(*out_info), BufferType_Normal);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 34;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
int is_dir;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out_is_dir = resp->is_dir != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileGetSize(const char *path, u64 *out_size) {
|
||||
DmntFileInformation info;
|
||||
bool is_dir;
|
||||
Result rc = dmntTargetIOFileGetInformation(path, &is_dir, &info);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (is_dir) {
|
||||
/* TODO: error code? */
|
||||
rc = 0x202;
|
||||
} else {
|
||||
*out_size = info.size;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _dmntTargetIOFileSetSize(const void *arg, size_t arg_size, u64 size) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendBuffer(&c, arg, arg_size, BufferType_Normal);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 size;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 36;
|
||||
raw->size = size;
|
||||
|
||||
Result rc = serviceIpcDispatch(&g_dmntSrv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileSetSize(const char *path, u64 size) {
|
||||
return _dmntTargetIOFileSetSize(path, FS_MAX_PATH, size);
|
||||
}
|
||||
|
||||
Result dmntTargetIOFileSetOpenFileSize(DmntFile *f, u64 size) {
|
||||
/* Atmosphere extension */
|
||||
return _dmntTargetIOFileSetSize(f, sizeof(*f), size);
|
||||
}
|
||||
57
stratosphere/tma/source/dmnt.h
Normal file
57
stratosphere/tma/source/dmnt.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
u64 handle;
|
||||
} DmntFile;
|
||||
|
||||
typedef enum {
|
||||
DmntTIOCreateOption_CreateNew = 1,
|
||||
DmntTIOCreateOption_CreateAlways = 2,
|
||||
DmntTIOCreateOption_OpenExisting = 3,
|
||||
DmntTIOCreateOption_OpenAlways = 4,
|
||||
DmntTIOCreateOption_ResetSize = 5,
|
||||
} DmntTIOCreateOption;
|
||||
|
||||
typedef struct {
|
||||
u64 size;
|
||||
u64 create_time;
|
||||
u64 access_time;
|
||||
u64 modify_time;
|
||||
} DmntFileInformation;
|
||||
|
||||
Result dmntInitialize(void);
|
||||
void dmntExit(void);
|
||||
|
||||
Result dmntTargetIOFileOpen(DmntFile *out, const char *path, int flags, DmntTIOCreateOption create_option);
|
||||
Result dmntTargetIOFileClose(DmntFile *f);
|
||||
Result dmntTargetIOFileRead(DmntFile *f, u64 off, void* buf, size_t len, size_t* out_read);
|
||||
Result dmntTargetIOFileWrite(DmntFile *f, u64 off, const void* buf, size_t len, size_t* out_written);
|
||||
Result dmntTargetIOFileGetInformation(const char *path, bool *out_is_dir, DmntFileInformation *out_info);
|
||||
Result dmntTargetIOFileGetSize(const char *path, u64 *out_size);
|
||||
Result dmntTargetIOFileSetSize(const char *path, u64 size);
|
||||
Result dmntTargetIOFileSetOpenFileSize(DmntFile *f, u64 size); /* Atmosphere extension */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
43
stratosphere/tma/source/settings/settings_service.cpp
Normal file
43
stratosphere/tma/source/settings/settings_service.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "settings_service.hpp"
|
||||
#include "settings_task.hpp"
|
||||
|
||||
TmaTask *SettingsService::NewTask(TmaPacket *packet) {
|
||||
TmaTask *new_task = nullptr;
|
||||
switch (packet->GetCommand()) {
|
||||
case SettingsServiceCmd_GetSetting:
|
||||
{
|
||||
new_task = new GetSettingTask(this->manager);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
new_task = nullptr;
|
||||
break;
|
||||
}
|
||||
if (new_task != nullptr) {
|
||||
new_task->SetServiceId(this->GetServiceId());
|
||||
new_task->SetTaskId(packet->GetTaskId());
|
||||
new_task->OnStart(packet);
|
||||
new_task->SetNeedsPackets(true);
|
||||
}
|
||||
|
||||
return new_task;
|
||||
}
|
||||
34
stratosphere/tma/source/settings/settings_service.hpp
Normal file
34
stratosphere/tma/source/settings/settings_service.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../tma_conn_service_ids.hpp"
|
||||
#include "../tma_service.hpp"
|
||||
|
||||
enum SettingsServiceCmd : u32 {
|
||||
SettingsServiceCmd_GetSetting = 0,
|
||||
};
|
||||
|
||||
class SettingsService : public TmaService {
|
||||
public:
|
||||
SettingsService(TmaServiceManager *m) : TmaService(m, "SettingsService") { }
|
||||
virtual ~SettingsService() { }
|
||||
|
||||
virtual TmaTask *NewTask(TmaPacket *packet) override;
|
||||
};
|
||||
48
stratosphere/tma/source/settings/settings_task.cpp
Normal file
48
stratosphere/tma/source/settings/settings_task.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "settings_task.hpp"
|
||||
|
||||
void GetSettingTask::OnStart(TmaPacket *packet) {
|
||||
size_t length;
|
||||
packet->ReadString(this->name, sizeof(this->name), &length);
|
||||
packet->ReadString(this->item_key, sizeof(this->item_key), &length);
|
||||
|
||||
if (R_SUCCEEDED(setsysGetSettingsItemValueSize(this->name, this->item_key, &this->value_size))) {
|
||||
if (this->value_size <= sizeof(this->value)) {
|
||||
if (R_SUCCEEDED(setsysGetSettingsItemValue(this->name, this->item_key, this->value, this->value_size))) {
|
||||
this->succeeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetSettingTask::OnReceivePacket(TmaPacket *packet) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void GetSettingTask::OnSendPacket(TmaPacket *packet) {
|
||||
packet->Write<u8>((u8)this->succeeded);
|
||||
if (this->succeeded) {
|
||||
packet->Write<u32>((u32)this->value_size);
|
||||
packet->Write(this->value, this->value_size);
|
||||
}
|
||||
|
||||
this->Complete();
|
||||
}
|
||||
38
stratosphere/tma/source/settings/settings_task.hpp
Normal file
38
stratosphere/tma/source/settings/settings_task.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../tma_task.hpp"
|
||||
|
||||
class GetSettingTask : public TmaTask {
|
||||
private:
|
||||
char name[0x40] = {0};
|
||||
char item_key[0x40] = {0};
|
||||
u8 value[0x40] = {0};
|
||||
u64 value_size = 0;
|
||||
bool succeeded = false;
|
||||
|
||||
public:
|
||||
GetSettingTask(TmaServiceManager *m) : TmaTask(m) { }
|
||||
virtual ~GetSettingTask() { }
|
||||
|
||||
virtual void OnStart(TmaPacket *packet) override;
|
||||
virtual void OnReceivePacket(TmaPacket *packet) override;
|
||||
virtual void OnSendPacket(TmaPacket *packet) override;
|
||||
};
|
||||
47
stratosphere/tma/source/target_io/tio_service.cpp
Normal file
47
stratosphere/tma/source/target_io/tio_service.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tio_service.hpp"
|
||||
#include "tio_task.hpp"
|
||||
|
||||
TmaTask *TIOService::NewTask(TmaPacket *packet) {
|
||||
TmaTask *new_task = nullptr;
|
||||
switch (packet->GetCommand()) {
|
||||
case TIOServiceCmd_FileRead:
|
||||
{
|
||||
new_task = new TIOFileReadTask(this->manager);
|
||||
}
|
||||
break;
|
||||
case TIOServiceCmd_FileWrite:
|
||||
{
|
||||
new_task = new TIOFileWriteTask(this->manager);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
new_task = nullptr;
|
||||
break;
|
||||
}
|
||||
if (new_task != nullptr) {
|
||||
new_task->SetServiceId(this->GetServiceId());
|
||||
new_task->SetTaskId(packet->GetTaskId());
|
||||
new_task->OnStart(packet);
|
||||
}
|
||||
|
||||
return new_task;
|
||||
}
|
||||
35
stratosphere/tma/source/target_io/tio_service.hpp
Normal file
35
stratosphere/tma/source/target_io/tio_service.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../tma_conn_service_ids.hpp"
|
||||
#include "../tma_service.hpp"
|
||||
|
||||
enum TIOServiceCmd : u32 {
|
||||
TIOServiceCmd_FileRead = 2,
|
||||
TIOServiceCmd_FileWrite = 3,
|
||||
};
|
||||
|
||||
class TIOService : public TmaService {
|
||||
public:
|
||||
TIOService(TmaServiceManager *m) : TmaService(m, "TIOService") { }
|
||||
virtual ~TIOService() { }
|
||||
|
||||
virtual TmaTask *NewTask(TmaPacket *packet) override;
|
||||
};
|
||||
173
stratosphere/tma/source/target_io/tio_task.cpp
Normal file
173
stratosphere/tma/source/target_io/tio_task.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tio_task.hpp"
|
||||
|
||||
|
||||
void TIOFileReadTask::OnStart(TmaPacket *packet) {
|
||||
char path[FS_MAX_PATH];
|
||||
|
||||
packet->ReadString(path, sizeof(path), nullptr);
|
||||
packet->Read<u64>(this->size_remaining);
|
||||
packet->Read<u64>(this->cur_offset);
|
||||
|
||||
Result rc = 0;
|
||||
if (strlen(path) == 0) {
|
||||
rc = 0x202;
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
u64 file_size;
|
||||
rc = dmntTargetIOFileGetSize(path, &file_size);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (file_size < this->cur_offset + this->size_remaining) {
|
||||
this->size_remaining = file_size - this->cur_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = dmntTargetIOFileOpen(&this->handle, path, FS_OPEN_READ, DmntTIOCreateOption_OpenExisting);
|
||||
if (R_FAILED(rc)) {
|
||||
this->SendResult(rc);
|
||||
return;
|
||||
} else {
|
||||
auto packet = this->AllocateSendPacket();
|
||||
rc = this->ProcessPacket(packet);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
this->manager->SendPacket(packet);
|
||||
if (this->size_remaining) {
|
||||
this->SetNeedsPackets(true);
|
||||
} else {
|
||||
this->SendResult(rc);
|
||||
}
|
||||
} else {
|
||||
this->manager->FreePacket(packet);
|
||||
this->SendResult(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TIOFileReadTask::OnSendPacket(TmaPacket *packet) {
|
||||
Result rc = this->ProcessPacket(packet);
|
||||
|
||||
if (this->size_remaining == 0 || R_FAILED(rc)) {
|
||||
this->SendResult(rc);
|
||||
}
|
||||
}
|
||||
|
||||
void TIOFileReadTask::SendResult(Result rc) {
|
||||
dmntTargetIOFileClose(&this->handle);
|
||||
this->SetNeedsPackets(false);
|
||||
|
||||
auto packet = this->AllocateSendPacket();
|
||||
packet->Write<Result>(rc);
|
||||
this->manager->SendPacket(packet);
|
||||
Complete();
|
||||
}
|
||||
|
||||
Result TIOFileReadTask::ProcessPacket(TmaPacket *packet) {
|
||||
Result rc = 0x196002;
|
||||
|
||||
size_t cur_read = static_cast<u32>((this->size_remaining > MaxDataSize) ? MaxDataSize : this->size_remaining);
|
||||
|
||||
u8 *buf = new u8[cur_read];
|
||||
if (buf != nullptr) {
|
||||
size_t actual_read = 0;
|
||||
rc = dmntTargetIOFileRead(&this->handle, this->cur_offset, buf, cur_read, &actual_read);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
packet->Write<Result>(rc);
|
||||
packet->Write<u32>(actual_read);
|
||||
packet->Write(buf, actual_read);
|
||||
this->cur_offset += actual_read;
|
||||
this->size_remaining -= actual_read;
|
||||
}
|
||||
|
||||
delete buf;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void TIOFileWriteTask::OnStart(TmaPacket *packet) {
|
||||
char path[FS_MAX_PATH];
|
||||
|
||||
packet->ReadString(path, sizeof(path), nullptr);
|
||||
packet->Read<u64>(this->size_remaining);
|
||||
packet->Read<u64>(this->cur_offset);
|
||||
|
||||
Result rc = 0;
|
||||
if (strlen(path) == 0) {
|
||||
rc = 0x202;
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
u64 file_size;
|
||||
rc = dmntTargetIOFileGetSize(path, &file_size);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (file_size < this->cur_offset + this->size_remaining) {
|
||||
this->size_remaining = file_size - this->cur_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = dmntTargetIOFileOpen(&this->handle, path, FS_OPEN_READ, DmntTIOCreateOption_OpenExisting);
|
||||
|
||||
if (R_FAILED(rc)) {
|
||||
this->SendResult(rc);
|
||||
}
|
||||
}
|
||||
|
||||
void TIOFileWriteTask::OnReceivePacket(TmaPacket *packet) {
|
||||
Result rc = this->ProcessPacket(packet);
|
||||
|
||||
if (this->size_remaining == 0 || R_FAILED(rc)) {
|
||||
this->SendResult(rc);
|
||||
}
|
||||
}
|
||||
|
||||
void TIOFileWriteTask::SendResult(Result rc) {
|
||||
dmntTargetIOFileClose(&this->handle);
|
||||
|
||||
auto packet = this->AllocateSendPacket();
|
||||
packet->Write<Result>(rc);
|
||||
this->manager->SendPacket(packet);
|
||||
Complete();
|
||||
}
|
||||
|
||||
Result TIOFileWriteTask::ProcessPacket(TmaPacket *packet) {
|
||||
Result rc = 0x196002;
|
||||
|
||||
/* Note: N does not bounds check this. We do. */
|
||||
u32 cur_write = 0;
|
||||
packet->Read<u32>(cur_write);
|
||||
|
||||
size_t actual_written = 0;
|
||||
if (cur_write < MaxDataSize) {
|
||||
if (cur_write > this->size_remaining) {
|
||||
cur_write = this->size_remaining;
|
||||
}
|
||||
rc = dmntTargetIOFileWrite(&this->handle, this->cur_offset, packet->GetCurrentBodyPtr(), cur_write, &actual_written);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
this->size_remaining -= actual_written;
|
||||
this->cur_offset += actual_written;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
85
stratosphere/tma/source/target_io/tio_task.hpp
Normal file
85
stratosphere/tma/source/target_io/tio_task.hpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../tma_task.hpp"
|
||||
#include "../tma_service_manager.hpp"
|
||||
#include "../dmnt.h"
|
||||
|
||||
class TIOTask : public TmaTask {
|
||||
public:
|
||||
TIOTask(TmaServiceManager *m) : TmaTask(m) { }
|
||||
virtual ~TIOTask() { }
|
||||
|
||||
virtual void SendResult(Result rc) {
|
||||
TmaPacket *packet = this->AllocateSendPacket();
|
||||
packet->Write<Result>(rc);
|
||||
this->manager->SendPacket(packet);
|
||||
this->Complete();
|
||||
}
|
||||
|
||||
virtual void OnStart(TmaPacket *packet) = 0;
|
||||
|
||||
virtual void OnReceivePacket(TmaPacket *packet) override {
|
||||
this->Complete();
|
||||
}
|
||||
|
||||
virtual void OnSendPacket(TmaPacket *packet) override {
|
||||
this->Complete();
|
||||
}
|
||||
};
|
||||
|
||||
class TIOFileReadTask : public TIOTask {
|
||||
private:
|
||||
static constexpr size_t HeaderSize = sizeof(Result) + sizeof(u32);
|
||||
static constexpr size_t MaxDataSize = TmaPacket::MaxBodySize - HeaderSize;
|
||||
private:
|
||||
DmntFile handle = {0};
|
||||
u64 size_remaining = 0;
|
||||
u64 cur_offset = 0;
|
||||
public:
|
||||
TIOFileReadTask(TmaServiceManager *m) : TIOTask(m) { }
|
||||
virtual ~TIOFileReadTask() { }
|
||||
|
||||
virtual void OnStart(TmaPacket *packet) override;
|
||||
virtual void OnSendPacket(TmaPacket *packet) override;
|
||||
virtual void SendResult(Result rc) override;
|
||||
|
||||
Result ProcessPacket(TmaPacket *packet);
|
||||
};
|
||||
|
||||
class TIOFileWriteTask : public TIOTask {
|
||||
private:
|
||||
static constexpr size_t HeaderSize = sizeof(u32);
|
||||
static constexpr size_t MaxDataSize = TmaPacket::MaxBodySize - HeaderSize;
|
||||
private:
|
||||
DmntFile handle = {0};
|
||||
u64 size_remaining = 0;
|
||||
u64 cur_offset = 0;
|
||||
public:
|
||||
TIOFileWriteTask(TmaServiceManager *m) : TIOTask(m) { }
|
||||
virtual ~TIOFileWriteTask() { }
|
||||
|
||||
virtual void OnStart(TmaPacket *packet) override;
|
||||
virtual void OnReceivePacket(TmaPacket *packet) override;
|
||||
virtual void SendResult(Result rc) override;
|
||||
|
||||
Result ProcessPacket(TmaPacket *packet);
|
||||
};
|
||||
|
||||
31
stratosphere/tma/source/test/atmosphere_test_service.cpp
Normal file
31
stratosphere/tma/source/test/atmosphere_test_service.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "atmosphere_test_service.hpp"
|
||||
#include "atmosphere_test_task.hpp"
|
||||
|
||||
TmaTask *AtmosphereTestService::NewTask(TmaPacket *packet) {
|
||||
auto new_task = new AtmosphereTestTask(this->manager);
|
||||
new_task->SetServiceId(this->GetServiceId());
|
||||
new_task->SetTaskId(packet->GetTaskId());
|
||||
new_task->OnStart(packet);
|
||||
new_task->SetNeedsPackets(true);
|
||||
|
||||
return new_task;
|
||||
}
|
||||
30
stratosphere/tma/source/test/atmosphere_test_service.hpp
Normal file
30
stratosphere/tma/source/test/atmosphere_test_service.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../tma_conn_service_ids.hpp"
|
||||
#include "../tma_service.hpp"
|
||||
|
||||
class AtmosphereTestService : public TmaService {
|
||||
public:
|
||||
AtmosphereTestService(TmaServiceManager *m) : TmaService(m, "AtmosphereTestService") { }
|
||||
virtual ~AtmosphereTestService() { }
|
||||
|
||||
virtual TmaTask *NewTask(TmaPacket *packet) override;
|
||||
};
|
||||
36
stratosphere/tma/source/test/atmosphere_test_task.cpp
Normal file
36
stratosphere/tma/source/test/atmosphere_test_task.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "atmosphere_test_task.hpp"
|
||||
|
||||
void AtmosphereTestTask::OnStart(TmaPacket *packet) {
|
||||
packet->Read<u32>(this->arg);
|
||||
}
|
||||
|
||||
void AtmosphereTestTask::OnReceivePacket(TmaPacket *packet) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void AtmosphereTestTask::OnSendPacket(TmaPacket *packet) {
|
||||
for (size_t i = 0; i < this->arg && i < 0x100; i++) {
|
||||
packet->Write<u8>('A');
|
||||
}
|
||||
|
||||
this->Complete();
|
||||
}
|
||||
33
stratosphere/tma/source/test/atmosphere_test_task.hpp
Normal file
33
stratosphere/tma/source/test/atmosphere_test_task.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../tma_task.hpp"
|
||||
|
||||
class AtmosphereTestTask : public TmaTask {
|
||||
private:
|
||||
u32 arg;
|
||||
public:
|
||||
AtmosphereTestTask(TmaServiceManager *m) : TmaTask(m) { }
|
||||
virtual ~AtmosphereTestTask() { }
|
||||
|
||||
virtual void OnStart(TmaPacket *packet) override;
|
||||
virtual void OnReceivePacket(TmaPacket *packet) override;
|
||||
virtual void OnSendPacket(TmaPacket *packet) override;
|
||||
};
|
||||
64
stratosphere/tma/source/tma_conn_connection.cpp
Normal file
64
stratosphere/tma/source/tma_conn_connection.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_conn_connection.hpp"
|
||||
#include "tma_service_manager.hpp"
|
||||
|
||||
/* Packet management. */
|
||||
TmaPacket *TmaConnection::AllocateSendPacket() {
|
||||
return this->service_manager->AllocateSendPacket();
|
||||
}
|
||||
|
||||
TmaPacket *TmaConnection::AllocateRecvPacket() {
|
||||
return this->service_manager->AllocateRecvPacket();
|
||||
}
|
||||
|
||||
void TmaConnection::FreePacket(TmaPacket *packet) {
|
||||
this->service_manager->FreePacket(packet);
|
||||
}
|
||||
|
||||
void TmaConnection::OnReceivePacket(TmaPacket *packet) {
|
||||
this->service_manager->OnReceivePacket(packet);
|
||||
}
|
||||
|
||||
void TmaConnection::OnDisconnected() {
|
||||
if (!this->is_initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (this->service_manager != nullptr) {
|
||||
this->service_manager->OnDisconnect();
|
||||
}
|
||||
|
||||
this->has_woken_up = false;
|
||||
this->OnConnectionEvent(ConnectionEvent::Disconnected);
|
||||
}
|
||||
|
||||
void TmaConnection::OnConnectionEvent(ConnectionEvent event) {
|
||||
if (this->connection_event_callback != nullptr) {
|
||||
this->connection_event_callback(this->connection_event_arg, event);
|
||||
}
|
||||
}
|
||||
|
||||
void TmaConnection::CancelTasks() {
|
||||
this->service_manager->CancelTasks();
|
||||
}
|
||||
|
||||
void TmaConnection::Tick() {
|
||||
this->service_manager->Tick();
|
||||
}
|
||||
90
stratosphere/tma/source/tma_conn_connection.hpp
Normal file
90
stratosphere/tma/source/tma_conn_connection.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_result.hpp"
|
||||
#include "tma_conn_packet.hpp"
|
||||
|
||||
enum class ConnectionEvent : u32 {
|
||||
Connected,
|
||||
Disconnected
|
||||
};
|
||||
|
||||
|
||||
class TmaServiceManager;
|
||||
|
||||
class TmaConnection {
|
||||
protected:
|
||||
HosMutex lock;
|
||||
void (*connection_event_callback)(void *, ConnectionEvent) = nullptr;
|
||||
void *connection_event_arg = nullptr;
|
||||
bool has_woken_up = false;
|
||||
bool is_initialized = false;
|
||||
TmaServiceManager *service_manager = nullptr;
|
||||
protected:
|
||||
void OnReceivePacket(TmaPacket *packet);
|
||||
void OnDisconnected();
|
||||
void OnConnectionEvent(ConnectionEvent event);
|
||||
void CancelTasks();
|
||||
void Tick();
|
||||
public:
|
||||
/* Setup */
|
||||
TmaConnection() { }
|
||||
virtual ~TmaConnection() { }
|
||||
|
||||
void Initialize() {
|
||||
if (this->is_initialized) {
|
||||
std::abort();
|
||||
}
|
||||
this->is_initialized = true;
|
||||
}
|
||||
|
||||
void SetConnectionEventCallback(void (*callback)(void *, ConnectionEvent), void *arg) {
|
||||
this->connection_event_callback = callback;
|
||||
this->connection_event_arg = arg;
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
if (this->is_initialized) {
|
||||
this->StopListening();
|
||||
if (this->IsConnected()) {
|
||||
this->Disconnect();
|
||||
}
|
||||
this->is_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetServiceManager(TmaServiceManager *manager) { this->service_manager = manager; }
|
||||
|
||||
/* Packet management. */
|
||||
TmaPacket *AllocateSendPacket();
|
||||
TmaPacket *AllocateRecvPacket();
|
||||
void FreePacket(TmaPacket *packet);
|
||||
|
||||
/* Sleep management. */
|
||||
bool HasWokenUp() const { return this->has_woken_up; }
|
||||
void SetWokenUp(bool woke) { this->has_woken_up = woke; }
|
||||
|
||||
/* For sub-interfaces to implement, connection management. */
|
||||
virtual void StartListening() { }
|
||||
virtual void StopListening() { }
|
||||
virtual bool IsConnected() = 0;
|
||||
virtual TmaConnResult Disconnect() = 0;
|
||||
virtual TmaConnResult SendPacket(TmaPacket *packet) = 0;
|
||||
};
|
||||
279
stratosphere/tma/source/tma_conn_packet.hpp
Normal file
279
stratosphere/tma/source/tma_conn_packet.hpp
Normal file
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include "tma_conn_result.hpp"
|
||||
#include "tma_conn_service_ids.hpp"
|
||||
#include "crc.h"
|
||||
|
||||
class TmaPacket {
|
||||
public:
|
||||
struct Header {
|
||||
u32 service_id;
|
||||
u32 task_id;
|
||||
u16 command;
|
||||
u8 is_continuation;
|
||||
u8 version;
|
||||
u32 body_len;
|
||||
u32 reserved[4]; /* This is where N's header ends. */
|
||||
u32 body_checksum;
|
||||
u32 header_checksum;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 0x28, "Packet::Header definition!");
|
||||
|
||||
static constexpr u32 MaxBodySize = 0xE000;
|
||||
static constexpr u32 MaxPacketSize = MaxBodySize + sizeof(Header);
|
||||
|
||||
private:
|
||||
std::unique_ptr<u8[]> buffer = std::make_unique<u8[]>(MaxPacketSize);
|
||||
u32 offset = 0;
|
||||
HosMessageQueue *free_queue = nullptr;
|
||||
|
||||
Header *GetHeader() const {
|
||||
return reinterpret_cast<Header *>(buffer.get());
|
||||
}
|
||||
|
||||
u8 *GetBody(u32 ofs) const {
|
||||
return reinterpret_cast<u8 *>(buffer.get() + sizeof(Header) + ofs);
|
||||
}
|
||||
public:
|
||||
TmaPacket() {
|
||||
memset(buffer.get(), 0, MaxPacketSize);
|
||||
}
|
||||
|
||||
/* Implicit ~TmaPacket() */
|
||||
|
||||
/* These allow reading a packet in. */
|
||||
void CopyHeaderFrom(Header *hdr) {
|
||||
*GetHeader() = *hdr;
|
||||
}
|
||||
|
||||
TmaConnResult CopyBodyFrom(void *body, size_t size) {
|
||||
if (size >= MaxBodySize) {
|
||||
return TmaConnResult::PacketOverflow;
|
||||
}
|
||||
|
||||
memcpy(GetBody(0), body, size);
|
||||
|
||||
return TmaConnResult::Success;
|
||||
}
|
||||
|
||||
void CopyHeaderTo(void *out) {
|
||||
memcpy(out, buffer.get(), sizeof(Header));
|
||||
}
|
||||
|
||||
void CopyBodyTo(void *out) const {
|
||||
memcpy(out, buffer.get() + sizeof(Header), GetBodyLength());
|
||||
}
|
||||
|
||||
bool IsHeaderValid() {
|
||||
Header *hdr = GetHeader();
|
||||
return crc32_arm64_le_hw(reinterpret_cast<const u8 *>(hdr), sizeof(*hdr) - sizeof(hdr->header_checksum)) == hdr->header_checksum;
|
||||
}
|
||||
|
||||
bool IsBodyValid() const {
|
||||
const u32 body_len = GetHeader()->body_len;
|
||||
if (body_len == 0) {
|
||||
return GetHeader()->body_checksum == 0;
|
||||
} else {
|
||||
return crc32_arm64_le_hw(GetBody(0), body_len) == GetHeader()->body_checksum;
|
||||
}
|
||||
}
|
||||
|
||||
HosMessageQueue *GetFreeQueue() const {
|
||||
return this->free_queue;
|
||||
}
|
||||
|
||||
void SetFreeQueue(HosMessageQueue *queue) {
|
||||
this->free_queue = queue;
|
||||
}
|
||||
|
||||
void SetChecksums() {
|
||||
Header *hdr = GetHeader();
|
||||
if (hdr->body_len) {
|
||||
hdr->body_checksum = crc32_arm64_le_hw(GetBody(0), hdr->body_len);
|
||||
} else {
|
||||
hdr->body_checksum = 0;
|
||||
}
|
||||
hdr->header_checksum = crc32_arm64_le_hw(reinterpret_cast<const u8 *>(hdr), sizeof(*hdr) - sizeof(hdr->header_checksum));
|
||||
}
|
||||
|
||||
u32 GetBodyLength() const {
|
||||
return GetHeader()->body_len;
|
||||
}
|
||||
|
||||
u32 GetLength() const {
|
||||
return GetBodyLength() + sizeof(Header);
|
||||
}
|
||||
|
||||
u32 GetBodyAvailableLength() const {
|
||||
return MaxPacketSize - this->offset;
|
||||
}
|
||||
|
||||
void SetServiceId(TmaServiceId srv) {
|
||||
GetHeader()->service_id = static_cast<u32>(srv);
|
||||
}
|
||||
|
||||
TmaServiceId GetServiceId() const {
|
||||
return static_cast<TmaServiceId>(GetHeader()->service_id);
|
||||
}
|
||||
|
||||
void SetTaskId(u32 id) {
|
||||
GetHeader()->task_id = id;
|
||||
}
|
||||
|
||||
u32 GetTaskId() const {
|
||||
return GetHeader()->task_id;
|
||||
}
|
||||
|
||||
void SetCommand(u16 cmd) {
|
||||
GetHeader()->command = cmd;
|
||||
}
|
||||
|
||||
u16 GetCommand() const {
|
||||
return GetHeader()->command;
|
||||
}
|
||||
|
||||
void SetContinuation(bool c) {
|
||||
GetHeader()->is_continuation = c ? 1 : 0;
|
||||
}
|
||||
|
||||
bool GetContinuation() const {
|
||||
return GetHeader()->is_continuation == 1;
|
||||
}
|
||||
|
||||
void SetVersion(u8 v) {
|
||||
GetHeader()->version = v;
|
||||
}
|
||||
|
||||
u8 GetVersion() const {
|
||||
return GetHeader()->version;
|
||||
}
|
||||
|
||||
u8 *GetCurrentBodyPtr() {
|
||||
return GetBody(this->offset);
|
||||
}
|
||||
|
||||
void ClearOffset() {
|
||||
this->offset = 0;
|
||||
}
|
||||
|
||||
void SetBodyLength() {
|
||||
GetHeader()->body_len = this->offset;
|
||||
}
|
||||
|
||||
TmaConnResult Write(const void *data, size_t size) {
|
||||
if (size > GetBodyAvailableLength()) {
|
||||
return TmaConnResult::PacketOverflow;
|
||||
}
|
||||
|
||||
memcpy(GetBody(this->offset), data, size);
|
||||
this->offset += size;
|
||||
GetHeader()->body_len = this->offset;
|
||||
|
||||
return TmaConnResult::Success;
|
||||
}
|
||||
|
||||
TmaConnResult Read(void *data, size_t size) {
|
||||
if (size > GetBodyAvailableLength()) {
|
||||
return TmaConnResult::PacketOverflow;
|
||||
}
|
||||
|
||||
memcpy(data, GetBody(this->offset), size);
|
||||
this->offset += size;
|
||||
|
||||
return TmaConnResult::Success;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TmaConnResult Write(const T &t) {
|
||||
return Write(&t, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TmaConnResult Read(T &t) {
|
||||
return Read(&t, sizeof(T));
|
||||
}
|
||||
|
||||
TmaConnResult WriteString(const char *s) {
|
||||
return Write(s, strlen(s) + 1);
|
||||
}
|
||||
|
||||
size_t WriteFormat(const char *format, ...) {
|
||||
va_list va_arg;
|
||||
va_start(va_arg, format);
|
||||
const size_t available = GetBodyAvailableLength();
|
||||
const int written = vsnprintf(reinterpret_cast<char *>(GetBody(this->offset)), available, format, va_arg);
|
||||
|
||||
size_t total_written;
|
||||
if (static_cast<size_t>(written) < available) {
|
||||
this->offset += written;
|
||||
*GetBody(this->offset++) = 0;
|
||||
total_written = written + 1;
|
||||
} else {
|
||||
this->offset += available;
|
||||
total_written = available;
|
||||
}
|
||||
|
||||
GetHeader()->body_len = this->offset;
|
||||
return total_written;
|
||||
}
|
||||
|
||||
TmaConnResult ReadString(char *buf, size_t buf_size, size_t *out_size) {
|
||||
TmaConnResult res = TmaConnResult::Success;
|
||||
|
||||
size_t available = GetBodyAvailableLength();
|
||||
size_t ofs = 0;
|
||||
while (ofs < buf_size) {
|
||||
if (ofs >= available) {
|
||||
res = TmaConnResult::PacketOverflow;
|
||||
break;
|
||||
}
|
||||
if (ofs == buf_size) {
|
||||
res = TmaConnResult::BufferOverflow;
|
||||
break;
|
||||
}
|
||||
|
||||
buf[ofs] = static_cast<char>(*GetBody(this->offset++));
|
||||
|
||||
if (buf[ofs++] == '\x00') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finish reading the string if the user buffer is too small. */
|
||||
if (res == TmaConnResult::BufferOverflow) {
|
||||
u8 cur = *GetBody(this->offset);
|
||||
while (cur != 0) {
|
||||
if (ofs >= available) {
|
||||
res = TmaConnResult::PacketOverflow;
|
||||
break;
|
||||
}
|
||||
cur = *GetBody(this->offset++);
|
||||
ofs++;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_size != nullptr) {
|
||||
*out_size = ofs;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
40
stratosphere/tma/source/tma_conn_result.hpp
Normal file
40
stratosphere/tma/source/tma_conn_result.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
enum class TmaConnResult : u32 {
|
||||
Success = 0,
|
||||
NotImplemented,
|
||||
GeneralFailure,
|
||||
ConnectionFailure,
|
||||
AlreadyConnected,
|
||||
WrongConnectionVersion,
|
||||
PacketOverflow,
|
||||
BufferOverflow,
|
||||
Disconnected,
|
||||
ServiceAlreadyRegistered,
|
||||
ServiceUnknown,
|
||||
Timeout,
|
||||
NotInitialized,
|
||||
};
|
||||
53
stratosphere/tma/source/tma_conn_service_ids.hpp
Normal file
53
stratosphere/tma/source/tma_conn_service_ids.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tma_conn_result.hpp"
|
||||
|
||||
/* This is just python's hash function, but official TMA code uses it. */
|
||||
static constexpr u32 HashServiceName(const char *name) {
|
||||
u32 h = *name;
|
||||
u32 len = 0;
|
||||
|
||||
while (*name) {
|
||||
h = (1000003 * h) ^ *name;
|
||||
name++;
|
||||
len++;
|
||||
}
|
||||
|
||||
return h ^ len;
|
||||
}
|
||||
|
||||
enum class TmaServiceId : u32 {
|
||||
Invalid = 0,
|
||||
|
||||
/* Special nodes, for facilitating connection over USB. */
|
||||
UsbQueryTarget = HashServiceName("USBQueryTarget"),
|
||||
UsbSendHostInfo = HashServiceName("USBSendHostInfo"),
|
||||
UsbConnect = HashServiceName("USBConnect"),
|
||||
UsbDisconnect = HashServiceName("USBDisconnect"),
|
||||
|
||||
|
||||
TestService = HashServiceName("AtmosphereTestService"), /* Temporary service, will be used to debug communications. */
|
||||
};
|
||||
|
||||
static constexpr bool IsMetaService(TmaServiceId id) {
|
||||
return id == TmaServiceId::UsbQueryTarget ||
|
||||
id == TmaServiceId::UsbSendHostInfo ||
|
||||
id == TmaServiceId::UsbConnect ||
|
||||
id == TmaServiceId::UsbDisconnect;
|
||||
}
|
||||
204
stratosphere/tma/source/tma_conn_usb_connection.cpp
Normal file
204
stratosphere/tma/source/tma_conn_usb_connection.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_conn_usb_connection.hpp"
|
||||
#include "tma_usb_comms.hpp"
|
||||
|
||||
static HosThread g_SendThread, g_RecvThread;
|
||||
|
||||
TmaConnResult TmaUsbConnection::InitializeComms() {
|
||||
return TmaUsbComms::Initialize();
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbConnection::FinalizeComms() {
|
||||
return TmaUsbComms::Finalize();
|
||||
}
|
||||
|
||||
void TmaUsbConnection::ClearSendQueue() {
|
||||
uintptr_t _packet;
|
||||
while (this->send_queue.TryReceive(&_packet)) {
|
||||
TmaPacket *packet = reinterpret_cast<TmaPacket *>(_packet);
|
||||
if (packet != nullptr) {
|
||||
this->FreePacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TmaUsbConnection::SendThreadFunc(void *arg) {
|
||||
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
|
||||
TmaConnResult res = TmaConnResult::Success;
|
||||
TmaPacket *packet = nullptr;
|
||||
|
||||
while (res == TmaConnResult::Success) {
|
||||
/* Receive a packet from the send queue. */
|
||||
{
|
||||
uintptr_t _packet;
|
||||
this_ptr->send_queue.Receive(&_packet);
|
||||
packet = reinterpret_cast<TmaPacket *>(_packet);
|
||||
}
|
||||
|
||||
if (packet != nullptr) {
|
||||
/* Send the packet if we're connected. */
|
||||
if (this_ptr->IsConnected()) {
|
||||
res = TmaUsbComms::SendPacket(packet);
|
||||
}
|
||||
|
||||
this_ptr->FreePacket(packet);
|
||||
this_ptr->Tick();
|
||||
} else {
|
||||
res = TmaConnResult::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
this_ptr->SetConnected(false);
|
||||
this_ptr->OnDisconnected();
|
||||
}
|
||||
|
||||
void TmaUsbConnection::RecvThreadFunc(void *arg) {
|
||||
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
|
||||
TmaConnResult res = TmaConnResult::Success;
|
||||
this_ptr->SetConnected(true);
|
||||
|
||||
while (res == TmaConnResult::Success) {
|
||||
TmaPacket *packet = this_ptr->AllocateRecvPacket();
|
||||
if (packet == nullptr) { std::abort(); }
|
||||
|
||||
res = TmaUsbComms::ReceivePacket(packet);
|
||||
|
||||
if (res == TmaConnResult::Success) {
|
||||
if (!IsMetaService(packet->GetServiceId())) {
|
||||
this_ptr->OnReceivePacket(packet);
|
||||
} else {
|
||||
switch (packet->GetServiceId()) {
|
||||
case TmaServiceId::UsbQueryTarget: {
|
||||
this_ptr->SetConnected(false);
|
||||
|
||||
res = this_ptr->SendQueryReply(packet);
|
||||
|
||||
if (!this_ptr->has_woken_up) {
|
||||
this_ptr->CancelTasks();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TmaServiceId::UsbSendHostInfo: {
|
||||
struct {
|
||||
u32 version;
|
||||
u32 sleeping;
|
||||
} host_info;
|
||||
packet->Read<decltype(host_info)>(host_info);
|
||||
|
||||
if (!this_ptr->has_woken_up || !host_info.sleeping) {
|
||||
this_ptr->CancelTasks();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TmaServiceId::UsbConnect: {
|
||||
res = this_ptr->SendQueryReply(packet);
|
||||
|
||||
if (res == TmaConnResult::Success) {
|
||||
this_ptr->SetConnected(true);
|
||||
this_ptr->OnConnectionEvent(ConnectionEvent::Connected);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TmaServiceId::UsbDisconnect: {
|
||||
this_ptr->SetConnected(false);
|
||||
this_ptr->OnDisconnected();
|
||||
|
||||
this_ptr->CancelTasks();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this_ptr->FreePacket(packet);
|
||||
}
|
||||
} else {
|
||||
this_ptr->FreePacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
this_ptr->SetConnected(false);
|
||||
this_ptr->send_queue.Send(reinterpret_cast<uintptr_t>(nullptr));
|
||||
}
|
||||
|
||||
void TmaUsbConnection::OnUsbStateChange(void *arg, u32 state) {
|
||||
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
|
||||
switch (state) {
|
||||
case 0:
|
||||
case 6:
|
||||
this_ptr->StopThreads();
|
||||
break;
|
||||
case 5:
|
||||
this_ptr->StartThreads();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TmaUsbConnection::StartThreads() {
|
||||
g_SendThread.Join();
|
||||
g_RecvThread.Join();
|
||||
|
||||
g_SendThread.Initialize(&TmaUsbConnection::SendThreadFunc, this, 0x4000, 38);
|
||||
g_RecvThread.Initialize(&TmaUsbConnection::RecvThreadFunc, this, 0x4000, 38);
|
||||
|
||||
this->ClearSendQueue();
|
||||
g_SendThread.Start();
|
||||
g_RecvThread.Start();
|
||||
}
|
||||
|
||||
void TmaUsbConnection::StopThreads() {
|
||||
TmaUsbComms::CancelComms();
|
||||
g_SendThread.Join();
|
||||
g_RecvThread.Join();
|
||||
}
|
||||
|
||||
bool TmaUsbConnection::IsConnected() {
|
||||
return this->is_connected;
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbConnection::Disconnect() {
|
||||
TmaUsbComms::SetStateChangeCallback(nullptr, nullptr);
|
||||
|
||||
this->StopThreads();
|
||||
|
||||
return TmaConnResult::Success;
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbConnection::SendPacket(TmaPacket *packet) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
if (this->IsConnected()) {
|
||||
this->send_queue.Send(reinterpret_cast<uintptr_t>(packet));
|
||||
return TmaConnResult::Success;
|
||||
} else {
|
||||
this->FreePacket(packet);
|
||||
this->Tick();
|
||||
return TmaConnResult::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbConnection::SendQueryReply(TmaPacket *packet) {
|
||||
packet->ClearOffset();
|
||||
struct {
|
||||
u32 version;
|
||||
} target_info;
|
||||
target_info.version = 0;
|
||||
packet->Write<decltype(target_info)>(target_info);
|
||||
return TmaUsbComms::SendPacket(packet);
|
||||
}
|
||||
52
stratosphere/tma/source/tma_conn_usb_connection.hpp
Normal file
52
stratosphere/tma/source/tma_conn_usb_connection.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_connection.hpp"
|
||||
#include "tma_usb_comms.hpp"
|
||||
|
||||
class TmaUsbConnection : public TmaConnection {
|
||||
private:
|
||||
HosMessageQueue send_queue = HosMessageQueue(64);
|
||||
std::atomic<bool> is_connected = false;
|
||||
private:
|
||||
static void SendThreadFunc(void *arg);
|
||||
static void RecvThreadFunc(void *arg);
|
||||
static void OnUsbStateChange(void *this_ptr, u32 state);
|
||||
TmaConnResult SendQueryReply(TmaPacket *packet);
|
||||
void ClearSendQueue();
|
||||
void StartThreads();
|
||||
void StopThreads();
|
||||
void SetConnected(bool c) { this->is_connected = c; }
|
||||
public:
|
||||
static TmaConnResult InitializeComms();
|
||||
static TmaConnResult FinalizeComms();
|
||||
|
||||
TmaUsbConnection() {
|
||||
TmaUsbComms::SetStateChangeCallback(&TmaUsbConnection::OnUsbStateChange, this);
|
||||
}
|
||||
|
||||
virtual ~TmaUsbConnection() {
|
||||
this->Disconnect();
|
||||
}
|
||||
|
||||
virtual bool IsConnected() override;
|
||||
virtual TmaConnResult Disconnect() override;
|
||||
virtual TmaConnResult SendPacket(TmaPacket *packet) override;
|
||||
};
|
||||
106
stratosphere/tma/source/tma_main.cpp
Normal file
106
stratosphere/tma/source/tma_main.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <atmosphere.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_target.hpp"
|
||||
|
||||
#include "dmnt.h"
|
||||
|
||||
extern "C" {
|
||||
extern u32 __start__;
|
||||
|
||||
u32 __nx_applet_type = AppletType_None;
|
||||
|
||||
#define INNER_HEAP_SIZE 0x400000
|
||||
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
|
||||
char nx_inner_heap[INNER_HEAP_SIZE];
|
||||
|
||||
void __libnx_initheap(void);
|
||||
void __appInit(void);
|
||||
void __appExit(void);
|
||||
}
|
||||
|
||||
|
||||
void __libnx_initheap(void) {
|
||||
void* addr = nx_inner_heap;
|
||||
size_t size = nx_inner_heap_size;
|
||||
|
||||
/* Newlib */
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
|
||||
fake_heap_start = (char*)addr;
|
||||
fake_heap_end = (char*)addr + size;
|
||||
}
|
||||
|
||||
void __appInit(void) {
|
||||
Result rc;
|
||||
|
||||
rc = smInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
|
||||
}
|
||||
|
||||
rc = pscInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
rc = setsysInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
rc = dmntInitialize();
|
||||
if (R_FAILED(rc)) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
|
||||
}
|
||||
|
||||
void __appExit(void) {
|
||||
/* Cleanup services. */
|
||||
dmntExit();
|
||||
setsysExit();
|
||||
pscExit();
|
||||
smExit();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
consoleDebugInit(debugDevice_SVC);
|
||||
|
||||
/* This will initialize the target. */
|
||||
TmaTarget::Initialize();
|
||||
|
||||
while (true) {
|
||||
svcSleepThread(10000000UL);
|
||||
}
|
||||
|
||||
TmaTarget::Finalize();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
68
stratosphere/tma/source/tma_power_manager.cpp
Normal file
68
stratosphere/tma/source/tma_power_manager.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_power_manager.hpp"
|
||||
|
||||
static constexpr u16 PscPmModuleId_Usb = 0x04;
|
||||
static constexpr u16 PscPmModuleId_Pcie = 0x13;
|
||||
static constexpr u16 PscPmModuleId_Tma = 0x1E;
|
||||
|
||||
static const u16 g_tma_pm_dependencies[] = {
|
||||
PscPmModuleId_Usb,
|
||||
};
|
||||
|
||||
static void (*g_pm_callback)(PscPmState, u32) = nullptr;
|
||||
static HosThread g_pm_thread;
|
||||
|
||||
static void PowerManagerThread(void *arg) {
|
||||
/* Setup psc module. */
|
||||
Result rc;
|
||||
PscPmModule tma_module = {0};
|
||||
if (R_FAILED((rc = pscGetPmModule(&tma_module, PscPmModuleId_Tma, g_tma_pm_dependencies, sizeof(g_tma_pm_dependencies)/sizeof(u16), true)))) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
/* For now, just do what dummy tma does -- loop forever, acknowledging everything. */
|
||||
while (true) {
|
||||
if (R_FAILED((rc = eventWait(&tma_module.event, U64_MAX)))) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
PscPmState state;
|
||||
u32 flags;
|
||||
if (R_FAILED((rc = pscPmModuleGetRequest(&tma_module, &state, &flags)))) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
|
||||
g_pm_callback(state, flags);
|
||||
|
||||
if (R_FAILED((rc = pscPmModuleAcknowledge(&tma_module, state)))) {
|
||||
fatalSimple(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TmaPowerManager::Initialize(void (*callback)(PscPmState, u32)) {
|
||||
g_pm_callback = callback;
|
||||
g_pm_thread.Initialize(PowerManagerThread, nullptr, 0x4000, 0x26);
|
||||
g_pm_thread.Start();
|
||||
}
|
||||
|
||||
void TmaPowerManager::Finalize() {
|
||||
/* TODO */
|
||||
}
|
||||
25
stratosphere/tma/source/tma_power_manager.hpp
Normal file
25
stratosphere/tma/source/tma_power_manager.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
class TmaPowerManager {
|
||||
public:
|
||||
static void Initialize(void (*callback)(PscPmState, u32));
|
||||
static void Finalize();
|
||||
};
|
||||
32
stratosphere/tma/source/tma_service.cpp
Normal file
32
stratosphere/tma/source/tma_service.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_service.hpp"
|
||||
#include "tma_service_manager.hpp"
|
||||
|
||||
u32 TmaService::GetNextTaskId() {
|
||||
return this->manager->GetNextTaskId();
|
||||
}
|
||||
|
||||
void TmaService::OnSleep() {
|
||||
/* Default service does nothing here. */
|
||||
}
|
||||
|
||||
void TmaService::OnWake() {
|
||||
/* Default service does nothing here. */
|
||||
}
|
||||
43
stratosphere/tma/source/tma_service.hpp
Normal file
43
stratosphere/tma/source/tma_service.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_service_ids.hpp"
|
||||
#include "tma_conn_packet.hpp"
|
||||
#include "tma_task.hpp"
|
||||
|
||||
class TmaServiceManager;
|
||||
|
||||
class TmaService {
|
||||
protected:
|
||||
TmaServiceManager *manager;
|
||||
const char *service_name;
|
||||
const TmaServiceId id;
|
||||
protected:
|
||||
u32 GetNextTaskId();
|
||||
public:
|
||||
TmaService(TmaServiceManager *m, const char *n) : manager(m), service_name(n), id(static_cast<TmaServiceId>(HashServiceName(this->service_name))) { }
|
||||
virtual ~TmaService() { }
|
||||
|
||||
TmaServiceId GetServiceId() const { return this->id; }
|
||||
|
||||
virtual TmaTask *NewTask(TmaPacket *packet) = 0;
|
||||
virtual void OnSleep();
|
||||
virtual void OnWake();
|
||||
};
|
||||
406
stratosphere/tma/source/tma_service_manager.cpp
Normal file
406
stratosphere/tma/source/tma_service_manager.cpp
Normal file
@@ -0,0 +1,406 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_service_manager.hpp"
|
||||
|
||||
TmaServiceManager::TmaServiceManager() {
|
||||
/* Set up queues */
|
||||
for (size_t i = 0; i < TmaServiceManager::PacketQueueDepth; i++) {
|
||||
TmaPacket *packet = nullptr;
|
||||
|
||||
packet = new TmaPacket();
|
||||
packet->SetFreeQueue(&this->free_send_packet_queue);
|
||||
this->free_send_packet_queue.Send(reinterpret_cast<uintptr_t>(packet));
|
||||
packet = nullptr;
|
||||
|
||||
packet = new TmaPacket();
|
||||
packet->SetFreeQueue(&this->free_recv_packet_queue);
|
||||
this->free_recv_packet_queue.Send(reinterpret_cast<uintptr_t>(packet));
|
||||
packet = nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < TmaServiceManager::WorkQueueDepth; i++) {
|
||||
this->free_work_queue.Send(reinterpret_cast<uintptr_t>(new TmaWorkItem()));
|
||||
}
|
||||
}
|
||||
|
||||
TmaServiceManager::~TmaServiceManager() {
|
||||
/* Destroy queues. */
|
||||
TmaPacket *packet = nullptr;
|
||||
while (this->free_send_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
|
||||
delete packet;
|
||||
packet = nullptr;
|
||||
}
|
||||
while (this->free_recv_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
|
||||
delete packet;
|
||||
packet = nullptr;
|
||||
}
|
||||
|
||||
TmaWorkItem *work_item = nullptr;
|
||||
while (this->free_work_queue.TryReceive(reinterpret_cast<uintptr_t *>(&work_item))) {
|
||||
delete work_item;
|
||||
work_item = nullptr;
|
||||
}
|
||||
while (this->work_queue.TryReceive(reinterpret_cast<uintptr_t *>(&work_item))) {
|
||||
delete work_item;
|
||||
work_item = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::Initialize() {
|
||||
this->initialized = true;
|
||||
this->work_thread.Initialize(TmaServiceManager::WorkThread, this, 0x4000, 0x26);
|
||||
this->work_thread.Start();
|
||||
}
|
||||
|
||||
void TmaServiceManager::Finalize() {
|
||||
if (this->initialized) {
|
||||
this->initialized = false;
|
||||
if (this->connection && this->connection->IsConnected()) {
|
||||
this->connection->Disconnect();
|
||||
}
|
||||
|
||||
/* Signal to work thread to end. */
|
||||
this->work_queue.Send(reinterpret_cast<uintptr_t>(nullptr));
|
||||
this->work_thread.Join();
|
||||
|
||||
/* TODO: N tells services that they have no manager here. Do we want to do that? */
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet) {
|
||||
if (!this->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
TmaWorkItem *work_item = nullptr;
|
||||
this->free_work_queue.Receive(reinterpret_cast<uintptr_t *>(&work_item));
|
||||
|
||||
work_item->task = task;
|
||||
work_item->packet = packet;
|
||||
work_item->work_type = type;
|
||||
this->work_queue.Send(reinterpret_cast<uintptr_t>(work_item));
|
||||
}
|
||||
|
||||
/* Packet management. */
|
||||
TmaConnResult TmaServiceManager::SendPacket(TmaPacket *packet) {
|
||||
TmaConnResult res = TmaConnResult::Disconnected;
|
||||
|
||||
if (this->connection != nullptr) {
|
||||
res = this->connection->SendPacket(packet);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void TmaServiceManager::OnReceivePacket(TmaPacket *packet) {
|
||||
this->AddWork(TmaWorkType::ReceivePacket, nullptr, packet);
|
||||
}
|
||||
|
||||
TmaPacket *TmaServiceManager::AllocateSendPacket() {
|
||||
if (!this->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
TmaPacket *packet = nullptr;
|
||||
this->free_send_packet_queue.Receive(reinterpret_cast<uintptr_t *>(&packet));
|
||||
|
||||
packet->ClearOffset();
|
||||
packet->SetBodyLength();
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
TmaPacket *TmaServiceManager::AllocateRecvPacket() {
|
||||
if (!this->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
TmaPacket *packet = nullptr;
|
||||
this->free_recv_packet_queue.Receive(reinterpret_cast<uintptr_t *>(&packet));
|
||||
|
||||
packet->ClearOffset();
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
void TmaServiceManager::FreePacket(TmaPacket *packet) {
|
||||
if (!this->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (packet != nullptr) {
|
||||
packet->GetFreeQueue()->Send(reinterpret_cast<uintptr_t>(packet));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Service/task management. */
|
||||
TmaService *TmaServiceManager::GetServiceById(TmaServiceId id) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
for (auto srv : this->services) {
|
||||
if (srv->GetServiceId() == id) {
|
||||
return srv;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TmaServiceManager::AddService(TmaService *service) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
this->services.push_back(service);
|
||||
}
|
||||
|
||||
void TmaServiceManager::AddTask(TmaTask *task, TmaPacket *packet) {
|
||||
this->AddWork(TmaWorkType::NewTask, task, packet);
|
||||
}
|
||||
|
||||
void TmaServiceManager::FreeTask(TmaTask *task) {
|
||||
this->AddWork(TmaWorkType::FreeTask, task, nullptr);
|
||||
}
|
||||
|
||||
void TmaServiceManager::CancelTask(u32 task_id) {
|
||||
if (this->initialized) {
|
||||
this->task_list.Cancel(task_id);
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::CancelTasks() {
|
||||
if (this->initialized) {
|
||||
this->task_list.CancelAll();
|
||||
}
|
||||
}
|
||||
|
||||
u32 TmaServiceManager::GetNextTaskId() {
|
||||
while (true) {
|
||||
u32 id;
|
||||
{
|
||||
/* N only uses 16 bits for the task id. We'll use 24. */
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
id = (this->next_task_id++) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
if (this->task_list.IsIdFree(id)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Connection management. */
|
||||
void TmaServiceManager::Tick() {
|
||||
this->AddWork(TmaWorkType::Tick, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void TmaServiceManager::SetConnection(TmaConnection *conn) {
|
||||
this->connection = conn;
|
||||
}
|
||||
|
||||
void TmaServiceManager::OnDisconnect() {
|
||||
if (!this->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (!this->GetAsleep()) {
|
||||
this->disconnect_signal.Reset();
|
||||
|
||||
this->AddWork(TmaWorkType::Disconnect, nullptr, nullptr);
|
||||
|
||||
/* TODO: why does N wait with a timeout of zero here? */
|
||||
this->disconnect_signal.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::Sleep() {
|
||||
if (!this->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (!this->GetAsleep()) {
|
||||
this->wake_signal.Reset();
|
||||
this->sleep_signal.Reset();
|
||||
|
||||
/* Tell the work thread to stall, wait for ACK. */
|
||||
this->AddWork(TmaWorkType::Sleep, nullptr, nullptr);
|
||||
this->sleep_signal.Wait();
|
||||
|
||||
this->SetAsleep(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::Wake(TmaConnection *conn) {
|
||||
if (this->connection != nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
if (this->GetAsleep()) {
|
||||
this->connection = conn;
|
||||
this->connection->SetWokenUp(true);
|
||||
this->connection->SetServiceManager(this);
|
||||
/* Tell the work thread to resume. */
|
||||
this->wake_signal.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
bool TmaServiceManager::GetConnected() const {
|
||||
return this->connection != nullptr && this->connection->IsConnected();
|
||||
}
|
||||
|
||||
/* Work thread. */
|
||||
void TmaServiceManager::WorkThread(void *_this) {
|
||||
TmaServiceManager *this_ptr = reinterpret_cast<TmaServiceManager *>(_this);
|
||||
if (!this_ptr->initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
/* Receive a work item. */
|
||||
TmaWorkItem *work_item = nullptr;
|
||||
this_ptr->work_queue.Receive(reinterpret_cast<uintptr_t *>(&work_item));
|
||||
|
||||
if (work_item == nullptr) {
|
||||
/* We're done. */
|
||||
this_ptr->task_list.CancelAll();
|
||||
break;
|
||||
}
|
||||
|
||||
switch (work_item->work_type) {
|
||||
case TmaWorkType::Tick:
|
||||
/* HandleTickWork called unconditionally. */
|
||||
break;
|
||||
case TmaWorkType::NewTask:
|
||||
this_ptr->HandleNewTaskWork(work_item);
|
||||
break;
|
||||
case TmaWorkType::FreeTask:
|
||||
this_ptr->HandleFreeTaskWork(work_item);
|
||||
break;
|
||||
case TmaWorkType::ReceivePacket:
|
||||
this_ptr->HandleReceivePacketWork(work_item);
|
||||
break;
|
||||
case TmaWorkType::Disconnect:
|
||||
this_ptr->HandleDisconnectWork();
|
||||
break;
|
||||
case TmaWorkType::Sleep:
|
||||
this_ptr->HandleSleepWork();
|
||||
break;
|
||||
case TmaWorkType::None:
|
||||
default:
|
||||
std::abort();
|
||||
break;
|
||||
}
|
||||
|
||||
this_ptr->free_work_queue.Send(reinterpret_cast<uintptr_t>(work_item));
|
||||
this_ptr->HandleTickWork();
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::HandleNewTaskWork(TmaWorkItem *work_item) {
|
||||
this->task_list.Add(work_item->task);
|
||||
if (this->GetConnected()) {
|
||||
if (work_item->packet != nullptr) {
|
||||
this->SendPacket(work_item->packet);
|
||||
}
|
||||
} else {
|
||||
work_item->task->Cancel();
|
||||
this->FreePacket(work_item->packet);
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::HandleFreeTaskWork(TmaWorkItem *work_item) {
|
||||
delete work_item->task;
|
||||
}
|
||||
|
||||
void TmaServiceManager::HandleReceivePacketWork(TmaWorkItem *work_item) {
|
||||
ON_SCOPE_EXIT { this->FreePacket(work_item->packet); };
|
||||
|
||||
/* Handle continuation packets. */
|
||||
if (work_item->packet->GetContinuation()) {
|
||||
this->task_list.ReceivePacket(work_item->packet);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make a new task for the packet. */
|
||||
TmaService *srv = this->GetServiceById(work_item->packet->GetServiceId());
|
||||
if (srv != nullptr) {
|
||||
TmaTask *task = srv->NewTask(work_item->packet);
|
||||
if (task != nullptr) {
|
||||
this->task_list.Add(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::HandleTickWork() {
|
||||
if (this->connection == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* N does this kind of manual cleanup if send isn't called. */
|
||||
/* It's pretty gross, but in lieu of a better idea... */
|
||||
bool needs_manual_cleanup = true;
|
||||
|
||||
TmaPacket *packet = nullptr;
|
||||
|
||||
while (this->connection != nullptr && this->free_send_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
|
||||
needs_manual_cleanup = false;
|
||||
|
||||
if (this->task_list.SendPacket(this->GetConnected(), packet)) {
|
||||
if (this->SendPacket(packet) != TmaConnResult::Success) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this->FreePacket(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_manual_cleanup) {
|
||||
this->task_list.CleanupDoneTasks();
|
||||
}
|
||||
}
|
||||
|
||||
void TmaServiceManager::HandleDisconnectWork() {
|
||||
this->task_list.CancelAll();
|
||||
this->disconnect_signal.Signal();
|
||||
}
|
||||
|
||||
void TmaServiceManager::HandleSleepWork() {
|
||||
/* Put the task list to sleep. */
|
||||
this->task_list.Sleep();
|
||||
|
||||
/* Put services to sleep. */
|
||||
for (auto srv : this->services) {
|
||||
srv->OnSleep();
|
||||
}
|
||||
|
||||
/* Signal to main thread that we're sleeping. */
|
||||
this->sleep_signal.Signal();
|
||||
/* Wait for us to wake up. */
|
||||
this->wake_signal.Wait();
|
||||
|
||||
/* We're awake now... */
|
||||
this->SetAsleep(false);
|
||||
|
||||
/* Wake up services. */
|
||||
for (auto srv : this->services) {
|
||||
srv->OnWake();
|
||||
}
|
||||
|
||||
/* Wake up the task list. */
|
||||
this->task_list.Wake();
|
||||
}
|
||||
109
stratosphere/tma/source/tma_service_manager.hpp
Normal file
109
stratosphere/tma/source/tma_service_manager.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_service_ids.hpp"
|
||||
#include "tma_conn_packet.hpp"
|
||||
#include "tma_task.hpp"
|
||||
#include "tma_service.hpp"
|
||||
#include "tma_task_list.hpp"
|
||||
#include "tma_conn_connection.hpp"
|
||||
|
||||
enum class TmaWorkType : u32 {
|
||||
None,
|
||||
NewTask,
|
||||
FreeTask,
|
||||
ReceivePacket,
|
||||
Tick,
|
||||
Disconnect,
|
||||
Sleep,
|
||||
};
|
||||
|
||||
struct TmaWorkItem {
|
||||
TmaTask *task;
|
||||
TmaPacket *packet;
|
||||
TmaWorkType work_type;
|
||||
};
|
||||
|
||||
class TmaServiceManager {
|
||||
public:
|
||||
static constexpr size_t PacketQueueDepth = 0x8;
|
||||
static constexpr size_t WorkQueueDepth = 0x80;
|
||||
private:
|
||||
HosMutex lock;
|
||||
bool initialized = false;
|
||||
TmaTaskList task_list;
|
||||
HosThread work_thread;
|
||||
std::vector<TmaService *> services;
|
||||
TmaConnection *connection = nullptr;
|
||||
u32 next_task_id = 0;
|
||||
|
||||
/* Work queues. */
|
||||
HosMessageQueue free_send_packet_queue = HosMessageQueue(PacketQueueDepth);
|
||||
HosMessageQueue free_recv_packet_queue = HosMessageQueue(PacketQueueDepth);
|
||||
HosMessageQueue work_queue = HosMessageQueue(WorkQueueDepth);
|
||||
HosMessageQueue free_work_queue = HosMessageQueue(WorkQueueDepth);
|
||||
|
||||
/* Sleep management. */
|
||||
HosSignal disconnect_signal;
|
||||
HosSignal wake_signal;
|
||||
HosSignal sleep_signal;
|
||||
bool asleep = false;
|
||||
private:
|
||||
static void WorkThread(void *arg);
|
||||
void AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet);
|
||||
void HandleNewTaskWork(TmaWorkItem *work_item);
|
||||
void HandleFreeTaskWork(TmaWorkItem *work_item);
|
||||
void HandleReceivePacketWork(TmaWorkItem *work_item);
|
||||
void HandleTickWork();
|
||||
void HandleDisconnectWork();
|
||||
void HandleSleepWork();
|
||||
|
||||
void SetAsleep(bool s) { this->asleep = s; }
|
||||
public:
|
||||
TmaServiceManager();
|
||||
virtual ~TmaServiceManager();
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
|
||||
/* Packet management. */
|
||||
TmaConnResult SendPacket(TmaPacket *packet);
|
||||
void OnReceivePacket(TmaPacket *packet);
|
||||
TmaPacket *AllocateSendPacket();
|
||||
TmaPacket *AllocateRecvPacket();
|
||||
void FreePacket(TmaPacket *packet);
|
||||
|
||||
/* Service/task management. */
|
||||
TmaService *GetServiceById(TmaServiceId id);
|
||||
void AddService(TmaService *service);
|
||||
void AddTask(TmaTask *task, TmaPacket *packet);
|
||||
void FreeTask(TmaTask *task);
|
||||
void CancelTask(u32 task_id);
|
||||
void CancelTasks();
|
||||
u32 GetNextTaskId();
|
||||
|
||||
/* Connection management. */
|
||||
void Tick();
|
||||
void SetConnection(TmaConnection *conn);
|
||||
void OnDisconnect();
|
||||
void Sleep();
|
||||
void Wake(TmaConnection *conn);
|
||||
bool GetAsleep() const { return this->asleep; }
|
||||
bool GetConnected() const;
|
||||
};
|
||||
234
stratosphere/tma/source/tma_target.cpp
Normal file
234
stratosphere/tma/source/tma_target.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_connection.hpp"
|
||||
#include "tma_conn_usb_connection.hpp"
|
||||
|
||||
#include "tma_service_manager.hpp"
|
||||
#include "tma_power_manager.hpp"
|
||||
|
||||
#include "tma_target.hpp"
|
||||
|
||||
#include "test/atmosphere_test_service.hpp"
|
||||
#include "settings/settings_service.hpp"
|
||||
#include "target_io/tio_service.hpp"
|
||||
|
||||
struct TmaTargetConfig {
|
||||
char configuration_id1[0x80];
|
||||
char serial_number[0x80];
|
||||
};
|
||||
|
||||
static TmaConnection *g_active_connection = nullptr;
|
||||
static TmaServiceManager *g_service_manager = nullptr;
|
||||
static HosMutex g_connection_event_mutex;
|
||||
static bool g_has_woken_up = false;
|
||||
static bool g_connected_before_sleep = false;
|
||||
static bool g_signal_on_disconnect = false;
|
||||
|
||||
static TmaUsbConnection *g_usb_connection = nullptr;
|
||||
|
||||
static TmaTargetConfig g_target_config = {
|
||||
"Unknown",
|
||||
"SerialNumber",
|
||||
};
|
||||
|
||||
static void RefreshTargetConfig() {
|
||||
setsysInitialize();
|
||||
|
||||
/* TODO: setsysGetConfigurationId1(&g_target_config.configuration_id1); */
|
||||
|
||||
g_target_config.serial_number[0] = 0;
|
||||
setsysGetSerialNumber(g_target_config.serial_number);
|
||||
|
||||
setsysExit();
|
||||
}
|
||||
|
||||
static void InitializeServices() {
|
||||
g_service_manager->Initialize();
|
||||
}
|
||||
|
||||
static void FinalizeServices() {
|
||||
g_service_manager->Finalize();
|
||||
}
|
||||
|
||||
static void SetActiveConnection(TmaConnection *connection) {
|
||||
if (g_active_connection != connection) {
|
||||
if (g_active_connection != nullptr) {
|
||||
FinalizeServices();
|
||||
g_service_manager->SetConnection(nullptr);
|
||||
g_active_connection->Disconnect();
|
||||
g_active_connection = nullptr;
|
||||
}
|
||||
|
||||
if (connection != nullptr) {
|
||||
g_active_connection = connection;
|
||||
InitializeServices();
|
||||
g_service_manager->SetConnection(g_active_connection);
|
||||
g_active_connection->SetServiceManager(g_service_manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void OnConnectionEvent(void *arg, ConnectionEvent evt) {
|
||||
std::scoped_lock<HosMutex> lk(g_connection_event_mutex);
|
||||
|
||||
switch (evt) {
|
||||
case ConnectionEvent::Connected:
|
||||
{
|
||||
bool has_active_connection = false;
|
||||
g_has_woken_up = false;
|
||||
|
||||
if (arg == g_usb_connection) {
|
||||
SetActiveConnection(g_usb_connection);
|
||||
has_active_connection = true;
|
||||
}
|
||||
|
||||
if (has_active_connection) {
|
||||
/* TODO: Signal connected */
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConnectionEvent::Disconnected:
|
||||
if (g_signal_on_disconnect) {
|
||||
/* TODO: Signal disconnected */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void Wake() {
|
||||
if (g_service_manager->GetAsleep()) {
|
||||
g_has_woken_up = true;
|
||||
|
||||
/* N checks what kind of connection to use here. For now, we only use USB. */
|
||||
TmaUsbConnection::InitializeComms();
|
||||
g_usb_connection = new TmaUsbConnection();
|
||||
g_usb_connection->SetConnectionEventCallback(OnConnectionEvent, g_usb_connection);
|
||||
g_usb_connection->Initialize();
|
||||
g_active_connection = g_usb_connection;
|
||||
|
||||
g_service_manager->Wake(g_active_connection);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sleep() {
|
||||
if (!g_service_manager->GetAsleep()) {
|
||||
if (g_active_connection->IsConnected()) {
|
||||
g_connected_before_sleep = true;
|
||||
|
||||
/* TODO: Send a packet saying we're going to sleep. */
|
||||
} else {
|
||||
g_connected_before_sleep = false;
|
||||
}
|
||||
|
||||
g_service_manager->Sleep();
|
||||
g_service_manager->SetConnection(nullptr);
|
||||
g_active_connection->Disconnect();
|
||||
g_active_connection = nullptr;
|
||||
g_service_manager->CancelTasks();
|
||||
|
||||
if (g_usb_connection != nullptr) {
|
||||
g_usb_connection->Finalize();
|
||||
delete g_usb_connection;
|
||||
g_usb_connection = nullptr;
|
||||
TmaUsbConnection::FinalizeComms();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void OnPowerManagementEvent(PscPmState state, u32 flags) {
|
||||
switch (state) {
|
||||
case PscPmState_Awake:
|
||||
{
|
||||
Wake();
|
||||
}
|
||||
break;
|
||||
case PscPmState_ReadyAwaken:
|
||||
{
|
||||
if (g_service_manager->GetAsleep()) {
|
||||
Wake();
|
||||
if (g_connected_before_sleep)
|
||||
{
|
||||
/* Try to restore a connection. */
|
||||
bool connected = g_service_manager->GetConnected();
|
||||
|
||||
/* N uses a seven-second timeout, here. */
|
||||
TimeoutHelper timeout_helper(7000000000ULL);
|
||||
while (!connected && !timeout_helper.TimedOut()) {
|
||||
connected = g_service_manager->GetConnected();
|
||||
if (!connected) {
|
||||
/* Sleep for 1ms. */
|
||||
svcSleepThread(1000000ULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
/* TODO: Signal disconnected */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PscPmState_ReadySleep:
|
||||
{
|
||||
Sleep();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Don't handle ReadySleepCritical/ReadyAwakenCritical/ReadyShutdown */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TmaTarget::Initialize() {
|
||||
/* Get current thread priority. */
|
||||
u32 cur_prio;
|
||||
if (R_FAILED(svcGetThreadPriority(&cur_prio, CUR_THREAD_HANDLE))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
g_active_connection = nullptr;
|
||||
g_service_manager = new TmaServiceManager();
|
||||
/* TODO: Make this better. */
|
||||
g_service_manager->AddService(new AtmosphereTestService(g_service_manager));
|
||||
g_service_manager->AddService(new SettingsService(g_service_manager));
|
||||
g_service_manager->AddService(new TIOService(g_service_manager));
|
||||
|
||||
RefreshTargetConfig();
|
||||
|
||||
/* N checks what kind of connection to use here. For now, we only use USB. */
|
||||
TmaUsbConnection::InitializeComms();
|
||||
g_usb_connection = new TmaUsbConnection();
|
||||
g_usb_connection->SetConnectionEventCallback(OnConnectionEvent, g_usb_connection);
|
||||
g_usb_connection->Initialize();
|
||||
SetActiveConnection(g_usb_connection);
|
||||
|
||||
/* TODO: Initialize connection events */
|
||||
|
||||
/* TODO: Initialize IPC services */
|
||||
|
||||
TmaPowerManager::Initialize(OnPowerManagementEvent);
|
||||
}
|
||||
|
||||
void TmaTarget::Finalize() {
|
||||
/* TODO: Is implementing this actually worthwhile? It will never be called in practice... */
|
||||
}
|
||||
25
stratosphere/tma/source/tma_target.hpp
Normal file
25
stratosphere/tma/source/tma_target.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
class TmaTarget {
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Finalize();
|
||||
};
|
||||
52
stratosphere/tma/source/tma_task.cpp
Normal file
52
stratosphere/tma/source/tma_task.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_task.hpp"
|
||||
#include "tma_service_manager.hpp"
|
||||
|
||||
void TmaTask::SetNeedsPackets(bool n) {
|
||||
this->needs_packets = n;
|
||||
this->manager->Tick();
|
||||
}
|
||||
|
||||
TmaPacket *TmaTask::AllocateSendPacket(bool continuation) {
|
||||
auto packet = this->manager->AllocateSendPacket();
|
||||
packet->SetServiceId(this->service_id);
|
||||
packet->SetTaskId(this->task_id);
|
||||
packet->SetCommand(this->command);
|
||||
packet->SetContinuation(continuation);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
void TmaTask::FreePacket(TmaPacket *packet) {
|
||||
this->manager->FreePacket(packet);
|
||||
}
|
||||
|
||||
void TmaTask::Complete() {
|
||||
SetNeedsPackets(false);
|
||||
this->state = TmaTaskState::Complete;
|
||||
this->manager->Tick();
|
||||
}
|
||||
|
||||
void TmaTask::Cancel() {
|
||||
SetNeedsPackets(false);
|
||||
this->state = TmaTaskState::Canceled;
|
||||
this->manager->Tick();
|
||||
}
|
||||
80
stratosphere/tma/source/tma_task.hpp
Normal file
80
stratosphere/tma/source/tma_task.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_service_ids.hpp"
|
||||
#include "tma_conn_packet.hpp"
|
||||
|
||||
enum class TmaTaskState : u32 {
|
||||
InProgress,
|
||||
Complete,
|
||||
Canceled,
|
||||
};
|
||||
|
||||
class TmaServiceManager;
|
||||
|
||||
class TmaTask {
|
||||
public:
|
||||
static constexpr u32 MaxPriority = 15;
|
||||
static constexpr u32 NumPriorities = MaxPriority + 1;
|
||||
protected:
|
||||
TmaServiceManager *manager;
|
||||
u32 priority = 0;
|
||||
TmaServiceId service_id = TmaServiceId::Invalid;
|
||||
u32 task_id = 0;
|
||||
u32 command = 0;
|
||||
TmaTaskState state = TmaTaskState::InProgress;
|
||||
HosSignal signal;
|
||||
bool owned_by_task_list = true;
|
||||
bool sleep_allowed = true;
|
||||
bool needs_packets = false;
|
||||
public:
|
||||
TmaTask(TmaServiceManager *m) : manager(m) { }
|
||||
virtual ~TmaTask() { }
|
||||
|
||||
u32 GetPriority() const { return this->priority; }
|
||||
TmaServiceId GetServiceId() const { return this->service_id; }
|
||||
u32 GetTaskId() const { return this->task_id; }
|
||||
u32 GetCommand() const { return this->command; }
|
||||
TmaTaskState GetState() const { return this->state; }
|
||||
bool GetOwnedByTaskList() const { return this->owned_by_task_list; }
|
||||
bool GetSleepAllowed() const { return this->sleep_allowed; }
|
||||
bool GetNeedsPackets() const { return this->needs_packets; }
|
||||
|
||||
void SetPriority(u32 p) { this->priority = p; }
|
||||
void SetServiceId(TmaServiceId s) { this->service_id = s; }
|
||||
void SetTaskId(u32 i) { this->task_id = i; }
|
||||
void SetCommand(u32 c) { this->command = c; }
|
||||
void SetOwnedByTaskList(bool o) { this->owned_by_task_list = o; }
|
||||
void SetSleepAllowed(bool a) { this->sleep_allowed = a; }
|
||||
void SetNeedsPackets(bool n);
|
||||
|
||||
void Signal() { this->signal.Signal(); }
|
||||
void ResetSignal() { this->signal.Reset(); }
|
||||
|
||||
TmaPacket *AllocateSendPacket(bool continuation = true);
|
||||
void FreePacket(TmaPacket *packet);
|
||||
|
||||
void Complete();
|
||||
void Cancel();
|
||||
|
||||
virtual void OnStart(TmaPacket *packet) = 0;
|
||||
virtual void OnReceivePacket(TmaPacket *packet) = 0;
|
||||
virtual void OnSendPacket(TmaPacket *packet) = 0;
|
||||
};
|
||||
218
stratosphere/tma/source/tma_task_list.cpp
Normal file
218
stratosphere/tma/source/tma_task_list.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <algorithm>
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "tma_task_list.hpp"
|
||||
|
||||
TmaTask *TmaTaskList::GetById(u32 task_id) const {
|
||||
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||
for (auto task : this->tasks[i]) {
|
||||
if (task->GetTaskId() == task_id) {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 TmaTaskList::GetNumTasks() const {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
u32 count = 0;
|
||||
|
||||
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||
count += this->tasks[i].size();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 TmaTaskList::GetNumSleepingTasks() const {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
u32 count = 0;
|
||||
|
||||
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||
count += this->sleeping_tasks[i].size();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool TmaTaskList::IsIdFree(u32 task_id) const {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
return GetById(task_id) == nullptr;
|
||||
}
|
||||
|
||||
bool TmaTaskList::SendPacket(bool connected, TmaPacket *packet) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
TmaTask *target_task = nullptr;
|
||||
|
||||
/* This loop both finds a target task, and cleans up finished tasks. */
|
||||
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||
auto it = this->tasks[i].begin();
|
||||
while (it != this->tasks[i].end()) {
|
||||
auto task = *it;
|
||||
switch (task->GetState()) {
|
||||
case TmaTaskState::InProgress:
|
||||
it++;
|
||||
if (target_task == nullptr && task->GetNeedsPackets()) {
|
||||
if (connected || IsMetaService(task->GetServiceId())) {
|
||||
target_task = task;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TmaTaskState::Complete:
|
||||
case TmaTaskState::Canceled:
|
||||
it = this->tasks[i].erase(it);
|
||||
if (task->GetOwnedByTaskList()) {
|
||||
delete task;
|
||||
} else {
|
||||
task->Signal();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* TODO: Panic to fatal? */
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target_task) {
|
||||
/* Setup packet. */
|
||||
packet->SetContinuation(true);
|
||||
packet->SetServiceId(target_task->GetServiceId());
|
||||
packet->SetTaskId(target_task->GetTaskId());
|
||||
packet->SetCommand(target_task->GetCommand());
|
||||
packet->ClearOffset();
|
||||
|
||||
/* Actually handle packet send. */
|
||||
target_task->OnSendPacket(packet);
|
||||
}
|
||||
|
||||
return target_task != nullptr;
|
||||
}
|
||||
|
||||
bool TmaTaskList::ReceivePacket(TmaPacket *packet) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
auto task = this->GetById(packet->GetTaskId());
|
||||
if (task != nullptr) {
|
||||
task->OnReceivePacket(packet);
|
||||
}
|
||||
return task != nullptr;
|
||||
}
|
||||
|
||||
|
||||
void TmaTaskList::CleanupDoneTasks() {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
/* Clean up all tasks in Complete/Canceled state. */
|
||||
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||
auto it = this->tasks[i].begin();
|
||||
while (it != this->tasks[i].end()) {
|
||||
auto task = *it;
|
||||
switch (task->GetState()) {
|
||||
case TmaTaskState::InProgress:
|
||||
it++;
|
||||
break;
|
||||
case TmaTaskState::Complete:
|
||||
case TmaTaskState::Canceled:
|
||||
it = this->tasks[i].erase(it);
|
||||
if (task->GetOwnedByTaskList()) {
|
||||
delete task;
|
||||
} else {
|
||||
task->Signal();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* TODO: Panic to fatal? */
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TmaTaskList::Add(TmaTask *task) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
this->tasks[task->GetPriority()].push_back(task);
|
||||
}
|
||||
|
||||
void TmaTaskList::Remove(TmaTask *task) {
|
||||
const auto priority = task->GetPriority();
|
||||
|
||||
/* Nintendo iterates over all lists instead of just the correct one. */
|
||||
/* TODO: Is there actually any reason to do that? */
|
||||
auto ind = std::find(this->tasks[priority].begin(), this->tasks[priority].end(), task);
|
||||
if (ind != this->tasks[priority].end()) {
|
||||
this->tasks[priority].erase(ind);
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Panic to fatal? */
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void TmaTaskList::Cancel(u32 task_id) {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
auto task = this->GetById(task_id);
|
||||
if (task != nullptr) {
|
||||
task->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void TmaTaskList::CancelAll() {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
|
||||
for (auto task : this->tasks[i]) {
|
||||
task->Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TmaTaskList::Sleep() {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||
auto it = this->tasks[i].begin();
|
||||
while (it != this->tasks[i].end()) {
|
||||
auto task = *it;
|
||||
if (task->GetSleepAllowed()) {
|
||||
it = this->tasks[i].erase(it);
|
||||
this->sleeping_tasks[i].push_back(task);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TmaTaskList::Wake() {
|
||||
std::scoped_lock<HosMutex> lk(this->lock);
|
||||
|
||||
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
|
||||
auto it = this->sleeping_tasks[i].begin();
|
||||
while (it != this->sleeping_tasks[i].end()) {
|
||||
auto task = *it;
|
||||
it = this->sleeping_tasks[i].erase(it);
|
||||
this->tasks[i].push_back(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
stratosphere/tma/source/tma_task_list.hpp
Normal file
50
stratosphere/tma/source/tma_task_list.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "tma_conn_service_ids.hpp"
|
||||
#include "tma_task.hpp"
|
||||
|
||||
class TmaTaskList {
|
||||
private:
|
||||
mutable HosMutex lock;
|
||||
std::vector<TmaTask *> tasks[TmaTask::NumPriorities];
|
||||
std::vector<TmaTask *> sleeping_tasks[TmaTask::NumPriorities];
|
||||
private:
|
||||
void Remove(TmaTask *task);
|
||||
TmaTask *GetById(u32 task_id) const;
|
||||
public:
|
||||
TmaTaskList() { }
|
||||
virtual ~TmaTaskList() { }
|
||||
|
||||
u32 GetNumTasks() const;
|
||||
u32 GetNumSleepingTasks() const;
|
||||
bool IsIdFree(u32 task_id) const;
|
||||
|
||||
bool SendPacket(bool connected, TmaPacket *packet);
|
||||
bool ReceivePacket(TmaPacket *packet);
|
||||
void CleanupDoneTasks();
|
||||
void Add(TmaTask *task);
|
||||
void Cancel(u32 task_id);
|
||||
void CancelAll();
|
||||
|
||||
void Sleep();
|
||||
void Wake();
|
||||
};
|
||||
484
stratosphere/tma/source/tma_usb_comms.cpp
Normal file
484
stratosphere/tma/source/tma_usb_comms.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tma_usb_comms.hpp"
|
||||
|
||||
/* TODO: Is this actually allowed? */
|
||||
#define ATMOSPHERE_INTERFACE_PROTOCOL 0xFC
|
||||
|
||||
static std::atomic<bool> g_initialized = false;
|
||||
static UsbDsInterface *g_interface;
|
||||
static UsbDsEndpoint *g_endpoint_in, *g_endpoint_out;
|
||||
|
||||
/* USB State Change Tracking. */
|
||||
static HosThread g_state_change_thread;
|
||||
static WaitableManagerBase *g_state_change_manager = nullptr;
|
||||
static void (*g_state_change_callback)(void *arg, u32 state);
|
||||
static void *g_state_change_arg;
|
||||
|
||||
/* USB Send/Receive mutexes. */
|
||||
static HosMutex g_send_mutex;
|
||||
static HosMutex g_recv_mutex;
|
||||
|
||||
/* Static arrays to do USB DMA into. */
|
||||
static constexpr size_t DmaBufferAlign = 0x1000;
|
||||
static constexpr size_t HeaderBufferSize = DmaBufferAlign;
|
||||
static constexpr size_t DataBufferSize = 0x18000;
|
||||
static __attribute__((aligned(DmaBufferAlign))) u8 g_header_buffer[HeaderBufferSize];
|
||||
static __attribute__((aligned(DmaBufferAlign))) u8 g_recv_data_buf[DataBufferSize];
|
||||
static __attribute__((aligned(DmaBufferAlign))) u8 g_send_data_buf[DataBufferSize];
|
||||
|
||||
/* Taken from libnx usb comms. */
|
||||
static Result _usbCommsInterfaceInit1x()
|
||||
{
|
||||
Result rc = 0;
|
||||
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = 4,
|
||||
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
|
||||
.bInterfaceProtocol = ATMOSPHERE_INTERFACE_PROTOCOL,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = 0x200,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = 0x200,
|
||||
};
|
||||
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
//Setup interface.
|
||||
rc = usbDsGetDsInterface(&g_interface, &interface_descriptor, "usb");
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
//Setup endpoints.
|
||||
rc = usbDsInterface_GetDsEndpoint(g_interface, &g_endpoint_in, &endpoint_descriptor_in);//device->host
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
rc = usbDsInterface_GetDsEndpoint(g_interface, &g_endpoint_out, &endpoint_descriptor_out);//host->device
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _usbCommsInterfaceInit5x() {
|
||||
Result rc = 0;
|
||||
|
||||
u8 iManufacturer, iProduct, iSerialNumber;
|
||||
static const u16 supported_langs[1] = {0x0409};
|
||||
// Send language descriptor
|
||||
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16));
|
||||
// Send manufacturer
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Nintendo");
|
||||
// Send product
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "Nintendo Switch");
|
||||
// Send serial number
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber");
|
||||
|
||||
// Send device descriptors
|
||||
struct usb_device_descriptor device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
.bcdUSB = 0x0110,
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = 0x057e,
|
||||
.idProduct = 0x3000,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = iManufacturer,
|
||||
.iProduct = iProduct,
|
||||
.iSerialNumber = iSerialNumber,
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
// Full Speed is USB 1.1
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor);
|
||||
|
||||
// High Speed is USB 2.0
|
||||
device_descriptor.bcdUSB = 0x0200;
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor);
|
||||
|
||||
// Super Speed is USB 3.0
|
||||
device_descriptor.bcdUSB = 0x0300;
|
||||
// Upgrade packet size to 512
|
||||
device_descriptor.bMaxPacketSize0 = 0x09;
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor);
|
||||
|
||||
// Define Binary Object Store
|
||||
u8 bos[0x16] = {
|
||||
0x05, // .bLength
|
||||
USB_DT_BOS, // .bDescriptorType
|
||||
0x16, 0x00, // .wTotalLength
|
||||
0x02, // .bNumDeviceCaps
|
||||
|
||||
// USB 2.0
|
||||
0x07, // .bLength
|
||||
USB_DT_DEVICE_CAPABILITY, // .bDescriptorType
|
||||
0x02, // .bDevCapabilityType
|
||||
0x02, 0x00, 0x00, 0x00, // dev_capability_data
|
||||
|
||||
// USB 3.0
|
||||
0x0A, // .bLength
|
||||
USB_DT_DEVICE_CAPABILITY, // .bDescriptorType
|
||||
0x03, // .bDevCapabilityType
|
||||
0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00
|
||||
};
|
||||
if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos));
|
||||
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = 4,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
|
||||
.bInterfaceProtocol = ATMOSPHERE_INTERFACE_PROTOCOL,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = 0x40,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = 0x40,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x0F,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
rc = usbDsRegisterInterface(&g_interface);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
interface_descriptor.bInterfaceNumber = g_interface->interface_index;
|
||||
endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
|
||||
// Full Speed Config
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
// High Speed Config
|
||||
endpoint_descriptor_in.wMaxPacketSize = 0x200;
|
||||
endpoint_descriptor_out.wMaxPacketSize = 0x200;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
// Super Speed Config
|
||||
endpoint_descriptor_in.wMaxPacketSize = 0x400;
|
||||
endpoint_descriptor_out.wMaxPacketSize = 0x400;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
//Setup endpoints.
|
||||
rc = usbDsInterface_RegisterEndpoint(g_interface, &g_endpoint_in, endpoint_descriptor_in.bEndpointAddress);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
rc = usbDsInterface_RegisterEndpoint(g_interface, &g_endpoint_out, endpoint_descriptor_out.bEndpointAddress);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* Actual function implementations. */
|
||||
TmaConnResult TmaUsbComms::Initialize() {
|
||||
TmaConnResult res = TmaConnResult::Success;
|
||||
|
||||
if (g_initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Result rc = usbDsInitialize();
|
||||
|
||||
/* Perform interface setup. */
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
rc = _usbCommsInterfaceInit5x();
|
||||
} else {
|
||||
rc = _usbCommsInterfaceInit1x();
|
||||
}
|
||||
}
|
||||
|
||||
/* Start the state change thread. */
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = g_state_change_thread.Initialize(&TmaUsbComms::UsbStateChangeThreadFunc, nullptr, 0x4000, 38);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = g_state_change_thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable USB communication. */
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = usbDsInterface_EnableInterface(g_interface);
|
||||
}
|
||||
if (R_SUCCEEDED(rc) && GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
rc = usbDsEnable();
|
||||
}
|
||||
|
||||
|
||||
if (R_FAILED(rc)) {
|
||||
/* TODO: Should I not abort here? */
|
||||
std::abort();
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbComms::Finalize() {
|
||||
Result rc = 0;
|
||||
/* We must have initialized before calling finalize. */
|
||||
if (!g_initialized) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Kill the state change thread. */
|
||||
g_state_change_manager->RequestStop();
|
||||
if (R_FAILED(g_state_change_thread.Join())) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
CancelComms();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
usbDsExit();
|
||||
}
|
||||
|
||||
g_state_change_callback = nullptr;
|
||||
g_interface = nullptr;
|
||||
g_endpoint_in = nullptr;
|
||||
g_endpoint_out = nullptr;
|
||||
g_initialized = false;
|
||||
|
||||
return R_SUCCEEDED(rc) ? TmaConnResult::Success : TmaConnResult::ConnectionFailure;
|
||||
}
|
||||
|
||||
void TmaUsbComms::CancelComms() {
|
||||
if (!g_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
usbDsEndpoint_Cancel(g_endpoint_in);
|
||||
usbDsEndpoint_Cancel(g_endpoint_out);
|
||||
}
|
||||
|
||||
void TmaUsbComms::SetStateChangeCallback(void (*callback)(void *, u32), void *arg) {
|
||||
g_state_change_callback = callback;
|
||||
g_state_change_arg = arg;
|
||||
}
|
||||
|
||||
Result TmaUsbComms::UsbXfer(UsbDsEndpoint *ep, size_t *out_xferd, void *buf, size_t size) {
|
||||
Result rc = 0;
|
||||
u32 urbId = 0;
|
||||
u32 total_xferd = 0;
|
||||
UsbDsReportData reportdata;
|
||||
|
||||
if (size) {
|
||||
/* Start transfer. */
|
||||
rc = usbDsEndpoint_PostBufferAsync(ep, buf, size, &urbId);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
/* Wait for transfer to complete. */
|
||||
eventWait(&ep->CompletionEvent, U64_MAX);
|
||||
eventClear(&ep->CompletionEvent);
|
||||
|
||||
rc = usbDsEndpoint_GetReportData(ep, &reportdata);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
rc = usbDsParseReportData(&reportdata, urbId, NULL, &total_xferd);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
}
|
||||
|
||||
if (out_xferd) *out_xferd = total_xferd;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbComms::ReceivePacket(TmaPacket *packet) {
|
||||
std::scoped_lock<HosMutex> lk{g_recv_mutex};
|
||||
TmaConnResult res = TmaConnResult::Success;
|
||||
|
||||
if (!g_initialized || packet == nullptr) {
|
||||
return TmaConnResult::GeneralFailure;
|
||||
}
|
||||
|
||||
/* Read the header. */
|
||||
size_t read = 0;
|
||||
if (R_SUCCEEDED(UsbXfer(g_endpoint_out, &read, g_header_buffer, sizeof(TmaPacket::Header)))) {
|
||||
packet->CopyHeaderFrom(reinterpret_cast<TmaPacket::Header *>(g_header_buffer));
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
|
||||
/* Validate the read header data. */
|
||||
if (res == TmaConnResult::Success) {
|
||||
if (read != sizeof(TmaPacket::Header) || !packet->IsHeaderValid()) {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the body! */
|
||||
if (res == TmaConnResult::Success) {
|
||||
const u32 body_len = packet->GetBodyLength();
|
||||
if (0 < body_len) {
|
||||
if (body_len <= sizeof(g_recv_data_buf)) {
|
||||
if (R_SUCCEEDED(UsbXfer(g_endpoint_out, &read, g_recv_data_buf, body_len))) {
|
||||
if (read == body_len) {
|
||||
res = packet->CopyBodyFrom(g_recv_data_buf, body_len);
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate the body. */
|
||||
if (res == TmaConnResult::Success) {
|
||||
if (!packet->IsBodyValid()) {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
}
|
||||
|
||||
if (res == TmaConnResult::Success) {
|
||||
packet->ClearOffset();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TmaConnResult TmaUsbComms::SendPacket(TmaPacket *packet) {
|
||||
std::scoped_lock<HosMutex> lk{g_send_mutex};
|
||||
TmaConnResult res = TmaConnResult::Success;
|
||||
|
||||
if (!g_initialized || packet == nullptr) {
|
||||
return TmaConnResult::GeneralFailure;
|
||||
}
|
||||
|
||||
/* Ensure our packets have the correct checksums. */
|
||||
packet->SetChecksums();
|
||||
|
||||
/* Send the packet. */
|
||||
size_t written = 0;
|
||||
const u32 body_len = packet->GetBodyLength();
|
||||
if (body_len <= sizeof(g_send_data_buf)) {
|
||||
/* Copy header to send buffer. */
|
||||
packet->CopyHeaderTo(g_send_data_buf);
|
||||
|
||||
/* Send the packet header. */
|
||||
if (R_SUCCEEDED(UsbXfer(g_endpoint_in, &written, g_send_data_buf, sizeof(TmaPacket::Header)))) {
|
||||
if (written == sizeof(TmaPacket::Header)) {
|
||||
res = TmaConnResult::Success;
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
|
||||
if (res == TmaConnResult::Success && 0 < body_len) {
|
||||
/* Copy body to send buffer. */
|
||||
packet->CopyBodyTo(g_send_data_buf);
|
||||
|
||||
|
||||
/* Send the packet body. */
|
||||
if (R_SUCCEEDED(UsbXfer(g_endpoint_in, &written, g_send_data_buf, body_len))) {
|
||||
if (written == body_len) {
|
||||
res = TmaConnResult::Success;
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = TmaConnResult::GeneralFailure;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void TmaUsbComms::UsbStateChangeThreadFunc(void *arg) {
|
||||
u32 state;
|
||||
g_state_change_manager = new WaitableManager(1);
|
||||
|
||||
auto state_change_event = LoadReadOnlySystemEvent(usbDsGetStateChangeEvent()->revent, [&](u64 timeout) {
|
||||
if (R_SUCCEEDED(usbDsGetState(&state))) {
|
||||
if (g_state_change_callback != nullptr) {
|
||||
g_state_change_callback(g_state_change_arg, state);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}, true);
|
||||
|
||||
g_state_change_manager->AddWaitable(state_change_event);
|
||||
g_state_change_manager->Process();
|
||||
|
||||
/* If we get here, we're exiting. */
|
||||
state_change_event->r_h = 0;
|
||||
delete g_state_change_manager;
|
||||
g_state_change_manager = nullptr;
|
||||
|
||||
svcExitThread();
|
||||
}
|
||||
36
stratosphere/tma/source/tma_usb_comms.hpp
Normal file
36
stratosphere/tma/source/tma_usb_comms.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "tma_conn_result.hpp"
|
||||
#include "tma_conn_packet.hpp"
|
||||
|
||||
class TmaUsbComms {
|
||||
private:
|
||||
static void UsbStateChangeThreadFunc(void *arg);
|
||||
static Result UsbXfer(UsbDsEndpoint *ep, size_t *out_xferd, void *buf, size_t size);
|
||||
public:
|
||||
static TmaConnResult Initialize();
|
||||
static TmaConnResult Finalize();
|
||||
static void CancelComms();
|
||||
static TmaConnResult ReceivePacket(TmaPacket *packet);
|
||||
static TmaConnResult SendPacket(TmaPacket *packet);
|
||||
|
||||
static void SetStateChangeCallback(void (*callback)(void *, u32), void *arg);
|
||||
};
|
||||
147
stratosphere/tma/tma.json
Normal file
147
stratosphere/tma/tma.json
Normal file
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"name": "tma",
|
||||
"title_id": "0x0100000000000007",
|
||||
"title_id_range_min": "0x0100000000000007",
|
||||
"title_id_range_max": "0x0100000000000007",
|
||||
"main_thread_stack_size": "0x00004000",
|
||||
"main_thread_priority": 38,
|
||||
"default_cpu_id": 3,
|
||||
"process_category": 0,
|
||||
"is_retail": true,
|
||||
"pool_partition": 2,
|
||||
"is_64_bit": true,
|
||||
"address_space_type": 1,
|
||||
"filesystem_access": {
|
||||
"permissions": "0xFFFFFFFFFFFFFFFF"
|
||||
},
|
||||
"service_access": [
|
||||
"bsd:s",
|
||||
"bsdcfg",
|
||||
"dmnt:-",
|
||||
"fatal:u",
|
||||
"i2c",
|
||||
"pcie",
|
||||
"psc:m",
|
||||
"set:cal",
|
||||
"set:fd",
|
||||
"set:sys",
|
||||
"sfdnsres",
|
||||
"spl:",
|
||||
"usb:ds"
|
||||
],
|
||||
"service_host": [
|
||||
"file_io",
|
||||
"gds",
|
||||
"htc",
|
||||
"htcs",
|
||||
"tma_log",
|
||||
"tmagent"
|
||||
],
|
||||
"kernel_capabilities": [{
|
||||
"type": "kernel_flags",
|
||||
"value": {
|
||||
"highest_thread_priority": 63,
|
||||
"lowest_thread_priority": 24,
|
||||
"lowest_cpu_id": 0,
|
||||
"highest_cpu_id": 3
|
||||
}
|
||||
}, {
|
||||
"type": "syscalls",
|
||||
"value": {
|
||||
"svcSetHeapSize": "0x01",
|
||||
"svcSetMemoryPermission": "0x02",
|
||||
"svcSetMemoryAttribute": "0x03",
|
||||
"svcMapMemory": "0x04",
|
||||
"svcUnmapMemory": "0x05",
|
||||
"svcQueryMemory": "0x06",
|
||||
"svcExitProcess": "0x07",
|
||||
"svcCreateThread": "0x08",
|
||||
"svcStartThread": "0x09",
|
||||
"svcExitThread": "0x0a",
|
||||
"svcSleepThread": "0x0b",
|
||||
"svcGetThreadPriority": "0x0c",
|
||||
"svcSetThreadPriority": "0x0d",
|
||||
"svcGetThreadCoreMask": "0x0e",
|
||||
"svcSetThreadCoreMask": "0x0f",
|
||||
"svcGetCurrentProcessorNumber": "0x10",
|
||||
"svcSignalEvent": "0x11",
|
||||
"svcClearEvent": "0x12",
|
||||
"svcMapSharedMemory": "0x13",
|
||||
"svcUnmapSharedMemory": "0x14",
|
||||
"svcCreateTransferMemory": "0x15",
|
||||
"svcCloseHandle": "0x16",
|
||||
"svcResetSignal": "0x17",
|
||||
"svcWaitSynchronization": "0x18",
|
||||
"svcCancelSynchronization": "0x19",
|
||||
"svcArbitrateLock": "0x1a",
|
||||
"svcArbitrateUnlock": "0x1b",
|
||||
"svcWaitProcessWideKeyAtomic": "0x1c",
|
||||
"svcSignalProcessWideKey": "0x1d",
|
||||
"svcGetSystemTick": "0x1e",
|
||||
"svcConnectToNamedPort": "0x1f",
|
||||
"svcSendSyncRequestLight": "0x20",
|
||||
"svcSendSyncRequest": "0x21",
|
||||
"svcSendSyncRequestWithUserBuffer": "0x22",
|
||||
"svcSendAsyncRequestWithUserBuffer": "0x23",
|
||||
"svcGetProcessId": "0x24",
|
||||
"svcGetThreadId": "0x25",
|
||||
"svcBreak": "0x26",
|
||||
"svcOutputDebugString": "0x27",
|
||||
"svcReturnFromException": "0x28",
|
||||
"svcGetInfo": "0x29",
|
||||
"svcWaitForAddress": "0x34",
|
||||
"svcSignalToAddress": "0x35",
|
||||
"svcCreateSession": "0x40",
|
||||
"svcAcceptSession": "0x41",
|
||||
"svcReplyAndReceiveLight": "0x42",
|
||||
"svcReplyAndReceive": "0x43",
|
||||
"svcReplyAndReceiveWithUserBuffer": "0x44",
|
||||
"svcCreateEvent": "0x45",
|
||||
"svcReadWriteRegister": "0x4E",
|
||||
"svcCreateSharedMemory": "0x50",
|
||||
"svcMapTransferMemory": "0x51",
|
||||
"svcUnmapTransferMemory": "0x52",
|
||||
"svcCreateInterruptEvent": "0x53",
|
||||
"svcQueryIoMapping": "0x55",
|
||||
"svcCreateDeviceAddressSpace": "0x56",
|
||||
"svcAttachDeviceAddressSpace": "0x57",
|
||||
"svcDetachDeviceAddressSpace": "0x58",
|
||||
"svcMapDeviceAddressSpaceByForce": "0x59",
|
||||
"svcMapDeviceAddressSpaceAligned": "0x5a",
|
||||
"svcMapDeviceAddressSpace": "0x5b",
|
||||
"svcUnmapDeviceAddressSpace": "0x5c",
|
||||
"svcInvalidateProcessDataCache": "0x5d",
|
||||
"svcStoreProcessDataCache": "0x5e",
|
||||
"svcFlushProcessDataCache": "0x5f",
|
||||
"svcCallSecureMonitor": "0x7f"
|
||||
}
|
||||
}, {
|
||||
"type": "map",
|
||||
"value": {
|
||||
"address": "0x02000000",
|
||||
"is_ro": false,
|
||||
"size": "0x05000000",
|
||||
"is_io": true
|
||||
}
|
||||
}, {
|
||||
"type": "map",
|
||||
"value": {
|
||||
"address": "0x10000000",
|
||||
"is_ro": false,
|
||||
"size": "0x04000000",
|
||||
"is_io": true
|
||||
}
|
||||
}, {
|
||||
"type": "irq_pair",
|
||||
"value": [130, null]
|
||||
}, {
|
||||
"type": "irq_pair",
|
||||
"value": [131, 132]
|
||||
}, {
|
||||
"type": "min_kernel_version",
|
||||
"value": "0x0030"
|
||||
}, {
|
||||
"type": "handle_table_size",
|
||||
"value": 256
|
||||
}]
|
||||
}
|
||||
Reference in New Issue
Block a user