diff --git a/README.md b/README.md index 3b0e4d6..f3b692a 100644 --- a/README.md +++ b/README.md @@ -561,6 +561,46 @@ On device, set the slider to minimum: cat /sys/class/backlight/backlight_pwm0/brightness # should read 1, not 5 ``` --- +## HiBy R1 — Bluetooth Audio Input Unlock +**Target binary:** `/usr/bin/hiby_player` (MIPS32 LE ELF) +**Change:** Unlocks the natively supported but hidden "Bluetooth audio input" (DAC/Sink mode) in the Bluetooth settings list. Works flawlessly with UI navigation. +--- +### Background +The firmware fully supports Bluetooth Sink mode (used in R3 Pro II), but in R1 the option is removed from the settings list, and a blocklist routine prevents its view (`vg_bt_input_hiby`) from opening. + +**Change 1 (Bypass Blocklist):** The blocklist routine (`FUN_004f7220`) explicitly checks against `vg_bt_input_hiby`. We shift the string pointer by 1 byte, making it check for `g_bt_input_hiby` instead, effectively unblocking the view. +```asm +addiu s1, s1, LOW(vg_addr) -> addiu s1, s1, LOW(vg_addr) + 1 +``` + +**Change 2 (Inject Menu Item):** The UI builds the Bluetooth menu item by item. We overwrite one item addition (`volume_sync`) with a jump to a code cave (40 bytes of `0x00` in `.rodata`). In the cave, we execute the original `volume_sync` addition, followed by our new `bt_input` addition, and jump back seamlessly. +```asm +lui a1, HIGH(bt_in_addr) +move a0, s2 +jal add_item_func +addiu a1, a1, LOW(bt_in_addr) +``` +--- +### Patch Locations +Offset is build-dependent. Values for the Sorting-patch player (fw 1.6): + +| Description | Offset (hex) | Offset (dec) | Before | After | Change | +|---|---|---|---|---|---| +| Blocklist bypass | `0x000F723C` | 1,012,284 | `58 3C 31 26` | `59 3C 31 26` | `addiu s1,s1,15448` → `15449` | +| Cave Jump | `0x000AD034` | 708,660 | `79 00 05 3C` | `FE BD 1D 08` | `lui a1,0x79` → `j 0x76f7f8` | +| Code Cave | `0x0036F7F8` | 3,602,424 | `00 00 00 ...` | *(Cave payload)* | 40 bytes of payload | +--- +### How to Apply +> **Back up your original binary before patching.** + +**Using `patch_bt_input.py` (any build).** Locates the UI menu builder, blocklist offsets, and an empty code cave automatically by instruction signature. It works regardless of binary layout (handy for other community variants where the offset differs): +```bash +python3 patch_bt_input.py hiby_player # patches hiby_player in place +``` +--- +### Verification +On device, open Bluetooth settings. "Bluetooth audio input" should appear in the list. Tapping it opens the DAC/Sink mode. Tapping the back arrow returns to the menu seamlessly. +--- ## After Flashing — Manual Setup diff --git a/patch_bt_input.py b/patch_bt_input.py new file mode 100644 index 0000000..bbd0658 --- /dev/null +++ b/patch_bt_input.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +import struct +import sys + +def main(): + if len(sys.argv) < 2: + print("Usage: patch_bt_input.py ") + sys.exit(1) + + target_file = sys.argv[1] + with open(target_file, 'rb') as f: + data = bytearray(f.read()) + + def find_string(s): + idx = data.find(s + b'\x00') + if idx != -1: + return idx + 0x400000 + return None + + vg_addr = find_string(b'vg_bt_input_hiby') + vol_addr = find_string(b'volume_sync') + bt_in_addr = find_string(b'bt_input') + + if not all([vg_addr, vol_addr, bt_in_addr]): + print("Error: Could not find required strings in the binary. Are you using an unmodified R1 hiby_player?") + sys.exit(1) + + print(f"[+] Found vg_bt_input_hiby at {hex(vg_addr)}") + print(f"[+] Found volume_sync at {hex(vol_addr)}") + print(f"[+] Found bt_input at {hex(bt_in_addr)}") + + # 1. Patch the blocklist (FUN_004f7220) + # Search for addiu s1, s1, LOW(vg_addr) -> 0x2631xxxx + target_addiu_s1 = 0x26310000 | (vg_addr & 0xffff) + target_bytes_s1 = struct.pack(' 0x24a5xxxx + target_addiu_a1 = 0x24a50000 | (vol_addr & 0xffff) + target_bytes_a1 = struct.pack('> 16) != 0x3c05 or move_instr != 0x02402025 or (jal_instr >> 26) != 0x0c: + print("Error: Menu builder sequence mismatch. Has it already been patched with a code cave?") + sys.exit(1) + + add_item_func = (jal_instr & 0x03ffffff) << 2 + print(f"[+] Found menu builder sequence at {hex(menu_start_idx)}. UI add_item func: {hex(add_item_func)}") + + # 3. Find code cave (40 bytes of \x00) + cave_size = 40 + zeros = b'\x00' * cave_size + # Search in executable regions (.text or .rodata, starting around 0x300000) + cave_idx = data.find(zeros, 0x300000) + if cave_idx == -1: + print("Error: Could not find code cave of 40 zero bytes.") + sys.exit(1) + + cave_vaddr = cave_idx + 0x400000 + print(f"[+] Found code cave at file offset {hex(cave_idx)} (vaddr {hex(cave_vaddr)})") + + # 4. Generate payload (Code Cave) + # We will execute the original volume_sync addition, then add our new bt_input, then jump back. + def get_high_low(addr): + low = addr & 0xffff + high = addr >> 16 + if low >= 0x8000: + high += 1 + return high, low + + vol_high, vol_low = get_high_low(vol_addr) + bt_high, bt_low = get_high_low(bt_in_addr) + + payload = struct.pack('> 2)) # j return_addr + payload += struct.pack('> 2) + overwrite = struct.pack('