- [FIX] font.bmp afegit - [NEW] Afegides regles de comprobació de memòria en la versió debug de compilació en el lagueirtofile - [FIX] la rom ha de carregar abans de atari2600::init() - [NEW] Implementació bàsica del PIA completada. - [FIX] El framebuffer intern en el mòdul TIA era incorrecte. - [NEW] Afegits accesors als registres del 6502 - [NEW] m6502::reset() - [FIX] [6502m] Corregits opcodes amb direccionament implicit: CLC, SEC, PHA, CLI, PLA, SEI, DEY, TXA, TYA, TXS, TAY, TAX, CLV, TSX, INY, DEX, CLD, INX, NOP i SED). Augmentaven el PC una posició de més.
465 lines
18 KiB
C++
465 lines
18 KiB
C++
#include "tia.h"
|
|
#include "tia-audio.h"
|
|
#include "display.h"
|
|
|
|
namespace tia
|
|
{
|
|
bool collision_missile0_player1 = false;
|
|
bool collision_missile0_player0 = false;
|
|
bool collision_missile1_player0 = false;
|
|
bool collision_missile1_player1 = false;
|
|
bool collision_player0_playfield = false;
|
|
bool collision_player0_ball = false;
|
|
bool collision_player1_playfield = false;
|
|
bool collision_player1_ball = false;
|
|
bool collision_missile0_playfield = false;
|
|
bool collision_missile0_ball = false;
|
|
bool collision_missile1_playfield = false;
|
|
bool collision_missile1_ball = false;
|
|
bool collision_ball_playfield = false;
|
|
bool collision_player0_player1 = false;
|
|
bool collision_missile0_missile1 = false;
|
|
|
|
float paddle_pos[4] { 0.5f, 0.5f, 0.5f, 0.5f };
|
|
uint32_t paddle_charge_cycles[4] { 0, 0, 0, 0 };
|
|
|
|
bool joy0_button_pressed = false;
|
|
bool joy1_button_pressed = false;
|
|
|
|
uint8_t player0_size = 1;
|
|
uint8_t player0_num_copies = 1;
|
|
uint8_t player0_copies_distance = 0;
|
|
uint8_t player0_color = 0;
|
|
bool player0_reflect = false;
|
|
uint8_t player0_pos = 0;
|
|
uint8_t player0_bits = 0;
|
|
int8_t player0_hmotion = 0;
|
|
bool player0_vert_delay = false;
|
|
uint8_t player0_pixel_counter = 0;
|
|
uint8_t player0_pixel_position = 0;
|
|
|
|
uint8_t player1_size = 1;
|
|
uint8_t player1_num_copies = 1;
|
|
uint8_t player1_copies_distance = 0;
|
|
uint8_t player1_color = 0;
|
|
bool player1_reflect = false;
|
|
uint8_t player1_pos = 0;
|
|
uint8_t player1_bits = 0;
|
|
int8_t player1_hmotion = 0;
|
|
bool player1_vert_delay = false;
|
|
uint8_t player1_pixel_counter = 0;
|
|
uint8_t player1_pixel_position = 0;
|
|
|
|
|
|
bool missile0_enabled = false;
|
|
uint8_t missile0_size = 1;
|
|
uint8_t missile0_pos = 0;
|
|
int8_t missile0_hmotion = 0;
|
|
bool missile0_reset = false;
|
|
uint8_t missile0_pixel_counter = 0;
|
|
|
|
bool missile1_enabled = false;
|
|
uint8_t missile1_size = 1;
|
|
uint8_t missile1_pos = 0;
|
|
int8_t missile1_hmotion = 0;
|
|
bool missile1_reset = false;
|
|
uint8_t missile1_pixel_counter = 0;
|
|
|
|
bool ball_enabled = false;
|
|
uint8_t ball_size = 1;
|
|
uint8_t ball_pos = 0;
|
|
int8_t ball_hmotion = 0;
|
|
bool ball_vert_delay = false;
|
|
uint8_t ball_pixel_counter = 0;
|
|
|
|
uint8_t playfield_color = 0;
|
|
bool playfield_reflected = false;
|
|
bool playfield_score = false;
|
|
bool playfield_priority = false;
|
|
uint32_t playfield_bits = 0x00000000;
|
|
uint8_t playfield_shifts[2][40] = {
|
|
// Repeat
|
|
{16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7, 16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7},
|
|
// Reflect
|
|
{16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7, 7,6,5,4,3,2,1,0, 8,9,10,11,12,13,14,15, 19,18,17,16}
|
|
};
|
|
|
|
uint8_t background_color = 0;
|
|
|
|
uint32_t current_frame_cycles = 0;
|
|
|
|
bool hmove = false;
|
|
|
|
bool vsync_set = false;
|
|
bool vblank_set = false;
|
|
bool vblank_latch = false;
|
|
bool vblank_dump = false;
|
|
|
|
uint16_t color_clock = 0;
|
|
uint16_t current_scanline = 0;
|
|
bool rdy_low = false;
|
|
|
|
uint8_t framebuffer[192][160];
|
|
|
|
void reset()
|
|
{
|
|
|
|
}
|
|
|
|
const uint8_t *get_framebuffer()
|
|
{
|
|
return (const uint8_t*)framebuffer;
|
|
}
|
|
|
|
void tick()
|
|
{
|
|
if (!vblank_dump) current_frame_cycles++;
|
|
|
|
color_clock = (color_clock + 1) % 228;
|
|
if (color_clock == 0) {
|
|
rdy_low = false;
|
|
hmove = false;
|
|
current_scanline++;
|
|
}
|
|
|
|
if (missile0_reset) missile0_pos = player0_pos + (4*player0_size);
|
|
if (missile1_reset) missile1_pos = player1_pos + (4*player1_size);
|
|
|
|
audio::tick();
|
|
|
|
// [TODO] Pintar pixels
|
|
if ( (!vsync_set) && (!vblank_set) && (color_clock >= 68) && (current_scanline >= 37) && (current_scanline < 229) && (!hmove || color_clock >= 92) ) {
|
|
const uint8_t pixel_clock = color_clock - 68;
|
|
|
|
if (player0_pos == pixel_clock) {
|
|
player0_pixel_counter = player0_size;
|
|
player0_pixel_position = 7;
|
|
}
|
|
if (player1_pos == pixel_clock) {
|
|
player1_pixel_counter = player1_size;
|
|
player1_pixel_position = 7;
|
|
}
|
|
if ( (missile0_enabled) && (missile0_pos == pixel_clock) ) {
|
|
missile0_pixel_counter = missile0_size;
|
|
}
|
|
if ( (missile1_enabled) && (missile1_pos == pixel_clock) ) {
|
|
missile1_pixel_counter = missile1_size;
|
|
}
|
|
if ( (ball_enabled) && (ball_pos == pixel_clock) ) {
|
|
ball_pixel_counter = ball_size;
|
|
}
|
|
|
|
bool player0_visible = false;
|
|
bool player1_visible = false;
|
|
bool missile0_visible = false;
|
|
bool missile1_visible = false;
|
|
bool ball_visible = false;
|
|
bool playfield_visible = false;
|
|
|
|
if (player0_pixel_counter>0) {
|
|
if ((player0_bits>>player0_pixel_position)&0x01) player0_visible = true;
|
|
player0_pixel_counter--;
|
|
if ( (player0_pixel_counter==0) && (player0_pixel_position>0) ) {
|
|
player0_pixel_counter = player0_size;
|
|
player0_pixel_position--;
|
|
}
|
|
}
|
|
|
|
if (player1_pixel_counter>0) {
|
|
if ((player1_bits>>player1_pixel_position)&0x01) player1_visible = true;
|
|
player1_pixel_counter--;
|
|
if ( (player1_pixel_counter==0) && (player1_pixel_position>0) ) {
|
|
player1_pixel_counter = player1_size;
|
|
player1_pixel_position--;
|
|
}
|
|
}
|
|
|
|
if (missile0_pixel_counter>0) {
|
|
missile0_visible = true;
|
|
missile0_pixel_counter--;
|
|
}
|
|
|
|
if (missile1_pixel_counter>0) {
|
|
missile1_visible = true;
|
|
missile1_pixel_counter--;
|
|
}
|
|
|
|
if (ball_pixel_counter>0) {
|
|
ball_visible = true;
|
|
ball_pixel_counter--;
|
|
}
|
|
|
|
const uint8_t playfield_clock = pixel_clock >> 2;
|
|
const uint8_t playfield_shift = playfield_shifts[playfield_reflected?1:0][playfield_clock];
|
|
if ( (playfield_bits >> playfield_shift) & 0x01) {
|
|
playfield_visible = true;
|
|
}
|
|
|
|
if (missile0_visible && player1_visible) collision_missile0_player1 = true;
|
|
if (missile0_visible && player0_visible) collision_missile0_player0 = true;
|
|
if (missile1_visible && player0_visible) collision_missile1_player0 = true;
|
|
if (missile1_visible && player1_visible) collision_missile1_player1 = true;
|
|
if (player0_visible && playfield_visible) collision_player0_playfield = true;
|
|
if (player0_visible && ball_visible) collision_player0_ball = true;
|
|
if (player1_visible && playfield_visible) collision_player1_playfield = true;
|
|
if (player1_visible && ball_visible) collision_player1_ball = true;
|
|
if (missile0_visible && playfield_visible) collision_missile0_playfield = true;
|
|
if (missile0_visible && ball_visible) collision_missile0_ball = true;
|
|
if (missile1_visible && playfield_visible) collision_missile1_playfield = true;
|
|
if (missile1_visible && ball_visible) collision_missile1_ball = true;
|
|
if (ball_visible && playfield_visible) collision_ball_playfield = true;
|
|
if (player0_visible && player1_visible) collision_player0_player1 = true;
|
|
if (missile0_visible && missile1_visible) collision_missile0_missile1 = true;
|
|
|
|
const uint8_t x = pixel_clock;
|
|
const uint8_t y = current_scanline-37;
|
|
|
|
if (playfield_priority) {
|
|
if (ball_visible) {
|
|
framebuffer[y][x] = playfield_color;
|
|
} else if (playfield_visible) {
|
|
framebuffer[y][x] = playfield_score ? (x<80 ? player0_color : player1_color) : playfield_color;
|
|
} else if (player0_visible || missile0_visible) {
|
|
framebuffer[y][x] = player0_color;
|
|
} else if (player1_visible || missile1_visible) {
|
|
framebuffer[y][x] = player1_color;
|
|
} else {
|
|
framebuffer[y][x] = background_color;
|
|
}
|
|
} else {
|
|
if (player0_visible || missile0_visible) {
|
|
framebuffer[y][x] = player0_color;
|
|
} else if (player1_visible || missile1_visible) {
|
|
framebuffer[y][x] = player1_color;
|
|
} else if (ball_visible) {
|
|
framebuffer[y][x] = playfield_color;
|
|
} else if (playfield_visible) {
|
|
framebuffer[y][x] = playfield_score ? (x<80 ? player0_color : player1_color) : playfield_color;
|
|
} else {
|
|
framebuffer[y][x] = background_color;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
uint8_t get_paddle_state(int index)
|
|
{
|
|
if (vblank_dump) return 0x00;
|
|
return (current_frame_cycles >= paddle_charge_cycles[index]) ? 0x80 : 0x00;
|
|
}
|
|
|
|
uint8_t read(uint16_t address)
|
|
{
|
|
switch (address & 0x0f) {
|
|
case 0x00: return (collision_missile0_player1?0x80:0x00) | (collision_missile0_player0?0x40:0x00);
|
|
case 0x01: return (collision_missile1_player0?0x80:0x00) | (collision_missile1_player1?0x40:0x00);
|
|
case 0x02: return (collision_player0_playfield?0x80:0x00) | (collision_player0_ball?0x40:0x00);
|
|
case 0x03: return (collision_player1_playfield?0x80:0x00) | (collision_player1_ball?0x40:0x00);
|
|
case 0x04: return (collision_missile0_playfield?0x80:0x00) | (collision_missile0_ball?0x40:0x00);
|
|
case 0x05: return (collision_missile1_playfield?0x80:0x00) | (collision_missile1_ball?0x40:0x00);
|
|
case 0x06: return (collision_ball_playfield?0x80:0x00);
|
|
case 0x07: return (collision_player0_player1?0x80:0x00) | (collision_missile0_missile1?0x40:0x00);
|
|
|
|
case 0x08: return get_paddle_state(0);
|
|
case 0x09: return get_paddle_state(1);
|
|
case 0x0A: return get_paddle_state(2);
|
|
case 0x0B: return get_paddle_state(3);
|
|
|
|
case 0x0C: return joy0_button_pressed ? 0x00 : 0x80;
|
|
case 0x0D: return joy1_button_pressed ? 0x00 : 0x80;
|
|
default: return 0x00;
|
|
}
|
|
}
|
|
|
|
void write(uint16_t address, uint8_t value)
|
|
{
|
|
address &= 0x3f;
|
|
if (address >= 0x2d) address -= 0x20;
|
|
|
|
// [TODO] Procesar segons l'adreça resultant
|
|
switch (address) {
|
|
case 0x00: // VSYNC
|
|
{
|
|
const bool old_vsync = vsync_set;
|
|
vsync_set = value & 0x20;
|
|
if (old_vsync && !vsync_set) { // Resetejem el frame
|
|
current_scanline = 0;
|
|
display::redraw();
|
|
}
|
|
break;
|
|
}
|
|
case 0x01: // VBLANK
|
|
{
|
|
bool prev_dump = vblank_dump;
|
|
vblank_set = value & 0x20;
|
|
vblank_latch = value & 0x40;
|
|
vblank_dump = value & 0x80;
|
|
if (prev_dump && !vblank_dump) {
|
|
current_frame_cycles = 0;
|
|
for (int i = 0; i < 4; ++i) paddle_charge_cycles[i] = static_cast<uint32_t>(paddle_pos[i] * 2400.0f);
|
|
|
|
}
|
|
break;
|
|
}
|
|
case 0x02: // WSYNC
|
|
rdy_low = true;
|
|
break;
|
|
|
|
case 0x03: // RSYNC
|
|
color_clock = 0;
|
|
rdy_low = false;
|
|
break;
|
|
|
|
case 0x04: // NUSIZ0
|
|
missile0_size = 1 << ((value >> 4) & 0x03);
|
|
switch (value & 0x07) {
|
|
case 0: player0_num_copies = 1; player0_copies_distance = 0; player0_size = 1; break;
|
|
case 1: player0_num_copies = 2; player0_copies_distance = 2; player0_size = 1; break;
|
|
case 2: player0_num_copies = 2; player0_copies_distance = 4; player0_size = 1; break;
|
|
case 3: player0_num_copies = 3; player0_copies_distance = 2; player0_size = 1; break;
|
|
case 4: player0_num_copies = 2; player0_copies_distance = 8; player0_size = 1; break;
|
|
case 5: player0_num_copies = 1; player0_copies_distance = 0; player0_size = 2; break;
|
|
case 6: player0_num_copies = 3; player0_copies_distance = 4; player0_size = 1; break;
|
|
case 7: player0_num_copies = 1; player0_copies_distance = 0; player0_size = 4; break;
|
|
}
|
|
break;
|
|
|
|
case 0x05: // NUSIZ1
|
|
missile1_size = 1 << ((value >> 4) & 0x03);
|
|
switch (value & 0x07) {
|
|
case 0: player1_num_copies = 1; player1_copies_distance = 0; player1_size = 1; break;
|
|
case 1: player1_num_copies = 2; player1_copies_distance = 2; player1_size = 1; break;
|
|
case 2: player1_num_copies = 2; player1_copies_distance = 4; player1_size = 1; break;
|
|
case 3: player1_num_copies = 3; player1_copies_distance = 2; player1_size = 1; break;
|
|
case 4: player1_num_copies = 2; player1_copies_distance = 8; player1_size = 1; break;
|
|
case 5: player1_num_copies = 1; player1_copies_distance = 0; player1_size = 2; break;
|
|
case 6: player1_num_copies = 3; player1_copies_distance = 4; player1_size = 1; break;
|
|
case 7: player1_num_copies = 1; player1_copies_distance = 0; player1_size = 4; break;
|
|
}
|
|
break;
|
|
|
|
case 0x06: // COLUP0
|
|
player0_color = (value >> 1) & 0x7F; break;
|
|
case 0x07: // COLUP1
|
|
player1_color = (value >> 1) & 0x7F; break;
|
|
case 0x08: // COLUPF
|
|
playfield_color = (value >> 1) & 0x7F; break;
|
|
case 0x09: // COLUBK
|
|
background_color = (value >> 1) & 0x7F; break;
|
|
|
|
case 0x0A: // CTRLPF
|
|
playfield_reflected = value & 0x01;
|
|
playfield_score = value & 0x02;
|
|
playfield_priority = value & 0x04;
|
|
ball_size = 1 << ((value >> 4) & 0x03);
|
|
break;
|
|
|
|
case 0x0B: // REFP0
|
|
player0_reflect = value & 0x08; break;
|
|
case 0x0C: // REFP1
|
|
player1_reflect = value & 0x08; break;
|
|
|
|
case 0x0D: // PF0
|
|
playfield_bits = (playfield_bits & 0x0000ffff) | (uint32_t(value>>4)<<16); break;
|
|
case 0x0E: // PF1
|
|
playfield_bits = (playfield_bits & 0x000f00ff) | (uint32_t(value)<<8); break;
|
|
case 0x0F: // PF2
|
|
playfield_bits = (playfield_bits & 0x000fff00) | (uint32_t(value)); break;
|
|
|
|
case 0x10: // RESP0
|
|
player0_pos = color_clock < 68 ? 0 : color_clock-68; break;
|
|
case 0x11: // RESP1
|
|
player1_pos = color_clock < 68 ? 0 : color_clock-68; break;
|
|
case 0x12: // RESM0
|
|
missile0_pos = color_clock < 68 ? 0 : color_clock-68; break;
|
|
case 0x13: // RESM1
|
|
missile1_pos = color_clock < 68 ? 0 : color_clock-68; break;
|
|
case 0x14: // RESBL
|
|
ball_pos = color_clock < 68 ? 0 : color_clock-68; break;
|
|
|
|
case 0x15: // AUDC0
|
|
case 0x16: // AUDC1
|
|
case 0x17: // AUDF0
|
|
case 0x18: // AUDF1
|
|
case 0x19: // AUDV0
|
|
case 0x1A: // AUDV1
|
|
audio::write(address, value);
|
|
break;
|
|
|
|
case 0x1B: //GRP0
|
|
player0_bits = value; break;
|
|
case 0x1C: //GRP1
|
|
player1_bits = value; break;
|
|
|
|
case 0x1D: //ENAM0
|
|
missile0_enabled = value & 0x02; break;
|
|
case 0x1E: //ENAM1
|
|
missile0_enabled = value & 0x02; break;
|
|
case 0x1F: //ENABL
|
|
missile0_enabled = value & 0x02; break;
|
|
|
|
case 0x20: //HMP0
|
|
player0_hmotion = int8_t(value) >> 4; break;
|
|
case 0x21: //HMP1
|
|
player1_hmotion = int8_t(value) >> 4; break;
|
|
case 0x22: //HMM0
|
|
missile0_hmotion = int8_t(value) >> 4; break;
|
|
case 0x23: //HMM1
|
|
missile1_hmotion = int8_t(value) >> 4; break;
|
|
case 0x24: //HMBL
|
|
ball_hmotion = int8_t(value) >> 4; break;
|
|
|
|
case 0x25: //VDELP0
|
|
player0_vert_delay = value & 0x01; break;
|
|
case 0x26: //VDELP1
|
|
player1_vert_delay = value & 0x01; break;
|
|
case 0x27: //VDELBL
|
|
ball_vert_delay = value & 0x01; break;
|
|
|
|
case 0x28: // RESMP0
|
|
missile0_reset = value & 0x02; break;
|
|
case 0x29: // RESMP1
|
|
missile1_reset = value & 0x02; break;
|
|
|
|
case 0x2A: // HMOVE
|
|
// [TODO] Implementació bàsica, reemplazar per una que use els 15 color_clocks en el futur
|
|
player0_pos += player0_hmotion;
|
|
player1_pos += player1_hmotion;
|
|
missile0_pos += missile0_hmotion;
|
|
missile1_pos += missile1_hmotion;
|
|
ball_pos += ball_hmotion;
|
|
hmove = true;
|
|
break;
|
|
|
|
case 0x2B: // HMCLR
|
|
player0_hmotion = player1_hmotion = missile0_hmotion = missile1_hmotion = ball_hmotion = 0;
|
|
break;
|
|
case 0x2C: // CXCLR
|
|
collision_missile0_player1 = false;
|
|
collision_missile0_player0 = false;
|
|
collision_missile1_player0 = false;
|
|
collision_missile1_player1 = false;
|
|
collision_player0_playfield = false;
|
|
collision_player0_ball = false;
|
|
collision_player1_playfield = false;
|
|
collision_player1_ball = false;
|
|
collision_missile0_playfield = false;
|
|
collision_missile0_ball = false;
|
|
collision_missile1_playfield = false;
|
|
collision_missile1_ball = false;
|
|
collision_ball_playfield = false;
|
|
collision_player0_player1 = false;
|
|
collision_missile0_missile1 = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool is_rdy_low()
|
|
{
|
|
return rdy_low;
|
|
}
|
|
|
|
}
|