Sonic and Sega Retro Message Board: How do Mega CD games start up? - Sonic and Sega Retro Message Board

Jump to content

Hey there, Guest!  (Log In · Register) Help
Page 1 of 1
    Locked
    Locked Forum

How do Mega CD games start up? at my wit's end trying to reverse-engineer games

#1 User is offline Andlabs 

Posted 05 January 2013 - 05:01 PM

  • 「いっきまーす」
  • Posts: 2175
  • Joined: 11-July 08
  • Gender:Male
  • Project:Writing my own MD/Genesis sound driver :D
  • Wiki edits:7,061
I am trying to split a binary that, for example, begins with
0000000: 00ff ffff ffff ffff ffff ff00 0002 0001  ................
0000010: 5345 4741 4449 5343 5359 5354 454d 2020  SEGADISCSYSTEM  
0000020: 5748 4c4f 4646 4f52 544e 4500 0100 0001  WHLOFFORTNE.....
0000030: 5345 4741 4f53 2020 2020 2000 0001 0000  SEGAOS     .....
0000040: 0000 0800 0000 0d00 0000 0000 0000 0000  ................
0000050: 0000 1000 0000 7000 0000 0000 0000 0000  ......p.........
0000060: 3132 3035 3139 3934 2020 2020 2020 2020  12051994        
0000070: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000080: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000090: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000a0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000b0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000c0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000d0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000e0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00000f0: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000100: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000110: 5345 4741 2047 454e 4553 4953 2020 2020  SEGA GENESIS    
0000120: 2843 2954 2d39 3320 3139 3934 2e44 4543  (C)T-93 1994.DEC
0000130: 5748 4545 4c20 4f46 2046 4f52 5455 4e45  WHEEL OF FORTUNE
0000140: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000150: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000160: 5748 4545 4c20 4f46 2046 4f52 5455 4e45  WHEEL OF FORTUNE
0000170: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000180: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000190: 474d 2054 2d39 3330 3835 2d30 3020 2020  GM T-93085-00   
00001a0: 4a34 2020 2020 2020 2020 2020 2020 2020  J4              
00001b0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00001c0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00001d0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00001e0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00001f0: 2020 2020 2020 2020 2020 2020 2020 2020                  
0000200: 5520 2020 2020 2020 2020 2020 2020 2020  U               
0000210: 43fa 000a 4eb8 0364 6000 057a 600f 0000  C...N..d'..z'...
0000220: 0000 0c22 0e44 0e66 0e88 0eee 0aaa 0888  ...".D.f........
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.
This post has been edited by Andlabs: 05 January 2013 - 05:03 PM

#2 User is offline Andlabs 

Posted 05 January 2013 - 05:23 PM

  • 「いっきまーす」
  • Posts: 2175
  • Joined: 11-July 08
  • Gender:Male
  • Project:Writing my own MD/Genesis sound driver :D
  • Wiki edits:7,061
