| 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


        .org    0x1000


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

| Global Variable offsets
        .equ    VBLANK_HANDLER, 0
        .equ    VBLANK_PARAM,   4
        .equ    INIT_CD,        8
        .equ    READ_CD,        12
        .equ    SET_CWD,        16
        .equ    FIRST_DIR_SEC,  20
        .equ    NEXT_DIR_SEC,   24
        .equ    FIND_DIR_ENTRY, 28
        .equ    NEXT_DIR_ENTRY, 32
        .equ    LOAD_FILE,      36
        .equ    DISC_TYPE,      40
        .equ    DIR_ENTRY,      42
        .equ    CWD_OFFSET,     44
        .equ    CWD_LENGTH,     48
        .equ    CURR_OFFSET,    52
        .equ    CURR_LENGTH,    56
        .equ    ROOT_OFFSET,    60
        .equ    ROOT_LENGTH,    64
        .equ    DENTRY_OFFSET,  68
        .equ    DENTRY_LENGTH,  72
        .equ    DENTRY_FLAGS,   76
        .equ    DENTRY_NAME,    78
        .equ    TEMP_NAME,      78+256
        .equ    SIZE_GLOBALVARS,78+256+256

| Disc Read Buffer
        .equ    DISC_BUFFER, 0x6800

| Program Load Buffer
        .equ    LOAD_BUFFER, 0x8000

| ISO directory offsets (big-endian where applicable)
        .equ    RECORD_LENGTH,  0
        .equ    EXTENT,         6
        .equ    FILE_LENGTH,    14
        .equ    FILE_FLAGS,     25
        .equ    FILE_NAME_LEN,  32
        .equ    FILE_NAME,      33

| Primary Volume Descriptor offset
        .equ    PVD_ROOT, 0x9C

| CDFS Error codes
        .equ    ERR_READ_FAILED,    -2
        .equ    ERR_NO_PVD,         -3
        .equ    ERR_NO_MORE_ENTRIES,-4
        .equ    ERR_BAD_ENTRY,      -5
        .equ    ERR_NAME_NOT_FOUND, -6
        .equ    ERR_NO_DISC,        -7

| 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

|----------------------------------------------------------------------|
|                      File System Support Code                        |
|----------------------------------------------------------------------|

| Initialize CD - pass PVD Magic to look for in a5 and GlobalVars in a6

InitCD:
        lea     drive_init_parms(pc),a0
        move.w  #0x0010,d0              /* DRVINIT */
        jsr     0x5F22.w                /* call CDBIOS function */

        move.w  #0,d1                   /* Mode 1 (CD-ROM with full error correction) */
        move.w  #0x0096,d0              /* CDCSETMODE */
        jsr     0x5F22.w                /* call CDBIOS function */

        moveq   #-1,d0
        move.l  d0,ROOT_OFFSET(a6)
        move.l  d0,ROOT_LENGTH(a6)
        move.l  d0,CURR_OFFSET(a6)
        move.l  d0,CURR_LENGTH(a6)
        move.w  d0,DISC_TYPE(a6)        /* no disc/not recognized */

| find Primary Volume Descriptor

        moveq   #16,d2                  /* starting sector when searching for PVD */
0:
        lea     DISC_BUFFER.w,a0        /* buffer */
        move.l  d2,d0                   /* sector */
        moveq   #1,d1                   /* # sectors */
        move.w  d2,-(sp)
        bsr     ReadCD
        move.w  (sp)+,d2
        tst.l   d0
        bmi.b   9f                      /* error */
        lea     DISC_BUFFER.w,a0
        movea.l a5,a1                   /* PVD magic */
        cmpm.l  (a0)+,(a1)+
        bne.b   1f                      /* next sector */
        cmpm.l  (a0)+,(a1)+
        bne.b   1f                      /* next sector */
        /* found PVD */
        move.l  DISC_BUFFER+PVD_ROOT+EXTENT.w,ROOT_OFFSET(a6)
        move.l  DISC_BUFFER+PVD_ROOT+FILE_LENGTH.w,ROOT_LENGTH(a6)
        move.w  #0,DISC_TYPE(a6)        /* found PVD */
        moveq   #0,d0
        rts
1:
        addq.w  #1,d2
        cmpi.w  #32,d2
        bne.b   0b                      /* check next sector */

| No PVD found

        moveq   #ERR_NO_PVD,d0
9:
        rts

| Set directory entry variables to next entry of directory in disc buffer

NextDirEntry:
        lea     DISC_BUFFER.w,a0
        move.w  DIR_ENTRY(a6),d2
        cmpi.w  #2048,d2
        blo.b   1f
        moveq   #ERR_NO_MORE_ENTRIES,d0
        rts
