Écriture en assemblage pour Sega Genesis #5

Dans cette note, je décrirai le processus de lecture du joystick, de changement de position du sprite, de retournement horizontal, de l’émulateur Sega Genesis et potentiellement de la console elle-même.

La lecture des clics et le traitement des « événements » d’un joystick shogi se déroulent selon le schéma suivant :

  1. Demande de combinaison de bits de boutons enfoncés
  2. Lecture de morceaux de boutons enfoncés
  3. Traitement au niveau de la logique du jeu

Pour déplacer le sprite squelette, nous devons stocker les variables de la position actuelle.

RAM

Les variables logiques du jeu sont stockées dans la RAM ; jusqu’à présent, les gens n’ont rien trouvé de mieux. Déclarons les adresses de variables et modifions le code de rendu :

skeletonYpos = $FF0002 
frameCounter = $FF0004 
skeletonHorizontalFlip = $FF0006

    move.w #$0100,skeletonXpos 
    move.w #$0100,skeletonYpos 
    move.w #$0001,skeletonHorizontalFlip 

FillSpriteTable: 
    move.l #$70000003,vdp_control_port 
    move.w skeletonYpos,vdp_data_port  
    move.w #$0F00,vdp_data_port 
    move.w skeletonHorizontalFlip,vdp_data_port 
    move.w skeletonXpos,vdp_data_port 

Comme vous pouvez le constater, l’adresse disponible pour le travail commence à 0xFF0000 et se termine à 0xFFFFFF, au total 64 Ko de mémoire sont à notre disposition. Les positions des squelettes sont déclarées à skeletonXpos, skeletonYpos, flip horizontal à skeletonHorizontalFlip.

Joypad

Par analogie avec VDP, le travail avec les joypads s’effectue via deux ports séparément – ; port de contrôle et port de données, pour le premier 0xA10009 et 0xA10003 co-no. Il y a une fonctionnalité intéressante lorsque l’on travaille avec un joypad : Vous devez d’abord demander une combinaison de boutons pour l’interrogation, puis, après avoir attendu une mise à jour sur le bus, lire les pressions requises. Pour les boutons C/B et D-pad, il s’agit de 0x40, exemple ci-dessous :

  move.b #$40,joypad_one_control_port; C/B/Dpad 
  nop ; bus sync 
  nop ; bus sync 
  move.b joypad_one_data_port,d2 
  rts 

Dans le registre d2, l’état des boutons enfoncés ou non enfoncés restera, en général, ce qui a été demandé via le port date restera. Après cela, accédez à la visionneuse de registre Motorola 68000 de votre émulateur préféré, voyez à quoi est égal le registre d2, en fonction des frappes au clavier. De manière intelligente, vous pouvez le découvrir dans le manuel, mais nous ne les croyons pas sur parole. Traitement ultérieur des boutons enfoncés dans le registre d2

    cmp #$FFFFFF7B,d2; handle left 
    beq MoveLeft  
    cmp #$FFFFFF77,d2; handle right  
    beq MoveRight  
    cmp #$FFFFFF7E,d2; handle up  
    beq MoveUp  
    cmp #$FFFFFF7D,d2; handle down  
    beq MoveDown  
    rts

Проверять нужно конечно отдельные биты, а не целыми словами, но пока и так сойдет. Теперь осталось самое простое – написать обработчики всех событий перемещения по 4-м направлениям. Для этого меняем переменные в RAM, и запускаем процедуру перерисовки.

Пример для перемещения влево + изменение горизонтального флипа:

    move.w skeletonXpos,d0 
    sub.w #1,d0 
    move.w d0,skeletonXpos 
    move.w #$0801,skeletonHorizontalFlip 
    jmp FillSpriteTable

После добавления всех обработчиков и сборки, вы увидите как скелет перемещается и поворачивается по экрану, но слишком быстро, быстрее самого ежа Соника.

Не так быстро!

Чтобы замедлить скорость игрового цикла, существуют несколько техник, я выбрал самую простую и не затрагивающую работу с внешними портами – подсчет цифры через регистр пока она не станет равна нулю.

Пример замедляющего цикла и игрового цикла:

  move.w #512,frameCounter 
WaitFrame: 
  move.w frameCounter,d0 
  sub.w #1,d0 
  move.w d0,frameCounter 
  dbra d0,WaitFrame 
