diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index fdba59e2a..59379d38a 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -526,8 +526,9 @@ uint8_t TMS9918::get_current_line() { int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_; // This assumes NTSC 192-line. TODO: other modes. if(source_row >= 0xdb) source_row -= 6; + +// printf("Current row: %d -> %d\n", row_, source_row); return static_cast(source_row); -} /* TODO: Full proper sequence of current lines: @@ -539,6 +540,21 @@ uint8_t TMS9918::get_current_line() { PAL 256x224 00-FF, 00-02, CA-FF PAL 256x240 00-FF, 00-0A, D2-FF */ +} + +uint8_t TMS9918::get_latched_horizontal_counter() { + // Translate from internal numbering, which puts pixel output + // in the final 256 pixels of 342, to the public numbering, + // which makes the 256 pixels the first 256 spots, but starts + // counting at -48, and returns only the top 8 bits of the number. + int public_counter = latched_column_ - 86; + if(public_counter < -48) public_counter += 342; + return uint8_t(public_counter >> 1); +} + +void TMS9918::latch_horizontal_counter() { + latched_column_ = column_; +} uint8_t TMS9918::get_register(int address) { write_phase_ = false; diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index 2ffc010df..5dc552e67 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -66,6 +66,10 @@ class TMS9918: public Base { /*! Gets the current scan line; provided by the Master System only. */ uint8_t get_current_line(); + uint8_t get_latched_horizontal_counter(); + + void latch_horizontal_counter(); + /*! Returns the amount of time until get_interrupt_line would next return true if there are no interceding calls to set_register or get_register. diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 8318813b4..b18572a97 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -106,7 +106,7 @@ class Base { uint8_t background_colour_ = 0; // Internal mechanisms for position tracking. - int column_ = 0, row_ = 0; + int column_ = 0, row_ = 0, latched_column_ = 0; int cycles_error_ = 0; HalfCycles half_cycles_before_internal_cycles(int internal_cycles); @@ -156,8 +156,8 @@ class Base { // Set the position, in cycles, of the two interrupts, // within a line. struct { - int column = 342; - int row = 191; + int column = 4; + int row = 193; } end_of_frame_interrupt_position; int line_interrupt_position = -1; diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 54b4ffbdf..63c179126 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -184,10 +184,13 @@ class ConcreteMachine: printf("TODO: [input] I/O port control\n"); *cycle.value = 0xff; break; - case 0x40: case 0x41: + case 0x40: update_video(); *cycle.value = vdp_->get_current_line(); break; + case 0x41: + *cycle.value = vdp_->get_latched_horizontal_counter(); + break; case 0x80: case 0x81: update_video(); *cycle.value = vdp_->get_register(address); @@ -201,7 +204,11 @@ class ConcreteMachine: } break; case 0xc1: { Joystick *const joypad2 = static_cast(joysticks_[1].get()); - *cycle.value = (joypad2->get_state() >> 2) | 0xf; + + *cycle.value = + (joypad2->get_state() >> 2) | + 0x30 | + get_th_values(); } break; default: @@ -219,9 +226,20 @@ class ConcreteMachine: page_cartridge(); } break; - case 0x01: - printf("TODO: [output] I/O port control\n"); - break; + case 0x01: { + // A programmer can force the TH lines to 0 here, + // causing a phoney lightgun latch, so check for any + // discontinuity in TH inputs. + const auto previous_ths = get_th_values(); + io_port_control_ = *cycle.value; + const auto new_ths = get_th_values(); + + // Latch if either TH has newly gone to 1. + if((new_ths^previous_ths)&new_ths) { + update_video(); + vdp_->latch_horizontal_counter(); + } + } break; case 0x40: case 0x41: update_audio(); sn76489_.set_register(*cycle.value); @@ -233,7 +251,7 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc0: -// printf("TODO: [output] I/O port A/N [%02x]\n", *cycle.value); + printf("TODO: [output] I/O port A/N [%02x]\n", *cycle.value); break; case 0xc1: printf("TODO: [output] I/O port B/misc\n"); @@ -274,6 +292,17 @@ class ConcreteMachine: } private: + inline uint8_t get_th_values() { + // Quick not on TH inputs here: if either is setup as an output, then the + // currently output level is returned. Otherwise they're fixed at 1. + return + static_cast( + ((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) | + ((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80) + ); + + } + inline void update_audio() { speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); } @@ -299,6 +328,8 @@ class ConcreteMachine: uint8_t bios_[8*1024]; std::vector cartridge_; + uint8_t io_port_control_ = 0x0f; + // The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM. const uint8_t *read_pointers_[64]; uint8_t *write_pointers_[64];