1:
        tst.b   (a0,d2.w)               /* record length */
        bne.b   2f
        moveq   #ERR_NO_MORE_ENTRIES,d0
        rts
2:
        lea     (a0,d2.w),a1            /* entry */
        moveq   #0,d0
        move.b  (a1),d0                 /* record length */
        add.w   d0,d2
        move.w  d2,DIR_ENTRY(a6)        /* next entry */
        cmpi.w  #2048,d2
        bls.b   3f
        moveq   #ERR_NO_MORE_ENTRIES,d0 /* entries should NEVER cross a sector boundary */
        rts
3:
        tst.b   FILE_NAME_LEN(a1)
        bne.b   4f
        moveq   #ERR_BAD_ENTRY,d0
        rts
4:
        move.b  FILE_NAME_LEN(a1),d0
        subq.w  #1,d0
        lea     FILE_NAME(a1),a2
        lea     DENTRY_NAME(a6),a3
5:
        move.b  (a2)+,(a3)+
        dbeq    d0,5b
        move.b  #0,(a3)                 /* make sure is null-terminated */

        lea     DENTRY_NAME(a6),a2
        /* check for special case 0 */
        cmpi.b  #0,(a2)
        bne.b   9f
        move.l  #0x2E000000,(a2)        /* "." */
        bra.b   10f
9:
        /* check for special case 1 */
        cmpi.b  #1,(a2)
        bne.b   10f
        move.l  #0x2E2E0000,(a2)        /* ".." */
10:
        cmpi.b  #0x3B,(a2)+             /* look for ";" */
        beq.b   11f
        tst.b   (a2)
        bne     10b
        bra.b   12f
11:
        move.b  #0,-(a2)                /* apply Rockridge correction to name */
12:
        move.l  EXTENT(a1),DENTRY_OFFSET(a6)
        move.l  FILE_LENGTH(a1),DENTRY_LENGTH(a6)
        move.b  FILE_FLAGS(a1),DENTRY_FLAGS(a6)

        moveq   #0,d0
        rts

| Find entry in directory in the disc buffer using name in a0

FindDirEntry:
        move.w  DIR_ENTRY(a6),d0
        cmpi.w  #2048,d0
        blo.b   1f
0:
        moveq   #ERR_NAME_NOT_FOUND,d0
        rts
1:
        move.l  a0,-(sp)
        bsr     NextDirEntry
        movea.l (sp)+,a0
        bmi.b   FindDirEntry
| got an entry, check the name
        lea     DENTRY_NAME(a6),a1
        movea.l a0,a2
2:
        cmpm.b  (a1)+,(a2)+
        bne.b   FindDirEntry
        tst.b   -1(a1)
        bne.b   2b
        /* dentry holds match */
        moveq   #0,d0
        rts

| Read first sector in CWD

FirstDirSector:
        move.l  CWD_OFFSET(a6),d0
        cmp.l   CURR_OFFSET(a6),d0
        beq.b   0f                      /* already loaded, just reset length */
        moveq   #1,d1
        lea     DISC_BUFFER.w,a0        /* buffer */
        bsr     ReadCD
        bmi.b   1f
        /* disc buffer holds first sector of dir */
        move.l  CWD_OFFSET(a6),CURR_OFFSET(a6)
0:
        clr.l   CURR_LENGTH(a6)
        clr.w   DIR_ENTRY(a6)
        moveq   #0,d0
1:
        rts

| Read next sector in CWD

NextDirSector:
        addq.l  #1,CURR_OFFSET(a6)
        addi.l  #2048,CURR_LENGTH(a6)
        move.l  CWD_LENGTH(a6),d0
        cmp.l   CURR_LENGTH(a6),d0
        bhi.b   0f
        moveq   #ERR_NO_MORE_ENTRIES,d0
        rts
0:
        move.l  CURR_OFFSET(a6),d0
        moveq   #1,d1
        lea     DISC_BUFFER.w,a0        /* buffer */
        bsr     ReadCD
        bmi     1f
        /* disc buffer holds next sector of dir */
        clr.w   DIR_ENTRY(a6)
        moveq   #0,d0
1:
        rts

| Set current working directory using path at a0

SetCWD:
        cmpi.b  #0x2F,(a0)              /* check for leading "/" */
        bne.b   0f                      /* relative to cwd */
        /* start at root dir */
        addq.l  #1,a0                   /* skip over "/" */
        move.l  ROOT_OFFSET(a6),CWD_OFFSET(a6)
        move.l  ROOT_LENGTH(a6),CWD_LENGTH(a6)
