Hackeando meu velho Nokia e72
Comprei um Nokia e72 dois meses atrás. Meu objetivo principal era experimentar um celular com teclado QWERTY e uma bateria grande (não grande em mAh, mas grande no sentido de que só preciso carregar uma vez por semana).


A UI/UX desse celular é muito estranha, porque a Nokia colocou um monte de funcionalidades em uma única tela. Mas o primeiro problema que encontrei foi TLS: o navegador não suportava nenhuma versão moderna de TLS. O aparelho parou de receber atualizações por volta de 2011, então a maioria dos sites que exigem protocolos modernos não carrega. Procurando uma solução, encontrei grupos da comunidade com vários projetos dedicados a manter esses aparelhos usáveis.

/nnproject/
Esse grupo tem muitas soluções para Nokia. Eles têm um cliente de Telegram, um cliente SSH e, o mais importante para mim, um patch que habilita TLS 1.2. Ele usa mbedTLS e funciona muito bem. Também têm alguns patches que melhoram o Java, mas não instalei, já que não pretendo instalar aplicações Java por enquanto.
Um amigo me mostrou outra coisa que é essencial nesse aparelho: o ROM Patcher. Ele me deixou "hackear" o celular e instalar qualquer aplicação que eu quisesse. O processo de instalação em si é bem divertido. Com o ROM Patcher você pode escrever dados arbitrários na memória, então a comunidade construiu bypasses para a proteção de arquivos de sistema, para aplicações não assinadas e mais. Depois de instalar, consegui instalar qualquer aplicação sem cair na verificação de certificados da Nokia.
Então: boas aplicações, uma boa comunidade, mas eu não tinha nenhum ferramental decente para escrever apps novos. Foi aí que pensei: precisamos de um SDK de verdade para aplicações nativas, um que pegue ideias emprestadas da web e de bibliotecas modernas.
E foi assim que nasceu o projeto Epoc. Um SDK para construir aplicações em Rust.
Como o Epoc funciona
Depois que a indústria migrou para outros sistemas operacionais como iOS e Android, a Symbian Foundation publicou o código-fonte do sistema, mas sem os detalhes de firmware específicos da Nokia e de outros fabricantes. Isso me deu acesso a muitas funções e me permitiu escrever uma camada em C++ para conectar meu SDK a elas. O Nokia e72 não tem GPU, então tudo na tela precisa ser desenhado à mão. Comecei por aí e, quando isso funcionou, adicionei uma nova camada de abstração para deixar o processo mais parecido com construir uma aplicação web.
Criando uma aplicação com cpp
void CRecentControl::DrawRowL(const TRect& aRect, TInt aIndex, TBool aSelected)
{
if (aSelected)
{
iBackGc->SetBrushColor(TRgb(0x2d, 0x5a, 0x8c));
iBackGc->SetBrushStyle(CGraphicsContext::ESolidBrush);
iBackGc->Clear(aRect);
}
// The row has to know it is selected in order to know what colour it is.
const TRgb name = aSelected ? TRgb(0xff, 0xff, 0xff) : TRgb(0xf0, 0xf0, 0xf0);
const TRgb time = aSelected ? TRgb(0xff, 0xff, 0xff) : TRgb(0x8a, 0x8a, 0x94);
const TRect inner(aRect.iTl.iX + KPad, aRect.iTl.iY,
aRect.iBr.iX - KPad, aRect.iBr.iY);
const CFbsFont* strong = (const CFbsFont*) iCoeEnv->NormalFont();
const CFbsFont* small = (const CFbsFont*) CEikonEnv::Static()->AnnotationFont();
iBackGc->SetBrushStyle(CGraphicsContext::ENullBrush);
// Right column first, because the left one is whatever it leaves.
// The baseline offset is measured from the bottom of the box.
TPtrC timeText(KChats[aIndex].iTime);
const TInt timeW = small->TextWidthInPixels(timeText);
const TInt timeBase = inner.Height()
- (inner.Height() - small->HeightInPixels()) / 2
- small->DescentInPixels();
iBackGc->UseFont(small);
iBackGc->SetPenColor(time);
iBackGc->DrawText(timeText, inner, timeBase, CGraphicsContext::ERight, 0);
iBackGc->DiscardFont();
TPtrC nameText(KChats[aIndex].iName);
const TRect left(inner.iTl.iX, inner.iTl.iY,
inner.iBr.iX - timeW - KPad, inner.iBr.iY);
const TInt nameBase = left.Height()
- (left.Height() - strong->HeightInPixels()) / 2
- strong->DescentInPixels();
iBackGc->UseFont(strong);
iBackGc->SetPenColor(name);
iBackGc->DrawText(nameText, left, nameBase, CGraphicsContext::ELeft, 0);
iBackGc->DiscardFont();
}
Primeira versão com a minha symbian-ui (já escrita em Rust)
fn draw_row(c: &mut Canvas<'_>, rect: Rect, theme: &Theme<'_>, index: usize, selected: bool) {
let (name, time) = CHATS[index];
let pad = theme.metrics.pad;
let inner = rect.inset_xy(pad, 0);
let (fg, dim) = if selected {
(theme.palette.selection_text, theme.palette.selection_text)
} else {
(theme.palette.text, theme.palette.dim)
};
// Right column first, because the left one is whatever it leaves.
let time_w = theme.fonts.small.measure(time);
let (right, left) = inner.split_right(time_w);
c.draw_text_in(right, time, theme.fonts.small, dim, Align::End);
c.draw_text_in(Rect { x1: left.x1 - pad, ..left },
name, theme.fonts.strong, fg, Align::Start);
}
Versão atual (usando symbian-decl-ui)
fn declared_row(index: usize) -> Group {
let (name, time) = CHATS[index];
// No `if selected`. `Ink::Text` and `Ink::Dim` resolve against the ground
// the list put them on, so the row says what it means and the highlight is
// handled where the highlight is painted.
Row::new()
.align(CrossAlign::Stretch)
.padding(Edges { left: 5, right: 5, top: 0, bottom: 0 })
.gap(5)
.child(Text::new(name).font(FontRole::Strong).ink(Ink::Text).flex(1))
.child(Text::new(time).font(FontRole::Small).ink(Ink::Dim).align(Align::End))
}