GameLoop: 
  jsr ReadJoypad 
  jsr HandleJoypad 
  jmp GameLoop 

Après cela, le squelette fonctionne plus lentement, ce qui était nécessaire. Comme je le sais, l’option la plus courante pour « ralentir » consiste à compter le drapeau de synchronisation verticale ; vous pouvez compter combien de fois l’écran a été dessiné, étant ainsi lié à un fps spécifique.

Liens

https://gitlab .com/demensdeum/segagenesisamples/-/blob/main/8Joypad/vasm/main.asm

Sources

https://www.chibiakumas.com/68000/platform2.php
https://huguesjohnson.com/programming/genesis/tiles-sprites/

Écriture en assemblage pour Sega Genesis #4

Dans cet article, je vais décrire comment dessiner des sprites à l’aide de l’émulateur VDP de la console Sega Genesis.
Le processus de rendu des sprites est très similaire au rendu des tuiles :

  1. Chargement des couleurs dans CRAM
  2. Importation de parties de sprites 8×8 dans la VRAM
  3. Remplir la table des sprites dans la VRAM

Par exemple, prenons un sprite de squelette avec une épée de 32 à 32 pixels

Skeleton Guy [Animated] by Disthorn

CRAM

À l’aide d’ImaGenesis, convertissons-le en couleurs CRAM et modèles VRAM pour l’assembleur. Après cela, nous obtiendrons deux fichiers au format asm, puis nous réécrirons les couleurs à la taille du mot et les carreaux devront être placés dans le bon ordre pour le dessin.
Information intéressante : vous pouvez basculer l’auto-incrémentation VDP via le registre 0xF sur la taille des mots, cela supprimera l’incrément d’adresse du code de remplissage de couleur CRAM.

VRAM

Le manuel Shogi a le bon ordre des tuiles pour les grands sprites, mais nous sommes plus intelligents, nous prendrons donc les index du blog ChibiAkumas, commençons à compter à partir de l’index 0 :

0 4 8 12

1 5 9 13

2 6 10 14

3 7 11 15

Pourquoi tout est à l’envers ? Que veux-tu, la console est japonaise ! Cela pourrait même être de droite à gauche !
Modifions manuellement l’ordre dans le fichier de sprite asm :

	dc.l	$11111111	; Tile #0 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111	; Tile #4 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111
	dc.l	$11111111	; Tile #8 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111122 
	dc.l	$11111122 
	dc.l	$11111166 
	dc.l	$11111166	; Tile #12 
	dc.l	$11111166 
	dc.l	$11111166 
	и т.д. 

Chargez le sprite comme des tuiles/motifs normaux :

  lea Sprite,a0 
  move.l #$40200000,vdp_control_port; write to VRAM command 
  move.w #128,d0 ; (16*8 rows of sprite) counter 
SpriteVRAMLoop: 
  move.l (a0)+,vdp_data_port; 
  dbra d0,SpriteVRAMLoop 

Pour dessiner un sprite, il ne reste plus qu’à remplir la Table des Sprites

Tableau des Sprites

La table des sprites est remplie en VRAM, l’adresse de son emplacement est inscrite dans le registre VDP 0x05, l’adresse est encore une fois délicate, vous pouvez la voir dans le manuel, un exemple pour l’adresse F000 :

Ок, теперь запишем наш спрайт в таблицу. Для этого нужно заполнить “структуру” данных состоящую из четырех word. Бинарное описание структуры вы можете найти в мануале. Лично я сделал проще, таблицу спрайтов можно редактировать вручную в эмуляторе Exodus.
Les paramètres de structure ressortent clairement du nom, par exemple XPos, YPos – coordonnées, Tuiles – numéro de la tuile de départ pour le dessin, HSize, VSize – tailles de sprite en ajoutant les parties 8×8, HFlip, VFlip – rotation matérielle du sprite horizontalement et verticalement.

Il est très important de se rappeler que les sprites peuvent être hors écran, c’est un comportement correct, car… décharger les sprites hors écran de la mémoire – une activité assez gourmande en ressources.
Après avoir rempli les données dans l’émulateur, elles doivent être copiées de la VRAM à l’adresse 0xF000, Exodus prend également en charge cette fonctionnalité.
Par analogie avec le dessin des tuiles, on accède d’abord au port de contrôle VDP pour commencer l’écriture à l’adresse 0xF000, puis on écrit la structure sur le port de données.
Je vous rappelle que la description de l’adressage VRAM peut être lue dans le manuel ou dans le blog Algorithme sans nom .

