nkeynes@87: /** nkeynes@561: * $Id$ nkeynes@87: * nkeynes@87: * "Fake" BIOS functions, for operation without the actual BIOS. nkeynes@87: * nkeynes@1100: * Copyright (c) 2005-2010 Nathan Keynes. nkeynes@87: * nkeynes@87: * This program is free software; you can redistribute it and/or modify nkeynes@87: * it under the terms of the GNU General Public License as published by nkeynes@87: * the Free Software Foundation; either version 2 of the License, or nkeynes@87: * (at your option) any later version. nkeynes@87: * nkeynes@87: * This program is distributed in the hope that it will be useful, nkeynes@87: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@87: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@87: * GNU General Public License for more details. nkeynes@87: */ nkeynes@87: nkeynes@87: #include "dream.h" nkeynes@87: #include "mem.h" nkeynes@102: #include "syscall.h" nkeynes@1100: #include "asic.h" nkeynes@422: #include "dreamcast.h" nkeynes@1099: #include "bootstrap.h" nkeynes@564: #include "sh4/sh4.h" nkeynes@1099: #include "drivers/cdrom/cdrom.h" nkeynes@1107: #include "drivers/cdrom/isofs.h" nkeynes@1099: #include "gdrom/gdrom.h" nkeynes@87: nkeynes@1103: gboolean bios_boot_gdrom_disc( void ); nkeynes@1103: nkeynes@1100: /* Definitions from KOS */ nkeynes@87: #define COMMAND_QUEUE_LENGTH 16 nkeynes@87: nkeynes@1100: #define GD_CMD_PIOREAD 16 /* readcd */ nkeynes@1100: #define GD_CMD_DMAREAD 17 /* readcd */ nkeynes@87: #define GD_CMD_GETTOC 18 nkeynes@1100: #define GD_CMD_GETTOC2 19 /* toc2 */ nkeynes@1100: #define GD_CMD_PLAY 20 /* playcd */ nkeynes@1100: #define GD_CMD_PLAY2 21 /* playcd */ nkeynes@1100: #define GD_CMD_PAUSE 22 /* No params */ nkeynes@1100: #define GD_CMD_RELEASE 23 /* No params */ nkeynes@1100: #define GD_CMD_INIT 24 /* No params */ nkeynes@87: #define GD_CMD_SEEK 27 nkeynes@87: #define GD_CMD_READ 28 nkeynes@1100: #define GD_CMD_STOP 33 /* No params */ nkeynes@87: #define GD_CMD_GETSCD 34 nkeynes@87: #define GD_CMD_GETSES 35 nkeynes@87: nkeynes@1100: #define GD_CMD_STATUS_NONE 0 nkeynes@87: #define GD_CMD_STATUS_ACTIVE 1 nkeynes@1100: #define GD_CMD_STATUS_DONE 2 nkeynes@1100: #define GD_CMD_STATUS_ABORT 3 nkeynes@1100: #define GD_CMD_STATUS_ERROR 4 nkeynes@87: nkeynes@87: #define GD_ERROR_OK 0 nkeynes@87: #define GD_ERROR_NO_DISC 2 nkeynes@87: #define GD_ERROR_DISC_CHANGE 6 nkeynes@87: #define GD_ERROR_SYSTEM 1 nkeynes@87: nkeynes@1101: struct gdrom_toc2_params { nkeynes@1101: uint32_t session; nkeynes@1101: sh4addr_t buffer; nkeynes@1101: }; nkeynes@1101: nkeynes@1101: struct gdrom_readcd_params { nkeynes@1101: cdrom_lba_t lba; nkeynes@1101: cdrom_count_t count; nkeynes@1101: sh4addr_t buffer; nkeynes@1101: uint32_t unknown; nkeynes@1101: }; nkeynes@1101: nkeynes@1101: struct gdrom_playcd_params { nkeynes@1101: cdrom_lba_t start; nkeynes@1101: cdrom_lba_t end; nkeynes@1101: uint32_t repeat; nkeynes@1101: }; nkeynes@1101: nkeynes@1100: typedef union gdrom_cmd_params { nkeynes@1101: struct gdrom_toc2_params toc2; nkeynes@1101: struct gdrom_readcd_params readcd; nkeynes@1101: struct gdrom_playcd_params playcd; nkeynes@1100: } *gdrom_cmd_params_t; nkeynes@1100: nkeynes@1100: nkeynes@1100: nkeynes@1100: nkeynes@1100: typedef struct gdrom_queue_entry { nkeynes@87: int status; nkeynes@87: uint32_t cmd_code; nkeynes@1101: union gdrom_cmd_params params; nkeynes@87: uint32_t result[4]; nkeynes@1100: } *gdrom_queue_entry_t; nkeynes@87: nkeynes@1100: static struct gdrom_queue_entry gdrom_cmd_queue[COMMAND_QUEUE_LENGTH]; nkeynes@87: nkeynes@87: static struct bios_gdrom_status { nkeynes@87: uint32_t status; nkeynes@87: uint32_t disk_type; nkeynes@87: } bios_gdrom_status; nkeynes@87: nkeynes@1101: void bios_gdrom_init( void ) nkeynes@1101: { nkeynes@1101: memset( &gdrom_cmd_queue, 0, sizeof(gdrom_cmd_queue) ); nkeynes@1101: } nkeynes@1101: nkeynes@1100: void bios_gdrom_run_command( gdrom_queue_entry_t cmd ) nkeynes@87: { nkeynes@87: DEBUG( "BIOS GD command %d", cmd->cmd_code ); nkeynes@1101: cdrom_error_t status = CDROM_ERROR_OK; nkeynes@1101: sh4ptr_t ptr; nkeynes@87: switch( cmd->cmd_code ) { nkeynes@87: case GD_CMD_INIT: nkeynes@736: /* *shrug* */ nkeynes@736: cmd->status = GD_CMD_STATUS_DONE; nkeynes@736: break; nkeynes@1101: case GD_CMD_GETTOC2: nkeynes@1101: ptr = mem_get_region( cmd->params.toc2.buffer ); nkeynes@1101: status = gdrom_read_toc( ptr ); nkeynes@1101: if( status == CDROM_ERROR_OK ) { nkeynes@1101: /* Convert data to little-endian */ nkeynes@1101: struct gdrom_toc *toc = (struct gdrom_toc *)ptr; nkeynes@1101: for( unsigned i=0; i<99; i++ ) { nkeynes@1101: toc->track[i] = ntohl(toc->track[i]); nkeynes@1101: } nkeynes@1101: toc->first = ntohl(toc->first); nkeynes@1101: toc->last = ntohl(toc->last); nkeynes@1101: toc->leadout = ntohl(toc->leadout); nkeynes@1101: } nkeynes@1101: break; nkeynes@1101: case GD_CMD_PIOREAD: nkeynes@1101: case GD_CMD_DMAREAD: nkeynes@1101: ptr = mem_get_region( cmd->params.readcd.buffer ); nkeynes@1101: status = gdrom_read_cd( cmd->params.readcd.lba, nkeynes@1101: cmd->params.readcd.count, 0x28, ptr, NULL ); nkeynes@1101: break; nkeynes@1101: default: nkeynes@1101: WARN( "Unknown BIOS GD command %d\n", cmd->cmd_code ); nkeynes@1101: cmd->status = GD_CMD_STATUS_ERROR; nkeynes@1101: cmd->result[0] = GD_ERROR_SYSTEM; nkeynes@1101: return; nkeynes@1101: } nkeynes@1101: nkeynes@1101: switch( status ) { nkeynes@1101: case CDROM_ERROR_OK: nkeynes@1101: cmd->status = GD_CMD_STATUS_DONE; nkeynes@1101: cmd->result[0] = GD_ERROR_OK; nkeynes@1101: break; nkeynes@1101: case CDROM_ERROR_NODISC: nkeynes@1101: cmd->status = GD_CMD_STATUS_ERROR; nkeynes@1101: cmd->result[0] = GD_ERROR_NO_DISC; nkeynes@1101: break; nkeynes@87: default: nkeynes@736: cmd->status = GD_CMD_STATUS_ERROR; nkeynes@736: cmd->result[0] = GD_ERROR_SYSTEM; nkeynes@87: } nkeynes@87: } nkeynes@87: nkeynes@1101: uint32_t bios_gdrom_enqueue( uint32_t cmd, sh4addr_t data ) nkeynes@87: { nkeynes@87: int i; nkeynes@87: for( i=0; i= COMMAND_QUEUE_LENGTH || nkeynes@736: gdrom_cmd_queue[id].status == GD_CMD_STATUS_NONE ) nkeynes@736: return NULL; nkeynes@87: return &gdrom_cmd_queue[id]; nkeynes@87: } nkeynes@87: nkeynes@87: /** nkeynes@1102: * Address of the system information block (in the flash rom). Also repeats nkeynes@1102: * at FLASH_SYSINFO_SEGMENT+0xA0 nkeynes@87: */ nkeynes@1102: #define FLASH_SYSINFO_SEGMENT 0x0021a000 nkeynes@1102: #define FLASH_CONFIG_SEGMENT 0x0021c000 nkeynes@1102: #define FLASH_CONFIG_LENGTH 0x00004000 nkeynes@1102: #define FLASH_PARTITION_MAGIC "KATANA_FLASH____" nkeynes@87: nkeynes@1102: /** nkeynes@1102: * Locate the active config block. FIXME: This isn't completely correct, but it works nkeynes@1102: * under at least some circumstances. nkeynes@1102: */ nkeynes@1102: static char *bios_find_flash_config( sh4addr_t segment, uint32_t length ) nkeynes@1102: { nkeynes@1102: char *start = mem_get_region(segment); nkeynes@1102: char *p = start + 0x80; nkeynes@1102: char *end = p + length; nkeynes@1102: char *result = NULL; nkeynes@1102: nkeynes@1102: if( memcmp( start, FLASH_PARTITION_MAGIC, 16 ) != 0 ) nkeynes@1102: return NULL; /* Missing magic */ nkeynes@1102: while( p < end ) { nkeynes@1102: if( p[0] == 0x05 && p[1] == 0 ) { nkeynes@1102: result = p; nkeynes@1102: } nkeynes@1102: p += 0x40; nkeynes@1102: } nkeynes@1102: return result; nkeynes@1102: } nkeynes@1102: nkeynes@1102: /** nkeynes@1102: * Syscall information courtesy of Marcus Comstedt nkeynes@1102: */ nkeynes@1102: static void bios_sysinfo_vector( uint32_t syscallid ) nkeynes@1102: { nkeynes@1102: char *flash_segment, *flash_config; nkeynes@1102: char *dest; nkeynes@1102: DEBUG( "BIOS SYSINFO: r4 = %08X, r5 = %08X, r6 = %08x, r7= %08X", sh4r.r[4], sh4r.r[5], sh4r.r[6], sh4r.r[7] ); nkeynes@1102: nkeynes@1102: switch( sh4r.r[7] ) { nkeynes@1102: case 0: /* SYSINFO_INIT */ nkeynes@1102: /* Initialize the region 8c000068 .. 8c00007f from the flash rom nkeynes@1102: * uint64_t system_id; nkeynes@1102: * char [5] system_props; nkeynes@1102: * char [3] zero_pad (?) nkeynes@1102: * char [8] settings; nkeynes@1102: **/ nkeynes@1102: flash_segment = mem_get_region(FLASH_SYSINFO_SEGMENT); nkeynes@1102: flash_config = bios_find_flash_config(FLASH_CONFIG_SEGMENT,FLASH_CONFIG_LENGTH); nkeynes@1102: dest = mem_get_region( 0x8c000068 ); nkeynes@1102: memset( dest, 0, 24 ); nkeynes@1102: memcpy( dest, flash_segment + 0x56, 8 ); nkeynes@1102: memcpy( dest + 8, flash_segment, 5 ); nkeynes@1102: if( flash_config != NULL ) { nkeynes@1102: memcpy( dest+16, flash_config+2, 8 ); nkeynes@1102: } nkeynes@1102: break; nkeynes@1102: case 2: /* SYSINFO_ICON */ nkeynes@1102: /* Not supported yet */ nkeynes@1102: break; nkeynes@1102: case 3: /* SYSINFO_ID */ nkeynes@1102: sh4r.r[0] = 0x8c000068; nkeynes@1102: break; nkeynes@1102: } nkeynes@1102: } nkeynes@1102: nkeynes@1102: static void bios_flashrom_vector( uint32_t syscallid ) nkeynes@1102: { nkeynes@1102: char *dest; nkeynes@1102: DEBUG( "BIOS FLASHROM: r4 = %08X, r5 = %08X, r6 = %08x, r7= %08X", sh4r.r[4], sh4r.r[5], sh4r.r[6], sh4r.r[7] ); nkeynes@1102: nkeynes@1102: switch( sh4r.r[7] ) { nkeynes@1102: case 0: /* FLASHROM_INFO */ nkeynes@1102: break; nkeynes@1102: case 1: /* FLASHROM_READ */ nkeynes@1102: nkeynes@1102: break; nkeynes@1102: case 2: /* FLASHROM_WRITE */ nkeynes@1102: break; nkeynes@1102: case 3: /* FLASHROM_DELETE */ nkeynes@1102: break; nkeynes@1102: } nkeynes@1102: } nkeynes@1102: nkeynes@1102: static void bios_romfont_vector( uint32_t syscallid ) nkeynes@1102: { nkeynes@1102: DEBUG( "BIOS ROMFONT: r4 = %08X, r5 = %08X, r6 = %08x, r7= %08X", sh4r.r[4], sh4r.r[5], sh4r.r[6], sh4r.r[7] ); nkeynes@1102: /* Not implemented */ nkeynes@1102: } nkeynes@1102: nkeynes@1102: static void bios_gdrom_vector( uint32_t syscallid ) nkeynes@87: { nkeynes@1100: gdrom_queue_entry_t cmd; nkeynes@87: nkeynes@1102: DEBUG( "BIOS GDROM: r4 = %08X, r5 = %08X, r6 = %08x, r7= %08X", sh4r.r[4], sh4r.r[5], sh4r.r[6], sh4r.r[7] ); nkeynes@1102: nkeynes@1102: switch( sh4r.r[6] ) { nkeynes@1102: case 0: /* GD-Rom */ nkeynes@1102: switch( sh4r.r[7] ) { nkeynes@1102: case 0: /* Send command */ nkeynes@1102: sh4r.r[0] = bios_gdrom_enqueue( sh4r.r[4], sh4r.r[5] ); nkeynes@1102: break; nkeynes@1102: case 1: /* Check command */ nkeynes@1102: cmd = bios_gdrom_get_command( sh4r.r[4] ); nkeynes@1102: if( cmd == NULL ) { nkeynes@1102: sh4r.r[0] = GD_CMD_STATUS_NONE; nkeynes@1102: } else { nkeynes@1102: sh4r.r[0] = cmd->status; nkeynes@1102: if( cmd->status == GD_CMD_STATUS_ERROR && nkeynes@1102: sh4r.r[5] != 0 ) { nkeynes@1102: mem_copy_to_sh4( sh4r.r[5], (sh4ptr_t)&cmd->result, sizeof(cmd->result) ); nkeynes@736: } nkeynes@736: } nkeynes@736: break; nkeynes@1102: case 2: /* Mainloop */ nkeynes@1102: bios_gdrom_run_queue(); nkeynes@736: break; nkeynes@1102: case 3: /* Init */ nkeynes@1102: bios_gdrom_init(); nkeynes@1102: break; nkeynes@1102: case 4: /* Drive status */ nkeynes@1102: if( sh4r.r[4] != 0 ) { nkeynes@1102: mem_copy_to_sh4( sh4r.r[4], (sh4ptr_t)&bios_gdrom_status, nkeynes@1102: sizeof(bios_gdrom_status) ); nkeynes@1102: } nkeynes@1102: sh4r.r[0] = 0; nkeynes@1102: break; nkeynes@1102: case 8: /* Abort command */ nkeynes@1102: cmd = bios_gdrom_get_command( sh4r.r[4] ); nkeynes@1102: if( cmd == NULL || cmd->status != GD_CMD_STATUS_ACTIVE ) { nkeynes@1102: sh4r.r[0] = -1; nkeynes@1102: } else { nkeynes@1102: cmd->status = GD_CMD_STATUS_ABORT; nkeynes@1102: sh4r.r[0] = 0; nkeynes@1102: } nkeynes@1102: break; nkeynes@1102: case 9: /* Reset */ nkeynes@1102: break; nkeynes@1102: case 10: /* Set mode */ nkeynes@1102: sh4r.r[0] = 0; nkeynes@1102: break; nkeynes@736: } nkeynes@736: break; nkeynes@1102: case -1: /* Misc */ nkeynes@1102: break; nkeynes@1102: default: /* ??? */ nkeynes@1102: break; nkeynes@1102: } nkeynes@1102: } nkeynes@1102: nkeynes@1102: static void bios_menu_vector( uint32_t syscallid ) nkeynes@1102: { nkeynes@1102: DEBUG( "BIOS MENU: r4 = %08X, r5 = %08X, r6 = %08x, r7= %08X", sh4r.r[4], sh4r.r[5], sh4r.r[6], sh4r.r[7] ); nkeynes@1102: nkeynes@1102: switch( sh4r.r[4] ) { nkeynes@1102: case 0: nkeynes@1102: WARN( "Entering main program" ); nkeynes@1102: break; nkeynes@1102: case 1: nkeynes@1102: WARN( "Program aborted to DC menu"); nkeynes@1102: dreamcast_stop(); nkeynes@1102: break; nkeynes@87: } nkeynes@87: } nkeynes@102: nkeynes@1099: void bios_boot( uint32_t syscallid ) nkeynes@1099: { nkeynes@1099: /* Initialize hardware */ nkeynes@1099: /* Boot disc if present */ nkeynes@1103: if( !bios_boot_gdrom_disc() ) { nkeynes@1100: dreamcast_stop(); nkeynes@1100: } nkeynes@1099: } nkeynes@1099: nkeynes@102: void bios_install( void ) nkeynes@102: { nkeynes@102: bios_gdrom_init(); nkeynes@1102: syscall_add_hook_vector( 0xB0, 0x8C0000B0, bios_sysinfo_vector ); nkeynes@1102: syscall_add_hook_vector( 0xB4, 0x8C0000B4, bios_romfont_vector ); nkeynes@1102: syscall_add_hook_vector( 0xB8, 0x8C0000B8, bios_flashrom_vector ); nkeynes@1102: syscall_add_hook_vector( 0xBC, 0x8C0000BC, bios_gdrom_vector ); nkeynes@1102: syscall_add_hook_vector( 0xE0, 0x8C0000E0, bios_menu_vector ); nkeynes@102: } nkeynes@1099: nkeynes@1099: #define MIN_ISO_SECTORS 32 nkeynes@1099: nkeynes@1107: static gboolean bios_load_ipl( cdrom_disc_t disc, cdrom_track_t track, const char *program_name, nkeynes@1107: unsigned char *buffer, gboolean unscramble ) nkeynes@1107: { nkeynes@1107: gboolean rv = TRUE; nkeynes@1107: nkeynes@1107: IsoImageFilesystem *iso = iso_filesystem_new_from_track( disc, track, NULL ); nkeynes@1107: if( iso == NULL ) { nkeynes@1107: ERROR( "Disc is not bootable (invalid ISO9660 filesystem)" ); nkeynes@1107: return FALSE; nkeynes@1107: } nkeynes@1107: IsoFileSource *file = NULL; nkeynes@1107: int status = iso->get_by_path(iso, program_name, &file ); nkeynes@1107: if( status != 1 ) { nkeynes@1107: ERROR( "Disc is not bootable (initial program '%s' not found)", program_name ); nkeynes@1107: iso_filesystem_unref(iso); nkeynes@1107: return FALSE; nkeynes@1107: } nkeynes@1107: nkeynes@1107: struct stat st; nkeynes@1107: if( iso_file_source_stat(file, &st) == 1 ) { nkeynes@1107: if( st.st_size > (0x8D000000 - BINARY_LOAD_ADDR) ) { nkeynes@1107: ERROR( "Disc is not bootable (Initial program is too large to fit into memory)" ); nkeynes@1107: rv = FALSE; nkeynes@1107: } else if( iso_file_source_open(file) == 1 ) { nkeynes@1107: size_t len; nkeynes@1107: if( unscramble ) { nkeynes@1107: char *tmp = g_malloc(st.st_size); nkeynes@1107: len = iso_file_source_read(file, tmp, st.st_size); nkeynes@1107: bootprogram_unscramble(buffer, tmp, st.st_size); nkeynes@1107: g_free(tmp); nkeynes@1107: } else { nkeynes@1107: len = iso_file_source_read(file, buffer, st.st_size); nkeynes@1107: } nkeynes@1107: nkeynes@1107: if( len != st.st_size ) { nkeynes@1107: ERROR( "Disc is not bootable (Unable to read initial program '%s')", program_name ); nkeynes@1107: rv = FALSE; nkeynes@1107: } nkeynes@1107: iso_file_source_close(file); nkeynes@1107: } nkeynes@1107: } else { nkeynes@1107: ERROR( "Disc is not bootable (Unable to get size of initial program '%s')", program_name ); nkeynes@1107: rv = FALSE; nkeynes@1107: } nkeynes@1107: nkeynes@1107: iso_file_source_unref(file); nkeynes@1107: iso_filesystem_unref(iso); nkeynes@1107: return rv; nkeynes@1107: } nkeynes@1107: nkeynes@1099: gboolean bios_boot_gdrom_disc( void ) nkeynes@1099: { nkeynes@1099: cdrom_disc_t disc = gdrom_get_current_disc(); nkeynes@1099: nkeynes@1099: int status = gdrom_get_drive_status(); nkeynes@1099: if( status == CDROM_DISC_NONE ) { nkeynes@1099: ERROR( "No disc in drive" ); nkeynes@1099: return FALSE; nkeynes@1099: } nkeynes@1099: nkeynes@1099: /* Find the bootable data track (if present) */ nkeynes@1099: cdrom_track_t track = gdrom_disc_get_boot_track(disc); nkeynes@1099: if( track == NULL ) { nkeynes@1099: ERROR( "Disc is not bootable" ); nkeynes@1099: return FALSE; nkeynes@1099: } nkeynes@1099: uint32_t lba = track->lba; nkeynes@1099: uint32_t sectors = cdrom_disc_get_track_size(disc,track); nkeynes@1099: if( sectors < MIN_ISO_SECTORS ) { nkeynes@1099: ERROR( "Disc is not bootable" ); nkeynes@1099: return FALSE; nkeynes@1099: } nkeynes@1099: /* Load the initial bootstrap into DC ram at 8c008000 */ nkeynes@1099: size_t length = BOOTSTRAP_SIZE; nkeynes@1099: unsigned char *bootstrap = mem_get_region(BOOTSTRAP_LOAD_ADDR); nkeynes@1099: if( cdrom_disc_read_sectors( disc, track->lba, BOOTSTRAP_SIZE/2048, nkeynes@1099: CDROM_READ_DATA|CDROM_READ_MODE2_FORM1, bootstrap, &length ) != nkeynes@1099: CDROM_ERROR_OK ) { nkeynes@1099: ERROR( "Disc is not bootable" ); nkeynes@1099: return FALSE; nkeynes@1099: } nkeynes@1099: nkeynes@1099: /* Check the magic just to be sure */ nkeynes@1099: dc_bootstrap_head_t metadata = (dc_bootstrap_head_t)bootstrap; nkeynes@1099: if( memcmp( metadata->magic, BOOTSTRAP_MAGIC, BOOTSTRAP_MAGIC_SIZE ) != 0 ) { nkeynes@1099: ERROR( "Disc is not bootable (missing dreamcast bootstrap)" ); nkeynes@1099: return FALSE; nkeynes@1099: } nkeynes@1099: nkeynes@1099: /* Get the initial program from the bootstrap (usually 1ST_READ.BIN) */ nkeynes@1107: char program_name[18] = "/"; nkeynes@1107: memcpy(program_name+1, metadata->boot_file, 16); nkeynes@1107: program_name[17] = '\0'; nkeynes@1107: for( int i=16; i >= 0 && program_name[i] == ' '; i-- ) { nkeynes@1099: program_name[i] = '\0'; nkeynes@1099: } nkeynes@1099: nkeynes@1099: /* Bootstrap is good. Now find the program in the actual filesystem... */ nkeynes@1107: unsigned char *program = mem_get_region(BINARY_LOAD_ADDR); nkeynes@1107: gboolean isGDROM = (disc->disc_type == CDROM_DISC_GDROM ); nkeynes@1107: if( !bios_load_ipl( disc, track, program_name, program, !isGDROM ) ) nkeynes@1099: return FALSE; nkeynes@1107: asic_enable_ide_interface(isGDROM); nkeynes@1100: dreamcast_program_loaded( "", BOOTSTRAP_ENTRY_ADDR ); nkeynes@1100: return TRUE; nkeynes@1099: }