Schreiben in Assembly für Sega Genesis #5

In dieser Notiz beschreibe ich den Prozess des Lesens des Joysticks, des Änderns der Position des Sprites, des horizontalen Umdrehens, des Sega Genesis-Emulators und möglicherweise der Konsole selbst.

Das Lesen von Klicks und die Verarbeitung von „Ereignissen“ eines Shogi-Joysticks erfolgt nach folgendem Schema:

  1. Anfrage nach einer Kombination von Bits gedrückter Tasten
  2. Teile gedrückter Tasten lesen
  3. Verarbeitung auf der Ebene der Spiellogik

Um das Skelett-Sprite zu verschieben, müssen wir Variablen der aktuellen Position speichern.

RAM

Spiellogikvariablen werden im RAM gespeichert; bisher ist noch nichts Besseres erfunden worden. Lassen Sie uns Variablenadressen deklarieren und den Rendering-Code ändern:

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 

Wie Sie sehen, beginnt die für die Arbeit verfügbare Adresse bei 0xFF0000 und endet bei 0xFFFFFF, insgesamt stehen uns 64 KB Speicher zur Verfügung. Skeleton-Positionen werden bei SkeletonXpos, SkeletonYpos, horizontale Drehung bei SkeletonHorizontalFlip deklariert.

Joypad

In Analogie zu VDP erfolgt die Arbeit mit Joypads über zwei separate Ports – Steuerport und Datenport, für den ersten 0xA10009 und 0xA10003 Co-Nr. Bei der Arbeit mit einem Joypad gibt es eine interessante Funktion: Zuerst müssen Sie eine Tastenkombination für die Abfrage anfordern und dann, nachdem Sie auf ein Update am Bus gewartet haben, die erforderlichen Tastendrücke lesen. Für die C/B- und D-Pad-Tasten ist dies 0x40, Beispiel unten:

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

Im Register d2 bleibt der Zustand der gedrückten oder nicht gedrückten Tasten erhalten, im Allgemeinen bleibt das, was über den Datumsport angefordert wurde, erhalten. Gehen Sie anschließend zum Motorola 68000-Register-Viewer Ihres Lieblingsemulators und sehen Sie, was das d2-Register je nach Tastenanschlägen entspricht. Auf clevere Weise können Sie es im Handbuch herausfinden, aber wir verlassen uns nicht auf ihr Wort. Weiterverarbeitung gedrückter Tasten im d2

-Register

    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 

Danach läuft das Skelett langsamer, was erforderlich war. Wie ich weiß, ist die häufigste Option zum „Verlangsamen“ das Zählen der vertikalen Synchronisierungsflagge. Sie können zählen, wie oft der Bildschirm gezeichnet wurde, und sind somit an eine bestimmte Anzahl an Bildern pro Sekunde gebunden.

Links

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

Quellen

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

Schreiben in Assembly für Sega Genesis #4

In diesem Beitrag beschreibe ich, wie man Sprites mit dem VDP-Emulator der Sega Genesis-Konsole zeichnet.
Der Prozess des Renderns von Sprites ist dem Rendern von Kacheln sehr ähnlich:

  1. Farben in CRAM laden
  2. Teile der Sprites 8×8 in VRAM hochladen
  3. Sprite-Tabelle im VRAM füllen

Nehmen wir zum Beispiel ein Sprite eines Skeletts mit einem Schwert mit 32×32 Pixeln

Skeleton Guy [Animated] by Disthorn

CRAM

Mit ImaGenesis konvertieren wir es in CRAM-Farben und VRAM-Muster für Assembler. Danach erhalten wir zwei Dateien im ASM-Format, dann schreiben wir die Farben auf die Wortgröße um und die Kacheln müssen zum Zeichnen in der richtigen Reihenfolge platziert werden.
Interessante Informationen: Sie können die automatische VDP-Inkrementierung über das 0xF-Register auf die Wortgröße umstellen. Dadurch wird die Adresserhöhung aus dem CRAM-Farbfüllcode entfernt.

VRAM

Das Shogi-Handbuch enthält die korrekte Reihenfolge der Kacheln für große Sprites, aber wir sind schlauer, also übernehmen wir die Indizes aus dem Blog ChibiAkumas, beginnen wir mit dem Zählen ab Index 0:

