ScriptedAmigaEmulator/sae/amiga.js
2017-08-15 00:49:19 +02:00

624 lines
16 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.
|
| Notes on the global-namespace
| -----------------------------
| Global object:
| function SAEO_<name>() {}
|
| Global error constant:
| const SAEE_<object_name>_<name>;
|
| Global constant:
| const SAEC_<object_name>_<name>;
|
| Global variable:
| var SAEV_<object_name>_<name>;
|
| Global function:
| function SAEF_<object_name>_<name>() {}
|
| Global reference (pointer) to another object:
| var SAER_<object_name>_<name>;
|
|
| Tags that may appear in comments (use full-word, case-sensitive text-search)
| --------------------------------
| OWN Own code added.
| ATT Attention, possible source of error or problem.
| FIX Need to be fixed or implemented.
| OPT Need or can be optimized.
| ORG Original code, e.g. something required to be disabled.
| SECT Code section.
-------------------------------------------------------------------------*/
const SAEC_Version = 0;
const SAEC_Revision = 9;
const SAEC_Patch = 0;
/*---------------------------------*/
/* errors */
function SAEO_Error(err, msg) {
this.err = err;
this.msg = msg;
}
SAEO_Error.prototype = new Error;
const SAEE_None = 0;
const SAEE_AlreadyRunning = 1;
const SAEE_NotRunning = 2;
const SAEE_NoTimer = 3;
const SAEE_NoMemory = 4;
const SAEE_Assert = 5;
const SAEE_Internal = 6;
const SAEE_Config_Invalid = 10;
const SAEE_Config_Compressed = 11;
const SAEE_CPU_Internal = 20;
const SAEE_CPU_Requires68020 = 21;
const SAEE_CPU_Requires680EC20 = 22;
const SAEE_CPU_Requires68030 = 23;
const SAEE_CPU_Requires68040 = 24;
const SAEE_Memory_NoKickstartRom = 30;
const SAEE_Memory_NoExtendedRom = 31;
const SAEE_Memory_RomSize = 32;
const SAEE_Memory_RomKey = 33;
const SAEE_Memory_RomDecode = 34;
const SAEE_Memory_RomChecksum = 35;
const SAEE_Memory_RomUnknown = 36;
const SAEE_Video_ElementNotFound = 40;
const SAEE_Video_RequiresCanvas = 41;
const SAEE_Video_RequiresWegGl = 42;
const SAEE_Video_ComphileShader = 43;
const SAEE_Video_LinkShader = 44;
const SAEE_Audio_RequiresWebAudio = 50;
const SAEE_Input_GamepadNotReady = 60;
/*-----------------------------------------------------------------------*/
/* global references */
var SAER = null;
/*---------------------------------*/
/* global constants */
const SAEC_spcflag_STOP = 2;
const SAEC_spcflag_COPPER = 4;
const SAEC_spcflag_INT = 8;
const SAEC_spcflag_BRK = 16;
//const SAEC_spcflag_UAEINT = 32;
const SAEC_spcflag_TRACE = 64;
const SAEC_spcflag_DOTRACE = 128;
const SAEC_spcflag_DOINT = 256;
const SAEC_spcflag_BLTNASTY = 512;
//const SAEC_spcflag_EXEC = 1024;
//const SAEC_spcflag_ACTION_REPLAY = 2048;
//const SAEC_spcflag_TRAP = 4096; /* enforcer-hack */
const SAEC_spcflag_MODE_CHANGE = 8192;
const SAEC_spcflag_CHECK = 32768;
const SAEC_command_Quit = 1;
const SAEC_command_Reset = 2;
const SAEC_command_KeyboardReset = 3;
const SAEC_command_HardReset = 4;
const SAEC_command_Pause = 5;
const SAEC_command_Resume = 6;
/*---------------------------------*/
const SAEC_Info_Brower_ID_Unknown = 0;
const SAEC_Info_Brower_ID_Chrome = 1;
const SAEC_Info_Brower_ID_Safari = 2;
const SAEC_Info_Brower_ID_Opera = 3;
const SAEC_Info_Brower_ID_Firefox = 4;
const SAEC_Info_Brower_ID_InternetExplorer = 5;
const SAEC_info = (function() {
var info = {
browser: {
id: SAEC_Info_Brower_ID_Unknown,
name: "Unknown",
plat: "Unknown",
lang: "en"
},
memory: {
maxSize: 0
},
audio: {
webAudio: false
},
video: {
canvas: false,
webGL: false,
pointerLock: false
},
input: {
gamepad: false
}
};
/* browser */
if (navigator.userAgent.indexOf("Chrome") > -1) {
info.browser.id = SAEC_Info_Brower_ID_Chrome;
info.browser.name = "Google Chrome";
}
else if (navigator.userAgent.indexOf("Safari") > -1) {
info.browser.id = SAEC_Info_Brower_ID_Safari;
info.browser.name = "Apple Safari";
}
else if (navigator.userAgent.indexOf("Opera") > -1) {
info.browser.id = SAEC_Info_Brower_ID_Opera;
info.browser.name = "Opera";
}
else if (navigator.userAgent.indexOf("Firefox") > -1) {
info.browser.id = SAEC_Info_Brower_ID_Firefox;
info.browser.name = "Mozilla Firefox";
}
else if (navigator.userAgent.indexOf("MSIE") > -1) {
info.browser.id = SAEC_Info_Brower_ID_InternetExplorer;
info.browser.name = "Microsoft Internet Explorer";
}
info.browser.plat = navigator.platform;
info.browser.lang = navigator.language;
/* max memory */
if (0) {
var size = 1048576;
while (true) {
try {
var data = new Uint8Array(size);
delete data;
info.memory.maxSize = size;
} catch (e) {
break;
}
size *= 2;
}
} else
info.memory.maxSize = 1073741824; //1G
/* audio */
var audioContext = null;
try {
var audioContextDriver = window.AudioContext || window.webkitAudioContext;
audioContext = new audioContextDriver();
var audioProcessor = audioContext.createScriptProcessor(1024, 2, 2);
info.audio.webAudio = true;
if (audioContext.close) audioContext.close().then(function() {});
audioContext = null;
} catch (e) {
if (audioContext) {
if (audioContext.close) audioContext.close().then(function() {});
audioContext = null;
}
}
/* video */
var canvas = document.createElement("canvas");
if (canvas && canvas.getContext) {
try {
var ctx = canvas.getContext("2d");
var imageData = ctx.createImageData(16, 16);
info.video.canvas = true;
try {
const glParams = {
alpha: false,
depth: true,
stencil: false,
antialias: false,
premultipliedAlpha: false,
preserveDrawingBuffer: true,
failIfMajorPerformanceCaveat: false
};
ctx = canvas.getContext("webgl", glParams) || canvas.getContext("experimental-webgl", glParams);
info.video.webGL = true;
} catch(e) {}
if (canvas.requestPointerLock || canvas.mozRequestPointerLock)
info.video.pointerLock = true;
} catch(e) {}
}
/* input */
if (navigator.getGamepads || navigator.webkitGetGamepads)
info.input.gamepad = true;
return info;
})();
/*---------------------------------*/
/* global variables */
var SAEV_spcflags = 0;
var SAEV_command = 0;
/*---------------------------------*/
/* global functions */
function SAEF_setSpcFlags(x) { SAEV_spcflags |= x; };
function SAEF_clrSpcFlags(x) { SAEV_spcflags &= ~x; };
/*---------------------------------*/
function SAEF_now() {
return Math.floor(performance.now() * 1000); /* micro-seconds since page-load */
}
function SAEF_sleep(ms) {
var start = performance.now();
while ((performance.now() - start) < ms) {} /* pretty nasty */
}
/*---------------------------------*/
/* debug */
function SAEF_log() {
if (SAEV_config.debug.level >= SAEC_Config_Debug_Level_Log && arguments.length) {
var str = sprintf.apply(this, arguments);
if (console.log) console.log(str);
}
}
function SAEF_info() {
if (SAEV_config.debug.level >= SAEC_Config_Debug_Level_Info && arguments.length) {
var str = sprintf.apply(this, arguments);
if (console.info) console.info(str);
}
}
function SAEF_warn() {
if (SAEV_config.debug.level >= SAEC_Config_Debug_Level_Warn && arguments.length) {
var str = sprintf.apply(this, arguments);
if (console.warn) console.warn(str);
}
}
function SAEF_error() {
if (SAEV_config.debug.level >= SAEC_Config_Debug_Level_Error && arguments.length) {
var str = sprintf.apply(this, arguments);
if (console.error) console.error(str);
}
}
function SAEF_fatal() {
var argumentsArray = Array.prototype.slice.call(arguments);
var err = argumentsArray[0];
var str = sprintf.apply(this, argumentsArray.slice(1));
if (console.error) console.error(str);
throw new SAEO_Error(err, str);
}
function SAEF_assert(cond) {
if (!cond) {
var err = SAEE_Assert;
var str = "Assertion failed. This is a bug in SAE.";
if (console.error) console.error(str);
throw new SAEO_Error(err, str);
}
}
/*---------------------------------*/
function ScriptedAmigaEmulator() {
SAER = this;
this.autoconf = new SAEO_AutoConf();
this.audio = new SAEO_Audio();
this.blitter = new SAEO_Blitter();
this.cia = new SAEO_CIA();
this.config = new SAEO_Configuration();
this.copper = new SAEO_Copper();
this.cpu = new SAEO_CPU();
this.custom = new SAEO_Custom();
this.devices = new SAEO_Devices();
this.disk = new SAEO_Disk();
this.events = new SAEO_Events();
this.expansion = new SAEO_Expansion();
this.filesys = new SAEO_Filesys();
this.gayle = new SAEO_Gayle();
this.gui = new SAEO_GUI();
this.hardfile = new SAEO_Hardfile();
this.ide = new SAEO_IDE();
this.input = new SAEO_Input();
this.m68k = new SAEO_M68K();
this.memory = new SAEO_Memory();
this.playfield = new SAEO_Playfield();
this.roms = new SAEO_Roms();
this.rtc = new SAEO_RTC();
this.serial = new SAEO_Serial();
this.video = new SAEO_Video();
/*---------------------------------*/
this.running = false;
this.paused = false;
/*-----------------------------------------------------------------------*/
this.dump = function () {
this.m68k.dump();
//this.memory.dump();
//this.cia.dump();
};
/*-----------------------------------------------------------------------*/
this.do_start_program = function() {
if (SAEV_command >= 0)
SAEV_command = SAEC_command_Reset;
this.m68k.m68k_go(true);
}
this.do_leave_program = function() {
//sampler_free();
this.video.cleanup();
this.input.cleanup();
this.disk.cleanup();
this.audio.cleanup();
//dump_counts();
this.serial.cleanup();
/*#ifdef CDTV
cdtv_free();
cdtvcr_free();
#endif
#ifdef CD32
akiko_free();
cd32_fmv_free();
#endif*/
//this.gui.cleanup(); //empty
//#ifdef AUTOCONFIG
this.expansion.cleanup();
//#endif
//#ifdef FILESYS
this.filesys.cleanup();
//#endif
this.gayle.cleanup();
/*idecontroller_free();
device_func_reset();
#ifdef WITH_TOCCATA
sndboard_free();
#endif*/
this.memory.cleanup();
//free_shm();
this.autoconf.cleanup();
}
this.start_program = function() {
this.do_start_program();
if (typeof SAEV_config.hook.event.started === "function")
SAEV_config.hook.event.started();
}
this.leave_program = function() {
this.dump();
this.do_leave_program();
if (typeof SAEV_config.hook.event.stopped === "function")
SAEV_config.hook.event.stopped();
}
this.pause_program = function(p) {
this.audio.pauseResume(p);
this.events.pauseResume(p);
if (typeof SAEV_config.hook.event.paused === "function")
SAEV_config.hook.event.paused(p);
}
/*---------------------------------*/
/* API */
this.getVersion = function(str) {
if (str)
return sprintf("%d.%d.%d", SAEC_Version, SAEC_Revision, SAEC_Patch);
else
return [SAEC_Version, SAEC_Revision, SAEC_Patch];
}
this.getInfo = function() {
return SAEC_info;
}
this.getConfig = function() {
return SAEV_config;
}
this.setDefaults = function() {
return this.config.setDefaults();
}
this.setModel = function(model, config) {
return this.config.setModel(model, config);
}
this.setMountInfoDefaults = function(num) {
var ci = SAEV_config.mount.config[num].ci;
this.filesys.uci_set_defaults(ci, false);
}
this.start = function() {
if (SAER.running) {
SAEF_warn("sae.start() emulation already running");
return SAEE_AlreadyRunning;
}
SAEF_info("sae.start() starting...");
var err;
if ((err = this.config.setup()) != SAEE_None)
return err;
if ((err = this.video.obtain()) != SAEE_None)
return err;
if ((err = this.audio.obtain()) != SAEE_None)
return err;
if ((err = this.input.setup()) != SAEE_None) //inputdevice_init();
return err;
if ((err = this.gui.setup()) != SAEE_None)
return err;
/*#ifdef PICASSO96
picasso_reset();
#endif*/
//this.config.fixup_prefs(currprefs, true);
//SAEV_config.audio.mode = 0; /* force sound settings change */
this.memory.hardreset(2);
if ((err = this.memory.reset(true)) == SAEE_None)
{
/*#ifdef AUTOCONFIG
native2amiga_install();
#endif*/
this.custom.setup(); //OWN
this.blitter.setup(); //OWN
this.playfield.setup(); //custom_init();
this.serial.setup();
this.disk.setup();
this.events.reset_frame_rate_hack();
if ((err = this.m68k.setup()) == SAEE_None) /* m68k_init() must come after reset_frame_rate_hack() */
{
//this.gui.update(); //empty
if ((err = this.video.setup(true)) == SAEE_None)
{
if ((err = this.audio.setup()) == SAEE_None)
{
this.start_program();
SAEF_info("sae.start() ...done");
return SAEE_None;
}
this.video.cleanup();
}
}
}
this.input.cleanup();
SAEF_error("sae.start() ...error %d", err);
return err;
}
this.stop = function() { //uae_quit()
if (this.running) {
SAEF_info("sae.stop()");
if (SAEV_command != -SAEC_command_Quit)
SAEV_command = -SAEC_command_Quit;
return SAEE_None;
} else {
SAEF_warn("sae.stop() emulation not running");
return SAEE_NotRunning;
}
};
this.reset = function(hard, keyboard) { //uae_reset(hard, keyboard)
if (typeof hard == "undefined") var hard = false;
if (typeof keyboard == "undefined") var keyboard = false;
if (this.running) {
SAEF_info("sae.reset() hard %d, keyboard %d", hard?1:0, keyboard?1:0);
if (SAEV_command == 0) {
SAEV_command = -SAEC_command_Reset; /* soft */
if (keyboard)
SAEV_command = -SAEC_command_KeyboardReset;
if (hard)
SAEV_command = -SAEC_command_HardReset;
}
return SAEE_None;
} else {
SAEF_warn("sae.reset() emulation not running");
return SAEE_NotRunning;
}
};
this.pause = function(pause) {
if (this.running) {
if (!this.paused && pause) {
SAEF_info("sae.pause() pausing emulation");
SAEV_command = SAEC_command_Pause;
}
else if (this.paused && !pause) {
SAEF_info("sae.pause() resuming emulation");
SAEV_command = SAEC_command_Resume;
}
return SAEE_None;
} else {
SAEF_warn("sae.pause() emulation not running");
return SAEE_NotRunning;
}
};
this.insert = function(unit) {
if (this.running) {
var file = SAEV_config.floppy.drive[unit].file;
this.disk.insert(unit, file);
SAEF_info("sae.insert() unit %d inserted, name '%s', size %d, protected %d", unit, file.name, file.size, file.prot?1:0);
return SAEE_None;
} else {
SAEF_warn("sae.insert() emulation not running");
return SAEE_NotRunning;
}
};
this.eject = function(unit) {
if (this.running) {
this.disk.eject(unit);
SAEF_info("sae.eject() unit %d ejected", unit);
return SAEE_None;
} else {
SAEF_warn("sae.eject() emulation not running");
return SAEE_NotRunning;
}
};
this.getRomInfo = function(ri, file) {
return this.roms.examine(ri, file);
};
this.getDiskInfo = function(di, unit) {
return this.disk.examine(di, unit);
};
this.createDisk = function(unit, name, mode, type, label, ffs, bootable) {
if (!this.disk.create(unit, name, mode, type, label, ffs, bootable))
return SAEE_NoMemory;
return SAEE_None;
};
this.mute = function(mute) {
if (this.running) {
this.audio.mute(mute);
if (mute)
SAEF_log("sae.mute() audio muted");
else
SAEF_log("sae.mute() playing audio");
return SAEE_None;
} else {
SAEF_warn("sae.mute() emulation not running");
return SAEE_NotRunning;
}
};
this.keyPress = function(e, down) {
this.input.keyPress(e, down);
return SAEE_None;
};
/*---------------------------------*/
SAEF_info("SAE %d.%d.%d", SAEC_Version, SAEC_Revision, SAEC_Patch);
}