diff --git a/doswasmx/main.ttf b/doswasmx/main.ttf new file mode 100644 index 0000000..ff0815c Binary files /dev/null and b/doswasmx/main.ttf differ diff --git a/doswasmx/main.wasm b/doswasmx/main.wasm new file mode 100644 index 0000000..6f715aa Binary files /dev/null and b/doswasmx/main.wasm differ diff --git a/doswasmx/romlist.js b/doswasmx/romlist.js new file mode 100644 index 0000000..ad78b25 --- /dev/null +++ b/doswasmx/romlist.js @@ -0,0 +1,7 @@ +var ROMLIST = [ + /* + {url:"game1.iso",title:"Game1",skipiso:"true",ram:"128"}, + {url:"game2.iso",title:"Game2",skipiso:"true",ram:"128"}, + {url:"game3.iso",title:"Game3",skipiso:"true",ram:"128"}, + */ +]; diff --git a/doswasmx/script.js b/doswasmx/script.js new file mode 100644 index 0000000..13e9529 --- /dev/null +++ b/doswasmx/script.js @@ -0,0 +1,2588 @@ +var AUDIOBUFFSIZE = 1024; + +const SaveTypes = { + Savestate: "savestate", + Disk: "disk", + ISO: "iso", + BaseImage: "baseimage", +} + +class MyClass { + constructor() { + this.rom_name = ''; + this.rom_size = 0; + this.mobileMode = false; + this.iosMode = false; + this.base_name = ''; + this.initCount = 0; + this.baseImageSaved = false; + this.isoSaved = false; + this.moduleInitializing = true; + this.exportFilesRequested = false; + this.lblError = ''; + this.isoMounted = false; + this.floppyMounted = false; + this.canvasHeight = 480; + this.ram = 32; + this.initialHardDrive = 'hd_520'; + this.dosVersion = '7.1'; + this.iso_loaded = false; + this.noIso = false; + this.importedFileNames = []; + this.isSpecialHandler = false; + this.img_loaded = false; + this.cueFile = ''; + this.hasBinCue = false; + this.beforeEmulatorStarted = true; + this.audioInited = false; + this.dblistSavestates = []; + this.dblistDisks = []; + this.dblistBaseImages = []; + this.dblistIsos = []; + this.multiFiles = []; + this.multiFileMode = false; + this.singleFileUpload = false; + this.noLocalSave = true; + this.message = ''; + this.loading = true; + this.isoName = ''; + this.loginModalOpened = false; + this.noCloudSave = true; + this.password = ''; + this.loggedIn = false; + this.dosSaveStates = []; + this.allSaveStates = []; + this.baseHardDrive = new Uint8Array(); + this.compareCount = 0; + this.doIntegrityCheck = false; + this.cpu = 'auto'; + this.showLoadAndSavestate = false; + this.loadSavestateAfterBoot = false; + this.noCopyImport = false; + this.changeCD = false; + this.changeFloppy = false; + this.loadFloppy = false; + this.isDosMode = true; + this.autoKeyboard = false; + this.autoKeyboardTimer = 0; + this.autoKeyboardInterval = 48*180; //three minutes (audioprocessrecurring gets called 48 times a second) + this.lastCalledTime = new Date(); + this.fpscounter = 0; + this.currentfps = 0; + this.fpsInterval = 1000 / 60; + this.then = Date.now(); + this.hasCloud = false; + this.initialInstallation = false; + this.hardDiskFallbackFromFloppy = false; + this.ranWindowsSetup = false; + this.win95InstallationFix = false; + this.winNotFoundCommands = ''; + this.doswasmxBatFound = false; + this.romList = []; + this.settings = { + CLOUDSAVEURL: "", + ISOURL: "", + DEFAULTIMG: "" + }; + this.specialFileHandlers = + [ + '.7z', + '.zip', + '.bin', + '.cue', + '.img', + '.iso' + ]; + var Module = {}; + Module['canvas'] = document.getElementById('canvas'); + window['Module'] = Module; + document.getElementById('file-upload').addEventListener('change', this.uploadRom.bind(this)); + document.getElementById('file-import').addEventListener('change', this.importFiles.bind(this)); + + //comes from settings.js + this.settings = window["DOSWASMSETTINGS"]; + + if (this.settings.CLOUDSAVEURL) + { + this.hasCloud = true; + } + + if (window["ROMLIST"].length > 0) + { + window["ROMLIST"].forEach(rom => { + this.romList.push(rom); + }); + } + + + rivets.formatters.ev = function (value, arg) { + return eval(value + arg); + } + rivets.formatters.ev_string = function (value, arg) { + let eval_string = "'" + value + "'" + arg; + return eval(eval_string); + } + + rivets.bind(document.getElementById('maindiv'), { data: this }); + rivets.bind(document.getElementById('importModal'), { data: this }); + rivets.bind(document.getElementById('loginModal'), { data: this }); + rivets.bind(document.getElementById('settingsModal'), { data: this }); + rivets.bind(document.getElementById('divInstructions'), { data: this }); + + this.detectBrowser(); + this.setupDragDropRom(); + this.createDB(); + this.retrieveSettings(); + + if (this.hasCloud) + { + this.setupLogin(); + } + + + $('#topPanel').show(); + $('#errorOuter').show(); + + } + + detectBrowser(){ + if (navigator.userAgent.toLocaleLowerCase().includes('iphone')) + { + this.iosMode = true; + try { + let iosVersion = navigator.userAgent.substring(navigator.userAgent.indexOf("iPhone OS ") + 10); + iosVersion = iosVersion.substring(0, iosVersion.indexOf(' ')); + iosVersion = iosVersion.substring(0, iosVersion.indexOf('_')); + this.iosVersion = parseInt(iosVersion); + } catch (err) { } + } + if (window.innerWidth < 600 || this.iosMode) + this.mobileMode = true; + else + this.mobileMode = false; + + // firefox only supports 250 megs?? + if (navigator.userAgent.toLocaleLowerCase().includes('firefox')) + { + this.initialHardDrive = 'hd_250'; + } + + if (this.iosMode) + { + this.initialHardDrive = 'hd -size 25'; + } + + if (this.mobileMode) + { + this.canvasHeight = window.innerWidth / 2; + console.log('detected mobile mode - canvasheight: ' + this.canvasHeight) + } + } + + //DRAG AND DROP ROM + setupDragDropRom(){ + let dropArea = document.getElementById('dropArea'); + + dropArea.addEventListener('dragenter', this.preventDefaults, false); + dropArea.addEventListener('dragover', this.preventDefaults, false); + dropArea.addEventListener('dragleave', this.preventDefaults, false); + dropArea.addEventListener('drop', this.preventDefaults, false); + + dropArea.addEventListener('dragenter', this.dragDropHighlight, false); + dropArea.addEventListener('dragover', this.dragDropHighlight, false); + dropArea.addEventListener('dragleave', this.dragDropUnHighlight, false); + dropArea.addEventListener('drop', this.dragDropUnHighlight, false); + + dropArea.addEventListener('drop', this.handleDrop, false); + + } + + preventDefaults(e){ + e.preventDefault(); + e.stopPropagation(); + } + + dragDropHighlight(e){ + $('#dropArea').css({"background-color": "lightblue"}); + } + + dragDropUnHighlight(e){ + $('#dropArea').css({"background-color": "inherit"}); + } + + handleDrop(e){ + myClass.initAudio(); + myClass.showProgress = true; + + let dt = e.dataTransfer; + let files = dt.files; + + if (files.length == 1) + { + myClass.detectSingleFileUpload(files[0].name); + } + else if (files.length > 1) + { + myClass.handleMultipleFiles(files, 0); + return; + } + + var file = files[0]; + myClass.rom_name = file.name; + myClass.extractBaseName(); + + console.log(file); + var reader = new FileReader(); + reader.onprogress = function (event) { + myClass.handleProgress(event, file); + }; + reader.onload = function (e) { + console.log('finished loading'); + var byteArray = new Uint8Array(this.result); + myClass.LoadEmulator(byteArray); + } + reader.readAsArrayBuffer(file); + + } + + handleProgress(event, file){ + console.log('loaded: ' + event.loaded); + let loaded = event.loaded; + let total = event.total; + let percent = (loaded / total)*100; + + loaded = Math.ceil(loaded / 1000000); + total = Math.ceil(total / 1000000); + + let formatted = file.name + ' ' + loaded + 'MB / ' + total + 'MB'; + + document.getElementById('myProgress').style.width= percent + '%'; + document.getElementById('myProgress').innerHTML = formatted; + } + + + configureEmulator(){ + + if (this.password) + this.loginSilent(); + + let size = localStorage.getItem('doswasmx-height'); + if (size) { + console.log('size found'); + let sizeNum = parseInt(size); + this.canvasHeight = sizeNum; + } + + this.resizeCanvas(); + + $('#canvasDiv').show(); + $('#divInstructions').show(); + + } + + processPrintStatement(text) { + console.log(text); + + //they tried to load an .img file that turned out to be a floppy disk + if (text.includes('detected floppy disk')) + { + if (this.dblistDisks.length == 0 && !this.settings.DEFAULTIMG) + { + //this means they don't have a hard disk + myClass.base_name = 'mydisk'; + myClass.initialInstallation = true; + } + else + { + //fall back to using their hard drive + myClass.base_name = 'mydisk'; + myClass.hardDiskFallbackFromFloppy = true; + } + } + + //we detected a floppy disk + if (text.includes('floppy disk mounted')) + { + setTimeout(() => { + if (myClass.initialInstallation) + { + myClass.sendDosCommands( + 'imgmake \"' + this.base_name + ".img\" -t " + this.initialHardDrive + "\n" + + 'imgmount c \"' + this.base_name + ".img\na:\n"); + } + else if (myClass.hardDiskFallbackFromFloppy) + { + //if they already have a hard disk we load it + //currently does not support this.settings.DEFAULTIMG + dragging .img floppy + if (this.dblistDisks.length > 0) + { + this.loadFromDatabase(SaveTypes.Disk); + } + } + else + { + myClass.sendDosCommands("a:\n"); + } + myClass.floppyMounted = true; + }, + + //TODO this is a hack + //dos commands should queue up rather + //than overwrite eachother + 500); + } + + + + //this means we detected the windows cd + if (text.includes("iso mounted root file: WIN98") || text.includes("iso mounted root file: WIN95")) + { + //auto start the setup process - only do this once + if (!myClass.ranWindowsSetup) + { + myClass.ranWindowsSetup = true; + setTimeout(() => { + myClass.initialInstallation = true; + myClass.sendDosCommands("d:setup.exe\n"); + }, 50); + + //set cpu to max during windows installation + setTimeout(() => { + myClass.updateCpuNeil('cycles=max'); + }, 100); + } + } + + if (text.includes('windows not found') || text.includes('found noboot.txt')) + { + //if we don't detect a windows installation just send + //them to the C drive + setTimeout(() => { + + let dosCommands = "c:\n"; + + //if we found a DOSWASMX.BAT we run it + if (myClass.doswasmxBatFound) + { + dosCommands += 'doswasmx.bat\n' + } + + //add any additional commands appended based on the rom file + dosCommands += myClass.winNotFoundCommands; + + //send it to the dos shell + myClass.sendDosCommands(dosCommands); + + //clear it for next time + myClass.winNotFoundCommands = ''; + }, 50); + } + + if (text.includes('Parsing command line: d:setup.exe')) + { + //a bunch of hacks to get it to dismiss the install + //warnings for win95rtm, win95osr2, and win98se + if (myClass.initialInstallation) + { + setTimeout(() => { + myClass.sendKey(52); //enter + }, 1000); + setTimeout(() => { + myClass.sendKey(49); //escape + }, 3000); + setTimeout(() => { + myClass.sendKey(52); //enter + }, 3100); + } + } + + if (text.includes('Plug & Play OS reports itself inactive')) + { + //this is hack during windows 95 installation + //where it doesnt detect one of the restarts + if (myClass.initialInstallation && !myClass.win95InstallationFix) + { + console.log('windows95 fix'); + myClass.win95InstallationFix = true; + setTimeout(() => { + myClass.updateAutoexecAdditional("boot c:\r\n"); + // myClass.saveDrive(); + }, 100); + } + } + + if (text.includes('drive mounted C file: DOSWASMX.BAT')) + { + myClass.doswasmxBatFound = true; + } + + if (text.includes('x ==')) + { + if (text.includes('x == 2')) + { + //this means we are booting into windows + myClass.isDosMode = false; + } + else + { + if (text.includes('x == 0')) + { + //this means we explicitly selected shutdown so go to DOS + } + else + { + //otherwise they probably picked restart + //so send them back to windows + setTimeout(() => { + myClass.updateAutoexecAdditional("boot c:\r\n"); + }, 100); + } + + //save the hard disk every time we restart/shutdown + if (!myClass.loggedIn) + { + setTimeout(() => { + myClass.saveDrive(); + }, 100); + } + + //we are back to the dos shell + myClass.isoMounted = false; + myClass.floppyMounted = false; + myClass.isDosMode = true; + } + } + + if (text.includes('iso drive mounted')) + { + //we mounted a cd + myClass.isoMounted = true; + } + + //emulator has started event + if (text.includes('DEBUG_ShowMsg: pixratio 1.000') + && myClass.loadSavestateAfterBoot) { + console.log('detected windows started'); + myClass.loadSavestateAfterBoot = false; + + if (myClass.loggedIn && !myClass.noCloudSave) + { + //we give it a 5 second delay because we + //want to wait for the windows startup sound + setTimeout(() => { + myClass.loadCloud(); + }, 5000); + } + } + + //this means its done exporting + if (text.includes('echo DONE')) + { + if (this.exportFilesRequested) + { + this.exportFilesRequested = false; + setTimeout(() => { + let filearray = FS.readFile("/export.zip"); + var file = new File([filearray], "export.zip", {type: "text/plain; charset=x-user-defined"}); + saveAs(file); + Module._neil_clear_autoexec(); + }, 500); + } + } + + //this means its done importing + if (text.includes('echo Import Finished')) + { + setTimeout(() => { + Module._neil_clear_autoexec(); + }, 500); + } + } + + + + async initModule(){ + myClass.initCount++; + myClass.finishInitialization(); + console.log('module initialized'); + } + + //need to wait for both indexedDB and wasm runtime + finishInitialization() + { + if (myClass.initCount == 2) + { + myClass.moduleInitializing = false; + myClass.message = ''; + + //create some directories we will need + FS.mkdir('/uploaded'); + FS.mkdir('/res'); + FS.mkdir('/save'); + + $('#githubDiv').show(); + this.loading = false; + + } + } + + uploadBrowse() { + this.initAudio(); + document.getElementById('file-upload').click(); + } + + importBrowse() { + document.getElementById('file-import').click(); + } + + detectSingleFileUpload(fileName) { + let fileExtension = fileName.substr(fileName.lastIndexOf('.')).toLocaleLowerCase(); + if (!this.specialFileHandlers.includes(fileExtension)) + { + myClass.singleFileUpload = true; + } + } + + uploadRom(event) { + myClass.initAudio(); + myClass.showProgress = true; + + if (event.currentTarget.files.length == 1) + { + myClass.detectSingleFileUpload(event.currentTarget.files[0].name); + } + else if (event.currentTarget.files.length > 1) + { + myClass.handleMultipleFiles(event.currentTarget.files, 0); + return; + } + + var file = event.currentTarget.files[0]; + myClass.rom_name = file.name; + myClass.extractBaseName(); + + console.log(file); + var reader = new FileReader(); + reader.onprogress = function (event) { + myClass.handleProgress(event, file); + }; + reader.onload = function (e) { + console.log('finished loading'); + var byteArray = new Uint8Array(this.result); + myClass.LoadEmulator(byteArray); + } + reader.readAsArrayBuffer(file); + } + + async parseMultipleFiles() + { + console.log('parseMultipleFiles', this.multiFiles); + this.multiFileMode = true; + + //set some baseline default + this.rom_name = 'blank.txt'; + let firstBytes = new Uint8Array(5); + this.extractBaseName(); + + + + for(let i = 0; i < this.multiFiles.length; i++) + { + let file = this.multiFiles[i]; + + if (file.name.toLocaleLowerCase().endsWith('img')) + { + //we prioritize the img name as the rom_name + //because we want to be sure it uses this as the + //hard drive when it gets to the LoadEmulator stage + this.rom_name = file.name; + this.extractBaseName(); + + this.baseHardDrive = file.data; + let finalByteArray = await this.loadHardDriveDiffs(file.data); + FS.writeFile('/' + this.base_name + '.img',finalByteArray); + + this.img_loaded = true; + } + else if ( + file.name.toLocaleLowerCase().endsWith('iso') || + file.name.toLocaleLowerCase().endsWith('.cue')) + { + FS.writeFile('/' + file.name,file.data); + this.isoName = file.name; + + if (file.name.toLocaleLowerCase().endsWith('.cue')) + { + this.hasBinCue = true; + this.cueFile = file.name; + } + + //if we didn't find an img then use this as the rom_name + if (!this.rom_name) + { + this.rom_name = file.name; + this.extractBaseName(); + } + } + else + { + //except bin/cue files + if (file.name.toLocaleLowerCase().endsWith('.bin') ) + { + //will handle these manually + FS.writeFile('/' + file.name,file.data); + } + else + { + //put them in the uploaded folder + FS.writeFile('/uploaded/' + file.name,file.data); + } + + } + } + + //FREE THE MEMORY + this.multiFiles = null; + + //we want to avoid setting the iso bytes because they were set above + this.noIso = true; + + this.LoadEmulator(firstBytes); + + } + + handleMultipleFiles(files, index) { + + var file = files[index]; + console.log('processing file ' + (index+1) + ' of ' + files.length, file); + + var reader = new FileReader(); + + reader.onprogress = function (event) { + console.log('loaded: ' + event.loaded); + let loaded = event.loaded; + let total = event.total; + let percent = (loaded / total)*100; + + loaded = Math.ceil(loaded / 1000000); + total = Math.ceil(total / 1000000); + + let formatted = '(' + (index+1) + ' of ' + files.length + ') ' + + file.name + ' ' + loaded + 'MB / ' + total + 'MB'; + + document.getElementById('myProgress').style.width= percent + '%'; + document.getElementById('myProgress').innerHTML = formatted; + }; + reader.onload = function (e) { + var byteArray = new Uint8Array(this.result); + myClass.multiFiles.push( + { + name: file.name, + data: byteArray + } + ) + if ( (index+1) 6) + { + name = name.substr(0,6); + } + else if (name.length<3) // as long as its atleast 3 long we leave it + { + //fill in the gaps with random numbers + var rando = Math.floor(Math.random() * Math.floor(100000)); + name += rando; + if (name.length > 6) name = name.substr(0,6); + } + + return name; + } + + readRomProp(key){ + let myselect = document.getElementById('romselect'); + try + { + return myselect.options[myselect.selectedIndex].attributes[key].value; + } + catch(err) + { + return ''; + } + } + + loadRomAndSavestate(){ + this.loadSavestateAfterBoot = true; + this.loadRom(); + } + + extractRomName(name){ + if (name.includes('/')) + { + name = name.substr(name.lastIndexOf('/')+1); + } + + return name; + } + + async loadRom(noIso) { + + this.initAudio(); + + + if (noIso) + { + this.noIso = true; + this.LoadEmulator(); + } + else + { + let romurl = this.readRomProp("value"); + let ram = this.readRomProp("ram"); + this.rom_name = this.extractRomName(romurl); + if (ram) + { + this.ram = ram; + } + if (this.settings.ISOURL) + { + romurl = this.settings.ISOURL + romurl; + } + this.extractBaseName(); + + this.load_file(romurl); + } + } + + async initAudio() { + + if (!this.audioInited) + { + this.audioInited = true; + this.audioContext = new AudioContext({ + latencyHint: 'interactive', + sampleRate: 48000, + }); + this.gainNode = this.audioContext.createGain(); + this.gainNode.gain.value = 0.5; + this.gainNode.connect(this.audioContext.destination); + + //point at where the emulator is storing the audio buffer + this.audioBufferResampled = new Int16Array(Module.HEAP16.buffer,Module._neilGetSoundBufferResampledAddress(),64000); + + this.audioWritePosition = 0; + this.audioReadPosition = 0; + this.audioBackOffCounter = 0; + + + this.pcmPlayer = this.audioContext.createScriptProcessor(AUDIOBUFFSIZE, 2, 2); + this.pcmPlayer.onaudioprocess = this.AudioProcessRecurring.bind(this); + this.pcmPlayer.connect(this.gainNode); + } + + } + + countFPS(){ + this.fpscounter++; + let delta = (new Date().getTime() - this.lastCalledTime.getTime())/1000; + if (delta>1) + { + this.currentfps = this.fpscounter; + this.fpscounter = 0; + this.lastCalledTime = new Date(); + + console.log(this.currentfps); + } + } + + //this method keeps getting called when it needs more audio + //data to play so we just keep streaming it from the emulator + AudioProcessRecurring(audioProcessingEvent){ + + if (this.beforeEmulatorStarted) + { + return; + } + + if (this.autoKeyboard) + { + this.tickAutoKeyboard(); + } + + + var sampleRate = audioProcessingEvent.outputBuffer.sampleRate; + let outputBuffer = audioProcessingEvent.outputBuffer; + let outputData1 = outputBuffer.getChannelData(0); + let outputData2 = outputBuffer.getChannelData(1); + + this.audioWritePosition = Module._neilGetAudioWritePosition(); + + + //the bytes are arranged L,R,L,R,etc.... for each speaker + for (let sample = 0; sample < AUDIOBUFFSIZE; sample++) { + + if (this.audioWritePosition != this.audioReadPosition) { + outputData1[sample] = (this.audioBufferResampled[this.audioReadPosition] / 32768); + outputData2[sample] = (this.audioBufferResampled[this.audioReadPosition + 1] / 32768); + + this.audioReadPosition += 2; + + //wrap back around within the ring buffer + if (this.audioReadPosition == 64000) { + this.audioReadPosition = 0; + } + } + else { + //if there's nothing to play then just play silence + outputData1[sample] = 0; + outputData2[sample] = 0; + } + + } + + //calculate remaining audio in buffer + let audioBufferRemaining = 0; + let readPositionTemp = this.audioReadPosition; + let writePositionTemp = this.audioWritePosition; + for(let i = 0; i < 64000; i++) + { + if (readPositionTemp != writePositionTemp) + { + readPositionTemp += 2; + audioBufferRemaining += 2; + + if (readPositionTemp == 64000) { + readPositionTemp = 0; + } + } + } + + } + + extractBaseName(){ + try + { + this.base_name = this.rom_name.substr(0,this.rom_name.lastIndexOf('.')); + } + catch{ + this.base_name = 'blank'; + } + } + + async load_file(path) { + + console.log('loading ' + path); + myClass.load_url_request(path); + } + + load_url_request(path){ + + //check cache + let cleanPath = path.substr(path.lastIndexOf('/')+1); + if (cleanPath.endsWith('.img')) + { + let baseImageName = cleanPath.replace(".img",".baseimage"); + if (myClass.dblistBaseImages.includes(baseImageName)) + { + myClass.loadFromDatabase(SaveTypes.BaseImage); + return; + } + } + if (cleanPath.endsWith('.iso')) + { + if (myClass.dblistIsos.includes(cleanPath)) + { + myClass.loadFromDatabase(SaveTypes.ISO); + return; + } + } + + this.showProgress = true; + + var req = new XMLHttpRequest(); + req.open("GET", path); + req.overrideMimeType("text/plain; charset=x-user-defined"); + req.onerror = () => console.log(`Error loading ${path}: ${req.statusText}`); + req.responseType = "arraybuffer"; + + req.onprogress = function (event) { + let loaded = event.loaded; + let total = event.total; + let percent = (loaded / total)*100; + + loaded = Math.ceil(loaded / 1000000); + total = Math.ceil(total / 1000000); + + let formatted = loaded + 'MB / ' + total + 'MB'; + + document.getElementById('myProgress').style.width= percent + '%'; + document.getElementById('myProgress').innerHTML = formatted; + }; + req.onload = function (e) { + console.log('request loaded',e,req); + var arrayBuffer = req.response; // Note: not oReq.responseText + try{ + if (req.status==404) + { + console.log('request returned 404'); + + if (myClass.loggedIn) + { + myClass.load_file(myClass.settings.DEFAULTIMG); + } + } + else if (arrayBuffer) { + var byteArray = new Uint8Array(arrayBuffer); + myClass.LoadEmulator(byteArray); + } + else{ + this.lblError = 'Error downloading data. Try reloading browser.'; + console.log('error downloading') + console.log(req); + } + } + catch(error){ + console.log(error); + toastr.error('Error Loading Save'); + } + }; + + req.send(); + } + + + newRom(){ + location.reload(); + } + + onError(message){ + console.log('error triggered',event); + if ( + !message.includes('user has exited the lock') + ) + { + this.lblError = message; + } + } + + //prevent dropdown from popping up from keyboard events + dropdownKeyDown(e){ + e.preventDefault(); + e.stopPropagation(); + } + + fullscreen() { + let el = document.getElementById('canvasDiv'); + + if (el.webkitRequestFullScreen) { + el.webkitRequestFullScreen(); + } + else { + el.mozRequestFullScreen(); + } + } + + zoomIn(){ + this.canvasHeight += 30; + document.getElementById('canvasDiv').style.height = + this.canvasHeight + 'px' + localStorage.setItem('doswasmx-height', this.canvasHeight.toString()); + this.resizeCanvas(); + console.log('zoom in'); + } + + zoomOut(){ + this.canvasHeight -= 30; + localStorage.setItem('doswasmx-height', this.canvasHeight.toString()); + this.resizeCanvas(); + console.log('zoom out'); + } + + resizeCanvas(){ + document.getElementById('canvasDiv').style.height = this.canvasHeight + 'px'; + } + + saveDrive() + { + let bytes = FS.readFile('/' + this.base_name + '.img'); //this is a Uint8Array + this.saveToDatabase(bytes, SaveTypes.Disk); + } + + readFromLocalStorage(localStorageName, name){ + if (localStorage.getItem(localStorageName)) + { + if (localStorage.getItem(localStorageName)=="true") + this[name] = true; + else if (localStorage.getItem(localStorageName)=="false") + this[name] = false; + else + this[name] = localStorage.getItem(localStorageName); + } + } + + writeToLocalStorage(localStorageName, name){ + + if (typeof(this[name]) == 'boolean') + { + if (this[name]) + localStorage.setItem(localStorageName, 'true'); + else + localStorage.setItem(localStorageName, 'false'); + } + else + { + localStorage.setItem(localStorageName, this[name]); + } + + } + + retrieveSettings(){ + this.readFromLocalStorage('doswasmx-ram','ram'); + this.readFromLocalStorage('doswasmx-initialhd','initialHardDrive'); + this.readFromLocalStorage('doswasmx-dosversion','dosVersion'); + } + + saveOptions(){ + this.ram = this.ramTemp; + this.initialHardDrive = this.initialHardDriveTemp; + this.dosVersion = this.dosVersionTemp; + + this.writeToLocalStorage('doswasmx-ram','ram'); + this.writeToLocalStorage('doswasmx-initialhd','initialHardDrive'); + this.writeToLocalStorage('doswasmx-dosversion','dosVersion'); + } + + createDB() { + + if (window["indexedDB"]==undefined){ + console.log('indexedDB not available'); + return; + } + + var request = indexedDB.open('DOSWASMXDB'); + request.onupgradeneeded = function (ev) { + console.log('upgrade needed'); + let db = ev.target.result; + let objectStore = db.createObjectStore('DOSWASMXSTATES', { autoIncrement: true }); + objectStore.transaction.oncomplete = function (event) { + console.log('db created'); + }; + } + + request.onsuccess = function (ev) { + var db = ev.target.result; + var romStore = db.transaction("DOSWASMXSTATES", "readwrite").objectStore("DOSWASMXSTATES"); + try { + //rewrote using cursor instead of getAllKeys + //for compatibility with MS EDGE + romStore.openCursor().onsuccess = function (ev) { + var cursor = ev.target.result; + if (cursor) { + let rom = cursor.key.toString(); + if (rom.endsWith('.savestate')) + { + myClass.dblistSavestates.push(rom); + } + if (rom.endsWith('.disk')) + { + myClass.dblistDisks.push(rom); + } + if (rom.endsWith('.iso')) + { + myClass.dblistIsos.push(rom); + } + if (rom.endsWith('.baseimage')) + { + myClass.dblistBaseImages.push(rom); + } + cursor.continue(); + } + else { + myClass.initCount++; + myClass.finishInitialization(); + } + } + + } catch (error) { + console.log('error reading keys'); + console.log(error); + } + + } + + } + + findSavestateInDatabase() { + + let imgKey = myClass.base_name; + if (!myClass.loggedIn) imgKey = 'win95'; + imgKey += + '.savestate'; + + myClass.dblistSavestates.forEach(save => { + if (save == imgKey) + { + console.log('found savestate in indexedDB'); + myClass.noLocalSave = false; + } + }); + } + + + /** + * Description + * @param {any} data + * @param {SaveTypes} saveType + * @returns {any} + */ + saveToDatabase(data, saveType) { + + if (!window["indexedDB"]==undefined){ + console.log('indexedDB not available'); + return; + } + + console.log('save to database called: ', data.length); + + var request = indexedDB.open('DOSWASMXDB'); + request.onsuccess = function (ev) { + var db = ev.target.result; + var transaction = db.transaction("DOSWASMXSTATES", "readwrite"); + var romStore = transaction.objectStore("DOSWASMXSTATES"); + let imgKey = myClass.base_name; + if (!myClass.loggedIn) imgKey = 'win95'; + + if (saveType == SaveTypes.Savestate) + { + imgKey = imgKey + '.savestate'; + } + if (saveType == SaveTypes.Disk) + { + imgKey = imgKey + '.disk'; + } + if (saveType == SaveTypes.ISO) + { + imgKey = imgKey + '.iso'; + } + if (saveType == SaveTypes.BaseImage) + { + imgKey = imgKey + '.baseimage' + } + + var addRequest = romStore.put(data, imgKey); + addRequest.onsuccess = function (event) { + console.log('data onsuccess'); + //these take a long time so we want to let the user know + if (saveType != SaveTypes.Savestate) + { + toastr.info('Please Wait...'); + } + }; + addRequest.onerror = function (event) { + toastr.error('Error Saving Data'); + console.log('error adding data'); + console.log(event); + }; + transaction.oncomplete = function(event) { + console.log('transaction completed'); + if (saveType == SaveTypes.Savestate) + { + myClass.showToast("State Saved") + toastr.info('State Saved'); + } + if (saveType == SaveTypes.Disk) + { + myClass.showToast("Hard Drive Saved") + toastr.info('Hard Drive Saved'); + } + if (saveType == SaveTypes.BaseImage) + { + myClass.showToast("Base Image Saved") + toastr.info('Base Image Saved'); + myClass.baseImageSaved = true; + myClass.cacheIsoAndBaseImage(); + } + if (saveType == SaveTypes.ISO) + { + myClass.showToast("ISO Saved") + toastr.info('ISO Saved'); + myClass.isoSaved = true; + myClass.cacheIsoAndBaseImage(); + + } + } + } + } + + + /** + * Description + * @param {SaveTypes} saveType + * @returns {any} + */ + loadFromDatabase(saveType) { + + var request = indexedDB.open('DOSWASMXDB'); + request.onsuccess = function (ev) { + var db = ev.target.result; + var romStore = db.transaction("DOSWASMXSTATES", "readwrite").objectStore("DOSWASMXSTATES"); + let imgKey = myClass.base_name; + if (!myClass.loggedIn) imgKey = 'win95'; + + if (saveType == SaveTypes.Savestate) + { + imgKey = imgKey + '.savestate'; + } + if (saveType == SaveTypes.Disk) + { + imgKey = imgKey + '.disk'; + } + if (saveType == SaveTypes.ISO) + { + imgKey = imgKey + '.iso'; + } + if (saveType == SaveTypes.BaseImage) + { + imgKey = imgKey + '.baseimage'; + } + + + var rom = romStore.get(imgKey); + rom.onsuccess = function (event) { + if (saveType == SaveTypes.Savestate) + { + let byteArray = rom.result; //Uint8Array + FS.writeFile('/save/1.sav',byteArray); + Module._neil_unserialize(); + } + if (saveType == SaveTypes.Disk) + { + if (myClass.hardDiskFallbackFromFloppy) + { + let byteArray = rom.result; //Uint8Array + let imgName = '/' + myClass.base_name + '.img'; + FS.writeFile(imgName,byteArray); + myClass.sendDosCommands('imgmount c \"' + myClass.base_name + ".img\na:\n"); + } + else if (!myClass.loggedIn) + { + let byteArray = rom.result; //Uint8Array + let imgName = '/' + myClass.base_name + '.img'; + FS.writeFile(imgName,byteArray); + console.log('loaded drive from db: ' + imgName); + myClass.img_loaded = true; + myClass.LoadEmulator(); + } + else + { + //TODO - if we are logged in then this is the + //base image so we need to apply the diff drive + } + } + if (saveType == SaveTypes.ISO || saveType == SaveTypes.BaseImage) + { + let byteArray = rom.result; //Uint8Array + myClass.LoadEmulator(byteArray); + } + }; + rom.onerror = function (event) { + toastr.error('error getting rom from store'); + } + } + request.onerror = function (ev) { + toastr.error('error loading from db') + } + + } + + clearHardDrive(){ + + let romToDelete = 'win95.disk'; + + if (!window["indexedDB"]==undefined){ + console.log('indexedDB not available'); + return; + } + + var request = indexedDB.open('DOSWASMXDB'); + request.onsuccess = function (ev) { + var db = ev.target.result; + var transaction = db.transaction("DOSWASMXSTATES", "readwrite"); + let request = transaction.objectStore("DOSWASMXSTATES").delete(romToDelete); + + try { + // report that the data item has been deleted + transaction.oncomplete = function() { + toastr.success('Hard Drive Deleted'); + $('#settingsModal').modal('hide'); + myClass.dblistDisks = []; + }; + + } catch (error) { + toastr.error('Error Deleting Disk'); + console.log(error); + } + } + + } + + + clearDatabase() { + + var request = indexedDB.deleteDatabase('DOSWASMXDB'); + request.onerror = function (event) { + console.log("Error deleting database."); + toastr.error("Error deleting database"); + }; + + request.onsuccess = function (event) { + console.log("Database deleted successfully"); + toastr.error("Database deleted successfully"); + }; + + } + + async unzipFile(arrayBuffer){ + + const data = new Blob([ arrayBuffer ]) + let file = new File([data], 'win95.zip'); + + document.getElementById('myProgress').innerHTML = 'Decompressing...'; + + let zipReader = new zip.ZipReader(new zip.BlobReader(file)); + let entries = await zipReader.getEntries() + let blob = await entries[0].getData(new zip.BlobWriter()); + let byteArray = new Uint8Array(await blob.arrayBuffer()); + document.getElementById('myProgress').innerHTML = 'Finished Decompressing'; + + + myClass.LoadEmulator(byteArray); + } + + toggleFPS(){ + Module._neil_toggle_fps(); + } + + exportModal(){ + $("#exportModal").modal(); + } + + settingsModal(){ + + this.ramTemp = this.ram; + this.initialHardDriveTemp = this.initialHardDrive; + this.dosVersionTemp = this.dosVersion; + + $("#settingsModal").modal(); + } + + settingsSubmit(){ + this.saveOptions(); + $('#settingsModal').modal('hide'); + toastr.info("Settings Saved"); + } + + importModal(importType){ + myClass.noCopyImport = false; + myClass.changeCD = false; + myClass.loadCD = false; + myClass.changeFloppy = false; + myClass.loadFloppy = false; + if (importType == 'noCopy') + { + myClass.noCopyImport = true; + } + if (importType == 'changeCD') + { + myClass.changeCD = true; + } + if (importType == 'changeFloppy') + { + myClass.changeFloppy = true; + } + if (importType == 'loadFloppy') + { + myClass.loadFloppy = true; + } + if (importType == 'loadCD') + { + myClass.loadCD = true; + } + myClass.importStatus = ''; + $("#importModal").modal(); + } + + exportFiles(){ + console.log('exportFiles'); + $('#exportModal').modal('hide'); + this.exportFilesRequested = true; + Module._neil_export_files(); + } + + saveStateLocal(){ + console.log('saveStateLocal'); + this.noLocalSave = false; + Module._neil_serialize(); + } + + loadStateLocal(){ + console.log('loadStateLocal'); + myClass.loadFromDatabase(SaveTypes.Savestate); + } + + //when it returns from emscripten + SaveStateEvent() + { + console.log('js savestate event'); + let compressed = FS.readFile('/save/1.sav'); //this is a Uint8Array + + if (!myClass.loggedIn) + { + myClass.saveToDatabase(compressed, SaveTypes.Savestate); + return; + } + + var saveMessage = "Cloud State Saved"; + + var xhr = new XMLHttpRequest; + xhr.open("POST", this.settings.CLOUDSAVEURL + "/SendStaveState?name=" + this.base_name + '.savestate.doswasmx' + + "&password=" + this.password + "&emulator=doswasmx", true); + xhr.send(compressed); + + xhr.onreadystatechange = function() { + try{ + if (xhr.readyState === 4) { + let result = xhr.response; + if (result=="\"Success\""){ + myClass.noCloudSave = false; + toastr.info(saveMessage); + myClass.showToast(saveMessage); + }else{ + toastr.error('Error Saving Cloud Save'); + } + } + } + catch(error){ + console.log(error); + toastr.error('Error Loading Cloud Save'); + } + + } + } + + async loadHardDriveDiffs(byteArray){ + + await myClass.getSaveStates(); + + let promise = new Promise(function (resolve, reject) { + + let foundCloudDrive = false; + + for(let i = 0; i < myClass.allSaveStates.length; i++) + { + let element = myClass.allSaveStates[i]; + if (element.Name==myClass.base_name + ".doswasmx") + { + foundCloudDrive = true; + console.log('foundCloudDrive'); + } + } + + // we didnt find a cloud drive + if (!foundCloudDrive) + { + resolve(byteArray); + return; + } + + toastr.info('Found Diff Drive'); + + var oReq = new XMLHttpRequest(); + oReq.open("GET", myClass.settings.CLOUDSAVEURL + "/LoadStaveState?name=" + myClass.base_name + '.doswasmx' + + "&password=" + myClass.password, true); + oReq.responseType = "arraybuffer"; + + oReq.onload = function (oEvent) { + var arrayBuffer = oReq.response; // Note: not oReq.responseText + try{ + if (arrayBuffer) { + var byteArray = new Uint8Array(arrayBuffer); + myClass.applyHardDriveDiffs(byteArray, resolve); + } + else{ + reject(); + } + } + catch(error){ + console.log(error); + reject(); + } + + }; + + oReq.send(null); + + }); + + return promise; + } + + async applyHardDriveDiffs(byteArrayDiffs, resolve){ + console.log('applyHardDriveDiffs'); + + let pointer = 0; + + byteArrayDiffs = await this.decompressArrayBuffer(byteArrayDiffs.buffer); + + //start with a copy of the hold hard drive + let newHardDrive = new Uint8Array(this.baseHardDrive); + + while(pointer < byteArrayDiffs.length) + { + let index = byteArrayDiffs[pointer] + (byteArrayDiffs[pointer+1]*256) + + (byteArrayDiffs[pointer+2]*256*256) + (byteArrayDiffs[pointer+3]*256*256*256); + pointer += 4; + + let length = byteArrayDiffs[pointer] + (byteArrayDiffs[pointer+1]*256) + + (byteArrayDiffs[pointer+2]*256*256) + (byteArrayDiffs[pointer+3]*256*256*256); + pointer += 4; + + //apply the diffs + for (let i = 0; i < length; i++) + { + newHardDrive[index] = byteArrayDiffs[pointer]; + pointer++; + index++; + } + } + + resolve(newHardDrive); + } + + async saveHardDriveDiffs(){ + + //pause dosbox + Module._neil_toggle_pause(); + + this.message += 'Calculating Diffs...'; + await new Promise(resolve => {setTimeout(resolve, 20); }); + + let compareHardDrive = new Uint8Array(); + compareHardDrive = FS.readFile('/' + this.base_name + '.img'); //this is a Uint8Array + + let chunkSize = 10000; + let arrayChunks = []; //array of Uint8SubArrays each of size chunk + + this.diffCount = 0; + let progressCounter = 5000000; //we update progress every 5 million + for (let i = 0; i < this.baseHardDrive.length; i++) { + if (this.baseHardDrive[i] != compareHardDrive[i]) + { + let end = i + chunkSize; + if (end >= this.baseHardDrive.length) + { + end = this.baseHardDrive.length-1; + } + + let subArray = compareHardDrive.subarray(i,end); + arrayChunks.push( + { + index: i, + data: subArray + }); + + + i += chunkSize-1; + this.diffCount++; + + } + + if (i > progressCounter) + { + + let percent = Math.floor( (i / this.baseHardDrive.length)*100 ); + + this.message = "Diffs: " + this.diffCount + + ", " + percent + "%"; + + await new Promise(resolve => {setTimeout(resolve, 20); }); + + progressCounter += 5000000; + } + } + + + this.arrayChunks = arrayChunks; + console.log(arrayChunks); + + let finalsize = 0; + + for(let i = 0; i < arrayChunks.length; i++) + { + //8 bytes for the two ints representing index and length + finalsize += 8; + + let chunk = arrayChunks[i]; + finalsize += chunk.data.length; + } + + this.message = "Generating Final Array..."; + await new Promise(resolve => {setTimeout(resolve, 20); }); + + + let finalArray = new Uint8Array(finalsize); + let pointer = 0; + for(let i = 0; i < arrayChunks.length; i++) + { + let chunk = arrayChunks[i]; + let index = chunk.index; + + // index (little endian) + finalArray[pointer] = index & 0xFF; + finalArray[pointer+1] = (index >> 8) & 0xFF; + finalArray[pointer+2] = (index >> 16) & 0xFF; + finalArray[pointer+3] = (index >> 24) & 0xFF; + + pointer += 4; + + let length = chunk.data.length; + + // length (little endian) + finalArray[pointer] = length & 0xFF; + finalArray[pointer+1] = (length >> 8) & 0xFF; + finalArray[pointer+2] = (length >> 16) & 0xFF; + finalArray[pointer+3] = (length >> 24) & 0xFF; + + pointer += 4; + + for(let j = 0; j < chunk.data.length; j++) + { + finalArray[pointer] = chunk.data[j] + pointer++; + } + } + + //compress drive + finalArray = await this.compressArrayBuffer(finalArray.buffer); + + console.log('diffSize: ' + finalsize + ' compressedSize: ' + finalArray.length); + + if (this.doIntegrityCheck) + { + this.message = 'Doing Integrity Check...'; + } + else + { + Module._neil_toggle_pause(); + this.message = 'Sending to server...'; + } + + + var saveMessage = "Saved: " + finalArray.length.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + + var xhr = new XMLHttpRequest; + xhr.open("POST", this.settings.CLOUDSAVEURL + "/SendStaveState?name=" + this.base_name + '.doswasmx' + + "&password=" + this.password + "&emulator=doswasmx", true); + xhr.send(finalArray); + + xhr.onreadystatechange = function() { + try{ + if (xhr.readyState === 4) { + let result = xhr.response; + if (result=="\"Success\""){ + toastr.info(saveMessage); + + if (myClass.doIntegrityCheck) + { + myClass.integrityCheck(compareHardDrive); + } + else + { + myClass.message = ''; + } + + }else{ + toastr.error('Error Saving Cloud Save'); + } + } + } + catch(error){ + console.log(error); + toastr.error('Error Loading Cloud Save'); + } + + } + + } + + async compressArrayBuffer(input) { + //create the stream + const cs = new CompressionStream("gzip"); + //create the writer + const writer = cs.writable.getWriter(); + //write the buffer to the writer + writer.write(input); + writer.close(); + //create the output + const output = []; + const reader = cs.readable.getReader(); + let totalSize = 0; + //go through each chunk and add it to the output + while (true) { + const { value, done } = await reader.read(); + if (done) break; + output.push(value); + totalSize += value.byteLength; + } + const concatenated = new Uint8Array(totalSize); + let offset = 0; + //finally build the compressed array and return it + for (const array of output) { + concatenated.set(array, offset); + offset += array.byteLength; + } + console.log('compressed', concatenated); + + return concatenated; + } + + async decompressArrayBuffer(input) { + //create the stream + const ds = new DecompressionStream("gzip"); + //create the writer + const writer = ds.writable.getWriter(); + //write the buffer to the writer thus decompressing it + writer.write(input); + writer.close(); + //create the output + const output = []; + //create the reader + const reader = ds.readable.getReader(); + let totalSize = 0; + //go through each chunk and add it to the output + while (true) { + const { value, done } = await reader.read(); + if (done) break; + output.push(value); + totalSize += value.byteLength; + } + const concatenated = new Uint8Array(totalSize); + let offset = 0; + //finally build the compressed array and return it + for (const array of output) { + concatenated.set(array, offset); + offset += array.byteLength; + } + + return concatenated; + } + + exportHardDrive(){ + let imgName = this.base_name + '.img'; + let exportName = imgName; + if (!this.loggedIn) + { + exportName = 'hdd.img'; + } + let filearray = FS.readFile(imgName); + var file = new File([filearray], exportName, {type: "text/plain; charset=x-user-defined"}); + saveAs(file); + } + + importFiles(event){ + console.log('import files'); + + if (!myClass.noCopyImport) + { + var rando = Math.floor(Math.random() * Math.floor(1000)); + myClass.importFolderName = 'Imp' + rando; + FS.mkdir('/' + myClass.importFolderName); + } + + this.isSpecialHandler = false; + this.importedFileNames = []; + let files = event.currentTarget.files; + + for(let i = 0; i < files.length; i++) + { + this.importedFileNames.push(files[i].name); + let fileExtension = files[i].name.substr(files[i].name.lastIndexOf('.')).toLocaleLowerCase(); + if (this.specialFileHandlers.includes(fileExtension)) + { + this.isSpecialHandler = true; + } + } + + myClass.processImportFiles(files, 0) + } + + processImportFiles(files, index){ + var file = files[index]; + console.log('processing file ' + (index+1) + ' of ' + files.length, file); + + var reader = new FileReader(); + + reader.onprogress = function (event) { + let loaded = event.loaded; + let total = event.total; + + loaded = Math.ceil(loaded / 1000000); + total = Math.ceil(total / 1000000); + + console.log('loaded: ' + event.loaded); + myClass.importStatus = '(' + (index+1) + ' of ' + files.length + ') ' + + file.name + ' ' + loaded + 'MB / ' + total + 'MB'; + }; + reader.onload = function (e) { + var byteArray = new Uint8Array(this.result); + + if (myClass.noCopyImport || myClass.isSpecialHandler || myClass.changeFloppy || myClass.loadFloppy) + { + FS.writeFile('/' + file.name, byteArray); + } + else + { + FS.writeFile('/' + myClass.importFolderName + '/' + file.name, byteArray); + } + + if ( (index+1) { + //focus on textbox + $("#txtPassword").focus(); + }, 500); + } + + logout(){ + this.loggedIn = false; + this.password = ''; + localStorage.setItem('doswasmx-password', this.password); + } + + async loginSubmit(){ + $('#loginModal').modal('hide'); + this.loginModalOpened = false; + let result = await this.loginToServer(); + if (result=='Success'){ + toastr.success('Logged In'); + localStorage.setItem('doswasmx-password', this.password); + await this.getSaveStates(); + this.postLoginProcess(); + } + else{ + toastr.error('Login Failed'); + this.password = ''; + localStorage.setItem('doswasmx-password', ''); + } + } + + async loginSilent(){ + if (!this.hasCloud) + return; + + let result = await this.loginToServer(); + if (result=='Success'){ + await this.getSaveStates(); + this.postLoginProcess(); + } + } + + postLoginProcess(){ + //filter by .doswasmx extension and sort by date + this.dosSaveStates = this.allSaveStates.filter((state)=>{ + return state.Name.endsWith('.savestate.doswasmx') + }); + this.dosSaveStates.forEach(state => { + state.Date = this.convertCSharpDateTime(state.Date); + }); + this.dosSaveStates.sort((a,b)=>{ return b.Date.getTime() - a.Date.getTime() }); + this.loggedIn = true; + } + + convertCSharpDateTime(initialDate) { + let dateString = initialDate; + dateString = dateString.substring(0, dateString.indexOf('T')); + let timeString = initialDate.substr(initialDate.indexOf("T") + 1); + let dateComponents = dateString.split('-'); + let timeComponents = timeString.split(':'); + let myDate = null; + + myDate = new Date(parseInt(dateComponents[0]), parseInt(dateComponents[1]) - 1, parseInt(dateComponents[2]), + parseInt(timeComponents[0]), parseInt(timeComponents[1]), parseInt(timeComponents[2])); + return myDate; + } + + async loginToServer(){ + let result = await $.get(this.settings.CLOUDSAVEURL + '/Login?password=' + this.password); + console.log('login result: ' + result); + return result; + } + + async getSaveStates(){ + if (!this.loggedIn) + return; + + let result = await $.get(this.settings.CLOUDSAVEURL + '/GetSaveStates?password=' + this.password); + console.log('getSaveStates result: ', result); + this.allSaveStates = result; + result.forEach(element => { + if (element.Name==this.base_name + ".savestate.doswasmx") + this.noCloudSave = false; + }); + } + + + + //USE THIS FOR DOING AN INTEGRITY CHECK ON DIFFED HARD DRIVE - + async integrityCheck(newHardDriveBytes) { + + let finalByteArray = await this.loadHardDriveDiffs(this.baseHardDrive); //hard drive with applied diffs + + //compare bytes + + this.message += 'Calculating Diffs...'; + await new Promise(resolve => {setTimeout(resolve, 20); }); + + let compareHardDrive = finalByteArray; + + let chunkSize = 10000; + let arrayChunks = []; //array of Uint8SubArrays each of size chunk + + this.diffCount = 0; + let progressCounter = 5000000; //we update progress every 5 million + for (let i = 0; i < newHardDriveBytes.length; i++) { + if (newHardDriveBytes[i] != compareHardDrive[i]) + { + let end = i + chunkSize; + if (end >= newHardDriveBytes.length) + { + end = newHardDriveBytes.length-1; + } + + let subArray = compareHardDrive.subarray(i,end); + arrayChunks.push( + { + index: i, + data: subArray + }); + + + i += chunkSize; + this.diffCount++; + + } + + if (i > progressCounter) + { + + let percent = Math.floor( (i / newHardDriveBytes.length)*100 ); + + this.message = "Diffs: " + this.diffCount + + ", " + percent + "%"; + + await new Promise(resolve => {setTimeout(resolve, 20); }); + + progressCounter += 5000000; + } + } + + console.log(arrayChunks); + + let finalsize = 0; + + for(let i = 0; i < arrayChunks.length; i++) + { + //8 bytes for the two ints representing index and length + finalsize += 8; + + let chunk = arrayChunks[i]; + finalsize += chunk.data.length; + } + + this.message = "Generating Final Array Size: " + finalsize.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + + this.message = "Diffs: " + this.diffCount + + " Final Array Size: " + finalsize.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + + " DONE"; + + console.log('integrity check results', arrayChunks, this.diffCount); + + if (arrayChunks.length>0) + { + toastr.error("Failed integrity check"); + } + else + { + toastr.success("Passed integrity check"); + } + + setTimeout(() => { + myClass.message = ''; + }, 2000); + + Module._neil_toggle_pause(); + + } + + togglePause(){ + Module._neil_toggle_pause(); + } + + updateCPU(value){ + this.cpu = value; + if (value == 'auto') + { + this.updateCpuNeil('cycles=auto'); + } + else if (value == 'max') + { + this.updateCpuNeil('cycles=max'); + } + else + { + this.updateCpuNeil('cycles=fixed ' + value); + } + } + + sendCtrlAltDel(){ + Module._neil_send_ctrlaltdel(); + } + + toggle16BitColorFix(){ + Module._neil_toggle_16_bit_color_fix(); + } + + toggleAlwaysUseBackbuffer(){ + Module._neil_toggle_always_use_backbuffer(); + } + + toggleAutoKeybaord(){ + this.autoKeyboard = !this.autoKeyboard; + + if (this.autoKeyboard) + { + this.autoKeyboardTimer = this.autoKeyboardInterval; + toastr.info('Auto Keyboard Enabled'); + } + else + { + toastr.info('Auto Keyboard Disabled'); + } + } + + //used to automate keyboard buttons on a timer (useful for certain games) + tickAutoKeyboard(){ + this.autoKeyboardTimer--; + if (this.autoKeyboardTimer==0) + { + this.showToast("Autokeyboard...") + + this.sendKey(48) //F12 + + setTimeout(() => { + myClass.sendKey(52); //enter + }, 600); + + setTimeout(() => { + myClass.sendKey(52); //enter + }, 3000); + + this.autoKeyboardTimer = this.autoKeyboardInterval; + } + } + + turboSpeed() + { + Module._neil_turbo(); + } + +} + + +let myClass = new MyClass(); +window["myApp"] = myClass; //so that I can reference from EM_ASM + +window["Module"] = { + onRuntimeInitialized: myClass.initModule, + canvas: document.getElementById('canvas'), + print: (text) => myClass.processPrintStatement(text), + // printErr: (text) => myClass.print(text) +} + +var script = document.createElement('script'); +script.src = 'main.js' +document.getElementsByTagName('head')[0].appendChild(script); + + +window.onerror = function(message) { + console.log('window.onerror',message); + myClass.onError(message); +} + +window.onunhandledrejection = function(error) { + console.log('window.onunhandledrejection',error); + myClass.onError(error.reason.message); +} + \ No newline at end of file diff --git a/doswasmx/settings.js b/doswasmx/settings.js new file mode 100644 index 0000000..67c1f22 --- /dev/null +++ b/doswasmx/settings.js @@ -0,0 +1,10 @@ +var DOSWASMSETTINGS = { + CLOUDSAVEURL: "", + ISOURL: "", + DEFAULTIMG: "" +} + +var rando = Math.floor(Math.random() * Math.floor(100000)); +var script = document.createElement('script'); +script.src = 'script.js?v=' + rando; +document.getElementsByTagName('head')[0].appendChild(script); \ No newline at end of file