0 4 8 12

1 5 9 13

2 6 10 14

3 7 11 15

Warum steht alles auf dem Kopf? Was willst du, die Konsole ist japanisch! Es könnte sogar von rechts nach links sein!
Lassen Sie uns die Reihenfolge in der ASM-Sprite-Datei manuell ändern:

	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 
	и т.д. 

Laden Sie das Sprite wie normale Kacheln/Muster:

  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 

Um ein Sprite zu zeichnen, müssen Sie nur noch die Sprite-Tabelle ausfüllen

Sprite-Tabelle

Die Sprite-Tabelle wird im VRAM gefüllt, die Adresse ihres Standorts wird in das VDP-Register 0x05 eingetragen, die Adresse ist wieder knifflig, man kann es im Handbuch sehen, ein Beispiel für Adresse F000:

Ок, теперь запишем наш спрайт в таблицу. Для этого нужно заполнить “структуру” данных состоящую из четырех word. Бинарное описание структуры вы можете найти в мануале. Лично я сделал проще, таблицу спрайтов можно редактировать вручную в эмуляторе Exodus.
Die Strukturparameter sind aus dem Namen ersichtlich, zum Beispiel XPos, YPos – Koordinaten, Kacheln – Nummer der Startkachel zum Zeichnen, HSize, VSize – Sprite-Größen durch Hinzufügen der Teile 8×8, HFlip, VFlip – Hardware-Rotation des Sprites horizontal und vertikal.

Es ist sehr wichtig, sich daran zu erinnern, dass Sprites außerhalb des Bildschirms sein können. Dies ist das richtige Verhalten, weil… Off-Screen-Sprites aus dem Speicher entladen – eine ziemlich ressourcenintensive Aktivität.
Nachdem die Daten im Emulator eingegeben wurden, müssen sie vom VRAM an die Adresse 0xF000 kopiert werden. Exodus unterstützt diese Funktion ebenfalls.
Analog zum Zeichnen von Kacheln greifen wir zunächst auf den VDP-Steuerport zu, um mit dem Schreiben an der Adresse 0xF000 zu beginnen, und schreiben dann die Struktur in den Datenport.
Ich möchte Sie daran erinnern, dass die Beschreibung der VRAM-Adressierung im Handbuch oder im Blog Namenloser Algorithmus .

Kurz gesagt funktioniert die VDP-Adressierung folgendermaßen:
[..DC BA98 7654 3210 …. …. …. ..FE]
Dabei ist Hex die Bitposition in der gewünschten Adresse. Die ersten beiden Bits geben die Art des angeforderten Befehls an, zum Beispiel 01 – in den VRAM schreiben. Für die Adresse 0XF000 ergibt sich dann:
0111 0000 0000 0000 0000 0000 0000 0011 (70000003)

Als Ergebnis erhalten wir den 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 

Danach wird das Skelett-Sprite an den Koordinaten 256, 256 angezeigt. Cool, oder?

Links

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

Quellen

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

Schreiben in Assembly für Sega Genesis #3

In diesem Beitrag beschreibe ich, wie man mithilfe von Assembler ein Bild aus Kacheln auf dem Sega Genesis-Emulator anzeigt.
Das Splash-Bild von Demens Deum im Exodus-Emulator sieht folgendermaßen aus:

Die Ausgabe eines PNG-Bildes mithilfe von Kacheln erfolgt Schritt für Schritt:

  1. Verkleinerung des Bildes auf die Größe des Shogi-Bildschirms
  2. Konvertieren Sie PNG in Assembly-Datencode, getrennt in Farben und Kacheln
  3. Laden einer Farbpalette in CRAM
  4. Laden von Kacheln/Mustern in VRAM
  5. Laden von Kachelindizes an Plane A/B-Adressen im VRAM
  6. Sie können das Bild mit Ihrem bevorzugten Grafikeditor wie Blender auf die Größe des Shogi-Bildschirms verkleinern.

PNG-Konvertierung

