Compiling a bootable ISO?

Ask anything your want about Mega/SegaCD programming.

Moderator: Mask of Destiny

Post Reply
RavenWorks
Newbie
Posts: 3
Joined: Sun Mar 17, 2019 12:39 am

Compiling a bootable ISO?

Post by RavenWorks » Sat Mar 23, 2019 5:49 pm

Hello! Newbie question here—I've written homebrew for other systems before, even dual-CPU ones like the DS, but I'm at a loss figuring out where to start with writing a little Sega CD demo that will compile to a bootable ISO (with custom code for both CPUs).

I gave BasiEgaXorz a shot, and that basically worked fine, except that there don't seem to be any functions for accessing the scaling/rotation hardware, or any straightforward way to include your own code for the Sub CPU so as to do it yourself (I thought about including some assembly and then manually copying it into the Sub CPU's program memory at runtime, but that's starting to seem complicated enough that doing it through BasiEgaXorz instead of just through a compiler starts to feel silly)

I guess I'm asking is, are there any good examples or instructions of how to compile to a Mode 2 .iso rather than a cartridge .bin in something like SGDK? (What does the Mode 2 iso format even look like—is it as simple as compiling one cartridge-style ROM for each CPU and then just inserting them at the right offsets into the CD? Or does the bios only load one CPU's code, and then that CPU's program has to manually load the other's, or something?)

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Re: Compiling a bootable ISO?

Post by Chilly Willy » Sun Mar 24, 2019 12:06 am

The boot sector of the CD contains the initial program loader, which runs on the MD side. This loads the CD side code, which then controls everything. I've done a few examples you'll find here, including a raycasting demo with MOD music on the PCM chip. There's not any real ASIC drawing examples yet, though I know someone working on one.

Right now, there's nothing like SGDK for the CD, it's a bit more low-level right now, so you need more knowledge of the CD hardware and how it works. You'll want to start by looking at all the various open source CD code, like Frog Feast and my PCM music and raycasting examples.

RavenWorks
Newbie
Posts: 3
Joined: Sun Mar 17, 2019 12:39 am

Re: Compiling a bootable ISO?

Post by RavenWorks » Sun Mar 24, 2019 12:58 am

OK, that makes sense! So the idea really is basically "compile some assembly, append the result into the initial Main CPU program (as "data"), then DMA it into the Sub CPU's program memory"? (Does the Sub CPU always jump execution to a certain address when it gets back access to its program memory, I guess?)

