/* trdos.c: Routines for handling the Betadisk interface Copyright (c) 2002-2004 Dmitry Sanarin, Fredrick Meunier, Philip Kendall $Id: trdos.c,v 1.37 2007/02/02 16:21:52 pak21 Exp $ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: Philip: philip-fuse@shadowmagic.org.uk Dmitry: sdb386@hotmail.com Fred: fredm@spamcop.net */ #include #include #include #include #include #include #include #ifdef HAVE_STRINGS_H #include /* Needed for strncasecmp() on QNX6 */ #endif /* #ifdef HAVE_STRINGS_H */ #include #include #include #include "compat.h" #include "event.h" #include "machine.h" #include "memory.h" #include "settings.h" #include "spectrum.h" #include "trdos.h" #include "ui/ui.h" #include "utils.h" #include "z80/z80.h" #include "z80/z80_macros.h" #define TRDOS_DISC_SIZE 655360 typedef struct { int disc_ready; /* Is this disk present? */ char filename[PATH_MAX]; /* The filename we used to open this disk */ int fd; /* The fd will we use to access this disk */ int ro; /* True if we have read-only access to this disk */ int dirty; /* Has this disk been written to? */ libspectrum_byte trk; } discs_type; #ifdef WORDS_BIGENDIAN typedef struct { unsigned b7 : 1; /* This bit reflects the status of the Motor On output */ unsigned b6 : 1; /* disk is write-protected */ unsigned b5 : 1; /* When set, this bit indicates that the Motor Spin-Up sequence has completed (6 revolutions) on type 1 commands. Type 2 & 3 commands, this bit indicates record Type. 0 = Data Mark, 1 = Deleted Data Mark. */ unsigned b4 : 1; /* When set, it indicates that the desired track, sector, or side were not found. This bit is reset when updated. */ unsigned b3 : 1; /* If this is set, an error is found in one or more ID fields; otherwise it indicates error in data field. This bit is reset when updated. */ unsigned b2 : 1; /* When set, it indicates the computer did not respond to DRQ in one byte time. This bit is reset to zero when update. On type 1 commands, this bit reflects the status of the TRACK 00 pin. */ unsigned b1 : 1; /* This bit is a copy of the DRQ output. When set, it indicates the DR is full on a Read Operation or the DR is empty on a write operation. This bit is reset to zero when updated. On type 1 commands, this bit indicates the status of the index pin. */ unsigned b0 : 1; /* When set, command is under execution. When reset, no command is under execution. */ } rs_type; #else /* #ifdef WORDS_BIGENDIAN */ typedef struct { unsigned b0 : 1; /* When set, command is under execution. When reset, no command is under execution. */ unsigned b1 : 1; /* This bit is a copy of the DRQ output. When set, it indicates the DR is full on a Read Operation or the DR is empty on a write operation. This bit is reset to zero when updated. On type 1 commands, this bit indicates the status of the index pin. */ unsigned b2 : 1; /* When set, it indicates the computer did not respond to DRQ in one byte time. This bit is reset to zero when update. On type 1 commands, this bit reflects the status of the TRACK 00 pin. */ unsigned b3 : 1; /* If this is set, an error is found in one or more ID fields; otherwise it indicates error in data field. This bit is reset when updated. */ unsigned b4 : 1; /* When set, it indicates that the desired track, sector, or side were not found. This bit is reset when updated. */ unsigned b5 : 1; /* When set, this bit indicates that the Motor Spin-Up sequence has completed (6 revolutions) on type 1 commands. Type 2 & 3 commands, this bit indicates record Type. 0 = Data Mark, 1 = Deleted Data Mark. */ unsigned b6 : 1; /* disk is write-protected */ unsigned b7 : 1; /* This bit reflects the status of the Motor On output */ } rs_type; #endif /* #ifdef WORDS_BIGENDIAN */ # define CurrentDiscNum ( trdos_system_register & 3 ) # define CurrentDisk discs[CurrentDiscNum] static int busy = 0; static int index_impulse = 0; static int towrite = 0; static int towriteaddr; static int pointer; static int side; static libspectrum_byte track[ TRDOS_DISC_SIZE ]; static libspectrum_byte *toread; static unsigned int toread_num = 0; static unsigned int toread_position = 0; static libspectrum_byte six_bytes[6]; static int vg_spin; static int trdos_direction; /* 0 - spindlewards 1 - rimwards */ static libspectrum_byte trdos_status_register; /* Betadisk status register */ static libspectrum_byte trdos_track_register; /* FDC track register */ static libspectrum_byte trdos_sector_register; /* FDC sector register */ static libspectrum_byte trdos_data_register; /* FDC data register */ static libspectrum_byte vg_portFF_in; static libspectrum_byte trdos_system_register; /* FDC system register */ union { libspectrum_byte byte; rs_type bit; } vg_rs; static discs_type discs[4]; int trdos_available = 0; int trdos_active=0; /* The template used for naming the results of the SCL->TRD conversion */ #define SCL_TMP_FILE_TEMPLATE "fuse.scl.XXXXXX" /* The template used for the temporary copy of a .trd file */ static const char *trd_template = "fuse.trd.XXXXXX"; static int Scl2Trd( const char *oldname, int TRD ); static int insert_scl( trdos_drive_number which, const char *filename ); static int insert_trd( trdos_drive_number which, const char *filename ); static libspectrum_dword lsb2dw( libspectrum_byte *mem ); static void dw2lsb( libspectrum_byte *mem, libspectrum_dword value ); int trdos_init( void ) { discs[0].disc_ready = 0; discs[1].disc_ready = 0; discs[2].disc_ready = 0; discs[3].disc_ready = 0; return 0; } void trdos_reset( void ) { trdos_active = 0; busy = 0; trdos_event_index( 0 ); /* We can eject disks only if they are currently present */ ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_TRDOS_A_EJECT, discs[ TRDOS_DRIVE_A ].disc_ready ); ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_TRDOS_B_EJECT, discs[ TRDOS_DRIVE_B ].disc_ready ); ui_statusbar_update( UI_STATUSBAR_ITEM_DISK, UI_STATUSBAR_STATE_INACTIVE ); } void trdos_end( void ) { trdos_available = 0; } void trdos_page( void ) { trdos_active = 1; machine_current->ram.romcs = 1; machine_current->memory_map(); } void trdos_unpage( void ) { trdos_active = 0; machine_current->ram.romcs = 0; machine_current->memory_map(); } void trdos_memory_map( void ) { memory_map_read[0] = memory_map_write[0] = memory_map_romcs[0]; memory_map_read[1] = memory_map_write[1] = memory_map_romcs[1]; } int trdos_from_snapshot( libspectrum_snap *snap, int capabilities ) { if( !( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK ) ) return 0; trdos_active = libspectrum_snap_beta_paged( snap ); if( trdos_active ) { trdos_page(); } else { trdos_unpage(); } trdos_direction = libspectrum_snap_beta_direction( snap ); trdos_cr_write ( 0x001f, libspectrum_snap_beta_status( snap ) ); trdos_tr_write ( 0x003f, libspectrum_snap_beta_track ( snap ) ); trdos_sec_write( 0x005f, libspectrum_snap_beta_sector( snap ) ); trdos_dr_write ( 0x007f, libspectrum_snap_beta_data ( snap ) ); trdos_sp_write ( 0x00ff, libspectrum_snap_beta_system( snap ) ); return 0; } int trdos_to_snapshot( libspectrum_snap *snap ) { libspectrum_snap_set_beta_paged( snap, trdos_active ); libspectrum_snap_set_beta_direction( snap, trdos_direction ); libspectrum_snap_set_beta_status( snap, trdos_status_register ); libspectrum_snap_set_beta_track ( snap, trdos_system_register ); libspectrum_snap_set_beta_sector( snap, trdos_track_register ); libspectrum_snap_set_beta_data ( snap, trdos_sector_register ); libspectrum_snap_set_beta_system( snap, trdos_data_register ); return 0; } static void trdos_update_index_impulse( void ) { if ( vg_spin ) { vg_rs.bit.b1 = 0; if ( CurrentDisk.disc_ready && index_impulse ) { vg_rs.bit.b1 = 1; } } ui_statusbar_update( UI_STATUSBAR_ITEM_DISK, busy ? UI_STATUSBAR_STATE_ACTIVE : UI_STATUSBAR_STATE_INACTIVE ); } libspectrum_byte trdos_sr_read( libspectrum_word port GCC_UNUSED, int *attached ) { if( !trdos_active ) return 0xff; *attached = 1; trdos_update_index_impulse(); if ( !CurrentDisk.disc_ready ) { return( 0x80 ); /* No disk in drive */ } return( vg_rs.byte ); } libspectrum_byte trdos_tr_read( libspectrum_word port GCC_UNUSED, int *attached ) { if( !trdos_active ) return 0xff; *attached = 1; trdos_update_index_impulse(); return trdos_track_register; } void trdos_tr_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { if( !trdos_active ) return; trdos_track_register = b; } libspectrum_byte trdos_sec_read( libspectrum_word port GCC_UNUSED, int *attached ) { if( !trdos_active ) return 0xff; *attached = 1; trdos_update_index_impulse(); return trdos_sector_register; } void trdos_sec_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { if( !trdos_active ) return; trdos_sector_register = b; } libspectrum_byte trdos_dr_read( libspectrum_word port GCC_UNUSED, int *attached ) { if( !trdos_active ) return 0xff; *attached = 1; trdos_update_index_impulse(); if( toread_position >= toread_num ) return trdos_data_register; trdos_data_register = toread[toread_position]; if ( ( ( toread_position & 0x00ff ) == 0 ) && toread_position != 0 ) trdos_sector_register++; if( trdos_sector_register > 16 ) trdos_sector_register = 1; toread_position++; if ( toread_position == toread_num ) { vg_portFF_in = 0x80; vg_rs.byte = 0; } else { vg_portFF_in = 0x40; vg_rs.bit.b0 = 1; /* Busy */ vg_rs.bit.b1 = 1; /* DRQ copy */ } return trdos_data_register; } void trdos_dr_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { if( !trdos_active ) return; trdos_data_register = b; if( towrite == 0 ) return; if( lseek( CurrentDisk.fd, towriteaddr, SEEK_SET ) == -1 ) { towrite = 0; ui_error( UI_ERROR_ERROR, "trdos_dr_write: seeking in '%s' failed: %s", CurrentDisk.filename, strerror( errno ) ); return; } write( CurrentDisk.fd, &b, 1 ); CurrentDisk.dirty = 1; towrite--; towriteaddr++; if( towrite == 0 ) { vg_portFF_in = 0x80; vg_rs.byte = 0; } else { vg_portFF_in = 0x40; } } libspectrum_byte trdos_sp_read( libspectrum_word port GCC_UNUSED, int *attached ) { if( !trdos_active ) return 0xff; *attached = 1; trdos_update_index_impulse(); if ( busy == 1 ) { return( vg_portFF_in &~ 0x40 ); } return( vg_portFF_in ); } void trdos_sp_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { if( !trdos_active ) return; trdos_system_register = b; } static int insert_trd( trdos_drive_number which, const char *filename ) { int fd, error; char tempfilename[ PATH_MAX ]; /* Make a temporary copy of the disk */ error = utils_make_temp_file( &fd, tempfilename, filename, trd_template ); if( error ) return error; unlink( tempfilename ); discs[which].ro = 0; discs[which].fd = fd; strcpy( discs[which].filename, filename ); discs[which].disc_ready = 1; return 0; } static int insert_scl( trdos_drive_number which, const char *filename ) { const char *temp_path; size_t length; char* trd_template; int ret; temp_path = utils_get_temp_path(); /* +2 is for the slash between the path and the template and for the null at the end */ length = strlen( temp_path ) + strlen( SCL_TMP_FILE_TEMPLATE ) + 2; trd_template = malloc( length ); if( !trd_template ) { ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } snprintf( trd_template, length, "%s/%s", temp_path, SCL_TMP_FILE_TEMPLATE ); discs[ which ].disc_ready = 0; discs[ which ].fd = mkstemp( trd_template ); if( discs[ which ].fd == -1 ) { ui_error( UI_ERROR_ERROR, "couldn't get a temporary filename: %s", strerror( errno ) ); free( trd_template ); return 1; } /* Unlink the file so it will be removed when the fd is closed */ unlink( trd_template ); if( ( ret = Scl2Trd( filename, discs[ which ].fd ) ) ) { close( discs[ which ].fd ); free( trd_template ); return ret; } strcpy( discs[which].filename, trd_template ); discs[which].disc_ready = 1; discs[which].ro = 1; free( trd_template ); return 0; } int trdos_disk_insert_default_autoload( trdos_drive_number which, const char *filename ) { return trdos_disk_insert( which, filename, settings_current.auto_load ); } int trdos_disk_insert( trdos_drive_number which, const char *filename, int autoload ) { int error; utils_file file; libspectrum_id_t type; libspectrum_class_t class; if( discs[ which ].disc_ready ) { if( trdos_disk_eject( which, 0 ) ) return 1; } if( utils_read_file( filename, &file ) ) return 1; if( libspectrum_identify_file_with_class( &type, &class, filename, file.buffer, file.length ) ) { utils_close_file( &file ); return 1; } if( class != LIBSPECTRUM_CLASS_DISK_TRDOS ) { ui_error( UI_ERROR_ERROR, "trdos_disk_insert: file `%s' is not a TR-DOS disk", filename ); return 1; } if( utils_close_file( &file ) ) return 1; switch( type ) { case LIBSPECTRUM_ID_DISK_SCL: error = insert_scl( which, filename ); break; case LIBSPECTRUM_ID_DISK_TRD: error = insert_trd( which, filename ); break; default: ui_error( UI_ERROR_ERROR, "trdos_disk_insert: file `%s' is an unsupported TR-DOS disk", filename ); error = 1; } discs[ which ].dirty = 0; if( error ) return error; /* Set the `eject' item active */ ui_menu_activate( which == TRDOS_DRIVE_A ? UI_MENU_ITEM_MEDIA_DISK_TRDOS_A_EJECT : UI_MENU_ITEM_MEDIA_DISK_TRDOS_B_EJECT , 1 ); if( autoload ) { PC = 0; machine_current->ram.last_byte |= 0x10; /* Select ROM 1 */ trdos_page(); } return 0; } int trdos_disk_eject( trdos_drive_number which, int write ) { int error; if( write ) { if( ui_trdos_disk_write( which ) ) return 1; } else { if( discs[ which ].dirty ) { ui_confirm_save_t confirm = ui_confirm_save( "Disk has been modified.\nDo you want to save it?" ); switch( confirm ) { case UI_CONFIRM_SAVE_SAVE: if( ui_trdos_disk_write( which ) ) return 1; break; case UI_CONFIRM_SAVE_DONTSAVE: error = close( discs[ which ].fd ); if( error ) { ui_error( UI_ERROR_ERROR, "Error closing '%s': %s", discs[which].filename, strerror( errno ) ); return 1; } discs[ which ].fd = -1; break; case UI_CONFIRM_SAVE_CANCEL: return 1; } } } discs[which].disc_ready = 0; /* Set the `eject' item inactive */ ui_menu_activate( which == TRDOS_DRIVE_A ? UI_MENU_ITEM_MEDIA_DISK_TRDOS_A_EJECT : UI_MENU_ITEM_MEDIA_DISK_TRDOS_B_EJECT , 0 ); return 0; } int trdos_disk_write( trdos_drive_number which, const char *filename ) { FILE *f; utils_file file; int error; ssize_t bytes_written; f = fopen( filename, "wb" ); if( !f ) { ui_error( UI_ERROR_ERROR, "couldn't open '%s' for writing: %s", filename, strerror( errno ) ); } error = utils_read_fd( discs[ which ].fd, discs[ which ].filename, &file ); if( error ) { fclose( f ); return error; } bytes_written = fwrite( file.buffer, 1, file.length, f ); if( bytes_written != file.length ) { ui_error( UI_ERROR_ERROR, "could write only %lu of %lu bytes to '%s'", (unsigned long)bytes_written, (unsigned long)file.length, filename ); utils_close_file( &file ); fclose( f ); } error = utils_close_file( &file ); if( error ) { fclose( f ); return error; } if( fclose( f ) ) { ui_error( UI_ERROR_ERROR, "error closing '%s': %s", filename, strerror( errno ) ); return 1; } return 0; } int trdos_event_cmd_done( libspectrum_dword last_tstates GCC_UNUSED ) { busy = 0; return 0; } int trdos_event_index( libspectrum_dword last_tstates ) { int error; int next_tstates; static int num_calls = 0; if( num_calls == 0 ) { /* schedule next call in 20ms */ next_tstates = 20 * machine_current->timings.processor_speed / 1000; index_impulse = 1; num_calls = 1; } else { /* schedule next call in 180ms */ next_tstates = 180 * machine_current->timings.processor_speed / 1000; index_impulse = 0; num_calls = 0; } error = event_add( last_tstates + next_tstates, EVENT_TYPE_TRDOS_INDEX ); if( error ) return error; return 0; } static void vg_seek_delay( libspectrum_byte dst_track ) { int error; if( ( dst_track - CurrentDisk.trk ) > 0 ) trdos_direction = 1; if( ( dst_track - CurrentDisk.trk ) < 0 ) trdos_direction = 0; busy = 1; /* FIXME: is this code really what was meant? */ if ( !CurrentDisk.disc_ready ) vg_portFF_in = 0x80; else vg_portFF_in = 0x80; CurrentDisk.trk = dst_track; trdos_track_register = dst_track; /* schedule event */ error = event_add( tstates + machine_current->timings.processor_speed / 1000 * 20 * abs( dst_track - CurrentDisk.trk ), EVENT_TYPE_TRDOS_CMD_DONE ); } static void vg_setFlagsSeeks( void ) { vg_rs.bit.b0 = 0; if ( CurrentDisk.trk == 0 ) vg_rs.bit.b2 = 1; else vg_rs.bit.b2 = 0; vg_rs.bit.b3 = 0; vg_rs.bit.b4 = 0; vg_rs.bit.b6 = CurrentDisk.ro; vg_rs.bit.b7 = CurrentDisk.disc_ready; } void trdos_cr_write( libspectrum_word port GCC_UNUSED, libspectrum_byte b ) { int error; if( !trdos_active ) return; trdos_status_register = b; side = trdos_system_register & 0x10 ? 0 : 1; if ( (b & 0xF0) == 0xD0 ) { /* interrupt */ vg_portFF_in = 0x80; vg_setFlagsSeeks(); return; } if ( (b & 0xF0) == 0x00 ) { /* seek trk0 AKA Initialisation */ vg_seek_delay( 0 ); vg_rs.bit.b5 = b & 8 ? 1 : 0; vg_setFlagsSeeks(); if ( b & 8 ) vg_spin = 1; return; } if ( (b & 0xF0) == 0x10 ) { /* seek track */ vg_seek_delay( trdos_data_register ); vg_rs.bit.b5 = b & 8 ? 1 : 0; vg_setFlagsSeeks(); if ( b & 8 ) vg_spin = 1; return; } if ( (b & 0xE0) == 0x40 ) { /* fwd */ trdos_direction = 1; b = 0x20; /* step */ } if ( (b & 0xE0) == 0x60 ) { /* back */ trdos_direction = 0; b = 0x20; /* step */ } if ( (b & 0xE0) == 0x20 ) { /* step */ if ( trdos_direction ) vg_seek_delay( ++CurrentDisk.trk ); else vg_seek_delay( --CurrentDisk.trk ); vg_rs.bit.b5 = 1; vg_setFlagsSeeks(); vg_spin = 1; return; } if ( (b & 0xE0) == 0x80 ) { /* readsec */ vg_rs.byte = 0x81; vg_portFF_in = 0x40; vg_spin = 0; if ( !CurrentDisk.disc_ready ) { vg_rs.byte = 0x90; vg_portFF_in = 0x80; return; } if( ( trdos_sector_register == 0 ) || ( trdos_sector_register > 16 ) ) { vg_rs.byte |= 0x10; /* sector not found */ vg_portFF_in = 0x80; ui_error( UI_ERROR_ERROR, "Attempt to read TRDOS sector with sector register 0x%02x", trdos_sector_register ); return; } pointer = ( CurrentDisk.trk * 2 + side ) * 256 * 16 + ( trdos_sector_register - 1 ) * 256; if( lseek( CurrentDisk.fd, pointer, SEEK_SET) == -1 ) { ui_error( UI_ERROR_ERROR, "trdos_cr_write: seeking in '%s' failed: %s", CurrentDisk.filename, strerror( errno ) ); vg_rs.byte |= 0x10; /* sector not found */ vg_portFF_in = 0x80; return; } if ( b & 0x10 ) { ui_error( UI_ERROR_ERROR, "Unimplemented TRDOS multisector read: sector register = %d", trdos_sector_register ); read( CurrentDisk.fd, track, 256 * ( 17 - trdos_sector_register ) ); lseek( CurrentDisk.fd, /* This line used to say: -256 * ( ( 17 - vg_reg_sec ) + ( vg_reg_sec - 1 ) ), which is fairly nonsensical */ -256 * 16, SEEK_CUR ); read( CurrentDisk.fd, track, 256 * ( trdos_sector_register - 1 ) ); toread = track; toread_num = 256 * ( 16 ); toread_position = 0; /* vg_portFF_in=0x80; */ /* todo : Eto proverit' !!! */ } else { if( read( CurrentDisk.fd, track, 256 ) == 256 ) { toread = track; toread_num = 256; toread_position = 0; /* schedule event */ busy = 1; error = event_add( tstates + machine_current->timings.processor_speed / 1000 * 30, EVENT_TYPE_TRDOS_CMD_DONE ); } else { vg_rs.byte |= 0x10; /* sector not found */ vg_portFF_in = 0x80; ui_error( UI_ERROR_ERROR, "Error reading from '%s'", CurrentDisk.filename ); } } return; } if ( (b & 0xFB) == 0xC0 ) { /* read adr */ vg_rs.byte = 0x81; vg_portFF_in = 0x40; six_bytes[0] = CurrentDisk.trk; six_bytes[1] = 0; six_bytes[2] = trdos_sector_register; six_bytes[3] = 1; six_bytes[4] = 0; /* todo : crc !!! */ six_bytes[5] = 0; /* todo : crc !!! */ toread = six_bytes; toread_num = 6; toread_position = 0; /* schedule event */ busy = 1; vg_spin = 0; error = event_add( tstates + machine_current->timings.processor_speed / 1000 * 30, EVENT_TYPE_TRDOS_CMD_DONE ); return; } if ( (b & 0xE0) == 0xA0 ) { /* writesec */ vg_rs.byte = 0x81; vg_portFF_in = 0x40; vg_spin = 0; if ( CurrentDisk.ro ) { vg_rs.byte = 0x60; vg_portFF_in = 0x80; return; } if ( !CurrentDisk.disc_ready ) { vg_rs.byte = 0x90; vg_portFF_in = 0x80; ui_error( UI_ERROR_ERROR, "No disk" ); return; } if ( ( trdos_sector_register == 0 ) || ( trdos_sector_register > 16 ) ) { vg_rs.byte |= 0x10; /* sector not found */ vg_portFF_in = 0x80; ui_error( UI_ERROR_ERROR, "Attempt to write TRDOS sector with sector register 0x%02x", trdos_sector_register ); return; } towriteaddr = ( CurrentDisk.trk * 2 + side ) * 256 * 16 + ( trdos_sector_register - 1 ) * 256; towrite = 256; vg_spin = 0; return; } } #define TRD_NAMEOFFSET 0x08F5 #define TRD_DIRSTART 0x08E2 #define TRD_DIRLEN 32 #define TRD_MAXNAMELENGTH 8 #define BLOCKSIZE 10240 typedef union { #ifdef WORDS_BIGENDIAN struct { libspectrum_byte b3, b2, b1, b0; } b; #else struct { libspectrum_byte b0, b1, b2, b3; } b; #endif libspectrum_dword dword; } lsb_dword; static libspectrum_dword lsb2dw( libspectrum_byte *mem ) { return ( mem[0] + ( mem[1] * 256 ) + ( mem[2] * 256 * 256 ) + ( mem[3] * 256 * 256 * 256 ) ); } static void dw2lsb( libspectrum_byte *mem, libspectrum_dword value ) { lsb_dword ret; ret.dword = value; mem[0] = ret.b.b0; mem[1] = ret.b.b1; mem[2] = ret.b.b2; mem[3] = ret.b.b3; } static int Scl2Trd( const char *oldname, int TRD ) { int SCL, i; libspectrum_byte *TRDh; libspectrum_byte *trd_free; libspectrum_byte *trd_fsec; libspectrum_byte *trd_ftrk; libspectrum_byte *trd_files; libspectrum_byte size; char signature[8]; libspectrum_byte blocks; libspectrum_byte headers[256][14]; void *tmpscl; libspectrum_dword left; libspectrum_dword fptr; size_t x; ssize_t written; libspectrum_byte template[34] = { 0x01, 0x16, 0x00, 0xF0, 0x09, 0x10, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x64, 0x69, 0x73, 0x6B, 0x6E, 0x61, 0x6D, 0x65, 0x00, 0x00, 0x00, 0x46, 0x55 }; libspectrum_byte *mem; mem = malloc( BLOCKSIZE ); if( !mem ) { ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } memset( mem, 0, BLOCKSIZE ); memcpy( &mem[TRD_DIRSTART], template, TRD_DIRLEN ); memcpy( &mem[TRD_NAMEOFFSET], "Fuse", 5 ); written = write( TRD, mem, BLOCKSIZE ); if( written != BLOCKSIZE ) { ui_error( UI_ERROR_ERROR, "Error writing to temporary TRD file" ); free( mem ); return 1; } memset( mem, 0, BLOCKSIZE ); for( i = 0; i < 63; i++ ) { written = write( TRD, mem, BLOCKSIZE ); if( written != BLOCKSIZE ) { ui_error( UI_ERROR_ERROR, "Error writing to temporary TRD file" ); free( mem ); return 1; } } free( mem ); if( lseek( TRD, 0, SEEK_SET ) == -1 ) { ui_error( UI_ERROR_ERROR, "Error seeking in temporary TRD file" ); return 1; } TRDh = malloc( 4096 ); if( !TRDh ) { ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } if( read( TRD, TRDh, 4096 ) != 4096 ) { ui_error( UI_ERROR_ERROR, "Error reading from temporary TRD file" ); free( TRDh ); return 1; } trd_free = TRDh + 0x8E5; trd_files = TRDh + 0x8E4; trd_fsec = TRDh + 0x8E1; trd_ftrk = TRDh + 0x8E2; if( ( SCL = open( oldname, O_RDONLY | O_BINARY ) ) == -1 ) { ui_error( UI_ERROR_ERROR, "Can't open SCL file '%s': %s", oldname, strerror( errno ) ); free( TRDh ); return 1; } if( read( SCL, &signature, 8 ) != 8 ) { ui_error( UI_ERROR_ERROR, "Error reading from '%s'", oldname ); close( SCL ); free( TRDh ); return 1; } if( strncasecmp( signature, "SINCLAIR", 8 ) ) { ui_error( UI_ERROR_ERROR, "SCL file '%s' has the wrong signature", oldname ); close( SCL ); free( TRDh ); return 1; } if( read( SCL, &blocks, 1 ) != 1 ) { ui_error( UI_ERROR_ERROR, "Error reading from '%s'", oldname ); close( SCL ); free( TRDh ); return 1; } for( x = 0; x < blocks; x++ ) { if( read( SCL, &(headers[x][0]), 14 ) != 14 ) { ui_error( UI_ERROR_ERROR, "Error reading from '%s'", oldname ); close( SCL ); free( TRDh ); return 1; } } for( x = 0; x < blocks; x++ ) { size = headers[x][13]; if( lsb2dw(trd_free) < size ) { ui_error( UI_ERROR_ERROR, "File is too long to fit in the image *trd_free=%u < size=%u", lsb2dw( trd_free ), size ); goto Finish; } if( *trd_files > 127 ) { ui_error( UI_ERROR_ERROR, "Image is full" ); goto Finish; } memcpy( TRDh + *trd_files * 16, headers[x], 14 ); memcpy( TRDh + *trd_files * 16 + 0x0E, trd_fsec, 2 ); tmpscl = malloc( 32000 ); if( !tmpscl ) { ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ ); goto Finish; } left = ( headers[x][13] ) * (libspectrum_dword)256; fptr = (*trd_ftrk) * (libspectrum_dword)4096 + (*trd_fsec) * (libspectrum_dword)256; if( lseek( TRD, fptr, SEEK_SET ) == -1 ) { ui_error( UI_ERROR_ERROR, "Error seeking in temporary TRD file: %s", strerror( errno ) ); goto Finish; } while ( left > 32000 ) { read( SCL, tmpscl, 32000 ); write( TRD, tmpscl, 32000 ); left -= 32000; } read( SCL, tmpscl, left ); write( TRD, tmpscl, left ); free( tmpscl ); (*trd_files)++; dw2lsb( trd_free, lsb2dw(trd_free) - size ); while ( size > 15 ) { (*trd_ftrk)++; size -= 16; } (*trd_fsec) += size; while ( (*trd_fsec) > 15 ) { (*trd_fsec) -= 16; (*trd_ftrk)++; } } Finish: close( SCL ); lseek( TRD, 0L, SEEK_SET ); write( TRD, TRDh, 4096 ); free( TRDh ); return 0; }