Featured image of post Quer criar um emulador? Entenda primeiro como emuladores funcionam

Quer criar um emulador? Entenda primeiro como emuladores funcionam

Uma orientação sobre como emular outros dispositivos

Hoje em dia, é possível jogar praticamente qualquer jogo antigo em qualquer lugar, bastando apenas baixar um emulador e os jogos para a plataforma que deseja, sem custo nenhum. Como eles conseguem dar suporte a jogos originais, caso você conecte um leitor de cartuchos ou CD? Como é possível adicionar funcionalidades novas, como save states, aumentar velocidade do jogo, inserir cheats, jogar online ou incluir achievements/conquistas, sendo que o console original não tinha estas capacidades?

Ou mais importante: como o programa roda estes jogos? Se eu for um programador, posso criar um emulador também?

Mas o que é um emulador?

A primeira coisa que vem à cabeça das pessoas é que emuladores são programas que imitam outros computadores, de forma a poder executar jogos ou programas, sem precisar comprar o aparelho original. De certa forma esta frase está correta, mas não é apenas um PROGRAMA. Uma emulação por hardware também é possível, sendo esta uma das formas mais comuns de emulação no começo da era da computação. Computadores antigamente não eram padronizados ou compatíveis entre si, como hoje ocorre. Produtos Commodore, Apple e IBM eram totalmente proprietários e não rodavam em seus concorrentes, mesmo que os disquetes ou fitas cassete fossem iguais. Com isso, algumas empresas basicamente condensavam as peças inteiras de um computador e vendiam em placas (parecidas com as placas de vídeo de hoje) que encaixavam em outros aparelhos, para conseguir ler arquivos e programas do seu concorrente.

Tanto a Sega quanto a Nintendo já fizeram este tipo de emulação por hardware nos anos 80 e 90. O Super Game Boy era um Game Boy inteiro em um cartucho de Super Nintendo e que havia uma entrada de cartuchos para rodar estes jogos no console, enquanto a Sega já lançou o Power Base Converter para rodar jogos de Master System no Mega Drive. A diferença é que este converter não possui um console inteiro no adaptador, porque o próprio Mega Drive já utilizou o mesmo processador do Master System (Zilog Z80) para outra função no videogame (áudio), o que bastava apenas alterar a forma que o sistema trabalha e ler os cartuchos de Master System do adaptador.

Ainda assim, como este circuito inteiro não possui os periféricos do produto original que foi criado, é necessário fazer um “driver” para ele, que é o que vai converter as informações para algo que o computador reconheça e saiba o que executar. Ou seja, também precisa de um programa que defina os comportamentos do sistema antigo, que é um EMULADOR.

Como criamos um programa que “imita” outra máquina?

Como tudo em programação, precisamos entender o que nosso programa precisa fazer. Já sabemos que ele quer “se passar por outra máquina”, então ele precisa executar as mesmas funções que o computador faz. Só que, para quê você quer isso?

Você quer que ele rode apenas UM jogo ou programa em seu computador? Se for, talvez faça mais sentido recriar o programa com a tecnologia atual e copiar o visual e funcionalidade.

Você quer uma CÓPIA IDÊNTICA ao computador original para rodar TUDO? Se sim, em qual nível de fidelidade você quer? Você pode imitar apenas o comportamento dos componentes internos ou ser ainda mais preciso, copiando até a entrada e saída de voltagem daquele aparelho. Quanto maior a fidelidade, mais complexo fica.

Muito provavelmente você pretenda apenas simular os comportamentos internos do console/computador, já que você não é doido só quer entender como um emulador funciona. Neste caso, você já tem um primeiro passo: entender como aquele aparelho funciona. E não é uma tarefa relativamente fácil.

Consoles dos anos 80, como NES e Master System, eram aparelhos bem fraquinhos e relativamente simples, mas ainda assim são computadores. A CPU (Unidade Central de Processamento) entende apenas 0 e 1, que é a ausência ou presença de corrente elétrica e que, com isso, consegue realizar cálculos aritméticos como soma e subtração. Porém, isto é apenas a CPU, então precisamos entender como cada uma das peças funciona, pra que servem e como se comunicam com a CPU. Logo, sua primeira tarefa é descobrir como é o console por dentro e como cada peça conversa entre si.

Antigamente, os jogos eram feitos em Assembly proprietário de cada processador, porque memória era algo extremamente caro. Por conta disso, o espaço reservado pra algum tipo de programa inicial era… extremamente limitado e não cabia bibliotecas inteiras para usar linguagens mais complexas, como C, Fortran ou BASIC. Além disso, escrever nessas linguagens deixaria o programa mais pesado ainda, porque o computador precisaria primeiro transcrever seu código para linguagem de máquina e, depois disso, executar. A otimização antigamente não era tão boa, então uma linguagem de mais alto nível gerava um código assembly menos performático.

