waves/public/assets/g/commodoreclicker/js/c64/cia.js
2025-04-09 17:11:14 -05:00

501 lines
18 KiB
JavaScript

define(function() {
return {
JOY_UP: 1,
JOY_DOWN: 2,
JOY_LEFT: 4,
JOY_RIGHT: 8,
JOY_FIRE: 16,
IEC_ID: 255,
timers: [],
CNTPIN: null,
CNTPIN_prev: null,
vicBank: null,
IRQ: null,
IRM: null,
currJoyPort: null,
currJoyState: null,
registers: [
// CIA1: peripherals/IRQ
[
0, // Port A
0, // Port B
0, // Port A direction
0, // Port B direction
0, // Timer A low byte
0, // Timer A high byte
0, // Timer B low byte
0, // Timer B high byte
0, // RTC tenth-of-sec
0, // RTC sec
0, // RTC min
0, // RTC hour
0, // Shift register
0, // IRQ source
0, // Timer A control
0 // Timer B control
],
// CIA2: RS232/NMI
[
0, // Port A
0, // Port B
0, // Port A direction
0, // Port B direction
0, // Timer A low byte
0, // Timer A high byte
0, // Timer B low byte
0, // Timer B high byte
0, // RTC tenth-of-sec
0, // RTC sec
0, // RTC min
0, // RTC hour
0, // Shift register
0, // NMI source
0, // Timer A control
0 // Timer B control
]
],
keysPressed: null,
keymap: {
27: 63, // Esc: Run/Stop
112: 4, // F1: F1
113: 5, // F2: F3
114: 6, // F3: F5
115: 3, // F4: F7
116: 54, // F5: Arrow Up
117: 48, // F6: Pound
118: 51, // F7: Home
119: 'NMI', // F8: Restore
192: 57, // `: Arrow Left
49: 56, // 1: 1
50: 59, // 2: 2
51: 8, // 3: 3
52: 11, // 4: 4
53: 16, // 5: 5
54: 19, // 6: 6
55: 24, // 7: 7
56: 27, // 8: 8
57: 32, // 9: 9
48: 35, // 0: 0
189: 40, // -: +
187: 43, // =: -
8: 0, // Bksp: Del
9: 58, // Tab: Control
81: 62, // Q: Q
87: 9, // W: W
69: 14, // E: E
82: 17, // R: R
84: 22, // T: T
89: 25, // Y: Y
85: 30, // U: U
73: 33, // I: I
79: 38, // O: O
80: 41, // P: P
219: 46, // [: @
221: 49, // ]: *
220: 53, // \: =
65: 10, // A: A
83: 13, // S: S
68: 18, // D: D
70: 21, // F: F
71: 26, // G: G
72: 29, // H: H
74: 34, // J: J
75: 37, // K: K
76: 42, // L: L
186: 45, // ;: :
222: 50, // ': ;
13: 1, // Enter: Return
16: 15, // Shift: Left Shift
90: 12, // Z: Z
88: 23, // X: X
67: 20, // C: C
86: 31, // V: V
66: 28, // B: B
78: 39, // N: N
77: 36, // M: M
188: 47, // ,: ,
190: 44, // .: .
191: 55, // /: /
17: 61, // Ctrl: C=
91: 7, // Left Win: Cursor Down
32: 60, // Space: Space
93: 2, // Right Win: Cursor Right
18: 'FIRE', // Alt: Joystick Fire
37: 'L', // Left: Joystick Left
38: 'U', // Up: Joystick Up
39: 'R', // Right: Joystick Right
40: 'D' // Down: Joystick Down
},
io_r: function(addr) {
var i, j, chip = (addr & 0x0100) ? 1 : 0;
addr &= 0x0F;
switch (addr) {
case 0: // Port A
if (chip) {
this.registers[1][0] &= 0xFC;
this.registers[1][0] |= (3 - this.vicBank);
} else {
for (i = 7; i >= 0; i--) {
j = 1 << i;
if (this.currJoyPort == addr) {
this.registers[0][addr] &= (255 - j);
this.registers[0][addr] |= (this.currJoyState & j);
}
}
}
break;
case 1: // Port B
if (chip) {
// TODO: RS232
} else {
this.registers[0][1] = 255;
for (i = 0; i < this.keysPressed.length; i++) {
j = this.keysPressed[i] >> 3;
if (!(this.registers[0][0] & (1 << j))) {
// Keyboard column is cleared, clear row
j = this.keysPressed[i] & 7;
this.registers[0][1] &= (255 - (1 << j));
}
}
for (i = 7; i >= 0; i--) {
j = 1 << i;
if (this.currJoyPort == addr) {
this.registers[0][addr] &= (255 - j);
this.registers[0][addr] |= (this.currJoyState & j);
}
}
}
break;
case 4: // Timer A lo
this.registers[chip][addr] = this.timers[chip][0].value & 255;
break;
case 5: // Timer A hi
this.registers[chip][addr] = this.timers[chip][0].value >> 8;
break;
case 7: // Timer B lo
this.registers[chip][addr] = this.timers[chip][1].value & 255;
break;
case 8: // Timer B hi
this.registers[chip][addr] = this.timers[chip][1].value >> 8;
break;
case 13: // IRQ
this.registers[chip][addr] = this.IRQ[chip];
this.IRQ[chip] = 0;
break;
}
return this.registers[chip][addr];
},
io_w: function(addr, val) {
var prevRunning, i, j, chip = (addr & 0x0100) ? 1 : 0;
addr &= 0x0F;
val &= 255;
switch (addr) {
case 0:
if (chip) {
this.vicBank = (3 - (val & 3));
this.owner.IEC[val & 8 ? 'pulldown' : 'release'](this.IEC_ID, 'ATN');
this.owner.IEC[val & 16 ? 'pulldown' : 'release'](this.IEC_ID, 'CLK');
this.owner.IEC[val & 32 ? 'pulldown' : 'release'](this.IEC_ID, 'DATA');
}
break;
case 4: // Timer A lo latch
this.timers[chip][0].latch &= 0xFF00;
this.timers[chip][0].latch |= val;
return;
case 5: // Timer A hi latch
this.timers[chip][0].latch &= 0x00FF
this.timers[chip][0].latch |= (val << 8);
return;
case 6: // Timer B lo latch
this.timers[chip][1].latch &= 0xFF00;
this.timers[chip][1].latch |= val;
return;
case 7: // Timer B hi latch
this.timers[chip][1].latch &= 0x00FF;
this.timers[chip][1].latch |= (val << 8);
return;
case 13: // IRM
if (val & 31) {
this.IRM[chip] = (val & 31) ^ ((val & 128) ? 0 : 31);
}
break;
case 14: // Timer A control
prevRunning = this.timers[chip][0].running;
this.timers[chip][0].running = !!(val & 1);
this.timers[chip][0].oneshot = !!(val & 8);
this.timers[chip][0].latchroll = !(val & 16);
this.timers[chip][0].mode = (val & 32) >> 5;
if (!this.timers[chip][0].running) {
this.timers[chip][0].latch &= 255;
}
if (!prevRunning && this.timers[chip][0].running) {
this.timers[chip][0].value = this.timers[chip][0].latch;
}
break;
case 15: // Timer B control
prevRunning = this.timers[chip][1].running;
this.timers[chip][1].running = !!(val & 1);
this.timers[chip][1].oneshot = !!(val & 8);
this.timers[chip][1].latchroll = !(val & 16);
this.timers[chip][1].mode = (val & 96) >> 5;
if (!this.timers[chip][1].running) {
this.timers[chip][1].latch &= 255;
}
if (!prevRunning && this.timers[chip][1].running) {
this.timers[chip][1].value = this.timers[chip][1].latch;
}
break;
}
this.registers[chip][addr] = val & 255;
},
handlers: {
keydown: function(e) {
var i, k = this.keymap[e.keyCode];
if (k === undefined) {
return;
}
e.preventDefault();
switch (k) {
case 'L':
$('#joy_left').addClass('active');
this.currJoyState &= (255 - this.JOY_LEFT);
break;
case 'U':
$('#joy_up').addClass('active');
this.currJoyState &= (255 - this.JOY_UP);
break;
case 'R':
$('#joy_right').addClass('active');
this.currJoyState &= (255 - this.JOY_RIGHT);
break;
case 'D':
$('#joy_down').addClass('active');
this.currJoyState &= (255 - this.JOY_DOWN);
break;
case 'FIRE':
$('#joy_fire').addClass('active');
this.currJoyState &= (255 - this.JOY_FIRE);
break;
case 'NMI':
this.owner.IEC.signal('RESET');
this.owner.CPU.signal('NMI');
break;
default:
i = this.keysPressed.indexOf(k);
if (i == -1) {
this.keysPressed.push(k);
}
break;
}
},
keyup: function(e) {
var i, k = this.keymap[e.keyCode];
if (k === undefined) {
return;
}
e.preventDefault();
switch (k) {
case 'L':
$('#joy_left').removeClass('active');
this.currJoyState |= this.JOY_LEFT;
break;
case 'U':
$('#joy_up').removeClass('active');
this.currJoyState |= this.JOY_UP;
break;
case 'R':
$('#joy_right').removeClass('active');
this.currJoyState |= this.JOY_RIGHT;
break;
case 'D':
$('#joy_down').removeClass('active');
this.currJoyState |= this.JOY_DOWN;
break;
case 'FIRE':
$('#joy_fire').removeClass('active');
this.currJoyState |= this.JOY_FIRE;
break;
case 'NMI':
break;
default:
i = this.keysPressed.indexOf(k);
if (i >= 0) {
this.keysPressed.splice(i, 1);
}
break;
}
}
},
getState: function() {
var t = [], i, j;
for (i = 0; i < 2; i++) {
t[i] = [];
for (j = 0; j < 2; j++) {
t[i][j] = $.extend({}, this.timers[i][j])
}
}
return {
CIA1: this.registers[0].slice(0),
CIA2: this.registers[1].slice(0),
timers: t
}
},
setState: function(state) {
var t = [], i, j;
for (i = 0; i < 2; i++) {
t[i] = [];
for (j = 0; j < 2; j++) {
t[i][j] = $.extend({}, state.timers[i][j])
}
}
for (i in state.CIA1) {
this.io_w(i, state.CIA1[i]);
}
for (i in state.CIA2) {
this.io_w(0x0100 | i, state.CIA2[i]);
}
this.registers[0][this.currJoyPort] &= 0xE0;
this.registers[0][this.currJoyPort] |= this.currJoyState;
this.registers[1][0] &= 0x3F;
if (!this.owner.IEC.check('CLK')) {
this.registers[1][0] |= 0x40;
}
if (!this.owner.IEC.check('DATA')) {
this.registers[1][0] |= 0x80;
}
this.timers = t;
},
step: function() {
this.CNTPIN_prev = this.CNTPIN;
var i, j, timer, dec;
for (i = 0; i < 2; i++) {
for (j = 0; j < 2; j++) {
timer = this.timers[i][j];
if (timer.running) {
if (timer.value == 0) {
timer.underflowed = true;
if (timer.oneshot) {
timer.running = false;
} else {
timer.value = timer.latchroll ? timer.latch : 65535;
}
if (j) {
this.IRQ[i] |= 0x02;
if (this.IRM[i] & 2) {
this.IRQ[i] |= 0x80;
this.owner.CPU.signal('INT');
}
} else {
this.IRQ[i] |= 0x01;
if (this.IRM[i] & 1) {
this.IRQ[i] |= 0x80;
this.owner.CPU.signal('INT');
}
}
} else {
timer.underflowed = false;
switch (j * 4 + timer.mode) {
// Timer A: clock
case 0:
dec = true;
break;
// Timer A: positive slope on CNT
case 1:
dec = this.CNTPIN && !this.CNTPIN_prev;
break;
// Timer B: clock
case 4:
dec = true;
break;
// Timer B: positive slope on CNT
case 5:
dec = this.CNTPIN && !this.CNTPIN_prev;
break;
// Timer B: A underflow
case 6:
dec = this.timers[i][0].underflowed;
break;
// Timer B: A underflow and +ve slope on CNT
case 7:
dec = this.timers[i][0].underflowed && this.CNTPIN && !this.CNTPIN_prev;
break;
}
if (dec) {
timer.value--;
}
}
}
}
}
this.registers[1][0] &= 0x3F;
if (!this.owner.IEC.check('CLK')) {
this.registers[1][0] |= 0x40;
}
if (!this.owner.IEC.check('DATA')) {
this.registers[1][0] |= 0x80;
}
},
reset: function() {
// All pins are pulled high on the data ports
this.registers[0][0] = 255;
this.registers[0][1] = 255;
this.registers[1][0] = 255;
this.registers[1][1] = 255;
this.currJoyPort = 0;
this.currJoyState = 31;
this.CNTPIN = false;
this.CNTPIN_prev = false;
this.IRQ = [0,0];
this.IRM = [0,0];
this.keysPressed = [];
this.vicBank = 0;
this.owner.IEC.register(this.IEC_ID);
var i, j;
this.timers.length = 0;
for (i = 0; i < 2; i++) {
this.timers[i] = [];
for (j = 0; j < 2; j++) {
this.timers[i][j] = {
mode: 0,
running: false,
underflowed: false,
oneshot: false,
latchroll: false,
latch: 0,
value: 0
};
}
}
},
init: function() {
this.reset();
$(document)
.on('keydown', this.handlers.keydown.bind(this))
.on('keyup', this.handlers.keyup.bind(this));
}
};
});