Zum Konvertieren von Bildern können Sie das ImaGenesis-Tool verwenden; für die Arbeit unter Wine sind Visual Basic 6-Bibliotheken erforderlich, diese können mit winetricks (winetricks vb6run) installiert werden, oder RICHTX32.OCX kann aus dem Internet heruntergeladen und abgelegt werden für den ordnungsgemäßen Betrieb im Anwendungsordner.< /p>

In ImaGenesis müssen Sie 4-Bit-Farbe auswählen und Farben und Kacheln in zwei Dateien im Assembler-Format exportieren. Als nächstes müssen Sie in der Datei mit den Farben jede Farbe in ein Wort (2 Bytes) einfügen. Dazu verwenden Sie den Opcode dc.w.

Zum Beispiel CRAM-Begrüßungsbildschirm:

  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 

Lassen Sie die Kacheldatei unverändert, sie enthält bereits das richtige Format zum Laden. Beispiel eines Teils einer Kacheldatei:

	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 

Wie Sie dem obigen Beispiel entnehmen können, handelt es sich bei den Kacheln um ein 8×8-Raster, das aus CRAM-Farbpalettenindizes besteht.

Farben im CRAM

Das Laden in den CRAM erfolgt durch Setzen eines Farbladebefehls an eine bestimmte CRAM-Adresse im Steuerport (vdp-Steuerung). Das Befehlsformat ist im Sega Genesis Software Manual (1989) beschrieben. Ich füge nur hinzu, dass Sie nur 0x20000 zur Adresse hinzufügen müssen, um zur nächsten Farbe zu wechseln.

Als nächstes müssen Sie die Farbe in den Datenport (vdp-Daten) laden; Der einfachste Weg, das Laden zu verstehen, ist das folgende Beispiel:

    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 

Kacheln im VRAM

Als nächstes erfolgt das Laden von Kacheln/Mustern in den VRAM-Videospeicher. Wählen Sie dazu eine Adresse im VRAM aus, zum Beispiel 0x00000000. Analog zum CRAM kontaktieren wir den VDP-Steuerport mit einem Befehl zum Schreiben in den VRAM und die Startadresse.

Danach können Sie Langwörter in den VRAM hochladen; im Vergleich zum CRAM müssen Sie nicht für jedes Langwort die Adresse angeben, da es einen VRAM-Auto-Inkrementierungsmodus gibt. Sie können es mit dem VDP-Registerflag 0x0F (dc.b $02)

aktivieren

  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 

Kachelindizes in Ebene A/B

Jetzt müssen wir den Bildschirm entsprechend ihrem Index mit Kacheln füllen. Dazu wird VRAM an der Plane A/B-Adresse gefüllt, die in den VDP-Registern (0x02, 0x04) eingetragen ist. Weitere Informationen zur kniffligen Adressierung finden Sie im Handbuch von Sega; in meinem Beispiel ist die VRAM-Adresse 0xC000, laden wir die Indizes dort hoch.

Ihr Bild füllt ohnehin den VRAM-Bereich außerhalb des Bildschirms aus. Nach dem Zeichnen des Bildschirmbereichs sollte Ihr Renderer also mit dem Zeichnen aufhören und wieder fortfahren, wenn sich der Cursor in eine neue Zeile bewegt. Es gibt viele Möglichkeiten, dies zu implementieren; ich habe die einfachste Version des Zählens auf zwei Registern des Bildbreitenzählers und des Cursorpositionszählers verwendet.

Codebeispiel:

  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 

Danach müssen Sie nur noch das ROM mit Vasm zusammenbauen, den Simulator starten und sich das Bild ansehen.

Debuggen

Es wird nicht alles auf Anhieb klappen, daher möchte ich die folgenden Exodus-Emulator-Tools empfehlen:

  1. M68k-Prozessor-Debugger
  2. Ändern der Anzahl der m68k-Prozessorzyklen (für den Zeitlupenmodus im Debugger)
  3. Zuschauer CRAM, VRAM, Plane A/B
  4. Lesen Sie sorgfältig die Dokumentation für m68k und die verwendeten Opcodes (nicht alles ist so offensichtlich, wie es auf den ersten Blick scheint)
  5. Beispiele für Spielcode/Demontage auf Github ansehen
  6. Unterprogramme von Prozessorausnahmen implementieren und verarbeiten

