forked from sent/waves
501 lines
18 KiB
JavaScript
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));
|
|
}
|
|
};
|
|
});
|