Skip to content

Commit

Permalink
👷‍♂️ Prototype driver for Razer Deathadder 3.5G.
Browse files Browse the repository at this point in the history
It is close to working, but it sadly doesn't.
The code to set the LEDs and other parameters should be correct, however it doesn't work because something else is still missing.
  • Loading branch information
hexawyz committed Sep 12, 2024
1 parent a184fe2 commit f1250cf
Show file tree
Hide file tree
Showing 6 changed files with 514 additions and 1 deletion.
140 changes: 139 additions & 1 deletion Docs/Razer DeathAdder 3.5G.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ Example:
01 01 01 03
03 01 01 03

RR ?? ?? LL
RR DD 01 LL

RR is the polling rate, where `01` is 1000Hz, `02` is 500Hz and `03` is 125Hz.
DD is a value indicating the DPI. `01` is either 1800 or 3500 (depending on the mouse model). `02` is 900. `03` is 450.
LL is a bitfield indicating the active lighting zones. `01` is the logo, and `02` is the wheel.

It seems that the report used is not published in the HID descriptor, though.
Expand Down Expand Up @@ -84,3 +85,140 @@ Razer Synapse will mostly send `88883000` IOCTLs with 24 bytes of input and 32 b

Other IOCTLs are sent to the Razer device.

So, apparently, the Razer_ device won't show up if all drivers aren't installed, in which case, IOCTLs to RzUdd will fail.

However, once the drivers are installed, we can send to RzUdd the IOCTL 88883000, which will be used to enumerate the devices. This, it turns out, will allow to find out the name of the `Razer_` device that corresponds to our device.

The input IOCTL packet is composed as such:

````csharp
ulong Unknown0; // IIRC this was supposed some kind of pointer within the driver?
uint Two; // Set to `2`
uint One; // Set to `1`
uint Index; // Zero-based index of the RzUdd device to retrieve
uint Unknown1; // Don't know if it does something here.
````

The output will be composed as such:
````csharp
ulong Unknown0; // Should be zero
uint Handle; // This is the value that will be used in the `Razer_` device name.
uint Unknown1; // 0x0000677e ? This value is supposed to be called `IsBluetooth` from what I found in the dlls, but I can't make sense of the value
ushort ProductId; // Zero-based index of the RzUdd device to retrieve
ushort Unknown2; // Zero
ushort Unknown3; // 0x0001 ?
ushort Unknown4; // 0x0001 ?
uint Unknown5; // 0x00000033 ?
uint Unknown6; // Zero
````

So, basically, we can enumerate on index until we get one product ID matching our device. It none is found, the IOCT will fail with an exception indicating a malfunctioning device.

I tested all I could, and ended up validating my theory about IOCTL `88883020`, which is that is is simply a URB setup data + data fragment, however, I couldn't get it to work.
Apparently, something needs to be initialized within the driver before some of the IOCTLs work. And I couldn't figure out what yet.

Another important IOCTL seem to be 88883004. It is composed of several functions which seem to implement most of the device features such as key remapping on the kernel side.