Zeiger auf Prozessor-Ausnahme-Subroutinen werden im ROM-Header platziert; es gibt auch ein Projekt auf GitHub mit einem interaktiven Laufzeit-Debugger für Sega, genannt genesis-debugger.

Verwenden Sie alle verfügbaren Tools, haben Sie eine nette Codierung der alten Schule und vielleicht ist Blast Processing dabei!

Links

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

Quellen

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

 

Schreiben in Assembly für Sega Genesis #2

In diesem Beitrag beschreibe ich, wie man Farben in Assemblersprache in die Shogi-Palette lädt.
Das Endergebnis im Exodus-Emulator sieht folgendermaßen aus:

Um den Prozess zu vereinfachen, finden Sie im Internet ein PDF mit dem Titel Genesis Software Manual (1989). Es beschreibt den gesamten Prozess sehr detailliert. Tatsächlich handelt es sich bei dieser Notiz um einen Kommentar zum Originalhandbuch.< /p>

Um Farben auf den VDP-Chip des Sega-Emulators zu schreiben, müssen Sie die folgenden Dinge tun:

  • TMSS-Schutz deaktivieren
  • Korrekte Parameter in VDP-Register schreiben
  • Schreiben Sie die gewünschten Farben in CRAM

Für den Zusammenbau verwenden wir vasmm68k_mot und einen bevorzugten Texteditor, zum Beispiel Echo. Der Zusammenbau erfolgt mit dem Befehl:

Порты VDP

VDP чип общается с M68K через два порта в оперативной памяти – порт контроля и порт данных.
По сути:

  1. Через порт контроля можно выставлять значения регистрам VDP.
  2. Также порт контроля является указателем на ту часть VDP (VRAM, CRAM, VSRAM etc.) через которую передаются данные через порт данных

Интересная информация: Сега сохранила совместимость с играми Master System, на что указывает MODE 4 из мануала разработчика, в нем VDP переключается в режим Master System.

Объявим порты контроля и данных:

vdp_data_port        = $C00000

Отключить систему защиты TMSS

Защита от нелицензионных игр TMSS имеет несколько вариантов разблокировки, например требуется чтобы до обращения к VDP в адресном регистре A1 лежала строка “SEGA”.

MOVE.B A1,D0; Получаем версию хардвары цифрой из A1 в регистр D0 
ANDI.B 0x0F,D0; По маске берем последние биты, чтобы ничего не сломать 
BEQ.B SkipTmss; Если версия равна 0, скорее всего это японка или эмулятор без включенного TMSS, тогда идем в сабрутину SkipTmss 
MOVE.L "SEGA",A1; Или записываем строку SEGA в A1 

Korrekte Parameter in VDP-Register schreiben

Warum überhaupt die richtigen Parameter in den VDP-Registern einstellen? Der Grundgedanke ist, dass VDP viel kann. Sie müssen es also vor dem Rendern mit den erforderlichen Funktionen initialisieren, sonst versteht es einfach nicht, was Sie von ihm erwarten.

Jedes Register ist für eine bestimmte Einstellung/Betriebsart verantwortlich. Das Segov-Handbuch gibt alle Bits/Flags für jedes der 24 Register an, eine Beschreibung der Register selbst.

Nehmen wir vorgefertigte Parameter mit Kommentaren aus dem Bigevilcorporation-Blog:


VDPReg0:   dc.b $14 ;  0: H interrupt on, palettes on 
VDPReg1:   dc.b $74 ;  1: V interrupt on, display on, DMA on, Genesis mode on 
VDPReg2:   dc.b $30 ;  2: Pattern table for Scroll Plane A at VRAM $C000 
                    ;     (bits 3-5 = bits 13-15) 
VDPReg3:   dc.b $00 ;  3: Pattern table for Window Plane at VRAM $0000 
                    ;     (disabled) (bits 1-5 = bits 11-15) 
VDPReg4:   dc.b $07 ;  4: Pattern table for Scroll Plane B at VRAM $E000 
                    ;     (bits 0-2 = bits 11-15) 
VDPReg5:   dc.b $78 ;  5: Sprite table at VRAM $F000 (bits 0-6 = bits 9-15) 
VDPReg6:   dc.b $00 ;  6: Unused 
VDPReg7:   dc.b $00 ;  7: Background colour - bits 0-3 = colour, 
                    ;     bits 4-5 = palette 
