filename | src/loader.c |
changeset | 1109:700c5ab26a63 |
prev | 1108:305ef2082079 |
next | 1117:0b14a8ec373b |
author | nkeynes |
date | Thu Jun 10 22:13:16 2010 +1000 (13 years ago) |
permissions | -rw-r--r-- |
last change | Integrate executable wrapping into the user interface - command-line now loads wrapped by default, -e <bin> to run binary - add support for .bin executables - Add useful (internal) error codes |
file | annotate | diff | log | raw |
1.1 --- a/src/loader.c Fri Jun 04 09:13:40 2010 +10001.2 +++ b/src/loader.c Thu Jun 10 22:13:16 2010 +10001.3 @@ -33,6 +33,7 @@1.4 #include "loader.h"1.5 #include "drivers/cdrom/cdrom.h"1.6 #include "drivers/cdrom/isofs.h"1.7 +#include "gdrom/gdrom.h"1.9 const char bootstrap_magic[32] = "SEGA SEGAKATANA SEGA ENTERPRISES";1.10 const char iso_magic[6] = "\001CD001";1.11 @@ -41,101 +42,171 @@1.12 { "bin", "SH4 Bin file" },1.13 { NULL, NULL } };1.15 -gboolean file_load_elf_fd( const gchar *filename, int fd );1.16 +static cdrom_disc_t cdrom_wrap_elf( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err );1.17 +static cdrom_disc_t cdrom_wrap_binary( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err );1.18 +static gboolean file_load_binary( const gchar *filename, int fd, ERROR *err );1.19 +static gboolean file_load_elf( const gchar *filename, int fd, ERROR *err );1.21 -typedef enum {1.22 - FILE_ERROR,1.23 - FILE_BINARY,1.24 - FILE_ELF,1.25 - FILE_ISO,1.26 - FILE_DISC,1.27 - FILE_ZIP,1.28 - FILE_SAVE_STATE,1.29 -} lxdream_file_type_t;1.31 -static lxdream_file_type_t file_magic( const gchar *filename, int fd, ERROR *err )1.32 +1.33 +lxdream_file_type_t file_identify( const gchar *filename, int fd, ERROR *err )1.34 {1.35 char buf[32];1.36 + lxdream_file_type_t result = FILE_UNKNOWN;1.37 + gboolean mustClose = FALSE;1.38 + off_t posn;1.40 - /* begin magic */1.41 - if( read( fd, buf, 32 ) != 32 ) {1.42 - SET_ERROR( err, errno, "Unable to read from file '%s'", filename );1.43 - return FILE_ERROR;1.44 -1.45 + if( fd == -1 ) {1.46 + fd = open( filename, O_RDONLY );1.47 + if( fd == -1 ) {1.48 + SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s' (%s)" ,filename, strerror(errno) );1.49 + return FILE_ERROR;1.50 + }1.51 + mustClose = TRUE;1.52 + } else {1.53 + /* Save current file position */1.54 + posn = lseek(fd, 0, SEEK_CUR);1.55 + if( posn == -1 ) {1.56 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "Unable to read from file '%s' (%s)", filename, strerror(errno) );1.57 + return FILE_ERROR;1.58 + }1.59 }1.61 - lseek( fd, 0, SEEK_SET );1.62 - if( buf[0] == 0x7F && buf[1] == 'E' &&1.63 + int status = read(fd, buf, 32);1.64 + if( status == -1 ) {1.65 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "Unable to read from file '%s' (%s)", filename, strerror(errno) );1.66 + result = FILE_ERROR;1.67 + } else if( status != 32 ) {1.68 + result = FILE_UNKNOWN;1.69 + } else if( buf[0] == 0x7F && buf[1] == 'E' &&1.70 buf[2] == 'L' && buf[3] == 'F' ) {1.71 - return FILE_ELF;1.72 + result = FILE_ELF;1.73 } else if( memcmp( buf, "PK\x03\x04", 4 ) == 0 ) {1.74 - return FILE_ZIP;1.75 + result = FILE_ZIP;1.76 } else if( memcmp( buf, DREAMCAST_SAVE_MAGIC, 16 ) == 0 ) {1.77 - return FILE_SAVE_STATE;1.78 + result = FILE_SAVE_STATE;1.79 } else if( lseek( fd, 32768, SEEK_SET ) == 32768 &&1.80 read( fd, buf, 8 ) == 8 &&1.81 memcmp( buf, iso_magic, 6) == 0 ) {1.82 - lseek( fd, 0, SEEK_SET );1.83 - return FILE_ISO;1.84 + result = FILE_ISO;1.85 + } else {1.86 + /* Check the file extension - .bin = sh4 binary */1.87 + int len = strlen(filename);1.88 + struct stat st;1.89 +1.90 + if( len > 4 && strcasecmp(filename + (len-4), ".bin") == 0 &&1.91 + fstat(fd, &st) != -1 && st.st_size <= BINARY_MAX_SIZE ) {1.92 + result = FILE_BINARY;1.93 + }1.94 }1.95 - lseek( fd, 0, SEEK_SET );1.96 - return FILE_BINARY;1.97 +1.98 + if( mustClose ) {1.99 + close(fd);1.100 + } else {1.101 + lseek( fd, posn, SEEK_SET );1.102 + }1.103 + return result;1.104 }1.107 -gboolean file_load_magic( const gchar *filename )1.108 +gboolean file_load_exec( const gchar *filename, ERROR *err )1.109 {1.110 - char buf[32];1.111 - struct stat st;1.112 gboolean result = TRUE;1.114 int fd = open( filename, O_RDONLY );1.115 if( fd == -1 ) {1.116 + SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s' (%s)" ,filename, strerror(errno) );1.117 return FALSE;1.118 }1.120 - fstat( fd, &st );1.121 + lxdream_file_type_t type = file_identify(filename, fd, err);1.122 + switch( type ) {1.123 + case FILE_ERROR:1.124 + result = FALSE;1.125 + break;1.126 + case FILE_ELF:1.127 + result = file_load_elf( filename, fd, err );1.128 + break;1.129 + case FILE_BINARY:1.130 + result = file_load_binary( filename, fd, err );1.131 + break;1.132 + default:1.133 + SET_ERROR( err, LX_ERR_FILE_UNKNOWN, "File '%s' could not be recognized as an executable binary", filename );1.134 + result = FALSE;1.135 + break;1.136 + }1.138 - /* begin magic */1.139 - if( read( fd, buf, 32 ) != 32 ) {1.140 - ERROR( "Unable to read from file '%s'", filename );1.141 - close(fd);1.142 - return FALSE;1.143 + close(fd);1.144 + return result;1.145 +}1.146 +1.147 +lxdream_file_type_t file_load_magic( const gchar *filename, gboolean wrap_exec, ERROR *err )1.148 +{1.149 + gboolean result;1.150 + /* Try disc types first */1.151 + cdrom_disc_t disc = cdrom_disc_open( filename, err );1.152 + if( disc != NULL ) {1.153 + gdrom_mount_disc(disc);1.154 + return FILE_DISC;1.155 + } else if( err != LX_ERR_FILE_UNKNOWN ) {1.156 + return FILE_ERROR;1.157 }1.158 - if( memcmp( buf, bootstrap_magic, 32 ) == 0 ) {1.159 - /* we have a DC bootstrap */1.160 - if( st.st_size == BOOTSTRAP_SIZE ) {1.161 - sh4ptr_t load = mem_get_region( BOOTSTRAP_LOAD_ADDR );1.162 - lseek( fd, 0, SEEK_SET );1.163 - read( fd, load, BOOTSTRAP_SIZE );1.164 - bootstrap_dump( load, TRUE );1.165 - dreamcast_program_loaded( filename, BOOTSTRAP_LOAD_ADDR + 0x300 );1.166 +1.167 + int fd = open( filename, O_RDONLY );1.168 + if( fd == -1 ) {1.169 + SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s' (%s)" ,filename, strerror(errno) );1.170 + return FILE_ERROR;1.171 + }1.172 +1.173 + lxdream_file_type_t type = file_identify(filename, fd, err);1.174 + switch( type ) {1.175 + case FILE_ERROR:1.176 + result = FALSE;1.177 + break;1.178 + case FILE_ELF:1.179 + if( wrap_exec ) {1.180 + disc = cdrom_wrap_elf( CDROM_DISC_XA, filename, fd, err );1.181 + result = disc != NULL;1.182 + if( disc != NULL ) {1.183 + gdrom_mount_disc(disc);1.184 + }1.185 } else {1.186 - /* look for a valid ISO9660 header */1.187 - lseek( fd, 32768, SEEK_SET );1.188 - read( fd, buf, 8 );1.189 - if( memcmp( buf, iso_magic, 6 ) == 0 ) {1.190 - /* Alright, got it */1.191 - WARN( "ISO images not supported yet" );1.192 + result = file_load_elf( filename, fd, err );1.193 + }1.194 + break;1.195 + case FILE_BINARY:1.196 + if( wrap_exec ) {1.197 + disc = cdrom_wrap_binary( CDROM_DISC_XA, filename, fd, err );1.198 + result = disc != NULL;1.199 + if( disc != NULL ) {1.200 + gdrom_mount_disc(disc);1.201 }1.202 + } else {1.203 + result = file_load_binary( filename, fd, err );1.204 }1.205 - } else if( memcmp( buf, "PK\x03\x04", 4 ) == 0 ) {1.206 - /* ZIP file, aka SBI file */1.207 - WARN( "SBI files not supported yet" );1.208 + break;1.209 + case FILE_SAVE_STATE:1.210 + result = dreamcast_load_state( filename );1.211 + break;1.212 + case FILE_ZIP:1.213 + SET_ERROR( err, LX_ERR_FILE_UNSUP, "ZIP/SBI not currently supported" );1.214 result = FALSE;1.215 - } else if( memcmp( buf, DREAMCAST_SAVE_MAGIC, 16 ) == 0 ) {1.216 - /* Save state */1.217 - result = (dreamcast_load_state( filename )==0);1.218 - } else if( buf[0] == 0x7F && buf[1] == 'E' &&1.219 - buf[2] == 'L' && buf[3] == 'F' ) {1.220 - /* ELF binary */1.221 - lseek( fd, 0, SEEK_SET );1.222 - result = file_load_elf_fd( filename, fd );1.223 - } else {1.224 + break;1.225 + case FILE_ISO:1.226 + SET_ERROR( err, LX_ERR_FILE_UNSUP, "ISO files are not currently supported" );1.227 result = FALSE;1.228 + break;1.229 + default:1.230 + SET_ERROR( err, LX_ERR_FILE_UNKNOWN, "File '%s' could not be recognized", filename );1.231 + result = FALSE;1.232 + break;1.233 }1.234 close(fd);1.235 - return result;1.236 + if( result ) {1.237 + CLEAR_ERROR(err);1.238 + return type;1.239 + }1.240 + return FILE_ERROR;1.241 }1.243 void file_load_postload( const gchar *filename, int pc )1.244 @@ -156,18 +227,7 @@1.245 }1.248 -gboolean file_load_binary( const gchar *filename )1.249 -{1.250 - /* Load the binary itself */1.251 - if( mem_load_block( filename, BINARY_LOAD_ADDR, -1 ) == 0 ) {1.252 - file_load_postload( filename, BINARY_LOAD_ADDR );1.253 - return TRUE;1.254 - } else {1.255 - return FALSE;1.256 - }1.257 -}1.258 -1.259 -gboolean is_sh4_elf( Elf32_Ehdr *head )1.260 +static gboolean is_sh4_elf( Elf32_Ehdr *head )1.261 {1.262 return ( head->e_ident[EI_CLASS] == ELFCLASS32 &&1.263 head->e_ident[EI_DATA] == ELFDATA2LSB &&1.264 @@ -177,7 +237,7 @@1.265 head->e_version == 1 );1.266 }1.268 -gboolean is_arm_elf( Elf32_Ehdr *head )1.269 +static gboolean is_arm_elf( Elf32_Ehdr *head )1.270 {1.271 return ( head->e_ident[EI_CLASS] == ELFCLASS32 &&1.272 head->e_ident[EI_DATA] == ELFDATA2LSB &&1.273 @@ -187,7 +247,7 @@1.274 head->e_version == 1 );1.275 }1.277 -gboolean file_load_elf_fd( const gchar *filename, int fd )1.278 +static gboolean file_load_elf( const gchar *filename, int fd, ERROR *err )1.279 {1.280 Elf32_Ehdr head;1.281 Elf32_Phdr phdr;1.282 @@ -196,7 +256,7 @@1.283 if( read( fd, &head, sizeof(head) ) != sizeof(head) )1.284 return FALSE;1.285 if( !is_sh4_elf(&head) ) {1.286 - ERROR( "File is not an SH4 ELF executable file" );1.287 + SET_ERROR( err, LX_ERR_FILE_INVALID, "File is not an SH4 ELF executable file" );1.288 return FALSE;1.289 }1.291 @@ -211,7 +271,6 @@1.292 if( phdr.p_memsz > phdr.p_filesz ) {1.293 memset( target + phdr.p_filesz, 0, phdr.p_memsz - phdr.p_filesz );1.294 }1.295 - INFO( "Loaded %d bytes to %08X", phdr.p_filesz, phdr.p_vaddr );1.296 }1.297 }1.299 @@ -219,6 +278,30 @@1.300 return TRUE;1.301 }1.303 +static gboolean file_load_binary( const gchar *filename, int fd, ERROR *err )1.304 +{1.305 + struct stat st;1.306 +1.307 + if( fstat( fd, &st ) == -1 ) {1.308 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "Error reading binary file '%s' (%s)", filename, strerror(errno) );1.309 + return FALSE;1.310 + }1.311 +1.312 + if( st.st_size > BINARY_MAX_SIZE ) {1.313 + SET_ERROR( err, LX_ERR_FILE_INVALID, "Binary file '%s' is too large to fit in memory", filename );1.314 + return FALSE;1.315 + }1.316 +1.317 + sh4ptr_t target = mem_get_region( BINARY_LOAD_ADDR );1.318 + if( read( fd, target, st.st_size ) != st.st_size ) {1.319 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "Error reading binary file '%s' (%s)", filename, strerror(errno) );1.320 + return FALSE;1.321 + }1.322 +1.323 + file_load_postload( filename, BINARY_LOAD_ADDR );1.324 + return TRUE;1.325 +}1.326 +1.327 /**1.328 * Create a new CDROM disc containing a single 1ST_READ.BIN.1.329 * @param type The disc type - must be CDROM_DISC_GDROM or CDROM_DISC_XA1.330 @@ -233,25 +316,25 @@1.331 cdrom_lba_t start_lba = 45000; /* GDROM_START */1.332 char bootstrap[32768];1.334 - /* 1. Load in the bootstrap */1.335 + /* 1. Load in the bootstrap: Note failures here are considered configuration errors */1.336 gchar *bootstrap_file = lxdream_get_global_config_path_value(CONFIG_BOOTSTRAP);1.337 if( bootstrap_file == NULL || bootstrap_file[0] == '\0' ) {1.338 g_free(data);1.339 - SET_ERROR( err, ENOENT, "Unable to create CD image: bootstrap file is not configured" );1.340 + SET_ERROR( err, LX_ERR_CONFIG, "Unable to create CD image: bootstrap file is not configured" );1.341 return NULL;1.342 }1.344 FILE *f = fopen( bootstrap_file, "ro" );1.345 if( f == NULL ) {1.346 g_free(data);1.347 - SET_ERROR( err, errno, "Unable to create CD image: bootstrap file '%s' could not be opened", bootstrap_file );1.348 + SET_ERROR( err, LX_ERR_CONFIG, "Unable to create CD image: bootstrap file '%s' could not be opened", bootstrap_file );1.349 return FALSE;1.350 }1.351 size_t len = fread( bootstrap, 1, 32768, f );1.352 fclose(f);1.353 if( len != 32768 ) {1.354 g_free(data);1.355 - SET_ERROR( err, EINVAL, "Unable to create CD image: bootstrap file '%s' is invalid", bootstrap_file );1.356 + SET_ERROR( err, LX_ERR_CONFIG, "Unable to create CD image: bootstrap file '%s' is invalid", bootstrap_file );1.357 return FALSE;1.358 }1.360 @@ -280,6 +363,7 @@1.361 int status = iso_image_new("autocd", &iso);1.362 if( status != 1 ) {1.363 g_free(data);1.364 + SET_ERROR( err, LX_ERR_NOMEM, "Unable to create CD image: out of memory" );1.365 return NULL;1.366 }1.368 @@ -287,6 +371,7 @@1.369 if( iso_memory_stream_new(data, bin_size, &stream) != 1 ) {1.370 g_free(data);1.371 iso_image_unref(iso);1.372 + SET_ERROR( err, LX_ERR_NOMEM, "Unable to create CD image: out of memory" );1.373 return NULL;1.374 }1.375 iso_tree_add_new_file(iso_image_get_root(iso), "1ST_READ.BIN", stream, NULL);1.376 @@ -297,15 +382,15 @@1.377 return NULL;1.378 }1.380 - cdrom_disc_t disc = cdrom_disc_new_from_track( type, track, start_lba );1.381 + cdrom_disc_t disc = cdrom_disc_new_from_track( type, track, start_lba, err );1.382 iso_image_unref(iso);1.383 if( disc != NULL ) {1.384 disc->name = g_strdup(filename);1.385 - }1.386 + }1.387 return disc;1.388 }1.390 -cdrom_disc_t cdrom_wrap_elf_fd( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err )1.391 +static cdrom_disc_t cdrom_wrap_elf( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err )1.392 {1.393 Elf32_Ehdr head;1.394 int i;1.395 @@ -313,12 +398,13 @@1.396 /* Check the file header is actually an SH4 binary */1.397 if( read( fd, &head, sizeof(head) ) != sizeof(head) )1.398 return FALSE;1.399 +1.400 if( !is_sh4_elf(&head) ) {1.401 - SET_ERROR( err, EINVAL, "File is not an SH4 ELF executable file" );1.402 + SET_ERROR( err, LX_ERR_FILE_INVALID, "File is not an SH4 ELF executable file" );1.403 return FALSE;1.404 }1.405 if( head.e_entry != BINARY_LOAD_ADDR ) {1.406 - SET_ERROR( err, EINVAL, "SH4 Binary has incorrect entry point (should be %08X but is %08X)", BINARY_LOAD_ADDR, head.e_entry );1.407 + 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 );1.408 return FALSE;1.409 }1.411 @@ -326,7 +412,7 @@1.412 Elf32_Phdr phdr[head.e_phnum];1.413 lseek( fd, head.e_phoff, SEEK_SET );1.414 if( read( fd, phdr, sizeof(phdr) ) != sizeof(phdr) ) {1.415 - SET_ERROR( err, EINVAL, "File is not a valid executable file" );1.416 + SET_ERROR( err, LX_ERR_FILE_INVALID, "File is not a valid executable file" );1.417 return FALSE;1.418 }1.420 @@ -342,11 +428,11 @@1.421 }1.423 if( start != BINARY_LOAD_ADDR ) {1.424 - SET_ERROR( err, EINVAL, "SH4 Binary has incorrect load address (should be %08X but is %08X)", BINARY_LOAD_ADDR, start );1.425 + SET_ERROR( err, LX_ERR_FILE_INVALID, "SH4 Binary has incorrect load address (should be %08X but is %08X)", BINARY_LOAD_ADDR, start );1.426 return FALSE;1.427 }1.428 if( end >= 0x8D000000 ) {1.429 - SET_ERROR( err, EINVAL, "SH4 binary is too large to fit in memory (end address is %08X)", end );1.430 + SET_ERROR( err, LX_ERR_FILE_INVALID, "SH4 binary is too large to fit in memory (end address is %08X)", end );1.431 return FALSE;1.432 }1.434 @@ -356,7 +442,12 @@1.435 if( phdr[i].p_type == PT_LOAD ) {1.436 lseek( fd, phdr[i].p_offset, SEEK_SET );1.437 uint32_t size = MIN( phdr[i].p_filesz, phdr[i].p_memsz);1.438 - read( fd, program + phdr[i].p_vaddr, size );1.439 + int status = read( fd, program + phdr[i].p_vaddr, size );1.440 + if( status == -1 ) {1.441 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "I/O error reading SH4 binary %s (%s)", filename, strerror(errno) );1.442 + } else if( status != size ) {1.443 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "SH4 binary %s is corrupt", filename );1.444 + }1.445 }1.446 }1.448 @@ -364,39 +455,52 @@1.449 return cdrom_disc_new_wrapped_binary(type, filename, program, end-start, err );1.450 }1.452 -cdrom_disc_t cdrom_wrap_magic( cdrom_disc_type_t type, const gchar *filename, ERROR *err )1.453 +static cdrom_disc_t cdrom_wrap_binary( cdrom_disc_type_t type, const gchar *filename, int fd, ERROR *err )1.454 {1.455 - cdrom_disc_t disc;1.456 + struct stat st;1.457 char *data;1.458 - int len;1.459 - struct stat st;1.460 - int fd = open( filename, O_RDONLY );1.461 - if( fd == -1 ) {1.462 - SET_ERROR( err, errno, "Unable to open file '%s'", filename );1.463 + size_t len;1.464 +1.465 + if( fstat(fd, &st) == -1 ) {1.466 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "Error reading binary file '%s' (%s)", filename, strerror(errno) );1.467 return NULL;1.468 }1.470 -1.471 - lxdream_file_type_t filetype = file_magic( filename, fd, err );1.472 - switch( filetype ) {1.473 - case FILE_BINARY:1.474 - fstat( fd, &st );1.475 - data = g_malloc(st.st_size);1.476 - len = read( fd, data, st.st_size );1.477 - close(fd);1.478 - if( len != st.st_size ) {1.479 - SET_ERROR( err, errno, "Error reading binary file '%s'", filename );1.480 - return NULL;1.481 - }1.482 - return cdrom_disc_new_wrapped_binary( type, filename, data, st.st_size, err );1.483 - case FILE_ELF:1.484 - disc = cdrom_wrap_elf_fd(type, filename, fd, err);1.485 - close(fd);1.486 - return disc;1.487 - default:1.488 - close(fd);1.489 - SET_ERROR( err, EINVAL, "File '%s' cannot be wrapped (not a binary)", filename );1.490 + data = g_malloc(st.st_size);1.491 + len = read( fd, data, st.st_size );1.492 + if( len != st.st_size ) {1.493 + SET_ERROR( err, LX_ERR_FILE_IOERROR, "Error reading binary file '%s' (%s)", filename, strerror(errno) );1.494 + free(data);1.495 return NULL;1.496 }1.498 + return cdrom_disc_new_wrapped_binary( type, filename, data, st.st_size, err );1.499 }1.500 +1.501 +cdrom_disc_t cdrom_wrap_magic( cdrom_disc_type_t type, const gchar *filename, ERROR *err )1.502 +{1.503 + cdrom_disc_t disc = NULL;1.504 +1.505 + int fd = open( filename, O_RDONLY );1.506 + if( fd == -1 ) {1.507 + SET_ERROR( err, LX_ERR_FILE_NOOPEN, "Unable to open file '%s'", filename );1.508 + return NULL;1.509 + }1.510 +1.511 + lxdream_file_type_t filetype = file_identify( filename, fd, err );1.512 + switch( filetype ) {1.513 + case FILE_ELF:1.514 + disc = cdrom_wrap_elf(type, filename, fd, err);1.515 + break;1.516 + case FILE_BINARY:1.517 + disc = cdrom_wrap_binary(type, filename, fd, err);1.518 + break;1.519 + default:1.520 + SET_ERROR( err, LX_ERR_FILE_UNKNOWN, "File '%s' cannot be wrapped (not a recognized binary)", filename );1.521 + break;1.522 + }1.523 +1.524 + close(fd);1.525 + return disc;1.526 +1.527 +}
.