This IOCTL is likely the key to unlocking IOCTL `88883020`, as it is one of the only three called by Razer Synapse when starting up.
However, which functions are called is a mystery (the last release of IRPMon doesn't allow to see the contents of IOCTLs. It would have been soooooo simple 😢)

Functions:

| 3 | ID | Input Length | Output Length | Description | Parameter 1 | Parameter 2 |
| ---- | ---: | -----------: | ------------: | ----------- | ----------- | ----------- |
| `03` | `01` | `8018` | `10` | CMapping::InternalSetMappingForPauseBreakKey, CMapping::SetMappingsToHW | | |
| `03` | `02` | `18` | `10` | CMapping::ClearAllMappings | | |
| `03` | `03` | `18` | `10` | ? | ? | |
| `03` | `06` | `28` | `1b0` | ? | ? | ?+ |
| `03` | `06` | `28` | `10` | ? | ? | ?+ |
| `03` | `07` | `18` | `10` | ? | | |
| `03` | `08` | `18` | `10` | ? | | |
| `03` | `0B` | `18` | `10` | CSensitivityScaler::Enable | | |
| `03` | `0C` | `18` | `10` | CSensitivityScaler::Disable | | |
| `03` | `0D` | `18` | `10` | CSensitivityScaler::SetActiveLevel | X | Y |
| `03` | `0E` | `18` | `10` | CSensitivityScaler::? (GetActiveLevel?) | | |
| `03` | `0F` | `18` | `10` | ? | | |
| `03` | `11` | `18` | `10` | ? | | |
| `03` | `12` | `18` | `10` | ? | | |
| `03` | `13` | `18` | `10` | ? If flag = 1 / Enable ? | | |
| `03` | `14` | `18` | `10` | ? If flag = 0 / Disable ? | | |
| `03` | `15` | `18` | `10` | ? | | |
| `03` | `16` | `18` | `20` | ? | | |
| `03` | `17` | `18` | `10` | ? | | |
| `03` | `18` | `28` | `10` | ? | ? | ?+ |
| `03` | `1C` | `18` | `10` | ? | | |
| `03` | `1D` | `18` | `10` | ? | | |
| `03` | `1E` | `18` | `10` | ? | | |
| `03` | `1F` | `18` | `10` | ? | | |
| `03` | `20` | `18` | `10` | ? If flag = 1 / Enable ? | | |
| `03` | `21` | `18` | `10` | ? If flag = 0 / Disable ? | | |
| `03` | `22` | `18` | `8010` | CMapping::GetMappings | | |
| `03` | `23` | `18` | `10` | ? | | |
| `03` | `25` | `18` | `10` | ? | | |
| `03` | `26` | `18` | `10` | ? | (OPT) ? | (OPT) ? |
| `03` | `27` | `28` | `10` | CMacroExecute::StartMacro | ? | (18 bytes)? |
| `03` | `28` | `20` | `10` | CMacroExecute::StopMacro | ? | (10 bytes)? |
| `03` | `2A` | `18` | `10` | CSensorConfig::StartCalibration | 1000 | |
| `03` | `2B` | `18` | `10` | CSensorConfig::StartCalibration | ? | |
| `03` | `2C` | (Var) `18` | (Var) `10` | CUsageInfo::GetUsage | ? | ?+ |
| `03` | `2D` | `18` | `10` | CUsageInfo::ResetUsage | | |
| `03` | `2E` | `18` | `7C08` | CSensorConfig::GetResult | | |
| `03` | `2F` | `18` | `10` | ? | (byte) ? | |
| `03` | `30` | `18` | `10` | ? If flag = 1 / Enable ? | | |
| `03` | `31` | `18` | `10` | ? If flag = 0 / Disable ? | | |
| `03` | `32` | `18` | `10` | ? | | |
| `03` | `33` | `18` | `10` | CWireless::IsConnected | | |
| `03` | `34` | (Var) `18` | (Var) `10` | CUsageMapInfo::GetUsage | ? | ?+ |
| `03` | `35` | `18` | `10` | CUsageMapInfo::ResetUsage, CUsageMapInfoEx::ResetUsage | | |
| `03` | `36` | `18` | `10` | (Combo of `36` then `38`) ? | | |
| `03` | `37` | `18` | `10` | (Combo of `37` then `39`) ? | | |
| `03` | `38` | `18` | `10` | (Combo of `36` then `38`) ? | | |
| `03` | `39` | `18` | `10` | (Combo of `37` then `39`) ? | | |
| `03` | `3A` | `18` | `10` | ? | | |
| `03` | `3B` | `18` | `10` | ? | | |
| `03` | `3C` | `18` | `10` | ? | | |
| `03` | `3D` | `18` | `10` | ? | | |
| `03` | `3E` | `18` | `10` | ? | | |
| `03` | `3F` | `18` | `10` | ? | | |
| `03` | `40` | (Var) `18` | (Var) `10` | CUsageMapInfoEx::GetUsage | ? | ?+ |
| `03` | `41` | `1018` | `1010` | GetFromDriverStore, CKeyBoardLayout::GetEditionInfo | | |
| `03` | `42` | `1018` | `10` | CEventManager::SetToDriverStore, CStoreData::SetStoreData | | |
| `03` | `43` | `18` | `10` | ? | ? | |
| `03` | `44` | `10` | `10` | CRzFrameEngine::IsEnable | | |
| `03` | `45` | `10` | `10` | CRzFrameEngine::GetRefreshRate | | |
| `03` | `46` | `18` | `10` | ? | | |
| `03` | `48` | `858` | `10` | (Two message sizes possible) ? | | |
| `03` | `48` | `b18` | `10` | (Two message sizes possible) ? | | |
| `03` | `4A` | `858` | `10` | (Two message sizes possible) ? | | |
| `03` | `4A` | `b18` | `10` | (Two message sizes possible) ? | | |
| `03` | `4B` | `18` | `18` | CRzEffectMgr::EnumRzEffect | | |
| `03` | `4C` | `20` | `10` | CRzEffectMgr::NewRzEffect | | |
| `03` | `4D` | `20` | `10` | CRzEffectMgr::DeleteRzEffect | ? | ?+ |
| `03` | `4E` | `20` | `30` | CRzEffect::GetInfo | ? | ?+ |
| `03` | `4F` | `38` | `10` | ? | ? | ?+ |
| `03` | `51` | `7C28` | `10` | ? | ? | ?+ |
| `03` | `53` | `2E8` | `10` | (Calls of `41`, `53`, `61` are related) | ? | ?+ |
| `03` | `54` | `20` | `10` | ? | ? | ?+ |
| `03` | `55` | `20` | `10` | ? | ? | ?+ |
| `03` | `56` | `20` | `10` | ? | ? | ?+ |
| `03` | `58` | `A8` | `10` | ? | ? | ?+ |
| `03` | `59` | `18` | `10` | ? If flag = 1 / Enable ? | ? | |
| `03` | `5A` | `18` | `10` | ? If flag = 0 / Disable ? | ? | |
| `03` | `5B` | `18` | `10` | ? | ? | |
| `03` | `5C` | `8018` | `10` | ? | ? | ?+ |
| `03` | `5D` | `18` | `10` | ? | ? | |
| `03` | `5F` | `7C28` | `10` | ? | ? | ?+ |
| `03` | `61` | `2E8` | `10` | (Calls of `41`, `53`, `61` are related) ? | ? | ?+ |
| `03` | `66` | `18` | `7C08` | CSensorConfig::GetQResult | | |
| `07` | `05` | `28` | `1B0` | ? | 1 | ?+ |
| `07` | `05` | `28` | `1B0` | CMultipleKeyManager::EnumMultipleKey | 2 | ? |
| `07` | `06` | `28` | `1A8` | ? | 1 | |
| `03` | `07` | `18` | `10` | ? | | |

Another IOCTL seem to be used to receive notifications from the driver: `8888C008`
I'm not entirely sure of how to use it, but from my understanding, the idea is to start an async IOCTL on it, and the IOCTL will complete when an event is ready.

From what I understand, the client side will send out 10 concurrent IOCTLs to the driver, and re-emit one when there is a completion.
Each of those IOCTL calls uses a 64 bytes buffer, presumably the same for input and output. I'm unsure of what would be needed for initialization, but maybe an empty buffer is enough.

Could also be the key to unlock the features of the driver, although I didn't find any confirmation of that and didn't try to use it yet.
19 changes: 19 additions & 0 deletions Exo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exo", "Exo", "{DDDF07E1-D79
src\Exo\ExoMetadata.targets = src\Exo\ExoMetadata.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exo.Devices.Razer.Legacy", "src\Exo\Devices\Exo.Devices.Razer.Legacy\Exo.Devices.Razer.Legacy.csproj", "{A00F0142-5832-4873-8944-874B918419ED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1225,6 +1227,22 @@ Global
{8B863FA5-F652-4A76-AD43-9D9E22B9A83F}.Release|x64.Build.0 = Release|Any CPU
{8B863FA5-F652-4A76-AD43-9D9E22B9A83F}.Release|x86.ActiveCfg = Release|Any CPU
{8B863FA5-F652-4A76-AD43-9D9E22B9A83F}.Release|x86.Build.0 = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|arm64.ActiveCfg = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|arm64.Build.0 = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|x64.ActiveCfg = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|x64.Build.0 = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|x86.ActiveCfg = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Debug|x86.Build.0 = Debug|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|Any CPU.Build.0 = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|arm64.ActiveCfg = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|arm64.Build.0 = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|x64.ActiveCfg = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|x64.Build.0 = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|x86.ActiveCfg = Release|Any CPU
{A00F0142-5832-4873-8944-874B918419ED}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1299,6 +1317,7 @@ Global
{8B863FA5-F652-4A76-AD43-9D9E22B9A83F} = {59B8EF45-EA19-477D-B57C-5783B7B1C5CF}
{062C0665-616A-4FB4-B0DA-2EEBE7FDA7B9} = {DDDF07E1-D796-4821-AF60-74E655366233}
{F25C31A1-5D36-46E4-AE9B-9059FA393F4C} = {DDDF07E1-D796-4821-AF60-74E655366233}
{A00F0142-5832-4873-8944-874B918419ED} = {B93F3F85-DA80-48CF-A549-9217DD34DD11}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E9FEED7F-27EC-4720-9CAB-C2E6FDD8556D}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableDynamicLoading>true</EnableDynamicLoading>
<IsExoPluginAssembly>true</IsExoPluginAssembly>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\Exo.Core\Exo.Core.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="..\..\..\DeviceTools\DeviceTools.Core\DeviceTools.Core.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="..\..\..\DeviceTools\DeviceTools.HumanInterfaceDevices\DeviceTools.HumanInterfaceDevices.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="..\..\..\DeviceTools\DeviceTools.Bluetooth\DeviceTools.Bluetooth.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="..\..\Discovery\Exo.Discovery.Hid\Exo.Discovery.Hid.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="..\..\Discovery\Exo.Discovery.System\Exo.Discovery.System.csproj" Private="false" ExcludeAssets="runtime" />
</ItemGroup>

</Project>
Loading

0 comments on commit f1250cf

Please sign in to comment.