582 lines
17 KiB
JavaScript
582 lines
17 KiB
JavaScript
/*-------------------------------------------------------------------------
|
|
| SAE - Scripted Amiga Emulator
|
|
| https://github.com/naTmeg/ScriptedAmigaEmulator
|
|
|
|
|
| Copyright (C) 2012-2016 Rupert Hausberger
|
|
|
|
|
| This program is free software; you can redistribute it and/or
|
|
| modify it under the terms of the GNU General Public License
|
|
| as published by the Free Software Foundation; either version 2
|
|
| of the License, or (at your option) any later version.
|
|
|
|
|
| This program is distributed in the hope that it will be useful,
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| GNU General Public License for more details.
|
|
|
|
|
| Note: This file does not contain any emulator-code.
|
|
-------------------------------------------------------------------------*/
|
|
|
|
var sae = null; /* SAE instance */
|
|
var cfg = null; /* Reference to the config-object */
|
|
var inf = null; /* Reference to the info-object */
|
|
|
|
var running = false; /* Is the emualtion currently running? */
|
|
var paused = false; /* Is the emualtion currently paused? */
|
|
var muted = false; /* Is the audio-output currently muted? */
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/* Helpers */
|
|
|
|
function getSelect(id, asString) {
|
|
if (typeof asString == "undefined") asString = false;
|
|
var e = document.getElementById(id);
|
|
for (var i = 0; i < e.length; i++) {
|
|
if (e[i].selected)
|
|
return asString ? e[i].value : Number(e[i].value);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function setSelect(id, v) {
|
|
var e = document.getElementById(id);
|
|
var vs = String(v);
|
|
for (var i = 0; i < e.length; i++) {
|
|
if (e[i].value === vs) {
|
|
e[i].selected = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function styleDisplayInline(id, show) {
|
|
var e = document.getElementById(id);
|
|
e.style.display = show ? "inline" : "none";
|
|
}
|
|
|
|
function switchPauseResume(p) {
|
|
var e = document.getElementById("controls_pr");
|
|
if (p) {
|
|
e.innerHTML = "Resume";
|
|
e.onclick = function() { pause(false); };
|
|
} else {
|
|
e.innerHTML = "Pause";
|
|
e.onclick = function() { pause(true); };
|
|
}
|
|
}
|
|
|
|
function switchMutePlay(m) {
|
|
var e = document.getElementById("controls_mp");
|
|
if (m) {
|
|
e.innerHTML = "Play";
|
|
e.onclick = function() { mute(false); };
|
|
} else {
|
|
e.innerHTML = "Mute";
|
|
e.onclick = function() { mute(true); };
|
|
}
|
|
}
|
|
|
|
function saee2text(err) {
|
|
switch (err) {
|
|
case SAEE_AlreadyRunning: return "The emulator is already running.";
|
|
case SAEE_NotRunning: return "The emulator is not running.";
|
|
case SAEE_NoTimer: return "No timing-functions avail. Please upgrade your browser.";
|
|
case SAEE_NoMemory: return "Out of memory.";
|
|
case SAEE_Assert: return "Assertiation failed.";
|
|
case SAEE_Internal: return "Internal emulator error.";
|
|
case SAEE_Config_Invalid: return "Invalid configuration.";
|
|
case SAEE_Config_Compressed: return "A ZIP file was detected. Compressed files are not yet supported.";
|
|
case SAEE_CPU_Internal: return "Internal CPU-error.";
|
|
case SAEE_CPU_Requires68020: return "The selected kickstart-rom does require a 68020 and 32bit address-space";
|
|
case SAEE_CPU_Requires680EC20: return "The selected kickstart-rom does require a 68020.";
|
|
case SAEE_CPU_Requires68030: return "The selected kickstart-rom does require a 68030.";
|
|
case SAEE_CPU_Requires68040: return "The selected kickstart-rom does require a 68040/68060.";
|
|
case SAEE_Memory_NoKickstartRom: return "The kickstart-rom is missing.";
|
|
case SAEE_Memory_NoExtendedRom: return "An extended-rom is required but missing.\n\nGo to the ROM-page and select a rom from disk...";
|
|
case SAEE_Memory_RomSize: return "The kickstart- or extended-rom does have an invalid size.";
|
|
case SAEE_Memory_RomKey: return "A ROM-keyfile is required. (Cloanto)";
|
|
case SAEE_Memory_RomDecode: return "Invalid ROM-keyfile. (Cloanto)";
|
|
case SAEE_Memory_RomChecksum: return "Checksum-error at the kickstart- or extended-rom.";
|
|
case SAEE_Memory_RomUnknown: return "Unknown ROM.";
|
|
case SAEE_Video_ElementNotFound: return "Video DIV-element not found. Check 'cfg.video.id'";
|
|
case SAEE_Video_RequiresCanvas: return "This browser does not support 'Canvas'. Please upgrade to an actual version.";
|
|
case SAEE_Video_RequiresWegGl: return "This browser does not support 'WebGL'. Please upgrade to an actual version.";
|
|
case SAEE_Video_ComphileShader: return "Can not compile the required shader-program.";
|
|
case SAEE_Video_LinkShader: return "Can not link the required shader-program.";
|
|
case SAEE_Audio_RequiresWebAudio: return "This browser does not support 'WebAudio'. Please upgrade to an actual version.";
|
|
default: return "("+err+")";
|
|
}
|
|
}
|
|
|
|
function loadFile(e, callback) {
|
|
var reader = new FileReader();
|
|
reader.onload = callback;
|
|
reader.readAsBinaryString(e);
|
|
}
|
|
|
|
/*---------------------------------*/
|
|
/* CRC-32 checksumming */
|
|
|
|
const crc32Table = (function() {
|
|
var table = new Uint32Array(256);
|
|
var n, c, k;
|
|
|
|
for (n = 0; n < 256; n++) {
|
|
c = n;
|
|
for (k = 0; k < 8; k++)
|
|
c = ((c >>> 1) ^ (c & 1 ? 0xedb88320 : 0)) >>> 0;
|
|
table[n] = c;
|
|
}
|
|
return table;
|
|
})();
|
|
|
|
function crc32(data) {
|
|
var length = data.length;
|
|
var offset = 0;
|
|
var crc = 0xffffffff;
|
|
|
|
while (length-- > 0)
|
|
crc = crc32Table[(crc ^ data.charCodeAt(offset++)) & 0xff] ^ (crc >>> 8);
|
|
|
|
return (crc ^ 0xffffffff) >>> 0;
|
|
}
|
|
|
|
if (crc32("The quick brown fox jumps over the lazy dog") != 0x414fa339)
|
|
alert("CRC32-hash testing failed. SAE will not work. This is an internal bug!?");
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/* Config */
|
|
|
|
function fixConfig() {
|
|
/* video */
|
|
if (cfg.video.enabled) {
|
|
if (!inf.video.canvas && !inf.video.webGL)
|
|
cfg.video.enabled = false;
|
|
else if (SAEV_config.video.api == SAEC_Config_Video_API_WebGL && !inf.video.webGL)
|
|
SAEV_config.video.api = SAEC_Config_Video_API_Canvas;
|
|
}
|
|
/* audio */
|
|
if (cfg.audio.mode >= SAEC_Config_Audio_Mode_On) {
|
|
if (!inf.audio.webAudio)
|
|
cfg.audio.mode = SAEC_Config_Audio_Mode_Off_Emul;
|
|
}
|
|
}
|
|
|
|
function setRomName() {
|
|
var e = document.getElementById("cfg_rom_name");
|
|
if (cfg.memory.rom.size) {
|
|
e.className = "";
|
|
e.innerHTML = cfg.memory.rom.name;
|
|
styleDisplayInline("cfg_rom_remove", 1);
|
|
} else {
|
|
e.className = "red";
|
|
e.innerHTML = "<unset> (required)";
|
|
styleDisplayInline("cfg_rom_remove", 0);
|
|
document.getElementById("cfg_rom_file").value = "";
|
|
}
|
|
}
|
|
|
|
function setFloppyName(n) {
|
|
var e = document.getElementById("cfg_df"+n+"_name");
|
|
if (cfg.floppy.drive[n].file.size) {
|
|
e.className = "";
|
|
e.innerHTML = cfg.floppy.drive[n].file.name;
|
|
styleDisplayInline("cfg_df"+n+"_eject", 1);
|
|
} else {
|
|
e.className = "gray";
|
|
e.innerHTML = "<unset>";
|
|
styleDisplayInline("cfg_df"+n+"_eject", 0);
|
|
document.getElementById("cfg_df"+n+"_file").value = "";
|
|
}
|
|
}
|
|
|
|
function setConfig() {
|
|
fixConfig();
|
|
setSelect("cfg_ntsc", cfg.chipset.ntsc ? 1 : 0);
|
|
setRomName();
|
|
setFloppyName(0);
|
|
switch (cfg.video.hresolution) {
|
|
case SAEC_Config_Video_HResolution_LoRes:
|
|
setSelect("cfg_res", 1);
|
|
break;
|
|
case SAEC_Config_Video_HResolution_HiRes:
|
|
setSelect("cfg_res", 2);
|
|
break;
|
|
case SAEC_Config_Video_HResolution_SuperHiRes:
|
|
setSelect("cfg_res", 3);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*---------------------------------*/
|
|
|
|
function getConfig() {
|
|
var model = SAEC_Model_A2000; /* Default model after page-load*/
|
|
var modelSubConfig = 0; /* See config.js */
|
|
|
|
switch (getSelect("cfg_model", true)) {
|
|
case "A500": model = SAEC_Model_A500; break;
|
|
case "A500P": model = SAEC_Model_A500P; break;
|
|
case "A600": model = SAEC_Model_A600; break;
|
|
case "A1000": model = SAEC_Model_A1000; break;
|
|
case "A1200": model = SAEC_Model_A1200; break;
|
|
case "A2000": model = SAEC_Model_A2000; break;
|
|
case "A3000": model = SAEC_Model_A3000; break;
|
|
case "A4000": model = SAEC_Model_A4000; break;
|
|
case "A4000T": model = SAEC_Model_A4000T; break;
|
|
/* future. do not use. cd-emulation is not implemented yet.
|
|
case "CDTV": model = SAEC_Model_CDTV; break;
|
|
case "CD32": model = SAEC_Model_CD32; break; */
|
|
}
|
|
/* Set the defaults for the selected model.
|
|
ROMs and Floppies are not affected. */
|
|
sae.setModel(model, modelSubConfig);
|
|
|
|
/* After here, you may tweak additional settings */
|
|
|
|
cfg.chipset.ntsc = getSelect("cfg_ntsc") == 1;
|
|
|
|
cfg.memory.z2FastSize = 2 << 20; /* Give 2mb zorro2 fast-ram */
|
|
|
|
/* Do we have rom-data? */
|
|
if (cfg.memory.rom.size == 0) {
|
|
alert(saee2text(SAEE_Memory_NoKickstartRom));
|
|
return false;
|
|
}
|
|
|
|
cfg.floppy.speed = SAEC_Config_Floppy_Speed_Turbo; /* Set speed to turbo. This is not always compatible */
|
|
|
|
cfg.video.id = "myVideo"; /* Set the id-name of the desired output-div or output-canvas */
|
|
|
|
switch (getSelect("cfg_res")) {
|
|
case 1: /* Lores */
|
|
cfg.video.hresolution = SAEC_Config_Video_HResolution_LoRes;
|
|
cfg.video.vresolution = SAEC_Config_Video_VResolution_NonDouble;
|
|
cfg.video.size_win.width = SAEC_Video_DEF_AMIGA_WIDTH; /* 360 */
|
|
cfg.video.size_win.height = SAEC_Video_DEF_AMIGA_HEIGHT; /* 284 */
|
|
break;
|
|
case 2: /* Hires */
|
|
cfg.video.hresolution = SAEC_Config_Video_HResolution_HiRes;
|
|
cfg.video.vresolution = SAEC_Config_Video_VResolution_Double;
|
|
cfg.video.size_win.width = SAEC_Video_DEF_AMIGA_WIDTH << 1; /* 720 */
|
|
cfg.video.size_win.height = SAEC_Video_DEF_AMIGA_HEIGHT << 1; /* 568 */
|
|
break;
|
|
case 3: /* SuperHires */
|
|
cfg.video.hresolution = SAEC_Config_Video_HResolution_SuperHiRes;
|
|
cfg.video.vresolution = SAEC_Config_Video_VResolution_Double;
|
|
cfg.video.size_win.width = SAEC_Video_DEF_AMIGA_WIDTH << 2; /* 1440 */
|
|
cfg.video.size_win.height = SAEC_Video_DEF_AMIGA_HEIGHT << 1; /* 568 */
|
|
break;
|
|
}
|
|
|
|
/* Set hooks */
|
|
cfg.hook.log.error = hook_log_error;
|
|
|
|
cfg.hook.event.started = hook_event_started;
|
|
cfg.hook.event.stopped = hook_event_stopped;
|
|
cfg.hook.event.reseted = hook_event_reseted;
|
|
cfg.hook.event.paused = hook_event_paused;
|
|
|
|
cfg.hook.led.power = hook_led_power;
|
|
cfg.hook.led.hd = hook_led_hd;
|
|
cfg.hook.led.df = hook_led_df;
|
|
cfg.hook.led.fps = hook_led_fps;
|
|
cfg.hook.led.cpu = hook_led_cpu;
|
|
|
|
/* Enable debug-log to developer-console */
|
|
cfg.debug.level = SAEC_Config_Debug_Level_Log;
|
|
|
|
/* NOTE:
|
|
DO NOT ALTER the config-object while the emulator is running.
|
|
|
|
I will release a detailed description of the config-object soon.
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
/*---------------------------------*/
|
|
/* Control buttons */
|
|
|
|
function start() {
|
|
if (getConfig()) {
|
|
/* Start the emulator. This does take some time.
|
|
Once started, the hook SAEV_config.hook.event.started will be called. (new 0.9.1) */
|
|
var err = sae.start();
|
|
if (err == SAEE_None) {
|
|
/* ... */
|
|
} else
|
|
alert(saee2text(err));
|
|
}
|
|
}
|
|
|
|
function stop() {
|
|
/* Send stop-request. This does take some time.
|
|
Once stopped, the hook SAEV_config.hook.event.stopped will be called. (new 0.9.1) */
|
|
var err = sae.stop();
|
|
if (err == SAEE_None) {
|
|
/* ... */
|
|
} else
|
|
alert(saee2text(err));
|
|
}
|
|
|
|
function reset() {
|
|
/* Send soft-reset request. This does take some time.
|
|
Once done, the hook SAEV_config.hook.event.reseted will be called. (new 0.9.1) */
|
|
var hard = false, keyboard = false;
|
|
var err = sae.reset(hard, keyboard);
|
|
if (err == SAEE_None) {
|
|
/* ... */
|
|
} else
|
|
alert(saee2text(err));
|
|
}
|
|
|
|
function pause(p) {
|
|
/* Send Pause- or Resume-request. This does take some time.
|
|
Once paused or resumed, the hook SAEV_config.hook.event.paused will be called. (new 0.9.1)
|
|
true == pause, false == resume */
|
|
var err = sae.pause(p);
|
|
if (err == SAEE_None) {
|
|
/* ... */
|
|
} else
|
|
alert(saee2text(err));
|
|
}
|
|
|
|
function mute(m) {
|
|
/* Mute or play audio.
|
|
true == mute, false == play */
|
|
var err = sae.mute(m);
|
|
if (err == SAEE_None) {
|
|
muted = m;
|
|
switchMutePlay(muted);
|
|
/* ... */
|
|
} else
|
|
alert(saee2text(err));
|
|
}
|
|
|
|
function romSelect() {
|
|
var e = document.getElementById("cfg_rom_file").files[0];
|
|
if (e) {
|
|
loadFile(e, function (event) {
|
|
//cfg.memory.rom.path = e.path; currently unused in SAE
|
|
cfg.memory.rom.name = e.name; /* filename */
|
|
cfg.memory.rom.data = event.target.result; /* typeof 'String' or 'Uint8Array' */
|
|
cfg.memory.rom.size = e.size; /* size in bytes */
|
|
cfg.memory.rom.crc32 = crc32(event.target.result); /* pre-calculate crc32 for a faster start */
|
|
setRomName();
|
|
|
|
/*ri = new SAEO_RomInfo();
|
|
var err = sae.getRomInfo(ri, cfg.memory.rom);
|
|
if (err == SAEE_None) {
|
|
examine 'ri'...
|
|
}*/
|
|
});
|
|
}
|
|
}
|
|
function romRemove() {
|
|
cfg.memory.rom.clr();
|
|
setRomName();
|
|
}
|
|
|
|
function floppyInsert(n) {
|
|
var e = document.getElementById("cfg_df"+n+"_file").files[0];
|
|
if (e) {
|
|
loadFile(e, function(event) {
|
|
var file = cfg.floppy.drive[n].file;
|
|
//file.path = e.path; currently unused in SAE
|
|
file.name = e.name; /* filename */
|
|
file.data = event.target.result; /* typeof 'String' or 'Uint8Array' */
|
|
file.size = e.size; /* size in bytes */
|
|
file.crc32 = crc32(event.target.result); /* pre-calculate crc32 for a faster start */
|
|
setFloppyName(n);
|
|
|
|
/*var di = new SAEO_DiskInfo();
|
|
var err = sae.getDiskInfo(di, n);
|
|
if (err == SAEE_None) {
|
|
examine 'di'...
|
|
}*/
|
|
|
|
/* If the emulator is running, notify about the disk-change */
|
|
if (running)
|
|
sae.insert(n);
|
|
});
|
|
}
|
|
}
|
|
function floppyEject(n) {
|
|
cfg.floppy.drive[n].file.clr();
|
|
setFloppyName(n);
|
|
|
|
/* If the emulator is running, notify about the disk-change */
|
|
if (running)
|
|
sae.eject(n);
|
|
}
|
|
|
|
/*---------------------------------*/
|
|
/* Init-routine, called once on page-load */
|
|
|
|
function init() {
|
|
/* Create the emulator */
|
|
sae = new ScriptedAmigaEmulator();
|
|
|
|
/* Get the reference to the info-object.
|
|
As alternative, you can use the constant 'SAEC_info' directly,
|
|
defined in amiga.js */
|
|
inf = sae.getInfo(); /* or */
|
|
//inf = SAEC_info;
|
|
//console.dir(inf);
|
|
|
|
/* Get the reference to the config-object.
|
|
As alternative, you can use the variable 'SAEV_config' directly,
|
|
defined in config.js */
|
|
cfg = sae.getConfig(); /* or */
|
|
//cfg = SAEV_config;
|
|
//console.dir(cfg);
|
|
|
|
initLEDs();
|
|
setConfig();
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/* Hooks (callbacks) */
|
|
|
|
/*---------------------------------*/
|
|
/* Logging */
|
|
|
|
/* There is currently only one log-hook, if a fatal error
|
|
does occure while the emulator is running. */
|
|
|
|
function hook_log_error(err, msg) {
|
|
/* err is a number type. See SAEE_* for the error code. */
|
|
running = false;
|
|
if (msg.length)
|
|
alert(msg);
|
|
}
|
|
|
|
/*---------------------------------*/
|
|
/* Events (new 0.9.1) */
|
|
|
|
/* Get call after the emulator has finished the starting-process. */
|
|
function hook_event_started() {
|
|
running = true;
|
|
}
|
|
|
|
/* Get call after the emulator has finished the stopping-process. */
|
|
function hook_event_stopped() {
|
|
running = false;
|
|
|
|
if (paused) {
|
|
paused = false;
|
|
switchPauseResume(paused);
|
|
}
|
|
if (muted) {
|
|
muted = false;
|
|
switchMutePlay(muted);
|
|
}
|
|
resetLEDs();
|
|
}
|
|
|
|
/* Get call after the emulator has finished the reset-routine. */
|
|
function hook_event_reseted(hard) {
|
|
if (paused) {
|
|
paused = false;
|
|
switchPauseResume(paused);
|
|
}
|
|
if (muted) {
|
|
muted = false;
|
|
switchMutePlay(muted);
|
|
}
|
|
}
|
|
|
|
/* Get call after the emulator has finished switching between pause-resume. */
|
|
function hook_event_paused(p) {
|
|
paused = p;
|
|
switchPauseResume(p);
|
|
}
|
|
|
|
/*---------------------------------*/
|
|
/* LEDs */
|
|
|
|
const COL_GRAY = "#000";
|
|
const COL_GREEN = "#8C8";
|
|
const COL_RED = "#E88";
|
|
const COL_ORANGE = "#CC8";
|
|
|
|
/* cache elements */
|
|
var e_led_power = null;
|
|
var e_led_hd = null;
|
|
var e_led_df = [null,null,null,null];
|
|
var e_led_fps = null;
|
|
var e_led_cpu = null;
|
|
|
|
/* Power LED: boolean On/Off */
|
|
function hook_led_power(on) {
|
|
e_led_power.style.color = on ? COL_GREEN : COL_GRAY;
|
|
}
|
|
|
|
/* Harddisk-LED */
|
|
function hook_led_hd(rw) {
|
|
/* rw 0=Off, 1=Read, 2=Write */
|
|
e_led_hd.style.color = rw == 1 ? COL_GREEN : (rw == 2 ? COL_RED : COL_GRAY);
|
|
}
|
|
|
|
/* Floppy-LED */
|
|
function hook_led_df(unit, disabled, cylinder, side, rw) {
|
|
/* unit 0-3 */
|
|
/* cylinder 0-81 */
|
|
/* side 0-1 */
|
|
/* rw 0=Off, 1=Read, 2=Write */
|
|
if (disabled) {
|
|
e_led_df[unit].innerHTML = "-";
|
|
e_led_df[unit].style.color = COL_GRAY;
|
|
} else {
|
|
e_led_df[unit].innerHTML = String(cylinder);
|
|
e_led_df[unit].style.color = rw == 1 ? COL_GREEN : (rw == 2 ? COL_RED : COL_GRAY);
|
|
}
|
|
}
|
|
|
|
/* FPS (Frames Per Second) */
|
|
function hook_led_fps(fps, paused) {
|
|
/* fps is a float-number, 0-50, in frames */
|
|
e_led_fps.innerHTML = paused ? "0.0" : fps.toFixed(1);
|
|
e_led_fps.style.color = COL_GRAY;
|
|
}
|
|
|
|
/* CPU-usage */
|
|
function hook_led_cpu(usage, paused) {
|
|
/* usage is a float-number, 0-1000, in percent */
|
|
if (paused) {
|
|
e_led_cpu.innerHTML = "0%";
|
|
e_led_cpu.style.color = COL_GRAY;
|
|
} else {
|
|
e_led_cpu.innerHTML = usage.toFixed(0) + "%";
|
|
if (usage < 90)
|
|
e_led_cpu.style.color = COL_GREEN;
|
|
else if (usage < 110)
|
|
e_led_cpu.style.color = COL_ORANGE;
|
|
else
|
|
e_led_cpu.style.color = COL_RED;
|
|
}
|
|
}
|
|
|
|
function resetLEDs() {
|
|
hook_led_power(false);
|
|
hook_led_hd(0);
|
|
hook_led_df(0, true, 0, 0, 0);
|
|
hook_led_df(1, true, 0, 0, 0);
|
|
hook_led_df(2, true, 0, 0, 0);
|
|
hook_led_df(3, true, 0, 0, 0);
|
|
hook_led_fps(0, false);
|
|
hook_led_cpu(0, false);
|
|
}
|
|
|
|
function initLEDs() {
|
|
e_led_power = document.getElementById("status_led_power");
|
|
e_led_hd = document.getElementById("status_led_hd");
|
|
e_led_df[0] = document.getElementById("status_led_df0");
|
|
e_led_df[1] = document.getElementById("status_led_df1");
|
|
e_led_df[2] = document.getElementById("status_led_df2");
|
|
e_led_df[3] = document.getElementById("status_led_df3");
|
|
e_led_fps = document.getElementById("status_led_fps");
|
|
e_led_cpu = document.getElementById("status_led_cpu");
|
|
|
|
resetLEDs();
|
|
}
|