En résumé, l’adressage VDP fonctionne comme ceci :
[..DC BA98 7654 3210…. …. …. ..FE]
Où hex est la position du bit dans l’adresse souhaitée. Les deux premiers bits correspondent au type de commande demandé, par exemple 01 – écrire dans la VRAM. Ensuite, pour l’adresse 0XF000, il s’avère :
0111 0000 0000 0000 0000 0000 0000 0011 (70000003)

En conséquence, nous obtenons le code :

  move.l #$70000003,vdp_control_port 
  move.w #$0100,vdp_data_port 
  move.w #$0F00,vdp_data_port 
  move.w #$0001,vdp_data_port 
  move.w #$0100,vdp_data_port 

Après cela, le sprite squelette sera affiché aux coordonnées 256, 256. Cool, non ?

Liens

https://gitlab.com/demensdeum /segagenesissamples/-/tree/main/7Sprite/vasm
https://opengameart.org/content/skeleton-guy-animated

Sources

https://namelessalgorithm.com/genesis/blog/vdp/
https://www.chibiakumas.com/68000/platform3.php#LessonP27
https://plutiedev.com/sprites

Écriture en assemblage pour Sega Genesis #3

Dans cet article, je vais décrire comment afficher une image à partir de tuiles sur l’émulateur Sega Genesis à l’aide de l’assembleur.
L’image de démarrage Demens Deum dans l’émulateur Exodus ressemblera à ceci :

Le processus de sortie d’une image PNG à l’aide de tuiles se fait étape par étape :

  1. Réduire l’image à la taille de l’écran Shogi
  2. Convertir le format PNG en code de données d’assemblage, séparé en couleurs et en mosaïques
  3. Charger une palette de couleurs dans CRAM
  4. Chargement de tuiles/motifs dans la VRAM
  5. Chargement des index de tuiles aux adresses du plan A/B dans la VRAM
  6. Vous pouvez réduire l’image à la taille de l’écran Shogi à l’aide de votre éditeur graphique préféré, tel que Blender.

Conversion PNG

Pour convertir des images, vous pouvez utiliser l’outil ImaGenesis ; pour travailler sous wine, les bibliothèques Visual Basic 6 sont requises, elles peuvent être installées à l’aide de winetricks (winetricks vb6run), ou RICHTX32.OCX peut être téléchargé depuis Internet et placé dans le dossier de l’application pour un fonctionnement correct.< /p>

Dans ImaGenesis, vous devez sélectionner une couleur 4 bits, exporter les couleurs et les tuiles vers deux fichiers au format assembleur. Ensuite, dans le fichier avec les couleurs, vous devez mettre chaque couleur dans un mot (2 octets), pour cela vous utilisez l’opcode dc.w.

Par exemple, écran de démarrage CRAM :

  dc.w $0000 
  dc.w $0000 
  dc.w $0222 
  dc.w $000A 
  dc.w $0226 
  dc.w $000C 
  dc.w $0220 
  dc.w $08AA 
  dc.w $0446 
  dc.w $0EEE 
  dc.w $0244 
  dc.w $0668 
  dc.w $0688 
  dc.w $08AC 
  dc.w $0200 
  dc.w $0000 

Laissez le fichier de tuiles tel quel, il contient déjà le format correct pour le chargement. Exemple de partie d’un fichier de tuiles :

	dc.l	$11111111	; Tile #0 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111	; Tile #1 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 
	dc.l	$11111111 

Comme vous pouvez le voir dans l’exemple ci-dessus, les tuiles sont une grille 8×8 composée d’indices de palette de couleurs CRAM.

Couleurs dans CRAM

Le chargement dans la CRAM s’effectue en définissant une commande de chargement de couleur sur une adresse CRAM spécifique dans le port de contrôle (contrôle vdp). Le format de commande est décrit dans le manuel du logiciel Sega Genesis (1989), j’ajouterai juste qu’il suffit d’ajouter 0x20000 à l’adresse pour passer à la couleur suivante.

Ensuite, vous devez charger la couleur dans le port de données (données vdp) ; La façon la plus simple de comprendre le chargement est d’utiliser l’exemple ci-dessous :

    lea Colors,a0 ; pointer to Colors label 
    move.l #15,d7; colors counter 
