为 Sega Genesis #5 编写汇编

在这篇文章中,我将描述读取操纵杆、更改精灵位置、水平翻转、Sega Genesis 模拟器以及可能的控制台本身的过程。

读取点击并处理将棋操纵杆的“事件”按照以下方案进行:

  1. 请求按下按钮的位组合
  2. 读取按下的按钮的位
  3. 游戏逻辑级别的处理

要移动骨架精灵,我们需要存储当前位置的变量。

内存

游戏逻辑变量存储在RAM中;到目前为止人们还没有想出更好的办法。让我们声明变量地址并更改渲染代码:

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 

如您所见,可用于工作的地址从 0xFF0000 开始,到 0xFFFFFF 结束,总共有 64 KB 的内存可供我们使用。骨架位置在 sculptureXpos、sculptureYpos 处声明,水平翻转在 sculptureHorizo​​ntalFlip 处声明。

手柄

与 VDP 类比,游戏手柄的工作分别通过两个端口进行:控制端口和数据端口,第一个为0xA10009和0xA10003共号。使用游戏手柄时有一个有趣的功能——首先,您需要请求用于轮询的按钮组合,然后在等待总线上的更新后,读取所需的按键。对于 C/B 和 D-pad 按钮,这是 0x40,示例如下:

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

在寄存器 d2 中,按钮按下或未按下的状态将保留,一般来说,通过日期端口请求的内容将保留。之后,转到您最喜欢的模拟器的 Motorola 68000 寄存器查看器,根据击键查看 d2 寄存器等于什么。您可以通过聪明的方式在手册中找到答案,但我们并不相信他们的话。进一步处理 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 

此后,骨架运行速度变慢,这正是所需要的。据我所知,“放慢速度”的最常见选项是计算垂直同步标志;您可以计算屏幕被绘制的次数,从而与特定的 fps 相关联。

链接

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

来源

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

为 Sega Genesis #4 编写汇编

在这篇文章中,我将介绍如何使用 Sega Genesis 控制台的 VDP 模拟器绘制精灵。
渲染精灵的过程与渲染图块非常相似:

  1. 将颜色加载到 CRAM
  2. 将部分精灵 8×8 上传到 VRAM
  3. 在 VRAM 中填充精灵表

例如,让我们取一个带有剑的骷髅精灵,大小为 32×32 像素

Skeleton Guy [Animated] by Disthorn

CRAM

使用 ImaGenesis,我们将其转换为用于汇编程序的 CRAM 颜色和 VRAM 模式。之后,我们将得到两个asm格式的文件,然后我们将颜色重写为字大小,并且图块必须按照正确的顺序放置才能绘制。
有趣的信息:您可以通过 0xF 寄存器将 VDP 自动递增切换为字大小,这将从 CRAM 颜色填充代码中删除地址增量。

显存

将棋手册对于大精灵有正确的图块顺序,但我们更聪明,所以我们将从博客中获取索引ChibiAkumas,让我们从索引 0 开始计数:

0 4 8 12

1 5 9 13

2 6 10 14

3 7 11 15

为什么一切都是颠倒的?你想要什么,控制台是日本的!甚至可以从右到左!
让我们手动更改 asm sprite 文件中的顺序:

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

像常规图块/图案一样加载精灵:

  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 

要绘制精灵,剩下的就是填写精灵表

精灵表

精灵表被填充在VRAM中,其所在位置的地址被输入到VDP寄存器0x05中,这个地址又很棘手,你可以在手册中看到它,地址F000的例子:

Ок, теперь запишем наш спрайт в таблицу. Для этого нужно заполнить “структуру” данных состоящую из четырех word. Бинарное описание структуры вы можете найти в мануале. Лично я сделал проще, таблицу спрайтов можно редактировать вручную в эмуляторе Exodus.
结构体参数从名字上就很明显了,例如XPos、YPos–坐标,瓷砖–绘制起始图块的编号、HSize、VSize –精灵尺寸通过添加第 8×8、HFlip、VFlip– 部分来实现。精灵水平和垂直的硬件旋转。

记住精灵可以离开屏幕非常重要,这是正确的行为,因为……从内存中卸载离屏精灵 –相当耗费资源的活动。
模拟器中填充数据后,需要从VRAM复制到地址0xF000,Exodus也支持此功能。
类比绘制图块,我们首先访问VDP控制端口从地址0xF000开始写入,然后将结构体写入数据端口。
让我提醒您,VRAM寻址的描述可以在手册或博客中阅读 无名算法 .

简而言之,VDP 寻址的工作原理如下:
[..DC BA98 7654 3210 …. …。 …。 ..FE]
其中 hex 是所需地址中的位位置。前两位是请求的命令类型,例如 01 –写入 VRAM。那么对于地址 0XF000 结果是:
0111 0000 0000 0000 0000 0000 0000 0011 (70000003)