Começando então pelo básico

Se você não é da área de computação, entenda que: Computadores precisam sempre de um processador (CPU), uma memória e uma forma de entrada de dados (input), seja físico ou digital. O input é basicamente uma série de passos, em ordem, que o processador ficará encarregado de executar e, quando necessário, gravar algum valor que será usado depois, salva na memória. Pense como se você tivesse em casa uma daquelas máquinas de cozinha que é um faz-tudo: aquece a comida, bate os alimentos que estão dentro, assa, refoga e possui um compartimento que pode reservar os alimentos quando necessário. O input seria a receita que você quer fazer naquele momento, a máquina é a CPU que vai ler cada um dos passos da receita e executá-los em ordem e, se for necessário separar alguma mistura que não será usada por agora, ela guarda nesse compartimento até que precise dela novamente.

Logo, provavelmente sua primeira tarefa para construir o emulador será criar um programa que permita executar todas as instruções que o processador sabe fazer. Se pegarmos o Zilog Z80, processador de diversos consoles e computadores como ZX Spectrum, Master System, MSX, ColecoVision e outros, vai descobrir que ele tem 158 instruções diferentes (pode ver aqui uma matriz com todos os comandos, ou opcodes). Segue a imagem para exemplo:

Opcodes do z80 tirado do site pastraiser.com

Está perdido? Então você nunca teve uma aula de Arquitetura de Computadores na Faculdade ou nunca viu nenhum tipo de linguagem Assembly na vida. Esse vai ser sua primeira tarefa então: aprender Z80 Assembly para fazer isso. Caso você nunca tenha estudado programação, não se assuste! Estes assuntos já são um pouco mais avançados e você não vai entender nada.

Agora, se você já percebeu que isso tudo é comando Assembly do Z80 e o que cada uma faz, parabéns. Você agora tem que implementar CADA INSTRUÇÃO COMO SUA PRÓPRIA FUNÇÃOZINHA na linguagem que você escolheu criar o seu emulador. Boa sorte 😛

Isso sem falar que você também tem que criar os registradores (o A, H, L, B, C, D, Z, etc.) que existem para o z80. Registradores são basicamente memórias minúsculas que o processador tem dentro dele e usa pra fazer alguma tarefa. O registrador A, por exemplo, é o acumulador, que serve para fazer cálculos de soma e subtração. Z é um registrador que guarda se o resultado do cálculo feito é igual a 0 ou não e por aí vai. Cada um vai ter sua funcionalidade.

E, se você notar, a tabela tem colunas e linhas que vão de 0 a F. Isso significa que….

É hora de aprender a contar números de formas diferentes

Lembra das aulas de matemática, que você aprendia a contar de 1 a 10, depois de 10 a 100 e por aí vai? Então, este método é a contagem em notação decimal. Ou seja, todos os números são escritos na base 10. Se formos representar o número 33 (trinta e três):

$$ 33 = $$

$$ 30 + 3 = $$

$$ 3.10^1 + 3.10^0 $$

Porém, essa não é a única forma de representar números. As mais comuns que usamos são a decimal, binária, octal e hexadecimal. E é com os binários e hexadecimais que queremos chegar. Notação binária você utiliza apenas dois números para cada “unidade”: 0 e 1. Convertendo os números decimais em binários, seria algo assim:

0 = 0

1 = 1

2 = 10 (não dá pra representar mais números com 1 unidade, então adicionamos mais um número binário)

3 = 11

4 = 100

Esta é a forma que o computador entende: uma fita de bits (dígitos binários). Se quisermos converter pra decimal, fazemos o mesmo cálculo que fizemos anteriormente na base 10, mas dessa vez na base 2 (por isso binário) Quanto é 01001000 em decimal, por exemplo?

$$ 01001000 $$

$$ 0.2^7 + 1.2^6 + 0.2^5 + 0.2^4 + 1.2^3 + 0.2^2 + 0.2^1 + 0.2^0 $$

$$ 0 + 64 + 0 + 0 + 8 + 0 + 0 + 0 $$

$$ 72 $$

Perceberam que a quantidade de unidades em binário cresce muito rápido? Isso é HORRÍVEL para seres humanos. Como são sempre múltiplos de 2 que crescem muito rápido, decimal não é uma forma muito boa de representar os números. Por isso, as pessoas criaram a notação hexadecimal, para que agrupassemos 4 bits em uma única unidade. Assim, o número demora a crescer.

