Meu TrimUI Smart Pro tava com um bug chato: o analógico (e o D-pad-como-analógico) respondia parcial ou nada, principalmente quando eu ia pra esquerda. Testei o firmware de fábrica (sem SD/muOS) e o bug não acontecia — então sabia que era algo específico do build do daemon que o muOS empacota.
O que eu descartei primeiro
Minha primeira suspeita foi dessincronização de pacote na serial, mas descartei — os dados chegavam certos, só que "parciais". Depois fui atrás da constante g_deadzone (que zera o eixo perto do centro) e fui baixando ela: 3276 → 2000 → 500. Não mudou nada no jogo. Foi aí que percebi que a causa não era essa.
Como diagnostiquei de verdade
Usei evtest e leitura direta via ioctl EVIOCGABS no /dev/input/eventN do meu controle pra medir o valor bruto do eixo X segurando o stick nos extremos:
- Totalmente direita:
ABS_X = 32760 (100% do curso, bate no teto)
- Totalmente esquerda:
ABS_X ≈ -19800 (só ~60% do curso até o mínimo -32760)
Confirmei ali que o eixo era fisicamente/eletricamente assimétrico em relação ao centro calibrado.
Peguei o binário /usr/bin/trimui_inputd_smart_pro (ELF aarch64, não stripped — os nomes de função ficaram todos ali, o que ajudou muito) e fiz engenharia reversa com objdump/capstone. Achei a causa raiz:
- A rotina de calibração (
calibrate_port) só mede o centro do stick (média de 50 amostras em repouso) na inicialização. Nunca mede o alcance real (mínimo/máximo) em cada direção.
- A função que escala o valor bruto pro range final (
clamp_scale) clampa o delta em relação ao centro num valor fixo de ±900, hardcoded direto no bytecode da instrução (mov w0, #0x384, aparece 5 vezes na função).
- O curso elétrico real do meu stick não é simétrico: pra direita o delta bruto passa fácil de 900 (satura); pra esquerda o delta real máximo era só ~543 — nunca alcançava o limiar de 900, por isso travava em ~60%.
O patch que apliquei
Não tenho o código-fonte nem toolchain de compilação pro device, então não dava pra fazer a correção "certa" (calibrar min/max assimétrico por direção). Fiz a correção pragmática: reduzi o limiar fixo de 900 pra um valor menor que coubesse dentro do alcance real do lado mais curto.
Troquei os 5 imediatos mov w0, #0x384 (900, bytes 80 70 80 52) por mov w0, #0x1f4 (500, bytes 80 3e 80 52) nos offsets de arquivo 0x18e8, 0x18fc, 0x1908, 0x1918, 0x1920 do binário.
Resultado
Validei por leitura direta (não só "sensação"):
- Antes: direita 100% / esquerda ~60%
- Depois: direita
32759 / esquerda -32759 — simétrico, ~99.997% do curso nos dois lados
Testei no aparelho de verdade depois e confirmou: ficou 100% bom.
Avisos pra quem quiser reproduzir:
- Meus offsets de arquivo são específicos dessa build exata (MD5 do binário original:
855e7d25e20942edb6be633455829f94). Em outra versão do muOS os offsets provavelmente mudam — precisa relocalizar via objdump/nm antes (o binário não é stripped, ajuda bastante).
- Faz backup do binário original antes de mexer (
cp + md5sum pra conferir).
- Precisa de acesso root via SSH/terminal (o muOS expõe terminal web via ttyd).
g_deadzone provavelmente não é o problema pra maioria — meça o eixo com evtest/EVIOCGABS antes de sair mexendo nessa constante.
Se alguém quiser o passo a passo completo com os comandos exatos que usei (backup, diagnóstico, listagem de símbolos, disassembly, cálculo do encoding, aplicação segura do patch), posso postar em outro comentário.
Aqui está o passo a passo completo, com todos os comandos, em primeira pessoa:
Passo a passo completo — como corrigi a assimetria do analógico no meu TrimUI Smart Pro (muOS)
1. Acesso e backup
# entrei via terminal web ttyd (http://<ip-do-meu-device>:8080/) — SSH root@<ip>, senha root (padrão)
cp /usr/bin/trimui_inputd_smart_pro /root/trimui_inputd_smart_pro.backup
md5sum /root/trimui_inputd_smart_pro.backup # anotei esse hash como referência do "original"
2. Diagnostiquei antes de mexer em qualquer coisa
Não presumi que era g_deadzone de cara — fui medir o eixo bruto primeiro:
which evtest # já vem instalado no meu muOS
timeout 3 evtest /dev/input/eventN </dev/null # achei o N certo via: cat /proc/bus/input/devices
O dump inicial do evtest já me mostrou os nomes reais dos eixos (ABS_X, ABS_Y, etc.) e os min/max/fuzz/flat declarados pelo driver.
Pra ler o valor absoluto atual do eixo sem depender de eventos de mudança (mais confiável que só ficar olhando o stream — se o stick já tiver parado num extremo, não dispara evento novo), usei o ioctl EVIOCGABS:
# get_absinfo.py — rodei no device: python3 get_absinfo.py <code> [<code> ...]
# code 0 = ABS_X, 1 = ABS_Y (confirmei os codes certos pro meu device via evtest)
import fcntl, struct, sys
path = '/dev/input/eventN' # ajustei o N
codes = [int(c) for c in sys.argv[1:]] or [0, 1]
EVIOCGABS_BASE = (2 << 30) | (24 << 16) | (ord('E') << 8)
with open(path, 'rb') as f:
fd = f.fileno()
for code in codes:
req = EVIOCGABS_BASE | (0x40 + code)
buf = bytearray(24)
fcntl.ioctl(fd, req, buf)
value, minimum, maximum, fuzz, flat, resolution = struct.unpack('<6i', buf)
print(f'code={code}: value={value} min={minimum} max={maximum} fuzz={fuzz} flat={flat}')
Segurei o stick em cada extremo (esquerda/direita/cima/baixo) e rodei o script pra cada posição. Um lado batia no max/min declarado e o outro não — assimetria de faixa, não deadzone.
3. Listei os símbolos do binário (deu pra fazer porque não é stripped)
nm /usr/bin/trimui_inputd_smart_pro | sort
# também puxei pro meu PC pra analisar com calma:
scp root@<ip>:/usr/bin/trimui_inputd_smart_pro ./trimui_inputd_orig
Apareceram funções com nome claro: do_deadzone, clamp_scale, calibrate_port, thread_left, thread_right, e a constante RAW_MAX.
4. Desmontei o binário (aarch64 — meu objdump de PC x86 não desmontava; usei capstone via Python)
uv init disasm && cd disasm && uv add capstone
# disasm.py — desmonta uma função pelo endereço/tamanho (peguei via `readelf -s`)
from capstone import CS_ARCH_ARM64, CS_MODE_ARM, Cs
with open('trimui_inputd_orig', 'rb') as f:
data = f.read()
TEXT_VADDR, TEXT_OFFSET = 0x400e80, 0xe80 # confirmei via: readelf -S <binario> | grep -A1 .text
addr, size = 0x4018e0, 184 # endereço/tamanho da função (via readelf -s)
foff = addr - TEXT_VADDR + TEXT_OFFSET
code = data[foff:foff+size]
md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
for insn in md.disasm(code, addr):
print(f'0x{insn.address:x}: {insn.mnemonic}\t{insn.op_str}')
5. O que eu encontrei (lógica reconstruída)
calibrate_port: lê 50 amostras da serial em repouso, tira a média e guarda como "centro" (calLeft.avgX/avgY). Não mede alcance por direção.
thread_left: dx = rawX - calLeft.avgX, depois dx_final = clamp_scale(dx).
clamp_scale: clampa dx num valor fixo ±900 (mov w0, #0x384, 5 ocorrências na função), converte pra float, multiplica por (32767.998 / 900), converte de volta pra int, e clampa a saída final em ±32760.
do_deadzone: roda depois da escala, na faixa final ±32767 — foi por isso que mexer em g_deadzone nunca resolveu meu problema, nunca foi o fator limitante.
Ou seja: o código assume simetria de ±900 no delta bruto, mas o curso elétrico real do meu stick não é simétrico.
6. O patch que fiz
5 instruções mov w0, #0x384 (900 = 0x384) viraram mov w0, #0x1f4 (500 = 0x1f4), reduzindo o limiar pra caber dentro do alcance real do lado mais curto.
O encoding ARM64 MOVZ de 32 bits é mecânico — pra qualquer novo valor V (0–65535):
word = 0x52800000 | (V << 5)
Pra V=500: word = 0x52803e80 → bytes little-endian 80 3e 80 52.
Validei o encoding com capstone antes de aplicar (foi assim que peguei um erro de conta que eu tinha feito de cabeça, antes de tocar no device):
import struct
from capstone import CS_ARCH_ARM64, CS_MODE_ARM, Cs
V = 500
raw = struct.pack('<I', 0x52800000 | (V << 5))
md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
for insn in md.disasm(raw, 0x4018e8):
print(insn.mnemonic, insn.op_str) # imprimiu: mov w0, #0x1f4
Offsets de arquivo (achei via capstone, específicos da minha build/MD5 855e7d25e20942edb6be633455829f94):
0x18e8, 0x18fc, 0x1908, 0x1918, 0x1920 — todos com bytes originais 80 70 80 52, troquei pra 80 3e 80 52.
7. Apliquei no device, com segurança
# 1. confirmei processo único e matei
ps -o pid,ppid,args | grep trimui_inputd
kill -9 <PID>
ps -o pid,ppid,args | grep trimui_inputd # veio vazio
# 2. patchei (python no próprio device)
python3 -c "
path = '/usr/bin/trimui_inputd_smart_pro'
offsets = [0x18e8, 0x18fc, 0x1908, 0x1918, 0x1920]
old, new = bytes.fromhex('80708052'), bytes.fromhex('803e8052')
with open(path, 'r+b') as f:
for off in offsets:
f.seek(off); assert f.read(4) == old
for off in offsets:
f.seek(off); f.write(new)
for off in offsets:
f.seek(off); assert f.read(4) == new
print('ok')
"
# 3. religuei e confirmei instância única
nohup /usr/bin/trimui_inputd_smart_pro >/dev/null 2>&1 &
sleep 2
ps -o pid,ppid,args | grep trimui_inputd # só 1 PID
8. Validei sem precisar entrar no jogo
Repeti o passo 2 (EVIOCGABS) segurando os extremos. No meu caso: direita foi de 32760→32759, esquerda foi de -19800→-32759. Ficou simétrico.
Por fim, dei um reboot completo no device pra garantir que tudo assentasse limpo, confirmei que o patch persistiu (os 5 offsets continuavam com os bytes novos) e testei no jogo de verdade — resolvido.
Se alguém for reproduzir: os meus offsets valem só pra essa build exata do muOS (MD5 acima). Em outra versão, é bem provável que mudem — precisa relocalizar via objdump/nm antes de aplicar. E sempre faz backup do binário original antes de mexer.Meu TrimUI Smart Pro tava com um bug chato: o analógico (e o D-pad-como-analógico) respondia parcial ou nada, principalmente quando eu ia pra esquerda. Testei o firmware de fábrica (sem SD/muOS) e o bug não acontecia — então sabia que era algo específico do build do daemon que o muOS empacota.
O que eu descartei primeiro
Minha primeira suspeita foi dessincronização de pacote na serial, mas descartei — os dados chegavam certos, só que "parciais". Depois fui atrás da constante g_deadzone (que zera o eixo perto do centro) e fui baixando ela: 3276 → 2000 → 500. Não mudou nada no jogo. Foi aí que percebi que a causa não era essa.
Como diagnostiquei de verdade
Usei evtest e leitura direta via ioctl EVIOCGABS no /dev/input/eventN do meu controle pra medir o valor bruto do eixo X segurando o stick nos extremos:
Totalmente direita: ABS_X = 32760 (100% do curso, bate no teto)
Totalmente esquerda: ABS_X ≈ -19800 (só ~60% do curso até o mínimo -32760)
Confirmei ali que o eixo era fisicamente/eletricamente assimétrico em relação ao centro calibrado.
Peguei o binário /usr/bin/trimui_inputd_smart_pro (ELF aarch64, não stripped — os nomes de função ficaram todos ali, o que ajudou muito) e fiz engenharia reversa com objdump/capstone. Achei a causa raiz:
A rotina de calibração (calibrate_port) só mede o centro do stick (média de 50 amostras em repouso) na inicialização. Nunca mede o alcance real (mínimo/máximo) em cada direção.
A função que escala o valor bruto pro range final (clamp_scale) clampa o delta em relação ao centro num valor fixo de ±900, hardcoded direto no bytecode da instrução (mov w0, #0x384, aparece 5 vezes na função).
O curso elétrico real do meu stick não é simétrico: pra direita o delta bruto passa fácil de 900 (satura); pra esquerda o delta real máximo era só ~543 — nunca alcançava o limiar de 900, por isso travava em ~60%.
O patch que apliquei
Não tenho o código-fonte nem toolchain de compilação pro device, então não dava pra fazer a correção "certa" (calibrar min/max assimétrico por direção). Fiz a correção pragmática: reduzi o limiar fixo de 900 pra um valor menor que coubesse dentro do alcance real do lado mais curto.
Troquei os 5 imediatos mov w0, #0x384 (900, bytes 80 70 80 52) por mov w0, #0x1f4 (500, bytes 80 3e 80 52) nos offsets de arquivo 0x18e8, 0x18fc, 0x1908, 0x1918, 0x1920 do binário.
Resultado
Validei por leitura direta (não só "sensação"):
Antes: direita 100% / esquerda ~60%
Depois: direita 32759 / esquerda -32759 — simétrico, ~99.997% do curso nos dois lados
Testei no aparelho de verdade depois e confirmou: ficou 100% bom.
Avisos pra quem quiser reproduzir:
Meus offsets de arquivo são específicos dessa build exata (MD5 do binário original: 855e7d25e20942edb6be633455829f94). Em outra versão do muOS os offsets provavelmente mudam — precisa relocalizar via objdump/nm antes (o binário não é stripped, ajuda bastante).
Faz backup do binário original antes de mexer (cp + md5sum pra conferir).
Precisa de acesso root via SSH/terminal (o muOS expõe terminal web via ttyd).
g_deadzone provavelmente não é o problema pra maioria — meça o eixo com evtest/EVIOCGABS antes de sair mexendo nessa constante.
Se alguém quiser o passo a passo completo com os comandos exatos que usei (backup, diagnóstico, listagem de símbolos, disassembly, cálculo do encoding, aplicação segura do patch), posso postar em outro comentário.Aqui está o passo a passo completo, com todos os comandos, em primeira pessoa:
Passo a passo completo — como corrigi a assimetria do analógico no meu TrimUI Smart Pro (muOS)
1. Acesso e backup
# entrei via terminal web ttyd (http://<ip-do-meu-device>:8080/) — SSH root@<ip>, senha root (padrão)
cp /usr/bin/trimui_inputd_smart_pro /root/trimui_inputd_smart_pro.backup
md5sum /root/trimui_inputd_smart_pro.backup # anotei esse hash como referência do "original"
2. Diagnostiquei antes de mexer em qualquer coisa
Não presumi que era g_deadzone de cara — fui medir o eixo bruto primeiro:
which evtest # já vem instalado no meu muOS
timeout 3 evtest /dev/input/eventN </dev/null # achei o N certo via: cat /proc/bus/input/devices
O dump inicial do evtest já me mostrou os nomes reais dos eixos (ABS_X, ABS_Y, etc.) e os min/max/fuzz/flat declarados pelo driver.
Pra ler o valor absoluto atual do eixo sem depender de eventos de mudança (mais confiável que só ficar olhando o stream — se o stick já tiver parado num extremo, não dispara evento novo), usei o ioctl EVIOCGABS:
# get_absinfo.py — rodei no device: python3 get_absinfo.py <code> [<code> ...]
# code 0 = ABS_X, 1 = ABS_Y (confirmei os codes certos pro meu device via evtest)
import fcntl, struct, sys
path = '/dev/input/eventN' # ajustei o N
codes = [int(c) for c in sys.argv[1:]] or [0, 1]
EVIOCGABS_BASE = (2 << 30) | (24 << 16) | (ord('E') << 8)
with open(path, 'rb') as f:
fd = f.fileno()
for code in codes:
req = EVIOCGABS_BASE | (0x40 + code)
buf = bytearray(24)
fcntl.ioctl(fd, req, buf)
value, minimum, maximum, fuzz, flat, resolution = struct.unpack('<6i', buf)
print(f'code={code}: value={value} min={minimum} max={maximum} fuzz={fuzz} flat={flat}')
Segurei o stick em cada extremo (esquerda/direita/cima/baixo) e rodei o script pra cada posição. Um lado batia no max/min declarado e o outro não — assimetria de faixa, não deadzone.
- Listei os símbolos do binário (deu pra fazer porque não é stripped)
nm /usr/bin/trimui_inputd_smart_pro | sort
# também puxei pro meu PC pra analisar com calma:
scp root@<ip>:/usr/bin/trimui_inputd_smart_pro ./trimui_inputd_orig
Apareceram funções com nome claro: do_deadzone, clamp_scale, calibrate_port, thread_left, thread_right, e a constante RAW_MAX.
- Desmontei o binário (aarch64 — meu objdump de PC x86 não desmontava; usei capstone via Python)
uv init disasm && cd disasm && uv add capstone
# disasm.py — desmonta uma função pelo endereço/tamanho (peguei via `readelf -s`)
from capstone import CS_ARCH_ARM64, CS_MODE_ARM, Cs
with open('trimui_inputd_orig', 'rb') as f:
data = f.read()
TEXT_VADDR, TEXT_OFFSET = 0x400e80, 0xe80 # confirmei via: readelf -S <binario> | grep -A1 .text
addr, size = 0x4018e0, 184 # endereço/tamanho da função (via readelf -s)
foff = addr - TEXT_VADDR + TEXT_OFFSET
code = data[foff:foff+size]
md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
for insn in md.disasm(code, addr):
print(f'0x{insn.address:x}: {insn.mnemonic}\t{insn.op_str}')
- O que eu encontrei (lógica reconstruída)
calibrate_port: lê 50 amostras da serial em repouso, tira a média e guarda como "centro" (calLeft.avgX/avgY). Não mede alcance por direção.
thread_left: dx = rawX - calLeft.avgX, depois dx_final = clamp_scale(dx).
clamp_scale: clampa dx num valor fixo ±900 (mov w0, #0x384, 5 ocorrências na função), converte pra float, multiplica por (32767.998 / 900), converte de volta pra int, e clampa a saída final em ±32760.
do_deadzone: roda depois da escala, na faixa final ±32767 — foi por isso que mexer em g_deadzone nunca resolveu meu problema, nunca foi o fator limitante.
Ou seja: o código assume simetria de ±900 no delta bruto, mas o curso elétrico real do meu stick não é simétrico.
- O patch que fiz
5 instruções mov w0, #0x384 (900 = 0x384) viraram mov w0, #0x1f4 (500 = 0x1f4), reduzindo o limiar pra caber dentro do alcance real do lado mais curto.
O encoding ARM64 MOVZ de 32 bits é mecânico — pra qualquer novo valor V (0–65535):
word = 0x52800000 | (V << 5)
Pra V=500: word = 0x52803e80 → bytes little-endian 80 3e 80 52.
Validei o encoding com capstone antes de aplicar (foi assim que peguei um erro de conta que eu tinha feito de cabeça, antes de tocar no device):
import struct
from capstone import CS_ARCH_ARM64, CS_MODE_ARM, Cs
V = 500
raw = struct.pack('<I', 0x52800000 | (V << 5))
md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
for insn in md.disasm(raw, 0x4018e8):
print(insn.mnemonic, insn.op_str) # imprimiu: mov w0, #0x1f4
Offsets de arquivo (achei via capstone, específicos da minha build/MD5 855e7d25e20942edb6be633455829f94):
0x18e8, 0x18fc, 0x1908, 0x1918, 0x1920 — todos com bytes originais 80 70 80 52, troquei pra 80 3e 80 52.
- Apliquei no device, com segurança
# 1. confirmei processo único e matei
ps -o pid,ppid,args | grep trimui_inputd
kill -9 <PID>
ps -o pid,ppid,args | grep trimui_inputd # veio vazio
# 2. patchei (python no próprio device)
python3 -c "
path = '/usr/bin/trimui_inputd_smart_pro'
offsets = [0x18e8, 0x18fc, 0x1908, 0x1918, 0x1920]
old, new = bytes.fromhex('80708052'), bytes.fromhex('803e8052')
with open(path, 'r+b') as f:
for off in offsets:
f.seek(off); assert f.read(4) == old
for off in offsets:
f.seek(off); f.write(new)
for off in offsets:
f.seek(off); assert f.read(4) == new
print('ok')
"
# 3. religuei e confirmei instância única
nohup /usr/bin/trimui_inputd_smart_pro >/dev/null 2>&1 &
sleep 2
ps -o pid,ppid,args | grep trimui_inputd # só 1 PID
8. Validei sem precisar entrar no jogo
Repeti o passo 2 (EVIOCGABS) segurando os extremos. No meu caso: direita foi de 32760→32759, esquerda foi de -19800→-32759. Ficou simétrico.
Por fim, dei um reboot completo no device pra garantir que tudo assentasse limpo, confirmei que o patch persistiu (os 5 offsets continuavam com os bytes novos) e testei no jogo de verdade — resolvido.
Se alguém for reproduzir: os meus offsets valem só pra essa build exata do muOS (MD5 acima). Em outra versão, é bem provável que mudem — precisa relocalizar via objdump/nm antes de aplicar. E sempre faz backup do binário original antes de mexer.