mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Merge pull request #2 from whscullin/whscullin/more-cleanup
More cleanup.
This commit is contained in:
commit
e021c692ce
@ -134,7 +134,17 @@
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="updateSpeed()"/>
|
||||
<select id="computer_type" value="apple2plus" onchange="updateCPU()">
|
||||
<option value="apple2plus">Apple ][+</option>
|
||||
<option value="apple2">Autostart Apple ][</option>
|
||||
<option value="original">Apple ][</option>
|
||||
<select>
|
||||
<label for="computer_type">
|
||||
Type
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="updateCPU()"/>
|
||||
<label for="accelerator_toggle">
|
||||
Accelerated CPU
|
||||
</label>
|
||||
@ -142,6 +152,13 @@
|
||||
</ul>
|
||||
<h3>Joystick</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="disable_mouse"
|
||||
onclick="updateJoystick()" />
|
||||
<label for="disable_mouse">
|
||||
Disable Mouse Joystick
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="flip_x"
|
||||
onclick="updateJoystick()" />
|
||||
@ -194,12 +211,25 @@
|
||||
</div>
|
||||
<div id="save" title="Save Disk">
|
||||
<form action="#" onsubmit="return false;">
|
||||
<b>Save to Browser</a>
|
||||
<br /><br />
|
||||
Save Name: <input type="text" name="name" id="save_name"
|
||||
style="width: 200px" />
|
||||
</form>
|
||||
<hr />
|
||||
<div>
|
||||
<b>Download to Local Disk</b>
|
||||
<br /><br />
|
||||
<a id="local_save_link" class="button">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="manage" title="Manage Disks">
|
||||
</div>
|
||||
<div id="http_load" title="Load URL">
|
||||
<form action="#">
|
||||
<input type="text" id="http_url" style="width: 500px"/>
|
||||
</form>
|
||||
</div>
|
||||
<div id="load" title="Load Disk">
|
||||
<table>
|
||||
<tr>
|
||||
|
@ -138,7 +138,16 @@
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="updateSpeed()"/>
|
||||
<select id="computer_type" value="apple2enh" onchange="updateCPU()">
|
||||
<option value="apple2enh">Enhanced Apple //e</option>
|
||||
<option value="apple2e">Apple //e</option>
|
||||
<select>
|
||||
<label for="computer_type">
|
||||
Type
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="updateCPU()"/>
|
||||
<label for="accelerator_toggle">
|
||||
Accelerated CPU
|
||||
</label>
|
||||
@ -146,6 +155,13 @@
|
||||
</ul>
|
||||
<h3>Joystick</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="disable_mouse"
|
||||
onclick="updateJoystick()" />
|
||||
<label for="disable_mouse">
|
||||
Disable Mouse Joystick
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="flip_x"
|
||||
onclick="updateJoystick()" />
|
||||
@ -198,12 +214,25 @@
|
||||
</div>
|
||||
<div id="save" title="Save Disk">
|
||||
<form action="#" onsubmit="return false;">
|
||||
<b>Save to Browser</a>
|
||||
<br /><br />
|
||||
Save Name: <input type="text" name="name" id="save_name"
|
||||
style="width: 200px" />
|
||||
</form>
|
||||
<hr />
|
||||
<div>
|
||||
<b>Download to Local Disk</b>
|
||||
<br /><br />
|
||||
<a id="local_save_link" class="button">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="manage" title="Manage Disks">
|
||||
</div>
|
||||
<div id="http_load" title="Load URL">
|
||||
<form action="#">
|
||||
<input type="text" id="http_url" style="width: 500px"/>
|
||||
</form>
|
||||
</div>
|
||||
<div id="load" title="Load Disk">
|
||||
<table>
|
||||
<tr>
|
||||
|
102
js/apple2io.js
102
js/apple2io.js
@ -9,13 +9,16 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
/*globals debug: false, toHex: false, each: false */
|
||||
/*globals debug: false */
|
||||
/*exported Apple2IO */
|
||||
|
||||
function Apple2IO(cpu, callbacks)
|
||||
{
|
||||
'use strict';
|
||||
|
||||
var _slot = [];
|
||||
var _auxRom = null;
|
||||
|
||||
var _hz = 1023000;
|
||||
var _rate = 44000;
|
||||
var _sample_size = 4096;
|
||||
@ -85,16 +88,15 @@ function Apple2IO(cpu, callbacks)
|
||||
PADDLE2: 0x66, // bit 7: status of pdl-2 timer (read)
|
||||
PADDLE3: 0x67, // bit 7: status of pdl-3 timer (read)
|
||||
PDLTRIG: 0x70, // trigger paddles
|
||||
BANK: 0x73, // Back switched RAM card bank
|
||||
SETIOUDIS:0x7E, // Enable double hires
|
||||
CLRIOUDIS:0x7F // Disable double hires
|
||||
};
|
||||
|
||||
function _debug() {
|
||||
// debug.apply(arguments);
|
||||
debug.apply(arguments);
|
||||
}
|
||||
|
||||
var _locs = [];
|
||||
|
||||
function _tick() {
|
||||
var now = cpu.cycles();
|
||||
var phase = _phase > 0 ? _high : _low;
|
||||
@ -297,47 +299,95 @@ function Apple2IO(cpu, callbacks)
|
||||
}
|
||||
|
||||
return {
|
||||
registerSwitches: function apple2io_registerSwitches(a, locs) {
|
||||
each(locs, function(key) {
|
||||
var val = locs[key];
|
||||
if (_locs[val]) {
|
||||
debug('duplicate switch! ' + toHex(val));
|
||||
}
|
||||
_locs[val] = a;
|
||||
});
|
||||
},
|
||||
start: function apple2io_start() {
|
||||
this.registerSwitches(this, LOC);
|
||||
return 0xc0;
|
||||
},
|
||||
end: function apple2io_end() {
|
||||
return 0xc0;
|
||||
return 0xcf;
|
||||
},
|
||||
|
||||
ioSwitch: function apple2io_ioSwitch(off, val) {
|
||||
var result;
|
||||
if (off < 0x80) {
|
||||
result = _access(off, val);
|
||||
} else {
|
||||
var slot = (off & 0x70) >> 4;
|
||||
if (_slot[slot]) {
|
||||
result = _slot[slot].ioSwitch(off, val);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
read: function apple2io_read(page, off) {
|
||||
var result = 0;
|
||||
if (_locs[off]) {
|
||||
result = _locs[off].ioSwitch(off);
|
||||
} else {
|
||||
debug('I/O read: C0' + toHex(off));
|
||||
var slot;
|
||||
switch (page) {
|
||||
case 0xc0:
|
||||
result = this.ioSwitch(off);
|
||||
break;
|
||||
case 0xc1:
|
||||
case 0xc2:
|
||||
case 0xc3:
|
||||
case 0xc4:
|
||||
case 0xc5:
|
||||
case 0xc6:
|
||||
case 0xc7:
|
||||
slot = page & 0x0f;
|
||||
_auxRom = _slot[slot];
|
||||
if (_slot[slot]) {
|
||||
result = _slot[slot].read(page, off);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_auxRom) {
|
||||
result = _auxRom.read(page, off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
write: function apple2io_write(page, off, val) {
|
||||
if (_locs[off]) {
|
||||
_locs[off].ioSwitch(off, val);
|
||||
} else {
|
||||
debug('I/O write: C0' + toHex(off));
|
||||
var slot;
|
||||
switch (page) {
|
||||
case 0xc0:
|
||||
this.ioSwitch(off);
|
||||
break;
|
||||
case 0xc1:
|
||||
case 0xc2:
|
||||
case 0xc3:
|
||||
case 0xc4:
|
||||
case 0xc5:
|
||||
case 0xc6:
|
||||
case 0xc7:
|
||||
slot = page & 0x0f;
|
||||
_auxRom = _slot[slot];
|
||||
if (_slot[slot]) {
|
||||
_slot[slot].write(page, off, val);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_auxRom) {
|
||||
_auxRom.write(page, off, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
getState: function apple2io_getState() { return {}; },
|
||||
setState: function apple2io_setState() { },
|
||||
ioSwitch: function apple2io_ioSwitch(off, val) {
|
||||
return _access(off, val);
|
||||
|
||||
addSlot: function apple2io_addSlot(slot, card) {
|
||||
_slot[slot] = card;
|
||||
},
|
||||
|
||||
keyDown: function apple2io_keyDown(ascii) {
|
||||
_keyDown = true;
|
||||
_key = ascii | 0x80;
|
||||
},
|
||||
|
||||
keyUp: function apple2io_keyUp() {
|
||||
_keyDown = false;
|
||||
_key = 0;
|
||||
@ -346,9 +396,11 @@ function Apple2IO(cpu, callbacks)
|
||||
buttonDown: function apple2io_buttonDown(b) {
|
||||
_button[b] = true;
|
||||
},
|
||||
|
||||
buttonUp: function apple2io_buttonUp(b) {
|
||||
_button[b] = false;
|
||||
},
|
||||
|
||||
paddle: function apple2io_paddle(p, v) {
|
||||
_paddle[p] = v;
|
||||
},
|
||||
|
@ -9,6 +9,7 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
/*jshint browser:true */
|
||||
/*globals allocMemPages: false, debug: false,
|
||||
base64_encode: false, base64_decode: false,
|
||||
enhanced: false */
|
||||
@ -173,6 +174,7 @@ function LoresPage(page, charset)
|
||||
base = addr - 0x400 * _page;
|
||||
return _buffer[bank][base];
|
||||
},
|
||||
|
||||
_write: function(page, off, val, bank) {
|
||||
var addr = (page << 8) | off,
|
||||
base = addr - 0x400 * _page,
|
||||
@ -321,6 +323,7 @@ function LoresPage(page, charset)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
var addr = 0x400 * _page;
|
||||
_refreshing = true;
|
||||
@ -331,6 +334,7 @@ function LoresPage(page, charset)
|
||||
}
|
||||
_refreshing = false;
|
||||
},
|
||||
|
||||
blink: function() {
|
||||
var addr = 0x400 * _page;
|
||||
_refreshing = true;
|
||||
@ -516,11 +520,14 @@ function HiresPage(page)
|
||||
},
|
||||
|
||||
_start: function() { return (0x20 * _page); },
|
||||
|
||||
_end: function() { return (0x020 * _page) + 0x1f; },
|
||||
|
||||
_read: function(page, off, bank) {
|
||||
var addr = (page << 8) | off, base = addr - 0x2000 * _page;
|
||||
return _buffer[bank][base];
|
||||
},
|
||||
|
||||
_write: function(page, off, val, bank) {
|
||||
function dim(c) {
|
||||
return [c[0] * 0.75, c[1] * 0.75, c[2] * 0.75];
|
||||
@ -665,6 +672,7 @@ function HiresPage(page)
|
||||
off = dx * 4 + dy * 280 * 4 * 2;
|
||||
for (idx = 0; idx < 9; idx++, off += 8) {
|
||||
val >>= 1;
|
||||
|
||||
if (v1) {
|
||||
if (_greenMode) {
|
||||
color = _green;
|
||||
|
@ -10,8 +10,8 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
/*globals toHex: false, debug: false*/
|
||||
/*exported CPU6502 */
|
||||
/*globals debug: false, toHex: false */
|
||||
|
||||
function CPU6502(options)
|
||||
{
|
||||
@ -1039,8 +1039,8 @@ function CPU6502(options)
|
||||
if (key in ops) {
|
||||
debug('overriding opcode ' + toHex(key));
|
||||
}
|
||||
ops[key] = cops[key];
|
||||
}
|
||||
ops[key] = cops[key];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1379,14 +1379,14 @@ function CPU6502(options)
|
||||
' P=' + toHex(sr) +
|
||||
' S=' + toHex(sp) +
|
||||
' ' +
|
||||
(sr & flags.N ? 'N' : '-') +
|
||||
(sr & flags.V ? 'V' : '-') +
|
||||
((sr & flags.N) ? 'N' : '-') +
|
||||
((sr & flags.V) ? 'V' : '-') +
|
||||
'-' +
|
||||
(sr & flags.B ? 'B' : '-') +
|
||||
(sr & flags.D ? 'D' : '-') +
|
||||
(sr & flags.I ? 'I' : '-') +
|
||||
(sr & flags.Z ? 'Z' : '-') +
|
||||
(sr & flags.C ? 'C' : '-');
|
||||
((sr & flags.B) ? 'B' : '-') +
|
||||
((sr & flags.D) ? 'D' : '-') +
|
||||
((sr & flags.I) ? 'I' : '-') +
|
||||
((sr & flags.Z) ? 'Z' : '-') +
|
||||
((sr & flags.C) ? 'C' : '-');
|
||||
},
|
||||
|
||||
read: function(page, off) {
|
||||
|
37
js/disk2.js
37
js/disk2.js
@ -10,12 +10,12 @@
|
||||
*/
|
||||
|
||||
/*exported DiskII */
|
||||
/*globals bytify, each: false, extend: false
|
||||
base64_encode, base64_decode
|
||||
Uint8Array
|
||||
/*globals bytify: false, each: false, extend: false, debug: false
|
||||
base64_decode: false, base64_encode: false
|
||||
Uint8Array: false
|
||||
*/
|
||||
|
||||
function DiskII(io, callbacks, slot)
|
||||
function DiskII(io, slot, callbacks)
|
||||
{
|
||||
'use strict';
|
||||
|
||||
@ -111,6 +111,7 @@ function DiskII(io, callbacks, slot)
|
||||
}
|
||||
|
||||
function _init() {
|
||||
debug('Disk ][ in slot', slot);
|
||||
each(LOC, function(key) {
|
||||
LOC[key] += slot * 0x10;
|
||||
});
|
||||
@ -358,11 +359,15 @@ function DiskII(io, callbacks, slot)
|
||||
}
|
||||
for (kdx = 0, jdx = 0x55; kdx < 0x100; kdx++) {
|
||||
data[kdx] <<= 1;
|
||||
if (data2[jdx] & 0x01) data[kdx] |= 0x01;
|
||||
if ((data2[jdx] & 0x01) !== 0) {
|
||||
data[kdx] |= 0x01;
|
||||
}
|
||||
data2[jdx] >>= 1;
|
||||
|
||||
data[kdx] <<= 1;
|
||||
if (data2[jdx] & 0x01) data[kdx] |= 0x01;
|
||||
if ((data2[jdx] & 0x01) !== 0) {
|
||||
data[kdx] |= 0x01;
|
||||
}
|
||||
data2[jdx] >>= 1;
|
||||
|
||||
if (--jdx < 0) jdx = 0x55;
|
||||
@ -492,7 +497,7 @@ function DiskII(io, callbacks, slot)
|
||||
if (callbacks.dirty) { callbacks.dirty(_drive, dirty); }
|
||||
}
|
||||
|
||||
var diskII_16 = [
|
||||
var diskII = [
|
||||
0xa2,0x20,0xa0,0x00,0xa2,0x03,0x86,0x3c,
|
||||
0x8a,0x0a,0x24,0x3c,0xf0,0x10,0x05,0x3c,
|
||||
0x49,0xff,0x29,0x7e,0xb0,0x08,0x4a,0xd0,
|
||||
@ -561,25 +566,28 @@ function DiskII(io, callbacks, slot)
|
||||
0x03,0x4c,0x01,0x03,0x4c,0x2d,0xff,0xff
|
||||
];
|
||||
*/
|
||||
var diskII = diskII_16;
|
||||
|
||||
_init();
|
||||
|
||||
return {
|
||||
start: function disk2_start() {
|
||||
io.registerSwitches(this, LOC);
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
|
||||
end: function disk2_end() {
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
|
||||
ioSwitch: function disk2_ioSwitch(off, val) {
|
||||
return _access(off, val);
|
||||
},
|
||||
|
||||
read: function disk2_read(page, off) {
|
||||
return diskII[off];
|
||||
},
|
||||
|
||||
write: function disk2_write() {},
|
||||
|
||||
reset: function disk2_reset() {
|
||||
if (_on) {
|
||||
_writeMode = false;
|
||||
@ -587,6 +595,7 @@ function DiskII(io, callbacks, slot)
|
||||
callbacks.driveLight(_drive, false);
|
||||
}
|
||||
},
|
||||
|
||||
getState: function disk2_getState() {
|
||||
function getDriveState(drive) {
|
||||
var result = {
|
||||
@ -613,7 +622,9 @@ function DiskII(io, callbacks, slot)
|
||||
drive: _drive
|
||||
};
|
||||
_drives.forEach(function (drive, idx) {
|
||||
result.drives[idx] = getDriveState(drive);
|
||||
var _drive = result.drives[idx] = getDriveState(drive);
|
||||
callbacks.driveLight(idx, _drive.on);
|
||||
callbacks.dirty(idx, _drive.dirty);
|
||||
});
|
||||
return result;
|
||||
},
|
||||
@ -635,7 +646,7 @@ function DiskII(io, callbacks, slot)
|
||||
return result;
|
||||
}
|
||||
state.drives.forEach(function(drive, idx) {
|
||||
_drives[idx] = setDriveState(state);
|
||||
_drives[idx] = setDriveState(drive);
|
||||
});
|
||||
_skip = state.skip;
|
||||
_latch = state.latch;
|
||||
@ -741,8 +752,8 @@ function DiskII(io, callbacks, slot)
|
||||
var flags =
|
||||
prefix[0x10] | (prefix[0x11] << 8) |
|
||||
(prefix[0x12] << 16) | (prefix[0x13] << 24);
|
||||
_cur.readOnly = (flags & 0x80000000) ? true : false;
|
||||
if (flags & 0x10) {
|
||||
_cur.readOnly = (flags & 0x80000000) !== 0;
|
||||
if ((flags & 0x10) !== 0) {
|
||||
_cur.volume = flags & 0xff;
|
||||
} else {
|
||||
_cur.volume = 254;
|
||||
|
@ -10,12 +10,12 @@
|
||||
*/
|
||||
|
||||
/*exported LanguageCard */
|
||||
/*globals RAM: false */
|
||||
/*globals debug: false
|
||||
RAM: false */
|
||||
|
||||
function LanguageCard(io, rom) {
|
||||
function LanguageCard(io, slot, rom) {
|
||||
'use strict';
|
||||
|
||||
var _io = io;
|
||||
var _rom = rom;
|
||||
var _bank1 = null;
|
||||
var _bank2 = null;
|
||||
@ -34,6 +34,8 @@ function LanguageCard(io, rom) {
|
||||
var _write2 = null;
|
||||
|
||||
function _init() {
|
||||
debug('Language card in slot', slot);
|
||||
|
||||
_bank1 = new RAM(0xd0, 0xdf);
|
||||
_bank2 = new RAM(0xd0, 0xdf);
|
||||
_ram = new RAM(0xe0, 0xff);
|
||||
@ -173,9 +175,9 @@ function LanguageCard(io, rom) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
start: function() {
|
||||
_io.registerSwitches(this, LOC);
|
||||
return 0xd0;
|
||||
},
|
||||
end: function() {
|
||||
|
356
js/main2.js
356
js/main2.js
@ -1,27 +1,29 @@
|
||||
/* globals debug: false, gup: false, hup: false
|
||||
/* globals debug: false, gup: false, hup: false, toHex: false,
|
||||
CPU6502: false,
|
||||
RAM: false,
|
||||
Apple2ROM: false
|
||||
Apple2ROM: false, IntBASIC: false, OriginalROM: false,
|
||||
apple2_charset: false,
|
||||
Apple2IO: false
|
||||
LoresPage: false, HiresPage: false, VideoModes: false
|
||||
scanlines: true,
|
||||
KeyBoard: false,
|
||||
Parallel: false,
|
||||
DiskII: false,
|
||||
RAMFactor: false,
|
||||
Printer: false,
|
||||
LanguageCard: false,
|
||||
RAMFactor: false,
|
||||
Thunderclock: false,
|
||||
Prefs: false,
|
||||
disk_index: false,
|
||||
initAudio: false, enableSound: false,
|
||||
initGamepad: false, processGamepad: false, gamepad: false,
|
||||
ApplesoftDump: false
|
||||
ApplesoftDump: false, SYMBOLS: false
|
||||
*/
|
||||
/* exported openLoad, openSave, doDelete,
|
||||
selectCategory, selectDisk, clickDisk, loadJSON,
|
||||
selectCategory, selectDisk, clickDisk,
|
||||
updateJoystick,
|
||||
pauseRun, step
|
||||
pauseRun, step,
|
||||
restoreState, saveState,
|
||||
dumpProgram, PageDebug
|
||||
*/
|
||||
|
||||
@ -35,6 +37,47 @@ var paused = false;
|
||||
|
||||
var hashtag;
|
||||
|
||||
var DEBUG = false;
|
||||
var TRACE = false;
|
||||
var MAX_TRACE = 256;
|
||||
var trace = [];
|
||||
|
||||
/*
|
||||
* Page viewer
|
||||
*/
|
||||
|
||||
function PageDebug(page)
|
||||
{
|
||||
var _page = page;
|
||||
|
||||
function _init() {
|
||||
var r, c;
|
||||
var row = $('<tr />').appendTo('#page' + toHex(_page));
|
||||
$('<th>\\</th>').appendTo(row);
|
||||
for (c = 0; c < 16; c++) {
|
||||
$('<th>' + toHex(c) + '</th>').appendTo(row);
|
||||
}
|
||||
for (r = 0; r < 16; r++) {
|
||||
row = $('<tr />').appendTo('#page' + toHex(_page));
|
||||
$('<th>' + toHex(r * 16) + '</th>').appendTo(row);
|
||||
for (c = 0; c < 16; c++) {
|
||||
$('<td>--</td>').appendTo(row).attr('id', 'page' + toHex(_page) + '-' + toHex(r * 16 + c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_init();
|
||||
|
||||
return {
|
||||
start: function() { return _page; },
|
||||
end: function() { return _page; },
|
||||
read: null,
|
||||
write: function(page, off, val) {
|
||||
$('#page' + toHex(page) + '-' + toHex(off)).text(toHex(val));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var disk_categories = {'Local Saves': []};
|
||||
var disk_sets = {};
|
||||
var disk_cur_name = [];
|
||||
@ -48,8 +91,8 @@ function DriveLights()
|
||||
on ? 'url(css/red-on-16.png)' :
|
||||
'url(css/red-off-16.png)');
|
||||
},
|
||||
dirty: function(drive, dirty) {
|
||||
$('#disksave' + drive).button('option', 'disabled', !dirty);
|
||||
dirty: function() {
|
||||
// $('#disksave' + drive).button('option', 'disabled', !dirty);
|
||||
},
|
||||
label: function(drive, label) {
|
||||
if (label) {
|
||||
@ -77,12 +120,11 @@ function DriveLights()
|
||||
var DISK_TYPES = ['dsk','do','po','raw','nib','2mg'];
|
||||
var TAPE_TYPES = ['wav','aiff','aif','mp3'];
|
||||
|
||||
var _saveDrive = 1;
|
||||
var _loadDrive = 1;
|
||||
var _currentDrive = 1;
|
||||
|
||||
function openLoad(drive, event)
|
||||
{
|
||||
_loadDrive = drive;
|
||||
_currentDrive = parseInt(drive, 10);
|
||||
if (event.metaKey) {
|
||||
openLoadHTTP(drive);
|
||||
} else {
|
||||
@ -95,11 +137,18 @@ function openLoad(drive, event)
|
||||
|
||||
function openSave(drive, event)
|
||||
{
|
||||
_saveDrive = drive;
|
||||
_currentDrive = parseInt(drive, 10);
|
||||
|
||||
var mimetype = 'application/octet-stream';
|
||||
var data = disk2.getBinary(drive);
|
||||
var a = $('#local_save_link');
|
||||
|
||||
var blob = new Blob([data], { 'type': mimetype });
|
||||
a.attr('href', window.URL.createObjectURL(blob));
|
||||
a.attr('download', drivelights.label(drive) + '.dsk');
|
||||
|
||||
if (event.metaKey) {
|
||||
dumpDisk(drive);
|
||||
} else if (event.altKey) {
|
||||
openSaveLocal(drive);
|
||||
} else {
|
||||
$('#save_name').val(drivelights.label(drive));
|
||||
$('#save').dialog('open');
|
||||
@ -108,16 +157,30 @@ function openSave(drive, event)
|
||||
|
||||
var loading = false;
|
||||
|
||||
function loadAjax(url) {
|
||||
function loadAjax(drive, url) {
|
||||
loading = true;
|
||||
$('#loading').dialog('open');
|
||||
|
||||
$.ajax({ url: url,
|
||||
cache: false,
|
||||
dataType: 'jsonp',
|
||||
jsonp: false,
|
||||
global: false
|
||||
});
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
modifiedSince: true,
|
||||
error: function(xhr, status, error) {
|
||||
alert(error || status);
|
||||
$('#loading').dialog('close');
|
||||
loading = false;
|
||||
},
|
||||
success: function(data) {
|
||||
if (data.type == 'binary') {
|
||||
loadBinary(drive, data);
|
||||
} else if ($.inArray(data.type, DISK_TYPES) >= 0) {
|
||||
loadDisk(drive, data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
$('#loading').dialog('close');
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doLoad() {
|
||||
@ -132,7 +195,7 @@ function doLoad() {
|
||||
|
||||
var files = $('#local_file').prop('files');
|
||||
if (files.length == 1) {
|
||||
doLoadLocal();
|
||||
doLoadLocal(_currentDrive);
|
||||
} else if (url) {
|
||||
var filename;
|
||||
$('#load').dialog('close');
|
||||
@ -141,22 +204,26 @@ function doLoad() {
|
||||
if (filename == '__manage') {
|
||||
openManage();
|
||||
} else {
|
||||
loadLocalStorage(_loadDrive, filename);
|
||||
loadLocalStorage(_currentDrive, filename);
|
||||
}
|
||||
} else {
|
||||
var r1 = /json\/disks\/(.*).json$/.exec(url);
|
||||
if (r1 && _loadDrive == 1) {
|
||||
if (r1) {
|
||||
filename = r1[1];
|
||||
document.location.hash = filename;
|
||||
} else {
|
||||
filename = url;
|
||||
}
|
||||
loadAjax(url);
|
||||
var parts = document.location.hash.split('|');
|
||||
parts[_currentDrive - 1] = filename;
|
||||
document.location.hash = parts.join('|');
|
||||
loadAjax(_currentDrive, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doSave() {
|
||||
var name = $('#save_name').val();
|
||||
saveLocalStorage(_saveDrive, name);
|
||||
saveLocalStorage(_currentDrive, name);
|
||||
$('#save').dialog('close');
|
||||
}
|
||||
|
||||
@ -166,14 +233,14 @@ function doDelete(name) {
|
||||
}
|
||||
}
|
||||
|
||||
function doLoadLocal() {
|
||||
function doLoadLocal(drive) {
|
||||
var files = $('#local_file').prop('files');
|
||||
if (files.length == 1) {
|
||||
var file = files[0];
|
||||
var parts = file.name.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if ($.inArray(ext, DISK_TYPES) >= 0) {
|
||||
doLoadLocalDisk(file);
|
||||
doLoadLocalDisk(drive, file);
|
||||
} else if ($.inArray(ext, TAPE_TYPES) >= 0) {
|
||||
doLoadLocalTape(file);
|
||||
} else {
|
||||
@ -183,13 +250,13 @@ function doLoadLocal() {
|
||||
}
|
||||
}
|
||||
|
||||
function doLoadLocalDisk(file) {
|
||||
function doLoadLocalDisk(drive, file) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function() {
|
||||
var parts = file.name.split('.');
|
||||
var name = parts[0], ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, this.result)) {
|
||||
drivelights.label(_saveDrive, name);
|
||||
if (disk2.setBinary(drive, name, ext, this.result)) {
|
||||
drivelights.label(drive, name);
|
||||
$('#load').dialog('close');
|
||||
initGamepad();
|
||||
}
|
||||
@ -251,7 +318,7 @@ function doLoadLocalTape(file) {
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function doLoadHTTP(_url) {
|
||||
function doLoadHTTP(drive, _url) {
|
||||
var url = _url || $('#http_url').val();
|
||||
if (url) {
|
||||
var req = new XMLHttpRequest();
|
||||
@ -262,8 +329,8 @@ function doLoadHTTP(_url) {
|
||||
var parts = url.split(/[\/\.]/);
|
||||
var name = decodeURIComponent(parts[parts.length - 2]);
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, req.response)) {
|
||||
drivelights.label(_saveDrive, name);
|
||||
if (disk2.setBinary(drive, name, ext, req.response)) {
|
||||
drivelights.label(drive, name);
|
||||
$('#http_load').dialog('close');
|
||||
initGamepad();
|
||||
}
|
||||
@ -273,23 +340,10 @@ function doLoadHTTP(_url) {
|
||||
}
|
||||
|
||||
function openLoadHTTP(drive) {
|
||||
_saveDrive = drive;
|
||||
_currentDrive = parseInt(drive, 10);
|
||||
$('#http_load').dialog('open');
|
||||
}
|
||||
|
||||
function openSaveLocal(drive) {
|
||||
_saveDrive = drive;
|
||||
var mimetype = 'application/octet-stream';
|
||||
var data = disk2.getBinary(drive);
|
||||
var a = $('#local_save_link');
|
||||
|
||||
var blob = new Blob([data], { 'type': mimetype });
|
||||
a.attr('href', window.URL.createObjectURL(blob));
|
||||
a.attr('download', drivelights.label(drive) + '.dsk');
|
||||
|
||||
$('#local_save').dialog('open');
|
||||
}
|
||||
|
||||
function openManage() {
|
||||
$('#manage').dialog('open');
|
||||
}
|
||||
@ -307,23 +361,27 @@ var hgr2 = new HiresPage(2);
|
||||
var gr = new LoresPage(1, apple2_charset);
|
||||
var gr2 = new LoresPage(2, apple2_charset);
|
||||
|
||||
var rom = new Apple2ROM();
|
||||
var dumper = new ApplesoftDump(ram2);
|
||||
var romVersion = prefs.readPref('computer_type');
|
||||
var rom;
|
||||
if (romVersion == 'apple2') {
|
||||
rom = new IntBASIC();
|
||||
} else if (romVersion == 'original') {
|
||||
rom = new OriginalROM();
|
||||
} else {
|
||||
rom = new Apple2ROM();
|
||||
}
|
||||
|
||||
var vm = new VideoModes(gr, hgr, gr2, hgr2);
|
||||
var mmu = {
|
||||
auxRom: function(slot, rom) {
|
||||
cpu.addPageHandler(rom);
|
||||
}
|
||||
};
|
||||
var dumper = new ApplesoftDump(cpu);
|
||||
|
||||
var drivelights = new DriveLights();
|
||||
var io = new Apple2IO(cpu, vm);
|
||||
var keyboard = new KeyBoard(io);
|
||||
var parallel = new Parallel(io, new Printer(), 1);
|
||||
var disk2 = new DiskII(io, drivelights, 6);
|
||||
var slinky = new RAMFactor(mmu, io, 2, 1024 * 1024);
|
||||
var clock = new Thunderclock(mmu, io, 7);
|
||||
var lc = new LanguageCard(io, rom);
|
||||
var lc = new LanguageCard(io, 0, rom);
|
||||
var parallel = new Parallel(io, 1, new Printer());
|
||||
var slinky = new RAMFactor(io, 2, 1024 * 1024);
|
||||
var disk2 = new DiskII(io, 6, drivelights);
|
||||
var clock = new Thunderclock(io, 7);
|
||||
|
||||
cpu.addPageHandler(ram1);
|
||||
cpu.addPageHandler(gr);
|
||||
@ -332,13 +390,14 @@ cpu.addPageHandler(ram2);
|
||||
cpu.addPageHandler(hgr);
|
||||
cpu.addPageHandler(hgr2);
|
||||
cpu.addPageHandler(ram3);
|
||||
|
||||
cpu.addPageHandler(lc);
|
||||
cpu.addPageHandler(io);
|
||||
cpu.addPageHandler(parallel);
|
||||
cpu.addPageHandler(slinky);
|
||||
cpu.addPageHandler(disk2);
|
||||
cpu.addPageHandler(clock);
|
||||
cpu.addPageHandler(lc);
|
||||
|
||||
io.addSlot(0, lc);
|
||||
io.addSlot(1, parallel);
|
||||
io.addSlot(2, slinky);
|
||||
io.addSlot(6, disk2);
|
||||
io.addSlot(7, clock);
|
||||
|
||||
var showFPS = false;
|
||||
|
||||
@ -403,7 +462,7 @@ function step()
|
||||
|
||||
var accelerated = false;
|
||||
|
||||
function updateSpeed()
|
||||
function updateCPU()
|
||||
{
|
||||
accelerated = $('#accelerator_toggle').prop('checked');
|
||||
kHz = accelerated ? 4092 : 1023;
|
||||
@ -429,6 +488,7 @@ function run(pc) {
|
||||
}
|
||||
|
||||
var ival = 30;
|
||||
|
||||
var now, last = Date.now();
|
||||
var runFn = function() {
|
||||
now = Date.now();
|
||||
@ -447,9 +507,23 @@ function run(pc) {
|
||||
processHash(hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loading) {
|
||||
cpu.stepCycles(step);
|
||||
if (DEBUG) {
|
||||
cpu.stepCyclesDebug(TRACE ? 1 : step, function() {
|
||||
var line = cpu.dumpRegisters() + ' ' +
|
||||
cpu.dumpPC(undefined, SYMBOLS);
|
||||
if (TRACE) {
|
||||
debug(line);
|
||||
} else {
|
||||
trace.push(line);
|
||||
if (trace.length > MAX_TRACE) {
|
||||
trace.shift();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cpu.stepCycles(step);
|
||||
}
|
||||
vm.blit();
|
||||
io.sampleTick();
|
||||
}
|
||||
@ -479,6 +553,65 @@ function reset()
|
||||
cpu.reset();
|
||||
}
|
||||
|
||||
var state = null;
|
||||
|
||||
function storeStateLocal() {
|
||||
window.localStorage['apple2.state'] = JSON.stringify(state);
|
||||
}
|
||||
|
||||
function restoreStateLocal() {
|
||||
var data = window.localStorage['apple2.state'];
|
||||
if (data) {
|
||||
state = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
if (state && !window.confirm('Overwrite Saved State?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {
|
||||
cpu: cpu.getState(),
|
||||
ram1: ram1.getState(),
|
||||
ram2: ram2.getState(),
|
||||
ram3: ram3.getState(),
|
||||
io: io.getState(),
|
||||
lc: lc.getState(),
|
||||
vm: vm.getState(),
|
||||
disk2: disk2.getState(),
|
||||
drivelights: drivelights.getState()
|
||||
};
|
||||
if (slinky) {
|
||||
state.slinky = slinky.getState();
|
||||
}
|
||||
|
||||
if (window.localStorage) {
|
||||
storeStateLocal();
|
||||
}
|
||||
}
|
||||
|
||||
function restoreState() {
|
||||
if (window.localStorage) {
|
||||
restoreStateLocal();
|
||||
}
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
cpu.setState(state.cpu);
|
||||
ram1.setState(state.ram1);
|
||||
ram2.setState(state.ram2);
|
||||
ram3.setState(state.ram3);
|
||||
io.setState(state.io);
|
||||
lc.setState(state.lc);
|
||||
vm.setState(state.vm);
|
||||
disk2.setState(state.disk2);
|
||||
drivelights.setState(state.drivelights);
|
||||
if (slinky && state.slinky) {
|
||||
slinky.setState(state.slinky);
|
||||
}
|
||||
}
|
||||
|
||||
function loadBinary(bin) {
|
||||
stop();
|
||||
for (var idx = 0; idx < bin.length; idx++) {
|
||||
@ -499,7 +632,7 @@ function selectCategory() {
|
||||
}
|
||||
var option = $('<option />').val(file.filename).text(name)
|
||||
.appendTo('#disk_select');
|
||||
if (disk_cur_name[_loadDrive] == name) {
|
||||
if (disk_cur_name[_currentDrive] == name) {
|
||||
option.attr('selected', 'selected');
|
||||
}
|
||||
}
|
||||
@ -514,7 +647,7 @@ function clickDisk() {
|
||||
doLoad();
|
||||
}
|
||||
|
||||
function loadDisk(disk) {
|
||||
function loadDisk(drive, disk) {
|
||||
var name = disk.name;
|
||||
var category = disk.category;
|
||||
|
||||
@ -522,25 +655,14 @@ function loadDisk(disk) {
|
||||
name += ' - ' + disk.disk;
|
||||
}
|
||||
|
||||
disk_cur_cat[_loadDrive] = category;
|
||||
disk_cur_name[_loadDrive] = name;
|
||||
disk_cur_cat[drive] = category;
|
||||
disk_cur_name[drive] = name;
|
||||
|
||||
drivelights.label(_loadDrive, name);
|
||||
disk2.setDisk(_loadDrive, disk);
|
||||
drivelights.label(drive, name);
|
||||
disk2.setDisk(drive, disk);
|
||||
initGamepad(disk.gamepad);
|
||||
}
|
||||
|
||||
function loadJSON(data) {
|
||||
if (data.type == 'binary') {
|
||||
loadBinary(data);
|
||||
} else if ($.inArray(data.type, DISK_TYPES) >= 0) {
|
||||
loadDisk(data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
$('#loading').dialog('close');
|
||||
loading = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* LocalStorage Disk Storage
|
||||
*/
|
||||
@ -608,16 +730,20 @@ function loadLocalStorage(drive, name) {
|
||||
}
|
||||
|
||||
function processHash(hash) {
|
||||
if (hash.indexOf('://') > 0) {
|
||||
var parts = hash.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (ext == 'json') {
|
||||
loadAjax(hash);
|
||||
var files = hash.split('|');
|
||||
for (var idx = 0; idx < files.length; idx++) {
|
||||
var file = files[idx];
|
||||
if (file.indexOf('://') > 0) {
|
||||
var parts = file.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (ext == 'json') {
|
||||
loadAjax(idx + 1, file);
|
||||
} else {
|
||||
doLoadHTTP(idx + 1, file);
|
||||
}
|
||||
} else {
|
||||
doLoadHTTP(hash);
|
||||
loadAjax(idx + 1, 'json/disks/' + file + '.json');
|
||||
}
|
||||
} else {
|
||||
loadAjax('json/disks/' + hash + '.json');
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,6 +752,14 @@ function processHash(hash) {
|
||||
*/
|
||||
|
||||
function _keydown(evt) {
|
||||
if (!focused || (!evt.metaKey || evt.ctrlKey)) {
|
||||
evt.preventDefault();
|
||||
|
||||
var key = keyboard.mapKeyEvent(evt);
|
||||
if (key != 0xff) {
|
||||
io.keyDown(key, evt.shiftKey);
|
||||
}
|
||||
}
|
||||
if (evt.keyCode === 112) { // F1 - Reset
|
||||
cpu.reset();
|
||||
} else if (evt.keyCode === 113) { // F2 - Full Screen
|
||||
@ -647,36 +781,31 @@ function _keydown(evt) {
|
||||
elem.mozRequestFullScreen();
|
||||
}
|
||||
}
|
||||
} else if (evt.keyCode === 114) { // F3
|
||||
io.keyDown(0x1b);
|
||||
} else if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(true);
|
||||
io.buttonDown(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(true);
|
||||
} else if (!focused && (!evt.metaKey || evt.ctrlKey)) {
|
||||
evt.preventDefault();
|
||||
|
||||
var key = keyboard.mapKeyEvent(evt);
|
||||
if (key != 0xff) {
|
||||
io.keyDown(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _keyup(evt) {
|
||||
if (!focused)
|
||||
io.keyUp();
|
||||
|
||||
if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(false);
|
||||
io.buttonUp(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(false);
|
||||
} else {
|
||||
if (!focused) {
|
||||
io.keyUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateScreen() {
|
||||
var green = $('#green_screen').prop('checked');
|
||||
scanlines = $('#show_scanlines').prop('checked');
|
||||
|
||||
vm.green(green);
|
||||
}
|
||||
@ -774,21 +903,30 @@ $(function() {
|
||||
keyboard.create($('#keyboard'));
|
||||
|
||||
if (prefs.havePrefs()) {
|
||||
$('input[type=checkbox]').each(function() {
|
||||
$('#options input[type=checkbox]').each(function() {
|
||||
var val = prefs.readPref(this.id);
|
||||
if (val)
|
||||
this.checked = JSON.parse(val);
|
||||
}).change(function() {
|
||||
prefs.writePref(this.id, JSON.stringify(this.checked));
|
||||
});
|
||||
$('#options select').each(function() {
|
||||
var val = prefs.readPref(this.id);
|
||||
if (val)
|
||||
this.value = val;
|
||||
}).change(function() {
|
||||
prefs.writePref(this.id, this.value);
|
||||
});
|
||||
}
|
||||
|
||||
reset();
|
||||
if (romVersion != 'original') {
|
||||
reset();
|
||||
}
|
||||
run();
|
||||
setInterval(updateKHz, 1000);
|
||||
updateSound();
|
||||
updateScreen();
|
||||
updateSpeed();
|
||||
updateCPU();
|
||||
|
||||
var cancel = function() { $(this).dialog('close'); };
|
||||
$('#loading').dialog({ autoOpen: false, modal: true });
|
||||
|
326
js/main2e.js
326
js/main2e.js
@ -1,25 +1,26 @@
|
||||
/* globals debug: false, gup: false, hup: false,
|
||||
/* globals debug: false, gup: false, hup: false, toHex: false
|
||||
CPU6502: false,
|
||||
Apple2eROM: false, Apple2eEnhancedROM: false,
|
||||
apple2e_charset: false,
|
||||
Apple2IO: false
|
||||
LoresPage: false, HiresPage: false, VideoModes: false
|
||||
LoresPage: false, HiresPage: false, VideoModes: false,
|
||||
scanlines: true,
|
||||
KeyBoard: false,
|
||||
Parallel: false,
|
||||
DiskII: false,
|
||||
RAMFactor: false,
|
||||
Printer: false,
|
||||
MMU: false,
|
||||
Slot3: false,
|
||||
RAMFactor: false,
|
||||
Thunderclock: false,
|
||||
Prefs: false,
|
||||
disk_index: false,
|
||||
initAudio: false, enableSound: false,
|
||||
initGamepad: false, processGamepad: false, gamepad: false,
|
||||
ApplesoftDump: false
|
||||
ApplesoftDump: false, SYMBOLS: false,
|
||||
*/
|
||||
/* exported openLoad, openSave, doDelete,
|
||||
selectCategory, selectDisk, clickDisk, loadJSON,
|
||||
selectCategory, selectDisk, clickDisk,
|
||||
updateJoystick,
|
||||
pauseRun, step,
|
||||
restoreState, saveState,
|
||||
@ -36,6 +37,47 @@ var paused = false;
|
||||
|
||||
var hashtag;
|
||||
|
||||
var DEBUG = false;
|
||||
var TRACE = false;
|
||||
var MAX_TRACE = 256;
|
||||
var trace = [];
|
||||
|
||||
/*
|
||||
* Page viewer
|
||||
*/
|
||||
|
||||
function PageDebug(page)
|
||||
{
|
||||
var _page = page;
|
||||
|
||||
function _init() {
|
||||
var r, c;
|
||||
var row = $('<tr />').appendTo('#page' + toHex(_page));
|
||||
$('<th>\\</th>').appendTo(row);
|
||||
for (c = 0; c < 16; c++) {
|
||||
$('<th>' + toHex(c) + '</th>').appendTo(row);
|
||||
}
|
||||
for (r = 0; r < 16; r++) {
|
||||
row = $('<tr />').appendTo('#page' + toHex(_page));
|
||||
$('<th>' + toHex(r * 16) + '</th>').appendTo(row);
|
||||
for (c = 0; c < 16; c++) {
|
||||
$('<td>--</td>').appendTo(row).attr('id', 'page' + toHex(_page) + '-' + toHex(r * 16 + c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_init();
|
||||
|
||||
return {
|
||||
start: function() { return _page; },
|
||||
end: function() { return _page; },
|
||||
read: null,
|
||||
write: function(page, off, val) {
|
||||
$('#page' + toHex(page) + '-' + toHex(off)).text(toHex(val));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var disk_categories = {'Local Saves': []};
|
||||
var disk_sets = {};
|
||||
var disk_cur_name = [];
|
||||
@ -49,8 +91,8 @@ function DriveLights()
|
||||
on ? 'url(css/red-on-16.png)' :
|
||||
'url(css/red-off-16.png)');
|
||||
},
|
||||
dirty: function(drive, dirty) {
|
||||
$('#disksave' + drive).button('option', 'disabled', !dirty);
|
||||
dirty: function() {
|
||||
// $('#disksave' + drive).button('option', 'disabled', !dirty);
|
||||
},
|
||||
label: function(drive, label) {
|
||||
if (label) {
|
||||
@ -78,12 +120,11 @@ function DriveLights()
|
||||
var DISK_TYPES = ['dsk','do','po','raw','nib','2mg'];
|
||||
var TAPE_TYPES = ['wav','aiff','aif','mp3'];
|
||||
|
||||
var _saveDrive = 1;
|
||||
var _loadDrive = 1;
|
||||
var _currentDrive = 1;
|
||||
|
||||
function openLoad(drive, event)
|
||||
{
|
||||
_loadDrive = drive;
|
||||
_currentDrive = parseInt(drive, 10);
|
||||
if (event.metaKey) {
|
||||
openLoadHTTP(drive);
|
||||
} else {
|
||||
@ -96,11 +137,18 @@ function openLoad(drive, event)
|
||||
|
||||
function openSave(drive, event)
|
||||
{
|
||||
_saveDrive = drive;
|
||||
_currentDrive = parseInt(drive, 10);
|
||||
|
||||
var mimetype = 'application/octet-stream';
|
||||
var data = disk2.getBinary(drive);
|
||||
var a = $('#local_save_link');
|
||||
|
||||
var blob = new Blob([data], { 'type': mimetype });
|
||||
a.attr('href', window.URL.createObjectURL(blob));
|
||||
a.attr('download', drivelights.label(drive) + '.dsk');
|
||||
|
||||
if (event.metaKey) {
|
||||
dumpDisk(drive);
|
||||
} else if (event.altKey) {
|
||||
openSaveLocal(drive);
|
||||
} else {
|
||||
$('#save_name').val(drivelights.label(drive));
|
||||
$('#save').dialog('open');
|
||||
@ -109,16 +157,30 @@ function openSave(drive, event)
|
||||
|
||||
var loading = false;
|
||||
|
||||
function loadAjax(url) {
|
||||
function loadAjax(drive, url) {
|
||||
loading = true;
|
||||
$('#loading').dialog('open');
|
||||
|
||||
$.ajax({ url: url,
|
||||
cache: false,
|
||||
dataType: 'jsonp',
|
||||
jsonp: false,
|
||||
global: false
|
||||
});
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
modifiedSince: true,
|
||||
error: function(xhr, status, error) {
|
||||
alert(error || status);
|
||||
$('#loading').dialog('close');
|
||||
loading = false;
|
||||
},
|
||||
success: function(data) {
|
||||
if (data.type == 'binary') {
|
||||
loadBinary(drive, data);
|
||||
} else if ($.inArray(data.type, DISK_TYPES) >= 0) {
|
||||
loadDisk(drive, data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
$('#loading').dialog('close');
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doLoad() {
|
||||
@ -133,7 +195,7 @@ function doLoad() {
|
||||
|
||||
var files = $('#local_file').prop('files');
|
||||
if (files.length == 1) {
|
||||
doLoadLocal();
|
||||
doLoadLocal(_currentDrive);
|
||||
} else if (url) {
|
||||
var filename;
|
||||
$('#load').dialog('close');
|
||||
@ -142,22 +204,26 @@ function doLoad() {
|
||||
if (filename == '__manage') {
|
||||
openManage();
|
||||
} else {
|
||||
loadLocalStorage(_loadDrive, filename);
|
||||
loadLocalStorage(_currentDrive, filename);
|
||||
}
|
||||
} else {
|
||||
var r1 = /json\/disks\/(.*).json$/.exec(url);
|
||||
if (r1 && _loadDrive == 1) {
|
||||
if (r1) {
|
||||
filename = r1[1];
|
||||
document.location.hash = filename;
|
||||
} else {
|
||||
filename = url;
|
||||
}
|
||||
loadAjax(url);
|
||||
var parts = document.location.hash.split('|');
|
||||
parts[_currentDrive - 1] = filename;
|
||||
document.location.hash = parts.join('|');
|
||||
loadAjax(_currentDrive, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doSave() {
|
||||
var name = $('#save_name').val();
|
||||
saveLocalStorage(_saveDrive, name);
|
||||
saveLocalStorage(_currentDrive, name);
|
||||
$('#save').dialog('close');
|
||||
}
|
||||
|
||||
@ -167,14 +233,14 @@ function doDelete(name) {
|
||||
}
|
||||
}
|
||||
|
||||
function doLoadLocal() {
|
||||
function doLoadLocal(drive) {
|
||||
var files = $('#local_file').prop('files');
|
||||
if (files.length == 1) {
|
||||
var file = files[0];
|
||||
var parts = file.name.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if ($.inArray(ext, DISK_TYPES) >= 0) {
|
||||
doLoadLocalDisk(file);
|
||||
doLoadLocalDisk(drive, file);
|
||||
} else if ($.inArray(ext, TAPE_TYPES) >= 0) {
|
||||
doLoadLocalTape(file);
|
||||
} else {
|
||||
@ -184,13 +250,13 @@ function doLoadLocal() {
|
||||
}
|
||||
}
|
||||
|
||||
function doLoadLocalDisk(file) {
|
||||
function doLoadLocalDisk(drive, file) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function() {
|
||||
var parts = file.name.split('.');
|
||||
var name = parts[0], ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, this.result)) {
|
||||
drivelights.label(_saveDrive, name);
|
||||
if (disk2.setBinary(drive, name, ext, this.result)) {
|
||||
drivelights.label(drive, name);
|
||||
$('#load').dialog('close');
|
||||
initGamepad();
|
||||
}
|
||||
@ -252,7 +318,7 @@ function doLoadLocalTape(file) {
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function doLoadHTTP(_url) {
|
||||
function doLoadHTTP(drive, _url) {
|
||||
var url = _url || $('#http_url').val();
|
||||
if (url) {
|
||||
var req = new XMLHttpRequest();
|
||||
@ -263,8 +329,8 @@ function doLoadHTTP(_url) {
|
||||
var parts = url.split(/[\/\.]/);
|
||||
var name = decodeURIComponent(parts[parts.length - 2]);
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, req.response)) {
|
||||
drivelights.label(_saveDrive, name);
|
||||
if (disk2.setBinary(drive, name, ext, req.response)) {
|
||||
drivelights.label(drive, name);
|
||||
$('#http_load').dialog('close');
|
||||
initGamepad();
|
||||
}
|
||||
@ -274,30 +340,18 @@ function doLoadHTTP(_url) {
|
||||
}
|
||||
|
||||
function openLoadHTTP(drive) {
|
||||
_saveDrive = drive;
|
||||
_currentDrive = parseInt(drive, 10);
|
||||
$('#http_load').dialog('open');
|
||||
}
|
||||
|
||||
function openSaveLocal(drive) {
|
||||
_saveDrive = drive;
|
||||
var mimetype = 'application/octet-stream';
|
||||
var data = disk2.getBinary(drive);
|
||||
var a = $('#local_save_link');
|
||||
|
||||
var blob = new Blob([data], { 'type': mimetype });
|
||||
a.attr('href', window.URL.createObjectURL(blob));
|
||||
a.attr('download', drivelights.label(drive) + '.dsk');
|
||||
|
||||
$('#local_save').dialog('open');
|
||||
}
|
||||
|
||||
function openManage() {
|
||||
$('#manage').dialog('open');
|
||||
}
|
||||
|
||||
var prefs = new Prefs();
|
||||
var enhanced = true;
|
||||
var enhanced = prefs.readPref('computer_type') != 'apple2e';
|
||||
var runTimer = null;
|
||||
|
||||
var cpu = new CPU6502({'65C02': enhanced});
|
||||
|
||||
var hgr = new HiresPage(1);
|
||||
@ -311,29 +365,30 @@ if (enhanced) {
|
||||
} else {
|
||||
rom = new Apple2eROM();
|
||||
}
|
||||
|
||||
var vm = new VideoModes(gr, hgr, gr2, hgr2);
|
||||
var dumper = new ApplesoftDump(cpu);
|
||||
|
||||
var drivelights = new DriveLights();
|
||||
var io = new Apple2IO(cpu, vm);
|
||||
var keyboard = new KeyBoard(io);
|
||||
|
||||
var mmu = new MMU(cpu, gr, gr2, hgr, hgr2, io, rom);
|
||||
var dumper = new ApplesoftDump(mmu);
|
||||
|
||||
var parallel = new Parallel(io, new Printer(), 1);
|
||||
var disk2 = new DiskII(io, drivelights, 6);
|
||||
var slot3 = new Slot3(mmu, rom);
|
||||
var slinky = new RAMFactor(mmu, io, 2, 1024 * 1024);
|
||||
var clock = new Thunderclock(mmu, io, 7);
|
||||
|
||||
mmu.addSlot(1, parallel);
|
||||
mmu.addSlot(2, slinky);
|
||||
mmu.addSlot(3, slot3);
|
||||
mmu.addSlot(6, disk2);
|
||||
mmu.addSlot(7, clock);
|
||||
|
||||
cpu.addPageHandler(mmu);
|
||||
|
||||
var parallel = new Parallel(io, 1, new Printer());
|
||||
var slinky = new RAMFactor(io, 2, 1024 * 1024);
|
||||
var slot3 = new Slot3(io, 3, rom);
|
||||
var disk2 = new DiskII(io, 6, drivelights);
|
||||
var clock = new Thunderclock(io, 7);
|
||||
|
||||
io.addSlot(1, parallel);
|
||||
io.addSlot(2, slinky);
|
||||
io.addSlot(3, slot3);
|
||||
io.addSlot(6, disk2);
|
||||
io.addSlot(7, clock);
|
||||
|
||||
var showFPS = false;
|
||||
|
||||
function updateKHz() {
|
||||
@ -397,7 +452,7 @@ function step()
|
||||
|
||||
var accelerated = false;
|
||||
|
||||
function updateSpeed()
|
||||
function updateCPU()
|
||||
{
|
||||
accelerated = $('#accelerator_toggle').prop('checked');
|
||||
kHz = accelerated ? 4092 : 1023;
|
||||
@ -423,6 +478,7 @@ function run(pc) {
|
||||
}
|
||||
|
||||
var ival = 30;
|
||||
|
||||
var now, last = Date.now();
|
||||
var runFn = function() {
|
||||
now = Date.now();
|
||||
@ -441,10 +497,24 @@ function run(pc) {
|
||||
processHash(hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loading) {
|
||||
mmu.resetVB();
|
||||
cpu.stepCycles(step);
|
||||
if (DEBUG) {
|
||||
cpu.stepCyclesDebug(TRACE ? 1 : step, function() {
|
||||
var line = cpu.dumpRegisters() + ' ' +
|
||||
cpu.dumpPC(undefined, SYMBOLS);
|
||||
if (TRACE) {
|
||||
debug(line);
|
||||
} else {
|
||||
trace.push(line);
|
||||
if (trace.length > MAX_TRACE) {
|
||||
trace.shift();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cpu.stepCycles(step);
|
||||
}
|
||||
vm.blit();
|
||||
io.sampleTick();
|
||||
}
|
||||
@ -474,6 +544,59 @@ function reset()
|
||||
cpu.reset();
|
||||
}
|
||||
|
||||
var state = null;
|
||||
|
||||
function storeStateLocal() {
|
||||
window.localStorage['apple2.state'] = JSON.stringify(state);
|
||||
}
|
||||
|
||||
function restoreStateLocal() {
|
||||
var data = window.localStorage['apple2.state'];
|
||||
if (data) {
|
||||
state = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
if (state && !window.confirm('Overwrite Saved State?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = {
|
||||
cpu: cpu.getState(),
|
||||
io: io.getState(),
|
||||
mmu: mmu.getState(),
|
||||
vm: vm.getState(),
|
||||
disk2: disk2.getState(),
|
||||
drivelights: drivelights.getState()
|
||||
};
|
||||
if (slinky) {
|
||||
state.slinky = slinky.getState();
|
||||
}
|
||||
|
||||
if (window.localStorage) {
|
||||
storeStateLocal();
|
||||
}
|
||||
}
|
||||
|
||||
function restoreState() {
|
||||
if (window.localStorage) {
|
||||
restoreStateLocal();
|
||||
}
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
cpu.setState(state.cpu);
|
||||
io.setState(state.io);
|
||||
mmu.setState(state.mmu);
|
||||
vm.setState(state.vm);
|
||||
disk2.setState(state.disk2);
|
||||
drivelights.setState(state.drivelights);
|
||||
if (slinky && state.slinky) {
|
||||
slinky.setState(state.slinky);
|
||||
}
|
||||
}
|
||||
|
||||
function loadBinary(bin) {
|
||||
stop();
|
||||
for (var idx = 0; idx < bin.length; idx++) {
|
||||
@ -494,7 +617,7 @@ function selectCategory() {
|
||||
}
|
||||
var option = $('<option />').val(file.filename).text(name)
|
||||
.appendTo('#disk_select');
|
||||
if (disk_cur_name[_loadDrive] == name) {
|
||||
if (disk_cur_name[_currentDrive] == name) {
|
||||
option.attr('selected', 'selected');
|
||||
}
|
||||
}
|
||||
@ -509,7 +632,7 @@ function clickDisk() {
|
||||
doLoad();
|
||||
}
|
||||
|
||||
function loadDisk(disk) {
|
||||
function loadDisk(drive, disk) {
|
||||
var name = disk.name;
|
||||
var category = disk.category;
|
||||
|
||||
@ -517,25 +640,14 @@ function loadDisk(disk) {
|
||||
name += ' - ' + disk.disk;
|
||||
}
|
||||
|
||||
disk_cur_cat[_loadDrive] = category;
|
||||
disk_cur_name[_loadDrive] = name;
|
||||
disk_cur_cat[drive] = category;
|
||||
disk_cur_name[drive] = name;
|
||||
|
||||
drivelights.label(_loadDrive, name);
|
||||
disk2.setDisk(_loadDrive, disk);
|
||||
drivelights.label(drive, name);
|
||||
disk2.setDisk(drive, disk);
|
||||
initGamepad(disk.gamepad);
|
||||
}
|
||||
|
||||
function loadJSON(data) {
|
||||
if (data.type == 'binary') {
|
||||
loadBinary(data);
|
||||
} else if ($.inArray(data.type, DISK_TYPES) >= 0) {
|
||||
loadDisk(data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
$('#loading').dialog('close');
|
||||
loading = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* LocalStorage Disk Storage
|
||||
*/
|
||||
@ -603,16 +715,20 @@ function loadLocalStorage(drive, name) {
|
||||
}
|
||||
|
||||
function processHash(hash) {
|
||||
if (hash.indexOf('://') > 0) {
|
||||
var parts = hash.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (ext == 'json') {
|
||||
loadAjax(hash);
|
||||
var files = hash.split('|');
|
||||
for (var idx = 0; idx < files.length; idx++) {
|
||||
var file = files[idx];
|
||||
if (file.indexOf('://') > 0) {
|
||||
var parts = file.split('.');
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if (ext == 'json') {
|
||||
loadAjax(idx + 1, file);
|
||||
} else {
|
||||
doLoadHTTP(idx + 1, file);
|
||||
}
|
||||
} else {
|
||||
doLoadHTTP(hash);
|
||||
loadAjax(idx + 1, 'json/disks/' + file + '.json');
|
||||
}
|
||||
} else {
|
||||
loadAjax('json/disks/' + hash + '.json');
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,19 +736,16 @@ function processHash(hash) {
|
||||
* Keyboard/Gamepad routines
|
||||
*/
|
||||
|
||||
var _key;
|
||||
function _keydown(evt) {
|
||||
if (!focused) {
|
||||
evt.preventDefault();
|
||||
|
||||
var key = keyboard.mapKeyEvent(evt);
|
||||
if (key != 0xff) {
|
||||
if (_key != 0xff) io.keyUp();
|
||||
io.keyDown(key, evt.shiftKey);
|
||||
_key = key;
|
||||
}
|
||||
}
|
||||
if (evt.keyCode === 112) {
|
||||
if (evt.keyCode === 112) { // F1 - Reset
|
||||
cpu.reset();
|
||||
} else if (evt.keyCode === 113) { // F2 - Full Screen
|
||||
var elem = document.getElementById('screen');
|
||||
@ -653,12 +766,11 @@ function _keydown(evt) {
|
||||
elem.mozRequestFullScreen();
|
||||
}
|
||||
}
|
||||
} else if (evt.keyCode === 114) {
|
||||
} else if (evt.keyCode === 114) { // F3
|
||||
io.keyDown(0x1b);
|
||||
_key = 0x1b;
|
||||
} else if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(true);
|
||||
io.buttonDown(2, true);
|
||||
io.buttonDown(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(true);
|
||||
} else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
|
||||
@ -669,14 +781,12 @@ function _keydown(evt) {
|
||||
}
|
||||
|
||||
function _keyup(evt) {
|
||||
_key = 0xff;
|
||||
|
||||
if (!focused)
|
||||
io.keyUp();
|
||||
|
||||
if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(false);
|
||||
io.buttonDown(2, false);
|
||||
io.buttonUp(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(false);
|
||||
} else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
|
||||
@ -688,6 +798,7 @@ function _keyup(evt) {
|
||||
|
||||
function updateScreen() {
|
||||
var green = $('#green_screen').prop('checked');
|
||||
scanlines = $('#show_scanlines').prop('checked');
|
||||
|
||||
vm.green(green);
|
||||
}
|
||||
@ -788,13 +899,20 @@ $(function() {
|
||||
keyboard.create($('#keyboard'));
|
||||
|
||||
if (prefs.havePrefs()) {
|
||||
$('input[type=checkbox]').each(function() {
|
||||
$('#options input[type=checkbox]').each(function() {
|
||||
var val = prefs.readPref(this.id);
|
||||
if (val)
|
||||
this.checked = JSON.parse(val);
|
||||
}).change(function() {
|
||||
prefs.writePref(this.id, JSON.stringify(this.checked));
|
||||
});
|
||||
$('#options select').each(function() {
|
||||
var val = prefs.readPref(this.id);
|
||||
if (val)
|
||||
this.value = val;
|
||||
}).change(function() {
|
||||
prefs.writePref(this.id, this.value);
|
||||
});
|
||||
}
|
||||
|
||||
reset();
|
||||
@ -802,7 +920,7 @@ $(function() {
|
||||
setInterval(updateKHz, 1000);
|
||||
updateSound();
|
||||
updateScreen();
|
||||
updateSpeed();
|
||||
updateCPU();
|
||||
|
||||
var cancel = function() { $(this).dialog('close'); };
|
||||
$('#loading').dialog({ autoOpen: false, modal: true });
|
||||
@ -823,10 +941,6 @@ $(function() {
|
||||
modal: true,
|
||||
width: 320,
|
||||
buttons: {'Close': cancel }});
|
||||
$('#local_save').dialog({ autoOpen: false,
|
||||
modal: true,
|
||||
width: 530,
|
||||
buttons: {'OK': cancel }});
|
||||
$('#http_load').dialog({ autoOpen: false,
|
||||
modal: true,
|
||||
width: 530,
|
||||
|
223
js/mmu.js
223
js/mmu.js
@ -21,8 +21,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
|
||||
var idx;
|
||||
|
||||
var _auxRom = 0x00;
|
||||
|
||||
var _readPages = new Array(0x100);
|
||||
var _writePages = new Array(0x100);
|
||||
var _pages = new Array(0x100);
|
||||
@ -47,6 +45,69 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
|
||||
var _vbEnd = 0;
|
||||
|
||||
/*
|
||||
* I/O Switch locations
|
||||
*/
|
||||
|
||||
var LOC = {
|
||||
// 80 Column
|
||||
_80STOREOFF: 0x00,
|
||||
_80STOREON: 0x01,
|
||||
|
||||
// Aux RAM
|
||||
RAMRDOFF: 0x02,
|
||||
RAMRDON: 0x03,
|
||||
|
||||
RAMWROFF: 0x04,
|
||||
RAMWRON: 0x05,
|
||||
|
||||
// Bank switched ROM
|
||||
INTCXROMOFF: 0x06,
|
||||
INTCXROMON: 0x07,
|
||||
ALTZPOFF: 0x08,
|
||||
ALTZPON: 0x09,
|
||||
SLOTC3ROMOFF: 0x0A,
|
||||
SLOTC3ROMON: 0x0B,
|
||||
|
||||
// Status
|
||||
BSRBANK2: 0x11,
|
||||
BSRREADRAM: 0x12,
|
||||
RAMRD: 0x13,
|
||||
RAMWRT: 0x14,
|
||||
INTCXROM: 0x15,
|
||||
ALTZP: 0x16,
|
||||
SLOTC3ROM: 0x17,
|
||||
_80STORE: 0x18,
|
||||
VERTBLANK: 0x19,
|
||||
|
||||
PAGE1: 0x54, // select text/graphics page1 main/aux
|
||||
PAGE2: 0x55, // select text/graphics page2 main/aux
|
||||
|
||||
// Bank 2
|
||||
READBSR2: 0x80,
|
||||
WRITEBSR2: 0x81,
|
||||
OFFBSR2: 0x82,
|
||||
READWRBSR2: 0x83,
|
||||
|
||||
// Shadow Bank 2
|
||||
_READBSR2: 0x84,
|
||||
_WRITEBSR2: 0x85,
|
||||
_OFFBSR2: 0x86,
|
||||
_READWRBSR2: 0x87,
|
||||
|
||||
// Bank 1
|
||||
READBSR1: 0x88,
|
||||
WRITEBSR1: 0x89,
|
||||
OFFBSR1: 0x8a,
|
||||
READWRBSR1: 0x8b,
|
||||
|
||||
// Shadow Bank 1
|
||||
_READBSR1: 0x8c,
|
||||
_WRITEBSR1: 0x8d,
|
||||
_OFFBSR1: 0x8e,
|
||||
_READWRBSR1: 0x8f
|
||||
};
|
||||
|
||||
function _initSwitches() {
|
||||
_bank1 = true;
|
||||
_readbsr = false;
|
||||
@ -64,28 +125,69 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
}
|
||||
|
||||
function _debug() {
|
||||
// debug.apply(arguments);
|
||||
debug.apply(arguments);
|
||||
}
|
||||
|
||||
var _last = 0x00;
|
||||
|
||||
function EmptySlots() {
|
||||
function Switches() {
|
||||
var locs = {};
|
||||
|
||||
for (var loc in LOC) {
|
||||
if (LOC.hasOwnProperty(loc)) {
|
||||
locs[LOC[loc]] = loc;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: function slot_start() {
|
||||
return 0xC1;
|
||||
start: function() {
|
||||
return 0xC0;
|
||||
},
|
||||
end: function slot_end() {
|
||||
return 0xCF;
|
||||
end: function() {
|
||||
return 0xC0;
|
||||
},
|
||||
read: function slot_read() {
|
||||
return 0x00;
|
||||
read: function(page, off) {
|
||||
var result;
|
||||
if (off in locs) {
|
||||
result = _access(off);
|
||||
} else {
|
||||
result = io.ioSwitch(off);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
write: function slot_write() {
|
||||
write: function(page, off, val) {
|
||||
if (off in locs) {
|
||||
_access(off, val);
|
||||
} else {
|
||||
io.ioSwitch(off, val);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var emptyslots = new EmptySlots();
|
||||
function AuxRom() {
|
||||
return {
|
||||
start: function() {
|
||||
return 0xC1;
|
||||
},
|
||||
end: function() {
|
||||
return 0xCF;
|
||||
},
|
||||
read: function(page, off) {
|
||||
var result;
|
||||
if (page == 0xc3) {
|
||||
result = io.read(page, off);
|
||||
} else {
|
||||
result = rom.read(page, off);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
write: function() {}
|
||||
};
|
||||
}
|
||||
|
||||
var switches = new Switches();
|
||||
var auxRom = new AuxRom();
|
||||
|
||||
var mem00_01 = [new RAM(0x0, 0x1), new RAM(0x0, 0x1)];
|
||||
var mem02_03 = [new RAM(0x2, 0x3), new RAM(0x2, 0x3)];
|
||||
@ -95,8 +197,8 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
var mem20_3F = [hires1.bank0(), hires1.bank1()];
|
||||
var mem40_5F = [hires2.bank0(), hires2.bank1()];
|
||||
var mem60_BF = [new RAM(0x60,0xBF), new RAM(0x60,0xBF)];
|
||||
var memC0_C0 = [io];
|
||||
// var memC1_CF = [emptyslots, rom];
|
||||
var memC0_C0 = [switches];
|
||||
var memC1_CF = [io, auxRom];
|
||||
var memD0_DF = [rom,
|
||||
new RAM(0xD0,0xDF), new RAM(0xD0,0xDF),
|
||||
new RAM(0xD0,0xDF), new RAM(0xD0,0xDF)];
|
||||
@ -161,7 +263,7 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
// Slots
|
||||
for (idx = 0xc1; idx < 0xd0; idx++) {
|
||||
_pages[idx] = [emptyslots, rom]; // memC1_CF;
|
||||
_pages[idx] = memC1_CF;
|
||||
_readPages[idx] = _pages[idx][0];
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
@ -178,69 +280,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
_writePages[idx] = _pages[idx][0];
|
||||
}
|
||||
|
||||
/*
|
||||
* I/O Switch locations
|
||||
*/
|
||||
|
||||
var LOC = {
|
||||
// 80 Column
|
||||
_80STOREOFF: 0x00,
|
||||
_80STOREON: 0x01,
|
||||
|
||||
// Aux RAM
|
||||
RAMRDOFF: 0x02,
|
||||
RAMRDON: 0x03,
|
||||
|
||||
RAMWROFF: 0x04,
|
||||
RAMWRON: 0x05,
|
||||
|
||||
// Bank switched ROM
|
||||
INTCXROMOFF: 0x06,
|
||||
INTCXROMON: 0x07,
|
||||
ALTZPOFF: 0x08,
|
||||
ALTZPON: 0x09,
|
||||
SLOTC3ROMOFF: 0x0A,
|
||||
SLOTC3ROMON: 0x0B,
|
||||
|
||||
// Status
|
||||
BSRBANK2: 0x11,
|
||||
BSRREADRAM: 0x12,
|
||||
RAMRD: 0x13,
|
||||
RAMWRT: 0x14,
|
||||
INTCXROM: 0x15,
|
||||
ALTZP: 0x16,
|
||||
SLOTC3ROM: 0x17,
|
||||
_80STORE: 0x18,
|
||||
VERTBLANK: 0x19,
|
||||
|
||||
PAGE1: 0x54, // select text/graphics page1 main/aux
|
||||
PAGE2: 0x55, // select text/graphics page2 main/aux
|
||||
|
||||
// Bank 2
|
||||
READBSR2: 0x80,
|
||||
WRITEBSR2: 0x81,
|
||||
OFFBSR2: 0x82,
|
||||
READWRBSR2: 0x83,
|
||||
|
||||
// Shadow Bank 2
|
||||
_READBSR2: 0x84,
|
||||
_WRITEBSR2: 0x85,
|
||||
_OFFBSR2: 0x86,
|
||||
_READWRBSR2: 0x87,
|
||||
|
||||
// Bank 1
|
||||
READBSR1: 0x88,
|
||||
WRITEBSR1: 0x89,
|
||||
OFFBSR1: 0x8a,
|
||||
READWRBSR1: 0x8b,
|
||||
|
||||
// Shadow Bank 1
|
||||
_READBSR1: 0x8c,
|
||||
_WRITEBSR1: 0x8d,
|
||||
_OFFBSR1: 0x8e,
|
||||
_READWRBSR1: 0x8f
|
||||
};
|
||||
|
||||
function _updateBanks() {
|
||||
if (_auxRamRead) {
|
||||
for (idx = 0x02; idx < 0xC0; idx++) {
|
||||
@ -549,7 +588,7 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
case LOC._80STORE: // 0xC018
|
||||
result = _80store ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.VERTBLANK: // 0xC018
|
||||
case LOC.VERTBLANK: // 0xC019
|
||||
// result = cpu.cycles() % 20 < 5 ? 0x80 : 0x00;
|
||||
result = (cpu.cycles() < _vbEnd) ? 0x80 : 0x00;
|
||||
break;
|
||||
@ -577,17 +616,11 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
lores1.start();
|
||||
lores2.start();
|
||||
|
||||
// Do us afterward because we override some of the above
|
||||
io.registerSwitches(this, LOC);
|
||||
|
||||
return 0x00;
|
||||
},
|
||||
end: function mmu_end() {
|
||||
return 0xff;
|
||||
},
|
||||
ioSwitch: function mmu_ioswitch(off, val) {
|
||||
return _access(off, val);
|
||||
},
|
||||
reset: function() {
|
||||
_initSwitches();
|
||||
_updateBanks();
|
||||
@ -603,24 +636,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
write: function mmu_write(page, off, val) {
|
||||
_writePages[page].write(page, off, val);
|
||||
},
|
||||
addSlot: function mmu_addSlot(slot, card) {
|
||||
if (slot == 3) {
|
||||
_pages[0xc0 + slot][1] = card;
|
||||
}
|
||||
_pages[0xc0 + slot][0] = card;
|
||||
card.start();
|
||||
},
|
||||
auxRom: function mmu_auxRom(slot, rom) {
|
||||
var idx;
|
||||
if (_auxRom != slot) {
|
||||
_debug("Slot " + slot + " expansion rom added");
|
||||
_auxRom = slot;
|
||||
for (idx = 0xc8; idx < 0xd0; idx++) {
|
||||
_pages[idx][0] = rom;
|
||||
}
|
||||
_updateBanks();
|
||||
}
|
||||
},
|
||||
resetVB: function mmu_resetVB() {
|
||||
_vbEnd = cpu.cycles() + 1000;
|
||||
},
|
||||
@ -631,7 +646,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
bank1: _bank1,
|
||||
last: _last,
|
||||
|
||||
auxRom: _auxRom,
|
||||
intcxrom: _intcxrom,
|
||||
slot3rom: _slot3rom,
|
||||
auxRamRead: _auxRamRead,
|
||||
@ -655,7 +669,6 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
_writebsr = state.writebsr;
|
||||
_bank1 = state.bank1;
|
||||
|
||||
_auxRom = state.auxRom;
|
||||
_intcxrom = state.intcxrom;
|
||||
_slot3rom = state.slot3rom;
|
||||
_auxRamRead = state.auxRamRead;
|
||||
|
@ -10,18 +10,19 @@
|
||||
*/
|
||||
|
||||
/*exported Parallel */
|
||||
/*globals debug: false */
|
||||
|
||||
function Parallel(io, cbs, slot) {
|
||||
function Parallel(io, slot, cbs) {
|
||||
'use strict';
|
||||
|
||||
slot = slot || 1;
|
||||
|
||||
debug('Parallel card in slot', slot);
|
||||
|
||||
var LOC = {
|
||||
IOREG: 0x80
|
||||
};
|
||||
|
||||
var _cbs = cbs;
|
||||
|
||||
var rom = [
|
||||
0x18,0xb0,0x38,0x48,0x8a,0x48,0x98,0x48,
|
||||
0x08,0x78,0x20,0x58,0xff,0xba,0x68,0x68,
|
||||
@ -57,18 +58,23 @@ function Parallel(io, cbs, slot) {
|
||||
0xff,0xf0,0x03,0xfe,0x38,0x07,0x70,0x84
|
||||
];
|
||||
|
||||
LOC.IOREG += 0x10 * slot;
|
||||
|
||||
function _access(off, val) {
|
||||
if (off == LOC.IOREG && val && 'putChar' in cbs) {
|
||||
cbs.putChar(val);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: function() {
|
||||
LOC.IOREG += 0x10 * slot;
|
||||
io.registerSwitches(this, LOC);
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
end: function() {
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
ioSwitch: function(off, val) {
|
||||
if (off == LOC.IOREG && val && 'putChar' in _cbs)
|
||||
_cbs.putChar(val);
|
||||
ioSwitch: function (off, val) {
|
||||
return _access(off, val);
|
||||
},
|
||||
read: function(page, off) {
|
||||
return rom[off];
|
||||
|
@ -9,11 +9,11 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
/*exported RAMFactor*/
|
||||
/*globals allocMem: false, bytify: false, each: false,
|
||||
/*exported RAMFactor */
|
||||
/*globals allocMem: false, bytify: false, debug: false, each: false,
|
||||
base64_encode: false, base64_decode: false
|
||||
*/
|
||||
function RAMFactor(mmu, io, slot, size) {
|
||||
function RAMFactor(io, slot, size) {
|
||||
'use strict';
|
||||
|
||||
var rom = [
|
||||
@ -1066,6 +1066,8 @@ function RAMFactor(mmu, io, slot, size) {
|
||||
};
|
||||
|
||||
function _init() {
|
||||
debug('RAMFactor card in slot', slot);
|
||||
|
||||
each(LOC, function(key) {
|
||||
LOC[key] += slot * 0x10;
|
||||
});
|
||||
@ -1154,19 +1156,6 @@ function RAMFactor(mmu, io, slot, size) {
|
||||
return result;
|
||||
}
|
||||
|
||||
var auxRomFn = {
|
||||
start: function auxRom_start() {
|
||||
return 0xc8;
|
||||
},
|
||||
end: function auxRom_end() {
|
||||
return 0xcf;
|
||||
},
|
||||
read: function auxRom_read(page, off) {
|
||||
return rom[_firmware * 0x1000 + (page - 0xC0) * 0x100 + off];
|
||||
},
|
||||
write: function auxRom_write() {}
|
||||
};
|
||||
|
||||
_init();
|
||||
|
||||
return {
|
||||
@ -1177,17 +1166,19 @@ function RAMFactor(mmu, io, slot, size) {
|
||||
end: function ramfactor_end() {
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
read: function ramfactor_read(page, off) {
|
||||
mmu.auxRom(slot, auxRomFn);
|
||||
|
||||
return rom[slot * 0x100 + off];
|
||||
},
|
||||
write: function ramfactor_write() {
|
||||
mmu.auxRom(slot, auxRomFn);
|
||||
},
|
||||
ioSwitch: function ramfactor_ioSwitch(off, val) {
|
||||
ioSwitch: function (off, val) {
|
||||
return _access(off, val);
|
||||
},
|
||||
read: function ramfactor_read(page, off) {
|
||||
var result;
|
||||
if (page == 0xc0 + slot) {
|
||||
result = rom[slot * 0x100 + off];
|
||||
} else {
|
||||
result = rom[_firmware * 0x1000 + (page - 0xC0) * 0x100 + off];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
write: function ramfactor_write() {},
|
||||
reset: function ramfactor_reset() {
|
||||
_firmware = 0;
|
||||
},
|
||||
|
19
js/slot3.js
19
js/slot3.js
@ -9,25 +9,12 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
/*exported Slot3*/
|
||||
/*exported Slot3 */
|
||||
|
||||
function Slot3(mmu, rom)
|
||||
function Slot3(io, slot, rom)
|
||||
{
|
||||
'use strict';
|
||||
|
||||
var auxRomFn = {
|
||||
start: function auxRom_start() {
|
||||
return 0xc8;
|
||||
},
|
||||
end: function auxRom_end() {
|
||||
return 0xcf;
|
||||
},
|
||||
read: function auxRom_read(page, off) {
|
||||
return rom.read(page, off);
|
||||
},
|
||||
write: function auxRom_write() {}
|
||||
};
|
||||
|
||||
return {
|
||||
start: function slot3_start() {
|
||||
return 0xc3;
|
||||
@ -36,11 +23,9 @@ function Slot3(mmu, rom)
|
||||
return 0xc3;
|
||||
},
|
||||
read: function slot3_read(page, off) {
|
||||
mmu.auxRom(0x3, auxRomFn);
|
||||
return rom.read(page, off);
|
||||
},
|
||||
write: function slot3_write() {
|
||||
mmu.auxRom(0x3, auxRomFn);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
/*exported Thunderclock */
|
||||
/*global each: false */
|
||||
/*global debug: false, each: false */
|
||||
|
||||
function Thunderclock(mmu, io, slot)
|
||||
function Thunderclock(io, slot)
|
||||
{
|
||||
'use strict';
|
||||
|
||||
@ -287,24 +287,13 @@ function Thunderclock(mmu, io, slot)
|
||||
};
|
||||
|
||||
function _init() {
|
||||
debug('Thunderclock card in slot', slot);
|
||||
|
||||
each(LOC, function(key) {
|
||||
LOC[key] += slot * 0x10;
|
||||
});
|
||||
}
|
||||
|
||||
var auxRomFn = {
|
||||
start: function auxRom_start() {
|
||||
return 0xc8;
|
||||
},
|
||||
end: function auxRom_end() {
|
||||
return 0xcf;
|
||||
},
|
||||
read: function auxRom_read(page, off) {
|
||||
return rom[(page - 0xc8) * 256 + off];
|
||||
},
|
||||
write: function auxRom_write() {}
|
||||
};
|
||||
|
||||
var _command = 0;
|
||||
var _bits = [];
|
||||
|
||||
@ -372,19 +361,21 @@ function Thunderclock(mmu, io, slot)
|
||||
|
||||
return {
|
||||
start: function thunderclock_start() {
|
||||
io.registerSwitches(this, LOC);
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
end: function thunderclock_end() {
|
||||
return 0xc0 + slot;
|
||||
},
|
||||
read: function thunderclock_read(page, off) {
|
||||
mmu.auxRom(slot, auxRomFn);
|
||||
|
||||
return rom[off];
|
||||
var result;
|
||||
if (page < 0xc8) {
|
||||
result = rom[off];
|
||||
} else {
|
||||
result = rom[(page - 0xc8) * 256 + off];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
write: function thunderclock_write() {
|
||||
mmu.auxRom(slot, auxRomFn);
|
||||
},
|
||||
ioSwitch: function thunderclock_ioSwitch(off, val) {
|
||||
return _access(off, val);
|
||||
|
@ -21,10 +21,9 @@ var _samples = [];
|
||||
|
||||
var audioContext;
|
||||
var audioNode;
|
||||
var AC = window.AudioContext;
|
||||
|
||||
if (typeof AC !== 'undefined') {
|
||||
audioContext = new AC();
|
||||
if (window.AudioContext) {
|
||||
audioContext = new window.AudioContext();
|
||||
audioNode = audioContext.createScriptProcessor(4096, 1, 1);
|
||||
|
||||
audioNode.onaudioprocess = function(event) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/* Copyright 2010-2016 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
|
@ -6,18 +6,22 @@ function Printer() {
|
||||
putChar: function(val) {
|
||||
if (!_printer || _printer.closed) {
|
||||
_printer = window.open('', '_blank','toolbar=0,location=0');
|
||||
_printer.document.title = 'Printer';
|
||||
_printer.document.write('<div style="font: 12px courier">');
|
||||
_printer.document.write('<span>');
|
||||
window.focus();
|
||||
if (_printer) {
|
||||
_printer.document.title = 'Printer';
|
||||
_printer.document.write('<div style="font: 12px courier">');
|
||||
_printer.document.write('<span>');
|
||||
window.focus();
|
||||
}
|
||||
}
|
||||
var c = String.fromCharCode(val & 0x7f);
|
||||
if (c == '\r') {
|
||||
_printer.document.write('<br /></span>');
|
||||
} else if (c == ' ') {
|
||||
_printer.document.write(' ');
|
||||
} else {
|
||||
_printer.document.write(c);
|
||||
if (_printer) {
|
||||
var c = String.fromCharCode(val & 0x7f);
|
||||
if (c == '\r') {
|
||||
_printer.document.write('<br /></span>');
|
||||
} else if (c == ' ') {
|
||||
_printer.document.write(' ');
|
||||
} else {
|
||||
_printer.document.write(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
loadJSON({
|
||||
{
|
||||
"name": "DOS 3.3 Master",
|
||||
"category": "System",
|
||||
"type": "dsk",
|
||||
@ -636,4 +636,4 @@ loadJSON({
|
||||
"AwhOEiBECI2UE6kAja0Tuo4cEyCLCCDaCK2sE6IWICwL0AkgYgggWQ1MPggg8QkgYgggQAutqxPQBaIPIM0KIB0LTA4IqYCFdoXZhTM4rdID6QfpHjABYCBY/KIVIM0KTNMDogG9HxMKCgoKnSETyhDzINwDhACFASDjA4QChQMgLRAg6A+pAI2rE2CpAIUiIFj8ohYgzQqiFyDNCiCO/aAAoh8gzQq5rxMg7f2iICDNCpgYaSiqIM0KyMAJ0OMgjv2iISDNCiBv/a0AAqIAICwL0LiNrBOMrhNgIFj8qQ+FJK2uExhpKKogzQqpA4UiIFj8rawTogogLAvQB6kAjQ==",
|
||||
"AAAAAAAAAAAAAAAAIg4iDSIMIgsiCiIJIggiByIGIgUiBCIDIgIiASIAAwIDAQMABA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
||||
]
|
||||
]});
|
||||
]}
|
||||
|
@ -1,4 +1,4 @@
|
||||
loadJSON({
|
||||
{
|
||||
"name": "ProDOS",
|
||||
"category": "System",
|
||||
"type": "dsk",
|
||||
@ -635,4 +635,4 @@ loadJSON({
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
||||
]
|
||||
]});
|
||||
]}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/perl -w
|
||||
# Copyright 2010-2016 Will Scullin <scullin@scullinsteel.com>
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, distribute, and sell this software and its
|
||||
# documentation for any purpose is hereby granted without fee, provided that
|
||||
# the above copyright notice appear in all copies and that both that
|
||||
@ -17,9 +17,9 @@ $Getopt::Std::STANDARD_HELP_VERSION = 1;
|
||||
my %opts;
|
||||
getopts('rn:c:t:', \%opts);
|
||||
|
||||
sub HELP_MESSAGE() {
|
||||
my $fh = shift;
|
||||
print $fh "dsk2js.pl [-c category] [-n name] [-t type] imagefile\n"
|
||||
sub HELP_MESSAGE() {
|
||||
my $fh = shift;
|
||||
print $fh "dsk2js.pl [-c category] [-n name] [-t type] imagefile\n"
|
||||
};
|
||||
sub VERSION_MESSAGE() { my $fh = shift; print $fh "Version 1.0\n" };
|
||||
|
||||
@ -102,7 +102,7 @@ if ($ext eq '2mg') {
|
||||
my $sector = 0;
|
||||
my $track = 0;
|
||||
|
||||
print "loadJSON({\n";
|
||||
print "{\n";
|
||||
print " \"name\": \"$name\",\n";
|
||||
print " \"type\": \"$ext\",\n";
|
||||
print " \"category\": \"$category\",\n";
|
||||
@ -127,6 +127,6 @@ for ($track = 0; $track < 0x23; $track++) {
|
||||
}
|
||||
print "\n ]";
|
||||
}
|
||||
print "\n]});\n";
|
||||
print "\n]}\n";
|
||||
|
||||
close(DISK);
|
||||
|
Loading…
Reference in New Issue
Block a user