Files
Arch-R/FLASHER.md
Douglas Teles b36a333523 Add Flasher app specification
Complete spec for the Arch R Flasher desktop app: console type selection,
panel identification wizard, image download from GitHub releases, SD card
flashing with panel overlay auto-configuration. Supports Windows/macOS/Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:23:03 -03:00

611 lines
24 KiB
Markdown

# Arch R Flasher
> Gravador de SD card + gerenciador de overlays para Arch R.
## Conceito
App desktop cross-platform (Windows, Linux, macOS) com duas funcoes:
1. **Flash** — Grava a imagem no SD card e aplica o overlay do painel selecionado
2. **Overlay** — Altera o painel de um SD card ja gravado (sem re-flash)
O usuario nao precisa saber nada de terminal, DTBs ou overlays.
Inspirado no Raspberry Pi Imager (flash) + ROCKNIX overlay_server (painel).
## Stack
**Tauri 2** (Rust backend + web frontend)
| Camada | Tecnologia | Motivo |
|--------|-----------|--------|
| Backend | Rust (Tauri) | Acesso raw a disco, download, manipulacao FAT32. Binario ~5MB (vs ~150MB Electron) |
| Frontend | HTML/CSS/JS (vanilla ou Svelte) | UI leve, sem framework pesado |
| Disk write | Rust (`std::fs::File` + raw device) | Gravacao direta no block device |
| FAT32 | Rust (`fatfs` crate) | Leitura/escrita na particao BOOT sem montar no OS |
| Download | Rust (`reqwest`) | Busca ultima release no GitHub |
| Empacotamento | Tauri bundler | .msi (Windows), .dmg (macOS), .AppImage/.deb (Linux) |
### Por que Tauri e nao Electron?
- Binario ~5MB vs ~150MB
- Sem Node.js runtime
- Rust da acesso direto a disco sem wrappers
- Alinhado com o lema: leve como uma pluma
## Arquitetura: Overlays (nao DTBs)
A imagem universal (`ArchR-R36S-no-panel`) contem:
- `/KERNEL` — kernel Image
- `/dtbs/` — 13 board DTBs (selecionados por boot.ini via hwrev/ADC)
- `/overlays/` — todos os 20 panel DTBOs disponiveis
- `/boot.ini` — carrega board DTB + aplica `overlays/mipi-panel.dtbo`
O **board DTB** (hardware: GPIOs, PMIC, joypad, audio) e selecionado automaticamente.
O **panel overlay** (init sequences do display) e o que o Flasher configura.
### Como funciona
1. Boot.ini seleciona o board DTB correto (hwrev ADC)
2. Boot.ini tenta carregar `overlays/mipi-panel.dtbo`
3. Se existir, aplica no DTB via `fdt apply` → display funciona
4. Se nao existir, boot continua sem overlay → display pode nao funcionar
O Flasher copia o DTBO do painel selecionado como `overlays/mipi-panel.dtbo`.
---
## Abas da Interface
### Aba 1: Flash (gravar imagem + painel)
```
┌─────────────────────────────────────────────────┐
│ ARCH R FLASHER │
│ ┌──────────┐ ┌──────────┐ │
│ │ Flash │ │ Overlay │ │
│ └──────────┘ └──────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Imagem: ArchR-R36S-no-panel │ │
│ │ v1.0 beta2 (2026-03-04) │ │
│ │ [Baixar nova versao] [Selecionar local] │ │
│ └───────────────────────────────────────────┘ │
│ │
│ 1. Console: │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ R36S Original│ │ R36S Clone │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 2. Painel: │
│ ┌───────────────────────────────────────┐ │
│ │ ▼ Panel 4 (padrao, ~60%) │ │
│ │ Panel 0 │ │
│ │ Panel 1 │ │
│ │ Panel 2 │ │
│ │ Panel 3 │ │
│ │ Panel 4 ★ │ │
│ │ Panel 4-V22 │ │
│ │ Panel 5 │ │
│ │ R46H (1024x768) │ │
│ └───────────────────────────────────────┘ │
│ │
│ 3. Destino: │
│ ┌───────────────────────────────────────┐ │
│ │ ▼ /dev/sdc (32GB SD Card) │ │
│ └───────────────────────────────────────┘ │
│ [Atualizar lista] │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ ★ GRAVAR ★ │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ████████████████░░░░░░░░ 67% 2.1GB/3.1GB │
│ Gravando imagem... │
│ │
└─────────────────────────────────────────────────┘
```
### Aba 2: Overlay (trocar painel sem re-flash)
```
┌─────────────────────────────────────────────────┐
│ ARCH R FLASHER │
│ ┌──────────┐ ┌──────────┐ │
│ │ Flash │ │ Overlay │ │
│ └──────────┘ └──────────┘ │
│ │
│ SD Card com Arch R: │
│ ┌───────────────────────────────────────┐ │
│ │ ▼ /dev/sdc (32GB SD Card) │ │
│ └───────────────────────────────────────┘ │
│ [Atualizar lista] │
│ │
│ Overlay atual: panel4.dtbo │
│ Console detectado: original │
│ │
│ Novo painel: │
│ ┌───────────────────────────────────────┐ │
│ │ ▼ Panel 3 │ │
│ └───────────────────────────────────────┘ │
│ │
│ Ajustes (opcional): │
│ ┌─────────────────────────────────────┐ │
│ │ Rotacao: [0°] [90°] [180°] [270°] │ │
│ │ │ │
│ │ Inversao de stick: │ │
│ │ [ ] Esquerdo [ ] Direito │ │
│ │ │ │
│ │ Audio: │ │
│ │ (•) Auto ( ) Amplificado │ │
│ │ ( ) Simples │ │
│ │ │ │
│ │ Inversao HP: [ ] │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ ★ APLICAR OVERLAY ★ │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ✓ Overlay aplicado! Insira o SD e reinicie. │
│ │
└─────────────────────────────────────────────────┘
```
---
## Paineis por Console
Cada painel tem seu proprio DTBO com init sequences nativas.
O Flasher copia o DTBO selecionado como `overlays/mipi-panel.dtbo`.
### R36S Original (8 paineis)
| Nome | DTBO | Padrao |
|------|------|--------|
| Panel 0 | panel0.dtbo | |
| Panel 1 | panel1.dtbo | |
| Panel 2 | panel2.dtbo | |
| Panel 3 | panel3.dtbo | |
| Panel 4 | panel4.dtbo | ★ padrao |
| Panel 4-V22 | panel4-v22.dtbo | |
| Panel 5 | panel5.dtbo | |
| R46H (1024x768) | r46h.dtbo | |
### R36S Clone (12 paineis)
| Nome | DTBO | Padrao |
|------|------|--------|
| Clone 1 (ST7703) | clone_panel_1.dtbo | |
| Clone 2 (ST7703) | clone_panel_2.dtbo | |
| Clone 3 (NV3051D) | clone_panel_3.dtbo | |
| Clone 4 (NV3051D) | clone_panel_4.dtbo | |
| Clone 5 (ST7703) | clone_panel_5.dtbo | |
| Clone 6 (NV3051D) | clone_panel_6.dtbo | |
| Clone 7 (JD9365DA) | clone_panel_7.dtbo | |
| Clone 8 G80CA (ST7703) | clone_panel_8.dtbo | ★ padrao |
| Clone 9 (NV3051D) | clone_panel_9.dtbo | |
| Clone 10 (ST7703) | clone_panel_10.dtbo | |
| R36 Max (ST7703 720x720) | r36_max.dtbo | |
| RX6S (NV3051D) | rx6s.dtbo | |
---
## Fluxo: Aba Flash
### Passo a passo
1. **App inicia** e busca a ultima release `ArchR-R36S-no-panel-*.img.xz` no GitHub Releases
2. **Imagem local ou download**: se ja tem uma imagem local, usa ela. Senao, oferece download
3. **Selecionar console**: R36S Original ou R36S Clone (dois botoes grandes)
4. **Selecionar painel**: dropdown muda conforme o console selecionado
5. **Selecionar destino**: lista de discos removiveis (SD cards)
6. **Gravar**: confirmacao ("Todos os dados do SD serao apagados"), barra de progresso
7. **Pos-gravacao**: injeta overlay + variant no BOOT (FAT32)
8. **Concluido**: "SD card pronto! Insira no R36S e ligue."
### Etapa 1: Gravar imagem raw
```
imagem.img.xz → descompacta on-the-fly → dd no block device
```
- Descompressao streaming (xz -> raw -> disco) para nao precisar de espaco extra
- Gravacao em blocos de 4MB (`bs=4M`)
- Barra de progresso baseada no tamanho descomprimido
### Etapa 2: Pos-gravacao (injetar configuracao)
Apos gravar a imagem raw, a particao BOOT (FAT32, particao 1) ja existe no SD. O Flasher:
1. **Abre a particao FAT32** diretamente no block device (sem montar no OS)
- Usa crate `fatfs` em Rust para ler/escrever FAT32 via offset no disco
- Offset da particao 1: lido da tabela de particoes (GPT ou MBR)
2. **Copia o DTBO do painel selecionado como `overlays/mipi-panel.dtbo`**:
- Le o DTBO da pasta `overlays/` que ja esta na imagem (ex: `overlays/panel3.dtbo`)
- Copia como `overlays/mipi-panel.dtbo`
- Boot.ini vai aplicar esse overlay no proximo boot
3. **Escreve `panel-confirmed`**: conteudo "confirmed\n" (wizard nao roda)
4. **Escreve `variant`**: "original\n" ou "clone\n"
- Arquivo na particao BOOT (FAT32)
- Systemd service no primeiro boot copia para `/etc/archr/variant`
5. **Sync e fecha** (fsync obrigatorio em FAT32!)
### Diagrama
```
SD Card (pos-gravacao):
Offset 0 32KB 128MB ~3.2GB
├─── U-Boot ──────┤── BOOT (FAT32) ──┤──── STORAGE (ext4) ───┤
│ │
├── KERNEL │
├── boot.ini │
├── dtbs/ │ 13 board DTBs (auto-select)
│ ├── rk3326-gameconsole-r36s.dtb
│ ├── rk3326-gameconsole-r36max.dtb
│ └── ... (13 total)
├── overlays/ │
│ ├── mipi-panel.dtbo ◄── escrito pelo Flasher
│ ├── panel0.dtbo
│ ├── panel4.dtbo
│ ├── clone_panel_8.dtbo
│ └── ... (20 total)
├── panel-confirmed ◄── escrito pelo Flasher
└── variant ◄── escrito pelo Flasher
```
---
## Fluxo: Aba Overlay
Permite trocar o painel de um SD card Arch R **sem re-gravar a imagem**.
Util quando o usuario troca de aparelho ou recebe um com painel diferente.
### Passo a passo
1. **Inserir SD card** com Arch R ja gravado
2. **Selecionar SD**: app detecta que tem Arch R (verifica presenca de `KERNEL` e `dtbs/`)
3. **Detectar estado atual**: le `overlays/mipi-panel.dtbo` e compara com os DTBOs conhecidos
4. **Selecionar novo painel**: dropdown (mesmo que na aba Flash)
5. **Ajustes opcionais** (inspirado no ROCKNIX overlay_server):
- Rotacao do display (0/90/180/270)
- Inversao de sticks analogicos (esquerdo/direito)
- Modo de audio (auto/amplificado/simples)
- Inversao da deteccao de headphone
6. **Aplicar**: copia DTBO selecionado para `overlays/mipi-panel.dtbo` + fsync
7. **Concluido**: "Overlay aplicado!"
### Deteccao do overlay atual
Para mostrar qual painel esta ativo, o Flasher:
1. Le `overlays/mipi-panel.dtbo` do SD card
2. Calcula hash (MD5/SHA256)
3. Compara com hashes dos 20 DTBOs conhecidos (embutidos no app)
4. Se match: mostra nome do painel
5. Se nao match: "Overlay personalizado" (pode ter sido gerado pelo overlay_server)
### Ajustes de painel (inspirado ROCKNIX overlay_server)
O overlay_server do ROCKNIX gera overlays a partir de DTBs de estoque, suportando
modificacoes como rotacao, inversao de sticks, e audio routing. O Flasher integra
essas opcoes diretamente:
| Ajuste | Flags | Descricao |
|--------|-------|-----------|
| Rotacao | DR0, DR90, DR180, DR270 | Rotacao do display (0-270 graus) |
| Inversao stick esquerdo | LSi | Inverte eixos X/Y do stick esquerdo |
| Inversao stick direito | RSi | Inverte eixos RX/RY do stick direito |
| Audio amplificado | SRa | Usa amplificador externo (rk817-sound-amplified) |
| Audio simples | SRs | Roteamento direto SPK/HP (rk817-sound-simple) |
| Inversao HP detect | HPi | Inverte polaridade da deteccao de headphone |
Quando o usuario seleciona ajustes, o Flasher **modifica o DTBO em memoria** antes
de gravar no SD card:
1. Le o DTBO base do painel selecionado
2. Aplica as modificacoes (altera propriedades no FDT)
3. Grava o DTBO modificado como `overlays/mipi-panel.dtbo`
Isso e feito puramente em Rust usando a crate `fdt` para manipulacao binaria do DTB.
---
## Deteccao de Imagem
### Fonte: GitHub Releases
```
GET https://api.github.com/repos/archr-linux/Arch-R/releases/latest
```
Procura por asset com nome `ArchR-R36S-no-panel-*.img.xz`.
### Cache local
- Imagens baixadas ficam em:
- Windows: `%APPDATA%\ArchR-Flasher\images\`
- macOS: `~/Library/Application Support/ArchR-Flasher/images/`
- Linux: `~/.local/share/archr-flasher/images/`
- Se ja tem a versao mais recente localmente, nao baixa de novo
- Botao "Selecionar arquivo local" para imagens baixadas manualmente
## Deteccao de Discos
### Linux
```rust
// Listar /sys/block/sd* e /sys/block/mmcblk*
// Filtrar por removable=1
// Ler size, vendor, model
```
### macOS
```rust
// diskutil list -plist
// Filtrar por removable=true, protocol=USB ou SD
```
### Windows
```rust
// WMI: Win32_DiskDrive WHERE MediaType='Removable Media'
// Ou: SetupDiGetClassDevs + DeviceIoControl
```
### Protecoes
- **Nunca listar discos fixos** (HDD/SSD do sistema)
- **Filtro de tamanho**: ignorar discos > 128GB (provavelmente nao e SD card do R36S)
- **Confirmacao com nome do disco**: "Gravar em SANDISK 32GB (/dev/sdc)? TODOS OS DADOS SERAO APAGADOS."
- **Bloquear disco do sistema**: nunca permitir gravar no disco onde o OS esta rodando
### Validacao de SD Arch R (aba Overlay)
Para a aba Overlay, o Flasher precisa verificar que o SD card ja tem Arch R:
1. Montar (ou ler via fatfs) a particao 1
2. Verificar presenca de: `KERNEL`, `dtbs/`, `overlays/`, `boot.ini`
3. Se presente: SD valido, mostrar overlay atual
4. Se ausente: "Este SD card nao contem Arch R"
## Permissoes de Disco
### Linux
- Precisa de root para gravar em block device
- Usar `pkexec` para elevar privilegios (sem terminal)
- Aba Overlay: tambem precisa de `pkexec` para escrever no SD
### macOS
- `diskutil unmountDisk /dev/diskN` antes de gravar
- Precisa de permissao de admin (Authorization Services)
### Windows
- Precisa de admin para abrir `\\.\PhysicalDriveN`
- UAC prompt automatico via manifesto do executavel
## Estrutura do Projeto
```
archr-flasher/
├── src-tauri/
│ ├── src/
│ │ ├── main.rs # Entry point Tauri
│ │ ├── disk.rs # Deteccao e listagem de discos
│ │ ├── flash.rs # Gravacao raw + descompressao xz
│ │ ├── overlay.rs # Leitura/escrita de overlays no SD
│ │ ├── fat32.rs # Leitura/escrita FAT32 direta
│ │ ├── github.rs # Download de releases
│ │ ├── panels.rs # Definicao dos paineis + hashes DTBOs
│ │ └── dtb_modify.rs # Modificacao binaria de DTBOs (rotacao, sticks, audio)
│ ├── Cargo.toml
│ └── tauri.conf.json
├── src/
│ ├── index.html # UI principal (2 abas: Flash + Overlay)
│ ├── style.css # Estilo (tema escuro, cores Arch R)
│ └── main.js # Logica de UI
├── assets/
│ ├── icon.png # Icone do app (logo Arch R)
│ └── i18n/ # Traducoes
│ ├── en.json
│ └── pt-BR.json
└── README.md
```
## Dependencias Rust (Cargo.toml)
```toml
[dependencies]
tauri = { version = "2", features = ["shell-open"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
xz2 = "0.1" # Descompressao .xz streaming
fatfs = "0.4" # Leitura/escrita FAT32 sem montar
gpt = "3" # Leitura de tabela de particoes GPT
byteorder = "1" # Leitura MBR
fdt = "0.1" # Manipulacao binaria de DTB/DTBO
md-5 = "0.10" # Hash para identificar overlay atual
```
## UI: Tema Visual
- **Fundo**: preto (#0a0a0a)
- **Destaque**: azul Arch (#1793D1)
- **Texto**: branco (#f0f0f0)
- **Botoes**: azul Arch com hover mais claro
- **Progresso**: barra azul gradiente
- **Logo**: "ARCH R" no topo (mesma fonte Quantico do splash)
- **Abas**: Flash e Overlay (tab bar no topo)
- **Ajustes**: secao colapsavel na aba Overlay (expandir para ver opcoes avancadas)
## Internacionalizacao (i18n)
O Flasher deve ser multilinguagem. O idioma e detectado automaticamente pelo OS, com fallback para ingles.
### Idiomas
| Codigo | Idioma | Status |
|--------|--------|--------|
| en | English | Base (fallback) |
| pt-BR | Portugues (Brasil) | Prioritario |
| es | Espanol | Futuro |
| zh | Chinese | Futuro |
### Implementacao
Arquivo JSON por idioma em `assets/i18n/`.
Exemplo `en.json`:
```json
{
"title": "ARCH R FLASHER",
"tab_flash": "Flash",
"tab_overlay": "Overlay",
"image": "Image",
"no_image": "No image selected",
"select_file": "Select file",
"download_latest": "Download latest version",
"console": "Console",
"panel": "Panel",
"select_panel": "Select panel",
"destination": "Destination",
"select_sd": "Select SD card",
"no_sd": "No SD card found",
"refresh": "Refresh list",
"flash": "FLASH",
"confirm_title": "Confirm flash",
"confirm_text": "Flash Arch R to {disk}?",
"confirm_warning": "ALL DATA ON THE SD CARD WILL BE ERASED!",
"cancel": "Cancel",
"writing": "Writing image...",
"syncing": "Syncing...",
"configuring": "Applying panel overlay...",
"done": "SD card ready! Insert in R36S and power on.",
"recommended": "recommended",
"overlay_current": "Current overlay",
"overlay_new": "New panel",
"overlay_custom": "Custom overlay",
"overlay_apply": "APPLY OVERLAY",
"overlay_done": "Overlay applied! Insert SD and reboot.",
"overlay_not_archr": "This SD card does not contain Arch R.",
"adjustments": "Adjustments (optional)",
"rotation": "Rotation",
"stick_inversion": "Stick inversion",
"stick_left": "Left",
"stick_right": "Right",
"audio_mode": "Audio mode",
"audio_auto": "Auto",
"audio_amplified": "Amplified",
"audio_simple": "Simple",
"hp_inversion": "Invert HP detection"
}
```
### Deteccao de idioma
1. Tauri detecta o locale do OS via `sys_locale` crate
2. Frontend recebe o locale via comando Tauri
3. Carrega o JSON correspondente (ou `en.json` como fallback)
4. Todas as strings da UI vem do arquivo de traducao
## Build e Release
### Compilacao
```bash
# Requisitos: Rust, Node.js (para Tauri CLI)
cargo install tauri-cli
# Dev
cargo tauri dev
# Build release (gera instalador por plataforma)
cargo tauri build
```
### CI/CD (GitHub Actions)
```yaml
# .github/workflows/flasher-release.yml
# Matrix: ubuntu-latest, macos-latest, windows-latest
# Cada um gera: .AppImage/.deb, .dmg, .msi
# Upload como assets na release do Flasher
```
### Distribuicao
| Plataforma | Formato | Tamanho estimado |
|-----------|---------|-----------------|
| Linux | .AppImage + .deb | ~8MB |
| macOS | .dmg | ~10MB |
| Windows | .msi | ~8MB |
## Primeiro Boot (pos-Flasher)
O SD gravado pelo Flasher ja tem tudo configurado:
1. Boot.ini seleciona board DTB correto via hwrev (automatico)
2. Boot.ini aplica `overlays/mipi-panel.dtbo` (painel selecionado pelo Flasher)
3. Display funciona desde o primeiro boot
4. `variant` na particao BOOT = "original" ou "clone"
5. `panel-confirmed` presente = wizard nao roda
6. Systemd service le `variant` do BOOT e copia para `/etc/archr/variant`
7. EmulationStation inicia normalmente
**Zero configuracao no device.** Liga e usa.
## Systemd: Variant Sync Service
Service no rootfs para copiar `variant` do BOOT:
```ini
# /etc/systemd/system/archr-variant-sync.service
[Unit]
Description=Sync variant from BOOT partition
After=local-fs.target
RequiresMountsFor=/boot
ConditionPathExists=!/etc/archr/variant
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'test -f /boot/variant && cp /boot/variant /etc/archr/variant'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
```
Roda apenas uma vez (ConditionPathExists). Apos copiar, nao roda mais.
## Fases de Desenvolvimento
### Fase 1: Core funcional
- [ ] Projeto Tauri inicializado
- [ ] UI basica com 2 abas (Flash + Overlay)
- [ ] Selecao console + painel + disco
- [ ] Gravacao de imagem local (.img, sem .xz)
- [ ] Pos-gravacao: copiar DTBO + variant + panel-confirmed
- [ ] Aba Overlay: trocar overlay sem re-flash
- [ ] Testar em Linux
### Fase 2: Polish
- [ ] Descompressao .xz streaming
- [ ] Download automatico do GitHub Releases
- [ ] Cache de imagens
- [ ] Deteccao de overlay atual (hash comparison)
- [ ] Ajustes avancados (rotacao, sticks, audio)
- [ ] Deteccao de discos (Linux + macOS + Windows)
- [ ] Barra de progresso real
### Fase 3: Release
- [ ] CI/CD para 3 plataformas
- [ ] Icone e branding
- [ ] i18n (pt-BR + en)
- [ ] Testes em hardware real (Original + Clone)
- [ ] Publicar na pagina do projeto