VDPCRAMFillLoopStep: 
    move.l  d0,vdp_control_port ;  
    move.w  (a0)+,d1; 
    move.w  d1,(vdp_data_port); 
    add.l #$20000,d0 ; increment CRAM address 
    dbra d7,VDPCRAMFillLoopStep 

Tuiles dans VRAM

Vient ensuite le chargement des tuiles/motifs dans la mémoire vidéo VRAM. Pour cela, sélectionnez une adresse dans la VRAM, par exemple 0x00000000. Par analogie avec la CRAM, nous contactons le port de contrôle VDP avec une commande pour écrire dans la VRAM et l’adresse de départ.

Après cela, vous pouvez télécharger des mots longs vers la VRAM ; par rapport à la CRAM, vous n’avez pas besoin de spécifier l’adresse pour chaque mot long, car il existe un mode d’incrémentation automatique de la VRAM. Vous pouvez l’activer en utilisant l’indicateur de registre VDP 0x0F (dc.b $02)

  lea Tiles,a0 
  move.l #$40200000,vdp_control_port; write to VRAM command 
  move.w #6136,d0 ; (767 tiles * 8 rows) counter 
TilesVRAMLoop: 
  move.l (a0)+,vdp_data_port; 
  dbra d0,TilesVRAMLoop 

Index de tuiles dans le plan A/B

Maintenant, nous devons remplir l’écran avec des tuiles en fonction de leur index. Pour ce faire, la VRAM est remplie à l’adresse Plane A/B, qui est inscrite dans les registres VDP (0x02, 0x04). Plus d’informations sur l’adressage délicat se trouvent dans le manuel de Sega ; dans mon exemple, l’adresse VRAM est 0xC000, téléchargeons les index ici.

Votre image remplira de toute façon l’espace VRAM hors écran, donc après avoir dessiné l’espace de l’écran, votre moteur de rendu devrait arrêter de dessiner et continuer lorsque le curseur se déplace vers une nouvelle ligne. Il existe de nombreuses options pour implémenter cela ; j’ai utilisé la version la plus simple de comptage sur deux registres du compteur de largeur d’image et du compteur de position du curseur.

Exemple de code :

  move.w #0,d0     ; column index 
  move.w #1,d1     ; tile index 
  move.l #$40000003,(vdp_control_port) ; initial drawing location 
  move.l #2500,d7     ; how many tiles to draw (entire screen ~2500) 

imageWidth = 31 
screenWidth = 64 

FillBackgroundStep: 
  cmp.w	#imageWidth,d0 
  ble.w	FillBackgroundStepFill 
FillBackgroundStep2: 
  cmp.w	#imageWidth,d0 
  bgt.w	FillBackgroundStepSkip 
FillBackgroundStep3: 
  add #1,d0 
  cmp.w	#screenWidth,d0 
  bge.w	FillBackgroundStepNewRow 
FillBackgroundStep4: 
  dbra d7,FillBackgroundStep    ; loop to next tile 

Stuck: 
  nop 
  jmp Stuck 

FillBackgroundStepNewRow: 
  move.w #0,d0 
  jmp FillBackgroundStep4 
FillBackgroundStepFill: 
  move.w d1,(vdp_data_port)    ; copy the pattern to VPD 
  add #1,d1 
  jmp FillBackgroundStep2 
FillBackgroundStepSkip: 
  move.w #0,(vdp_data_port)    ; copy the pattern to VPD 
  jmp FillBackgroundStep3 

Après cela, il ne reste plus qu’à assembler la rom à l’aide de vasm, lancer le simulateur et voir l’image.

Débogage

Tout ne fonctionnera pas tout de suite, c’est pourquoi je voudrais recommander les outils d’émulation Exodus suivants :

  1. Débogueur de processeur M68k
  2. Modification du nombre de cycles du processeur m68k (pour le mode ralenti dans le débogueur)
  3. Visionneuses CRAM, VRAM, plan A/B
  4. Lisez attentivement la documentation de m68k, les opcodes utilisés (tout n’est pas aussi évident qu’il y paraît à première vue)
  5. Afficher des exemples de code de jeu/démontage sur github
  6. Implémenter des sous-programmes d’exceptions de processeur et les traiter

Les pointeurs vers les sous-programmes d’exception du processeur sont placés dans l’en-tête de la rom ; il existe également un projet sur GitHub avec un débogueur d’exécution interactif pour Sega, appelé genesis-debugger.