结果我们得到代码:

  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 

之后,骨骼精灵将显示在坐标 256, 256 处。很酷吧?

链接

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

来源

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

为 Sega Genesis #3 编写汇编

在这篇文章中,我将描述如何使用汇编程序在 Sega Genesis 模拟器上显示图块中的图像。
Exodus 模拟器中的 Demens Deum 初始图像将如下所示:

使用图块输出PNG图像的过程是逐步完成的:

  1. 将图像缩小到将棋屏幕的大小
  2. 将 PNG 转换为汇编数据代码,分为颜色和图块
  3. 将调色板加载到 CRAM
  4. 将图块/图案加载到 VRAM
  5. 加载 VRAM 中平面 A/B 地址处的切片索引
  6. 您可以使用您最喜欢的图形编辑器(例如 Blender)将图像缩小到将棋屏幕的大小。

PNG 转换

要转换图像,可以使用ImaGenesis工具在wine下工作;需要Visual Basic 6库,可以使用winetricks (winetricks vb6run)安装,或者可以从网上下载RICHTX32.OCX并放在正确操作的应用程序文件夹。

在 ImaGenesis 中,您需要选择 4 位颜色,将颜色和图块导出到两个汇编格式文件。接下来,在带有颜色的文件中,您需要将每种颜色放入一个单词(2 个字节)中,为此您使用 dc.w 操作码。

例如 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 

保持图块文件不变,它已经包含了正确的加载格式。部分图块文件的示例:

	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 

正如您从上面的示例中看到的,图块是由 CRAM 调色板索引组成的 8×8 网格。

CRAM 中的颜色

通过将颜色加载命令设置到控制端口(vdp 控制)中的特定 CRAM 地址来完成加载到 CRAM 中。命令格式在Sega Genesis软件手册(1989)中有描述,我只是补充一下,您只需在地址中添加0x20000即可移动到下一个颜色。

接下来需要将颜色加载到数据端口(vdp数据);理解加载的最简单方法是使用下面的示例:

    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 

VRAM 中的图块

接下来是将图块/图案加载到 VRAM 视频内存中。为此,请在 VRAM 中选择一个地址,例如 0x00000000。与 CRAM 类比,我们通过命令联系 VDP 控制端口以写入 VRAM 和起始地址。

此后,您可以将长字上传到VRAM;与CRAM相比,您不需要为每个长字指定地址,因为有VRAM自动递增模式。您可以使用 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 

平面 A/B 中的平铺索引

现在我们必须根据瓷砖的索引来填充屏幕。为此,VRAM 被填充到平面 A/B 地址,该地址被输入到 VDP 寄存器(0x02、0x04)中。有关棘手寻址的更多信息,请参见 Sega 的手册;在我的示例中,VRAM 地址是 0xC000,让我们将索引上传到那里。

无论如何,您的图像都会填充屏幕外的 VRAM 空间,因此在绘制屏幕空间后,您的渲染器应该停止绘制,并在光标移动到新行时再次继续。如何实现这一点有很多选择;我使用了最简单的版本,即对图像宽度计数器和光标位置计数器的两个寄存器进行计数。

代码示例:

  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 

之后,剩下的就是使用 vasm 组装 rom,启动模拟器,然后查看图片。

调试

并不是所有事情都能立即解决,所以我想推荐以下 Exodus 模拟器工具:

  1. M68k 处理器调试器
  2. 更改 m68k 处理器周期数(针对调试器中的慢动作模式)
  3. 查看器 CRAM、VRAM、平面 A/B
  4. 仔细阅读 m68k 的文档、所使用的操作码(并非所有内容都像乍一看那么明显)
  5. 在 github 上查看游戏代码/反汇编示例
  6. 实现处理器异常的子例程并进行处理

指向处理器异常子例程的指针放置在 ROM 标头中;GitHub 上还有一个带有 Sega 交互式运行时调试器的项目,称为 genesis-debugger。

使用所有可用的工具,进行良好的老式编码,并且Blast Processing可能会与您同在!

链接

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

来源

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

 

为 Sega Genesis #2 编写汇编语言

在这篇文章中,我将描述如何使用汇编语言将颜色加载到将棋调色板中。
Exodus 模拟器中的最终结果将如下所示:

为了让这个过程更简单,在网上找到了一个pdf,叫做Genesis Software Manual (1989),它非常详细地描述了整个过程,实际上,这个说明是对原始手册的注释。< /p>

为了将颜色写入Sega模拟器的VDP芯片,您需要执行以下操作:

  • 停用 TMSS 保护
  • 将正确的参数写入VDP寄存器
  • 将所需的颜色写入CRAM