Having demos to refer to sounds great—where exactly can I find them? I think I've found Frog Feast here: http://frogfeast.rastersoft.net/SegaCDSrc.html but I'm not sure where to find the raycasting+pcm demo (I'm not planning on doing much of either, but more to refer to never hurts!)

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Re: Compiling a bootable ISO?

Post by Chilly Willy » Sun Mar 24, 2019 1:30 pm

Well, the cd boot sector is split into two parts - the IPL for the MD side, and then the loader code for the CD side. The MD initial program loader is in the first sector and looks like this

Code: Select all

| SEGA CD Loader code
| by Chilly Willy
| First part of cdrom header
|
| Main-CPU IP, comprising sectors 0 and 1. Sector 0 is loaded automatically,
| while sector 1 is from the header.

        .text

| Standard MegaCD Volume Header at 0x0

        .ascii  "SEGADISCSYSTEM  "
        .asciz  "CDBOOTLOADR"
        .word   0x0100
        .word   0x0001
        .asciz  "SEGACD BOOT"
        .word   0x0001
        .word   0x0000
        .long   0x00000800              /* main 68000 initial program cd offset */
        .long   0x00000800              /* main 68000 initial program cd size */
        .long   0x00000000              /* main 68000 initial program entry offset */
        .long   0x00000000              /* main 68000 initial program Work RAM size */
        .long   0x00001000              /* sub 68000 initial program cd offset */
        .long   0x00007000              /* sub 68000 initial program cd size */
        .long   0x00000000              /* sub 68000 initial program entry offset */
        .long   0x00000000              /* sub 68000 initial program Work RAM size */
        .ascii  "03242013        "      /* date - MMDDYYYY */
        .space  160, 0x20

| Standard MegaDrive ROM header at 0x100

        .ascii  "SEGA CD Loader  "
        .ascii  "(C)2013         "
        .ascii  "CD Boot Loader                                  "
        .ascii  "CD Boot Loader                                  "
        .ascii  "GM MK-0000 -00  "
        .ascii  "J6              "      /* controller info */
        .ascii  "                            "
        .ascii  "            "          /* modem info */
        .ascii  "                                        "
        .ascii  "JUE             "      /* region info */


| Standard MegaCD startup code at 0x200
|
| This data and the code following is copied to the Main-CPU Work RAM at
| 0xFF0000 and run.

        .global _start
_start:
        .ifdef  REGION_US
        .incbin "ipl/us.bin", 0, 0x584
        .endif
        .ifdef  REGION_EU
        .incbin "ipl/eu.bin", 0, 0x56E
        .endif
        .ifdef  REGION_JP
        .incbin "ipl/jp.bin", 0, 0x156
        .endif

| fall into startup code for Main-CPU

        lea     0xA10000.l,a5
        move.b  #0,0x200E(a5)           /* clear main comm port */

| wait for MD init handshake from CD
0:
        cmpi.b  #'I,0x200F(a5)
        bne.b   0b
        bset    #1,0x2003(a5)           /* give Sub-CPU Word RAM */

        move.b  #'B,0x200E(a5)          /* main comm port - do boot */

| wait for MD code handshake from CD
1:
        cmpi.b  #'M,0x200F(a5)
        bne.b   1b
2:
        btst    #0,0x2003(a5)           /* Main-CPU has Word RAM? */
        beq.b   2b

        move.w  #0x2700,sr
        lea     0xFFFD00,a0
        movea.l a0,sp

        jmp     0x200000.l
It is a couple headers followed by the security blob which then falls into the main MD loader code. The security blob is checksummed by the CD BIOS, which is why it's incbin'd into the code. This is legal in the US because copyright cannot be used for security, so the binary blob fails as copyrightable content. This was decided when Lexmark lost their case where they tried a similar trick on printer cart heads.

The MD loader must know how the CD loader works - note how I look for bytes in the comm regs. This code should be short - it's for loading code, not being game code. I tend to load the main MD code for a CD game into word ram, then jump to 0x200000. That code copies code to 0xF01000 and then jumps to that, so I'm not actually running from word ram, I run from work ram.

The CD side of the boot sector code looks like this

Code: Select all

        .org    0x1000


| Second part of cdrom header
|
| Sub-CPU IP, comprising sectors 2 to 15 from the header.

| Standard MegaCD Sub-CPU Program Header at 0x1000 (loaded to 0x6000)

SPHeader:
        .asciz  "MAIN-SUBCPU"
        .word   0x0001,0x0000
        .long   0x00000000
        .long   0x00000000
        .long   SPHeaderOffsets-SPHeader
        .long   0x00000000

SPHeaderOffsets:
        .word   SPInit-SPHeaderOffsets
        .word   SPMain-SPHeaderOffsets
        .word   SPInt2-SPHeaderOffsets
        .word   SPNull-SPHeaderOffsets
        .word   0x0000

| Sub-CPU Program Initialization (VBlank not enabled yet)

SPInit:
        lea     GlobalVars(pc),a0
        move.l  #0,VBLANK_HANDLER(a0)   /* clear VBlank handler vector */
        lea     InitCD(pc),a1
        move.l  a1,INIT_CD(a0)          /* set InitCD vector */
        lea     ReadCD(pc),a1
        move.l  a1,READ_CD(a0)          /* set ReadCD vector */
        lea     SetCWD(pc),a1
        move.l  a1,SET_CWD(a0)          /* set SetCWD vector */
        lea     FirstDirSector(pc),a1
        move.l  a1,FIRST_DIR_SEC(a0)    /* set FirstDirSector vector */
        lea     NextDirSector(pc),a1
        move.l  a1,NEXT_DIR_SEC(a0)     /* set NextDirSector vector */
        lea     FindDirEntry(pc),a1
        move.l  a1,FIND_DIR_ENTRY(a0)   /* set FindDirEntry vector */
        lea     NextDirEntry(pc),a1
        move.l  a1,NEXT_DIR_ENTRY(a0)   /* set NextDirEntry vector */
        lea     LoadFile(pc),a1
        move.l  a1,LOAD_FILE(a0)        /* set LoadFile vector */

        andi.b  #0xE2,0x8003.w          /* Priority Mode = off, 2M mode */
        move.b  #'I,0x800F.w            /* send MD init handshake to MD */
        rts

| Sub-CPU Program Main Entry Point (VBlank now enabled)

SPMain:
        lea     GlobalVars(pc),a6
        lea     iso_pvd_magic(pc),a5
        bsr     InitCD

| wait for boot handshake from main
0:
        cmpi.b  #'B,0x800E.w
        bne.b   0b
        move.b  #0,0x800F.w             /* clear sub comm port */

| read boot file from cd
        lea     root_dirname(pc),a0
        bsr     SetCWD                  /* set current working directory to root */
        lea     boot_name(pc),a0
        lea     LOAD_BUFFER.l,a1
        bsr     LoadFile
        bne.b   exit                    /* no Sub-CPU boot file */
1:
        lea     LOAD_BUFFER.l,a0        /* char *start(void) */
        jsr     (a0)

| app exited - if we return, the BIOS will call SPMain again
|
| my own idea - return NULL if done, else return a pointer to the filename
| to load and run, thus allowing apps to launch other apps

        move.w  #0x2700,sr              /* disallow interrupts */
        lea     GlobalVars(pc),a6
        clr.l   VBLANK_HANDLER(a6)
        clr.l   VBLANK_PARAM(a6)
        move.w  #0x2000,sr              /* allow interrupts */

        tst.l   d0
        beq     exit
        move.l  d0,-(sp)

        andi.b  #0xE2,0x8003.w          /* Priority Mode = off, 2M mode */
        move.b  #'I,0x800F.w            /* send init - switch Word RAM to Sub-CPU */
2:
        cmpi.b  #'I,0x800E.w
        bne.b   2b                      /* wait for command ACK */
        move.b  #0,0x800F.w             /* ACK handshake */

        lea     iso_pvd_magic(pc),a5
        bsr     InitCD
        lea     root_dirname(pc),a0
        bsr     SetCWD                  /* set current working directory to root */
        movea.l (sp)+,a0
        lea     LOAD_BUFFER.l,a1
        bsr     LoadFile
        beq.b   1b                      /* load okay */
exit:
        rts

| Sub-CPU Program VBlank (INT02) Service Handler

SPInt2:
        lea     GlobalVars(pc),a0
        tst.l   VBLANK_HANDLER(a0)
        bne.b   0f
        rts
0:
        move.l  VBLANK_PARAM(a0),d0
        move.l  d0,-(sp)
        movea.l VBLANK_HANDLER(a0),a0
        jsr     (a0)
        addq.l  #4,sp
        rts

| Sub-CPU program Reserved Function - we use it get the loader global vars pointer

SPNull:
        lea     GlobalVars(pc),a0
        move.l  a0,d0
        rts
It's a couple more headers and the main CD function entries: SPInit - called first before the vblank int is enabled; this should initialize the CD state, like the word ram mode and the like. SPMain - the main CD entry point; this should use the BIOS calls to load the game itself. SPInt2 - this is the main vblank int call; when the MD side asserts INT2 inside the MD vblank int code, the CD BIOS calls this function on the CD side. Note that while the MD side COULD assert INT2 any time it feels like, the BIOS assumes that it's only asserted once per vblank. If you don't use the CD BIOS after the game is loaded, you could use this int for other purposes, but that's beyond most examples or games. SPNull - this is a reserved call for the BIOS... I use it in my code to return a global variable table so that I can call pieces of the loader code from the game without worrying about where they are in memory.

After that code, you can have more code used for loading, but it should still be fairly small. I include a routine to call the BIOS to read the CD, and some really low-level ISO9660 helper functions.

Here's an arc of my current cdboot code: http://www.mediafire.com/file/bh3845u25 ... 31.7z/file
That has everything to make a SCD boot sector for any region.

Here's an arc of my MOD player for the SCD: http://www.mediafire.com/file/a46r5bmox ... ds.7z/file
That includes an older version of cdboot, my library for using the SCD PCM chip, and the mod player code. It's also got an ISO of the resulting player with some mods.

Here's the raycasting demo: http://www.mediafire.com/file/tg1hp1wax ... 09.7z/file

Here's a recent set of files to setup my toolchain for MD/CD/32X: http://www.mediafire.com/file/4wt3dym1g ... 7.zip/file

This should give you everything you need to get started, but looking over other code examples is always recommended. If you have questions, just ask. I'm always willing to answer questions if I know the answer, and this gets a bit complicated compared to plain cart games. CD32X added one more layer on top of it all - you load like a normal CD game, look for the 32X, and put a special boot record into the 32X frame buffer to start it up for CD32X games.

And here's an arc of my CD32X MOD player: http://www.mediafire.com/file/dn069tm15 ... 25.7z/file
Not sure if you plan on looking at CD32X, but I might as well stick it here as one more link for anyone looking for my CD/CD32X code.
8)

