このメモでは、ジョイスティックの読み取り、スプライトの位置の変更、水平方向の反転、Sega Genesis エミュレータ、および場合によってはコンソール自体のプロセスについて説明します。
将棋ジョイスティックのクリックの読み取りと「イベント」の処理は、次のスキームに従って行われます。
<オル>
スケルトン スプライトを移動するには、現在の位置の変数を保存する必要があります。
RAM
ゲーム ロジック変数は 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 のメモリが利用可能です。スケルトンの位置は、skeletonXpos、skeletonYpos で宣言され、水平方向の反転は、skeletonhorizontalflip で宣言されます。
ジョイパッド
VDP と同様に、ジョイパッドの操作は 2 つのポートを介して個別に行われます。制御ポートとデータ ポート。最初のポートは 0xA10009 と 0xA10003 の共通番号です。ジョイパッドを使用する場合、興味深い機能が 1 つあります。まず、ポーリング用のボタンの組み合わせをリクエストする必要があります。次に、バス上の更新を待った後、必要なボタンの押し方を読み取ります。 C/B および D パッド ボタンの場合、これは 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 エミュレータを使用してスプライトを描画する方法を説明します。
スプライトのレンダリングのプロセスは、タイルのレンダリングと非常に似ています。
<オル>
たとえば、32×32 ピクセルの剣を持ったスケルトンのスプライトを考えてみましょう。![]()
Skeleton Guy [Animated] by Disthorn
クラム
ImaGenesis を使って、アセンブラ用の CRAM カラーと VRAM パターンに変換してみましょう。この後、asm形式のファイルを2つ取得し、色をワードサイズに書き換え、タイルを正しい順序で配置して描画する必要があります
。興味深い情報: 0xF レジスタを介して VDP 自動インクリメントをワード サイズに切り替えることができます。これにより、CRAM カラー フィル コードからアドレス インクリメントが削除されます。
VRAM
将棋マニュアルには、大きなスプライトに対する正しい牌の順序が記載されていますが、私たちはより賢いので、ブログからインデックスを取得します。ちび悪魔、インデックス 0 から数え始めましょう:
0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
なぜすべてが逆さまなのですか?どうしたものか、コンソールは日本語です!右から左でも構いません
よ!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
и т.д.
通常のタイル/パターンと同様にスプライトを読み込みます:
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 は目的のアドレスのビット位置です。最初の 2 ビットは、要求されているコマンドのタイプです (例: 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 画像を出力するプロセスは、段階的に実行されます。
<オル>
PNG 変換
画像を変換するには、ImaGenesis ツールを使用できます。wine で作業するには、Visual Basic 6 ライブラリが必要です。これらのライブラリは、 winetricks (winetricks vb6run) を使用してインストールするか、インターネットから RICHTX32.OCX をダウンロードして配置できます。正しく動作するためにアプリケーション フォルダーを参照してください。
ImaGenesis では、4 ビット カラーを選択し、カラーとタイルを 2 つのアセンブラ形式ファイルにエクスポートする必要があります。次に、色を含むファイルで、各色を単語 (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 の色
CRAM へのロードは、制御ポート (vdp 制御) への特定の 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 と同様に、VRAM に書き込むコマンドと開始アドレスを使用して VDP 制御ポートに接続します。
この後、ロングワードを VRAM にアップロードできます。VRAM 自動インクリメント モードがあるため、CRAM とは異なり、各ロングワードにアドレスを指定する必要がありません。 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 のタイル インデックス
次に、インデックスに従って画面をタイルで埋める必要があります。これを行うには、VDP レジスタ (0x02、0x04) に入力されるプレーン A/B アドレスで VRAM が埋められます。難しいアドレス指定の詳細については、Sega のマニュアルを参照してください。この例では、VRAM アドレスは 0xC000 です。そこにインデックスをアップロードしましょう。
いずれにせよ、画像はオフスクリーン VRAM スペースを埋めるため、スクリーンスペースを描画した後、レンダラーは描画を停止し、カーソルが新しい行に移動したときに再び続行する必要があります。これを実装する方法には多くのオプションがあります。私は、画像幅カウンタとカーソル位置カウンタの 2 つのレジスタをカウントする最も単純なバージョンを使用しました。
コード例:
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 エミュレータ ツールをお勧めします。
<オル>
プロセッサ例外サブルーチンへのポインタは、ROM ヘッダーに配置されます。GitHub には、genesis-debugger と呼ばれる、Sega 用の対話型ランタイム デバッガーを備えたプロジェクトもあります。
利用可能なツールをすべて使用し、昔ながらの優れたコーディングを行い、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#レッスンH5
https://huguesjohnson.com/programming/genesis/tiles-sprites/
Sega Genesis #2 のアセンブリでの書き込み
この投稿では、アセンブリ言語で将棋パレットに色を読み込む方法について説明します。
Exodus エミュレータの最終結果は次のようになります。
プロセスを簡単にするために、Genesis Software Manual (1989) という名前の PDF をインターネット上で見つけてください。このマニュアルにはプロセス全体が詳細に説明されています。実際、このメモはオリジナルのマニュアルの解説です。< /p>
Sega エミュレータの VDP チップに色を書き込むには、次のことを行う必要があります。
- TMSS 保護を無効にする
- 正しいパラメータを VDP レジスタに書き込む
- 希望の色を CRAM に書き込みます
アセンブリには、vasmm68k_mot とお気に入りのテキスト エディタ (echo など) を使用します。アセンブリは次のコマンドで実行されます。
Порты VDP
VDP чип общается с M68K через два порта в оперативной памяти – порт контроля и порт данных.
По сути:
- Через порт контроля можно выставлять значения регистрам VDP.
- Также порт контроля является указателем на ту часть 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 は多くの機能を備えているため、レンダリングする前に必要な機能を使用して 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)
OK、制御ポートに移動して、すべてのフラグを 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 に基づいて、2 番目の色で塗りつぶしてみましょう。
<コード>コード>
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/ p>
このツールは Python 3 で開発されており、アセンブリの場合、拡張子 .asm または .gen68KryBabyDisasm を持つファイルが入力として提供され、出力は拡張子 .gen68KryBabyAsm.bin を持つファイルとなり、エミュレータまたは上で実行できます。本物のコンソール (コンソールが爆発する可能性があるので、注意して離れてください!)
ROM の逆アセンブルもサポートされており、そのためには、.asm または .gen68KryBabyDisasm 拡張子を付けずに、ROM ファイルを入力として送信する必要があります。オペコードのサポートは、トピックに対する私の関心と貢献者の参加に応じて増減します。
構造
Sega rom ヘッダーは最初の 512 バイトを占めます。これには、ゲーム、名前、サポートされている周辺機器、チェックサム、およびその他のシステム フラグに関する情報が含まれています。タイトルがなければ、コンソールはラム酒を確認することさえせず、それが間違っていると判断し、「ここで何をくれるのですか?」と言うと思います。
ヘッダーの後にサブルーチン/リセット サブルーチンが続き、ここで m68K プロセッサが作業を開始します。さて、それは小さな問題です –オペコード (オペレーション コード) を見つけます。つまり、何もせず (!)、メモリ内のアドレスのサブルーチンに切り替えます。グーグルで検索すると、何もしない NOP オペコードと、引数アドレスへの無条件ジャンプを実行する JSR オペコードを見つけることができます。つまり、何の気まぐれもせずに、要求した場所にキャリッジを移動するだけです。
すべてをまとめる
ROM のヘッダードナーはベータ版のゲームの 1 つであり、現在は 16 進データとして記録されています。
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
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 = One Love
このノートでは、アセンブラから C 関数を呼び出すプロセスについて説明します。
printf(“Hello World!\n”); を呼び出してみましょう。そして 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
すべては見かけよりもはるかに単純です。.rodata セクションでは静的データを記述します。この場合、行「Hello, world!」、10 は改行文字であり、null にすることも忘れません。
コード セクションでは、外部関数 printf、stdio の出口、stdlib ライブラリを宣言し、入力関数 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-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
ソースコード
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:
msglen equ $-message
Достаточно просто да?
Источники
https://github.com/Dman95/SASM
https://www.nasm.us/xdoc/2.15.05/html/nasmdoc0.html
http://acm.mipt.ru/twiki/bin/view/Asm/HelloNasm
https://jameshfisher.com/2018/03/10/linux-assembly-hello-world/
http://www.ece.uah.edu/~milenka/cpe323-10S/labs/lab3.pdf
https://c9x.me/x86/html/file_module_x86_id_176.html
https://www.recurse.com/blog/7-understanding-c-by-learning-assembly
https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%BB%D0%BE%D0%B3_%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D0%B4%D1%83%D1%80%D1%8B
https://www.tutorialspoint.com/assembly_programming/assembly_basic_syntax.html
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://man7.org/linux/man-pages/man2/syscall.2.html
https://en.wikipedia.org/wiki/Write_(system_call)
Исходный код
https://gitlab.com/demensdeum/assembly-playground