Programando em Rust para o console R36S();

Há alguns meses, adquiri um console portátil R36S. Apesar de gostar de algumas velharias alguns jogos emulados, comprei mais com o objetivo de “hackear” e estudar mesmo, e no último fim de semana, resolvi pôr a mão na massa: fiz um programa simples em linguagem Rust que lê os estados dos botões e joysticks e os exibe na tela. Fiz isso porque eu queria praticar um pouco de cross-compiling em Rust — uma técnica em que desenvolvo um software em uma plataforma, mas compilo gerando um binário que vai rodar em outra.

O console

O R36S é um console portátil fabricado por algumas empresas chinesas (existem “clones” dele), voltado para emulação de consoles antigos. Pode ser encontrado por aí na faixa de “duzentão” ou até menos.

Normalmente ele vem com o sistema operacional ArkOS (baseado na distribuição Ubuntu do Linux), voltado especificamente para a emulação retrô. Sua arquitetura de hardware é voltada para eficiência energética. Possui um chipset RK3326 que integra um processador ARM-Cortex-A35 quad-core de 64 bits.

O projeto

Pesquisando e experimentando, descobri que as entradas do console podem ser lidas a partir do dispositivo /dev/input/js0. Ele possui 17 botões, que são mapeados de 0 a 16, enquanto os eixos dos joysticks analógicos são mapeados de 0 a 3 (existem alguns botões na lateral do console também, mas resolvi não tentar usá-los, pois normalmente são usados para ligar/desligar o console, reset e controle de volume).

Entradas mapeadas do console R36S.

O objetivo, então, era simples: fazer um programa que ficasse “observando” as mudanças de estado nos botões e joysticks e exibindo esses estados na tela. Resolvi fazer a tela do programa em modo texto mesmo, já que o sistema operacional do console é um Linux e concluí que eu poderia usar um “modo terminal”. Para fazer o layout da tela, resolvi usar o ratatui, uma biblioteca para Rust que serve justamente para fazer TUIs (Text User Interfaces).

Tela "demo" da biblioteca ratatui.
Tutorial

Para quem quiser reproduzir a experiência, segue o passo a passo do projeto.

[
01
]
Requisitos

Este projeto foi desenvolvido em um ambiente WSL (Ubuntu 24.04.1 no Windows) em um PC de 64 bits. Esse tutorial considera que você está usando esse ambiente de desenvolvimento. Se você está desenvolvendo em um ambiente diferente, deve levar em consideração essas diferenças. O primeiro passo é certificar-se de que o sistema operacional está atualizado. Abra o terminal e digite:

				
					sudo apt update

sudo apt upgrade -y
				
			
[
02
]
Instalando Rust

As linhas de comando abaixo instalam o toolchain (compilador e conjunto de ferramentas):

				
					curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

. "$HOME/.cargo/env" -y
				
			

Se quiser verificar se Rust foi instalado corretamente, basta digitar:

				
					cargo --version
				
			

Cargo é o gerenciador de pacotes e de compilação do ecossistema Rust. Esse comando deverá mostrar a versão instalada do Cargo. No meu caso, 1.91.1.

[
03
]
Instalando o target

Vamos lembrar que esse é um projeto de cross-compiling. Na prática, estamos desenvolvendo em um PC Linux de 64bits (Linux x86_64), mas vamos compilar o programa para rodar num ambiente com processador ARM (Linux aarch64). Há um capítulo inteiro falando sobre esse assunto no meu livro. Para que isso seja possível, vamos usar a ferramenta rustup para instalar o target desejado:

				
					rustup target add aarch64-unknown-linux-musl
				
			

Para verificar se o target (a nova plataforma-alvo) foi instalado corretamente, digite:

				
					rustup target list --installed
				
			

Devem aparecer o target atual (que você está usando para desenvolver) e o novo target instalado. No meu caso:

aarch64-unknown-linux-musl
x86_64-unknown-linux-gnu

[
04
]
Instalando as dependências de desenvolvimento

Como estamos compilando para a plataforma Linux aarch64, o compilador Rust ainda precisa de ferramentas externas para completar a “linkagem” do binário executável. Ele até consegue fazer o primeiro estágio de compilação, gerando os arquivos “objeto”, mas quem cria o binário final é o “linker” – normalmente o ccgcc ou clang. Para instalar essas dependências, digite:

				
					sudo apt install build-essential -y
				
			
[
05
]
Clonando o repositório

Baixe e acesse o código-fonte do projeto com os comandos:

				
					git clone https://github.com/luizferreira-io/r36s-input-demo

cd r36s-input-demo
				
			
[
06
]
Compilando o programa

Para compilar o projeto, digite:

				
					cargo build --release --target aarch64-unknown-linux-musl
				
			

Com esse comando, o Cargo (gerenciador de pacotes e compilação do Rust) vai baixar as bibliotecas necessárias e compilar o projeto, tendo como target (plataforma-alvo) o ambiente aarch64-unknown-linux-musl.

O programa binário compilado estará disponível no subdiretório target/aarch64-unknown-linux-musl/release/.

[
07
]
Copiando o binário para o console e adicionando-o ao menu

A estratégia de deploy adotada nesse projeto foi instalar o programa no diretório /roms/ports. No console R36S, esse diretório é um local especial destinado a jogos e aplicações nativas, que não são ROMs de emuladores. Ele funciona como uma espécie de hub para jogos, engines e programas que são executáveis Linux reais — incluindo binários escritos por você.

A estrutura de diretórios deverá ficar assim:

/roms/ports/r36s-input-demo/r36s-input-demo
/roms/ports/R36S_Input_Demo.sh

Os arquivos marcados na cor verde são executáveis. O arquivo R36S_Input_Demo.sh deverá ter o conteúdo mostrado abaixo:

				
					#!/bin/bash

PORTNAME="R36S Input Demo"
GAMEDIR="/roms/ports/r36s_input_demo"

cd "$GAMEDIR"

if [ -z "$ESUDO" ]; then
    ESUDO="sudo"
fi

$ESUDO chmod 666 /dev/tty1 2>/dev/null
$ESUDO chmod 666 /dev/input/js0 2>/dev/null
$ESUDO chmod +x ./r36s_input_demo

$ESUDO ./r36s_input_demo < /dev/tty1 > /dev/tty1 2>&1
				
			

Para copiar os arquivos para o console, você tem duas opções:

Opção A (mais simples):

Conectar o cartão de memória no PC e copiar diretamente para o diretório correto no cartão de memória.

Opção B (requer algum conhecimento básico de Linux):

Fazer upload para o console. Para isso, você precisará ativar o wifi, se tiver um dispositivo wifi USB que encaixe nele. Também será necessário ativar os serviços remotos, acessando o menu principal Options -> ENABLE REMOTE SERVICES.

Com o serviço ativado, abra o navegador na barra de endereços, digite o endereço IP do console na sua rede wifi. Para ver o IP, acesse o menu principal Options –> WIFI –> Current Network Info.

Use o menu para fazer upload dos arquivos. Usando esse método, normalmente eles são gravados dentro do console, no diretório /roms2. Você terá que acessar o console via SSH para colocá-los no lugar certo. Para fazer isso, digite:

				
					ssh ark@IP_DO_SEU_CONSOLE
				
			

Por padrão, a senha de acesso do usuário ark é ark.

Acessando o console, coloque os arquivos nos diretórios indicados e marque-os como executáveis (chmod +x).

[
08
]
Conclusão

Fazendo isso, seu console deverá mostrar a opção PORTS no menu inicial, com o “jogo” R36S_INPUT_DEMO.

Programa final em execução.