/* loader.c: loader detection Copyright (c) 2006 Philip Kendall 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #include "event.h" #include "loader.h" #include "memory_pages.h" #include "rzx.h" #include "settings.h" #include "spectrum.h" #include "tape.h" #include "z80/z80.h" static int successive_reads = 0; static libspectrum_signed_dword last_tstates_read = -100000; static libspectrum_byte last_b_read = 0x00; static int length_known1 = 0, length_known2 = 0; static int length_long1 = 0, length_long2 = 0; typedef enum acceleration_mode_t { ACCELERATION_MODE_NONE = 0, ACCELERATION_MODE_INCREASING, ACCELERATION_MODE_DECREASING, } acceleration_mode_t; static acceleration_mode_t acceleration_mode; static size_t acceleration_pc; void loader_frame( libspectrum_dword frame_length ) { if( last_tstates_read > -100000 ) { last_tstates_read -= frame_length; } } void loader_tape_play( void ) { successive_reads = 0; acceleration_mode = ACCELERATION_MODE_NONE; } void loader_tape_stop( void ) { successive_reads = 0; acceleration_mode = ACCELERATION_MODE_NONE; } static void do_acceleration( void ) { if( length_known1 ) { /* B is used to indicate the length of the pulses */ int set_b_high = length_long1; set_b_high ^= ( acceleration_mode == ACCELERATION_MODE_DECREASING ); if( set_b_high ) { z80.bc.b.h = 0xfe; } else { z80.bc.b.h = 0x00; } /* Bit 5 of C is used to indicate the current microphone level */ z80.bc.b.l = (z80.bc.b.l & ~0x20) | (tape_microphone ? 0x00 : 0x20); z80.af.b.l |= 0x01; /* Simulate the RET at the end of the edge-finding loop */ z80.pc.b.l = readbyte_internal( z80.sp.w ); z80.sp.w++; z80.pc.b.h = readbyte_internal( z80.sp.w ); z80.sp.w++; event_remove_type( tape_edge_event ); tape_next_edge( tstates, 1 ); successive_reads = 0; } length_known1 = length_known2; length_long1 = length_long2; } static acceleration_mode_t acceleration_detector( libspectrum_word pc ) { int state = 0, count = 0; while( 1 ) { libspectrum_byte b = readbyte_internal( pc ); pc++; count++; switch( state ) { case 0: switch( b ) { case 0x03: state = 28; break; /* Data byte of JR NZ, ... - Alkatraz */ case 0x04: state = 1; break; /* INC B - Many loaders */ default: state = 13; break; /* Possible Digital Integration */ } break; case 1: switch( b ) { case 0x20: state = 40; break; /* JR NZ - variant Alkatraz */ case 0xc8: state = 2; break; /* RET Z */ default: return ACCELERATION_MODE_NONE; } break; case 2: switch( b ) { case 0x3e: state = 3; break; /* LD A,nn */ default: return ACCELERATION_MODE_NONE; } break; case 3: switch( b ) { case 0x00: /* Search Loader */ case 0x7f: /* ROM loader and variants */ case 0xff: /* Dinaload */ state = 4; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 4: switch( b ) { case 0xdb: state = 5; break; /* IN A,(nn) */ default: return ACCELERATION_MODE_NONE; } break; case 5: switch( b ) { case 0xfe: state = 6; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 6: switch( b ) { case 0x1f: state = 7; break; /* RRA */ case 0xa9: state = 24; break; /* XOR C - Search Loader */ default: return ACCELERATION_MODE_NONE; } break; case 7: switch( b ) { case 0x00: /* NOP - Bleepload */ case 0xa7: /* AND A - Microsphere */ case 0xc8: /* RET Z - Paul Owens */ case 0xd0: /* RET NC - ROM loader */ state = 8; break; case 0xa9: state = 9; break; /* XOR C - Speedlock */ default: return ACCELERATION_MODE_NONE; } break; case 8: switch( b ) { case 0xa9: state = 9; break; /* XOR C */ default: return ACCELERATION_MODE_NONE; } break; case 9: switch( b ) { case 0xe6: state = 10; break; /* AND nn */ default: return ACCELERATION_MODE_NONE; } break; case 10: switch( b ) { case 0x20: state = 11; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 11: switch( b ) { case 0x28: state = 12; break; /* JR nn */ default: return ACCELERATION_MODE_NONE; } break; case 12: if( b == 0x100 - count ) { return ACCELERATION_MODE_INCREASING; } else { return ACCELERATION_MODE_NONE; } break; /* Digital Integration loader */ case 13: state = 14; break; /* Possible Digital Integration */ case 14: switch( b ) { case 0x05: state = 15; break; /* DEC B - Digital Integration */ default: return ACCELERATION_MODE_NONE; } break; case 15: switch( b ) { case 0xc8: state = 16; break; /* RET Z */ default: return ACCELERATION_MODE_NONE; } break; case 16: switch( b ) { case 0xdb: state = 17; break; /* IN A,(nn) */ default: return ACCELERATION_MODE_NONE; } break; case 17: switch( b ) { case 0xfe: state = 18; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 18: switch( b ) { case 0xa9: state = 19; break; /* XOR C */ default: return ACCELERATION_MODE_NONE; } break; case 19: switch( b ) { case 0xe6: state = 20; break; /* AND nn */ default: return ACCELERATION_MODE_NONE; } break; case 20: switch( b ) { case 0x40: state = 21; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 21: switch( b ) { case 0xca: state = 22; break; /* JP Z,nnnn */ default: return ACCELERATION_MODE_NONE; } break; case 22: /* LSB of jump target */ if( b == ( z80.pc.w - 4 ) % 0x100 ) { state = 23; } else { return ACCELERATION_MODE_NONE; } break; case 23: /* MSB of jump target */ if( b == ( z80.pc.w - 4 ) / 0x100 ) { return ACCELERATION_MODE_DECREASING; } else { return ACCELERATION_MODE_NONE; } /* Search loader */ case 24: switch( b ) { case 0xe6: state = 25; break; /* AND nn */ default: return ACCELERATION_MODE_NONE; } break; case 25: switch( b ) { case 0x40: state = 26; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 26: switch( b ) { case 0x28: state = 12; break; /* JR Z - Space Crusade */ case 0xd8: state = 27; break; /* RET C */ default: return ACCELERATION_MODE_NONE; } break; case 27: switch( b ) { case 0x00: state = 11; break; /* NOP */ default: return ACCELERATION_MODE_NONE; } break; /* Alkatraz */ case 28: switch( b ) { case 0xc3: state = 29; break; /* JP nnnn */ default: return ACCELERATION_MODE_NONE; } break; case 29: state = 30; break; /* First data byte of JP */ case 30: state = 31; break; /* Second data byte of JP */ case 31: switch( b ) { case 0xdb: state = 32; break; /* IN A,(nn) */ default: return ACCELERATION_MODE_NONE; } break; case 32: switch( b ) { case 0xfe: state = 33; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 33: switch( b ) { case 0x1f: state = 34; break; /* RRA */ default: return ACCELERATION_MODE_NONE; } break; case 34: switch( b ) { case 0xc8: state = 35; break; /* RET Z */ default: return ACCELERATION_MODE_NONE; } break; case 35: switch( b ) { case 0xa9: state = 36; break; /* XOR C */ default: return ACCELERATION_MODE_NONE; } break; case 36: switch( b ) { case 0xe6: state = 37; break; /* AND nn */ default: return ACCELERATION_MODE_NONE; } break; case 37: switch( b ) { case 0x20: state = 38; break; /* Data byte */ default: return ACCELERATION_MODE_NONE; } break; case 38: switch( b ) { case 0x28: state = 39; break; /* JR Z,nn */ default: return ACCELERATION_MODE_NONE; } break; case 39: switch( b ) { case 0xf1: /* Normal data byte */ case 0xf3: /* Variant data byte */ return ACCELERATION_MODE_INCREASING; default: return ACCELERATION_MODE_NONE; } break; /* "Variant" Alkatraz */ case 40: switch( b ) { case 0x01: state = 41; break; /* Data byte of JR NZ */ default: return ACCELERATION_MODE_NONE; } break; case 41: switch( b ) { case 0xc9: state = 31; break; /* RET */ default: return ACCELERATION_MODE_NONE; } break; default: /* Can't happen */ break; } } } static void check_for_acceleration( void ) { /* If the IN occured at a different location to the one we're accelerating, stop acceleration */ if( acceleration_mode && z80.pc.w != acceleration_pc ) acceleration_mode = ACCELERATION_MODE_NONE; /* If we're not accelerating, check if this is a loader */ if( !acceleration_mode ) { acceleration_mode = acceleration_detector( z80.pc.w - 6 ); acceleration_pc = z80.pc.w; } if( acceleration_mode ) do_acceleration(); } void loader_detect_loader( void ) { libspectrum_dword tstates_diff = tstates - last_tstates_read; libspectrum_byte b_diff = z80.bc.b.h - last_b_read; last_tstates_read = tstates; last_b_read = z80.bc.b.h; if( settings_current.detect_loader ) { if( tape_is_playing() ) { if( tstates_diff > 1000 || ( b_diff != 1 && b_diff != 0 && b_diff != 0xff ) ) { successive_reads++; if( successive_reads >= 2 ) { tape_stop(); } } else { successive_reads = 0; } } else { if( tstates_diff <= 500 && ( b_diff == 1 || b_diff == 0xff ) ) { successive_reads++; if( successive_reads >= 10 ) { tape_do_play( 1 ); } } else { successive_reads = 0; } } } else { successive_reads = 0; } if( settings_current.accelerate_loader && tape_is_playing() && !rzx_recording ) check_for_acceleration(); } void loader_set_acceleration_flags( int flags, int from_acceleration ) { if( flags & LIBSPECTRUM_TAPE_FLAGS_LENGTH_SHORT ) { length_known2 = 1; length_long2 = 0; } else if( flags & LIBSPECTRUM_TAPE_FLAGS_LENGTH_LONG ) { length_known2 = 1; length_long2 = 1; } else { length_known2 = 0; } /* If this tape edge occurred due to normal timings rather than our tape acceleration, turn off acceleration for the next edge or we miss an edge. See [bugs:#387] for more details */ if( !from_acceleration ) { length_known1 = 0; } }