ifvms
Version:
The Interactive Fiction Virtual Machines Suite - in Javascript
267 lines (228 loc) • 5.42 kB
JavaScript
/*
ZVM - the ifvms.js Z-Machine (versions 3-5, 8)
==============================================
Copyright (c) 2017 The ifvms.js team
MIT licenced
https://github.com/curiousdannii/ifvms.js
*/
/*
This file is the public API of ZVM, which is based on the API of Quixe:
https://github.com/erkyrath/quixe/wiki/Quixe-Without-GlkOte#quixes-api
ZVM willfully ignores the standard in these ways:
Non-buffered output is not supported
Saving tables is not supported (yet?)
No interpreter number or version is set
Any other non-standard behaviour should be considered a bug
*/
'use strict';
var utils = require( './common/utils.js' ),
file = require( './common/file.js' ),
default_options = {
stack_len: 100 * 1000,
undo_len: 1000 * 1000,
},
api = {
init: function()
{
// Create this here so that it won't be cleared on restart
this.jit = {};
// The Quixe API expects the start function to be named init
this.init = this.start;
},
prepare: function( storydata, options )
{
// If we are not given a glk option then we cannot continue
if ( !options.Glk )
{
throw new Error( 'A reference to Glk is required' );
}
this.Glk = options.Glk;
this.data = storydata;
this.options = utils.extend( {}, default_options, options );
},
start: function()
{
var Glk = this.Glk,
data;
try
{
// Identify the format and version number of the data file we were given
data = file.identify( this.data );
delete this.data;
if ( !data || data.format !== 'ZCOD' )
{
throw new Error( 'This is not a Z-Code file' );
}
if ( [ 3, 4, 5, 8 ].indexOf( data.version ) < 0 )
{
throw new Error( 'Unsupported Z-Machine version: ' + data.version );
}
// Load the storyfile we are given into our MemoryView (an enhanced DataView)
this.m = utils.MemoryView( data.data );
// Make a seperate MemoryView for the ram, and store the original ram
this.staticmem = this.m.getUint16( 0x0E );
this.ram = utils.MemoryView( this.m, 0, this.staticmem );
this.origram = this.m.getUint8Array( 0, this.staticmem );
// Cache the game signature
let signature = ''
let i = 0
while ( i < 0x1E )
{
signature += ( this.origram[i] < 0x10 ? '0' : '' ) + this.origram[i++].toString( 16 )
}
this.signature = signature
// Handle loading and clearing autosaves
let autorestored
const Dialog = this.options.Dialog
if ( Dialog )
{
if ( this.options.clear_vm_autosave )
{
Dialog.autosave_write( signature, null )
}
else if ( this.options.do_vm_autosave )
{
try
{
const snapshot = Dialog.autosave_read( signature )
if ( snapshot )
{
this.do_autorestore( snapshot )
autorestored = 1
}
}
catch (ex)
{
this.log('Autorestore failed, deleting it: ' + ex)
Dialog.autosave_write( signature, null )
}
}
}
// Initiate the engine, run, and wait for our first Glk event
if ( !autorestored )
{
this.restart();
this.run();
}
if ( !this.quit )
{
this.glk_event = new Glk.RefStruct();
if ( !this.glk_blocking_call )
{
Glk.glk_select( this.glk_event );
}
else
{
this.glk_event.push_field( this.glk_blocking_call );
}
}
Glk.update()
}
catch ( e )
{
Glk.fatal_error( e );
console.log( e );
}
},
resume: function( resumearg )
{
var Glk = this.Glk,
glk_event = this.glk_event,
event_type,
run;
try
{
event_type = glk_event.get_field( 0 );
// Process the event
if ( event_type === 2 )
{
this.handle_char_input( glk_event.get_field( 2 ) );
run = 1;
}
if ( event_type === 3 )
{
this.handle_line_input( glk_event.get_field( 2 ), glk_event.get_field( 3 ) );
run = 1;
}
// Arrange events
if ( event_type === 5 )
{
this.update_screen_size()
}
// glk_fileref_create_by_prompt handler
if ( event_type === 'fileref_create_by_prompt' )
{
run = this.handle_create_fileref( resumearg );
}
this.glk_blocking_call = null;
if ( run )
{
this.run();
}
// Wait for another event
if ( !this.quit )
{
this.glk_event = new Glk.RefStruct();
if ( !this.glk_blocking_call )
{
Glk.glk_select( this.glk_event );
}
else
{
this.glk_event.push_field( this.glk_blocking_call );
}
}
Glk.update()
}
catch ( e )
{
Glk.fatal_error( e );
console.log( e );
}
},
get_signature: function()
{
return this.signature
},
// Run
run: function()
{
var pc,
result;
// Stop when ordered to
this.stop = 0;
while ( !this.stop )
{
pc = this.pc;
if ( !this.jit[pc] )
{
this.compile();
}
result = this.jit[pc]( this );
// Return from a VM func if the JIT function returned a result
if ( !isNaN( result ) )
{
this.ret( result );
}
}
},
// Compile a JIT routine
compile: function()
{
var context = this.disassemble();
// Compile the routine with new Function()
this.jit[context.pc] = new Function( 'e', '' + context );
if ( context.pc < this.staticmem )
{
this.log( 'Caching a JIT function in dynamic memory: ' + context.pc );
}
},
},
VM = utils.Class.subClass( utils.extend(
api,
require( './zvm/runtime.js' ),
require( './zvm/text.js' ),
require( './zvm/io.js' ),
require( './zvm/disassembler.js' )
) );
module.exports = VM;