Skip to content

Instantly share code, notes, and snippets.

@ayufan
Created October 14, 2022 15:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ayufan/a287300f63d0fb2ee6977469f96e71e2 to your computer and use it in GitHub Desktop.
Save ayufan/a287300f63d0fb2ee6977469f96e71e2 to your computer and use it in GitHub Desktop.
Steam Deck dual-boot copy Windows Bluetooth Paring
#!/bin/bash
if [[ $# -ne 1 ]]; then
echo "usage: $0 <windows-partition>"
echo " ex.: $0 /dev/nvme0n1p10"
echo " ex.: $0 /dev/mmcblk0p3"
exit 1
fi
if [[ $(id -u) != 0 ]]; then
echo "run as root"
sudo -- "$0" "$@"
exit 1
fi
set -eo pipefail
which chntpw || pacman -Sy chntpw
mkdir -p /mnt/windows
umount /mnt/windows || true
mount /dev/nvme0n1p10 /mnt/windows
trap "cd /; umount /mnt/windows" EXIT
cd /mnt/windows/Windows/System32/config
changed=""
get_value() {
printf "%s \ControlSet001\Services\BTHPORT\Parameters\Keys\%s\nq\n" "$1" "$2" |
chntpw -e SYSTEM |
grep ":0000"
}
ls_devices() {
printf "ls \ControlSet001\Services\BTHPORT\Parameters\Keys\%s\nq\n" "$1" |
chntpw -e SYSTEM |
grep '^\s*<[a-z0-9]*>\s*$'
}
filter_hex() {
set -- "$@"
for arg; do
if [[ ${arg} =~ ^[0-9a-fA-F][0-9a-fA-F]$ ]]; then
echo "$arg"
fi
done
}
get_hex() {
local value=($(filter_hex $(get_value hex "$1")))
# join with :
local IFS="${2}"
echo "${value[*]}"
}
get_hex_r() {
local hexstring=""
for i in $(filter_hex $(get_value hex "$@")); do
hexstring="$i$hexstring"
done
echo "$hexstring"
}
get_dec() {
local hexstring=$(get_hex_r "$1")
if [[ -n "$hexstring" ]]; then
printf "%u" "0x${hexstring}"
fi
}
# chntpw -e SYSTEM
# 16 3 REG_BINARY <LTK>
# 4 4 REG_DWORD <KeyLength> 16 [0x10]
# 8 b REG_QWORD <ERand>
# 4 4 REG_DWORD <EDIV> 56572 [0xdcfc]
# 16 3 REG_BINARY <IRK>
# 8 b REG_QWORD <Address>
# 4 4 REG_DWORD <AddressType> 1 [0x1]
# 16 3 REG_BINARY <CSRK>
# 4 4 REG_DWORD <OutboundSignCounter> 0 [0x0]
# 4 4 REG_DWORD <MasterIRKStatus> 1 [0x1]
# 4 4 REG_DWORD <AuthReq> 45 [0x2d]
# git config -f /var/lib/bluetooth/14\:D4\:24\:70\:19\:B0/D4\:61\:2F\:82\:C2\:1B/info --list
# general.name=M720 Triathlon
# general.appearance=0x03c2
# general.addresstype=static
# general.supportedtechnologies=LE
# general.trusted=true
# general.blocked=false
# general.wakeallowed=true
# general.services=00001800-0000-1000-8000-00805f9b34fb
# connectionparameters.mininterval=6
# connectionparameters.maxinterval=9
# connectionparameters.latency=44
# connectionparameters.timeout=216
# identityresolvingkey.key=88A1BA3D0F64994C59A9058545901A6E
# localsignaturekey.key=9D07AAF0395CAA4D91AE97DA1D7A0639
# localsignaturekey.counter=0
# localsignaturekey.authenticated=false
# longtermkey.key=ED235B5197B3673E12BE9F9D2AF019EB
# longtermkey.authenticated=0
# longtermkey.encsize=16
# longtermkey.ediv=42387
# longtermkey.rand=16144039604717484547
# deviceid.source=2
# deviceid.vendor=1133
# deviceid.product=45077
# deviceid.version=9
to_hive_key() {
local value="$1"
value="${value//:}"
value="${value,,}"
echo "$value"
}
from_hive_key() {
local value="$1"
local ret=()
while [[ -n "$value" ]]; do
ret+=("${value:0:2}")
value="${value:2}"
done
local IFS=":"
echo "${ret[*]^^}"
}
update_ini() {
local hiveType="$1"
local hivePath="$2"
local configPath="$3"
local iniPath="$4"
local iniValue=$(git config -f "$configPath" --get "$iniPath")
if [[ -z "$iniValue" ]]; then
return 0
fi
local hiveValue=$(get_$hiveType "$hivePath")
if [[ -z "$hiveValue" ]]; then
return 0
fi
if [[ "$hiveValue" == "$iniValue" ]]; then
echo ">> The '$iniPath' ($hivePath) is not changed."
return 0
fi
echo ">> Update '$iniValue' ($iniPath) to '$hiveValue' ($hivePath)"
git config -f "$configPath" --replace-all "$iniPath" "$hiveValue"
changed="$changed\n$hivePath"
}
find_device_by_irk() {
local uniqueID="${1}"
local queryIRK="${2}"
if [[ -z "$queryIRK" ]]; then
return 1
fi
for configPath in /var/lib/bluetooth/$uniqueID/*/info; do
local deviceID=$(dirname "$configPath")
deviceID=$(basename "$deviceID")
local deviceIRK=$(git config -f "$configPath" --get "IdentityResolvingKey.Key")
if [[ "$deviceIRK" == "$queryIRK" ]]; then
echo "$deviceID"
return 0
fi
done
return 1
}
update_device() {
local configPath="/var/lib/bluetooth/$1/$2/info"
local hivePath="$(to_hive_key "$1")\\$(to_hive_key "$2")"
if [[ ! -e "$configPath" ]]; then
echo "The '$configPath' does not exist."
return
fi
if [[ ! -e "$configPath.bak" ]]; then
cp "$configPath" "$configPath.bak"
fi
update_ini hex_r "${hivePath}\\IRK" "$configPath" IdentityResolvingKey.Key
update_ini hex "${hivePath}\\CSRK" "$configPath" LocalSignatureKey.Key
update_ini hex "${hivePath}\\LTK" "$configPath" LongTermKey.Key
update_ini dec "${hivePath}\\KeyLength" "$configPath" LongTermKey.EncSize
update_ini dec "${hivePath}\\EDIV" "$configPath" LongTermKey.EDiv
update_ini dec "${hivePath}\\ERand" "$configPath" LongTermKey.Rand
update_ini hex "${hivePath}" "$configPath" LinkKey.Key
}
for uniqueID in /var/lib/bluetooth/*:*; do
uniqueID=$(basename "$uniqueID")
hiveUniqueID="$(to_hive_key "$uniqueID")"
echo "Cloning existing devices with IRK..."
ls_devices "$hiveUniqueID" | while read hiveDeviceID; do
hiveDeviceID="${hiveDeviceID//[ <>]}"
deviceID="$(from_hive_key "$hiveDeviceID")"
if [[ -e "/var/lib/bluetooth/$uniqueID/$deviceID/info" ]]; then
continue
fi
queryIRK=$(get_hex_r "$hiveUniqueID\\$hiveDeviceID\\IRK")
if ! otherDeviceID=$(find_device_by_irk "$uniqueID" "$queryIRK"); then
continue
fi
if [[ "$deviceID" == "$otherDeviceID" ]]; then
continue
fi
echo ">> Clonning '$otherDeviceID' to '$deviceID'..."
cp -rv "/var/lib/bluetooth/$uniqueID/$otherDeviceID" "/var/lib/bluetooth/$uniqueID/$deviceID"
done
echo
for deviceID in /var/lib/bluetooth/$uniqueID/*:*; do
deviceID=$(basename "$deviceID")
echo "Updating $deviceID..."
update_device "$uniqueID" "$deviceID"
echo
done
done
if [[ -n "$changed" ]]; then
echo -e "Changed: $changed"
systemctl restart bluetooth.service
systemctl restart graphical.target
fi
echo Done
@ayufan
Copy link
Author

ayufan commented Oct 14, 2022

Do this before running the script:

sudo pacman-key --init
sudo pacman-key --populate archlinux
sudo steamos-readonly disable

This requires that:

  1. The device is first paired on SteamOS
  2. The device is second paired on Windows
  3. The script is run in SteamOS to copy parings from Windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment