In this post I will describe how to display an image from tiles on the Sega Genesis emulator using assembler.
The splash picture of Demens Deum in Exodus emulator will look like this:
The process of outputting a PNG image using tiles is done in steps:
- Reducing the image to fit the Sega screen
- Convert PNG to assembler data-code, with division into colors and tiles
- Loading the color picker into CRAM
- Loading tiles / patterns into VRAM
- Loading tile indices at Plane A / B addresses in VRAM
You can reduce the image to fit Sega’s screen using your favorite graphics editor, for example Blender.
To convert images, you can use the ImaGenesis tool, to work under wine you need Visual Basic 6 libraries, they can be installed using winetricks (winetricks vb6run), or RICHTX32.OCX can be downloaded from the Internet and put into the application folder for correct operation. < / p>
In ImaGenesis, you need to select a 4-bit chroma, export colors and tiles in two assembly files. Next, in the file with colors, you need to put each color in a word (2 bytes), for this you use the dc.w opcode.
For example CRAM splash screen:
Colors: 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
Leave the tile file as it is, it already contains the correct format for loading. An example of a part of a tile file:
Tiles: 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
As you can see from the example above, the tiles are an 8×8 grid of CRAM color palette indices.
Colors in CRAM
Loading into CRAM is performed by setting the color load command at a specific CRAM address to the vdp control port. The command format is described in the Sega Genesis Software Manual (1989), I just add that it is enough to add to the address 0x20000 to go to the next color.
Next, you need to load the color into the data port (vdp data); The easiest way to understand loading is with the example below:
VDPCRAMFillLoop: 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
Tiles in VRAM
Next comes the loading of tiles / patterns into VRAM. To do this, select an address in VRAM, for example 0x00000000. By analogy with CRAM, we address the VDP control port with a command to write to VRAM and a starting address.
After that, you can upload longwords to VRAM, compared to CRAM, you do not need to specify an address for each longword, since there is a VRAM auto-increment mode. You can enable it using the register flag VDP 0x0F (dc.b $ 02)
TilesVRAM: 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
Tile indices in Plane A / B
Now we have to fill the screen with tiles by their index. For this, VRAM is filled at the Plane A / B address, which is put in the VDP registers (0x02, 0x04). For more information about tricky addressing, see Sega’s manual, in my example the VRAM address is 0xC000, let’s unload the indexes there.
Your picture will fill the VRAM off-screen space anyway, so after rendering the screen space, your render should stop rendering and resume when the cursor moves to a new line. There are many ways to implement this set, I used the simplest version of counting on two registers of the image width counter, the cursor position counter.
FillBackground: 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
After that, it remains only to collect rum using vasm, launching the simulator, and see the picture.
Not everything will work out right away, so I would like to recommend the following Exodus emulator tools:
- m68k processor debugger
- Changing the number of cycles of the m68k processor (for slow-mo mode in debugger)
- Viewers CRAM, VRAM, Plane A / B
- Carefully read the documentation for m68k, used opcodes (not everything is as obvious as it seems at first glance)
- See examples of code / game disassembly on github
- Implement processor execution sabrutines, process them
Pointers to processor execution sabrutines are put in the title of the rum, there is also a project on GitHub with an interactive runtime debugger for Sega, called genesis-debugger.
Use all available tools, nice old school coding and may Blast Processing come with you!