Merge branch 'develop'

# Conflicts:
#	.github/ISSUE_TEMPLATE/bug_report.yaml
#	README.md
This commit is contained in:
ndeadly 2022-10-05 23:58:48 +02:00
commit 132e3c593a
72 changed files with 1417 additions and 955 deletions

View file

@ -12,7 +12,8 @@ body:
label: Switch Firmware Version
description: Which Nintendo Switch firmware (HOS) version are you running?
options:
- 14.1.1 (Latest)
- 14.1.2 (Latest)
- 14.1.1
- 14.1.0
- 14.0.0
- 13.2.1
@ -24,8 +25,6 @@ body:
- 12.0.2
- 12.0.1
- 12.0.0
- 11.0.1
- 11.0.0
- Other (please specify in issue description)
validations:
required: true
@ -45,9 +44,6 @@ body:
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.0
- Other (please specify in issue description)
validations:
required: true
@ -57,7 +53,8 @@ body:
label: Mission Control Version
description: Which version of Mission Control are you using?
options:
- 0.7.0 (Latest)
- 0.7.1 (Latest)
- 0.7.0
- 0.6.4
- 0.6.3
- 0.6.2
@ -98,4 +95,4 @@ body:
attributes:
label: Additional Context
description: Sometimes issues do not occur universally and may be caused by a factor unique to your setup. Add any additional contextual information about your environment here, eg. which other custom sysmodules are you running, are you booting sysnand or emummc, did you install Atmosphère or Mission Control as part of some unofficial third-party release/distribution etc
placeholder: Please provide any additional context here.
placeholder: Please provide any additional context here.

View file

@ -4,6 +4,8 @@ MC_MITM_TID := 010000000000bd00
GIT_BRANCH := $(shell git symbolic-ref --short HEAD | sed s/[^a-zA-Z0-9_-]/_/g)
GIT_HASH := $(shell git rev-parse --short HEAD)
GIT_TAG := $(shell git describe --tags `git rev-list --tags --max-count=1`)
VERSION := $(shell [[ $(GIT_TAG) =~ ^v([0-9]+).([0-9]+).([0-9]+) ]] && printf '0x%02X%02X%02X' $${BASH_REMATCH[1]} $${BASH_REMATCH[2]} $${BASH_REMATCH[3]})
BUILD_VERSION := $(GIT_TAG:v%=%)-$(GIT_BRANCH)-$(GIT_HASH)
TARGETS := mcmitm_version.cpp mc_mitm
@ -11,7 +13,7 @@ TARGETS := mcmitm_version.cpp mc_mitm
all: $(TARGETS)
mcmitm_version.cpp: .git/HEAD .git/index
echo "namespace ams::mitm { const char *version_string = \"$(BUILD_VERSION)\"; const char *build_date = \"$$(date)\"; }" > mc_mitm/source/$@
echo "namespace ams::mitm { unsigned int mc_version = $(VERSION); const char *mc_build_name = \"$(BUILD_VERSION)\"; const char *mc_build_date = \"$$(date)\"; }" > mc_mitm/source/$@
mc_mitm:
$(MAKE) -C $@
@ -24,6 +26,8 @@ clean:
dist: all
rm -rf dist
mkdir -p dist/atmosphere/contents/$(MC_MITM_TID)
cp mc_mitm/out/nintendo_nx_arm64_armv8a/release/mc_mitm.nsp dist/atmosphere/contents/$(MC_MITM_TID)/exefs.nsp
echo "btdrv" >> dist/atmosphere/contents/$(MC_MITM_TID)/mitm.lst

View file