Você deve estar em dúvida como representamos 16 números com uma unidade, se na decimal usamos 2 unidades (10 + 6). Para isso, usamos letras. Então contamos de 0 a 9 e, a partir do 10, usamos A e vamos até F, que é 15.

Como ficaria então o 72 em hexadecimal? 48

$$ 48 $$

$$ 4.16^1 + 8.16^0 $$

$$ 64 + 8 $$

Precisa saber calcular isso tudo de cabeça? Obviamente não! É loucura saber isso de primeira e vale mais a pena usar uma calculadora que converte isso, mas pode ter certeza que hexadecimal é uma notação muito usada na computação, principalmente na programação em assembly que você precisa acessar espaços de memória, que são números gigantescos. Caso você veja algum código-fonte de z80 assembly, vai ver que os números são representados em hexadecimal junto com um sinal $, como por exemplo o que está no site SMSPower:

ld bc, $4000    ; Load register pair BC with number $4000
out ($be),a     ; Output to port number $be the number stored in register A

$4000, em decimal, é 16384. Totalmente aleatório, não é? Mais fácil lembrar 4000 do que 16384, neste contexto. Você também pode ver representação hexadecimal nas linguagens de programação mais novas como 0x, antes dos números.

Então está aí mais um tema pra você aprender enquanto cria seu emulador: representar e identificar números em hexadecimal. Você vai me agradecer por isso.

Operações Bitwise - Toda linguagem de programação tem, mas você quase não usa

Aqui, neste ponto, só deve ter ficado gente que entende de computação, né? Se não, você é corajoso por ficar até aqui e não me espanta se não tiver entendido nada 😸 estes assuntos não fazem sentido se jogados sem um conhecimento antecipado. Se quiser, faço um texto BEEEEEEEM mais simples com o básico de computação. Garanto que, ali, vai fazer muito mais sentido, mas neste post não é este o propósito.

Em linguagens de programação novas, vocês com certeza já aprenderam sobre operações condicionais (if/else) e conhecem os operadores lógicos AND ( && ) e Or ( || ), não é mesmo?

Muito provavelmente, em algum momento vocês devem ter digitado apenas um & ou | e repararam que isso existe, só que o resultado não é o esperado. Estes são os operadores bitwise! Ao invés de compararem os valores que vocês repassaram dentro desses condicionais, ele faz uma operação lógica bit a bit do número. Basicamente, converte os dois números em binários e faz a comparação lógica de cada bit. Em Java, por exemplo:

int a = 12;
int b = 7;

System.out.println(a & b);

O terminal irá imprimir 4. Por quê?

12 = 1100

7 = 0111

Fazendo a operação bitwise AND (&), ele só vai deixar ter resultado 1 se os dois bits em ambos os números estiverem 1. Então, o resultado será 0100, já que ambos estão com bit 1 apenas na segunda posição. E 0100 em binário é 4

12 7 Resultado
1 0 0
1 1 1
0 1 0
0 1 0

E por que estou ensinando esses conceitos de operações bitwise? Na criação de emuladores, você vai precisar fazer alguns cálculos ali que usam exatamente essas operações nas operações do processador. Então, é bom aprender estas operações e como usá-las em sua linguagem de programação favorita, senão você vai estar em apuros. E tem outros que você vai usar, como:

Simbolo Comando
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
! (ou ~) Negação
>> Shift right (move os bits uma casa para a direita)
<< Shift left (move os bits uma casa para a esquerda)

Acabou por aqui?

Como você percebeu, não é uma tarefa tão simples de criar um emulador, mas é algo extremamente satisfatório e recompensador, porque todos esses conhecimentos não são usados apenas para emuladores. Lembrem-se que os computadores atuais funcionam da mesma forma. A diferença é que usamos linguagens de programação bem mais próximas da linguagem humana, já que temos espaço de sobra para usar essas abstrações que facilitam a nossa vida. Mesmo assim, estes conhecimentos podem fazer toda a diferença em uma entrevista de emprego ou na resolução de um problema de programação que dá errado relacionados à memória ou execução de instrução.

Estas informações que passei nos tópicos acima são só para representar o processador. Imagine que há uma gama gigantesca de coisas a mais que você precisará saber, como, por exemplo, a forma que o console faz o desenho da tela, como o áudio é gerado, como se detecta que um botão do controle foi apertado e muito mais. Tenho certeza de que você vai ter muito estudo aí pela frente 😛

Criado com Hugo
Tema Stack desenvolvido por Jimmy