Contenido
Intro
Entonces, ¿por qué escribir un emulador? Para mí, las chispas fueron la nostalgia y la curiosidad. Luego, la emoción y el familiar dolor de cabeza de «¿por qué esto no funciona?», Seguido por el orgullo y una sensación de logro cuando vi gráficos en la pantalla. 🎉🎉🎉
Sí, escribir emuladores puede ser increíblemente divertido. Experimentarás estos sentimientos mientras lo haces (o cualquier cosa desafiante). 😎
Cómo comencé a hacer emuladores
Omite esta parte si solo quieres aprender a hacer emuladores. Quédate para disfrutar de una dosis de nostalgia y genialidad de la década de 2000.
A principios de la década de 2000, yo era un niño obsesionado con los videojuegos. Mi familia compró nuestra primera computadora alrededor de noviembre de 2001. Era una máquina bestial con Pentium 4 1.7Ghz, 256 MB de RAM y GeForce 2 MX. Sin embargo, mi acceso a él era limitado, ya que me dijeron que «las computadoras son malas» y «ve a jugar afuera» . 😂😂😭
Afortunadamente, yo era uno de esos niños que traducen «no permitido» por «debo hacer esto obsesivamente» y poco a poco me retorcí de mi parte del tiempo de juego. Mis padres descubrieron rápidamente que tenía talento para romper cosas y la computadora aterrizó en el taller de reparación o le borraron los datos (o ambos) varias veces al año. 😈😛
La forma más rápida de aprender a instalar Windows XP a los 6 años es eliminar system32 2 h antes de que tu padre regrese del trabajo. 😱
Junto con este lío, comencé a salir con un niño que tenía una GameBoy y visitaba habitualmente un club después de la escuela con una PlayStation 2. Hacíamos cola para competir en Tony Hawk’s American Wasteland y FIFA.
THAW es uno de los mejores juegos de patinaje de todos los tiempos, por cierto. 🎮🛹
Alrededor de 2007, conseguí una PSP y la modifiqué rápidamente para permitir ejecutar juegos desde la tarjeta de memoria. Descubrí que además de juegos, podía ponerle otras cosas. Sí, lo has adivinado: emuladores. Imagina ser un niño pequeño y darte cuenta de que puedes jugar juegos de GBA en tu PSP. ¡ÉPICO! ¡Papá Noel está en la ciudad! 1 🎅🎅🎅
Alrededor de 2010 mi interés por las computadoras se aceleró y comencé a aprender a programar en C++. Ni siquiera pensé en hacer un emulador por mi cuenta. Eso estaba reservado para Grand Wizards y John Carmack. Tal vez podría juntar un clon de Pong con SDL 1.2 .
En ese entonces, me involucré en una comunidad en torno al desarrollo de un juego independiente, Ether Fields, que ayudó a alimentar mi pasión por la programación.
RIP Ether Fields, serás recordado. 😥
No tuve un gran avance con EmuDev (desarrollo de emuladores) hasta mucho más tarde. Me topé con una transmisión en vivo que era el primer episodio de la serie Ferris Makes Emulators . Inmediatamente me interesé y vi cada episodio a medida que salían. ¡ El tipo estaba haciendo un emulador en vivo ! ¡Y parecía fácil ! Parecía que podía hacerlo.
Teniendo la motivación y sabiendo que era posible, me armé de valor y escribí mi primer emulador de GameBoy. 🤠🤠🤠
Entonces, ¡hablemos de hacer eso!
Cómo hacer un emulador
Aprenderemos cómo funcionan las consolas y cómo emularlas. Esta parte va a ser mitad técnica y mitad orientada al proceso.
Nuestro objetivo es aprender a hacer un emulador por nuestra cuenta. Por eso, no quería escribir una guía paso a paso para hacer uno específico. Más bien, nos centraremos en el proceso.
Como dice el refrán, dale fuego a un hombre y estará caliente por un día; prende fuego a un hombre y estará caliente por el resto de su vida.
Oh, espera… De todos modos, ¡entiendes el punto! 🍺😁
Elijamos una consola para emular. Lo usaremos como base para el resto del artículo.
Las opciones populares para los emuladores de nivel principiante son:
- CHIP-8
- Gameboy clásico
- NES
Vamos a elegir CHIP-8 porque es, con mucho, el más simple y podemos usarlo para estudiar los conceptos básicos.
Te recomiendo encarecidamente que escribas un emulador de GameBoy Classic o NES por tu cuenta. Es increíblemente divertido y una satisfacción increíble cuando terminas y lo ves ejecutar juegos reales. 👾👾👾
Research
Antes de escribir cualquier código, querremos investigar la consola que estamos tratando de emular.
Necesitamos saber cosas como:
- ¿Qué tan complejo es?
- ¿Podemos obtener suficiente documentación para ello?
- ¿Podemos (legalmente) obtener juegos para ello?
Recomiendo buscar documentos de referencia técnica y hojearlos. Los hilos para principiantes en las comunidades de EmuDev también son buenos lugares para comenzar.
Aquí hay dos lugares de reunión de la comunidad EmuDev:
Déjame mostrarte las cosas que usaremos para CHIP-8:
Tenemos dos documentos de referencia que describen el funcionamiento interno de CHIP-8, otro emulador y una colección de ROM de dominio público. ¡Frio! 😎
Espere tener que buscar cosas durante el desarrollo. Empecé solo con el documento de Cowgod y recopilé el resto en el camino.
Múltiples documentos de referencia ayudarán cuando encontremos errores y/o ambigüedades. Y nos encontraremos con ellos.
Bien, estamos listos para empezar a escribir.
Elección de idioma
Bueno, casi. Todavía tenemos que elegir un idioma. 😁
Podría pensar que deberíamos elegir un lenguaje «rápido» como C o Rust. Mucha gente comienza de esta manera, pero para las viejas consolas 2D, no importa. Si Python es lo tuyo, hazlo.
Estas son algunas de las cosas más geniales (¿más locas?) que hizo la gente:
Este último me hizo pensar en esto:
Es gracioso. 😅😎
Haremos muchas operaciones binarias, por lo que es bueno sentirse cómodo con ellas antes de comenzar.
Como tenemos que elegir algo, usaremos Rust v1.45.
Cargando una ROM
¡Empecemos a escribir!
Lo primero que haremos será introducir una ROM de juego en la memoria. No nos importa qué ROM es, siempre y cuando funcione. Lo usaremos más adelante para implementar la instrucción de la CPU instrucción por instrucción.
Si eres lo suficientemente joven como para estar confundido acerca de qué es una ROM. Los juegos de consola solían distribuirse en cartuchos ROM. ROM para memoria de solo lectura. 💾
Según los documentos, CHIP-8 tiene 4096 bytes (4 KB) de memoria interna y las ROM se cargan en la dirección 0x200. Podemos modelar esto con una matriz simple. También crearemos una estructura para contener otros componentes de nuestro emulador que agregaremos más adelante.
Así es como se ve:
Podríamos cargar directamente en la
mem
matriz para ser eficientes, pero esto complica el código, así que opté por no hacerlo.
¡Frio! Ahora echemos un vistazo a la CPU.
Comprender la CPU
La CPU es lo principal que hace que las consolas funcionen.
Hay dos cosas que debemos emular con respecto a la CPU, independientemente de la consola o la arquitectura:
- registros de la CPU
- instrucciones de la CPU
Hablemos de ellos en orden.
Los registros y las instrucciones están definidos por la arquitectura de la CPU conocida como ISA (Instruction Set Architecture). Por ejemplo, las PC usan x86_64 ISA. GBA usa ARM . N64, PS1, PS2 y PSP utilizan MIPS .
Registers
Los registros son pequeños fragmentos de memoria rápida interna de la CPU.
En CHIP-8 estos son:
Registro | Tamaño | Contar | Descripción |
---|---|---|---|
EN | 8 bits | 16 (de V0 a VF) | Propósito general |
yo | 16 bits | 1 | Propósito general |
ordenador personal | 16 bits | 1 | Contador de programa |
SP | 8 bits | 1 | Puntero de pila |
DT | 8 bits | 1 | Temporizador de retardo |
S T | 8 bits | 1 | Temporizador de sonido |
Podemos modelarlos así:
Bastante simple, ¿no?
Instructions
Como ya habrás adivinado, las instrucciones son las operaciones que puede realizar la CPU y lo más importante que debemos implementar.
Como programadores, a menudo pensamos en el comportamiento y los datos como algo separado. Puede que le sorprenda saber que esta división es artificial.
A nivel de la CPU, el comportamiento ES datos. 🐱💻
Las instrucciones de la CPU se codifican y alimentan a la CPU como bytes regulares desde la memoria. Cada instrucción tiene un patrón de bits específico que permite que la CPU lo entienda.
Las instrucciones también se denominan operaciones y sus representaciones de bytes se denominan códigos de operación o códigos de operación para abreviar.
Las instrucciones CHIP-8 siempre tienen una longitud de 2 bytes y están dispuestas en orden big-endian , es decir, con el byte más significativo primero.
Por ejemplo, aquí hay tres instrucciones del conjunto de instrucciones CHIP-8:
código de operación | Nombre | pseudocódigo | Descripción |
---|---|---|---|
6xkk | LD | v[x] = kk |
Establezca Vx en kk. |
7xkk | AGREGAR | v[x] = v[x] + kk |
Agregue kk a Vx. |
8xy0 | LDR | v[x] = v[y] |
Establezca Vx en Vy. |
Donde opcode es un número hexadecimal que incluye símbolos especiales:
x
Índice de 4 bits del registro V (V0-VF)y
Índice de 4 bits del registro V (V0-VF)kk
constante de 8 bits
Veamos un programa de muestra para asegurarnos de que entendemos cómo funcionan:
Si ejecutamos este programa en la CPU CHIP-8, sucedería lo siguiente:
60 00
Establezca el valor del registro V0 en 0.61 02
Establezca el valor del registro V1 en 2.70 11
Establezca el valor del registro V0 en la suma de sí mismo y 11 (0 + 11 = 11).83 00
Establezca el valor del registro V3 en el valor del registro V0.
¡Estupendo! ¡Ahora entendemos cómo funcionan las instrucciones de la CPU! 🥳🥳🥳
Contador de programa
Pero, ¿cómo sabe la CPU qué instrucción ejecutar a continuación? 🦝
¿Recuerdas el registro especial de PC? Se llama contador de programa y se utiliza para almacenar la dirección de la siguiente instrucción a ejecutar.
Nuestro emulador CHIP-8 comenzará con la PC configurada al comienzo de la ROM cargada (0x200) y la incrementará en 2 cada instrucción. 💻
Estas son algunas de las cosas que se logran al manipular la PC:
- Omisión condicional de instrucciones (sentencias if)
- Saltar a una instrucción diferente (bucles y gotos)
- Llamar a procedimientos/funciones
Los procedimientos de llamada se realizan empujando la PC a la pila y volviendo a abrirla cuando regresa el procedimiento.
Si alguna vez reflexionó sobre cómo funcionan estos a nivel de CPU, ahora lo sabe. 😜
Implementando la CPU
Hemos hablado de todos los componentes que componen la CPU, así que es hora de juntarlos e implementarlos.
Veamos cómo podríamos hacerlo:
Op
Se ve como esto:
Cambié la memoria de una matriz de bytes sin formato a una
Memory
estructura fuera de la pantalla.
Divide y vencerás: Edición CPU
Cuando comenzamos a hablar sobre la CPU, mencioné que íbamos a implementarla instrucción por instrucción. Ya era hora de que hiciéramos eso.
La idea es esta:
- Cargamos una ROM y ejecutamos la CPU.
- Obtenemos un
Invalid opcode
error con el código de operación faltante. - Implementamos la instrucción faltante.
- Repetimos el proceso hasta que la ROM sea jugable.
Aprendí este método cuando vi la transmisión de Ferris. Nos permite abordar la implementación de la CPU paso a paso sin tener que preocuparnos por sentirnos abrumados porque solo implementamos lo que está inmediatamente frente a nosotros.
Divide y conquistaras.
No puedo enfatizar lo bueno que es este método. ¡Gracias Ferris! 🥳🥳🥳
Una vez que hayamos implementado las 35 instrucciones CHIP-8, todas las ROM deberían poder reproducirse.
Eso suponiendo que no hayamos cometido errores. 😂
Podemos imprimir las instrucciones a medida que se ejecutan. Aparte de verse genial, nos ayudará con la depuración si nos equivocamos. 😏
Por ejemplo, estas son las primeras 10 instrucciones de un juego CHIP-8 llamado BLINKY:
¿No se ven hermosos? 🧐🧐🙃
Para CPU Ad Astra
A medida que sigamos implementando las instrucciones, eventualmente encontraremos cosas fuera de la CPU:
- duendes
- Mostrar
- Temporizadores
- Sonar
- Teclado
Podemos implementarlos en el camino cuando lo creamos apropiado. Dejaré emular estos como un desafío para ti.
El teclado se puede falsificar con stdin y la pantalla con una matriz 2D. ¡Creatividad! 🤪
Graphics
Antes de terminar, veamos cómo obtener píxeles en la pantalla, ¿de acuerdo?
CHIP-8 utiliza una pantalla monocromática de 64 píxeles de ancho y 32 píxeles de alto. Como todas las consolas clásicas, los gráficos se dibujan usando sprites.
Los sprites son pequeñas imágenes almacenadas en la memoria que se pueden dibujar en la pantalla en las coordenadas proporcionadas.
El tamaño de pantalla limitado, la profundidad de color y la memoria crearon algunos mecanismos de dibujo y almacenamiento de sprites interesantes. 🕸 Déjame mostrártelos.
Veamos un ejemplo de un sprite CHIP-8:
Aquí, cada fila representa un byte de una imagen monocromática. Los bits establecidos (1) representan píxeles iluminados y los bits no establecidos (0) representan píxeles no iluminados.
Veamos cómo se ve este sprite cuando se dibuja:
De acuerdo, usar bits en lugar de bytes cuando se trata de gráficos monocromáticos es bastante obvio. Nada especial aquí, continuemos…
La instrucción de dibujo se ve así:
¡Ajá!
Hay múltiples victorias aquí, pero estas son las más grandes:
- Transparencia
- Detección de colisión perfecta como píxel
Transparency
Al dibujar en la pantalla, en lugar de copiar el sprite en la pantalla, realizamos un XOR (o lógico) con los datos de visualización existentes.
En otras palabras, dibujar un píxel no iluminado (no configurado) encima de uno iluminado (establecido) no lo borrará porque
1 XOR 0 => 1
.
¡Transparencia sin canal alfa o fusión bebé!
Detección de colisión perfecta como píxel
Cuando se borra un píxel lo señalamos a través del registro VF. Los juegos pueden usar esto para la detección de colisiones con píxeles perfectos.
Los píxeles se borran cuando un píxel iluminado se dibuja encima de otro píxel iluminado porque
1 XOR 1 => 0
.
ingenioso no? 🧐
¡Nada supera esa sensación de comprensión repentina cuando descubres el motivo de la elección del diseño de algunos plátanos! ¡Y hay muchos de ellos en las consolas antiguas! 🚀🚀🚀
Aquí hay una captura de pantalla del laberinto en BLINKY:
Pensamientos
Uff~ ¡Eso es todo! Espero que hayas encontrado este artículo valioso.
Hacer este emulador tomó tal vez varias horas, pero este artículo tomó más de una semana para escribir, reescribir y editar antes de que sintiera que era adecuado. 🧐
Ahora debería tener una idea de cómo se ve escribir un emulador. 🎓
Puedes ver el emulador terminado aquí: código fuente .
Antes de terminar, si desperté cierto interés en usted por escribir emuladores, aquí hay algunas cosas que puede hacer a continuación:
- escribir un emulador
- Ferris hace emuladores
- EmuDev Reddit
- Discordia de EmuDev
Como dice el refrán, ninguna cantidad de investigación es mejor que hacer lo real y ningún plan sobrevive al contacto con el enemigo. ¡Entonces, ve a escribir un emulador! 😁
Si tiene alguna pregunta o quiere aprender/necesita ayuda con la programación, envíeme un correo electrónico a wojciech@wjdevschoolcom .
Estoy feliz de ayudarte en todo lo que pueda. 🧐😊
Si te ha gustado este artículo y quieres ayudarnos puedes seguirnos en Facebook y Twitter . También publicaremos actualizaciones sobre nuevos artículos allí. 😋
¡Actualmente, todavía somos súper pequeños y cada uno de los Me gusta es de GRAN ayuda! 🙏🤠
¡Nos vemos en el futuro y hagamos más cosas geniales!
¡Salud! 🥳
¡ Muchas gracias a Torrent por ayudar a editar y mejorar el artículo!
Links
- El autor no aboga por la piratería y respeta los derechos de propiedad intelectual de Sony y otros. 🦜🏴☠️ ↩︎