Good luck! :D

RavenWorks
Newbie
Posts: 3
Joined: Sun Mar 17, 2019 12:39 am

Re: Compiling a bootable ISO?

Post by RavenWorks » Sun Mar 24, 2019 2:33 pm

Oh, shoot, so it's actually a little of both then? The BIOS automatically loads some code for both CPUs, but it's limited to a small enough amount that the real bulk of the *actual* program needs to then be loaded BY that initial program? (What is the actual limitation that determines how much can be in these bootstrap programs? I'm making a fairly small demo, I wonder if I can get away with fitting it all in that initial load...)

Thank you for the detailed examples! I'll try and get the toolchain running on my machine, hopefully this'll get me started on the right foot :) Much appreciated!

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Re: Compiling a bootable ISO?

Post by Chilly Willy » Sun Mar 24, 2019 5:43 pm

RavenWorks wrote:
Sun Mar 24, 2019 2:33 pm
Oh, shoot, so it's actually a little of both then? The BIOS automatically loads some code for both CPUs, but it's limited to a small enough amount that the real bulk of the *actual* program needs to then be loaded BY that initial program?
Yes, exactly.
What is the actual limitation that determines how much can be in these bootstrap programs? I'm making a fairly small demo, I wonder if I can get away with fitting it all in that initial load...
The MD side is limited to a max of $1000 minus the size of the headers and the binary security blob. The CD side is limited to $7000 bytes total for the headers and the loader code and functions. Remember, this is the boot sector of an ISO9660 CD, so the limit is 16 sectors, or 32KB ($8000 = $1000 + $7000).
Thank you for the detailed examples! I'll try and get the toolchain running on my machine, hopefully this'll get me started on the right foot :) Much appreciated!
You're welcome. Note that I do all my deving on Xubuntu, but any linux distro would work, and I know others who have gotten it to work with MinGW or Cygwin.

Post Reply