0:
        move.l  a0,-(sp)
        bsr     FirstDirSector          /* disc buffer holds first sector of dir */
        movea.l (sp)+,a0
        bmi.b   2f
        /* check if done */
        tst.b   (a0)
        bne.b   3f
1:
        moveq   #0,d0
2:
        rts
3:
        tst.b   (a0)
        beq.b   1b                      /* done */

        /* copy next part of path to temp */
        lea     TEMP_NAME(a6),a1
4:
        move.b  (a0)+,(a1)+
        beq.b   5f
        cmpi.b  #0x2F,-1(a0)            /* check for "/" */
        bne.b   4b
5:
        clr.b   -1(a1)                  /* null terminate string in temp */
        subq.l  #1,a0
6:
        /* check current directory sector for entry */
        move.l  a0,-(sp)
        lea     TEMP_NAME(a6),a0
        bsr     FindDirEntry
        movea.l (sp)+,a0
        bmi.b   7f
        /* found this part of path */
        move.l  DENTRY_OFFSET(a6),CWD_OFFSET(a6)
        move.l  DENTRY_LENGTH(a6),CWD_LENGTH(a6)
        bra.b   0b                      /* read first sector of dir and check if done */
7:
        /* not found, try next sector */
        move.l  a0,-(sp)
        bsr     NextDirSector
        movea.l (sp)+,a0
        beq.b   6b
        moveq   #ERR_NAME_NOT_FOUND,d0
        rts

| Load file in CWD with name at a0 to memory at a1

LoadFile:
        movem.l a0-a1,-(sp)
        bsr     FirstDirSector
        movem.l (sp)+,a0-a1
0:
        /* check current directory sector for entry */
        movem.l a0-a1,-(sp)
        bsr     FindDirEntry
        movem.l (sp)+,a0-a1
        bmi.b   1f

        /* found file */
        move.l  DENTRY_OFFSET(a6),d0
        move.l  DENTRY_LENGTH(a6),d1
        addi.l  #2047,d1
        moveq   #11,d2
        lsr.l   d2,d1                   /* # sectors */
        movea.l a1,a0
        bra     ReadCD
1:
        /* not found, try next sector */
        movem.l a0-a1,-(sp)
        bsr     NextDirSector
        movem.l (sp)+,a0-a1
        beq.b   0b
        moveq   #ERR_NAME_NOT_FOUND,d0
        rts

| Read d1 sectors starting at d0 into buffer in a0 (using Sub-CPU)

ReadCD:
        movem.l d0-d1/a0-a1,-(sp)
0:
        move.w  #0x0089,d0              /* CDCSTOP */
        jsr     0x5F22.w                /* call CDBIOS function */

        movea.l sp,a0                   /* ptr to 32 bit sector start and 32 bit sector count */
        move.w  #0x0020,d0              /* ROMREADN */
        jsr     0x5F22.w                /* call CDBIOS function */
1:
        move.w  #0x008A,d0              /* CDCSTAT */
        jsr     0x5F22.w                /* call CDBIOS function */
        bcs.b   1b                      /* no sectors in CD buffer */

        /* set CDC Mode destination device to Sub-CPU */
        andi.w  #0xF8FF,0x8004.w
        ori.w   #0x0300,0x8004.w
2:
        move.w  #0x008B,d0              /* CDCREAD */
        jsr     0x5F22.w                /* call CDBIOS function */
        bcs.b   2b                      /* not ready to xfer data */

        movea.l 8(sp),a0                /* data buffer */
        lea     12(sp),a1               /* header address */
        move.w  #0x008C,d0              /* CDCTRN */
        jsr     0x5F22.w                /* call CDBIOS function */
        bcs.b   0b                      /* failed, retry */

        move.w  #0x008D,d0              /* CDCACK */
        jsr     0x5F22.w                /* call CDBIOS function */

        addq.l  #1,(sp)                 /* next sector */
        addi.l  #2048,8(sp)             /* inc buffer ptr */
        subq.l  #1,4(sp)                /* dec sector count */
        bne.b   1b

        lea     16(sp),sp               /* cleanup stack */
        rts

|----------------------------------------------------------------------|
|                          Global Variables                            |
|----------------------------------------------------------------------|

        .align  2
root_dirname:
        .asciz  "/"

        .align  2
boot_name:
        .asciz  "APP.BIN"

        .align  2
iso_pvd_magic:
        .asciz  "\1CD001\1"

        .align  2
drive_init_parms:
        .byte   0x01, 0xFF              /* first track (1), last track (all) */

        .align  4
GlobalVars:
        .space  SIZE_GLOBALVARS


        .org    0x8000

