03a3459e Andy McCall 15 days ago
1
2
3
const statusElement = document . getElementById ( ' status' ) ;
4
const output = document . getElementById ( ' output' ) ;
5
const canvas = document . getElementById ( ' canvas' ) ;
6
const code = document . getElementById ( ' code' ) ;
7
const progressElement = document . getElementById ( ' progress' ) ;
8
const spinnerElement = document . getElementById ( ' spinner' ) ;
9
const volumeElementFullScreen = document . getElementById ( ' fullscreen_volume_icon' ) ;
10
const volumeElement = document . getElementById ( ' volume_icon' ) ;
11
12
13
14
var audioContext ;
15
16
window . addEventListener ( ' load' , init , false ) ;
17
function init ( ) {
18
19
try {
20
window . AudioContext = window . AudioContext || window . webkitAudioContext ;
21
audioContext = new AudioContext ( ) ;
22
}
23
catch ( e ) {
24
console . log ( " AudioContext not supported on this Browser." )
25
}
26
}
27
28
29
30
31
const layouts = [
32
' en-us' ,
33
' en-us-int' ,
34
' en-gb' ,
35
' sv' ,
36
' de' ,
37
' da' ,
38
' it' ,
39
' pl' ,
40
' nb' ,
41
' hu' ,
42
' es' ,
43
' fi' ,
44
' pt-br' ,
45
' cz' ,
46
' jp' ,
47
' fr' ,
48
' de-ch' ,
49
' en-us-dvo' ,
50
' et' ,
51
' fr-be' ,
52
' fr-ca' ,
53
' is' ,
54
' pt' ,
55
' hr' ,
56
' sk' ,
57
' sl' ,
58
' lv' ,
59
' lt'
60
] ;
61
62
lang = getFirstBrowserLanguage ( ) . toLowerCase ( ) . trim ( ) ;
63
64
if ( layouts . includes ( lang ) ) {
65
logOutput ( ' Using keyboard map: ' + lang ) ;
66
} else {
67
logOutput ( ' Language (' + lang + ' ) not found in keymaps so using keyboard map: en-us' ) ;
68
lang = ' en-us' ;
69
}
70
71
var url = new URL ( window . location . href ) ;
72
var manifest_link = url . searchParams . get ( " manifest" ) ;
73
var ram_val = url . searchParams . get ( " ram" ) ;
74
var cpu_val = url . searchParams . get ( " cpu" ) ;
75
var mhz_val = url . searchParams . get ( " mhz" ) ;
76
var lang_val = url . searchParams . get ( " keymap" ) ;
77
var diag = url . searchParams . get ( " longpwron" ) ;
78
var wide = url . searchParams . get ( " widescreen" ) ;
79
var capture = url . searchParams . get ( " capture" ) ;
80
var midline = url . searchParams . get ( " midlineeffects" ) ;
81
82
var emuArguments = [ ' -keymap' , lang , ' -rtc' ] ;
83
84
if ( ram_val ) {
85
emuArguments . push ( ' -ram' , ram_val ) ;
86
}
87
if ( cpu_val ) {
88
if ( cpu_val == ' c816' ) {
89
emuArguments . push ( ' -c816' ) ;
90
} else if ( cpu_val == ' c02' ) {
91
emuArguments . push ( ' -c02' ) ;
92
}
93
}
94
if ( mhz_val ) {
95
emuArguments . push ( ' -mhz' , mhz_val ) ;
96
}
97
if ( lang_val ) {
98
emuArguments . push ( ' -keymap' , lang_val ) ;
99
}
100
if ( diag != null ) {
101
emuArguments . push ( ' -longpwron' ) ;
102
}
103
if ( wide != null ) {
104
emuArguments . push ( ' -widescreen' ) ;
105
}
106
if ( capture != null ) {
107
emuArguments . push ( ' -capture' ) ;
108
}
109
if ( midline != null ) {
110
emuArguments . push ( ' -midline-effects' ) ;
111
}
112
113
if ( manifest_link ) {
114
openFs ( ) ;
115
}
116
117
var Module = {
118
119
preRun : [
120
function ( ) {
121
ENV . SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = " #canvas" ;
122
} ,
123
function ( ) {
124
125
if ( manifest_link ) {
126
loadManifestLink ( ) ;
127
}
128
}
129
] ,
130
postRun : [
131
function ( ) {
132
canvas . focus ( ) ;
133
}
134
] ,
135
arguments : emuArguments ,
136
print : ( function ( ) {
137
138
if ( output ) output . value = ' ' ;
139
return function ( text ) {
140
if ( arguments . length > 1 ) text = Array . prototype . slice . call ( arguments ) . join ( ' ' ) ;
141
logOutput ( text ) ;
142
} ;
143
} ) ( ) ,
144
printErr : function ( text ) {
145
if ( arguments . length > 1 ) text = Array . prototype . slice . call ( arguments ) . join ( ' ' ) ;
146
147
148
if ( text . startsWith ( ' wasm streaming compile failed:' ) ||
149
text . startsWith ( ' falling back to ArrayBuffer instantiation' ) ||
150
text . startsWith ( ' Calling stub instead of sigaction' ) ) {
151
logOutput ( " [known behavior] " + text ) ;
152
return ;
153
}
154
155
logError ( text ) ;
156
157
158
} ,
159
canvas : ( function ( ) {
160
161
162
163
164
canvas . addEventListener ( " webglcontextlost" , function ( e ) {
165
alert ( ' WebGL context lost. You will need to reload the page.' ) ;
166
e . preventDefault ( ) ;
167
} , false ) ;
168
return canvas ;
169
} ) ( ) ,
170
setStatus : function ( text ) {
171
if ( ! Module . setStatus . last ) Module . setStatus . last = {
172
time : Date . now ( ) ,
173
text : ' '
174
} ;
175
if ( text === Module . setStatus . last . text ) return ;
176
const m = text . match ( / ( [ ^ (] + ) \( ( \d + ( \. \d + ) ? ) \/ ( \d + ) \) / ) ;
177
let now = Date . now ( ) ;
178
if ( m && now - Module . setStatus . last . time < 30 ) return ;
179
Module . setStatus . last . time = now ;
180
Module . setStatus . last . text = text ;
181
if ( m ) {
182
text = m [ 1 ] ;
183
progressElement . value = parseInt ( m [ 2 ] ) * 100 ;
184
progressElement . max = parseInt ( m [ 4 ] ) * 100 ;
185
progressElement . hidden = false ;
186
spinnerElement . hidden = false ;
187
} else {
188
progressElement . value = null ;
189
progressElement . max = null ;
190
progressElement . hidden = true ;
191
if ( ! text ) spinnerElement . hidden = true ;
192
}
193
statusElement . innerHTML = text ;
194
logOutput ( text ) ;
195
} ,
196
totalDependencies : 0 ,
197
monitorRunDependencies : function ( left ) {
198
this . totalDependencies = Math . max ( this . totalDependencies , left ) ;
199
Module . setStatus ( left ? ' Preparing... (' + ( this . totalDependencies - left ) + ' /' + this . totalDependencies + ' )' : ' All downloads complete.' ) ;
200
}
201
} ;
202
203
Module . setStatus ( ' Downloading file...' ) ;
204
logOutput ( ' Downloading file...' ) ;
205
206
window . onerror = function ( ) {
207
208
spinnerElement . style . display = ' none' ;
209
Module . setStatus = function ( text ) {
210
if ( text ) Module . printErr ( ' [post-exception status] ' + text ) ;
211
} ;
212
} ;
213
214
function loadManifestLink ( ) {
215
addRunDependency ( ' load-manifest-link' ) ;
216
console . log ( " Loading URL:" , manifest_link ) ;
217
fetch ( manifest_link )
218
. then ( function ( response ) {
219
var disposition = response . headers . get ( ' Content-Disposition' ) ;
220
if ( disposition ) {
221
var filename = parseDispositionFilename ( disposition ) ;
222
if ( filename ) {
223
console . log ( " Loading filename:" , filename ) ;
224
if ( filename . toLowerCase ( ) . endsWith ( ' .bas' ) ) {
225
console . log ( " Loading from BAS." ) ;
226
loadBas ( manifest_link , filename ) ;
227
} else if ( filename . toLowerCase ( ) . endsWith ( ' .prg' ) ) {
228
console . log ( " Loading from PRG." ) ;
229
loadPrg ( manifest_link , filename ) ;
230
} else if ( filename . toLowerCase ( ) . endsWith ( ' .zip' ) ) {
231
console . log ( " Loading from ZIP." ) ;
232
loadZip ( manifest_link ) ;
233
}
234
}
235
} else {
236
if ( manifest_link . toLowerCase ( ) . endsWith ( ' .bas' ) ) {
237
var filename = manifest_link . replace ( / ^ . * [ \\ \/ ] / , ' ' ) ;
238
console . log ( " Loading from BAS." ) ;
239
loadBas ( manifest_link , filename ) ;
240
} else if ( manifest_link . toLowerCase ( ) . endsWith ( ' .prg' ) ) {
241
var filename = manifest_link . replace ( / ^ . * [ \\ \/ ] / , ' ' ) ;
242
console . log ( " Loading from PRG." ) ;
243
loadPrg ( manifest_link , filename ) ;
244
} else if ( manifest_link . toLowerCase ( ) . endsWith ( ' .zip' ) ) {
245
console . log ( " Loading from ZIP." ) ;
246
loadZip ( manifest_link ) ;
247
} else {
248
console . log ( " Loading from directory." ) ;
249
if ( ! manifest_link . endsWith ( ' /' ) ) {
250
manifest_link = manifest_link + ' /' ;
251
}
252
loadManifest ( ) ;
253
}
254
}
255
} )
256
. then ( function ( ) {
257
removeRunDependency ( ' load-manifest-link' ) ;
258
} ) ;
259
}
260
261
function parseDispositionFilename ( disposition ) {
262
const utf8FilenameRegex = / filename\* =UTF-8''( [ \w %\- \. ] + ) ( ?: ; ? | $ ) / i ;
263
const asciiFilenameRegex = / ^ filename=( [ "'] ? ) ( . *? [ ^ \\ ] ) \1 ( ?: ; ? | $ ) / i ;
264
265
let fileName = null ;
266
if ( utf8FilenameRegex . test ( disposition ) ) {
267
fileName = decodeURIComponent ( utf8FilenameRegex . exec ( disposition ) [ 1 ] ) ;
268
} else {
269
270
271
const filenameStart = disposition . toLowerCase ( ) . indexOf ( ' filename=' ) ;
272
if ( filenameStart >= 0 ) {
273
const partialDisposition = disposition . slice ( filenameStart ) ;
274
const matches = asciiFilenameRegex . exec ( partialDisposition ) ;
275
if ( matches != null && matches [ 2 ] ) {
276
fileName = matches [ 2 ] ;
277
}
278
}
279
}
280
return fileName ;
281
}
282
283
function loadBas ( basFileUrl , filename ) {
284
console . log ( ' Adding start BAS:' , filename )
285
emuArguments . push ( ' -bas' , filename , ' -run' ) ;
286
FS . createPreloadedFile ( ' /' , filename , basFileUrl , true , true ) ;
287
console . log ( " Starting Emulator..." )
288
console . log ( " Emulator arguments: " , emuArguments )
289
}
290
291
function loadPrg ( prgFileUrl , filename ) {
292
console . log ( ' Adding start PRG:' , filename )
293
emuArguments . push ( ' -prg' , filename , ' -run' ) ;
294
FS . createPreloadedFile ( ' /' , filename , prgFileUrl , true , true ) ;
295
console . log ( " Starting Emulator..." )
296
console . log ( " Emulator arguments: " , emuArguments )
297
}
298
299
function loadZip ( zipFileUrl ) {
300
addRunDependency ( ' load-zip' ) ;
301
fetch ( zipFileUrl )
302
. then ( function ( response ) {
303
if ( response . status === 200 || response . status === 0 ) {
304
return Promise . resolve ( response . blob ( ) ) ;
305
} else {
306
return Promise . reject ( new Error ( response . statusText ) ) ;
307
308
}
309
} )
310
. then ( JSZip . loadAsync )
311
. then ( extractManifestFromBuffer )
312
. then ( function ( ) {
313
console . log ( " Starting Emulator..." )
314
console . log ( " Emulator arguments: " , emuArguments )
315
removeRunDependency ( ' load-zip' ) ;
316
} ) ;
317
}
318
319
function extractManifestFromBuffer ( zip ) {
320
if ( zip . file ( " manifest.json" ) == null ) {
321
console . log ( " Unable to find manifest.json file. Writing all files from zip." ) ;
322
const promises = [ ] ;
323
writeAllFilesFromZip ( zip , promises , [ ] ) ;
324
return Promise . all ( promises ) . then ( ( value ) => {
325
console . log ( " Emulator filesystem loading complete." )
326
} ) ;
327
}
328
else {
329
return zip . file ( " manifest.json" ) . async ( " uint8array" )
330
. then ( function ( content ) {
331
let manifestString = new TextDecoder ( " utf-8" ) . decode ( content ) ;
332
let manifestObject = JSON . parse ( manifestString ) ;
333
console . log ( " Parsed manifest from zip:" )
334
console . log ( manifestObject ) ;
335
336
if ( manifestObject . ram ) {
337
console . log ( ' Found RAM amount: ' + manifestObject . ram ) ;
338
emuArguments . push ( ' -ram' , manifestObject . ram ) ;
339
}
340
341
let gen1 = true ;
342
343
if ( manifestObject . model ) {
344
console . log ( ' Found model: ' + manifestObject . model ) ;
345
if ( manifestObject . model === ' gs' ) {
346
gen1 = false ;
347
emuArguments . push ( ' -gs' ) ;
348
}
349
}
350
351
if ( gen1 === true && manifestObject . cpu ) {
352
console . log ( ' Found CPU type: ' + manifestObject . cpu ) ;
353
if ( manifestObject . cpu == ' c816' ) {
354
emuArguments . push ( ' -c816' ) ;
355
} else if ( manifestObject . cpu == ' c02' ) {
356
emuArguments . push ( ' -c02' ) ;
357
}
358
}
359
if ( manifestObject . mhz ) {
360
console . log ( ' Found mhz variable: ' + manifestObject . mhz ) ;
361
emuArguments . push ( ' -mhz' , manifestObject . mhz ) ;
362
}
363
if ( manifestObject . keymap ) {
364
console . log ( ' Found keymap variable: ' + manifestObject . keymap ) ;
365
emuArguments . push ( ' -keymap' , manifestObject . keymap ) ;
366
}
367
if ( manifestObject . longpwron ) {
368
console . log ( ' Found longpwron variable' ) ;
369
emuArguments . push ( ' -longpwron' ) ;
370
}
371
if ( manifestObject . widescreen ) {
372
console . log ( ' Found widescreen variable' ) ;
373
emuArguments . push ( ' -widescreen' ) ;
374
}
375
if ( manifestObject . capture ) {
376
console . log ( ' Found capture variable' ) ;
377
emuArguments . push ( ' -capture' ) ;
378
}
379
if ( manifestObject . midlineeffects ) {
380
console . log ( ' Found midlineeffects variable' ) ;
381
emuArguments . push ( ' -midline-effects' ) ;
382
}
383
384
const promises = [ ] ;
385
if ( manifestObject . resources ) {
386
console . log ( ' Found resources section in manifest.' ) ;
387
var startFiles = [ ] ;
388
manifestObject . resources . forEach ( function ( element ) {
389
let fileName = element . replace ( / ^ . * [ \\ \/ ] / , ' ' ) ;
390
if ( fileName . toLowerCase ( ) . endsWith ( " .bas" ) || fileName . toLowerCase ( ) . endsWith ( " .prg" ) ) {
391
startFiles . push ( fileName ) ;
392
}
393
if ( zip . file ( fileName ) == null ) {
394
logError ( " Unable to find resources entry: " + fileName ) ;
395
logError ( " This is likely an error, check resources section in manifest." )
396
} else {
397
promises . push ( zip . file ( fileName ) . async ( " uint8array" ) . then ( function ( content ) {
398
console . log ( ' Writing to emulator filesystem:' , fileName ) ;
399
try {
400
FS . writeFile ( fileName , content ) ;
401
}
402
catch ( e ) {
403
console . log ( ' Error writing to emulator filesystem:' , file . name ) ;
404
}
405
} ) ) ;
406
}
407
addStartFile ( manifestObject , startFiles ) ;
408
} ) ;
409
} else {
410
console . log ( ' Resources section not found in manifest. Writing all files from zip.' ) ;
411
writeAllFilesFromZip ( zip , promises , manifestObject ) ;
412
}
413
return Promise . all ( promises ) ;
414
} )
415
. then ( ( value ) => {
416
console . log ( " Emulator filesystem loading complete." )
417
} ) ;
418
}
419
}
420
421
function writeAllFilesFromZip ( zip , promises , manifestObject ) {
422
var startFiles = [ ] ;
423
const writeResources = ( zip ) => {
424
zip . forEach ( ( path , file ) => {
425
if ( file . dir ) {
426
FS . mkdirTree ( file . name ) ;
427
writeResources ( zip . folder ( path ) ) ;
428
return ;
429
} else {
430
if ( file . name . toLowerCase ( ) . endsWith ( " .bas" ) || file . name . toLowerCase ( ) . endsWith ( " .prg" ) ) {
431
startFiles . push ( file . name ) ;
432
}
433
}
434
435
promises . push ( zip . file ( path ) . async ( " uint8array" )
436
. then ( function ( content ) {
437
console . log ( ' Writing to emulator filesystem:' , file . name ) ;
438
try {
439
FS . writeFile ( file . name , content ) ;
440
}
441
catch ( e ) {
442
console . log ( ' Error writing to emulator filesystem:' , file . name ) ;
443
}
444
} )
445
) ;
446
} ) ;
447
} ;
448
writeResources ( zip ) ;
449
addStartFile ( manifestObject , startFiles ) ;
450
}
451
452
function loadManifest ( ) {
453
addRunDependency ( ' load-manifest' ) ;
454
fetch ( manifest_link + ' manifest.json' ) . then ( function ( response ) {
455
return response . json ( ) ;
456
} ) . then ( function ( manifest ) {
457
console . log ( " Loading from manifest:" )
458
console . log ( manifest ) ;
459
var startFiles = [ ] ;
460
manifest . resources . forEach ( element => {
461
element = manifest_link + element ;
462
let filename = element . replace ( / ^ . * [ \\ \/ ] / , ' ' )
463
if ( filename . toLowerCase ( ) . endsWith ( " .bas" ) || filename . toLowerCase ( ) . endsWith ( " .prg" ) ) {
464
startFiles . push ( filename ) ;
465
}
466
FS . createPreloadedFile ( ' /' , filename , element , true , true ) ;
467
} ) ;
468
addStartFile ( manifest , startFiles ) ;
469
console . log ( " Starting Emulator..." )
470
console . log ( " Emulator arguments: " , emuArguments )
471
removeRunDependency ( ' load-manifest' ) ;
472
} ) . catch ( function ( ) {
473
console . log ( " Unable to read manifest. Check the manifest http parameter" ) ;
474
} ) ;
475
}
476
477
function addStartFile ( manifestObject , startFiles ) {
478
if ( manifestObject && manifestObject . start_bas && manifestObject . start_prg ) {
479
logError ( " start_bas and start_prg used in manifest" ) ;
480
logError ( " This is likely an error, defaulting to start_bas" )
481
}
482
483
if ( manifestObject && manifestObject . start_bas ) {
484
console . log ( ' Adding start BAS:' , manifestObject . start_bas )
485
emuArguments . push ( ' -bas' , manifestObject . start_bas , ' -run' ) ;
486
} else if ( manifestObject && manifestObject . start_prg ) {
487
console . log ( ' Adding start PRG:' , manifestObject . start_prg )
488
emuArguments . push ( ' -prg' , manifestObject . start_prg , ' -run' ) ;
489
} else if ( startFiles ) {
490
if ( startFiles . length === 1 ) {
491
492
var filename = startFiles [ 0 ] ;
493
if ( filename . toLowerCase ( ) . endsWith ( " .bas" ) ) {
494
console . log ( ' Adding start BAS:' , filename )
495
emuArguments . push ( ' -bas' , filename , ' -run' ) ;
496
} else if ( filename . toLowerCase ( ) . endsWith ( " .prg" ) ) {
497
console . log ( ' Adding start PRG:' , filename )
498
emuArguments . push ( ' -prg' , filename , ' -run' ) ;
499
}
500
} else {
501
logOutput ( " Start files: " + startFiles ) ;
502
503
}
504
}
505
}
506
507
function toggleAudio ( ) {
508
if ( audioContext && audioContext . state != " running" ) {
509
audioContext . resume ( ) . then ( ( ) => {
510
volumeElement . innerHTML = " volume_up" ;
511
volumeElementFullScreen . innerHTML = " volume_up" ;
512
console . log ( " Resumed Audio." )
513
Module . ccall ( " j2c_start_audio" , " void" , [ " bool" ] , [ true ] ) ;
514
} ) ;
515
} else if ( audioContext && audioContext . state == " running" ) {
516
audioContext . suspend ( ) . then ( function ( ) {
517
console . log ( " Stopped Audio." )
518
volumeElement . innerHTML = " volume_off" ;
519
volumeElementFullScreen . innerHTML = " volume_off" ;
520
Module . ccall ( " j2c_start_audio" , " void" , [ " bool" ] , [ false ] ) ;
521
} ) ;
522
}
523
canvas . focus ( ) ;
524
}
525
526
function resetEmulator ( ) {
527
j2c_reset = Module . cwrap ( " j2c_reset" , " void" , [ ] ) ;
528
j2c_reset ( ) ;
529
canvas . focus ( ) ;
530
}
531
532
function runCode ( ) {
533
Module . ccall ( " j2c_paste" , " void" , [ " string" ] , [ ' \n NEW\n ' + code . value + ' \n RUN\n ' ] ) ;
534
canvas . focus ( ) ;
535
}
536
537
function closeFs ( ) {
538
canvas . parentElement . classList . remove ( " fullscreen" ) ;
539
canvas . focus ( ) ;
540
}
541
542
function openFs ( ) {
543
canvas . parentElement . classList . add ( " fullscreen" ) ;
544
canvas . focus ( ) ;
545
}
546
547
function logOutput ( text ) {
548
if ( output ) {
549
output . innerHTML += text + " \n " ;
550
output . parentElement . scrollTop = output . parentElement . scrollHeight ;
551
}
552
console . log ( text ) ;
553
}
554
555
function logError ( text ) {
556
if ( output ) {
557
output . innerHTML += " [error] " + text + " \n " ;
558
output . parentElement . scrollTop = output . parentElement . scrollHeight ;
559
}
560
console . error ( text ) ;
561
}
562
563
564
565
function getFirstBrowserLanguage ( ) {
566
const nav = window . navigator ,
567
browserLanguagePropertyKeys = [ ' language' , ' browserLanguage' , ' systemLanguage' , ' userLanguage' ] ;
568
let i ,
569
language ;
570
571
572
if ( Array . isArray ( nav . languages ) ) {
573
for ( i = 0 ; i < nav . languages . length ; i ++ ) {
574
language = nav . languages [ i ] ;
575
if ( language && language . length ) {
576
return language ;
577
}
578
}
579
}
580
581
582
for ( i = 0 ; i < browserLanguagePropertyKeys . length ; i ++ ) {
583
language = nav [ browserLanguagePropertyKeys [ i ] ] ;
584
if ( language && language . length ) {
585
return language ;
586
}
587
}
588
589
return null ;
590
}