VDPReg8:   dc.b $00 ;  8: Unused 
VDPReg9:   dc.b $00 ;  9: Unused 
VDPRegA:   dc.b $FF ; 10: Frequency of Horiz. interrupt in Rasters 
                    ;     (number of lines travelled by the beam) 
VDPRegB:   dc.b $00 ; 11: External interrupts off, V scroll fullscreen, 
                    ;     H scroll fullscreen 
VDPRegC:   dc.b $81 ; 12: Shadows and highlights off, interlace off, 
                    ;     H40 mode (320 x 224 screen res) 
VDPRegD:   dc.b $3F ; 13: Horiz. scroll table at VRAM $FC00 (bits 0-5) 
VDPRegE:   dc.b $00 ; 14: Unused 
VDPRegF:   dc.b $02 ; 15: Autoincrement 2 bytes 
VDPReg10:  dc.b $01 ; 16: Vert. scroll 32, Horiz. scroll 64 
VDPReg11:  dc.b $00 ; 17: Window Plane X pos 0 left 
                    ;     (pos in bits 0-4, left/right in bit 7) 
VDPReg12:  dc.b $00 ; 18: Window Plane Y pos 0 up 
                    ;     (pos in bits 0-4, up/down in bit 7) 
VDPReg13:  dc.b $FF ; 19: DMA length lo byte 
VDPReg14:  dc.b $FF ; 20: DMA length hi byte 
VDPReg15:  dc.b $00 ; 21: DMA source address lo byte 
VDPReg16:  dc.b $00 ; 22: DMA source address mid byte 
VDPReg17:  dc.b $80 ; 23: DMA source address hi byte, 
                    ;     memory-to-VRAM mode (bits 6-7)  

Ok, jetzt gehen wir zum Steuerport und schreiben alle Flags in die VDP-Register:

    move.l  #VDPRegisters,a0 ; Пишем адрес таблицы параметров в A1 
    move.l  #$18,d0          ; Счетчик цикла - 24 = 18 (HEX) в D0 
    move.l  #$00008000,d1    ; Готовим команду на запись в регистр VDP по индексу 0, по мануалу - 1000 0000 0000 0000 (BIN) = 8000 (HEX) 

FillInitialStateForVDPRegistersLoop: 
    move.b  (a0)+,d1         ; Записываем в D1 итоговое значение регистра VDP из таблицы параметров, на отправку в порт контроля VDP  
    move.w  d1,vdp_control_port     ; Отправляем итоговую команду + значение из D1 в порт контроля VDP 
    add.w   #$0100,d1        ; Поднимаем индекс регистра VDP на 1 (бинарное сложение +1 к индексу по мануалу Сеги) 
    dbra    d0,FillInitialStateForVDPRegistersLoop ; Уменьшаем счетчик регистров, продолжаем цикл если необходимо

Самое сложное это прочитать мануал и понять в каком формате подаются данные на порт контроля, опытные разработчики разберутся сразу, а вот неопытные… Немного подумают и поймут, что синтаксис для записи регистров такой:

0B100(5 бит – индекс регистра)(8 бит/байт – значение)

0B1000001001000101 – записать в регистр VDP 2 (00010), значение флажков 01000101.

Записать нужные цвета в CRAM

Далее идем писать два цвета в память цветов CRAM (Color RAM). Для этого пишем в порт контроля команду на доступ к цвету по индексу 0 в CRAM и отправляем по дата порту цвет. Все!

Пример:

    move.l  #$C0000000,vdp_control_port ; Доступ к цвету по индексу 0 в CRAM через порт контроля  
    move.w  #228,d0; Цвет в D0 
    move.w  d0,vdp_data_port; Отправляем цвет в порт данных 

Nachdem Sie den Emulator in Exodus erstellt und ausgeführt haben, sollte Ihr Bildschirm mit der Farbe 228 gefüllt sein.

Füllen wir es mit einer zweiten Farbe, basierend auf dem letzten Byte 127.

  move.l  #$C07f0000,vdp_control_port ; Доступ к цвету по байту 127 в CRAM через порт контроля 
  move.w  #69,d0; Цвет в D0 
  move.w  d0,vdp_data_port; Отправляем цвет в порт данных 

