nkeynes@26: /** nkeynes@561: * $Id$ nkeynes@1: * nkeynes@26: * File loading routines, mostly for loading demos without going through the nkeynes@1108: * whole procedure of manually making a CD image for them. nkeynes@26: * nkeynes@26: * Copyright (c) 2005 Nathan Keynes. nkeynes@26: * nkeynes@26: * This program is free software; you can redistribute it and/or modify nkeynes@26: * it under the terms of the GNU General Public License as published by nkeynes@26: * the Free Software Foundation; either version 2 of the License, or nkeynes@26: * (at your option) any later version. nkeynes@26: * nkeynes@26: * This program is distributed in the hope that it will be useful, nkeynes@26: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@26: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@26: * GNU General Public License for more details. nkeynes@1: */ nkeynes@1: nkeynes@481: #include nkeynes@1: #include nkeynes@736: #include nkeynes@1: #include nkeynes@1: #include nkeynes@1: #include nkeynes@1: #include nkeynes@1108: #include nkeynes@105: #include nkeynes@26: #include "mem.h" nkeynes@26: #include "bootstrap.h" nkeynes@171: #include "dreamcast.h" nkeynes@450: #include "config.h" nkeynes@427: #include "loader.h" nkeynes@1108: #include "drivers/cdrom/cdrom.h" nkeynes@1108: #include "drivers/cdrom/isofs.h" nkeynes@1109: #include "gdrom/gdrom.h" nkeynes@481: nkeynes@1108: const char bootstrap_magic[32] = "SEGA SEGAKATANA SEGA ENTERPRISES"; nkeynes@1108: const char iso_magic[6] = "\001CD001"; nkeynes@26: char *file_loader_extensions[][2] = { nkeynes@736: { "sbi", "Self Boot Inducer" }, nkeynes@736: { "bin", "SH4 Bin file" }, nkeynes@736: { NULL, NULL } }; nkeynes@26: nkeynes@1109: static cdrom_disc_t cdrom_wrap_elf( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err ); nkeynes@1109: static cdrom_disc_t cdrom_wrap_binary( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err ); nkeynes@1109: static gboolean file_load_binary( const gchar *filename, int fd, ERROR *err ); nkeynes@1109: static gboolean file_load_elf( const gchar *filename, int fd, ERROR *err ); nkeynes@1: nkeynes@1108: nkeynes@1109: nkeynes@1109: lxdream_file_type_t file_identify( const gchar *filename, int fd, ERROR *err ) nkeynes@1108: { nkeynes@1108: char buf[32]; nkeynes@1109: lxdream_file_type_t result = FILE_UNKNOWN; nkeynes@1109: gboolean mustClose = FALSE; nkeynes@1109: off_t posn; nkeynes@1108: nkeynes@1109: if( fd == -1 ) { nkeynes@1109: fd = open( filename, O_RDONLY ); nkeynes@1109: if( fd == -1 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s' (%s)" ,filename, strerror(errno) ); nkeynes@1109: return FILE_ERROR; nkeynes@1109: } nkeynes@1109: mustClose = TRUE; nkeynes@1109: } else { nkeynes@1109: /* Save current file position */ nkeynes@1109: posn = lseek(fd, 0, SEEK_CUR); nkeynes@1109: if( posn == -1 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_IOERROR, "Unable to read from file '%s' (%s)", filename, strerror(errno) ); nkeynes@1109: return FILE_ERROR; nkeynes@1109: } nkeynes@1108: } nkeynes@1108: nkeynes@1109: int status = read(fd, buf, 32); nkeynes@1109: if( status == -1 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_IOERROR, "Unable to read from file '%s' (%s)", filename, strerror(errno) ); nkeynes@1109: result = FILE_ERROR; nkeynes@1109: } else if( status != 32 ) { nkeynes@1109: result = FILE_UNKNOWN; nkeynes@1109: } else if( buf[0] == 0x7F && buf[1] == 'E' && nkeynes@1108: buf[2] == 'L' && buf[3] == 'F' ) { nkeynes@1109: result = FILE_ELF; nkeynes@1108: } else if( memcmp( buf, "PK\x03\x04", 4 ) == 0 ) { nkeynes@1109: result = FILE_ZIP; nkeynes@1108: } else if( memcmp( buf, DREAMCAST_SAVE_MAGIC, 16 ) == 0 ) { nkeynes@1109: result = FILE_SAVE_STATE; nkeynes@1108: } else if( lseek( fd, 32768, SEEK_SET ) == 32768 && nkeynes@1108: read( fd, buf, 8 ) == 8 && nkeynes@1108: memcmp( buf, iso_magic, 6) == 0 ) { nkeynes@1109: result = FILE_ISO; nkeynes@1109: } else { nkeynes@1109: /* Check the file extension - .bin = sh4 binary */ nkeynes@1109: int len = strlen(filename); nkeynes@1109: struct stat st; nkeynes@1109: nkeynes@1109: if( len > 4 && strcasecmp(filename + (len-4), ".bin") == 0 && nkeynes@1109: fstat(fd, &st) != -1 && st.st_size <= BINARY_MAX_SIZE ) { nkeynes@1109: result = FILE_BINARY; nkeynes@1109: } nkeynes@1108: } nkeynes@1109: nkeynes@1109: if( mustClose ) { nkeynes@1109: close(fd); nkeynes@1109: } else { nkeynes@1109: lseek( fd, posn, SEEK_SET ); nkeynes@1109: } nkeynes@1109: return result; nkeynes@1108: } nkeynes@427: nkeynes@427: nkeynes@1109: gboolean file_load_exec( const gchar *filename, ERROR *err ) nkeynes@1: { nkeynes@446: gboolean result = TRUE; nkeynes@736: nkeynes@1: int fd = open( filename, O_RDONLY ); nkeynes@1: if( fd == -1 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s' (%s)" ,filename, strerror(errno) ); nkeynes@26: return FALSE; nkeynes@1: } nkeynes@736: nkeynes@1109: lxdream_file_type_t type = file_identify(filename, fd, err); nkeynes@1109: switch( type ) { nkeynes@1109: case FILE_ERROR: nkeynes@1109: result = FALSE; nkeynes@1109: break; nkeynes@1109: case FILE_ELF: nkeynes@1109: result = file_load_elf( filename, fd, err ); nkeynes@1109: break; nkeynes@1109: case FILE_BINARY: nkeynes@1109: result = file_load_binary( filename, fd, err ); nkeynes@1109: break; nkeynes@1109: default: nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_UNKNOWN, "File '%s' could not be recognized as an executable binary", filename ); nkeynes@1109: result = FALSE; nkeynes@1109: break; nkeynes@1109: } nkeynes@736: nkeynes@1109: close(fd); nkeynes@1109: return result; nkeynes@1109: } nkeynes@1109: nkeynes@1109: lxdream_file_type_t file_load_magic( const gchar *filename, gboolean wrap_exec, ERROR *err ) nkeynes@1109: { nkeynes@1109: gboolean result; nkeynes@1109: /* Try disc types first */ nkeynes@1109: cdrom_disc_t disc = cdrom_disc_open( filename, err ); nkeynes@1109: if( disc != NULL ) { nkeynes@1109: gdrom_mount_disc(disc); nkeynes@1109: return FILE_DISC; nkeynes@1109: } else if( err != LX_ERR_FILE_UNKNOWN ) { nkeynes@1109: return FILE_ERROR; nkeynes@1: } nkeynes@1109: nkeynes@1109: int fd = open( filename, O_RDONLY ); nkeynes@1109: if( fd == -1 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s' (%s)" ,filename, strerror(errno) ); nkeynes@1109: return FILE_ERROR; nkeynes@1109: } nkeynes@1109: nkeynes@1109: lxdream_file_type_t type = file_identify(filename, fd, err); nkeynes@1109: switch( type ) { nkeynes@1109: case FILE_ERROR: nkeynes@1109: result = FALSE; nkeynes@1109: break; nkeynes@1109: case FILE_ELF: nkeynes@1109: if( wrap_exec ) { nkeynes@1109: disc = cdrom_wrap_elf( CDROM_DISC_XA, filename, fd, err ); nkeynes@1109: result = disc != NULL; nkeynes@1109: if( disc != NULL ) { nkeynes@1109: gdrom_mount_disc(disc); nkeynes@1109: } nkeynes@1: } else { nkeynes@1109: result = file_load_elf( filename, fd, err ); nkeynes@1109: } nkeynes@1109: break; nkeynes@1109: case FILE_BINARY: nkeynes@1109: if( wrap_exec ) { nkeynes@1109: disc = cdrom_wrap_binary( CDROM_DISC_XA, filename, fd, err ); nkeynes@1109: result = disc != NULL; nkeynes@1109: if( disc != NULL ) { nkeynes@1109: gdrom_mount_disc(disc); nkeynes@1: } nkeynes@1109: } else { nkeynes@1109: result = file_load_binary( filename, fd, err ); nkeynes@1: } nkeynes@1109: break; nkeynes@1109: case FILE_SAVE_STATE: nkeynes@1109: result = dreamcast_load_state( filename ); nkeynes@1109: break; nkeynes@1109: case FILE_ZIP: nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_UNSUP, "ZIP/SBI not currently supported" ); nkeynes@736: result = FALSE; nkeynes@1109: break; nkeynes@1109: case FILE_ISO: nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_UNSUP, "ISO files are not currently supported" ); nkeynes@736: result = FALSE; nkeynes@1109: break; nkeynes@1109: default: nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_UNKNOWN, "File '%s' could not be recognized", filename ); nkeynes@1109: result = FALSE; nkeynes@1109: break; nkeynes@446: } nkeynes@1: close(fd); nkeynes@1109: if( result ) { nkeynes@1109: CLEAR_ERROR(err); nkeynes@1109: return type; nkeynes@1109: } nkeynes@1109: return FILE_ERROR; nkeynes@1: } nkeynes@19: nkeynes@543: void file_load_postload( const gchar *filename, int pc ) nkeynes@110: { nkeynes@1036: gchar *bootstrap_file = lxdream_get_global_config_path_value(CONFIG_BOOTSTRAP); nkeynes@825: if( bootstrap_file != NULL && bootstrap_file[0] != '\0' ) { nkeynes@736: /* Load in a bootstrap before the binary, to initialize everything nkeynes@736: * correctly nkeynes@736: */ nkeynes@543: if( mem_load_block( bootstrap_file, BOOTSTRAP_LOAD_ADDR, BOOTSTRAP_SIZE ) == 0 ) { nkeynes@1100: dreamcast_program_loaded( filename, BOOTSTRAP_ENTRY_ADDR ); nkeynes@1036: g_free(bootstrap_file); nkeynes@736: return; nkeynes@736: } nkeynes@88: } nkeynes@543: dreamcast_program_loaded( filename, pc ); nkeynes@1036: g_free(bootstrap_file); nkeynes@110: } nkeynes@110: nkeynes@110: nkeynes@1109: static gboolean is_sh4_elf( Elf32_Ehdr *head ) nkeynes@1108: { nkeynes@1108: return ( head->e_ident[EI_CLASS] == ELFCLASS32 && nkeynes@1108: head->e_ident[EI_DATA] == ELFDATA2LSB && nkeynes@1108: head->e_ident[EI_VERSION] == 1 && nkeynes@1108: head->e_type == ET_EXEC && nkeynes@1108: head->e_machine == EM_SH && nkeynes@1108: head->e_version == 1 ); nkeynes@1108: } nkeynes@1108: nkeynes@1109: static gboolean is_arm_elf( Elf32_Ehdr *head ) nkeynes@1108: { nkeynes@1108: return ( head->e_ident[EI_CLASS] == ELFCLASS32 && nkeynes@1108: head->e_ident[EI_DATA] == ELFDATA2LSB && nkeynes@1108: head->e_ident[EI_VERSION] == 1 && nkeynes@1108: head->e_type == ET_EXEC && nkeynes@1108: head->e_machine == EM_ARM && nkeynes@1108: head->e_version == 1 ); nkeynes@1108: } nkeynes@1108: nkeynes@1109: static gboolean file_load_elf( const gchar *filename, int fd, ERROR *err ) nkeynes@105: { nkeynes@105: Elf32_Ehdr head; nkeynes@105: Elf32_Phdr phdr; nkeynes@105: int i; nkeynes@105: nkeynes@105: if( read( fd, &head, sizeof(head) ) != sizeof(head) ) nkeynes@736: return FALSE; nkeynes@1108: if( !is_sh4_elf(&head) ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "File is not an SH4 ELF executable file" ); nkeynes@736: return FALSE; nkeynes@105: } nkeynes@105: nkeynes@105: /* Program headers */ nkeynes@105: for( i=0; i phdr.p_filesz ) { nkeynes@736: memset( target + phdr.p_filesz, 0, phdr.p_memsz - phdr.p_filesz ); nkeynes@736: } nkeynes@736: } nkeynes@105: } nkeynes@736: nkeynes@543: file_load_postload( filename, head.e_entry ); nkeynes@446: return TRUE; nkeynes@105: } nkeynes@1108: nkeynes@1109: static gboolean file_load_binary( const gchar *filename, int fd, ERROR *err ) nkeynes@1109: { nkeynes@1109: struct stat st; nkeynes@1109: nkeynes@1109: if( fstat( fd, &st ) == -1 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_IOERROR, "Error reading binary file '%s' (%s)", filename, strerror(errno) ); nkeynes@1109: return FALSE; nkeynes@1109: } nkeynes@1109: nkeynes@1109: if( st.st_size > BINARY_MAX_SIZE ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "Binary file '%s' is too large to fit in memory", filename ); nkeynes@1109: return FALSE; nkeynes@1109: } nkeynes@1109: nkeynes@1109: sh4ptr_t target = mem_get_region( BINARY_LOAD_ADDR ); nkeynes@1109: if( read( fd, target, st.st_size ) != st.st_size ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_IOERROR, "Error reading binary file '%s' (%s)", filename, strerror(errno) ); nkeynes@1109: return FALSE; nkeynes@1109: } nkeynes@1109: nkeynes@1109: file_load_postload( filename, BINARY_LOAD_ADDR ); nkeynes@1109: return TRUE; nkeynes@1109: } nkeynes@1109: nkeynes@1108: /** nkeynes@1108: * Create a new CDROM disc containing a single 1ST_READ.BIN. nkeynes@1108: * @param type The disc type - must be CDROM_DISC_GDROM or CDROM_DISC_XA nkeynes@1108: * @param bin The binary data (takes ownership) nkeynes@1108: * @param bin_size nkeynes@1108: */ nkeynes@1108: cdrom_disc_t cdrom_disc_new_wrapped_binary( cdrom_disc_type_t type, const gchar *filename, unsigned char *bin, size_t bin_size, nkeynes@1108: ERROR *err ) nkeynes@1108: { nkeynes@1108: IsoImage *iso = NULL; nkeynes@1108: unsigned char *data = bin; nkeynes@1108: cdrom_lba_t start_lba = 45000; /* GDROM_START */ nkeynes@1108: char bootstrap[32768]; nkeynes@1108: nkeynes@1109: /* 1. Load in the bootstrap: Note failures here are considered configuration errors */ nkeynes@1108: gchar *bootstrap_file = lxdream_get_global_config_path_value(CONFIG_BOOTSTRAP); nkeynes@1108: if( bootstrap_file == NULL || bootstrap_file[0] == '\0' ) { nkeynes@1108: g_free(data); nkeynes@1109: SET_ERROR( err, LX_ERR_CONFIG, "Unable to create CD image: bootstrap file is not configured" ); nkeynes@1108: return NULL; nkeynes@1108: } nkeynes@1108: nkeynes@1108: FILE *f = fopen( bootstrap_file, "ro" ); nkeynes@1108: if( f == NULL ) { nkeynes@1108: g_free(data); nkeynes@1109: SET_ERROR( err, LX_ERR_CONFIG, "Unable to create CD image: bootstrap file '%s' could not be opened", bootstrap_file ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: size_t len = fread( bootstrap, 1, 32768, f ); nkeynes@1108: fclose(f); nkeynes@1108: if( len != 32768 ) { nkeynes@1108: g_free(data); nkeynes@1109: SET_ERROR( err, LX_ERR_CONFIG, "Unable to create CD image: bootstrap file '%s' is invalid", bootstrap_file ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: nkeynes@1108: /* 2. Scramble the binary if necessary (and set type settings) */ nkeynes@1108: if( type != CDROM_DISC_GDROM ) { nkeynes@1108: /* scramble the binary if we're going the MIL-CD route */ nkeynes@1108: unsigned char *scramblebin = g_malloc(bin_size); nkeynes@1108: bootprogram_scramble( scramblebin, bin, bin_size ); nkeynes@1108: data = scramblebin; nkeynes@1108: start_lba = 0x2DB6; /* CDROM_START (does it matter?) */ nkeynes@1108: g_free(bin); nkeynes@1108: } nkeynes@1108: nkeynes@1108: /* 3. Frob the bootstrap data */ nkeynes@1108: dc_bootstrap_head_t boot_header = (dc_bootstrap_head_t)bootstrap; nkeynes@1108: memcpy( boot_header->boot_file, "1ST_READ.BIN ", 16 ); nkeynes@1108: char tmp[129]; nkeynes@1108: int name_len = snprintf( tmp, 129, "lxdream wrapped image: %s", filename ); nkeynes@1108: if( name_len < 128 ) nkeynes@1108: memset( tmp+name_len, ' ', 128-name_len ); nkeynes@1108: memcpy( boot_header->product_name, tmp, 128 ); nkeynes@1108: // bootstrap_update_crc(bootstrap); nkeynes@1108: nkeynes@1108: nkeynes@1108: /* 4. Build the ISO image */ nkeynes@1108: int status = iso_image_new("autocd", &iso); nkeynes@1108: if( status != 1 ) { nkeynes@1108: g_free(data); nkeynes@1109: SET_ERROR( err, LX_ERR_NOMEM, "Unable to create CD image: out of memory" ); nkeynes@1108: return NULL; nkeynes@1108: } nkeynes@1108: nkeynes@1108: IsoStream *stream; nkeynes@1108: if( iso_memory_stream_new(data, bin_size, &stream) != 1 ) { nkeynes@1108: g_free(data); nkeynes@1108: iso_image_unref(iso); nkeynes@1109: SET_ERROR( err, LX_ERR_NOMEM, "Unable to create CD image: out of memory" ); nkeynes@1108: return NULL; nkeynes@1108: } nkeynes@1108: iso_tree_add_new_file(iso_image_get_root(iso), "1ST_READ.BIN", stream, NULL); nkeynes@1108: sector_source_t track = iso_sector_source_new( iso, SECTOR_MODE2_FORM1, start_lba, nkeynes@1108: bootstrap, err ); nkeynes@1108: if( track == NULL ) { nkeynes@1108: iso_image_unref(iso); nkeynes@1108: return NULL; nkeynes@1108: } nkeynes@1108: nkeynes@1109: cdrom_disc_t disc = cdrom_disc_new_from_track( type, track, start_lba, err ); nkeynes@1108: iso_image_unref(iso); nkeynes@1108: if( disc != NULL ) { nkeynes@1108: disc->name = g_strdup(filename); nkeynes@1109: } nkeynes@1108: return disc; nkeynes@1108: } nkeynes@1108: nkeynes@1109: static cdrom_disc_t cdrom_wrap_elf( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err ) nkeynes@1108: { nkeynes@1108: Elf32_Ehdr head; nkeynes@1108: int i; nkeynes@1108: nkeynes@1108: /* Check the file header is actually an SH4 binary */ nkeynes@1108: if( read( fd, &head, sizeof(head) ) != sizeof(head) ) nkeynes@1108: return FALSE; nkeynes@1109: nkeynes@1108: if( !is_sh4_elf(&head) ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "File is not an SH4 ELF executable file" ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: if( head.e_entry != BINARY_LOAD_ADDR ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "SH4 Binary has incorrect entry point (should be %08X but is %08X)", BINARY_LOAD_ADDR, head.e_entry ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: nkeynes@1108: /* Load the program headers */ nkeynes@1108: Elf32_Phdr phdr[head.e_phnum]; nkeynes@1108: lseek( fd, head.e_phoff, SEEK_SET ); nkeynes@1108: if( read( fd, phdr, sizeof(phdr) ) != sizeof(phdr) ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "File is not a valid executable file" ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: nkeynes@1108: sh4addr_t start = (sh4addr_t)-1, end=0; nkeynes@1108: /* Scan program headers for memory range in use */ nkeynes@1108: for( i=0; i end ) nkeynes@1108: end = phdr[i].p_vaddr + phdr[i].p_memsz; nkeynes@1108: } nkeynes@1108: } nkeynes@1108: nkeynes@1108: if( start != BINARY_LOAD_ADDR ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "SH4 Binary has incorrect load address (should be %08X but is %08X)", BINARY_LOAD_ADDR, start ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: if( end >= 0x8D000000 ) { nkeynes@1109: SET_ERROR( err, LX_ERR_FILE_INVALID, "SH4 binary is too large to fit in memory (end address is %08X)", end ); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: nkeynes@1108: /* Load the program into memory */ nkeynes@1108: char *program = g_malloc0( end-start ); nkeynes@1108: for( i=0; i