How do Mega CD games start up?

Discussion in 'Technical Discussion' started by Andlabs, Jan 5, 2013.

  1. Andlabs

    Andlabs

    「いっきまーす」 Wiki Sysop
    2,175
    0
    0
    Writing my own MD/Genesis sound driver :D
    I am trying to split a binary that, for example, begins with
    Code (Text):
    1. 0000000: 00ff ffff ffff ffff ffff ff00 0002 0001  ................
    2. 0000010: 5345 4741 4449 5343 5359 5354 454d 2020  SEGADISCSYSTEM  
    3. 0000020: 5748 4c4f 4646 4f52 544e 4500 0100 0001  WHLOFFORTNE.....
    4. 0000030: 5345 4741 4f53 2020 2020 2000 0001 0000  SEGAOS     .....
    5. 0000040: 0000 0800 0000 0d00 0000 0000 0000 0000  ................
    6. 0000050: 0000 1000 0000 7000 0000 0000 0000 0000  ......p.........
    7. 0000060: 3132 3035 3139 3934 2020 2020 2020 2020  12051994        
    8. 0000070: 2020 2020 2020 2020 2020 2020 2020 2020                  
    9. 0000080: 2020 2020 2020 2020 2020 2020 2020 2020                  
    10. 0000090: 2020 2020 2020 2020 2020 2020 2020 2020                  
    11. 00000a0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    12. 00000b0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    13. 00000c0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    14. 00000d0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    15. 00000e0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    16. 00000f0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    17. 0000100: 2020 2020 2020 2020 2020 2020 2020 2020                  
    18. 0000110: 5345 4741 2047 454e 4553 4953 2020 2020  SEGA GENESIS    
    19. 0000120: 2843 2954 2d39 3320 3139 3934 2e44 4543  (C)T-93 1994.DEC
    20. 0000130: 5748 4545 4c20 4f46 2046 4f52 5455 4e45  WHEEL OF FORTUNE
    21. 0000140: 2020 2020 2020 2020 2020 2020 2020 2020                  
    22. 0000150: 2020 2020 2020 2020 2020 2020 2020 2020                  
    23. 0000160: 5748 4545 4c20 4f46 2046 4f52 5455 4e45  WHEEL OF FORTUNE
    24. 0000170: 2020 2020 2020 2020 2020 2020 2020 2020                  
    25. 0000180: 2020 2020 2020 2020 2020 2020 2020 2020                  
    26. 0000190: 474d 2054 2d39 3330 3835 2d30 3020 2020  GM T-93085-00  
    27. 00001a0: 4a34 2020 2020 2020 2020 2020 2020 2020  J4              
    28. 00001b0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    29. 00001c0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    30. 00001d0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    31. 00001e0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    32. 00001f0: 2020 2020 2020 2020 2020 2020 2020 2020                  
    33. 0000200: 5520 2020 2020 2020 2020 2020 2020 2020  U              
    34. 0000210: 43fa 000a 4eb8 0364 6000 057a 600f 0000  C...N..d'..z'...
    35. 0000220: 0000 0c22 0e44 0e66 0e88 0eee 0aaa 0888  ...".D.f........
    36. 0000230: 0444 08ae 046a 000e 0008 0004 0e20 803f  .D...j....... .?
    (redump.org dump of Wheel of Fortune (US)). I have tried taking the data from $200 (or $210 in this case) and from the presumed IP/SP start address ($40/$50 in this case), but the IP just stops at an invalid opcode $08ED6B9B0414 at $5FE (which appears to be an invalid bset opcode) and the SP starts with a bunch of 0 bytes (the header indicates both should boot from relative offset $0). I have tried following documentation here but to no avail, and the Mega CD BIOS code is too confusing and I can't figure out where it even loads the data. What is the proper way to get the IP/SP out? Thanks.
     
  2. Andlabs

    Andlabs

    「いっきまーす」 Wiki Sysop
    2,175
    0
    0
    Writing my own MD/Genesis sound driver :D
    Well that was fast
    Code (Text):
    1. [17:04] <GerbilSoft> are you using a bin/cue
    2. [17:04] <GerbilSoft> http://www.powerdatarecovery.com/cd-dvd-resources/the-physical-format-of-cd-row-sector.html
    3. [17:04] <andlabs> redump.org dump
    4. [17:04] <GerbilSoft> if so, that's the sync and sector header data
    5. [17:04] <GerbilSoft> 16 bytes before the actual sector data
    6. [17:04] <andlabs> which seem to be individual track rips
    7. [17:04] <andlabs> ok
    8. [17:04] <andlabs> http://forums.sonicretro.org/index.php?showtopic=30588
    9. [17:04] <-- K2Jbook has left this channel.
    10. [17:05] <GerbilSoft> yeah, looks like you're attempting to disassemble CD-ROM stuff that isn't program code
    11. [17:05] <GerbilSoft> I recommend converting the track to 2048-byte "ISO" instead of disassembling 2352-byte raw
    12. [17:05] <andlabs> ..
    13. [17:05] <andlabs> so I ran up against non-sector data?
    14. [17:05] <GerbilSoft> non-user data
    15. [17:06] <andlabs> :|
    16. [17:06] <andlabs> is there a program that will convert the data to proper form or should I just do it myself
    17. [17:06] <GerbilSoft> probably easier to do it yourself
    18. [17:06] <GerbilSoft> tl;dr
    19. [17:06] <andlabs> ok
    20. [17:06] <GerbilSoft> read 2352-byte chunks
    21. [17:06] <andlabs> yeah
    22. [17:06] <GerbilSoft> trim off the first 16 bytes, write 2048
    23. [17:06] <andlabs> or just do this
    24. [17:06] <andlabs> oh
    25. [17:06] <andlabs> and I do this for each 2352 chunk?
    26. [17:06] <GerbilSoft> yes
    It now works right; I'll post my conversion program in a bit. Thanks anyway.

    EDIT here is the program in Go; there are implementations in other languages if you search around (for instance, for 2352to2048) Consider this under the MIT license.
    Code (Text):
    1. // 5 january 2013
    2. package main
    3.  
    4. // TODO better name?
    5.  
    6. import (
    7.     "fmt"
    8.     "os"
    9.     "io"
    10. )
    11.  
    12. const inchunksize = 2352        // in bytes
    13. const outchunksize = 2048   // in bytes
    14. const outoffset = 16            // bytes to look past in the start of the chunk
    15.                         // therefore chunk[outoffset:outoffset + outchunksize]
    16.  
    17. func main() {
    18.     if len(os.Args) != 3 {
    19.         fmt.Fprintf(os.Stderr, "usage: %s infile outfile\n", os.Args[0])
    20.         os.Exit(1)
    21.     }
    22.     inname := os.Args[1]
    23.     outname := os.Args[2]
    24.  
    25.     fin, err := os.Open(inname)
    26.     if err != nil {
    27.         fmt.Fprintf(os.Stderr, "%s: open failed: %v\n", inname, err)
    28.         os.Exit(1)
    29.     }
    30.     defer fin.Close()
    31.  
    32.     fout, err := os.Create(outname)
    33.     if err != nil {
    34.         fmt.Fprintf(os.Stderr, "%s: create failed: %v\n", outname, err)
    35.         os.Exit(1)
    36.     }
    37.     defer fout.Close()
    38.  
    39.     sector := make([]byte, inchunksize)
    40.  
    41.     // TODO does not handle files which do not end on an even sector size
    42.     for {
    43.         _, err = io.ReadFull(fin, sector)
    44.         if err == io.EOF {
    45.             break
    46.         } else if err != nil {
    47.             fmt.Fprintf(os.Stderr, "%s: read failed: %v\n", inname, err)
    48.             os.Exit(1)
    49.         }
    50.         n, err := fout.Write(sector[outoffset:outoffset + outchunksize])
    51.         if err != nil {
    52.             fmt.Fprintf(os.Stderr, "%s: write failed: %v\n", outname, err)
    53.             os.Exit(1)
    54.         } else if n < outchunksize {
    55.             fmt.Fprintf(os.Stderr, "%s: short write without error (wanted to write %d, actually wrote %d)\n", outname, outchunksize, n)
    56.             os.Exit(1)
    57.         }
    58.     }
    59. }
    I should put this up on GitHub but later.
     
  3. Sappharad

    Sappharad

    Oldbie
    1,384
    65
    28
    There is actually a program that can do this for you, and much more. I used a freeware tool called "CD Image Converter" (cic.exe) back during the Dreamcast days. It could convert between 2048, 2532, 2336 track formats, and would generate the proper EDC and ECC data if you converted from 2048 to one of the larger formats. It was the only tool I found that could generate the EDC/ECC data, which was very useful for building selfboot images that you could mount in a virtual drive and use without burning them. (If you used and of the available tools for building selfboot CDI's at the time, they would always fill the ECC and EDC sections with 00's, so the actual image wouldn't work in a Virtual Drive and you had to burn it and re-rip just for it to be usable in a virtual drive.)

    Not sure if that information is helpful at all since you already have a solution to your problem, but it was something I found helpful years ago. My use wasn't specifically Sega CD like you're doing here, but using that tool in combination with some others was the only way I could build full 1GB GD-ROM images in CDI format years ago. (Before I managed to find a place to buy 99-minute CD-Rs)
     
  4. Meat Miracle

    Meat Miracle

    Researcher
    1,654
    4
    18
    I still convert 2352 to 2048 with CDMage, that program is unbeatable. If only it could do batch functions over multiple images, what carnage I could do with it that way.

    I don't know about Mega CD but for Saturn, the header is followed by an initial boot program that has both the region protection and the "how to boot" the game code. From there it jumps to, well, wherever the code specifies, usually to a IP.BIN or 0 file on the disc. I imagine Mega CD games did the same.
     
  5. Andlabs

    Andlabs

    「いっきまーす」 Wiki Sysop
    2,175
    0
    0
    Writing my own MD/Genesis sound driver :D
    Yes, Mega CD games do the same. For the Mega CD, the region protection is actuallly a blob of code at the beginning of the IP... the code responsible for drawing the PRODDUCED BY OR UNDER LICENSE screen, apparently :/ (I could be wrong though)
     
  6. Meat Miracle

    Meat Miracle

    Researcher
    1,654
    4
    18
    Hah, so it seems they are identical.

    The produced by... text is by the Bios as far as I know.
     
  7. Chilly Willy

    Chilly Willy

    Tech Member
    746
    0
    0
    Doom 32X
    The data track of a SCD game uses plain 2048 byte sectors. If the image is not in that format, you need to get it into that format, even if all you do is write the image to a CDR and then dump the CDR using 2048 byte sectors.

    Standard ISO9660 is used (although it doesn't NEED to be), and as you know, the first 16 sectors aren't used. The SCD uses these first 16 sectors for a boot program - it has a "standard" header followed by the region lock binary blob, which is just code that shows the "licensed by" message. It's like this

    Code (Text):
    1. | SEGA CD Loader code
    2. | by Chilly Willy
    3. | First part of cdrom header
    4. |
    5. | Main-CPU IP, comprising sectors 0 and 1. Sector 0 is loaded automatically,
    6. | while sector 1 is from the header.
    7.  
    8.         .text
    9.  
    10. | Standard MegaCD Volume Header at 0x0
    11.  
    12.         .ascii  "SEGADISCSYSTEM  "
    13.         .asciz  "CDBOOTLOADR"
    14.         .word   0x0100
    15.         .word   0x0001
    16.         .asciz  "SEGACD BOOT"
    17.         .word   0x0001
    18.         .word   0x0000
    19.         .long   0x00000800              /* main 68000 initial program cd offset */
    20.         .long   0x00000800              /* main 68000 initial program cd size */
    21.         .long   0x00000000              /* main 68000 initial program entry offset */
    22.         .long   0x00000000              /* main 68000 initial program Work RAM size */
    23.         .long   0x00001000              /* sub 68000 initial program cd offset */
    24.         .long   0x00007000              /* sub 68000 initial program cd size */
    25.         .long   0x00000000              /* sub 68000 initial program entry offset */
    26.         .long   0x00000000              /* sub 68000 initial program Work RAM size */
    27.         .ascii  "07292012        "      /* date - MMDDYYYY */
    28.         .space  160, 0x20
    29.  
    30. | Standard MegaDrive ROM header at 0x100
    31.  
    32.         .ascii  "SEGA CD Loader  "
    33.         .ascii  "(C)2012         "
    34.         .ascii  "CD Boot Loader                                  "
    35.         .ascii  "CD Boot Loader                                  "
    36.         .ascii  "GM MK-0000 -00  "
    37.         .ascii  "J6              "      /* controller info */
    38.         .ascii  "                            "
    39.         .ascii  "            "          /* modem info */
    40.         .ascii  "                                        "
    41.         .ascii  "JUE             "      /* region info */
    42.  
    43.  
    44. | Standard MegaCD startup code at 0x200
    45. |
    46. | This data and the code following is copied to the Main-CPU Work RAM at
    47. | 0xFF0000 and run.
    48.  
    49.         .global _start
    50. _start:
    51.         .ifdef  REGION_US
    52.         .incbin "ipl/us.bin", 0, 0x584
    53.         .endif
    54.         .ifdef  REGION_EU
    55.         .incbin "ipl/eu.bin", 0, 0x56E
    56.         .endif
    57.         .ifdef  REGION_JP
    58.         .incbin "ipl/jp.bin", 0, 0x156
    59.         .endif
    60.  
    61. | fall into startup code for Main-CPU
    62.  
    At that point, the code is all up to the programmer - he can do literally ANYTHING as long as he loads the game code and runs it. In the case of my cdboot loader, I look through the root directory for a particular file, load it to 0x8000 and run it. That part of the loader looks like this


    Code (Text):
    1. | fall into startup code for Main-CPU
    2.  
    3.         lea     0xA10000.l,a5
    4.         move.b  #0,0x200E(a5)           /* clear main comm port */
    5.  
    6. | wait for MD init handshake from CD
    7. 0:
    8.         cmpi.b  #'I,0x200F(a5)
    9.         bne.b   0b
    10.         bset    #1,0x2003(a5)           /* give Sub-CPU Word RAM */
    11.  
    12.         move.b  #'B,0x200E(a5)          /* main comm port - do boot */
    13.  
    14. | wait for MD code handshake from CD
    15. 1:
    16.         cmpi.b  #'M,0x200F(a5)
    17.         bne.b   1b
    18. 2:
    19.         btst    #0,0x2003(a5)           /* Main-CPU has Word RAM? */
    20.         beq.b   2b
    21.  
    22.         move.w  #0x2700,sr
    23.         lea     0xFFFD00,a0
    24.         movea.l a0,sp
    25.  
    26.         jmp     0x200000.l
    27.  
    28.  
    29.         .org    0x1000
    30.  
    31.  
    32. | Second part of cdrom header
    33. |
    34. | Sub-CPU IP, comprising sectors 2 to 15 from the header.
    35.  
    36. | Global Variable offsets
    37.         .equ    VBLANK_HANDLER, 0
    38.         .equ    VBLANK_PARAM,   4
    39.         .equ    INIT_CD,        8
    40.         .equ    READ_CD,        12
    41.         .equ    SET_CWD,        16
    42.         .equ    FIRST_DIR_SEC,  20
    43.         .equ    NEXT_DIR_SEC,   24
    44.         .equ    FIND_DIR_ENTRY, 28
    45.         .equ    NEXT_DIR_ENTRY, 32
    46.         .equ    LOAD_FILE,      36
    47.         .equ    DISC_TYPE,      40
    48.         .equ    DIR_ENTRY,      42
    49.         .equ    CWD_OFFSET,     44
    50.         .equ    CWD_LENGTH,     48
    51.         .equ    CURR_OFFSET,    52
    52.         .equ    CURR_LENGTH,    56
    53.         .equ    ROOT_OFFSET,    60
    54.         .equ    ROOT_LENGTH,    64
    55.         .equ    DENTRY_OFFSET,  68
    56.         .equ    DENTRY_LENGTH,  72
    57.         .equ    DENTRY_FLAGS,   76
    58.         .equ    DENTRY_NAME,    78
    59.         .equ    TEMP_NAME,      78+256
    60.         .equ    SIZE_GLOBALVARS,78+256+256
    61.  
    62. | Disc Read Buffer
    63.         .equ    DISC_BUFFER, 0x6800
    64.  
    65. | Program Load Buffer
    66.         .equ    LOAD_BUFFER, 0x8000
    67.  
    68. | ISO directory offsets (big-endian where applicable)
    69.         .equ    RECORD_LENGTH,  0
    70.         .equ    EXTENT,         6
    71.         .equ    FILE_LENGTH,    14
    72.         .equ    FILE_FLAGS,     25
    73.         .equ    FILE_NAME_LEN,  32
    74.         .equ    FILE_NAME,      33
    75.  
    76. | Primary Volume Descriptor offset
    77.         .equ    PVD_ROOT, 0x9C
    78.  
    79. | CDFS Error codes
    80.         .equ    ERR_READ_FAILED,    -2
    81.         .equ    ERR_NO_PVD,         -3
    82.         .equ    ERR_NO_MORE_ENTRIES,-4
    83.         .equ    ERR_BAD_ENTRY,      -5
    84.         .equ    ERR_NAME_NOT_FOUND, -6
    85.         .equ    ERR_NO_DISC,        -7
    86.  
    87. | Standard MegaCD Sub-CPU Program Header at 0x1000 (loaded to 0x6000)
    88.  
    89. SPHeader:
    90.         .asciz  "MAIN-SUBCPU"
    91.         .word   0x0001,0x0000
    92.         .long   0x00000000
    93.         .long   0x00000000
    94.         .long   SPHeaderOffsets-SPHeader
    95.         .long   0x00000000
    96.  
    97. SPHeaderOffsets:
    98.         .word   SPInit-SPHeaderOffsets
    99.         .word   SPMain-SPHeaderOffsets
    100.         .word   SPInt2-SPHeaderOffsets
    101.         .word   SPNull-SPHeaderOffsets
    102.         .word   0x0000
    103.  
    104. | Sub-CPU Program Initialization (VBlank not enabled yet)
    105.  
    106. SPInit:
    107.         lea     GlobalVars(pc),a0
    108.         move.l  #0,VBLANK_HANDLER(a0)   /* clear VBlank handler vector */
    109.         lea     InitCD(pc),a1
    110.         move.l  a1,INIT_CD(a0)          /* set InitCD vector */
    111.         lea     ReadCD(pc),a1
    112.         move.l  a1,READ_CD(a0)          /* set ReadCD vector */
    113.         lea     SetCWD(pc),a1
    114.         move.l  a1,SET_CWD(a0)          /* set SetCWD vector */
    115.         lea     FirstDirSector(pc),a1
    116.         move.l  a1,FIRST_DIR_SEC(a0)    /* set FirstDirSector vector */
    117.         lea     NextDirSector(pc),a1
    118.         move.l  a1,NEXT_DIR_SEC(a0)     /* set NextDirSector vector */
    119.         lea     FindDirEntry(pc),a1
    120.         move.l  a1,FIND_DIR_ENTRY(a0)   /* set FindDirEntry vector */
    121.         lea     NextDirEntry(pc),a1
    122.         move.l  a1,NEXT_DIR_ENTRY(a0)   /* set NextDirEntry vector */
    123.         lea     LoadFile(pc),a1
    124.         move.l  a1,LOAD_FILE(a0)        /* set LoadFile vector */
    125.  
    126.         andi.b  #0xE2,0x8003.w          /* Priority Mode = off, 2M mode */
    127.         move.b  #'I,0x800F.w            /* send MD init handshake to MD */
    128.         rts
    129.  
    130. | Sub-CPU Program Main Entry Point (VBlank now enabled)
    131.  
    132. SPMain:
    133.         lea     GlobalVars(pc),a6
    134.         lea     iso_pvd_magic(pc),a5
    135.         bsr     InitCD
    136.  
    137. | wait for boot handshake from main
    138. 0:
    139.         cmpi.b  #'B,0x800E.w
    140.         bne.b   0b
    141.         move.b  #0,0x800F.w             /* clear sub comm port */
    142.  
    143. | read boot file from cd
    144.         lea     root_dirname(pc),a0
    145.         bsr     SetCWD                  /* set current working directory to root */
    146.         lea     boot_name(pc),a0
    147.         lea     LOAD_BUFFER.l,a1
    148.         bsr     LoadFile
    149.         bne.b   exit                    /* no Sub-CPU boot file */
    150. 1:
    151.         lea     LOAD_BUFFER.l,a0        /* char *start(void) */
    152.         jsr     (a0)
    153.  
    154. | app exited - if we return, the BIOS will call SPMain again
    155. |
    156. | my own idea - return NULL if done, else return a pointer to the filename
    157. | to load and run, thus allowing apps to launch other apps
    158.  
    159.         move.w  #0x2700,sr              /* disallow interrupts */
    160.         lea     GlobalVars(pc),a6
    161.         clr.l   VBLANK_HANDLER(a6)
    162.         clr.l   VBLANK_PARAM(a6)
    163.         move.w  #0x2000,sr              /* allow interrupts */
    164.  
    165.         tst.l   d0
    166.         beq     exit
    167.         move.l  d0,-(sp)
    168.  
    169.         andi.b  #0xE2,0x8003.w          /* Priority Mode = off, 2M mode */
    170.         move.b  #'I,0x800F.w            /* send init - switch Word RAM to Sub-CPU */
    171. 2:
    172.         cmpi.b  #'I,0x800E.w
    173.         bne.b   2b                      /* wait for command ACK */
    174.         move.b  #0,0x800F.w             /* ACK handshake */
    175.  
    176.         lea     iso_pvd_magic(pc),a5
    177.         bsr     InitCD
    178.         lea     root_dirname(pc),a0
    179.         bsr     SetCWD                  /* set current working directory to root */
    180.         movea.l (sp)+,a0
    181.         lea     LOAD_BUFFER.l,a1
    182.         bsr     LoadFile
    183.         beq.b   1b                      /* load okay */
    184. exit:
    185.         rts
    186.  
    187. | Sub-CPU Program VBlank (INT02) Service Handler
    188.  
    189. SPInt2:
    190.         lea     GlobalVars(pc),a0
    191.         tst.l   VBLANK_HANDLER(a0)
    192.         bne.b   0f
    193.         rts
    194. 0:
    195.         move.l  VBLANK_PARAM(a0),d0
    196.         move.l  d0,-(sp)
    197.         movea.l VBLANK_HANDLER(a0),a0
    198.         jsr     (a0)
    199.         addq.l  #4,sp
    200.         rts
    201.  
    202. | Sub-CPU program Reserved Function - we use it get the loader global vars pointer
    203.  
    204. SPNull:
    205.         lea     GlobalVars(pc),a0
    206.         move.l  a0,d0
    207.         rts
    208.  
    209. |----------------------------------------------------------------------|
    210. |                      File System Support Code                        |
    211. |----------------------------------------------------------------------|
    212.  
    213. | Initialize CD - pass PVD Magic to look for in a5 and GlobalVars in a6
    214.  
    215. InitCD:
    216.         lea     drive_init_parms(pc),a0
    217.         move.w  #0x0010,d0              /* DRVINIT */
    218.         jsr     0x5F22.w                /* call CDBIOS function */
    219.  
    220.         move.w  #0,d1                   /* Mode 1 (CD-ROM with full error correction) */
    221.         move.w  #0x0096,d0              /* CDCSETMODE */
    222.         jsr     0x5F22.w                /* call CDBIOS function */
    223.  
    224.         moveq   #-1,d0
    225.         move.l  d0,ROOT_OFFSET(a6)
    226.         move.l  d0,ROOT_LENGTH(a6)
    227.         move.l  d0,CURR_OFFSET(a6)
    228.         move.l  d0,CURR_LENGTH(a6)
    229.         move.w  d0,DISC_TYPE(a6)        /* no disc/not recognized */
    230.  
    231. | find Primary Volume Descriptor
    232.  
    233.         moveq   #16,d2                  /* starting sector when searching for PVD */
    234. 0:
    235.         lea     DISC_BUFFER.w,a0        /* buffer */
    236.         move.l  d2,d0                   /* sector */
    237.         moveq   #1,d1                   /* # sectors */
    238.         move.w  d2,-(sp)
    239.         bsr     ReadCD
    240.         move.w  (sp)+,d2
    241.         tst.l   d0
    242.         bmi.b   9f                      /* error */
    243.         lea     DISC_BUFFER.w,a0
    244.         movea.l a5,a1                   /* PVD magic */
    245.         cmpm.l  (a0)+,(a1)+
    246.         bne.b   1f                      /* next sector */
    247.         cmpm.l  (a0)+,(a1)+
    248.         bne.b   1f                      /* next sector */
    249.         /* found PVD */
    250.         move.l  DISC_BUFFER+PVD_ROOT+EXTENT.w,ROOT_OFFSET(a6)
    251.         move.l  DISC_BUFFER+PVD_ROOT+FILE_LENGTH.w,ROOT_LENGTH(a6)
    252.         move.w  #0,DISC_TYPE(a6)        /* found PVD */
    253.         moveq   #0,d0
    254.         rts
    255. 1:
    256.         addq.w  #1,d2
    257.         cmpi.w  #32,d2
    258.         bne.b   0b                      /* check next sector */
    259.  
    260. | No PVD found
    261.  
    262.         moveq   #ERR_NO_PVD,d0
    263. 9:
    264.         rts
    265.  
    266. | Set directory entry variables to next entry of directory in disc buffer
    267.  
    268. NextDirEntry:
    269.         lea     DISC_BUFFER.w,a0
    270.         move.w  DIR_ENTRY(a6),d2
    271.         cmpi.w  #2048,d2
    272.         blo.b   1f
    273.         moveq   #ERR_NO_MORE_ENTRIES,d0
    274.         rts
    275. 1:
    276.         tst.b   (a0,d2.w)               /* record length */
    277.         bne.b   2f
    278.         moveq   #ERR_NO_MORE_ENTRIES,d0
    279.         rts
    280. 2:
    281.         lea     (a0,d2.w),a1            /* entry */
    282.         moveq   #0,d0
    283.         move.b  (a1),d0                 /* record length */
    284.         add.w   d0,d2
    285.         move.w  d2,DIR_ENTRY(a6)        /* next entry */
    286.         cmpi.w  #2048,d2
    287.         bls.b   3f
    288.         moveq   #ERR_NO_MORE_ENTRIES,d0 /* entries should NEVER cross a sector boundary */
    289.         rts
    290. 3:
    291.         tst.b   FILE_NAME_LEN(a1)
    292.         bne.b   4f
    293.         moveq   #ERR_BAD_ENTRY,d0
    294.         rts
    295. 4:
    296.         move.b  FILE_NAME_LEN(a1),d0
    297.         subq.w  #1,d0
    298.         lea     FILE_NAME(a1),a2
    299.         lea     DENTRY_NAME(a6),a3
    300. 5:
    301.         move.b  (a2)+,(a3)+
    302.         dbeq    d0,5b
    303.         move.b  #0,(a3)                 /* make sure is null-terminated */
    304.  
    305.         lea     DENTRY_NAME(a6),a2
    306.         /* check for special case 0 */
    307.         cmpi.b  #0,(a2)
    308.         bne.b   9f
    309.         move.l  #0x2E000000,(a2)        /* "." */
    310.         bra.b   10f
    311. 9:
    312.         /* check for special case 1 */
    313.         cmpi.b  #1,(a2)
    314.         bne.b   10f
    315.         move.l  #0x2E2E0000,(a2)        /* ".." */
    316. 10:
    317.         cmpi.b  #0x3B,(a2)+             /* look for ";" */
    318.         beq.b   11f
    319.         tst.b   (a2)
    320.         bne     10b
    321.         bra.b   12f
    322. 11:
    323.         move.b  #0,-(a2)                /* apply Rockridge correction to name */
    324. 12:
    325.         move.l  EXTENT(a1),DENTRY_OFFSET(a6)
    326.         move.l  FILE_LENGTH(a1),DENTRY_LENGTH(a6)
    327.         move.b  FILE_FLAGS(a1),DENTRY_FLAGS(a6)
    328.  
    329.         moveq   #0,d0
    330.         rts
    331.  
    332. | Find entry in directory in the disc buffer using name in a0
    333.  
    334. FindDirEntry:
    335.         move.w  DIR_ENTRY(a6),d0
    336.         cmpi.w  #2048,d0
    337.         blo.b   1f
    338. 0:
    339.         moveq   #ERR_NAME_NOT_FOUND,d0
    340.         rts
    341. 1:
    342.         move.l  a0,-(sp)
    343.         bsr     NextDirEntry
    344.         movea.l (sp)+,a0
    345.         bmi.b   FindDirEntry
    346. | got an entry, check the name
    347.         lea     DENTRY_NAME(a6),a1
    348.         movea.l a0,a2
    349. 2:
    350.         cmpm.b  (a1)+,(a2)+
    351.         bne.b   FindDirEntry
    352.         tst.b   -1(a1)
    353.         bne.b   2b
    354.         /* dentry holds match */
    355.         moveq   #0,d0
    356.         rts
    357.  
    358. | Read first sector in CWD
    359.  
    360. FirstDirSector:
    361.         move.l  CWD_OFFSET(a6),d0
    362.         cmp.l   CURR_OFFSET(a6),d0
    363.         beq.b   0f                      /* already loaded, just reset length */
    364.         moveq   #1,d1
    365.         lea     DISC_BUFFER.w,a0        /* buffer */
    366.         bsr     ReadCD
    367.         bmi.b   1f
    368.         /* disc buffer holds first sector of dir */
    369.         move.l  CWD_OFFSET(a6),CURR_OFFSET(a6)
    370. 0:
    371.         clr.l   CURR_LENGTH(a6)
    372.         clr.w   DIR_ENTRY(a6)
    373.         moveq   #0,d0
    374. 1:
    375.         rts
    376.  
    377. | Read next sector in CWD
    378.  
    379. NextDirSector:
    380.         addq.l  #1,CURR_OFFSET(a6)
    381.         addi.l  #2048,CURR_LENGTH(a6)
    382.         move.l  CWD_LENGTH(a6),d0
    383.         cmp.l   CURR_LENGTH(a6),d0
    384.         bhi.b   0f
    385.         moveq   #ERR_NO_MORE_ENTRIES,d0
    386.         rts
    387. 0:
    388.         move.l  CURR_OFFSET(a6),d0
    389.         moveq   #1,d1
    390.         lea     DISC_BUFFER.w,a0        /* buffer */
    391.         bsr     ReadCD
    392.         bmi     1f
    393.         /* disc buffer holds next sector of dir */
    394.         clr.w   DIR_ENTRY(a6)
    395.         moveq   #0,d0
    396. 1:
    397.         rts
    398.  
    399. | Set current working directory using path at a0
    400.  
    401. SetCWD:
    402.         cmpi.b  #0x2F,(a0)              /* check for leading "/" */
    403.         bne.b   0f                      /* relative to cwd */
    404.         /* start at root dir */
    405.         addq.l  #1,a0                   /* skip over "/" */
    406.         move.l  ROOT_OFFSET(a6),CWD_OFFSET(a6)
    407.         move.l  ROOT_LENGTH(a6),CWD_LENGTH(a6)
    408. 0:
    409.         move.l  a0,-(sp)
    410.         bsr     FirstDirSector          /* disc buffer holds first sector of dir */
    411.         movea.l (sp)+,a0
    412.         bmi.b   2f
    413.         /* check if done */
    414.         tst.b   (a0)
    415.         bne.b   3f
    416. 1:
    417.         moveq   #0,d0
    418. 2:
    419.         rts
    420. 3:
    421.         addq.l  #1,a0                   /* skip over "/" */
    422.         tst.b   (a0)
    423.         beq.b   1b                      /* was trailing "/" */
    424.  
    425.         /* copy next part of path to temp */
    426.         lea     TEMP_NAME(a6),a1
    427. 4:
    428.         move.b  (a0)+,(a1)+
    429.         beq.b   5f
    430.         cmpi.b  #0x2F,-1(a0)            /* check for "/" */
    431.         bne.b   4b
    432. 5:
    433.         clr.b   -1(a1)                  /* null terminate string in temp */
    434.         subq.l  #1,a0
    435. 6:
    436.         /* check current directory sector for entry */
    437.         move.l  a0,-(sp)
    438.         lea     TEMP_NAME(a6),a0
    439.         bsr     FindDirEntry
    440.         movea.l (sp)+,a0
    441.         bmi.b   7f
    442.         /* found this part of path */
    443.         move.l  DENTRY_OFFSET(a6),CWD_OFFSET(a6)
    444.         move.l  DENTRY_LENGTH(a6),CWD_LENGTH(a6)
    445.         bra.b   0b                      /* read first sector of dir and check if done */
    446. 7:
    447.         /* not found, try next sector */
    448.         move.l  a0,-(sp)
    449.         bsr     NextDirSector
    450.         movea.l (sp)+,a0
    451.         beq.b   6b
    452.         moveq   #ERR_NAME_NOT_FOUND,d0
    453.         rts
    454.  
    455. | Load file in CWD with name at a0 to memory at a1
    456.  
    457. LoadFile:
    458.         movem.l a0-a1,-(sp)
    459.         bsr     FirstDirSector
    460.         movem.l (sp)+,a0-a1
    461. 0:
    462.         /* check current directory sector for entry */
    463.         movem.l a0-a1,-(sp)
    464.         bsr     FindDirEntry
    465.         movem.l (sp)+,a0-a1
    466.         bmi.b   1f
    467.  
    468.         /* found file */
    469.         move.l  DENTRY_OFFSET(a6),d0
    470.         move.l  DENTRY_LENGTH(a6),d1
    471.         addi.l  #2047,d1
    472.         moveq   #11,d2
    473.         lsr.l   d2,d1                   /* # sectors */
    474.         movea.l a1,a0
    475.         bra     ReadCD
    476. 1:
    477.         /* not found, try next sector */
    478.         movem.l a0-a1,-(sp)
    479.         bsr     NextDirSector
    480.         movem.l (sp)+,a0-a1
    481.         beq.b   0b
    482.         moveq   #ERR_NAME_NOT_FOUND,d0
    483.         rts
    484.  
    485. | Read d1 sectors starting at d0 into buffer in a0 (using Sub-CPU)
    486.  
    487. ReadCD:
    488.         movem.l d0-d1/a0-a1,-(sp)
    489. 0:
    490.         move.w  #0x0089,d0              /* CDCSTOP */
    491.         jsr     0x5F22.w                /* call CDBIOS function */
    492.  
    493.         movea.l sp,a0                   /* ptr to 32 bit sector start and 32 bit sector count */
    494.         move.w  #0x0020,d0              /* ROMREADN */
    495.         jsr     0x5F22.w                /* call CDBIOS function */
    496. 1:
    497.         move.w  #0x008A,d0              /* CDCSTAT */
    498.         jsr     0x5F22.w                /* call CDBIOS function */
    499.         bcs.b   1b                      /* no sectors in CD buffer */
    500.  
    501.         /* set CDC Mode destination device to Sub-CPU */
    502.         andi.w  #0xF8FF,0x8004.w
    503.         ori.w   #0x0300,0x8004.w
    504. 2:
    505.         move.w  #0x008B,d0              /* CDCREAD */
    506.         jsr     0x5F22.w                /* call CDBIOS function */
    507.         bcs.b   2b                      /* not ready to xfer data */
    508.  
    509.         movea.l 8(sp),a0                /* data buffer */
    510.         lea     12(sp),a1               /* header address */
    511.         move.w  #0x008C,d0              /* CDCTRN */
    512.         jsr     0x5F22.w                /* call CDBIOS function */
    513.         bcs.b   0b                      /* failed, retry */
    514.  
    515.         move.w  #0x008D,d0              /* CDCACK */
    516.         jsr     0x5F22.w                /* call CDBIOS function */
    517.  
    518.         addq.l  #1,(sp)                 /* next sector */
    519.         addi.l  #2048,8(sp)             /* inc buffer ptr */
    520.         subq.l  #1,4(sp)                /* dec sector count */
    521.         bne.b   1b
    522.  
    523.         lea     16(sp),sp               /* cleanup stack */
    524.         rts
    525.  
    526. |----------------------------------------------------------------------|
    527. |                          Global Variables                            |
    528. |----------------------------------------------------------------------|
    529.  
    530.         .align  2
    531. root_dirname:
    532.         .asciz  "/"
    533.  
    534.         .align  2
    535. boot_name:
    536.         .asciz  "APP.BIN"
    537.  
    538.         .align  2
    539. iso_pvd_magic:
    540.         .asciz  "\1CD001\1"
    541.  
    542.         .align  2
    543. drive_init_parms:
    544.         .byte   0x01, 0xFF              /* first track (1), last track (all) */
    545.  
    546.         .align  4
    547. GlobalVars:
    548.         .space  SIZE_GLOBALVARS
    549.  
    550.  
    551.         .org    0x8000
    552.  
    Notice how I pad out the loader to 0x8000 - that's 16 sectors. When I burn a CD, the binary from the above code is written to the first 16 sectors as a boot block (mkisofs allows for custom boot blocks). Then the actual SCD game code goes in the file to load - in the above case, it's "APP.BIN" but can be changed to anything I want. My boot loader goes above and beyond by providing the program a global structure with key routines in the loader available for call from the game so that things like reading ISO9660 files don't need to be duplicated in the game.

    And that's how SCD games start up - the BIOS loads the first couple sectors to get the header, loads the rest of the boot loader, checks the security blob, then runs the code, which eventually falls into the user code after the security blob which then loads the game code somehow.