Links

https://gitlab.com/demensdeum/segagenesissamples
https://www.exodusemulator.com/
http://sun.hasenbraten.de/vasm/
https://tomeko.net/online_tools/bin_to_32bit_hex.php?lang=en

Quellen

https://namelessalgorithm.com/genesis/blog/genesis/
https://plutiedev.com/vdp-commands
https://huguesjohnson.com/programming/genesis/palettes/
https://www.chibiakumas.com/68000/helloworld.php#LessonH5
https://blog.bigevilcorporation.co.uk/2012/03/09/sega-megadrive-3-awaking-the-beast/

Schreiben in Assembly für Sega Genesis #1

Der erste Artikel über das Schreiben von Spielen für die klassische Sega Genesis-Konsole in Motorola 68000 Assembly.

Lassen Sie uns die einfachste Endlosschleife für Sega schreiben. Dafür benötigen wir: einen Assembler, einen Emulator mit Disassembler, einen bevorzugten Texteditor, ein grundlegendes Verständnis der Struktur von Sega Rum.

Für die Entwicklung verwende ich meinen eigenen Assembler/Disassembler Gen68KryBaby:

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

Das Tool ist in Python 3 entwickelt, zum Zusammenstellen wird eine Datei mit der Erweiterung .asm oder .gen68KryBabyDisasm als Eingabe bereitgestellt, die Ausgabe ist eine Datei mit der Erweiterung .gen68KryBabyAsm.bin, die im Emulator oder auf ausgeführt werden kann eine echte Konsole (Vorsicht, weggehen, die Konsole könnte explodieren!)

Das Zerlegen von ROMs wird ebenfalls unterstützt. Dazu müssen Sie eine ROM-Datei als Eingabe übermitteln, ohne die Erweiterungen .asm oder .gen68KryBabyDisasm. Die Opcode-Unterstützung wird abhängig von meinem Interesse am Thema und der Beteiligung von Mitwirkenden steigen oder sinken.

Struktur

Der Sega-ROM-Header belegt die ersten 512 Bytes. Es enthält Informationen über das Spiel, den Namen, unterstützte Peripheriegeräte, Prüfsumme und andere Systemflags. Ich gehe davon aus, dass die Konsole ohne Titel nicht einmal auf den Rum schaut und denkt, dass er falsch ist, und sagt: „Was gibst du mir hier?“

Nach dem Header kommt das Unterprogramm/Reset-Unterprogramm, in dem der m68K-Prozessor seine Arbeit beginnt. Okay, es ist eine Kleinigkeit – Opcodes (Operationscodes) finden, nämlich nichts tun (!) und zur Unterroutine an der Adresse im Speicher wechseln. Wenn Sie googeln, können Sie den NOP-Opcode finden, der nichts tut, und den JSR-Opcode, der einen bedingungslosen Sprung zur Argumentadresse ausführt, das heißt, er bewegt den Schlitten einfach ohne irgendwelche Launen dorthin, wo wir ihn fragen.

Alles zusammenfügen

Der Header-Donor für die ROM war eines der Spiele in der Beta-Version, derzeit als Hex-Daten aufgezeichnet.


 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  

Vollständiges Beispiel zusammen mit ROM-Header:

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

Wir sammeln als nächstes:

Запускаем ром 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

x86_64 Assembler + C = Eine Liebe

In dieser Notiz beschreibe ich den Prozess des Aufrufs von C-Funktionen aus dem Assembler.
Versuchen wir, printf(“Hello World!\n”); aufzurufen. und Exit(0);

    message: db "Hello, world!", 10, 0

section .text
    extern printf
    extern exit
    global main

main:
    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

Alles ist viel einfacher als es scheint. Im Abschnitt .rodata beschreiben wir statische Daten, in diesem Fall die Zeile „Hallo Welt!“, 10 ist ein Zeilenumbruchzeichen und wir vergessen auch nicht, es auf Null zu setzen.

Im Codeabschnitt deklarieren wir die externen Funktionen printf, Exit der stdio- und stdlib-Bibliotheken und deklarieren auch die Eingabefunktion main:

    extern printf
    extern exit
    global main