Utilisez tous les outils disponibles, ayez un bon codage à l’ancienne et que Blast Processing soit avec vous !

Liens

https://gitlab.com/demensdeum /segagenesisamples/-/tree/main/6Image/vasm
http://devster.monkeeh.com/sega/imagenesis/
https://github.com/flamewing/genesis-debugger

Sources

https://www.chibiakumas.com/68000/helloworld .php#LeçonH5
https://huguesjohnson.com/programming/genesis/tiles-sprites/

 

Écriture en assemblage pour Sega Genesis #1

Le premier article consacré à l’écriture de jeux pour la console classique Sega Genesis dans Motorola 68000 Assembly.

Écrivons la boucle infinie la plus simple pour Sega. Pour cela nous aurons besoin : d’un assembleur, d’un émulateur avec un désassembleur, d’un éditeur de texte préféré, d’une compréhension de base de la structure du rhum Sega.

Pour le développement, j’utilise mon propre assembleur/désassembleur Gen68KryBaby :

https://gitlab.com/demensdeum/gen68krybaby/

L’outil est développé en Python 3, pour l’assemblage un fichier avec l’extension .asm ou .gen68KryBabyDisasm est fourni en entrée, la sortie est un fichier avec l’extension .gen68KryBabyAsm.bin, qui peut être exécuté dans l’émulateur ou sur une vraie console (attention, éloignez-vous, la console risque d’exploser !)

Le désassemblage des roms est également pris en charge, pour cela vous devez soumettre un fichier rom en entrée, sans les extensions .asm ou .gen68KryBabyDisasm. Le support des opcodes augmentera ou diminuera en fonction de mon intérêt pour le sujet et de la participation des contributeurs.

Structure

L’en-tête de la ROM Sega occupe les 512 premiers octets. Il contient des informations sur le jeu, le nom, les périphériques pris en charge, la somme de contrôle et d’autres indicateurs système. Je suppose que sans titre, la console ne regardera même pas le rhum, pensant que c’est incorrect, en disant “qu’est-ce que tu me donnes ici ?”

Après l’en-tête, il y a un sous-programme/sous-programme Reset, avec lequel commence le travail du processeur m68K. D’accord, c’est une petite affaire – trouver des opcodes (codes d’opération), à savoir ne rien faire (!) et passer au sous-programme à l’adresse en mémoire. En cherchant sur Google, vous pouvez trouver l’opcode NOP, qui ne fait rien, et l’opcode JSR, qui effectue un saut inconditionnel vers l’adresse de l’argument, c’est-à-dire qu’il déplace simplement le chariot là où nous le demandons, sans aucun caprice.

Rassembler tout cela

Le donneur d’en-tête de la rom était l’un des jeux de la version bêta, actuellement enregistré sous forme de données hexadécimales.


 00 ff 2b 52 00 00 02 00 00 00 49 90 00 00 49 90 00 00 49 90 00...и т.д. 

Код программы со-но представляет из себя объявление сабрутины Reset/EntryPoint в 512 (0x200) байте, NOP, возврат каретки к 0x00000200, таким образом мы получим бесконечный цикл.

Ассемблерный код сабрутины Reset/EntryPoint:

    NOP
    NOP
    NOP 
    NOP
    NOP
    JSR 0x00000200  

Exemple complet avec en-tête de rom :

https://gitlab.com /demensdeum/segagenesisamples/-/blob/main/1InfiniteLoop/1infiniteloop.asm

Nous collectons ensuite :

Запускаем ром 1infiniteloop.asm.gen68KryBabyAsm.bin в режиме дебаггера эмулятора Exodus/Gens, смотрим что m68K корректно считывает NOP, и бесконечно прыгает к EntryPoint в 0x200 на JSR

Здесь должен быть Соник показывающий V, но он уехал на Вакен.

Ссылки

https://gitlab.com/demensdeum/gen68krybaby/

https://gitlab.com/demensdeum/segagenesissamples

https://www.exodusemulator.com/downloads/release-archive

Источники

ROM Hacking Demo – Genesis and SNES games in 480i

http://68k.hax.com/

https://www.chibiakumas.com/68000/genesis.php

https://plutiedev.com/rom-header

https://blog.bigevilcorporation.co.uk/2012/02/28/sega-megadrive-1-getting-started/

https://opensource.apple.com/source/cctools/cctools-836/as/m68k-opcode.h.auto.html