feat: implement "Pro-Controller" mode (#26)
All checks were successful
Publish stable release / build (push) Successful in 7m38s
Publish nightly release / build (push) Successful in 10m3s

Based on the pro-controller code from https://github.com/HandHeldLegend/HOJA-LIB-RP2040

Author has given their consent for this to be GPL'd:

![image](/attachments/c2551d3a-9b35-4c67-ad28-3778f9e04e29)

Reviewed-on: #26
This commit is contained in:
Naxdy 2024-10-14 19:55:54 +00:00
parent 4e1af50ebd
commit 8f8d47704d
Signed by: git.naxdy.org
GPG key ID: 05379DCFAACD8AC2
15 changed files with 1505 additions and 631 deletions

12
.changelogs/v1.2.0.md Normal file
View file

@ -0,0 +1,12 @@
This release introduces a major new feature to combat a regression introduced in Switch firmware 19.0.0. For those unaware, Switch firmware 19.0.0 breaks compatibility with GCC adapters, including NaxGCC, which advertises itself as a GCC adapter.
Starting with this version, you will be able to connect NaxGCC in "Pro-Controller Mode" by pressing and holding `Start` while plugging it in. This will cause the NaxGCC to advertise itself as a Nintendo Switch Pro Controller, and therefore be unaffected by the bug in the latest Switch firmware. All input consistency modes remain fully functional while in this mode, and your settings (including calibration) carry over as well.
While in Pro-Controller Mode, pressing `Z+Start` will act like the home button on a regular Pro Controller. Additionally, pressing `L` will press both `L` and `ZL` at the same time (since the GCC only has one left shoulder button). This is useful if you want to map things like jump (short-hop macro) or shield (prevent rolling) to it.
> [!NOTE]
> As of this version, rumble will _not_ work while in Pro Controller Mode.
---
To update your firmware, plug in your controller to your computer while keeping the `A+X+Y` buttons held. Then drag & drop the `.uf2` file (found below, under Downloads) onto the storage device that appears.

View file

@ -10,8 +10,19 @@ jobs:
check:
runs-on: nix-flakes
steps:
- uses: actions/checkout@v4
- name: Set up attic binary cache
uses: https://git.naxdy.org/NaxdyOrg/attic-action@v0.3
with:
endpoint: "${{ vars.BINARY_CACHE_URL }}"
token: ""
cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
skip-push: true
- name: Run Clippy
run: |
nix develop . --command cargo clippy -- -Dwarnings
nix build .#clippy --print-build-logs -j auto

View file

@ -20,9 +20,15 @@ jobs:
token: "${{ secrets.PUBLIC_BINARY_CACHE_AUTH_KEY }}"
cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
- uses: actions/checkout@v4
- name: Run Clippy
run: |
nix build .#clippy --print-build-logs -j auto
- name: Build firmware image
run: |
nix build .# -o dist --print-build-logs
- name: (Re-)generate tag
run: |
git config --global user.email "noreply@naxdy.org"
@ -32,6 +38,7 @@ jobs:
git tag nightly -m "Nightly Release"
git checkout nightly
git push --set-upstream origin nightly --force
- name: Publish nightly release
uses: https://gitea.com/actions/gitea-release-action@v1.3.0
with:

163
Cargo.lock generated
View file

@ -2,6 +2,18 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.2"
@ -77,6 +89,12 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitfield"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -109,9 +127,9 @@ checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
[[package]]
name = "byteorder"
version = "1.4.3"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
@ -136,7 +154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [
"bare-metal",
"bitfield",
"bitfield 0.13.2",
"embedded-hal 0.2.7",
"volatile-register",
]
@ -309,7 +327,8 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "embassy-embedded-hal"
version = "0.1.0"
version = "0.2.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"defmt",
"embassy-futures",
@ -325,7 +344,8 @@ dependencies = [
[[package]]
name = "embassy-executor"
version = "0.5.0"
version = "0.6.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"cortex-m",
"critical-section",
@ -338,7 +358,8 @@ dependencies = [
[[package]]
name = "embassy-executor-macros"
version = "0.4.0"
version = "0.5.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"darling",
"proc-macro2",
@ -349,13 +370,15 @@ dependencies = [
[[package]]
name = "embassy-futures"
version = "0.1.1"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"defmt",
]
[[package]]
name = "embassy-hal-internal"
version = "0.1.0"
version = "0.2.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"cortex-m",
"critical-section",
@ -366,10 +389,12 @@ dependencies = [
[[package]]
name = "embassy-net-driver"
version = "0.2.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
[[package]]
name = "embassy-net-driver-channel"
version = "0.2.0"
version = "0.3.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"embassy-futures",
"embassy-net-driver",
@ -378,7 +403,8 @@ dependencies = [
[[package]]
name = "embassy-rp"
version = "0.1.0"
version = "0.2.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"atomic-polyfill",
"cfg-if",
@ -403,7 +429,6 @@ dependencies = [
"embedded-storage",
"embedded-storage-async",
"fixed",
"futures",
"nb 1.1.0",
"pio",
"pio-proc",
@ -414,7 +439,8 @@ dependencies = [
[[package]]
name = "embassy-sync"
version = "0.5.0"
version = "0.6.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"cfg-if",
"critical-section",
@ -426,7 +452,8 @@ dependencies = [
[[package]]
name = "embassy-time"
version = "0.3.0"
version = "0.3.2"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"cfg-if",
"critical-section",
@ -444,6 +471,7 @@ dependencies = [
[[package]]
name = "embassy-time-driver"
version = "0.1.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"document-features",
]
@ -451,10 +479,12 @@ dependencies = [
[[package]]
name = "embassy-time-queue-driver"
version = "0.1.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
[[package]]
name = "embassy-usb"
version = "0.1.0"
version = "0.3.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"defmt",
"embassy-futures",
@ -469,6 +499,7 @@ dependencies = [
[[package]]
name = "embassy-usb-driver"
version = "0.1.0"
source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
dependencies = [
"defmt",
]
@ -630,59 +661,12 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
@ -696,8 +680,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-macro",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
@ -733,6 +715,15 @@ dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -768,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.3",
]
[[package]]
@ -875,7 +866,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "naxgcc-fw"
version = "1.1.1"
version = "1.2.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
@ -1453,15 +1444,19 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "usb-device"
version = "0.2.9"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508"
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
dependencies = [
"heapless",
"portable-atomic",
]
[[package]]
name = "usbd-hid"
version = "0.6.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132"
checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c"
dependencies = [
"serde",
"ssmarshal",
@ -1471,20 +1466,22 @@ dependencies = [
[[package]]
name = "usbd-hid-descriptors"
version = "0.1.2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1"
checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed"
dependencies = [
"bitfield",
"bitfield 0.14.0",
]
[[package]]
name = "usbd-hid-macros"
version = "0.6.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2"
checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38"
dependencies = [
"byteorder",
"hashbrown 0.13.2",
"log",
"proc-macro2",
"quote",
"serde",
@ -1687,3 +1684,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]

View file

@ -1,6 +1,6 @@
[package]
name = "naxgcc-fw"
version = "1.1.1"
version = "1.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -9,33 +9,33 @@ edition = "2021"
embassy-time = { version = "0.3.0", features = [
"defmt",
"defmt-timestamp-uptime",
], path = "lib/embassy-rs/embassy-time" }
embassy-embedded-hal = { version = "0.1.0", features = [
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
embassy-embedded-hal = { version = "0.2.0", features = [
"defmt",
], path = "lib/embassy-rs/embassy-embedded-hal" }
embassy-sync = { version = "0.5.0", features = [
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
embassy-sync = { version = "0.6.0", features = [
"defmt",
], path = "lib/embassy-rs/embassy-sync" }
embassy-executor = { version = "0.5.0", features = [
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
embassy-executor = { version = "0.6.0", features = [
"task-arena-size-32768",
"arch-cortex-m",
"executor-thread",
"executor-interrupt",
"defmt",
"integrated-timers",
], path = "lib/embassy-rs/embassy-executor" }
embassy-rp = { version = "0.1.0", features = [
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
embassy-rp = { version = "0.2.0", features = [
"defmt",
"unstable-pac",
"time-driver",
"critical-section-impl",
], path = "lib/embassy-rs/embassy-rp" }
embassy-usb = { version = "0.1.0", features = [
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
embassy-usb = { version = "0.3.0", features = [
"defmt",
], path = "lib/embassy-rs/embassy-usb" }
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
embassy-futures = { version = "0.1.0", features = [
"defmt",
], path = "lib/embassy-rs/embassy-futures" }
], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
defmt = "0.3"
defmt-rtt = "0.4"
fixed = "1.23.1"
@ -48,7 +48,7 @@ libm = { version = "0.2.8" }
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
packed_struct = { version = "0.10.1", default_features = false }
packed_struct = { version = "0.10.1", default-features = false }
format_no_std = "1.0.2"
rand = { version = "0.8.5", default-features = false }
@ -79,14 +79,14 @@ overflow-checks = false
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
opt-level = 3
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
opt-level = 3
overflow-checks = false
# cargo test
@ -106,12 +106,3 @@ debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 3
# [patch.crates-io]
# embassy-rp = { path = "lib/embassy-rs/embassy-rp" }
# embassy-time = { path = "lib/embassy-rs/embassy-time" }
# embassy-embedded-hal = { path = "lib/embassy-rs/embassy-embedded-hal" }
# embassy-usb = { path = "lib/embassy-rs/embassy-usb" }
# embassy-sync = { path = "lib/embassy-rs/embassy-sync" }
# embassy-executor = { path = "lib/embassy-rs/embassy-executor" }
# embassy-futures = { path = "lib/embassy-rs/embassy-futures" }

View file

@ -1,22 +1,5 @@
{
"nodes": {
"embassy-rs-patched": {
"flake": false,
"locked": {
"lastModified": 1710754402,
"narHash": "sha256-HyTw5VQXlSqz9UOq0Dc6G/NWNzQOPXf3PNWffMkfLC4=",
"ref": "naxgcc-fw",
"rev": "2ee4657727b9679998d941de00b43e1754f570bf",
"revCount": 6754,
"type": "git",
"url": "https://gitea@git.naxdy.org/NaxdyOrg/embassy"
},
"original": {
"ref": "naxgcc-fw",
"type": "git",
"url": "https://gitea@git.naxdy.org/NaxdyOrg/embassy"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
@ -35,24 +18,6 @@
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": [
@ -75,27 +40,27 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1710283656,
"narHash": "sha256-nI+AOy4uK6jLGBi9nsbHjL1EdSIzoo8oa+9oeVhbyFc=",
"lastModified": 1728740863,
"narHash": "sha256-u+rxA79a0lyhG+u+oPBRtTDtzz8kvkc9a6SWSt9ekVc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "51063ed4f2343a59fdeebb279bb81d87d453942b",
"rev": "a3f9ad65a0bf298ed5847629a57808b97e6e8077",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-23.11",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1706487304,
"narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
"lastModified": 1718428119,
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
"type": "github"
},
"original": {
@ -107,7 +72,6 @@
},
"root": {
"inputs": {
"embassy-rs-patched": "embassy-rs-patched",
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs",
@ -116,15 +80,14 @@
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1710382258,
"narHash": "sha256-2FW1q+o34VBweYQiEkRaSEkNMq3ecrn83VzETeGiVbY=",
"lastModified": 1728700003,
"narHash": "sha256-Ox1pvEHxLK6lAdaKQW21Zvk65SPDag+cD8YA444R/og=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8ce81e71ab04a7e906fae62da086d6ee5d6cfc21",
"rev": "fc1e58ebabe0cef4442eedea07556ff0c9eafcfe",
"type": "github"
},
"original": {
@ -147,21 +110,6 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View file

@ -2,17 +2,12 @@
description = "Firmware for the NaxGCC";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-23.11";
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-24.05";
rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
embassy-rs-patched = {
url = "git+https://gitea@git.naxdy.org/NaxdyOrg/embassy?ref=naxgcc-fw";
flake = false;
};
naersk = {
url = "github:nmattia/naersk";
inputs.nixpkgs.follows = "nixpkgs";
@ -25,7 +20,6 @@
, rust-overlay
, flake-utils
, naersk
, embassy-rs-patched
}: (flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
@ -51,12 +45,6 @@
};
CARGO_BUILD_TARGET = "thumbv6m-none-eabi";
prepCmd = ''
mkdir -p lib
rm lib/embassy-rs || true
ln -s "${embassy-rs-patched}" lib/embassy-rs
'';
in
{
packages.default = self.packages.${system}.naxgcc-fw-uf2;
@ -66,20 +54,26 @@
${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2
'';
packages.naxgcc-fw = naersk_lib.buildPackage {
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
packages.naxgcc-fw = pkgs.callPackage
({ mode ? "build" }: naersk_lib.buildPackage {
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
prePatch = prepCmd;
inherit mode;
src = self;
src = self;
cargoBuildOptions = _orig: _orig ++ [
"--target=${CARGO_BUILD_TARGET}"
];
cargoBuildOptions = _orig: _orig ++ [
"--target=${CARGO_BUILD_TARGET}"
];
# if a tree falls in the forest and no one is around to hear it, does it make a sound?
DEFMT_LOG = "off";
# if a tree falls in the forest and no one is around to hear it, does it make a sound?
DEFMT_LOG = "off";
})
{ };
packages.clippy = self.packages.${system}.naxgcc-fw.override {
mode = "clippy";
};
devShells.default = pkgs.mkShell {
@ -92,8 +86,6 @@
DEFMT_LOG = "debug";
inherit CARGO_BUILD_TARGET;
shellHook = prepCmd;
};
}));
}

View file

@ -16,8 +16,8 @@ use packed_struct::{
};
use crate::{
gcc_hid::{Buttons1, Buttons2, MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair},
hid::gcc::{GcButtons1, GcButtons2, GcState},
input::{
read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED,
},
@ -26,6 +26,7 @@ use crate::{
LinearizedCalibration, NotchCalibration, NotchStatus, CALIBRATION_ORDER,
NOTCH_ADJUSTMENT_ORDER, NO_OF_ADJ_NOTCHES, NO_OF_CALIBRATION_POINTS, NO_OF_NOTCHES,
},
usb_comms::{MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
ADDR_OFFSET, FLASH_SIZE,
};
@ -36,7 +37,7 @@ use embassy_sync::{
};
use embassy_time::Timer;
use crate::{gcc_hid::GcReport, input::CHANNEL_GCC_STATE};
use crate::input::CHANNEL_GCC_STATE;
/// Whether we are currently calibrating the sticks. Updates are dispatched when the status changes.
/// Initial status is assumed to be false.
@ -78,7 +79,7 @@ const MAX_ANALOG_SCALER: u8 = 125;
/// a certain mode.
#[derive(Default, Debug, Clone, Format)]
pub struct OverrideGcReportInstruction {
pub report: GcReport,
pub report: GcState,
pub duration_ms: u64,
}
@ -563,6 +564,15 @@ pub enum InputConsistencyMode {
PC = 3,
}
/// Not saved, but set upon plugging in the controller.
#[derive(Debug, Clone, Copy, Format, PrimitiveEnum_u8, PartialEq, Eq)]
pub enum ControllerMode {
/// Advertise itself as a GCC adapter with 1 controller (itself) connected.
GcAdapter = 0,
/// Pretend to be a Nintendo Switch Pro Controller connected via USB.
Procon = 1,
}
#[derive(Debug, Clone, Format, PackedStruct)]
#[packed_struct(endian = "msb")]
pub struct ControllerConfig {
@ -637,6 +647,8 @@ impl ControllerConfig {
/// Trait for providing button presses, used in the calibration process.
trait ButtonPressProvider {
/// Wait for a single button press.
// TODO: remove allow once this is used somewhere
#[allow(dead_code)]
async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons);
/// Wait for a single button release.
@ -670,7 +682,7 @@ trait ButtonPressProvider {
}
impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPressProvider
for Subscriber<'a, T, GcReport, I, J, K>
for Subscriber<'a, T, GcState, I, J, K>
{
async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons) {
loop {
@ -770,7 +782,7 @@ impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPres
}
pub fn is_awaitable_button_pressed(
report: &GcReport,
report: &GcState,
button_to_wait_for: &AwaitableButtons,
) -> bool {
match button_to_wait_for {
@ -1136,7 +1148,7 @@ async fn configuration_main_loop<
>(
current_config: &ControllerConfig,
flash: &mut Flash<'static, FLASH, Async, FLASH_SIZE>,
gcc_subscriber: &mut Subscriber<'a, M, GcReport, C, S, P>,
gcc_subscriber: &mut Subscriber<'a, M, GcState, C, S, P>,
) -> ControllerConfig {
let mut final_config = current_config.clone();
let config_options = [
@ -1194,15 +1206,15 @@ async fn configuration_main_loop<
// exit
0 => {
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1222,15 +1234,15 @@ async fn configuration_main_loop<
// calibrate lstick
1 => {
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1251,15 +1263,15 @@ async fn configuration_main_loop<
// calibrate rstick
2 => {
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1310,15 +1322,15 @@ async fn configuration_main_loop<
.clamp(-ABS_MAX_SNAPBACK, ABS_MAX_SNAPBACK);
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1396,15 +1408,15 @@ async fn configuration_main_loop<
.clamp(0, MAX_WAVESHAPING) as u8;
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1482,15 +1494,15 @@ async fn configuration_main_loop<
.clamp(0, MAX_SMOOTHING) as u8;
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1557,15 +1569,15 @@ async fn configuration_main_loop<
.clamp(-1, MAX_CARDINAL_SNAP);
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1613,15 +1625,15 @@ async fn configuration_main_loop<
as u8;
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1661,16 +1673,16 @@ async fn configuration_main_loop<
SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(*to_adjust);
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
button_z: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1697,15 +1709,15 @@ async fn configuration_main_loop<
};
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1731,15 +1743,15 @@ async fn configuration_main_loop<
// display waveshaping values on both sticks
38 => {
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1757,15 +1769,15 @@ async fn configuration_main_loop<
// display stick smoothing values on both sticks
39 => {
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1783,15 +1795,15 @@ async fn configuration_main_loop<
// display snapback values on both sticks
40 => {
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1854,15 +1866,15 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
info!("Entering config mode.");
override_gcc_state_and_wait(&OverrideGcReportInstruction {
report: GcReport {
report: GcState {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_l: true,
button_r: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,

198
src/hid/gcc.rs Normal file
View file

@ -0,0 +1,198 @@
use defmt::{info, trace, Format};
use embassy_usb::{
class::hid::{ReportId, RequestHandler},
control::OutResponse,
};
use crate::usb_comms::{HidReportBuilder, SIGNAL_RUMBLE};
use packed_struct::{derive::PackedStruct, PackedStruct};
#[rustfmt::skip]
pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x05, // Usage (Game Pad)
0xA1, 0x01, // Collection (Application)
0xA1, 0x03, // Collection (Report)
0x85, 0x11, // Report ID (17)
0x19, 0x00, // Usage Minimum (Undefined)
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x05, // Report Count (5)
0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
0xA1, 0x03, // Collection (Report)
0x85, 0x21, // Report ID (33)
0x05, 0x00, // Usage Page (Undefined)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (-1)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x08, // Usage Maximum (0x08)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x09, 0x35, // Usage (Rz)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x06, // Report Count (6)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xA1, 0x03, // Collection (Report)
0x85, 0x13, // Report ID (19)
0x19, 0x00, // Usage Minimum (Undefined)
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
0xC0, // End Collection
];
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
pub struct GcButtons1 {
#[packed_field(bits = "0")]
pub button_a: bool,
#[packed_field(bits = "1")]
pub button_b: bool,
#[packed_field(bits = "2")]
pub button_x: bool,
#[packed_field(bits = "3")]
pub button_y: bool,
#[packed_field(bits = "4")]
pub dpad_left: bool,
#[packed_field(bits = "5")]
pub dpad_right: bool,
#[packed_field(bits = "6")]
pub dpad_down: bool,
#[packed_field(bits = "7")]
pub dpad_up: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
pub struct GcButtons2 {
#[packed_field(bits = "0")]
pub button_start: bool,
#[packed_field(bits = "1")]
pub button_z: bool,
#[packed_field(bits = "2")]
pub button_r: bool,
#[packed_field(bits = "3")]
pub button_l: bool,
#[packed_field(bits = "4..=7")]
pub blank1: u8,
}
///
/// Struct representing the controller state. Used for HID communications in
/// GCC adapter mode, as well as for the internal representation of the controller.
///
#[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
pub struct GcState {
#[packed_field(bits = "0..=7")]
pub buttons_1: GcButtons1,
#[packed_field(bits = "8..=15")]
pub buttons_2: GcButtons2,
#[packed_field(bits = "16..=23")]
pub stick_x: u8,
#[packed_field(bits = "24..=31")]
pub stick_y: u8,
#[packed_field(bits = "32..=39")]
pub cstick_x: u8,
#[packed_field(bits = "40..=47")]
pub cstick_y: u8,
#[packed_field(bits = "48..=55")]
pub trigger_l: u8,
#[packed_field(bits = "56..=63")]
pub trigger_r: u8,
}
impl Default for GcState {
fn default() -> Self {
Self {
buttons_1: GcButtons1::default(),
buttons_2: GcButtons2::default(),
stick_x: 127,
stick_y: 127,
cstick_x: 127,
cstick_y: 127,
trigger_l: 0,
trigger_r: 0,
}
}
}
#[derive(Default)]
pub struct GcReportBuilder {
gc_first: bool,
}
impl HidReportBuilder<37> for GcReportBuilder {
async fn get_hid_report(&mut self, state: &GcState) -> [u8; 37] {
let mut buffer = [0u8; 37];
buffer[0] = 0x21;
buffer[1] |= 0x14;
let data = state.pack().expect("Failed to pack GC input data");
if !self.gc_first {
buffer[1] |= 0x04;
buffer[10] |= 0x04;
buffer[19] |= 0x04;
buffer[28] |= 0x04;
self.gc_first = true;
} else {
// controller in "port 1"
buffer[2..=9].copy_from_slice(&data);
}
buffer
}
}
pub struct GccRequestHandler;
impl RequestHandler for GccRequestHandler {
fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
trace!("Set report for {:?}: {:x}", id, data);
if data.len() > 1 {
SIGNAL_RUMBLE.signal((data[1] & 0x01) != 0);
}
OutResponse::Accepted
}
fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
info!("Get idle rate for {:?}", id);
None
}
}

2
src/hid/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod gcc;
pub mod procon;

762
src/hid/procon.rs Normal file
View file

@ -0,0 +1,762 @@
///
/// The majority of the logic in this file is derived from HOJA-LIB-RP2040
/// https://github.com/HandHeldLegend/HOJA-LIB-RP2040
///
/// The original author gave their consent for this to be included in the NaxGCC firmware,
/// and for this file to be distributed under the GPLv3, see https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/26
///
use core::ops::{Deref, DerefMut};
use defmt::{info, trace, Format};
use embassy_rp::clocks::RoscRng;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use embassy_time::Instant;
use embassy_usb::{
class::hid::{ReportId, RequestHandler},
control::OutResponse,
};
use packed_struct::{derive::PackedStruct, PackedStruct};
use rand::RngCore;
use crate::usb_comms::HidReportBuilder;
use super::gcc::GcState;
const SW_INFO_SET_MAC: u8 = 0x01;
const SW_CMD_SET_INPUT_MODE: u8 = 0x03;
const SW_CMD_GET_DEVINFO: u8 = 0x02;
const SW_CMD_SET_SHIPMODE: u8 = 0x08;
const SW_CMD_GET_SPI: u8 = 0x10;
const SW_CMD_SET_PAIRING: u8 = 0x01;
const SW_CMD_GET_TRIGGERET: u8 = 0x04;
const ACK_GET_DEVINFO: u8 = 0x82;
const ACK_GET_SPI: u8 = 0x90;
const ACK_SET_PAIRING: u8 = 0x81;
const ACK_GET_TRIGERET: u8 = 0x83;
const ACK_GENERIC: u8 = 0x80;
const RM_SEND_STATE: u8 = 0x30;
const PRO_CONTROLLER_STRING: [u8; 24] = [
0x00, 0x25, 0x08, 0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65,
0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68,
];
#[derive(Debug, Format, Clone, Copy)]
struct ProconRequestInfo {
report_id: ProconRequestId,
response_id: ProconResponseId,
command_id: u8,
raw_data: [u8; 64],
}
#[derive(Debug, Format, Clone, Copy)]
enum ProconRequestId {
GetInfo = 0x80,
Command = 0x01,
Rumble = 0x10,
}
#[derive(Debug, Format, Clone, Copy)]
enum ProconResponseId {
GetInfo = 0x81,
GetState = 0x30,
Command = 0x21,
}
static SIGNAL_PROCON_REQUEST: Signal<CriticalSectionRawMutex, ProconRequestInfo> = Signal::new();
#[rustfmt::skip]
pub const PROCON_REPORT_DESCRIPTOR: &[u8] = &[
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x15, 0x00, // Logical Minimum (0)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x85, 0x30, // Report ID (48)