gameboy-sharp

Un émulateur Game Boy (DMG) en C#. Un coeur sans dépendance au frontend, si bien que toute la machine fonctionne et est vérifiée en headless sur des ROM de test standard.

gameboy-sharp
TL;DR

Un émulateur de Game Boy (modèle DMG) écrit en C#. Le cœur est séparé du frontend et vérifié headless contre des ROMs de test standard, pour que les jeux des autres tournent bit pour bit.

Aperçu

gameboy-sharp est un émulateur du Game Boy original, le modèle DMG, écrit en C#. La tâche paraît simple : faire tourner des jeux sortis il y a trente ans sans changer un seul octet. La difficulté se cache dans le mot "fidèlement" - l'émulateur doit imiter le matériel assez précisément pour que le code d'origine ne remarque rien.

Nous avons construit le cœur pour qu'il ne sache rien d'une fenêtre ni du son. Toute la machine est une bibliothèque pure qui renvoie un tampon d'image et des échantillons audio, et seul le frontend les transforme en ce que vous voyez et entendez. C'est ce qui permet de faire tourner toute la console sans écran et de comparer son résultat à ce qui devrait sortir.

Les bugs dans un émulateur ne plantent presque jamais avec fracas. Le jeu démarre, le jeu tourne - seul le son est un rien trop aigu, ou le personnage se déplace d'un pixel de trop. Il n'y a pas d'exception, pas de plantage, juste un comportement subtilement faux que vous ne pouvez pas distinguer du correct "à l'œil".

Cela change toute la façon de travailler. Vous ne pouvez pas vous fier à l'impression que ça "paraît bon", car c'est justement cette impression qui ment le plus souvent. La seule chose à laquelle on peut se fier, c'est la comparaison de l'état de la machine avec une valeur connue d'avance - et c'est autour de cela que tout le projet est construit.

Le cœur de la machine : le processeur

Le cœur de la console est le processeur du Game Boy, une variante du Z80. Chaque instruction est la lecture d'un octet d'opcode et l'exécution exacte de ce que ferait l'original, avec le nombre de cycles que coûte cette instruction. Le timing n'est pas un détail - c'est lui qui décide si le son et l'image sont synchronisés comme sur le vrai matériel.

Cpu.cs · csharp
byte opcode = ReadByte(PC++);

switch (opcode) {
    case 0x00: cycles += 4; break;
    case 0x3E: A = ReadByte(PC++); cycles += 8; break;
    case 0xC3: PC = ReadWord(); cycles += 16; break;
    default: cycles += Execute(opcode); break;
}

Une instruction et son coût en cycles

OpcodeInstructionCycles
0x00NOP4
0x3ELD A, d88
0xC3JP a1616
i
À noter

Le cœur ne sait rien d'une fenêtre ni du son - c'est une machine pure. Le frontend ne reçoit qu'un tampon d'image et des échantillons audio. C'est ce qui permet de faire tourner toute la console sans écran et de comparer le résultat à ce qui est attendu, cycle par cycle.

Des tests au cycle près

La communauté de l'émulation entretient des jeux de ROMs de test qui vérifient des registres, des drapeaux et le timing au cycle près. Nous les exécutons headless et comparons l'état de la machine à ce qui devrait sortir. C'est le seul moyen honnête de dire qu'un émulateur est correct, et pas seulement qu'il "marche chez moi".

C'est justement pour cela que le cœur a été coupé du frontend. Si la machine avait besoin d'une fenêtre, chaque test devrait être cliqué à la main. Comme c'est une bibliothèque pure, les ROMs de test défilent automatiquement, et le verdict est binaire : ça correspond ou non.

Le résultat : les jeux des autres bit pour bit

Ce qui en sort est un émulateur qui fait tourner les jeux des autres et passe les ROMs de test standard, et pas seulement un qui affiche le logo Nintendo et s'arrête. Le son est là où il doit être, le personnage se déplace du nombre de pixels qu'il doit, et la justesse est prouvée par des tests, non par une impression. C'est un projet né de l'amour du vieux matériel et mené à un état où cela se voit.

Plus de projets

D'autres réalisations de la même catégorie - découvrez comment nous abordons des défis similaires.

Vous avez un projet similaire ?

Contactez-nous - le devis est gratuit et arrive sous une heure.