/*------------------------------------------------------------------------- | 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? */ /*-----------------------------------------------------------------------*/ /* 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 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_Internal: return "Internal emulator error."; case SAEE_Config_Invalid: return "Invalid configuration."; 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 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); } resetLEDs(); } /* Get call after the emulator has finished the reset-routine. */ function hook_event_reseted(hard) { if (paused) { paused = false; switchPauseResume(paused); } } /* 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(); }