@ -32,6 +32,7 @@ Use controllers from other consoles natively on your Nintendo Switch via Bluetoo
Currently, the following controllers are supported. If you have a third-party variant of one of the below, or a Bluetooth controller that isn't listed, consider submitting an issue with the controller details, including vid/pid, to request support.
* __Nintendo Wii Remote + extensions (Nunchuck, Classic Controller, Classic Controller Pro, SNES Mini, TaTaCon (Taiko drum), MotionPlus)__
* __Nintendo Wii Balance Board (experimental)__
* __Nintendo WiiU Pro Controller__
* __Sony DualShock4 Controller__
* __Sony Dualsense Controller__
@ -117,13 +118,20 @@ A template for the config .ini file will be installed to `/config/MissionControl
- `[general]`
These are general settings for mission control features.
- `enable_rumble` Enables/disables rumble support for unofficial controllers.
- `enable_motion` Enables/disables motion controls support.
- `enable_rumble` Enable/disable rumble support for unofficial controllers.
- `enable_motion` Enable/disable motion controls support.
- `[bluetooth]`
These settings can be used to spoof your switch bluetooth to appear as another device. This may be useful (in conjunction with a link key) if you want to use your controller across multiple devices without having to re-pair every time you switch. Note that changing these settings will invalidate your console information stored in any previously paired controllers and will require re-pairing.
- `host_name` Override the bluetooth host adapter name
- `host_address` Override the bluetooth host adapter address
- `host_name` Override the bluetooth host adapter name.
- `host_address` Override the bluetooth host adapter address.
- `[misc]`
These are miscellaneous controller-specific settings etc.
- `enable_dualshock4_lightbar` Enable/disable the coloured LED lightbar on Sony Dualshock4 controllers.
- `enable_dualsense_lightbar` Enable/disable the coloured LED lightbar on Sony Dualsense controllers.
- `enable_dualsense_player_leds` Enable/disable the white player indicator LEDs below the Dualsense touchpad.
- `dualsense_vibration_intensity` Set Dualsense vibration intensity, 12.5% per increment. Valid range [1-8] where 1=12.5%, 8=100%.
### Removal
@ -145,6 +153,8 @@ Below is a list of features I am currently working on or would like to look into
* ~~Rumble support~~
* ~~Motion controls support~~
* Bluetooth LE support
* USB wired controllers
* UART MITM to allow button combos and other future features to apply to joycons in handheld mode
* Per-controller configurations
* Rumble on/off
* Motion controls on/off
@ -167,7 +177,7 @@ Below is a list of features I am currently working on or would like to look into
* Tesla overlay
* Keyboard and mouse support
* Gamecube mode (analog trigger support)
* USB wired controllers
* Title-specific button bindings
### Known Issues and Limitations

@ -1 +1 @@
Subproject commit 590cdaf022a9c6a50b7bd712d35add3ddf81dc9c
Subproject commit b91294d3b9960eafef6d5d80b08870d427324bc9

@ -1 +1 @@
Subproject commit a1d6b3be4322efcc0b92d61e68bba6e6f3fe534e
Subproject commit d8a4f33192f81d415292e775be2cbe4b83a9ed47

View file

@ -11,5 +11,11 @@
;host_address=04:20:69:04:20:69
[misc]
; Disable the LED lightbar on Sony Dualshock 4 and Dualsense controllers [default false]
;disable_sony_leds=false
; Enable the LED lightbar on Sony Dualshock 4 controllers [default true]
;enable_dualshock4_lightbar=false
; Enable the LED lightbar on Sony Dualsense controllers [default true]
;enable_dualsense_lightbar=false
; Enable the white player indicator LEDs below the Dualsense touchpad [default true]
;enable_dualsense_player_leds=false
; Set Dualsense vibration intensity, 12.5% per increment. Valid range [1-8] where 1=12.5%, 8=100% [default 4(50%)]
;dualsense_vibration_intensity=4

View file

@ -1,18 +1,20 @@
{
"name": "mc.mitm",
"title_id": "0x010000000000bd00",
"title_id_range_min": "0x010000000000bd00",
"title_id_range_max": "0x010000000000bd00",
"program_id": "0x010000000000bd00",
"program_id_range_min": "0x010000000000bd00",
"program_id_range_max": "0x010000000000bd00",
"main_thread_stack_size": "0x1000",
"main_thread_priority": 42,
"default_cpu_id": 3,
"process_category": 1,
"version": "0",
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"signature_key_generation": 0,
"address_space_type": 3,
"system_resource_size": "0",
"optimize_memory_allocation": false,
"disable_device_address_space_merge": true,
"system_resource_size": "0",
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
@ -27,7 +29,7 @@
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 12,
"lowest_thread_priority": 4,
"lowest_cpu_id": 3,
"highest_cpu_id": 3
}
@ -35,7 +37,6 @@
{
"type": "syscalls",
"value": {
"svcUnknown": "0x00",
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
@ -77,92 +78,29 @@
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcFlushEntireDataCache": "0x2a",
"svcFlushDataCache": "0x2b",
"svcMapPhysicalMemory": "0x2c",
"svcUnmapPhysicalMemory": "0x2d",
"svcGetFutureThreadInfo": "0x2e",
"svcGetLastThreadInfo": "0x2f",
"svcGetResourceLimitLimitValue": "0x30",
"svcGetResourceLimitCurrentValue": "0x31",
"svcSetThreadActivity": "0x32",
"svcGetThreadContext3": "0x33",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcUnknown": "0x36",
"svcUnknown": "0x37",
"svcUnknown": "0x38",
"svcUnknown": "0x39",
"svcUnknown": "0x3a",
"svcUnknown": "0x3b",
"svcDumpInfo": "0x3c",
"svcDumpInfoNew": "0x3d",
"svcUnknown": "0x3e",
"svcUnknown": "0x3f",
"svcSynchronizePreemptionState": "0x36",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcUnknown": "0x46",
"svcUnknown": "0x47",
"svcMapPhysicalMemoryUnsafe": "0x48",
"svcUnmapPhysicalMemoryUnsafe": "0x49",
"svcSetUnsafeLimit": "0x4a",
"svcCreateCodeMemory": "0x4b",
"svcControlCodeMemory": "0x4c",
"svcSleepSystem": "0x4d",
"svcReadWriteRegister": "0x4e",
"svcSetProcessActivity": "0x4f",
"svcCreateSharedMemory": "0x50",
"svcMapTransferMemory": "0x51",
"svcUnmapTransferMemory": "0x52",
"svcCreateInterruptEvent": "0x53",
"svcQueryPhysicalAddress": "0x54",
"svcReadWriteRegister": "0x4E",
"svcQueryIoMapping": "0x55",
"svcCreateDeviceAddressSpace": "0x56",
"svcAttachDeviceAddressSpace": "0x57",
"svcDetachDeviceAddressSpace": "0x58",
"svcMapDeviceAddressSpaceByForce": "0x59",
"svcMapDeviceAddressSpaceAligned": "0x5a",
"svcMapDeviceAddressSpace": "0x5b",
"svcUnmapDeviceAddressSpace": "0x5c",
"svcInvalidateProcessDataCache": "0x5d",
"svcStoreProcessDataCache": "0x5e",
"svcFlushProcessDataCache": "0x5f",
"svcDebugActiveProcess": "0x60",
"svcBreakDebugProcess": "0x61",
"svcTerminateDebugProcess": "0x62",
"svcGetDebugEvent": "0x63",
"svcContinueDebugEvent": "0x64",
"svcGetProcessList": "0x65",
"svcGetThreadList": "0x66",
"svcGetDebugThreadContext": "0x67",
"svcSetDebugThreadContext": "0x68",
"svcQueryDebugProcessMemory": "0x69",
"svcReadDebugProcessMemory": "0x6a",
"svcWriteDebugProcessMemory": "0x6b",
"svcSetHardwareBreakPoint": "0x6c",
"svcGetDebugThreadParam": "0x6d",
"svcUnknown": "0x6e",
"svcGetSystemInfo": "0x6f",
"svcCreatePort": "0x70",
"svcManageNamedPort": "0x71",
"svcConnectToPort": "0x72",
"svcSetProcessMemoryPermission": "0x73",
"svcMapProcessMemory": "0x74",
"svcUnmapProcessMemory": "0x75",
"svcQueryProcessMemory": "0x76",
"svcMapProcessCodeMemory": "0x77",
"svcUnmapProcessCodeMemory": "0x78",
"svcCreateProcess": "0x79",
"svcStartProcess": "0x7a",
"svcTerminateProcess": "0x7b",
"svcGetProcessInfo": "0x7c",
"svcCreateResourceLimit": "0x7d",
"svcSetResourceLimitLimitValue": "0x7e",
"svcCallSecureMonitor": "0x7f"
"svcCallSecureMonitor": "0x7F"
}
},
{
@ -172,13 +110,6 @@
{
"type": "handle_table_size",
"value": 128
},
{
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}
]
}

View file

@ -45,7 +45,7 @@ namespace ams::async {
}
Result Initialize(void) {
Result Initialize() {
os::InitializeMessageQueue(&g_work_queue, g_message_buffer, MessageBufferSize);
for (unsigned int i = 0; i < ThreadCount; ++i) {
@ -64,7 +64,7 @@ namespace ams::async {
return ams::ResultSuccess();
}
void Finalize(void) {
void Finalize() {
os::FinalizeMessageQueue(&g_work_queue);
for (unsigned int i = 0; i < ThreadCount; ++i) {

View file

@ -21,8 +21,8 @@ namespace ams::async {
using AsyncFunction = std::function<Result(void)>;
Result Initialize(void);
void Finalize(void);
Result Initialize();
void Finalize();
void QueueWork(AsyncFunction *function);

View file

@ -50,11 +50,11 @@ namespace ams {
return m_user_data;
}
void Wait(void) {
void Wait() {
os::WaitEvent(&m_ready_event);
}
bool TryWait(void) {
bool TryWait() {
return os::TryWaitEvent(&m_ready_event);
}

View file

@ -15,8 +15,6 @@
*/
#include "bluetooth_ble.hpp"
#include "../btdrv_mitm_flags.hpp"
#include <mutex>
#include <cstring>
namespace ams::bluetooth::ble {
@ -38,23 +36,23 @@ namespace ams::bluetooth::ble {
return g_init_event.TryWait();
}
void SignalInitialized(void) {
void SignalInitialized() {
g_init_event.Signal();
}
void WaitInitialized(void) {
void WaitInitialized() {
g_init_event.Wait();
}
os::SystemEvent *GetSystemEvent(void) {
os::SystemEvent *GetSystemEvent() {
return &g_system_event;
}
os::SystemEvent *GetForwardEvent(void) {
os::SystemEvent *GetForwardEvent() {
return &g_system_event_fwd;
}
os::SystemEvent *GetUserForwardEvent(void) {
os::SystemEvent *GetUserForwardEvent() {
return &g_system_event_user_fwd;
}
@ -69,7 +67,7 @@ namespace ams::bluetooth::ble {
return ams::ResultSuccess();
}
void HandleEvent(void) {
void HandleEvent() {
{
std::scoped_lock lk(g_event_data_lock);
R_ABORT_UNLESS(btdrvGetBleManagedEventInfo(&g_event_info, sizeof(bluetooth::BleEventInfo), &g_current_event_type));

View file

@ -20,15 +20,15 @@
namespace ams::bluetooth::ble {
bool IsInitialized(void);
void SignalInitialized(void);
void WaitInitialized(void);
bool IsInitialized();
void SignalInitialized();
void WaitInitialized();
os::SystemEvent *GetSystemEvent(void);
os::SystemEvent *GetForwardEvent(void);
os::SystemEvent *GetUserForwardEvent(void);
os::SystemEvent *GetSystemEvent();
os::SystemEvent *GetForwardEvent();
os::SystemEvent *GetUserForwardEvent();
Result GetEventInfo(bluetooth::BleEventType *type, void *buffer, size_t size);
void HandleEvent(void);
void HandleEvent();
}

View file

@ -14,12 +14,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "bluetooth_circular_buffer.hpp"
#include <mutex>
#include <cstring>
namespace ams::bluetooth {
CircularBuffer::CircularBuffer(void) {
CircularBuffer::CircularBuffer() {
this->readOffset = 0;
this->writeOffset = 0;
this->isInitialized = false;
@ -38,7 +36,7 @@ namespace ams::bluetooth {
this->isInitialized = true;
}
void CircularBuffer::Finalize(void) {
void CircularBuffer::Finalize() {
if (!this->isInitialized)
fatalThrow(-1);
@ -46,11 +44,11 @@ namespace ams::bluetooth {
this->event = nullptr;
}
bool CircularBuffer::IsInitialized(void) {
bool CircularBuffer::IsInitialized() {
return this->isInitialized;
}
u64 CircularBuffer::GetWriteableSize(void) {
u64 CircularBuffer::GetWriteableSize() {
u32 readOffset = this->readOffset;
u32 writeOffset = this->writeOffset;
@ -128,11 +126,11 @@ namespace ams::bluetooth {
}
}
CircularBufferPacket *CircularBuffer::Read(void) {
CircularBufferPacket *CircularBuffer::Read() {
return this->_read();
}
u64 CircularBuffer::Free(void) {
u64 CircularBuffer::Free() {
if (!this->isInitialized)
return -1;
@ -167,11 +165,11 @@ namespace ams::bluetooth {
this->writeOffset = offset;
}
u32 CircularBuffer::_getWriteOffset(void) {
u32 CircularBuffer::_getWriteOffset() {
return this->writeOffset;
}
u32 CircularBuffer::_getReadOffset(void) {
u32 CircularBuffer::_getReadOffset() {
return this->readOffset;
}
@ -200,14 +198,14 @@ namespace ams::bluetooth {
return 0;
}
void CircularBuffer::_updateUtilization(void) {
void CircularBuffer::_updateUtilization() {
u32 newCapacity = this->isInitialized ? this->GetWriteableSize() : 0;
if (this->size > newCapacity + 1000)
this->size = newCapacity;
}
CircularBufferPacket *CircularBuffer::_read(void) {
CircularBufferPacket *CircularBuffer::_read() {
if (this->isInitialized) {
CircularBufferPacket *packet;
u32 newOffset;

View file

@ -46,26 +46,26 @@ namespace ams::bluetooth {
class CircularBuffer {
public:
CircularBuffer(void);
CircularBuffer();
void Initialize(const char *name); // 10.0.0+, previously took event argument
void Finalize(void);
bool IsInitialized(void);
u64 GetWriteableSize(void);
void Finalize();
bool IsInitialized();
u64 GetWriteableSize();
void SetWriteCompleteEvent(os::EventType *event);
u64 Write(u8 type, void *data, size_t size);
void DiscardOldPackets(u8 type, u32 ageLimit);
CircularBufferPacket *Read(void);
u64 Free(void);
CircularBufferPacket *Read();
u64 Free();
private:
void _setReadOffset(u32 offset);
void _setWriteOffset(u32 offset);
u32 _getWriteOffset(void);
u32 _getReadOffset(void);
u32 _getWriteOffset();
u32 _getReadOffset();
u64 _write(u8 type, void *data, size_t size);
void _updateUtilization(void);
CircularBufferPacket *_read(void);
void _updateUtilization();
CircularBufferPacket *_read();
os::SdkMutex mutex;
os::EventType *event;

View file

@ -16,8 +16,6 @@
#include "bluetooth_core.hpp"
#include "../btdrv_mitm_flags.hpp"
#include "../../controllers/controller_management.hpp"
#include <mutex>
#include <cstring>
namespace ams::bluetooth::core {
@ -36,7 +34,9 @@ namespace ams::bluetooth::core {
os::Event g_data_read_event(os::EventClearMode_AutoClear);
bluetooth::Address ReverseBluetoothAddress(bluetooth::Address address) {
uint64_t tmp = util::SwapEndian48(*reinterpret_cast<uint64_t *>(&address));
uint64_t tmp;
std::memcpy(&tmp, &address, sizeof(address));
tmp = util::SwapEndian(tmp) >> 16;
return *reinterpret_cast<bluetooth::Address *>(&tmp);
}
@ -46,31 +46,31 @@ namespace ams::bluetooth::core {
return g_init_event.TryWait();
}
void SignalInitialized(void) {
void SignalInitialized() {
g_init_event.Signal();
}
void WaitInitialized(void) {
void WaitInitialized() {
g_init_event.Wait();
}
void SignalEnabled(void) {
void SignalEnabled() {
g_enable_event.Signal();
}
void WaitEnabled(void) {
void WaitEnabled() {
g_enable_event.Wait();
}
os::SystemEvent *GetSystemEvent(void) {
os::SystemEvent *GetSystemEvent() {
return &g_system_event;
}
os::SystemEvent *GetForwardEvent(void) {
os::SystemEvent *GetForwardEvent() {
return &g_system_event_fwd;
}
os::SystemEvent *GetUserForwardEvent(void) {
os::SystemEvent *GetUserForwardEvent() {
return &g_system_event_user_fwd;
}
@ -184,7 +184,7 @@ namespace ams::bluetooth::core {
R_ABORT_UNLESS(btdrvRespondToPinRequest(event_info->pairing_pin_code_request.addr, &pin));
}
void HandleEvent(void) {
void HandleEvent() {
{
std::scoped_lock lk(g_event_info_lock);
R_ABORT_UNLESS(btdrvGetEventInfo(&g_event_info, sizeof(bluetooth::EventInfo), &g_current_event_type));

View file

@ -20,18 +20,18 @@
namespace ams::bluetooth::core {
bool IsInitialized(void);
void SignalInitialized(void);
void WaitInitialized(void);
void SignalEnabled(void);
void WaitEnabled(void);
bool IsInitialized();
void SignalInitialized();
void WaitInitialized();
void SignalEnabled();
void WaitEnabled();
os::SystemEvent *GetSystemEvent(void);
os::SystemEvent *GetForwardEvent(void);
os::SystemEvent *GetUserForwardEvent(void);
os::SystemEvent *GetSystemEvent();
os::SystemEvent *GetForwardEvent();
os::SystemEvent *GetUserForwardEvent();
void SignalFakeEvent(bluetooth::EventType type, const void *data, size_t size);
Result GetEventInfo(ncm::ProgramId program_id, bluetooth::EventType *type, void *buffer, size_t size);
void HandleEvent(void);
void HandleEvent();
}

View file

@ -76,7 +76,7 @@ namespace ams::bluetooth::events {
}
Result Initialize(void) {
Result Initialize() {
R_TRY(os::CreateThread(&g_thread,
EventHandlerThreadFunc,
nullptr,
@ -91,7 +91,7 @@ namespace ams::bluetooth::events {
return ams::ResultSuccess();
}
void Finalize(void) {
void Finalize() {
os::DestroyThread(&g_thread);
}

View file

@ -24,7 +24,7 @@ namespace ams::bluetooth::events {
BtdrvEventType_BluetoothBle,
};
Result Initialize(void);
void Finalize(void);
Result Initialize();
void Finalize();
}

View file

@ -16,8 +16,6 @@
#include "bluetooth_hid.hpp"
#include "../btdrv_mitm_flags.hpp"
#include "../../controllers/controller_management.hpp"
#include <mutex>
#include <cstring>
namespace ams::bluetooth::hid {
@ -40,23 +38,23 @@ namespace ams::bluetooth::hid {
return g_init_event.TryWait();
}
void SignalInitialized(void) {
void SignalInitialized() {
g_init_event.Signal();
}
void WaitInitialized(void) {
void WaitInitialized() {
g_init_event.Wait();
}
os::SystemEvent *GetSystemEvent(void) {
os::SystemEvent *GetSystemEvent() {
return &g_system_event;
}
os::SystemEvent *GetForwardEvent(void) {
os::SystemEvent *GetForwardEvent() {
return &g_system_event_fwd;
}
os::SystemEvent *GetUserForwardEvent(void) {
os::SystemEvent *GetUserForwardEvent() {
return &g_system_event_user_fwd;
}
@ -104,7 +102,7 @@ namespace ams::bluetooth::hid {
}
}
void HandleEvent(void) {
void HandleEvent() {
{
std::scoped_lock lk(g_event_info_lock);
R_ABORT_UNLESS(btdrvGetHidEventInfo(&g_event_info, sizeof(bluetooth::HidEventInfo), &g_current_event_type));

View file

@ -20,16 +20,16 @@
namespace ams::bluetooth::hid {
bool IsInitialized(void);
void SignalInitialized(void);
void WaitInitialized(void);
bool IsInitialized();
void SignalInitialized();
void WaitInitialized();
os::SystemEvent *GetSystemEvent(void);
os::SystemEvent *GetForwardEvent(void);
os::SystemEvent *GetUserForwardEvent(void);
os::SystemEvent *GetSystemEvent();
os::SystemEvent *GetForwardEvent();
os::SystemEvent *GetUserForwardEvent();
void SignalFakeEvent(bluetooth::HidEventType type, const void *data, size_t size);
Result GetEventInfo(bluetooth::HidEventType *type, void *buffer, size_t size);
void HandleEvent(void);
void HandleEvent();
}

View file

@ -17,10 +17,7 @@
#include "bluetooth_circular_buffer.hpp"
#include "../btdrv_shim.h"
#include "../btdrv_mitm_flags.hpp"
#include "../../utils.hpp"
#include "../../controllers/controller_management.hpp"
#include <mutex>
#include <cstring>
namespace ams::bluetooth::hid::report {
@ -67,42 +64,42 @@ namespace ams::bluetooth::hid::report {
return g_init_event.TryWait();
}
void WaitInitialized(void) {
void WaitInitialized() {
g_init_event.Wait();
}
void SignalInitialized(void) {
void SignalInitialized() {
g_init_event.Signal();
}
void SignalReportRead(void) {
void SignalReportRead() {
g_report_read_event.Signal();
}
os::SharedMemory *GetRealSharedMemory(void) {
os::SharedMemory *GetRealSharedMemory() {
if (hos::GetVersion() < hos::Version_7_0_0)
return nullptr;
return &g_real_bt_shmem;
}
os::SharedMemory *GetFakeSharedMemory(void) {
os::SharedMemory *GetFakeSharedMemory() {
return &g_fake_bt_shmem;
}
os::SystemEvent *GetSystemEvent(void) {
os::SystemEvent *GetSystemEvent() {
return &g_system_event;
}
os::SystemEvent *GetForwardEvent(void) {
os::SystemEvent *GetForwardEvent() {
return &g_system_event_fwd;
}
os::SystemEvent *GetUserForwardEvent(void) {
os::SystemEvent *GetUserForwardEvent() {
return &g_system_event_user_fwd;
}
Result Initialize(void) {
Result Initialize() {
R_TRY(os::CreateThread(&g_thread,
EventThreadFunc,
nullptr,
@ -117,7 +114,7 @@ namespace ams::bluetooth::hid::report {
return ams::ResultSuccess();
}
void Finalize(void) {
void Finalize() {
os::DestroyThread(&g_thread);
}
@ -129,7 +126,7 @@ namespace ams::bluetooth::hid::report {
return ams::ResultSuccess();
}
Result InitializeReportBuffer(void) {
Result InitializeReportBuffer() {
g_fake_bt_shmem.Map(os::MemoryPermission_ReadWrite);
g_fake_buffer = reinterpret_cast<bluetooth::CircularBuffer *>(g_fake_bt_shmem.GetMappedAddress());
g_fake_buffer->Initialize("HID Report");
@ -226,7 +223,7 @@ namespace ams::bluetooth::hid::report {
return ams::ResultSuccess();
}
inline void HandleHidReportEventV1(void) {
inline void HandleHidReportEventV1() {
R_ABORT_UNLESS(btdrvGetHidReportEventInfo(&g_event_info, sizeof(bluetooth::HidReportEventInfo), &g_current_event_type));
switch (g_current_event_type) {
@ -259,7 +256,7 @@ namespace ams::bluetooth::hid::report {
}
}
inline void HandleHidReportEventV7(void) {
inline void HandleHidReportEventV7() {
while (true) {
auto real_packet = g_real_buffer->Read();
if (!real_packet)
@ -300,7 +297,7 @@ namespace ams::bluetooth::hid::report {
}
}
inline void HandleHidReportEventV12(void) {
inline void HandleHidReportEventV12() {
while (true) {
auto real_packet = g_real_buffer->Read();
if (!real_packet)
@ -341,7 +338,7 @@ namespace ams::bluetooth::hid::report {
}
}
void HandleEvent(void) {
void HandleEvent() {
if (g_redirect_hid_report_events) {
g_system_event_user_fwd.Signal();
g_report_read_event.Wait();

View file

@ -20,29 +20,29 @@
namespace ams::bluetooth::hid::report {
bool IsInitialized(void);
void WaitInitialized(void);
void SignalInitialized(void);
void SignalReportRead(void);
bool IsInitialized();
void WaitInitialized();
void SignalInitialized();
void SignalReportRead();
os::SharedMemory *GetRealSharedMemory(void);
os::SharedMemory *GetFakeSharedMemory(void);
os::SharedMemory *GetRealSharedMemory();
os::SharedMemory *GetFakeSharedMemory();
os::SystemEvent *GetSystemEvent(void);
os::SystemEvent *GetForwardEvent(void);
os::SystemEvent *GetUserForwardEvent(void);
os::SystemEvent *GetSystemEvent();
os::SystemEvent *GetForwardEvent();
os::SystemEvent *GetUserForwardEvent();
Result Initialize(void);
void Finalize(void);
Result Initialize();
void Finalize();
Result MapRemoteSharedMemory(os::NativeHandle handle);
Result InitializeReportBuffer(void);
Result InitializeReportBuffer();
Result WriteHidDataReport(const bluetooth::Address address, const bluetooth::HidReport *report);
Result WriteHidSetReport(const bluetooth::Address address, uint32_t status);
Result WriteHidGetReport(const bluetooth::Address address, const bluetooth::HidReport *report);
Result GetEventInfo(bluetooth::HidEventType *type, void *buffer, size_t size);
void HandleEvent(void);
void HandleEvent();
}

View file

@ -36,7 +36,7 @@ namespace ams::mitm::bluetooth {
static constexpr bool CanManageMitmServers = true;
};
constexpr size_t MaxSessions = 6;
constexpr size_t MaxSessions = 30;
class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> {
private:
@ -70,7 +70,7 @@ namespace ams::mitm::bluetooth {
}
Result Launch(void) {
Result Launch() {
R_TRY(os::CreateThread(&g_thread,
BtdrvMitmThreadFunction,
nullptr,
@ -85,7 +85,7 @@ namespace ams::mitm::bluetooth {
return ams::ResultSuccess();
}
void WaitFinished(void) {
void WaitFinished() {
os::WaitThread(&g_thread);
}

View file

@ -18,7 +18,7 @@
namespace ams::mitm::bluetooth {
Result Launch(void);
void WaitFinished(void);
Result Launch();
void WaitFinished();
}

View file

@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <atomic>
#include <stratosphere.hpp>
namespace ams {

View file

@ -22,7 +22,6 @@
#include "../mcmitm_initialization.hpp"
#include "../controllers/controller_management.hpp"
#include <switch.h>
#include <cstring>
namespace ams::mitm::bluetooth {
@ -50,7 +49,7 @@ namespace ams::mitm::bluetooth {
return ams::ResultSuccess();
}
Result BtdrvMitmService::EnableBluetooth(void) {
Result BtdrvMitmService::EnableBluetooth() {
R_TRY(btdrvEnableBluetoothFwd(m_forward_service.get()));
ams::bluetooth::core::SignalEnabled();
@ -61,13 +60,7 @@ namespace ams::mitm::bluetooth {
}
Result BtdrvMitmService::GetEventInfo(sf::Out<ams::bluetooth::EventType> out_type, const sf::OutPointerBuffer &out_buffer) {
R_TRY(ams::bluetooth::core::GetEventInfo(m_client_info.program_id,
out_type.GetPointer(),
static_cast<uint8_t *>(out_buffer.GetPointer()),
static_cast<size_t>(out_buffer.GetSize())
));
return ams::ResultSuccess();
return ams::bluetooth::core::GetEventInfo(m_client_info.program_id, out_type.GetPointer(), out_buffer.GetPointer(), out_buffer.GetSize());
}
Result BtdrvMitmService::InitializeHid(sf::OutCopyHandle out_handle, u16 version) {
@ -109,12 +102,7 @@ namespace ams::mitm::bluetooth {
}
Result BtdrvMitmService::GetHidEventInfo(sf::Out<ams::bluetooth::HidEventType> out_type, const sf::OutPointerBuffer &out_buffer) {
R_TRY(ams::bluetooth::hid::GetEventInfo(out_type.GetPointer(),
static_cast<uint8_t *>(out_buffer.GetPointer()),
static_cast<size_t>(out_buffer.GetSize())
));
return ams::ResultSuccess();
return ams::bluetooth::hid::GetEventInfo(out_type.GetPointer(), out_buffer.GetPointer(), out_buffer.GetSize());
}
Result BtdrvMitmService::SetTsi(ams::bluetooth::Address address, u8 tsi) {
@ -187,12 +175,7 @@ namespace ams::mitm::bluetooth {
Result _GetHidReportEventInfoDeprecated(Service *srv, sf::Out<ams::bluetooth::HidEventType> out_type, const sf::OutPointerBuffer &out_buffer) {
AMS_UNUSED(srv);
R_TRY(ams::bluetooth::hid::report::GetEventInfo(out_type.GetPointer(),
static_cast<uint8_t *>(out_buffer.GetPointer()),
static_cast<size_t>(out_buffer.GetSize())
));
return ams::ResultSuccess();
return ams::bluetooth::hid::report::GetEventInfo(out_type.GetPointer(), out_buffer.GetPointer(), out_buffer.GetSize());
}
/* 1.0.0 - 3.0.2 */
@ -242,12 +225,7 @@ namespace ams::mitm::bluetooth {
}
Result BtdrvMitmService::GetBleManagedEventInfo(sf::Out<ams::bluetooth::BleEventType> out_type, const sf::OutPointerBuffer &out_buffer) {
R_TRY(ams::bluetooth::ble::GetEventInfo(out_type.GetPointer(),
static_cast<uint8_t *>(out_buffer.GetPointer()),
static_cast<size_t>(out_buffer.GetSize())
));
return ams::ResultSuccess();
return ams::bluetooth::ble::GetEventInfo(out_type.GetPointer(), out_buffer.GetPointer(), out_buffer.GetSize());
}
Result BtdrvMitmService::GetRealSharedMemory(sf::OutCopyHandle out_handle) {
@ -276,7 +254,7 @@ namespace ams::mitm::bluetooth {
g_redirect_ble_events = redirect;
}
void BtdrvMitmService::SignalHidReportRead(void) {
void BtdrvMitmService::SignalHidReportRead() {
ams::bluetooth::hid::report::SignalReportRead();
}

View file

@ -19,7 +19,7 @@
#define AMS_BTDRV_MITM_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 1, Result, InitializeBluetooth, (sf::OutCopyHandle out_handle), (out_handle)) \
AMS_SF_METHOD_INFO(C, H, 2, Result, EnableBluetooth, (void), ()) \
AMS_SF_METHOD_INFO(C, H, 2, Result, EnableBluetooth, (), ()) \
AMS_SF_METHOD_INFO(C, H, 15, Result, GetEventInfo, (sf::Out<ams::bluetooth::EventType> out_type, const sf::OutPointerBuffer &out_buffer), (out_type, out_buffer)) \
AMS_SF_METHOD_INFO(C, H, 16, Result, InitializeHid, (sf::OutCopyHandle out_handle, u16 version), (out_handle, version)) \
AMS_SF_METHOD_INFO(C, H, 19, Result, WriteHidData, (ams::bluetooth::Address address, const sf::InPointerBuffer &buffer), (address, buffer)) \
@ -39,7 +39,7 @@
AMS_SF_METHOD_INFO(C, H, 65003, void, RedirectHidEvents, (bool redirect), (redirect)) \
AMS_SF_METHOD_INFO(C, H, 65004, void, RedirectHidReportEvents, (bool redirect), (redirect)) \
AMS_SF_METHOD_INFO(C, H, 65005, void, RedirectBleEvents, (bool redirect), (redirect)) \
AMS_SF_METHOD_INFO(C, H, 65006, void, SignalHidReportRead, (void), ()) \
AMS_SF_METHOD_INFO(C, H, 65006, void, SignalHidReportRead, (), ()) \
AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::bluetooth, IBtdrvMitmInterface, AMS_BTDRV_MITM_INTERFACE_INFO, 0xAACFC9A7)
@ -58,7 +58,7 @@ namespace ams::mitm::bluetooth {
public:
Result InitializeBluetooth(sf::OutCopyHandle out_handle);
Result EnableBluetooth(void);
Result EnableBluetooth();
Result GetEventInfo(sf::Out<ams::bluetooth::EventType> out_type, const sf::OutPointerBuffer &out_buffer);
Result InitializeHid(sf::OutCopyHandle out_handle, u16 version);
Result WriteHidData(ams::bluetooth::Address address, const sf::InPointerBuffer &buffer);
@ -81,7 +81,7 @@ namespace ams::mitm::bluetooth {
void RedirectHidEvents(bool redirect);
void RedirectHidReportEvents(bool redirect);
void RedirectBleEvents(bool redirect);
void SignalHidReportRead(void);
void SignalHidReportRead();
};
static_assert(IsIBtdrvMitmInterface<BtdrvMitmService>);

View file

@ -16,7 +16,6 @@
#include "btm_mitm_service.hpp"
#include "btm_shim.h"
#include "../controllers/controller_management.hpp"
#include <cstring>
namespace ams::mitm::btm {

View file

@ -70,7 +70,7 @@ namespace ams::mitm::btm {
}
Result Launch(void) {
Result Launch() {
R_TRY(os::CreateThread(&g_thread,
BtmMitmThreadFunction,
nullptr,
@ -85,7 +85,7 @@ namespace ams::mitm::btm {
return ams::ResultSuccess();
}
void WaitFinished(void) {
void WaitFinished() {
os::WaitThread(&g_thread);
}

View file

@ -18,7 +18,7 @@
namespace ams::mitm::btm {
Result Launch(void);
void WaitFinished(void);
Result Launch();
void WaitFinished();
}

View file

@ -29,7 +29,7 @@ namespace ams::controller {
switch(eightbitdo_report->id) {
case 0x01:
this->MapInputReport0x01(eightbitdo_report, report->size == 9 ? EightBitDoReportFormat_ZeroV1 : EightBitDoReportFormat_Other); break;
this->MapInputReport0x01(eightbitdo_report); break;
case 0x03:
this->MapInputReport0x03(eightbitdo_report, report->size == 11 ? EightBitDoReportFormat_ZeroV1 : EightBitDoReportFormat_ZeroV2); break;
default:
@ -37,8 +37,8 @@ namespace ams::controller {
}
}
void EightBitDoController::MapInputReport0x01(const EightBitDoReportData *src, EightBitDoReportFormat fmt) {
if (fmt == EightBitDoReportFormat_ZeroV1) {
void EightBitDoController::MapInputReport0x01(const EightBitDoReportData *src) {
if (m_controller_type == EightBitDoControllerType_Zero) {
m_buttons.dpad_down = (src->input0x01_v1.dpad == EightBitDoDPadV1_S) ||
(src->input0x01_v1.dpad == EightBitDoDPadV1_SE) ||
(src->input0x01_v1.dpad == EightBitDoDPadV1_SW);
@ -80,18 +80,32 @@ namespace ams::controller {
m_buttons.X = src->input0x01_v2.buttons.Y;
m_buttons.Y = src->input0x01_v2.buttons.X;
m_buttons.R = src->input0x01_v2.buttons.R1;
m_buttons.ZR = src->input0x01_v2.right_trigger > 0x7f;
m_buttons.L = src->input0x01_v2.buttons.L1;
m_buttons.ZL = src->input0x01_v2.left_trigger > 0x7f;
m_buttons.R = src->input0x01_v2.buttons.R1;
m_buttons.minus = src->input0x01_v2.buttons.select;
m_buttons.plus = src->input0x01_v2.buttons.start;
if (m_controller_type == EightBitDoControllerType_Sn30ProXboxCloud) {
m_buttons.ZL = src->input0x01_v2.left_trigger > 0x7f;
m_buttons.ZR = src->input0x01_v2.right_trigger > 0x7f;
m_buttons.lstick_press = src->input0x01_v2.buttons.L3;
m_buttons.rstick_press = src->input0x01_v2.buttons.R3;
m_buttons.minus = src->input0x01_v2.buttons.v1.select;
m_buttons.plus = src->input0x01_v2.buttons.v1.start;
m_buttons.home = src->input0x01_v2.buttons.home;
m_buttons.lstick_press = src->input0x01_v2.buttons.v1.L3;
m_buttons.rstick_press = src->input0x01_v2.buttons.v1.R3;
m_buttons.home = src->input0x01_v2.buttons.v1.home;
} else {
m_buttons.ZL = src->input0x01_v2.buttons.v2.L2;
m_buttons.ZR = src->input0x01_v2.buttons.v2.R2;
m_buttons.minus = src->input0x01_v2.buttons.v2.select;
m_buttons.plus = src->input0x01_v2.buttons.v2.start;
m_buttons.lstick_press = src->input0x01_v2.buttons.v2.L3;
m_buttons.rstick_press = src->input0x01_v2.buttons.v2.R3;
m_buttons.home = src->input0x01_v2.buttons.v2.home;
}
}
}

View file

@ -18,6 +18,13 @@
namespace ams::controller {
enum EightBitDoControllerType {
EightBitDoControllerType_Zero,
EightBitDoControllerType_Sn30ProXboxCloud,
EightBitDoControllerType_Sn30ProXboxCloudFwV2,
EightBitDoControllerType_Other
};
enum EightBitDoReportFormat {
EightBitDoReportFormat_ZeroV1,
EightBitDoReportFormat_ZeroV2,
@ -32,8 +39,8 @@ namespace ams::controller {
EightBitDoDPadV1_SE = 0x4f51,
EightBitDoDPadV1_S = 0x0051,
EightBitDoDPadV1_SW = 0x5150,
EightBitDoDPadV1_W = 0x0050,
EightBitDoDPadV1_NW = 0x5250,
EightBitDoDPadV1_W = 0x0050,
EightBitDoDPadV1_NW = 0x5250,
};
enum EightBitDoDPadDirectionV2 {
@ -71,7 +78,7 @@ namespace ams::controller {
uint8_t : 2;
uint8_t select : 1;
uint8_t start : 1;
uint8_t : 0;
uint8_t : 0;
}__attribute__((packed));
struct EightBitDoButtonDataV2 {
@ -84,16 +91,31 @@ namespace ams::controller {
uint8_t L1 : 1;
uint8_t R1 : 1;
uint8_t select : 1;
uint8_t : 2;
uint8_t start : 1;
uint8_t home : 1;
uint8_t L3 : 1;
uint8_t R3 : 1;
uint8_t : 1;
union {
struct {
uint8_t select : 1;
uint8_t : 2;
uint8_t start : 1;
uint8_t home : 1;
uint8_t L3 : 1;
uint8_t R3 : 1;
uint8_t : 1;
} v1;
struct {
uint8_t L2 : 1;
uint8_t R2 : 1;
uint8_t select : 1;
uint8_t start : 1;
uint8_t home : 1;
uint8_t L3 : 1;
uint8_t R3 : 1;
uint8_t : 1;
} v2;
};
uint8_t dpad;
}__attribute__((packed));
} __attribute__((packed));
struct EightBitDoInputReport0x01V1 {
uint8_t _unk0[2];
@ -141,20 +163,31 @@ namespace ams::controller {
public:
static constexpr const HardwareID hardware_ids[] = {
{0x05a0, 0x3232}, // 8BitDo Zero
{0x2dc8, 0x2100} // 8BitDo SN30 Pro for Xbox Cloud Gaming
};
{0x2dc8, 0x2100}, // 8BitDo SN30 Pro for Xbox Cloud Gaming
{0x2dc8, 0x2101} // 8BitDo SN30 Pro for Xbox Cloud Gaming (fw 2.00)
};
EightBitDoController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
: EmulatedSwitchController(address, id) {
if ((id.vid == hardware_ids[0].vid) && (id.pid == hardware_ids[0].pid))
m_controller_type = EightBitDoControllerType_Zero;
else if ((id.vid == hardware_ids[1].vid) && (id.pid == hardware_ids[1].pid))
m_controller_type = EightBitDoControllerType_Sn30ProXboxCloud;
else if ((id.vid == hardware_ids[2].vid) && (id.pid == hardware_ids[2].pid))
m_controller_type = EightBitDoControllerType_Sn30ProXboxCloudFwV2;
else
m_controller_type = EightBitDoControllerType_Other;
}
bool SupportsSetTsiCommand(void) { return !((m_id.vid == 0x05a0) && (m_id.pid == 0x3232)); }
bool SupportsSetTsiCommand() { return m_controller_type != EightBitDoControllerType_Zero; }
void ProcessInputData(const bluetooth::HidReport *report) override;
private:
void MapInputReport0x01(const EightBitDoReportData *src, EightBitDoReportFormat fmt);
void MapInputReport0x01(const EightBitDoReportData *src);
void MapInputReport0x03(const EightBitDoReportData *src, EightBitDoReportFormat fmt);
EightBitDoControllerType m_controller_type;
};
}

View file

@ -15,9 +15,7 @@
*/
#include "controller_management.hpp"
#include <stratosphere.hpp>
#include <mutex>
#include <vector>
#include <cstring>
#include "../utils.hpp"
namespace ams::controller {
@ -43,10 +41,6 @@ namespace ams::controller {
os::Mutex g_controller_lock(false);
std::vector<std::shared_ptr<SwitchController>> g_controllers;
inline bool bdcmp(const bluetooth::Address *addr1, const bluetooth::Address *addr2) {
return std::memcmp(addr1, addr2, sizeof(bluetooth::Address)) == 0;
}
}
ControllerType Identify(const bluetooth::DevicesSettings *device) {
@ -292,7 +286,7 @@ namespace ams::controller {
std::scoped_lock lk(g_controller_lock);
for (auto it = g_controllers.begin(); it < g_controllers.end(); ++it) {
if (bdcmp(&(*it)->Address(), address)) {
if (utils::BluetoothAddressCompare(&(*it)->Address(), address)) {
g_controllers.erase(it);
return;
}
@ -303,7 +297,7 @@ namespace ams::controller {
std::scoped_lock lk(g_controller_lock);
for (auto it = g_controllers.begin(); it < g_controllers.end(); ++it) {
if (bdcmp(&(*it)->Address(), address)) {
if (utils::BluetoothAddressCompare(&(*it)->Address(), address)) {
return (*it);
}
}

View file

@ -74,9 +74,7 @@ namespace ams::controller {
class UnknownController : public EmulatedSwitchController {
public:
UnknownController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) {
m_colours.buttons = {0xff, 0x00, 0x00};
};
: EmulatedSwitchController(address, id) { }
};
ControllerType Identify(const bluetooth::DevicesSettings *device);

View file

@ -38,6 +38,17 @@ namespace ams::controller {
0x0A
};
const uint8_t new_player_led_flags[] = {
0x04,
0x02,
0x05,
0x03,
0x07,
0x07,
0x07,
0x07
};
const constexpr RGBColour led_disable = {0x00, 0x00, 0x00};
const RGBColour player_led_colours[] = {
@ -53,12 +64,17 @@ namespace ams::controller {
{0x10, 0x00, 0x30} // purple
};
constexpr uint32_t crc_seed = 0x8C36CCAE; // CRC32 of {0xa2, 0x31} bytes at beginning of output report
}
Result DualsenseController::Initialize(void) {
Result DualsenseController::Initialize() {
R_TRY(this->PushRumbleLedState());
R_TRY(EmulatedSwitchController::Initialize());
// Request controller firmware version info
R_TRY(this->GetVersionInfo(&m_version_info));
// Request motion calibration data from DualSense
R_TRY(this->GetCalibrationData(&m_motion_calibration));
@ -71,23 +87,38 @@ namespace ams::controller {
return this->PushRumbleLedState();
}
Result DualsenseController::CancelVibration(void) {
Result DualsenseController::CancelVibration() {
m_rumble_state.amp_motor_left = 0;
m_rumble_state.amp_motor_right = 0;
return this->PushRumbleLedState();
}
Result DualsenseController::SetPlayerLed(uint8_t led_mask) {
auto config = mitm::GetGlobalConfig();
uint8_t player_number;
R_TRY(LedsMaskToPlayerNumber(led_mask, &player_number));
m_led_flags = player_led_flags[player_number];
uint16_t fw_version = *reinterpret_cast<uint16_t *>(&m_version_info.data[43]);
if (!config->misc.enable_dualsense_player_leds) {
m_led_flags = 0x00;
} else if (fw_version < 0x0282) {
m_led_flags = player_led_flags[player_number];
} else {
m_led_flags = new_player_led_flags[player_number];
}
// Disable LED fade-in
m_led_flags |= 0x20;
RGBColour colour = player_led_colours[player_number];
return this->SetLightbarColour(colour);
}
Result DualsenseController::SetLightbarColour(RGBColour colour) {
auto config = mitm::GetGlobalConfig();
m_led_colour = config->misc.disable_sony_leds ? led_disable : colour;
m_led_colour = config->misc.enable_dualsense_lightbar ? colour : led_disable;
return this->PushRumbleLedState();
}
@ -131,7 +162,7 @@ namespace ams::controller {
if (battery_level > 10)
battery_level = 10;
m_battery = static_cast<uint8_t>(8 * (battery_level + 1) / 10) & 0x0e;
m_battery = static_cast<uint8_t>(8 * (battery_level + 2) / 10) & 0x0e;
m_left_stick.SetData(
static_cast<uint16_t>(stick_scale_factor * src->input0x31.left_stick.x) & 0xfff,
@ -213,6 +244,16 @@ namespace ams::controller {
m_buttons.home = buttons->ps;
}
Result DualsenseController::GetVersionInfo(DualsenseVersionInfo *version_info) {
bluetooth::HidReport output;
R_TRY(this->GetFeatureReport(0x20, &output));
auto response = reinterpret_cast<DualsenseReportData *>(&output.data);
std::memcpy(version_info, &response->feature0x20.version_info, sizeof(DualsenseVersionInfo));
return ams::ResultSuccess();
}
Result DualsenseController::GetCalibrationData(DualsenseImuCalibrationData *calibration) {
bluetooth::HidReport output;
R_TRY(this->GetFeatureReport(0x05, &output));
@ -223,18 +264,30 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result DualsenseController::PushRumbleLedState(void) {
DualsenseOutputReport0x31 report = {0xa2, 0x31, 0x02, 0x03, 0x14, m_rumble_state.amp_motor_right, m_rumble_state.amp_motor_left};
report.data[41] = 0x02;
report.data[44] = 0x02;
report.data[46] = m_led_flags;
report.data[47] = m_led_colour.r;
report.data[48] = m_led_colour.g;
report.data[49] = m_led_colour.b;
report.crc = crc32Calculate(report.data, sizeof(report.data));
Result DualsenseController::PushRumbleLedState() {
auto config = mitm::GetGlobalConfig();
m_output_report.size = sizeof(report) - 1;
std::memcpy(m_output_report.data, &report.data[1], m_output_report.size);
std::scoped_lock lk(m_output_mutex);
DualsenseReportData report = {};
report.id = 0x31;
report.output0x31.data[0] = 0x02;
report.output0x31.data[1] = 0x03;
report.output0x31.data[2] = 0x54;
report.output0x31.data[3] = m_rumble_state.amp_motor_right;
report.output0x31.data[4] = m_rumble_state.amp_motor_left;
report.output0x31.data[37] = 0x08 - config->misc.dualsense_vibration_intensity; // User setting is inverse of how the controller sets intensity
report.output0x31.data[39] = 0x02 | 0x01;
report.output0x31.data[42] = 0x02;
report.output0x31.data[43] = 0x02;
report.output0x31.data[44] = m_led_flags;
report.output0x31.data[45] = m_led_colour.r;
report.output0x31.data[46] = m_led_colour.g;
report.output0x31.data[47] = m_led_colour.b;
report.output0x31.crc = crc32CalculateWithSeed(crc_seed, report.output0x31.data, sizeof(report.output0x31.data));
m_output_report.size = sizeof(report.output0x31) + sizeof(report.id);
std::memcpy(m_output_report.data, &report, m_output_report.size);
return this->WriteDataReport(&m_output_report);
}

View file

@ -86,14 +86,22 @@ namespace ams::controller {
} acc;
} __attribute__((packed));
struct DualsenseVersionInfo {
char data[64];
} __attribute__((packed));
struct DualsenseFeatureReport0x05 {
DualsenseImuCalibrationData calibration;
uint32_t crc;
} __attribute__((packed));
struct DualsenseFeatureReport0x20 {
DualsenseVersionInfo version_info;
} __attribute__((packed));
struct DualsenseOutputReport0x31 {
struct {
uint8_t data[75];
uint8_t data[73];
};
uint32_t crc;
} __attribute__((packed));
@ -133,6 +141,8 @@ namespace ams::controller {
uint8_t id;
union {
DualsenseFeatureReport0x05 feature0x05;
DualsenseFeatureReport0x20 feature0x20;
DualsenseOutputReport0x31 output0x31;
DualsenseInputReport0x01 input0x01;
DualsenseInputReport0x31 input0x31;
};
@ -151,9 +161,9 @@ namespace ams::controller {
, m_led_colour({0, 0, 0})
, m_rumble_state({0, 0}) { }
Result Initialize(void);
Result Initialize();
Result SetVibration(const SwitchRumbleData *rumble_data);
Result CancelVibration(void);
Result CancelVibration();
Result SetPlayerLed(uint8_t led_mask);
Result SetLightbarColour(RGBColour colour);
@ -165,13 +175,15 @@ namespace ams::controller {
void MapButtons(const DualsenseButtonData *buttons);
Result GetVersionInfo(DualsenseVersionInfo *version_info);
Result GetCalibrationData(DualsenseImuCalibrationData *calibration);
Result PushRumbleLedState(void);
Result PushRumbleLedState();
uint8_t m_led_flags;
RGBColour m_led_colour;
DualsenseRumbleData m_rumble_state;
DualsenseVersionInfo m_version_info;
DualsenseImuCalibrationData m_motion_calibration;
};

View file

@ -17,8 +17,6 @@
#include "../mcmitm_config.hpp"
#include <switch.h>
#include <stratosphere.hpp>
#include <cstring>
#include <cmath>
namespace ams::controller {
@ -44,9 +42,11 @@ namespace ams::controller {
{0x10, 0x00, 0x30} // purple
};
constexpr uint32_t crc_seed = 0xB758EC66; // CRC32 of {0xa2, 0x11} bytes at beginning of output report
}
Result Dualshock4Controller::Initialize(void) {
Result Dualshock4Controller::Initialize() {
R_TRY(this->PushRumbleLedState());
R_TRY(EmulatedSwitchController::Initialize());
@ -62,7 +62,7 @@ namespace ams::controller {
return this->PushRumbleLedState();
}
Result Dualshock4Controller::CancelVibration(void) {
Result Dualshock4Controller::CancelVibration() {
m_rumble_state.amp_motor_left = 0;
m_rumble_state.amp_motor_right = 0;
return this->PushRumbleLedState();
@ -77,7 +77,7 @@ namespace ams::controller {
Result Dualshock4Controller::SetLightbarColour(RGBColour colour) {
auto config = mitm::GetGlobalConfig();
m_led_colour = config->misc.disable_sony_leds ? led_disable : colour;
m_led_colour = config->misc.enable_dualshock4_lightbar ? colour : led_disable;
return this->PushRumbleLedState();
}
@ -121,7 +121,7 @@ namespace ams::controller {
if (battery_level > 10)
battery_level = 10;
m_battery = static_cast<uint8_t>(8 * (battery_level + 1) / 10) & 0x0e;
m_battery = static_cast<uint8_t>(8 * (battery_level + 2) / 10) & 0x0e;
m_left_stick.SetData(
static_cast<uint16_t>(stick_scale_factor * src->input0x11.left_stick.x) & 0xfff,
@ -223,15 +223,24 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result Dualshock4Controller::PushRumbleLedState(void) {
Dualshock4OutputReport0x11 report = {0xa2, 0x11, static_cast<uint8_t>(0xc0 | (m_report_rate & 0xff)), 0x20, 0xf3, 0x04, 0x00,
m_rumble_state.amp_motor_right, m_rumble_state.amp_motor_left,
m_led_colour.r, m_led_colour.g, m_led_colour.b
};
report.crc = crc32Calculate(report.data, sizeof(report.data));
Result Dualshock4Controller::PushRumbleLedState() {
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(report) - 1;
std::memcpy(m_output_report.data, &report.data[1], m_output_report.size);
Dualshock4ReportData report = {};
report.id = 0x11;
report.output0x11.data[0] = static_cast<uint8_t>(0xc0 | (m_report_rate & 0xff));
report.output0x11.data[1] = 0x20;
report.output0x11.data[2] = 0xf3;
report.output0x11.data[3] = 0x04;
report.output0x11.data[5] = m_rumble_state.amp_motor_right;
report.output0x11.data[6] = m_rumble_state.amp_motor_left;
report.output0x11.data[7] = m_led_colour.r;
report.output0x11.data[8] = m_led_colour.g;
report.output0x11.data[9] = m_led_colour.b;
report.output0x11.crc = crc32CalculateWithSeed(crc_seed, report.output0x11.data, sizeof(report.output0x11.data));
m_output_report.size = sizeof(report.output0x11) + sizeof(report.id);
std::memcpy(m_output_report.data, &report, m_output_report.size);
return this->WriteDataReport(&m_output_report);
}

View file

@ -132,7 +132,7 @@ namespace ams::controller {
struct Dualshock4OutputReport0x11 {
struct {
uint8_t data[75];
uint8_t data[73];
};
uint32_t crc;
} __attribute__((packed));
@ -201,9 +201,9 @@ namespace ams::controller {
, m_led_colour({0, 0, 0})
, m_rumble_state({0, 0}) { }
Result Initialize(void);
Result Initialize();
Result SetVibration(const SwitchRumbleData *rumble_data);
Result CancelVibration(void);
Result CancelVibration();
Result SetPlayerLed(uint8_t led_mask);
Result SetLightbarColour(RGBColour colour);
@ -217,7 +217,7 @@ namespace ams::controller {
Result GetVersionInfo(Dualshock4VersionInfo *version_info);
Result GetCalibrationData(Dualshock4ImuCalibrationData *calibration);
Result PushRumbleLedState(void);
Result PushRumbleLedState();
Dualshock4ReportRate m_report_rate;
RGBColour m_led_colour;

View file

@ -14,20 +14,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emulated_switch_controller.hpp"
#include "../utils.hpp"
#include "../mcmitm_config.hpp"
namespace ams::controller {
namespace {
// Factory calibration data representing analog stick ranges that span the entire 12-bit data type in x and y
SwitchAnalogStickFactoryCalibration lstick_factory_calib = {0xff, 0xf7, 0x7f, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80};
SwitchAnalogStickFactoryCalibration rstick_factory_calib = {0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0xff, 0xf7, 0x7f};
// Stick parameters data that produce a 12.5% inner deadzone and a 5% outer deadzone (in relation to the full 12 bit range above)
SwitchAnalogStickParameters default_stick_params = {0x0f, 0x30, 0x61, 0x00, 0x31, 0xf3, 0xd4, 0x14, 0x54, 0x41, 0x15, 0x54, 0xc7, 0x79, 0x9c, 0x33, 0x36, 0x63};
// Frequency in Hz rounded to nearest int
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md#frequency-table
const uint16_t rumble_freq_lut[] = {
@ -95,48 +87,34 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result InitializeVirtualSpiFlash(const char *path, size_t size) {
fs::FileHandle file;
// CRC-8 with polynomial 0x7 for NFC/IR packets
uint8_t crc8_lut[] = {
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
};
// Open the file for write
R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write));
ON_SCOPE_EXIT { fs::CloseFile(file); };
uint8_t ComputeCrc8(const void *data, size_t size) {
auto *bytes = reinterpret_cast<const uint8_t *>(data);
// Fill the file with 0xff
uint8_t buff[64];
std::memset(buff, 0xff, sizeof(buff));
unsigned int offset = 0;
while (offset < size) {
size_t write_size = std::min(static_cast<size_t>(size - offset), sizeof(buff));
R_TRY(fs::WriteFile(file, offset, buff, write_size, fs::WriteOption::None));
offset += write_size;
uint8_t crc = 0x00;
for (size_t i = 0; i < size; ++i) {
crc = crc8_lut[crc ^ bytes[i]];
}
// Write default values for data that the console attempts to read in practice
const struct {
SwitchAnalogStickFactoryCalibration lstick_factory_calib;
SwitchAnalogStickFactoryCalibration rstick_factory_calib;
} data1 = { lstick_factory_calib, rstick_factory_calib };
R_TRY(fs::WriteFile(file, 0x603d, &data1, sizeof(data1), fs::WriteOption::None));
const struct {
RGBColour body;
RGBColour buttons;
RGBColour left_grip;
RGBColour right_grip;
} data2 = { {0x32, 0x32, 0x32}, {0xe6, 0xe6, 0xe6}, {0x46, 0x46, 0x46}, {0x46, 0x46, 0x46} };
R_TRY(fs::WriteFile(file, 0x6050, &data2, sizeof(data2), fs::WriteOption::None));
const struct {
SwitchAnalogStickParameters lstick_default_parameters;
SwitchAnalogStickParameters rstick_default_parameters;
} data3 = { default_stick_params, default_stick_params };
R_TRY(fs::WriteFile(file, 0x6086, &data3, sizeof(data3), fs::WriteOption::None));
R_TRY(fs::FlushFile(file));
return ams::ResultSuccess();
return crc;
}
}
@ -148,72 +126,28 @@ namespace ams::controller {
, m_battery(BATTERY_MAX)
, m_led_pattern(0)
, m_gyro_sensitivity(2000)
, m_acc_sensitivity(8000) {
, m_acc_sensitivity(8000)
, m_input_report_mode(0x30) {
this->ClearControllerState();
m_colours.body = {0x32, 0x32, 0x32};
m_colours.buttons = {0xe6, 0xe6, 0xe6};
m_colours.left_grip = {0x46, 0x46, 0x46};
m_colours.right_grip = {0x46, 0x46, 0x46};
auto config = mitm::GetGlobalConfig();
m_enable_rumble = config->general.enable_rumble;
m_enable_motion = config->general.enable_motion;
};
EmulatedSwitchController::~EmulatedSwitchController() {
fs::CloseFile(m_spi_flash_file);
}
Result EmulatedSwitchController::Initialize(void) {
SwitchController::Initialize();
Result EmulatedSwitchController::Initialize() {
R_TRY(SwitchController::Initialize());
// Ensure config directory for this controller exists
std::string path = GetControllerDirectory(&m_address);
R_TRY(fs::EnsureDirectory(path.c_str()));
std::string controller_dir = GetControllerDirectory(&m_address);
R_TRY(fs::EnsureDirectory(controller_dir.c_str()));
// Check if the virtual spi flash file already exists and initialise it if not
path += "/spi_flash.bin";
bool file_exists;
R_TRY(fs::HasFile(&file_exists, path.c_str()));
if (!file_exists) {
auto spi_flash_size = 0x10000;
// Create file representing first 64KB of SPI flash
R_TRY(fs::CreateFile(path.c_str(), spi_flash_size));
R_TRY(m_virtual_memory.Initialize((controller_dir + "/spi_flash.bin").c_str()));
// Initialise the spi flash data
R_TRY(InitializeVirtualSpiFlash(path.c_str(), spi_flash_size));
}
// Open the virtual spi flash file for read and write
R_TRY(fs::OpenFile(std::addressof(m_spi_flash_file), path.c_str(), fs::OpenMode_ReadWrite));
bool mem_initialized;
// Write motion calibration parameters to virtual SPI flash
R_TRY(this->VirtualSpiFlashCheckInitialized(0x6020, sizeof(Switch6AxisCalibrationData), &mem_initialized));
if (!mem_initialized) {
Switch6AxisCalibrationData motion_calibration = {
.acc_bias = {0, 0, 0},
.acc_sensitivity = {16384, 16384, 16384},
.gyro_bias = {0, 0, 0},
.gyro_sensitivity = {13371, 13371, 13371}
};
R_TRY(this->VirtualSpiFlashWrite(0x6020, &motion_calibration, sizeof(motion_calibration)));
}
// Write 6-Axis Horizontal offsets
R_TRY(this->VirtualSpiFlashCheckInitialized(0x6080, sizeof(Switch6AxisHorizontalOffset), &mem_initialized));
if (!mem_initialized) {
Switch6AxisHorizontalOffset offset = {0, 0, 0};
R_TRY(this->VirtualSpiFlashWrite(0x6080, &offset, sizeof(offset)));
}
return ams::ResultSuccess();
}
void EmulatedSwitchController::ClearControllerState(void) {
void EmulatedSwitchController::ClearControllerState() {
std::memset(&m_buttons, 0, sizeof(m_buttons));
m_left_stick.SetData(STICK_ZERO, STICK_ZERO);
m_right_stick.SetData(STICK_ZERO, STICK_ZERO);
@ -223,26 +157,34 @@ namespace ams::controller {
void EmulatedSwitchController::UpdateControllerState(const bluetooth::HidReport *report) {
this->ProcessInputData(report);
m_input_report.size = sizeof(SwitchInputReport0x30) + 1;
auto switch_report = reinterpret_cast<SwitchReportData *>(m_input_report.data);
switch_report->id = 0x30;
switch_report->input0x30.conn_info = (0 << 1) | m_ext_power;
switch_report->input0x30.battery = m_battery | m_charging;
switch_report->input0x30.buttons = m_buttons;
switch_report->input0x30.left_stick = m_left_stick;
switch_report->input0x30.right_stick = m_right_stick;
std::memcpy(&switch_report->input0x30.motion, &m_motion_data, sizeof(m_motion_data));
switch_report->input0x30.timer = (switch_report->input0x30.timer + 1) & 0xff;
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
input_report->id = 0x30;
input_report->timer = (input_report->timer + 1) & 0xff;
input_report->conn_info = (0 << 1) | m_ext_power;
input_report->battery = m_battery | m_charging;
input_report->buttons = m_buttons;
input_report->left_stick = m_left_stick;
input_report->right_stick = m_right_stick;
std::memcpy(&input_report->type0x30.motion_data, &m_motion_data, sizeof(m_motion_data));
m_input_report.size = offsetof(SwitchInputReport, type0x30) + sizeof(input_report->type0x30);
}
Result EmulatedSwitchController::HandleOutputDataReport(const bluetooth::HidReport *report) {
auto report_data = reinterpret_cast<const SwitchReportData *>(&report->data);
auto output_report = reinterpret_cast<const SwitchOutputReport *>(&report->data);
switch (report_data->id) {
switch (output_report->id) {
case 0x01:
R_TRY(this->HandleSubCmdReport(report)); break;
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
R_TRY(this->HandleHidCommand(&output_report->type0x01.hid_command));
break;
case 0x10:
R_TRY(this->HandleRumbleReport(report)); break;
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
break;
case 0x11:
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
R_TRY(this->HandleNfcIrData(output_report->type0x11.nfc_ir_data));
break;
default:
break;
}
@ -250,109 +192,95 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result EmulatedSwitchController::HandleSubCmdReport(const bluetooth::HidReport *report) {
auto report_data = reinterpret_cast<const SwitchReportData *>(&report->data);
Result EmulatedSwitchController::HandleRumbleData(const SwitchRumbleDataEncoded *encoded) {
if (m_enable_rumble) {
SwitchRumbleData rumble_data[2];
DecodeRumbleValues(encoded->left_motor, &rumble_data[0]);
DecodeRumbleValues(encoded->right_motor, &rumble_data[1]);
R_TRY(this->SetVibration(rumble_data));
}
switch (report_data->output0x01.subcmd.id) {
case SubCmd_RequestDeviceInfo:
R_TRY(this->SubCmdRequestDeviceInfo(report));
return ams::ResultSuccess();
}
Result EmulatedSwitchController::HandleHidCommand(const SwitchHidCommand *command) {
switch (command->id) {
case HidCommand_GetDeviceInfo:
R_TRY(this->HandleHidCommandGetDeviceInfo(command));
break;
case SubCmd_SetInputReportMode:
R_TRY(this->SubCmdSetInputReportMode(report));
case HidCommand_SetDataFormat:
R_TRY(this->HandleHidCommandSetDataFormat(command));
break;
case SubCmd_TriggersElapsedTime:
R_TRY(this->SubCmdTriggersElapsedTime(report));
case HidCommand_LRButtonDetection:
R_TRY(this->HandleHidCommandLRButtonDetection(command));
break;
case SubCmd_ResetPairingInfo:
R_TRY(this->SubCmdResetPairingInfo(report));
case HidCommand_ClearPairingInfo:
R_TRY(this->HandleHidCommandClearPairingInfo(command));
break;
case SubCmd_SetShipPowerState:
R_TRY(this->SubCmdSetShipPowerState(report));
case HidCommand_Shipment:
R_TRY(this->HandleHidCommandShipment(command));
break;
case SubCmd_SpiFlashRead:
R_TRY(this->SubCmdSpiFlashRead(report));
case HidCommand_SerialFlashRead:
R_TRY(this->HandleHidCommandSerialFlashRead(command));
break;
case SubCmd_SpiFlashWrite:
R_TRY(this->SubCmdSpiFlashWrite(report));
case HidCommand_SerialFlashWrite:
R_TRY(this->HandleHidCommandSerialFlashWrite(command));
break;
case SubCmd_SpiSectorErase:
R_TRY(this->SubCmdSpiSectorErase(report));
case HidCommand_SerialFlashSectorErase:
R_TRY(this->HandleHidCommandSerialFlashSectorErase(command));
break;
case SubCmd_SetMcuConfig:
R_TRY(this->SubCmdSetMcuConfig(report));
case HidCommand_McuWrite:
R_TRY(this->HandleHidCommandMcuWrite(command));
break;
case SubCmd_SetMcuState:
R_TRY(this->SubCmdSetMcuState(report));
case HidCommand_McuResume:
R_TRY(this->HandleHidCommandMcuResume(command));
break;
case SubCmd_0x24:
R_TRY(this->SubCmd0x24(report));
case HidCommand_McuPollingEnable:
R_TRY(this->HandleHidCommandMcuPollingEnable(command));
break;
case SubCmd_0x25:
R_TRY(this->SubCmd0x25(report));
case HidCommand_McuPollingDisable:
R_TRY(this->HandleHidCommandMcuPollingDisable(command));
break;
case SubCmd_SetPlayerLeds:
R_TRY(this->SubCmdSetPlayerLeds(report));
case HidCommand_SetIndicatorLed:
R_TRY(this->HandleHidCommandSetIndicatorLed(command));
break;
case SubCmd_GetPlayerLeds:
R_TRY(this->SubCmdGetPlayerLeds(report));
case HidCommand_GetIndicatorLed:
R_TRY(this->HandleHidCommandGetIndicatorLed(command));
break;
case SubCmd_SetHomeLed:
R_TRY(this->SubCmdSetHomeLed(report));
case HidCommand_SetNotificationLed:
R_TRY(this->HandleHidCommandSetNotificationLed(command));
break;
case SubCmd_EnableImu:
R_TRY(this->SubCmdEnableImu(report));
case HidCommand_SensorSleep:
R_TRY(this->HandleHidCommandSensorSleep(command));
break;
case SubCmd_SetImuSensitivity:
R_TRY(this->SubCmdSetImuSensitivity(report));
case HidCommand_SensorConfig:
R_TRY(this->HandleHidCommandSensorConfig(command));
break;
case SubCmd_EnableVibration:
R_TRY(this->SubCmdEnableVibration(report));
case HidCommand_MotorEnable:
R_TRY(this->HandleHidCommandMotorEnable(command));
break;
default:
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = report_data->output0x01.subcmd.id,
.id = command->id,
.data = {
.raw = { 0x03 }
}
};
R_TRY(this->FakeSubCmdResponse(&response));
R_TRY(this->FakeHidCommandResponse(&response));
break;
}
// This report can also contain rumble data
if (m_enable_rumble) {
SwitchRumbleData rumble_data[2];
DecodeRumbleValues(report_data->output0x01.rumble.left_motor, &rumble_data[0]);
DecodeRumbleValues(report_data->output0x01.rumble.right_motor, &rumble_data[1]);
R_TRY(this->SetVibration(rumble_data));
}
return ams::ResultSuccess();
}
Result EmulatedSwitchController::HandleRumbleReport(const bluetooth::HidReport *report) {
if (m_enable_rumble) {
auto report_data = reinterpret_cast<const SwitchReportData *>(report->data);
SwitchRumbleData rumble_data[2];
DecodeRumbleValues(report_data->output0x10.rumble.left_motor, &rumble_data[0]);
DecodeRumbleValues(report_data->output0x10.rumble.right_motor, &rumble_data[1]);
R_TRY(this->SetVibration(rumble_data));
}
return ams::ResultSuccess();
}
Result EmulatedSwitchController::SubCmdRequestDeviceInfo(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandGetDeviceInfo(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x82,
.id = SubCmd_RequestDeviceInfo,
.id = command->id,
.data = {
.device_info = {
.get_device_info = {
.fw_ver = {
.major = 0x03,
.minor = 0x48
@ -366,61 +294,55 @@ namespace ams::controller {
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetInputReportMode(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
Result EmulatedSwitchController::HandleHidCommandSetDataFormat(const SwitchHidCommand *command) {
m_input_report_mode = command->set_data_format.id;
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SetInputReportMode
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdTriggersElapsedTime(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandLRButtonDetection(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x83,
.id = SubCmd_TriggersElapsedTime
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdResetPairingInfo(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
Result EmulatedSwitchController::HandleHidCommandClearPairingInfo(const SwitchHidCommand *command) {
R_TRY(m_virtual_memory.SectorErase(0x2000));
R_TRY(this->VirtualSpiFlashSectorErase(0x2000));
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_ResetPairingInfo
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetShipPowerState(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandShipment(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SetShipPowerState,
.id = command->id,
.data = {
.set_ship_power_state = {
.shipment = {
.enabled = false
}
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSpiFlashRead(const bluetooth::HidReport *report) {
Result EmulatedSwitchController::HandleHidCommandSerialFlashRead(const SwitchHidCommand *command) {
// These are read from official Pro Controller
// @ 0x00006000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff <= Serial
// @ 0x00006050: 32 32 32 ff ff ff ff ff ff ff ff ff <= RGB colours (body, buttons, left grip, right grip)
@ -429,104 +351,94 @@ namespace ams::controller {
// @ 0x00008010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff <= User Analog sticks calibration
// @ 0x0000603d: e6 a5 67 1a 58 78 50 56 60 1a f8 7f 20 c6 63 d5 15 5e ff 32 32 32 ff ff ff <= Analog stick factory calibration + face/button colours
// @ 0x00006020: 64 ff 33 00 b8 01 00 40 00 40 00 40 17 00 d7 ff bd ff 3b 34 3b 34 3b 34 <= 6-Axis motion sensor Factory calibration
auto read_addr = command->serial_flash_read.address;
auto read_size = command->serial_flash_read.size;
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
auto read_addr = switch_report->output0x01.subcmd.spi_flash_read.address;
auto read_size = switch_report->output0x01.subcmd.spi_flash_read.size;
SwitchSubcommandResponse response = {
SwitchHidCommandResponse response = {
.ack = 0x90,
.id = SubCmd_SpiFlashRead,
.id = command->id,
.data = {
.spi_flash_read = {
.serial_flash_read = {
.address = read_addr,
.size = read_size
}
}
};
R_TRY(this->VirtualSpiFlashRead(read_addr, response.data.spi_flash_read.data, read_size));
R_TRY(m_virtual_memory.Read(read_addr, response.data.serial_flash_read.data, read_size));
if (read_addr == 0x6050) {
if (ams::mitm::GetSystemLanguage() == 10) {
uint8_t data[] = {0xff, 0xd7, 0x00, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7};
std::memcpy(response.data.spi_flash_read.data, data, sizeof(data));
std::memcpy(response.data.serial_flash_read.data, data, sizeof(data));
}
}
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSpiFlashWrite(const bluetooth::HidReport *report) {
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
auto write_addr = switch_report->output0x01.subcmd.spi_flash_write.address;
auto write_size = switch_report->output0x01.subcmd.spi_flash_write.size;
auto write_data = switch_report->output0x01.subcmd.spi_flash_write.data;
Result EmulatedSwitchController::HandleHidCommandSerialFlashWrite(const SwitchHidCommand *command) {
auto write_addr = command->serial_flash_write.address;
auto write_size = command->serial_flash_write.size;
auto write_data = command->serial_flash_write.data;
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SpiFlashWrite,
.id = command->id,
.data = {
.spi_flash_write = {
.status = this->VirtualSpiFlashWrite(write_addr, write_data, write_size).IsFailure()
.serial_flash_write = {
.status = m_virtual_memory.Write(write_addr, write_data, write_size).IsFailure()
}
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSpiSectorErase(const bluetooth::HidReport *report) {
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
auto erase_addr = switch_report->output0x01.subcmd.spi_flash_sector_erase.address;
Result EmulatedSwitchController::HandleHidCommandSerialFlashSectorErase(const SwitchHidCommand *command) {
auto erase_addr = command->serial_flash_sector_erase.address;
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SpiSectorErase,
.id = command->id,
.data = {
.spi_sector_erase = {
.status = this->VirtualSpiFlashSectorErase(erase_addr).IsFailure()
.serial_flash_sector_erase = {
.status = m_virtual_memory.SectorErase(erase_addr).IsFailure()
}
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmd0x24(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandMcuPollingEnable(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_0x24,
.id = command->id,
.data = {
.raw = { 0x00 }
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmd0x25(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandMcuPollingDisable(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_0x25,
.id = command->id,
.data = {
.raw = { 0x00 }
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetMcuConfig(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandMcuWrite(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0xa0,
.id = SubCmd_SetMcuConfig,
.id = command->id,
.data = {
.raw = {
0x01, 0x00, 0xff, 0x00, 0x03, 0x00, 0x05, 0x01,
@ -538,85 +450,73 @@ namespace ams::controller {
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetMcuState(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandMcuResume(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SetMcuState
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetPlayerLeds(const bluetooth::HidReport *report) {
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
m_led_pattern = switch_report->output0x01.subcmd.set_player_leds.leds;
Result EmulatedSwitchController::HandleHidCommandSetIndicatorLed(const SwitchHidCommand *command) {
m_led_pattern = command->set_indicator_led.leds;
R_TRY(this->SetPlayerLed(m_led_pattern));
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SetPlayerLeds
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdGetPlayerLeds(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandGetIndicatorLed(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_GetPlayerLeds,
.id = command->id,
.data = {
.get_player_leds = {
.get_indicator_led = {
.leds = m_led_pattern
}
}
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetHomeLed(const bluetooth::HidReport *report) {
AMS_UNUSED(report);
const SwitchSubcommandResponse response = {
Result EmulatedSwitchController::HandleHidCommandSetNotificationLed(const SwitchHidCommand *command) {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SetHomeLed
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdEnableImu(const bluetooth::HidReport *report) {
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
if (switch_report->output0x01.subcmd.enable_imu.enabled) {
Result EmulatedSwitchController::HandleHidCommandSensorSleep(const SwitchHidCommand *command) {
if (command->sensor_sleep.disabled) {
if (!m_enable_motion) {
m_gyro_sensitivity = 2000;
m_acc_sensitivity = 8000;
}
}
m_enable_motion = mitm::GetGlobalConfig()->general.enable_motion & switch_report->output0x01.subcmd.enable_imu.enabled;
m_enable_motion = mitm::GetGlobalConfig()->general.enable_motion & command->sensor_sleep.disabled;
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_EnableImu
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdSetImuSensitivity(const bluetooth::HidReport *report) {
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
switch (switch_report->output0x01.subcmd.set_imu_sensitivity.gyro_sensitivity) {
Result EmulatedSwitchController::HandleHidCommandSensorConfig(const SwitchHidCommand *command) {
switch (command->sensor_config.gyro_sensitivity) {
case 0: m_gyro_sensitivity = 250; break;
case 1: m_gyro_sensitivity = 500; break;
case 2: m_gyro_sensitivity = 1000; break;
@ -624,7 +524,7 @@ namespace ams::controller {
AMS_UNREACHABLE_DEFAULT_CASE();
}
switch (switch_report->output0x01.subcmd.set_imu_sensitivity.acc_sensitivity) {
switch (command->sensor_config.acc_sensitivity) {
case 0: m_acc_sensitivity = 8000; break;
case 1: m_acc_sensitivity = 4000; break;
case 2: m_acc_sensitivity = 2000; break;
@ -632,81 +532,76 @@ namespace ams::controller {
AMS_UNREACHABLE_DEFAULT_CASE();
}
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_SetImuSensitivity
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::SubCmdEnableVibration(const bluetooth::HidReport *report) {
auto switch_report = reinterpret_cast<const SwitchReportData *>(&report->data);
Result EmulatedSwitchController::HandleHidCommandMotorEnable(const SwitchHidCommand *command) {
m_enable_rumble = mitm::GetGlobalConfig()->general.enable_rumble & command->motor_enable.enabled;
m_enable_rumble = mitm::GetGlobalConfig()->general.enable_rumble & switch_report->output0x01.subcmd.enable_vibration.enabled;
const SwitchSubcommandResponse response = {
const SwitchHidCommandResponse response = {
.ack = 0x80,
.id = SubCmd_EnableVibration
.id = command->id
};
return this->FakeSubCmdResponse(&response);
return this->FakeHidCommandResponse(&response);
}
Result EmulatedSwitchController::FakeSubCmdResponse(const SwitchSubcommandResponse *response) {
m_input_report.size = sizeof(SwitchInputReport0x21) + 1;
auto report_data = reinterpret_cast<SwitchReportData *>(m_input_report.data);
report_data->id = 0x21;
report_data->input0x21.conn_info = (0 << 1) | m_ext_power;
report_data->input0x21.battery = m_battery | m_charging;
report_data->input0x21.buttons = m_buttons;
report_data->input0x21.left_stick = m_left_stick;
report_data->input0x21.right_stick = m_right_stick;
report_data->input0x21.vibrator = 0;
std::memcpy(&report_data->input0x21.response, response, sizeof(SwitchSubcommandResponse));
report_data->input0x21.timer = os::ConvertToTimeSpan(os::GetSystemTick()).GetMilliSeconds() & 0xff;
Result EmulatedSwitchController::FakeHidCommandResponse(const SwitchHidCommandResponse *response) {
std::scoped_lock lk(m_input_mutex);
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
input_report->id = 0x21;
input_report->timer = (input_report->timer + 1) & 0xff;
input_report->conn_info = (0 << 1) | m_ext_power;
input_report->battery = m_battery | m_charging;
input_report->buttons = m_buttons;
input_report->left_stick = m_left_stick;
input_report->right_stick = m_right_stick;
input_report->vibrator = 0;
std::memcpy(&input_report->type0x21.hid_command_response, response, sizeof(SwitchHidCommandResponse));
m_input_report.size = offsetof(SwitchInputReport, type0x21) + sizeof(input_report->type0x21);
// Write a fake response into the report buffer
return bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report);
}
Result EmulatedSwitchController::VirtualSpiFlashRead(int offset, void *data, size_t size) {
return fs::ReadFile(m_spi_flash_file, offset, data, size);
Result EmulatedSwitchController::HandleNfcIrData(const uint8_t *nfc_ir) {
AMS_UNUSED(nfc_ir);
SwitchNfcIrResponse response = {};
// Send device not ready response for now
response.data[0] = 0xff;
return this->FakeNfcIrResponse(&response);
}
Result EmulatedSwitchController::VirtualSpiFlashWrite(int offset, const void *data, size_t size) {
return fs::WriteFile(m_spi_flash_file, offset, data, size, fs::WriteOption::Flush);
}
Result EmulatedSwitchController::FakeNfcIrResponse(const SwitchNfcIrResponse *response) {
std::scoped_lock lk(m_input_mutex);
Result EmulatedSwitchController::VirtualSpiFlashSectorErase(int offset) {
uint8_t buff[64];
std::memset(buff, 0xff, sizeof(buff));
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
input_report->id = 0x31;
input_report->timer = (input_report->timer + 1) & 0xff;
input_report->conn_info = (0 << 1) | m_ext_power;
input_report->battery = m_battery | m_charging;
input_report->buttons = m_buttons;
input_report->left_stick = m_left_stick;
input_report->right_stick = m_right_stick;
input_report->vibrator = 0;
// Fill sector at offset with 0xff
unsigned int sector_size = 0x1000;
for (unsigned int i = 0; i < (sector_size / sizeof(buff)); ++i) {
R_TRY(fs::WriteFile(m_spi_flash_file, offset, buff, sizeof(buff), fs::WriteOption::None));
offset += sizeof(buff);
}
std::memcpy(&input_report->type0x31.motion_data, &m_motion_data, sizeof(m_motion_data));
std::memcpy(&input_report->type0x31.nfc_ir_response, response, sizeof(SwitchNfcIrResponse));
input_report->type0x31.crc = ComputeCrc8(response, sizeof(SwitchNfcIrResponse));
m_input_report.size = offsetof(SwitchInputReport, type0x31) + sizeof(input_report->type0x31);
R_TRY(fs::FlushFile(m_spi_flash_file));
return ams::ResultSuccess();
}
Result EmulatedSwitchController::VirtualSpiFlashCheckInitialized(int offset, size_t size, bool *is_initialized) {
auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]());
R_TRY(this->VirtualSpiFlashRead(offset, data.get(), size));
for (size_t i = 0; i < size; ++i) {
if (data[i] != 0xff) {
*is_initialized = true;
return ams::ResultSuccess();
}
}
*is_initialized = false;
return ams::ResultSuccess();
// Write a fake response into the report buffer
return bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report);
}
}

View file

@ -15,6 +15,7 @@
*/
#pragma once
#include "switch_controller.hpp"
#include "virtual_spi_flash.hpp"
namespace ams::controller {
@ -26,51 +27,47 @@ namespace ams::controller {
public:
EmulatedSwitchController(const bluetooth::Address *address, HardwareID id);
virtual ~EmulatedSwitchController();
virtual ~EmulatedSwitchController() {};
virtual Result Initialize(void);
bool IsOfficialController(void) { return false; }
virtual Result Initialize();
bool IsOfficialController() { return false; }
//Result HandleDataReportEvent(const bluetooth::HidReportEventInfo *event_info) override;
Result HandleOutputDataReport(const bluetooth::HidReport *report) override;
protected:
void ClearControllerState(void);
void ClearControllerState();
virtual Result SetVibration(const SwitchRumbleData *rumble_data) { AMS_UNUSED(rumble_data); return ams::ResultSuccess(); }
virtual Result CancelVibration(void) { return ams::ResultSuccess(); }
virtual Result CancelVibration() { return ams::ResultSuccess(); }
virtual Result SetPlayerLed(uint8_t led_mask) { AMS_UNUSED(led_mask); return ams::ResultSuccess(); }
void UpdateControllerState(const bluetooth::HidReport *report) override;
virtual void ProcessInputData(const bluetooth::HidReport *report) { AMS_UNUSED(report); }
Result HandleSubCmdReport(const bluetooth::HidReport *report);
Result HandleRumbleReport(const bluetooth::HidReport *report);
Result HandleRumbleData(const SwitchRumbleDataEncoded *encoded);
Result HandleHidCommand(const SwitchHidCommand *command);
Result HandleNfcIrData(const uint8_t *nfc_ir);
Result SubCmdRequestDeviceInfo(const bluetooth::HidReport *report);
Result SubCmdSetInputReportMode(const bluetooth::HidReport *report);
Result SubCmdTriggersElapsedTime(const bluetooth::HidReport *report);
Result SubCmdResetPairingInfo(const bluetooth::HidReport *report);
Result SubCmdSetShipPowerState(const bluetooth::HidReport *report);
Result SubCmdSpiFlashRead(const bluetooth::HidReport *report);
Result SubCmdSpiFlashWrite(const bluetooth::HidReport *report);
Result SubCmdSpiSectorErase(const bluetooth::HidReport *report);
Result SubCmd0x24(const bluetooth::HidReport *report);
Result SubCmd0x25(const bluetooth::HidReport *report);
Result SubCmdSetMcuConfig(const bluetooth::HidReport *report);
Result SubCmdSetMcuState(const bluetooth::HidReport *report);
Result SubCmdSetPlayerLeds(const bluetooth::HidReport *report);
Result SubCmdGetPlayerLeds(const bluetooth::HidReport *report);
Result SubCmdSetHomeLed(const bluetooth::HidReport *report);
Result SubCmdEnableImu(const bluetooth::HidReport *report);
Result SubCmdSetImuSensitivity(const bluetooth::HidReport *report);
Result SubCmdEnableVibration(const bluetooth::HidReport *report);
Result HandleHidCommandGetDeviceInfo(const SwitchHidCommand *command);
Result HandleHidCommandSetDataFormat(const SwitchHidCommand *command);
Result HandleHidCommandLRButtonDetection(const SwitchHidCommand *command);
Result HandleHidCommandClearPairingInfo(const SwitchHidCommand *command);
Result HandleHidCommandShipment(const SwitchHidCommand *command);
Result HandleHidCommandSerialFlashRead(const SwitchHidCommand *command);
Result HandleHidCommandSerialFlashWrite(const SwitchHidCommand *command);
Result HandleHidCommandSerialFlashSectorErase(const SwitchHidCommand *command);
Result HandleHidCommandMcuWrite(const SwitchHidCommand *command);
Result HandleHidCommandMcuResume(const SwitchHidCommand *command);
Result HandleHidCommandMcuPollingEnable(const SwitchHidCommand *command);
Result HandleHidCommandMcuPollingDisable(const SwitchHidCommand *command);
Result HandleHidCommandSetIndicatorLed(const SwitchHidCommand *command);
Result HandleHidCommandGetIndicatorLed(const SwitchHidCommand *command);
Result HandleHidCommandSetNotificationLed(const SwitchHidCommand *command);
Result HandleHidCommandSensorSleep(const SwitchHidCommand *command);
Result HandleHidCommandSensorConfig(const SwitchHidCommand *command);
Result HandleHidCommandMotorEnable(const SwitchHidCommand *command);
Result FakeSubCmdResponse(const SwitchSubcommandResponse *response);
Result VirtualSpiFlashRead(int offset, void *data, size_t size);
Result VirtualSpiFlashWrite(int offset, const void *data, size_t size);
Result VirtualSpiFlashSectorErase(int offset);
Result VirtualSpiFlashCheckInitialized(int offset, size_t size, bool *is_initialized);
Result FakeHidCommandResponse(const SwitchHidCommandResponse *response);
Result FakeNfcIrResponse(const SwitchNfcIrResponse *response);
bool m_charging;
bool m_ext_power;
@ -85,11 +82,12 @@ namespace ams::controller {
uint16_t m_gyro_sensitivity;
uint16_t m_acc_sensitivity;
ProControllerColours m_colours;
uint8_t m_input_report_mode;
bool m_enable_rumble;
bool m_enable_motion;
fs::FileHandle m_spi_flash_file;
VirtualSpiFlash m_virtual_memory;
};
}

View file

@ -118,7 +118,7 @@ namespace ams::controller {
GamesirController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -15,7 +15,6 @@
*/
#include "gamestick_controller.hpp"
#include <stratosphere.hpp>
#include <cstring>
namespace ams::controller {

View file

@ -75,7 +75,7 @@ namespace ams::controller {
HyperkinController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -82,7 +82,7 @@ namespace ams::controller {
LanShenController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -90,7 +90,7 @@ namespace ams::controller {
MocuteController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -74,7 +74,7 @@ namespace ams::controller {
OuyaController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -80,7 +80,7 @@ namespace ams::controller {
PowerAController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -162,7 +162,7 @@ namespace ams::controller {
SteelseriesController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return !(m_id.pid == 0x1412); }
bool SupportsSetTsiCommand() { return !(m_id.pid == 0x1412); }
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -33,20 +33,20 @@ namespace ams::controller {
m_xy[2] = (y >> 4) & 0xff;
}
uint16_t SwitchAnalogStick::GetX(void) {
uint16_t SwitchAnalogStick::GetX() {
return m_xy[0] | ((m_xy[1] & 0xf) << 8);
}
uint16_t SwitchAnalogStick::GetY(void) {
uint16_t SwitchAnalogStick::GetY() {
return (m_xy[1] >> 4) | (m_xy[2] << 4);
}
void SwitchAnalogStick::InvertX(void) {
void SwitchAnalogStick::InvertX() {
m_xy[0] ^= 0xff;
m_xy[1] ^= 0x0f;
}
void SwitchAnalogStick::InvertY(void) {
void SwitchAnalogStick::InvertY() {
m_xy[1] ^= 0xf0;
m_xy[2] ^= 0xff;
}

View file

@ -25,10 +25,10 @@ namespace ams::controller {
void SetData(uint16_t x, uint16_t y);
void SetX(uint16_t x);
void SetY(uint16_t y);
uint16_t GetX(void);
uint16_t GetY(void);
void InvertX(void);
void InvertY(void);
uint16_t GetX();
uint16_t GetY();
void InvertX();
void InvertY();
uint8_t m_xy[3];
};

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "switch_controller.hpp"
#include "../utils.hpp"
#include "../mcmitm_config.hpp"
#include <string>
@ -64,14 +63,14 @@ namespace ams::controller {
return path;
}
Result SwitchController::Initialize(void) {
Result SwitchController::Initialize() {
if (this->HasSetTsiDisableFlag())
m_settsi_supported = false;
return ams::ResultSuccess();
}
bool SwitchController::HasSetTsiDisableFlag(void) {
bool SwitchController::HasSetTsiDisableFlag() {
std::string flag_file = GetControllerDirectory(&m_address) + "/settsi_disable.flag";
bool file_exists;
@ -98,32 +97,24 @@ namespace ams::controller {
}
}
std::scoped_lock lk(m_input_mutex);
this->UpdateControllerState(report);
auto switch_report = reinterpret_cast<SwitchReportData *>(m_input_report.data);
switch (switch_report->id) {
case 0x21:
this->ApplyButtonCombos(&switch_report->input0x21.buttons);
if (switch_report->input0x21.response.id == SubCmd_SpiFlashRead) {
if (switch_report->input0x21.response.data.spi_flash_read.address == 0x6050) {
if (ams::mitm::GetSystemLanguage() == 10) {
uint8_t data[] = {0xff, 0xd7, 0x00, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7};
std::memcpy(switch_report->input0x21.response.data.spi_flash_read.data, data, sizeof(data));
}
}
}
break;
case 0x30:
this->ApplyButtonCombos(&switch_report->input0x30.buttons); break;
case 0x31:
case 0x32:
case 0x33:
case 0x3f:
default:
break;
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
if (input_report->id == 0x21) {
if (input_report->type0x21.hid_command_response.id == HidCommand_SerialFlashRead) {
if (input_report->type0x21.hid_command_response.data.serial_flash_read.address == 0x6050) {
if (ams::mitm::GetSystemLanguage() == 10) {
uint8_t data[] = {0xff, 0xd7, 0x00, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7};
std::memcpy(input_report->type0x21.hid_command_response.data.serial_flash_read.data, data, sizeof(data));
}
}
}
}
this->ApplyButtonCombos(&input_report->buttons);
return bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report);
}

View file

@ -17,9 +17,8 @@
#include "switch_analog_stick.hpp"
#include "../bluetooth_mitm/bluetooth/bluetooth_types.hpp"
#include "../bluetooth_mitm/bluetooth/bluetooth_hid_report.hpp"
#include<queue>
#include "../async/future_response.hpp"
#include <queue>
namespace ams::controller {
@ -56,7 +55,7 @@ namespace ams::controller {
RGBColour left_grip;
RGBColour right_grip;
} __attribute__ ((__packed__));
struct SwitchButtonData {
uint8_t Y : 1;
uint8_t X : 1;
@ -124,6 +123,11 @@ namespace ams::controller {
int16_t z;
} __attribute__((packed));
struct SwitchRumbleDataEncoded {
uint8_t left_motor[4];
uint8_t right_motor[4];
} __attribute__ ((__packed__));
struct SwitchRumbleData {
float high_band_freq;
float high_band_amp;
@ -131,59 +135,62 @@ namespace ams::controller {
float low_band_amp;
} __attribute__ ((__packed__));
enum SubCmdType : uint8_t {
SubCmd_GetControllerState = 0x00,
SubCmd_ManualPair = 0x01,
SubCmd_RequestDeviceInfo = 0x02,
SubCmd_SetInputReportMode = 0x03,
SubCmd_TriggersElapsedTime = 0x04,
SubCmd_GetPageListState = 0x05,
SubCmd_SetHciState = 0x06,
SubCmd_ResetPairingInfo = 0x07,
SubCmd_SetShipPowerState = 0x08,
SubCmd_SpiFlashRead = 0x10,
SubCmd_SpiFlashWrite = 0x11,
SubCmd_SpiSectorErase = 0x12,
SubCmd_ResetMcu = 0x20,
SubCmd_SetMcuConfig = 0x21,
SubCmd_SetMcuState = 0x22,
SubCmd_0x24 = 0x24,
SubCmd_0x25 = 0x25,
SubCmd_0x28 = 0x28,
SubCmd_SetNfcIrMcuData = 0x29,
SubCmd_GetNfcIrMcuData = 0x2b,
SubCmd_SetPlayerLeds = 0x30,
SubCmd_GetPlayerLeds = 0x31,
SubCmd_SetHomeLed = 0x38,
SubCmd_EnableImu = 0x40,
SubCmd_SetImuSensitivity = 0x41,
SubCmd_WriteImuRegisters = 0x42,
SubCmd_ReadImuRegisters = 0x43,
SubCmd_EnableVibration = 0x48,
SubCmd_GetRegulatedVoltage = 0x50,
SubCmd_SetGpioPinValue = 0x51,
SubCmd_GetGpioPinValue = 0x52,
enum HidCommandType : uint8_t {
HidCommand_PairingOut = 0x01,
HidCommand_GetDeviceInfo = 0x02,
HidCommand_SetDataFormat = 0x03,
HidCommand_LRButtonDetection = 0x04,
HidCommand_Page = 0x05,
HidCommand_Reset = 0x06,
HidCommand_ClearPairingInfo = 0x07,
HidCommand_Shipment = 0x08,
HidCommand_SerialFlashRead = 0x10,
HidCommand_SerialFlashWrite = 0x11,
HidCommand_SerialFlashSectorErase = 0x12,
HidCommand_McuReset = 0x20,
HidCommand_McuWrite = 0x21,
HidCommand_McuResume = 0x22,
HidCommand_McuPollingEnable = 0x24,
HidCommand_McuPollingDisable = 0x25,
HidCommand_AttachmentWrite = 0x28,
HidCommand_AttachmentRead = 0x29,
HidCommand_AttachmentEnable = 0x2a,
HidCommand_SetIndicatorLed = 0x30,
HidCommand_GetIndicatorLed = 0x31,
HidCommand_SetNotificationLed = 0x38,
HidCommand_SensorSleep = 0x40,
HidCommand_SensorConfig = 0x41,
HidCommand_SensorWrite = 0x42,
HidCommand_SensorRead = 0x43,
HidCommand_MotorEnable = 0x48,
HidCommand_GetBatteryVoltage = 0x50,
HidCommand_WriteChargeSetting = 0x51,
HidCommand_ReadChargeSetting = 0x52,
};
struct SwitchSubcommand {
struct SwitchHidCommand {
uint8_t id;
union {
uint8_t data[0x26];
struct {
uint8_t id;
} set_data_format;
struct {
uint32_t address;
uint8_t size;
} spi_flash_read;
} serial_flash_read;
struct {
uint32_t address;
uint8_t size;
uint8_t data[];
} spi_flash_write;
} serial_flash_write;
struct {
uint32_t address;
} spi_flash_sector_erase;
} serial_flash_sector_erase;
struct {
union {
@ -194,26 +201,26 @@ namespace ams::controller {
uint8_t leds_on : 4;
};
};
} set_player_leds;
} set_indicator_led;
struct {
bool enabled;
} enable_imu;
bool disabled;
} sensor_sleep;
struct {
uint8_t gyro_sensitivity;
uint8_t acc_sensitivity;
uint8_t gyro_perf_rate;
uint8_t acc_aa_bandwidth;
} set_imu_sensitivity;
} sensor_config;
struct {
bool enabled;
} enable_vibration;
} motor_enable;
};
} __attribute__ ((__packed__));
struct SwitchSubcommandResponse {
struct SwitchHidCommandResponse {
uint8_t ack;
uint8_t id;
union {
@ -229,25 +236,25 @@ namespace ams::controller {
bluetooth::Address address;
uint8_t _unk1; // Always 0x01
uint8_t _unk2; // If 01, colors in SPI are used. Otherwise default ones
} __attribute__ ((__packed__)) device_info;
} __attribute__ ((__packed__)) get_device_info;
struct {
bool enabled;
} set_ship_power_state;
} shipment;
struct {
uint32_t address;
uint8_t size;
uint8_t data[];
} spi_flash_read;
} serial_flash_read;
struct {
uint8_t status;
} spi_flash_write;
} serial_flash_write;
struct {
uint8_t status;
} spi_sector_erase;
} serial_flash_sector_erase;
struct {
union {
@ -258,77 +265,58 @@ namespace ams::controller {
uint8_t leds_on : 4;
};
};
} get_player_leds;
} get_indicator_led;
} data;
} __attribute__ ((__packed__));
struct SwitchOutputReport0x01 {
uint8_t counter;
struct {
uint8_t left_motor[4];
uint8_t right_motor[4];
} rumble;
SwitchSubcommand subcmd;
struct SwitchNfcIrResponse {
uint8_t data[0x138];
} __attribute__ ((__packed__));
struct SwitchOutputReport0x03;
struct SwitchOutputReport0x10 {
uint8_t timer;
struct {
uint8_t left_motor[4];
uint8_t right_motor[4];
} rumble;
}__attribute__ ((__packed__));
struct SwitchOutputReport0x11;
struct SwitchOutputReport0x12;
struct SwitchInputReport0x21 {
uint8_t timer;
uint8_t conn_info : 4;
uint8_t battery : 4;
SwitchButtonData buttons;
SwitchAnalogStick left_stick;
SwitchAnalogStick right_stick;
uint8_t vibrator;
SwitchSubcommandResponse response;
} __attribute__ ((__packed__));
struct SwitchInputReport0x23;
struct SwitchInputReport0x30 {
uint8_t timer;
uint8_t conn_info : 4;
uint8_t battery : 4;
SwitchButtonData buttons;
SwitchAnalogStick left_stick;
SwitchAnalogStick right_stick;
uint8_t vibrator;
// IMU samples at 0, 5 and 10ms
Switch6AxisData motion[3];
} __attribute__ ((__packed__));
struct SwitchInputReport0x31;
struct SwitchInputReport0x32;
struct SwitchInputReport0x33;
struct SwitchInputReport0x3f;
struct SwitchReportData {
struct SwitchInputReport {
uint8_t id;
uint8_t timer;
uint8_t conn_info : 4;
uint8_t battery : 4;
SwitchButtonData buttons;
SwitchAnalogStick left_stick;
SwitchAnalogStick right_stick;
uint8_t vibrator;
union {
SwitchOutputReport0x01 output0x01;
//SwitchOutputReport0x03 output0x03;
SwitchOutputReport0x10 output0x10;
//SwitchOutputReport0x11 output0x11;
//SwitchOutputReport0x12 output0x12;
SwitchInputReport0x21 input0x21;
SwitchInputReport0x30 input0x30;
//SwitchInputReport0x31 input0x31;
//SwitchInputReport0x32 input0x32;
//SwitchInputReport0x33 input0x33;
//SwitchInputReport0x3f input0x3f;
struct {
SwitchHidCommandResponse hid_command_response;
} type0x21;
struct {
uint8_t mcu_fw_data[37];
} type0x23;
struct {
Switch6AxisData motion_data[3]; // IMU samples at 0, 5 and 10ms
} type0x30;
struct {
Switch6AxisData motion_data[3]; // IMU samples at 0, 5 and 10ms
SwitchNfcIrResponse nfc_ir_response;
uint8_t crc;
} type0x31;
};
} __attribute__ ((__packed__));
struct SwitchOutputReport {
uint8_t id;
uint8_t counter;
SwitchRumbleDataEncoded rumble_data;
union {
struct{
SwitchHidCommand hid_command;
} type0x01;
struct {
uint8_t nfc_ir_data[0x16];
} type0x11;
};
} __attribute__ ((__packed__));
@ -338,8 +326,8 @@ namespace ams::controller {
class SwitchController {
public:
static constexpr const HardwareID hardware_ids[] = {
public:
static constexpr const HardwareID hardware_ids[] = {
{0x057e, 0x2006}, // Official Joycon(L) Controller
{0x057e, 0x2007}, // Official Joycon(R) Controller/NES Online Controller
{0x057e, 0x2009}, // Official Switch Pro Controller
@ -351,23 +339,25 @@ namespace ams::controller {
SwitchController(const bluetooth::Address *address, HardwareID id)
: m_address(*address)
, m_id(id)
, m_settsi_supported(true) { }
, m_settsi_supported(true)
, m_input_mutex(false)
, m_output_mutex(false) { }
virtual ~SwitchController() { };
const bluetooth::Address& Address(void) const { return m_address; }
const bluetooth::Address& Address() const { return m_address; }
virtual bool IsOfficialController(void) { return true; }
virtual bool SupportsSetTsiCommand(void) { return m_settsi_supported; }
virtual bool IsOfficialController() { return true; }
virtual bool SupportsSetTsiCommand() { return m_settsi_supported; }
virtual Result Initialize(void);
virtual Result Initialize();
virtual Result HandleDataReportEvent(const bluetooth::HidReportEventInfo *event_info);
virtual Result HandleSetReportEvent(const bluetooth::HidReportEventInfo *event_info);
virtual Result HandleGetReportEvent(const bluetooth::HidReportEventInfo *event_info);
virtual Result HandleOutputDataReport(const bluetooth::HidReport *report);
private:
bool HasSetTsiDisableFlag(void);
bool HasSetTsiDisableFlag();
protected:
Result WriteDataReport(const bluetooth::HidReport *report);
@ -383,7 +373,10 @@ namespace ams::controller {
bool m_settsi_supported;
os::Mutex m_input_mutex;
bluetooth::HidReport m_input_report;
os::Mutex m_output_mutex;
bluetooth::HidReport m_output_report;
std::queue<std::shared_ptr<HidResponse>> m_future_responses;

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2020-2022 ndeadly
*
* 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 "virtual_spi_flash.hpp"
#include "switch_controller.hpp"
namespace ams::controller {
namespace {
constexpr size_t spi_flash_size = 0x10000;
// Factory calibration data representing analog stick ranges that span the entire 12-bit data type in x and y
SwitchAnalogStickFactoryCalibration lstick_factory_calib = {0xff, 0xf7, 0x7f, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80};
SwitchAnalogStickFactoryCalibration rstick_factory_calib = {0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0xff, 0xf7, 0x7f};
// Stick parameters data that produce a 12.5% inner deadzone and a 5% outer deadzone (in relation to the full 12 bit range above)
SwitchAnalogStickParameters default_stick_params = {0x0f, 0x30, 0x61, 0x00, 0x31, 0xf3, 0xd4, 0x14, 0x54, 0x41, 0x15, 0x54, 0xc7, 0x79, 0x9c, 0x33, 0x36, 0x63};
}
VirtualSpiFlash::~VirtualSpiFlash() {
fs::CloseFile(m_virtual_memory_file);
}
Result VirtualSpiFlash::Initialize(const char *path) {
// Check if the virtual spi flash file already exists and create it if not
bool file_exists;
R_TRY(fs::HasFile(&file_exists, path));
if (!file_exists) {
R_TRY(this->CreateFile(path));
}
// Open the virtual spi flash file for read and write
R_TRY(fs::OpenFile(std::addressof(m_virtual_memory_file), path, fs::OpenMode_ReadWrite));
// Make sure that all memory regions that we care about are initialised with defaults
R_TRY(this->EnsureInitialized());
return ams::ResultSuccess();
}
Result VirtualSpiFlash::Read(int offset, void *data, size_t size) {
return fs::ReadFile(m_virtual_memory_file, offset, data, size);
}
Result VirtualSpiFlash::Write(int offset, const void *data, size_t size) {
return fs::WriteFile(m_virtual_memory_file, offset, data, size, fs::WriteOption::Flush);
}
Result VirtualSpiFlash::SectorErase(int offset) {
uint8_t buff[64];
std::memset(buff, 0xff, sizeof(buff));
// Fill sector at offset with 0xff
unsigned int sector_size = 0x1000;
for (unsigned int i = 0; i < (sector_size / sizeof(buff)); ++i) {
R_TRY(fs::WriteFile(m_virtual_memory_file, offset, buff, sizeof(buff), fs::WriteOption::None));
offset += sizeof(buff);
}
R_TRY(fs::FlushFile(m_virtual_memory_file));
return ams::ResultSuccess();
}
Result VirtualSpiFlash::CheckMemoryRegion(int offset, size_t size, bool *is_initialized) {
auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]());
R_TRY(this->Read(offset, data.get(), size));
for (size_t i = 0; i < size; ++i) {
if (data[i] != 0xff) {
*is_initialized = true;
return ams::ResultSuccess();
}
}
*is_initialized = false;
return ams::ResultSuccess();
}
Result VirtualSpiFlash::CreateFile(const char *path) {
// Create file representing first 64KB of SPI flash
R_TRY(fs::CreateFile(path, spi_flash_size));
R_TRY(fs::OpenFile(std::addressof(m_virtual_memory_file), path, fs::OpenMode_Write));
ON_SCOPE_EXIT { fs::CloseFile(m_virtual_memory_file); };
// Fill the file with 0xff
uint8_t buff[64];
std::memset(buff, 0xff, sizeof(buff));
unsigned int offset = 0;
while (offset < spi_flash_size) {
size_t write_size = std::min(static_cast<size_t>(spi_flash_size - offset), sizeof(buff));
R_TRY(fs::WriteFile(m_virtual_memory_file, offset, buff, write_size, fs::WriteOption::None));
offset += write_size;
}
R_TRY(fs::FlushFile(m_virtual_memory_file));
return ams::ResultSuccess();
}
Result VirtualSpiFlash::EnsureMemoryRegion(int offset, const void *data, size_t size) {
bool initialized;
R_TRY(this->CheckMemoryRegion(offset, size, &initialized));
if (!initialized) {
R_TRY(fs::WriteFile(m_virtual_memory_file, offset, data, size, fs::WriteOption::None));
}
return ams::ResultSuccess();
}
Result VirtualSpiFlash::EnsureInitialized() {
const Switch6AxisCalibrationData factory_motion_calibration = {
.acc_bias = {0, 0, 0},
.acc_sensitivity = {16384, 16384, 16384},
.gyro_bias = {0, 0, 0},
.gyro_sensitivity = {13371, 13371, 13371}
};
R_TRY(this->EnsureMemoryRegion(0x6020, &factory_motion_calibration, sizeof(factory_motion_calibration)));
const struct {
SwitchAnalogStickFactoryCalibration lstick_factory_calib;
SwitchAnalogStickFactoryCalibration rstick_factory_calib;
} factory_stick_calibration = { lstick_factory_calib, rstick_factory_calib };
R_TRY(this->EnsureMemoryRegion(0x603d, &factory_stick_calibration, sizeof(factory_stick_calibration)));
const struct {
RGBColour body;
RGBColour buttons;
RGBColour left_grip;
RGBColour right_grip;
} factory_colours = { {0x32, 0x32, 0x32}, {0xe6, 0xe6, 0xe6}, {0x46, 0x46, 0x46}, {0x46, 0x46, 0x46} };
R_TRY(this->EnsureMemoryRegion(0x6050, &factory_colours, sizeof(factory_colours)));
const Switch6AxisHorizontalOffset offset = {0, 0, 0};
R_TRY(this->EnsureMemoryRegion(0x6080, &offset, sizeof(offset)));
const struct {
SwitchAnalogStickParameters lstick_default_parameters;
SwitchAnalogStickParameters rstick_default_parameters;
} factory_stick_parameters = { default_stick_params, default_stick_params };
R_TRY(this->EnsureMemoryRegion(0x6086, &factory_stick_parameters, sizeof(factory_stick_parameters)));
R_TRY(fs::FlushFile(m_virtual_memory_file));
return ams::ResultSuccess();
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020-2022 ndeadly
*
* 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 <stratosphere.hpp>
namespace ams::controller {
class VirtualSpiFlash {
public:
VirtualSpiFlash() {};
~VirtualSpiFlash();
Result Initialize(const char *path);
Result Read(int offset, void *data, size_t size);
Result Write(int offset, const void *data, size_t size);
Result SectorErase(int offset);
Result CheckMemoryRegion(int offset, size_t size, bool *is_initialized);
private:
Result CreateFile(const char *path);
Result EnsureMemoryRegion(int offset, const void *data, size_t size);
Result EnsureInitialized();
fs::FileHandle m_virtual_memory_file;
};
}

View file

@ -17,8 +17,6 @@
#include "controller_utils.hpp"
#include "../async/async.hpp"
#include <stratosphere.hpp>
#include <algorithm>
#include <cstring>
namespace ams::controller {
@ -35,6 +33,25 @@ namespace ams::controller {
constexpr float accel_scale_factor = 65535 / 16000.0f * 1000;
constexpr float gyro_scale_factor = 65535 / (13371 * 360.0f) * 1000;
float CalibrateWeightData(uint16_t x, uint16_t cal_0kg, uint16_t cal_17kg, uint16_t cal_34kg) {
x = util::SwapEndian(x);
if (x < cal_0kg) {
return 0.0f;
} else if (x < cal_17kg) {
return (17.0f * (x - cal_0kg)) / (cal_17kg - cal_0kg);
} else {
return ((17.0f * (x - cal_17kg)) / (cal_34kg - cal_17kg)) + 17.0f;
}
}
float ApplyEasingFunction(float x) {
constexpr float a = 3.0;
constexpr float s = 0.15;
return (std::pow(std::abs(x) + s, a) / (std::pow(std::abs(x) + s, a) + std::pow(1 - (std::abs(x) + s), a))) * (x < 0 ? -1.0f : 1.0f);
}
}
Result WiiController::Initialize() {
@ -45,12 +62,12 @@ namespace ams::controller {
if (m_id.pid == 0x0306) {
// Read the accelerometer calibration from Wiimote memory
R_TRY(this->GetAccelerometerCalibration(&m_accel_calibration));
// Request a status report to check extension controller status
R_TRY(this->QueryStatus());
}
return ams::ResultSuccess();
// Request a status report to check extension controller status
R_TRY(this->QueryStatus());
return ams::ResultSuccess();
}
void WiiController::ProcessInputData(const bluetooth::HidReport *report) {
@ -161,9 +178,15 @@ namespace ams::controller {
m_buttons.A = buttons->A;
m_buttons.B = buttons->B;
// Not the best mapping but at least most buttons are mapped to something when nunchuck is connected.
m_buttons.R = buttons->one;
m_buttons.ZR = buttons->two;
if (m_extension == WiiExtensionController_ClassicPro) {
// Allow buttons one and two to be used for L3/R3 when Classic or Classic Pro controller connected
m_buttons.lstick_press = buttons->one;
m_buttons.rstick_press = buttons->two;
} else {
// Not the best mapping but at least most buttons are mapped to something when nunchuck is connected.
m_buttons.R = buttons->one;
m_buttons.ZR = buttons->two;
}
m_buttons.minus = buttons->minus;
m_buttons.plus = buttons->plus;
@ -223,6 +246,8 @@ namespace ams::controller {
this->MapWiiUProControllerExtension(ext); break;
case WiiExtensionController_TaTaCon:
this->MapTaTaConExtension(ext); break;
case WiiExtensionController_BalanceBoard:
this->MapBalanceBoardExtension(ext); break;
case WiiExtensionController_MotionPlus:
case WiiExtensionController_MotionPlusNunchuckPassthrough:
case WiiExtensionController_MotionPlusClassicControllerPassthrough:
@ -326,6 +351,28 @@ namespace ams::controller {
m_buttons.dpad_right |= !extension_data->L_center;
}
void WiiController::MapBalanceBoardExtension(const uint8_t ext[]) {
auto extension = reinterpret_cast<const BalanceBoardExtensionData *>(ext);
float top_right = CalibrateWeightData(extension->top_right, m_ext_calibration.balance_board.top_right_0kg, m_ext_calibration.balance_board.top_right_17kg, m_ext_calibration.balance_board.top_right_34kg);
float bottom_right = CalibrateWeightData(extension->bottom_right, m_ext_calibration.balance_board.bottom_right_0kg, m_ext_calibration.balance_board.bottom_right_17kg, m_ext_calibration.balance_board.bottom_right_34kg);
float top_left = CalibrateWeightData(extension->top_left, m_ext_calibration.balance_board.top_left_0kg, m_ext_calibration.balance_board.top_left_17kg, m_ext_calibration.balance_board.top_left_34kg);
float bottom_left = CalibrateWeightData(extension->bottom_left, m_ext_calibration.balance_board.bottom_left_0kg, m_ext_calibration.balance_board.bottom_left_17kg, m_ext_calibration.balance_board.bottom_left_34kg);
float total_weight = top_right + bottom_right + top_left + bottom_left;
float x = 0.0f;
float y = 0.0f;
if (total_weight > 1.0f) {
x = ApplyEasingFunction(((top_right + bottom_right) - (top_left + bottom_left)) / total_weight);
y = ApplyEasingFunction(((top_right + top_left) - (bottom_right + bottom_left)) / total_weight);
}
m_left_stick.SetData(
std::clamp<uint16_t>(static_cast<uint16_t>((x * (UINT12_MAX / 2)) + STICK_ZERO), 0, UINT12_MAX),
std::clamp<uint16_t>(static_cast<uint16_t>((y * (UINT12_MAX / 2)) + STICK_ZERO), 0, UINT12_MAX)
);
}
void WiiController::MapMotionPlusExtension(const uint8_t ext[]) {
auto extension_data = reinterpret_cast<const MotionPlusExtensionData *>(ext);
@ -336,17 +383,17 @@ namespace ams::controller {
uint16_t roll_raw = ((extension_data->roll_speed_hi << 8) | extension_data->roll_speed_lo) << 2;
uint16_t yaw_raw = ((extension_data->yaw_speed_hi << 8) | extension_data->yaw_speed_lo) << 2;
uint16_t pitch_0deg = (extension_data->pitch_slow_mode ? m_gyro_calibration.slow.pitch_zero : m_gyro_calibration.fast.pitch_zero);
uint16_t roll_0deg = (extension_data->roll_slow_mode ? m_gyro_calibration.slow.roll_zero : m_gyro_calibration.fast.roll_zero);
uint16_t yaw_0deg = (extension_data->yaw_slow_mode ? m_gyro_calibration.slow.yaw_zero : m_gyro_calibration.fast.yaw_zero);
uint16_t pitch_0deg = (extension_data->pitch_slow_mode ? m_ext_calibration.motion_plus.slow.pitch_zero : m_ext_calibration.motion_plus.fast.pitch_zero);
uint16_t roll_0deg = (extension_data->roll_slow_mode ? m_ext_calibration.motion_plus.slow.roll_zero : m_ext_calibration.motion_plus.fast.roll_zero);
uint16_t yaw_0deg = (extension_data->yaw_slow_mode ? m_ext_calibration.motion_plus.slow.yaw_zero : m_ext_calibration.motion_plus.fast.yaw_zero);
uint16_t pitch_scale = (extension_data->pitch_slow_mode ? m_gyro_calibration.slow.pitch_scale : m_gyro_calibration.fast.pitch_scale);
uint16_t roll_scale = (extension_data->roll_slow_mode ? m_gyro_calibration.slow.roll_scale : m_gyro_calibration.fast.roll_scale);
uint16_t yaw_scale = (extension_data->yaw_slow_mode ? m_gyro_calibration.slow.yaw_scale : m_gyro_calibration.fast.yaw_scale);
uint16_t pitch_scale = (extension_data->pitch_slow_mode ? m_ext_calibration.motion_plus.slow.pitch_scale : m_ext_calibration.motion_plus.fast.pitch_scale);
uint16_t roll_scale = (extension_data->roll_slow_mode ? m_ext_calibration.motion_plus.slow.roll_scale : m_ext_calibration.motion_plus.fast.roll_scale);
uint16_t yaw_scale = (extension_data->yaw_slow_mode ? m_ext_calibration.motion_plus.slow.yaw_scale : m_ext_calibration.motion_plus.fast.yaw_scale);
uint16_t scale_deg_pitch = 6 * (extension_data->pitch_slow_mode ? m_gyro_calibration.slow.degrees_div_6 : m_gyro_calibration.fast.degrees_div_6);
uint16_t scale_deg_roll = 6 * (extension_data->roll_slow_mode ? m_gyro_calibration.slow.degrees_div_6 : m_gyro_calibration.fast.degrees_div_6);
uint16_t scale_deg_yaw = 6 * (extension_data->yaw_slow_mode ? m_gyro_calibration.slow.degrees_div_6 : m_gyro_calibration.fast.degrees_div_6);
uint16_t scale_deg_pitch = 6 * (extension_data->pitch_slow_mode ? m_ext_calibration.motion_plus.slow.degrees_div_6 : m_ext_calibration.motion_plus.fast.degrees_div_6);
uint16_t scale_deg_roll = 6 * (extension_data->roll_slow_mode ? m_ext_calibration.motion_plus.slow.degrees_div_6 : m_ext_calibration.motion_plus.fast.degrees_div_6);
uint16_t scale_deg_yaw = 6 * (extension_data->yaw_slow_mode ? m_ext_calibration.motion_plus.slow.degrees_div_6 : m_ext_calibration.motion_plus.fast.degrees_div_6);
int16_t pitch = static_cast<int16_t>(gyro_scale_factor * (float(pitch_raw - pitch_0deg) / (float(pitch_scale - pitch_0deg) / scale_deg_pitch)));
int16_t roll = -static_cast<int16_t>(gyro_scale_factor * (float(roll_raw - roll_0deg) / (float(roll_scale - roll_0deg) / scale_deg_roll)));
@ -436,7 +483,7 @@ namespace ams::controller {
MC_RUN_ASYNC (
auto mp_status = MotionPlusStatus_None;
if (m_id.pid == 0x0306) {
if ((m_id.pid == 0x0306) && m_enable_motion) {
mp_status = this->GetMotionPlusStatus();
if (!((mp_status == MotionPlusStatus_None) || (mp_status == MotionPlusStatus_Active))) {
@ -462,7 +509,12 @@ namespace ams::controller {
case WiiExtensionController_ClassicPro:
case WiiExtensionController_TaTaCon:
m_orientation = WiiControllerOrientation_Vertical;
R_TRY(this->SetReportMode(0x32));
R_TRY(this->SetReportMode(0x35));
break;
case WiiExtensionController_BalanceBoard:
R_TRY(this->GetBalanceBoardCalibration(&m_ext_calibration.balance_board));
m_orientation = WiiControllerOrientation_Vertical;
R_TRY(this->SetReportMode(0x34));
break;
case WiiExtensionController_WiiUPro:
m_orientation = WiiControllerOrientation_Horizontal;
@ -501,7 +553,7 @@ namespace ams::controller {
R_TRY(this->ActivateMotionPlusClassicPassthrough());
}
// A status report (0x20) will automatically be sent indicating that a normal extension has been plugged in,
// A status report (0x20) will automatically be sent indicating that a normal extension has been plugged in,
// if and only if there was no extension plugged into the MotionPlus pass-through extension port.
R_TRY(this->QueryStatus());
}
@ -520,13 +572,13 @@ namespace ams::controller {
return ams::ResultSuccess();
);
} else {
MC_RUN_ASYNC (
auto mp_status = this->GetMotionPlusStatus();
if (mp_status == MotionPlusStatus_None) {
if ((mp_status == MotionPlusStatus_None) || !m_enable_motion) {
m_extension = WiiExtensionController_None;
m_orientation = WiiControllerOrientation_Horizontal;
R_TRY(this->SetReportMode(0x31));
@ -566,6 +618,8 @@ namespace ams::controller {
return WiiExtensionController_MotionPlusClassicControllerPassthrough;
case 0xa4200111:
return WiiExtensionController_TaTaCon;
case 0xA4200402:
return WiiExtensionController_BalanceBoard;
default:
return WiiExtensionController_Unrecognised;
}
@ -654,7 +708,43 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result WiiController::GetBalanceBoardCalibration(BalanceBoardCalibrationData *calibration) {
struct {
union {
uint8_t raw[0x20];
struct {
uint8_t _unk;
uint8_t battery_reference;
uint8_t pad[2];
BalanceBoardCalibrationData calib;
};
};
} calibration_raw;
R_TRY(this->ReadMemory(0x04a40020, 16, &calibration_raw.raw));
R_TRY(this->ReadMemory(0x04a40030, 16, &calibration_raw.raw[0x10]));
calibration->top_right_0kg = util::SwapEndian(calibration_raw.calib.top_right_0kg);
calibration->bottom_right_0kg = util::SwapEndian(calibration_raw.calib.bottom_right_0kg);
calibration->top_left_0kg = util::SwapEndian(calibration_raw.calib.top_left_0kg);
calibration->bottom_left_0kg = util::SwapEndian(calibration_raw.calib.bottom_left_0kg);
calibration->top_right_17kg = util::SwapEndian(calibration_raw.calib.top_right_17kg);
calibration->bottom_right_17kg = util::SwapEndian(calibration_raw.calib.bottom_right_17kg);
calibration->top_left_17kg = util::SwapEndian(calibration_raw.calib.top_left_17kg);
calibration->bottom_left_17kg = util::SwapEndian(calibration_raw.calib.bottom_left_17kg);
calibration->top_right_34kg = util::SwapEndian(calibration_raw.calib.top_right_34kg);
calibration->bottom_right_34kg = util::SwapEndian(calibration_raw.calib.bottom_right_34kg);
calibration->top_left_34kg = util::SwapEndian(calibration_raw.calib.top_left_34kg);
calibration->bottom_left_34kg = util::SwapEndian(calibration_raw.calib.bottom_left_34kg);
return ams::ResultSuccess();
}
Result WiiController::SetReportMode(uint8_t mode) {
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(WiiOutputReport0x12) + 1;
auto report_data = reinterpret_cast<WiiReportData *>(m_output_report.data);
report_data->id = 0x12;
@ -665,7 +755,9 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result WiiController::QueryStatus(void) {
Result WiiController::QueryStatus() {
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(WiiOutputReport0x15) + 1;
auto report_data = reinterpret_cast<WiiReportData *>(m_output_report.data);
report_data->id = 0x15;
@ -675,12 +767,14 @@ namespace ams::controller {
return ams::ResultSuccess();
}
Result WiiController::WriteMemory(uint32_t write_addr, const void *data, uint8_t size) {
Result WiiController::WriteMemory(uint32_t write_addr, const void *data, uint8_t size) {
os::SleepThread(ams::TimeSpan::FromMilliSeconds(30));
Result result;
auto output = std::make_unique<bluetooth::HidReport>();
std::scoped_lock lk(m_output_mutex);
int attempts = 0;
do {
m_output_report.size = sizeof(WiiOutputReport0x16) + 1;
@ -704,6 +798,8 @@ namespace ams::controller {
Result result;
auto output = std::make_unique<bluetooth::HidReport>();
std::scoped_lock lk(m_output_mutex);
int attempts = 0;
do {
m_output_report.size = sizeof(WiiOutputReport0x17) + 1;
@ -735,7 +831,7 @@ namespace ams::controller {
R_TRY(this->WriteMemory(0x04a600f0, init_data1, sizeof(init_data1)));
// Get the MotionPlus calibration
R_TRY(this->GetMotionPlusCalibration(&m_gyro_calibration));
R_TRY(this->GetMotionPlusCalibration(&m_ext_calibration.motion_plus));
return ams::ResultSuccess();
}
@ -827,6 +923,8 @@ namespace ams::controller {
rumble_data[1].low_band_amp > 0 ||
rumble_data[1].high_band_amp > 0;
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(WiiOutputReport0x10) + 1;
auto report_data = reinterpret_cast<WiiReportData *>(m_output_report.data);
report_data->id = 0x10;
@ -835,9 +933,11 @@ namespace ams::controller {
return this->WriteDataReport(&m_output_report);
}
Result WiiController::CancelVibration(void) {
Result WiiController::CancelVibration() {
m_rumble_state = 0;
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(WiiOutputReport0x10) + 1;
auto report_data = reinterpret_cast<WiiReportData *>(m_output_report.data);
report_data->id = 0x10;
@ -847,6 +947,8 @@ namespace ams::controller {
}
Result WiiController::SetPlayerLed(uint8_t led_mask) {
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(WiiOutputReport0x11) + 1;
auto report_data = reinterpret_cast<WiiReportData *>(m_output_report.data);
report_data->id = 0x11;

View file

@ -39,6 +39,7 @@ namespace ams::controller {
WiiExtensionController_MotionPlusNunchuckPassthrough,
WiiExtensionController_MotionPlusClassicControllerPassthrough,
WiiExtensionController_TaTaCon,
WiiExtensionController_BalanceBoard,
WiiExtensionController_Unrecognised,
};
@ -291,6 +292,33 @@ namespace ams::controller {
uint8_t : 0;
} __attribute__ ((__packed__));
struct BalanceBoardExtensionData {
uint16_t top_right;
uint16_t bottom_right;
uint16_t top_left;
uint16_t bottom_left;
uint8_t temperature;
uint8_t _pad;
uint8_t battery;
} __attribute__ ((__packed__));
struct BalanceBoardCalibrationData {
uint16_t top_right_0kg;
uint16_t bottom_right_0kg;
uint16_t top_left_0kg;
uint16_t bottom_left_0kg;
uint16_t top_right_17kg;
uint16_t bottom_right_17kg;
uint16_t top_left_17kg;
uint16_t bottom_left_17kg;
uint16_t top_right_34kg;
uint16_t bottom_right_34kg;
uint16_t top_left_34kg;
uint16_t bottom_left_34kg;
} __attribute__ ((__packed__));
struct WiiOutputReport0x10 {
uint8_t rumble : 1;
uint8_t : 0;
@ -459,7 +487,7 @@ namespace ams::controller {
public:
static constexpr const HardwareID hardware_ids[] = {
{0x057e, 0x0306}, // Official Wiimote
{0x057e, 0x0306}, // Official Wiimote/Balance Board
{0x057e, 0x0330}, // Official Wii U Pro Controller
};
@ -471,9 +499,9 @@ namespace ams::controller {
, m_mp_extension_flag(false)
, m_mp_state_changing(false) { }
Result Initialize(void);
Result Initialize();
Result SetVibration(const SwitchRumbleData *rumble_data);
Result CancelVibration(void);
Result CancelVibration();
Result SetPlayerLed(uint8_t led_mask);
void ProcessInputData(const bluetooth::HidReport *report) override;
@ -495,6 +523,7 @@ namespace ams::controller {
void MapClassicControllerExtension(const uint8_t ext[]);
void MapWiiUProControllerExtension(const uint8_t ext[]);
void MapTaTaConExtension(const uint8_t ext[]);
void MapBalanceBoardExtension(const uint8_t ext[]);
void MapMotionPlusExtension(const uint8_t ext[]);
void MapNunchuckExtensionPassthroughMode(const uint8_t ext[]);
void MapClassicControllerExtensionPassthroughMode(const uint8_t ext[]);
@ -506,6 +535,8 @@ namespace ams::controller {
Result GetAccelerometerCalibration(WiiAccelerometerCalibrationData *calibration);
Result GetMotionPlusCalibration(MotionPlusCalibrationData *calibration);
Result GetBalanceBoardCalibration(BalanceBoardCalibrationData *calibration);
Result SetReportMode(uint8_t mode);
Result QueryStatus();
@ -529,7 +560,11 @@ namespace ams::controller {
bool m_mp_state_changing;
WiiAccelerometerCalibrationData m_accel_calibration;
MotionPlusCalibrationData m_gyro_calibration;
union {
MotionPlusCalibrationData motion_plus;
BalanceBoardCalibrationData balance_board;
} m_ext_calibration;
};
}

View file

@ -15,7 +15,6 @@
*/
#include "xbox_one_controller.hpp"
#include <stratosphere.hpp>
#include <cstring>
namespace ams::controller {

View file

@ -146,7 +146,7 @@ namespace ams::controller {
XboxOneController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
Result SetVibration(const SwitchRumbleData *rumble_data);
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -27,11 +27,15 @@ namespace ams::controller {
}
Result XiaomiController::Initialize(void) {
Result XiaomiController::Initialize() {
R_TRY(EmulatedSwitchController::Initialize());
std::scoped_lock lk(m_output_mutex);
m_output_report.size = sizeof(init_packet);
std::memcpy(m_output_report.data, init_packet, sizeof(init_packet));
R_TRY(this->WriteDataReport(&m_output_report));
return ams::ResultSuccess();
}

View file

@ -91,9 +91,9 @@ namespace ams::controller {
XiaomiController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
bool SupportsSetTsiCommand(void) { return false; }
bool SupportsSetTsiCommand() { return false; }
Result Initialize(void);
Result Initialize();
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2020-2021 ndeadly
*
* 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 "mc_module.hpp"
#include "mc_service.hpp"
namespace ams::mitm::mc {
namespace {
enum PortIndex {
PortIndex_MissionControl,
PortIndex_Count,
};
constexpr sm::ServiceName MissionControlServiceName = sm::ServiceName::Encode("mc");
using ServerOptions = sf::hipc::DefaultServerManagerOptions;
constexpr size_t MaxSessions = 4;
class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> {
private:
virtual Result OnNeedsToAccept(int port_index, Server *server) override;
};
ServerManager g_server_manager;
sf::UnmanagedServiceObject<IMissionControlInterface, MissionControlService> g_mission_control_service;
ams::Result ServerManager::OnNeedsToAccept(int port_index, Server *server) {
switch (port_index) {
case PortIndex_MissionControl:
return this->AcceptImpl(server, g_mission_control_service.GetShared());
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
const s32 ThreadPriority = 20;
const size_t ThreadStackSize = 0x1000;
alignas(os::ThreadStackAlignment) u8 g_thread_stack[ThreadStackSize];
os::ThreadType g_thread;
void MissionControlThreadFunction(void *) {
R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_MissionControl, MissionControlServiceName, MaxSessions));
g_server_manager.LoopProcess();
}
}
Result Launch() {
R_TRY(os::CreateThread(&g_thread,
MissionControlThreadFunction,
nullptr,
g_thread_stack,
ThreadStackSize,
ThreadPriority
));
os::SetThreadNamePointer(&g_thread, "mc::MissionControlThread");
os::StartThread(&g_thread);
return ams::ResultSuccess();
}
void WaitFinished() {
os::WaitThread(&g_thread);
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2020-2021 ndeadly
*
* 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 <stratosphere.hpp>
namespace ams::mitm::mc {
Result Launch();
void WaitFinished();
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020-2021 ndeadly
*
* 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 "mc_service.hpp"
#include "../mcmitm_version.hpp"
namespace ams::mitm::mc {
Result MissionControlService::GetVersion(sf::Out<u32> version) {
version.SetValue(mc_version);
return ams::ResultSuccess();
}
Result MissionControlService::GetBuildVersionString(sf::Out<mc::VersionString> version) {
std::strncpy(version.GetPointer()->version, mc_build_name, sizeof(mc::VersionString));
return ams::ResultSuccess();
}
Result MissionControlService::GetBuildDateString(sf::Out<ams::mitm::mc::DateString> date) {
std::strncpy(date.GetPointer()->date, mc_build_date, sizeof(mc::DateString));
return ams::ResultSuccess();
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020-2021 ndeadly
*
* 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 <stratosphere.hpp>
#include "mc_types.hpp"
#include "../bluetooth_mitm/bluetooth/bluetooth_types.hpp"
#define AMS_MISSION_CONTROL_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, Result, GetVersion, (sf::Out<u32> version), (version)) \
AMS_SF_METHOD_INFO(C, H, 1, Result, GetBuildVersionString, (sf::Out<ams::mitm::mc::VersionString> version), (version)) \
AMS_SF_METHOD_INFO(C, H, 2, Result, GetBuildDateString, (sf::Out<ams::mitm::mc::DateString> version), (version)) \
AMS_SF_DEFINE_INTERFACE(ams::mitm::mc, IMissionControlInterface, AMS_MISSION_CONTROL_INTERFACE_INFO, 0x30eba3d4)
namespace ams::mitm::mc {
class MissionControlService {
private:
public:
Result GetVersion(sf::Out<u32> version);
Result GetBuildVersionString(sf::Out<ams::mitm::mc::VersionString> version);
Result GetBuildDateString(sf::Out<ams::mitm::mc::DateString> date);
};
static_assert(IsIMissionControlInterface<MissionControlService>);
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2020-2022 ndeadly
*
* 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
namespace ams::mitm::mc {
struct VersionString {
char version[32];
};
struct DateString {
char date[32];
};
}

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include <cstring>
#include "mcmitm_config.hpp"
namespace ams::mitm {
@ -31,7 +30,10 @@ namespace ams::mitm {
.enable_motion = true
},
.misc = {
.disable_sony_leds = false
.enable_dualshock4_lightbar = true,
.enable_dualsense_lightbar = true,
.enable_dualsense_player_leds = true,
.dualsense_vibration_intensity = 4
}
};
@ -42,6 +44,12 @@ namespace ams::mitm {
*out = false;
}
void ParseInt(const char *value, int *out, int min=INT_MIN, int max=INT_MAX) {
int tmp = std::strtol(value, nullptr, 10);
if ((tmp >= min) && (tmp <= max))
*out = tmp;
}
void ParseBluetoothAddress(const char *value, bluetooth::Address *out) {
// Check length of address string is correct
if (std::strlen(value) != 3*sizeof(bluetooth::Address) - 1) return;
@ -78,8 +86,14 @@ namespace ams::mitm {
ParseBluetoothAddress(value, &config->bluetooth.host_address);
}
else if (strcasecmp(section, "misc") == 0) {
if (strcasecmp(name, "disable_sony_leds") == 0)
ParseBoolean(value, &config->misc.disable_sony_leds);
if (strcasecmp(name, "enable_dualshock4_lightbar") == 0)
ParseBoolean(value, &config->misc.enable_dualshock4_lightbar);
else if (strcasecmp(name, "enable_dualsense_lightbar") == 0)
ParseBoolean(value, &config->misc.enable_dualsense_lightbar);
else if (strcasecmp(name, "enable_dualsense_player_leds") == 0)
ParseBoolean(value, &config->misc.enable_dualsense_player_leds);
else if (strcasecmp(name, "dualsense_vibration_intensity") == 0)
ParseInt(value, &config->misc.dualsense_vibration_intensity, 1, 8);
}
else {
return 0;
@ -90,7 +104,7 @@ namespace ams::mitm {
}
void ParseIniConfig(void) {
void ParseIniConfig() {
/* Open the file. */
fs::FileHandle file;
{
@ -114,7 +128,7 @@ namespace ams::mitm {
R_ABORT_UNLESS(setMakeLanguage(language_code, &g_system_language));
}
MissionControlConfig *GetGlobalConfig(void) {
MissionControlConfig *GetGlobalConfig() {
return &g_global_config;
}

View file

@ -29,12 +29,15 @@ namespace ams::mitm {
} bluetooth;
struct {
bool disable_sony_leds;
bool enable_dualshock4_lightbar;
bool enable_dualsense_lightbar;
bool enable_dualsense_player_leds;
int dualsense_vibration_intensity;
} misc;
};
void InitializeConfig();
MissionControlConfig *GetGlobalConfig(void);
MissionControlConfig *GetGlobalConfig();
SetLanguage GetSystemLanguage();
}

View file

@ -21,6 +21,7 @@
#include "bluetooth_mitm/btdrv_mitm_service.hpp"
#include "bluetooth_mitm/bluetoothmitm_module.hpp"
#include "btm_mitm/btmmitm_module.hpp"
#include "mc/mc_module.hpp"
#include "bluetooth_mitm/bluetooth/bluetooth_events.hpp"
#include "bluetooth_mitm/bluetooth/bluetooth_core.hpp"
#include "bluetooth_mitm/bluetooth/bluetooth_hid.hpp"
@ -91,7 +92,7 @@ namespace ams::mitm {
}
void StartInitialize(void) {
void StartInitialize() {
R_ABORT_UNLESS(os::CreateThread(&g_thread,
InitializeThreadFunc,
nullptr,
@ -104,16 +105,18 @@ namespace ams::mitm {
os::StartThread(&g_thread);
}
void WaitInitialized(void) {
void WaitInitialized() {
g_init_event.Wait();
}
void LaunchModules(void) {
void LaunchModules() {
R_ABORT_UNLESS(ams::mitm::bluetooth::Launch());
R_ABORT_UNLESS(ams::mitm::btm::Launch());
R_ABORT_UNLESS(ams::mitm::mc::Launch());
}
void WaitModules(void) {
void WaitModules() {
ams::mitm::mc::WaitFinished();
ams::mitm::btm::WaitFinished();
ams::mitm::bluetooth::WaitFinished();
}

View file

@ -17,8 +17,9 @@
namespace ams::mitm {
void StartInitialize(void);
void WaitInitialized(void);
void LaunchModules(void);
void WaitModules(void);
void StartInitialize();
void WaitInitialized();
void LaunchModules();
void WaitModules();
}

View file

@ -17,7 +17,8 @@
namespace ams::mitm {
extern const char *version_string;
extern const char *build_date;
extern const unsigned int mc_version;
extern const char *mc_build_name;
extern const char *mc_build_date;
}

View file

@ -17,24 +17,8 @@
namespace ams::utils {
namespace {
constexpr inline s32 TargetThreadPriorityRangeSize = svc::LowestThreadPriority - svc::HighestThreadPriority + 1;
constexpr inline s32 UserThreadPriorityOffset = 28;
constexpr inline s32 HighestTargetThreadPriority = 0;
constexpr inline s32 LowestTargetThreadPriority = TargetThreadPriorityRangeSize - 1;
}
s32 ConvertToHorizonPriority(s32 user_priority) {
const s32 horizon_priority = user_priority + UserThreadPriorityOffset;
AMS_ASSERT(HighestTargetThreadPriority <= horizon_priority && horizon_priority <= LowestTargetThreadPriority);
return horizon_priority;
}
s32 ConvertToUserPriority(s32 horizon_priority) {
AMS_ASSERT(HighestTargetThreadPriority <= horizon_priority && horizon_priority <= LowestTargetThreadPriority);
return horizon_priority - UserThreadPriorityOffset;
bool BluetoothAddressCompare(const bluetooth::Address *addr1, const bluetooth::Address *addr2) {
return std::memcmp(addr1, addr2, sizeof(bluetooth::Address)) == 0;
}
Result BluetoothAddressToString(const bluetooth::Address *address, char *out, size_t out_size) {

View file

@ -18,9 +18,7 @@
namespace ams::utils {
s32 ConvertToHorizonPriority(s32 user_priority);
s32 ConvertToUserPriority(s32 horizon_priority);
bool BluetoothAddressCompare(const bluetooth::Address *addr1, const bluetooth::Address *addr2);
Result BluetoothAddressToString(const bluetooth::Address *address, char *out, size_t out_size);
}