Fazendo engenharia reversa das aplicações do sistema
Depois que rodei meu primeiro exemplo em Rust no Nokia, comecei uma aplicação para navegar mais facilmente dentro do sistema e listar aplicações, ler e escrever. Então, inspirado pelo ecossistema Android, construí o meu próprio ADB para Symbian (ADBian). Com esse programa eu consigo plugá-lo no Claude Code ou em outro harness de código e ler muitos binários para entender como isso funciona. Depois de algumas semanas descobri muitas funções e comecei o processo de criar algumas aplicações usando esse SDK.


- Biblioteca de componentes;
- Tg, um cliente de Telegram (a comunidade já tem várias soluções para isso, mas construir um eu mesmo parecia divertido, mesmo que termine sendo slop);
- Cal, uma solução de calendário integrada com iCal e com suporte a notificações MTM;
- Epoc OS, uma ideia doida de mudar o processo de boot e colocar o meu launcher controlando o ciclo de vida das aplicações e montar o meu servidor de notificações dentro dele;
- Store, para tornar fácil instalar a versão mais recente de um app usando a mesma abordagem do ObtainX.


Resultados
Vou tornar tudo isso público, e tentei documentar tudo para ajudar as pessoas a construir novas aplicações. Comecei com IA porque meu objetivo principal era provar que isso podia ser viável de usar, e agora posso começar a codar no meu SDK. Portar todas as funções à mão, sem IA, teria sido lento demais, e isso é só um projeto paralelo para mim.