Well that was fast
[17:04] <GerbilSoft> are you using a bin/cue
[17:04] <GerbilSoft> http://www.powerdatarecovery.com/cd-dvd-resources/the-physical-format-of-cd-row-sector.html
[17:04] <andlabs> redump.org dump
[17:04] <GerbilSoft> if so, that's the sync and sector header data
[17:04] <GerbilSoft> 16 bytes before the actual sector data
[17:04] <andlabs> which seem to be individual track rips
[17:04] <andlabs> ok
[17:04] <andlabs> http://forums.sonicretro.org/index.php?showtopic=30588
[17:04] <-- K2Jbook has left this channel.
[17:05] <GerbilSoft> yeah, looks like you're attempting to disassemble CD-ROM stuff that isn't program code
[17:05] <GerbilSoft> I recommend converting the track to 2048-byte "ISO" instead of disassembling 2352-byte raw
[17:05] <andlabs> ..
[17:05] <andlabs> so I ran up against non-sector data?
[17:05] <GerbilSoft> non-user data
[17:06] <andlabs> :|
[17:06] <andlabs> is there a program that will convert the data to proper form or should I just do it myself
[17:06] <GerbilSoft> probably easier to do it yourself
[17:06] <GerbilSoft> tl;dr
[17:06] <andlabs> ok
[17:06] <GerbilSoft> read 2352-byte chunks
[17:06] <andlabs> yeah
[17:06] <GerbilSoft> trim off the first 16 bytes, write 2048
[17:06] <andlabs> or just do this
[17:06] <andlabs> oh
[17:06] <andlabs> and I do this for each 2352 chunk?
[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.
// 5 january 2013
package main

// TODO better name?

import (
	"fmt"
	"os"
	"io"
)

const inchunksize = 2352		// in bytes
const outchunksize = 2048	// in bytes
const outoffset = 16			// bytes to look past in the start of the chunk
						// therefore chunk[outoffset:outoffset + outchunksize]

func main() {
	if len(os.Args) != 3 {
		fmt.Fprintf(os.Stderr, "usage: %s infile outfile\n", os.Args[0])
		os.Exit(1)
	}
	inname := os.Args[1]
	outname := os.Args[2]

	fin, err := os.Open(inname)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: open failed: %v\n", inname, err)
		os.Exit(1)
	}
	defer fin.Close()

	fout, err := os.Create(outname)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: create failed: %v\n", outname, err)
		os.Exit(1)
	}
	defer fout.Close()

	sector := make([]byte, inchunksize)

	// TODO does not handle files which do not end on an even sector size
	for {
		_, err = io.ReadFull(fin, sector)
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Fprintf(os.Stderr, "%s: read failed: %v\n", inname, err)
			os.Exit(1)
		}
		n, err := fout.Write(sector[outoffset:outoffset + outchunksize])
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: write failed: %v\n", outname, err)
			os.Exit(1)
		} else if n < outchunksize {
			fmt.Fprintf(os.Stderr, "%s: short write without error (wanted to write %d, actually wrote %d)\n", outname, outchunksize, n)
			os.Exit(1)
		}
	}
}
I should put this up on GitHub but later.
This post has been edited by Andlabs: 05 January 2013 - 05:30 PM

#3 User is offline sonicblur 

Posted 06 January 2013 - 06:19 PM

  • Posts: 848
  • Joined: 18-February 08
  • Gender:Male
  • Wiki edits:6
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 User is offline Meat Miracle 

Posted 13 January 2013 - 10:13 AM

  • Posts: 1568
  • Joined: 11-January 03
  • Gender:Male
  • Wiki edits:2
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 User is offline Andlabs 

Posted 13 January 2013 - 10:27 AM

  • 「いっきまーす」
  • Posts: 2175
  • Joined: 11-July 08
  • Gender:Male
  • Project:Writing my own MD/Genesis sound driver :D
  • Wiki edits:7,061
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)
This post has been edited by Andlabs: 13 January 2013 - 10:28 AM

#6 User is offline Meat Miracle 

Posted 13 January 2013 - 08:50 PM

  • Posts: 1568
  • Joined: 11-January 03
  • Gender:Male
  • Wiki edits:2
Hah, so it seems they are identical.

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

#7 User is offline Chilly Willy 

Posted 17 January 2013 - 06:24 PM

  • Posts: 746
  • Joined: 10-April 09
  • Gender:Male
  • Project: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

| 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  "07292012        "      /* date - MMDDYYYY */
        .space  160, 0x20

| Standard MegaDrive ROM header at 0x100

        .ascii  "SEGA CD Loader  "
        .ascii  "(C)2012         "
        .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



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


| 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:
        addq.l  #1,a0                   /* skip over "/" */
        tst.b   (a0)
        beq.b   1b                      /* was trailing "/" */

        /* 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



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.

Page 1 of 1
    Locked
    Locked Forum

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users