对于组装,我们将使用 vasmm68k_mot 和最喜欢的文本编辑器,例如 echo。使用以下命令进行组装:

Порты 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 

将正确的参数写入VDP寄存器

为什么要在 VDP 寄存器中设置正确的参数?我们的想法是,VDP 可以做很多事情,因此在渲染之前,您需要使用必要的功能对其进行初始化,否则它根本无法理解他们想要从中得到什么。

每个寄存器负责特定的设置/操作模式。 Segov 手册指出了 24 个寄存器中每个寄存器的所有位/标志,以及寄存器本身的描述。

让我们采用现成的参数以及来自 bigevilcorporation 博客的评论:


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)  

好的,现在让我们进入控制端口并将所有标志写入 VDP 寄存器:

    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; Отправляем цвет в порт данных 

在 Exodus 的模拟器中构建并运行后,您的屏幕应填充颜色 228。

让我们根据最后一个字节 127 用第二种颜色填充它。

<代码>

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

链接

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

来源

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/

为 Sega Genesis #1 编写汇编

第一篇专门为 Motorola 68000 Assembly 中的经典 Sega Genesis 控制台编写游戏的文章。

让我们为 Sega 编写最简单的无限循环。为此,我们需要:一个汇编器、一个带有反汇编器的模拟器、一个最喜欢的文本编辑器、对 Sega rum 结构的基本了解。

对于开发,我使用自己的汇编器/反汇编器 Gen68KryBaby:

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

该工具是用 Python 3 开发的,用于汇编时提供扩展名为 .asm 或 .gen68KryBabyDisasm 的文件作为输入,输出是扩展名为 .gen68KryBabyAsm.bin 的文件,该文件可以在模拟器或计算机上运行一个真正的控制台(小心,走开,控制台可能会爆炸!)

还支持反汇编 rom,为此您需要提交一个 rom 文件作为输入,不带 .asm 或 .gen68KryBabyDisasm 扩展名。操作码支持将根据我对该主题的兴趣和贡献者的参与而增加或减少。

结构

Sega ROM 标头占据前 512 个字节。它包含有关游戏、名称、支持的外围设备、校验和以及其他系统标志的信息。我认为如果没有标题,控制台甚至不会看朗姆酒,认为它是不正确的,并说“你在这里给我什么?”

标头之后是子例程/重置子例程,这是 m68K 处理器开始工作的地方。好吧,这是一件小事……查找操作码(操作码),即什么都不做(!)并切换到内存中地址处的子程序。通过谷歌搜索,您可以找到 NOP 操作码,它不执行任何操作,以及 JSR 操作码,它执行无条件跳转到参数地址,也就是说,它只是将回车移动到我们要求的位置,没有任何突发奇想。

将它们放在一起

ROM 的标头捐赠者是 Beta 版本中的游戏之一,目前记录为十六进制数据。


 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  

完整示例以及 rom 标头:

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

我们接下来收集:

Запускаем ром 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 汇编器 + C = 一份爱

在这篇笔记中我将描述从汇编程序调用 C 函数的过程。
让我们尝试调用 printf(“Hello World!\n”);并退出(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

一切都比看起来简单得多,在 .rodata 部分我们将描述静态数据,在本例中为“Hello, world!”行,10 是换行符,我们也不要忘记将其设为 null。

在代码部分,我们将声明外部函数 printf、stdio、stdlib 库的 exit,并且我们还将声明输入函数 main:

    extern printf
    extern exit
    global main

我们从rax函数中将0传递给返回寄存器,您可以使用mov rax, 0;但为了加快速度,他们使用 xor rax, rax;接下来,我们将指向字符串的指针传递给第一个参数:

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

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

以此类推,我们将 0 传递给第一个参数并调用 exit:

    call exit

正如美国人所说:
谁不听谁的
那抓饭正在吃@ Alexander Pelevin

来源

https://www.devdungeon。 com/content/how-mix-c-and-assemble
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

源代码

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

Hello World x86_64 汇编器

在这篇文章中,我将描述设置 IDE 的过程,为 Ubuntu Linux 操作系统在 x86_64 汇编程序中编写第一个 Hello World。
让我们从安装 SASM IDE、nasm 汇编器开始:

Далее запустим 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 代码取自博客 James Fisher,适用于 SASM 中的汇编和调试。 SASM文档规定,入口点必须是名为main的函数,否则代码的调试和编译将不正确。
我们在这段代码中做了什么?拨打了系统调用–使用寄存器中的正确参数、指向数据部分中的字符串的指针来访问 Linux 操作系统内核。

放大镜下

让我们更详细地看一下代码:

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: