I am trying to split a binary that, for example, begins with Code (Text): 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.
Well that was fast Code (Text): [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. Code (Text): // 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.
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)
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.
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)
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): | 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 Code (Text): | 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.