Wir übergeben 0 von der Rax-Funktion an das Rückgaberegister. Sie können mov rax, 0; aber um es zu beschleunigen, benutzen sie xor rax, rax; Als nächstes übergeben wir einen Zeiger auf die Zeichenfolge an das erste Argument:

Далее вызываем внешнюю функцию Си printf:

    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

In Analogie dazu übergeben wir 0 an das erste Argument und rufen „exit:“ auf.

    call exit

Wie die Amerikaner sagen:
Wer hört niemandem zu
Dieser Pilaw isst @ Alexander Pelevin

Quellen

https://www.devdungeon. com/content/how-mix-c-and-assembly
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html

Quellcode

https://gitlab.com/demensdeum/assembly-playground

Hallo Welt x86_64-Assembler

In diesem Beitrag beschreibe ich den Prozess der Einrichtung der IDE und schreibe den ersten Hello World in x86_64-Assembler für das Ubuntu-Linux-Betriebssystem.
Beginnen wir mit der Installation der SASM-IDE, Nasm-Assembler:

Далее запустим SASM и напишем Hello World:


section .text

main:
    mov rbp, rsp      ; for correct debugging
    mov rax, 1        ; write(
    mov rdi, 1        ;   STDOUT_FILENO,
    mov rsi, msg      ;   "Hello, world!\n",
    mov rdx, msglen   ;   sizeof("Hello, world!\n")
    syscall           ; );

    mov rax, 60       ; exit(
    mov rdi, 0        ;   EXIT_SUCCESS
    syscall           ; );

section .rodata
    msg: db "Hello, world!"
    msglen: equ $-msg

Hello World-Code aus dem Blog James Fisher, angepasst für Assemblierung und Debugging in SASM. In der SASM-Dokumentation heißt es, dass der Einstiegspunkt eine Funktion namens „main“ sein muss, da sonst das Debuggen und Kompilieren des Codes fehlerhaft ist.
Was haben wir in diesem Code gemacht? Habe einen Systemaufruf getätigt – Zugriff auf den Kernel des Linux-Betriebssystems mit korrekten Argumenten in Registern, einem Zeiger auf eine Zeichenfolge im Datenabschnitt.

Unter der Lupe

Sehen wir uns den Code genauer an:

global – директива ассемблера позволяющая задавать глобальные символы со строковыми именами. Хорошая аналогия – интерфейсы заголовочных файлов языков C/C++. В данном случае мы задаем символ main для функции входа.

section – директива ассемблера позволяющая задавать секции (сегменты) кода. Директивы section или segment равнозначны. В секции .text помещается код программы.

Обьявляем начало функции main. В ассемблере функции называются подпрограммами (subroutine)

Первая машинная команда mov – помещает значение из аргумента 1 в аргумент 2. В данном случае мы переносим значение регистра rbp в rsp. Из комментария можно понять что эту строку добавил SASM для упрощения отладки. Видимо это личные дела между SASM и дебаггером gdb.

Далее посмотрим на код до сегмента данных .rodata, два вызова syscall, первый выводит строку Hello World, второй обеспечивает выход из приложения с корректным кодом 0.

Представим себе что регистры это переменные с именами rax, rdi, rsi, rdx, r10, r8, r9. По аналогии с высокоуровневыми языками, перевернем вертикальное представление ассемблера в горизонтальное, тогда вызов syscall будет выглядеть так:

Тогда вызов печати текста:

Вызов exit с корректным кодом 0:

Рассмотрим аргументы подробнее, в заголовочном файле asm/unistd_64.h находим номер функции __NR_write – 1, далее в документации смотрим аргументы для write:
ssize_t write(int fd, const void *buf, size_t count);

Первый аргумент – файловый дескриптор, второй – буфер с данными, третий – счетчик байт для записи в дескриптор. Ищем номер файлового дескриптора для стандартного вывода, в мануале по stdout находим код 1. Далее дело за малым, передать указатель на буфер строки Hello World из секции данных .rodata – msg, счетчик байт – msglen, передать в регистры rax, rdi, rsi, rdx корректные аргументы и вызвать syscall.

Обозначение константных строк и длины описывается в мануале nasm: