mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-14 05:30:53 +00:00
667 lines
14 KiB
C++
667 lines
14 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "glk/frotz/processor.h"
|
|
#include "glk/frotz/frotz.h"
|
|
|
|
namespace Glk {
|
|
namespace Frotz {
|
|
|
|
// TODO: Stubs to replace with actual code
|
|
zword save_undo() { return 0; }
|
|
zword restore_undo() { return 0; }
|
|
|
|
|
|
Opcode Processor::var_opcodes[64] = {
|
|
&Processor::__illegal__,
|
|
&Processor::z_je,
|
|
&Processor::z_jl,
|
|
&Processor::z_jg,
|
|
&Processor::z_dec_chk,
|
|
&Processor::z_inc_chk,
|
|
&Processor::z_jin,
|
|
&Processor::z_test,
|
|
&Processor::z_or,
|
|
&Processor::z_and,
|
|
&Processor::z_test_attr,
|
|
&Processor::z_set_attr,
|
|
&Processor::z_clear_attr,
|
|
&Processor::z_store,
|
|
&Processor::z_insert_obj,
|
|
&Processor::z_loadw,
|
|
&Processor::z_loadb,
|
|
&Processor::z_get_prop,
|
|
&Processor::z_get_prop_addr,
|
|
&Processor::z_get_next_prop,
|
|
&Processor::z_add,
|
|
&Processor::z_sub,
|
|
&Processor::z_mul,
|
|
&Processor::z_div,
|
|
&Processor::z_mod,
|
|
&Processor::z_call_s,
|
|
&Processor::z_call_n,
|
|
&Processor::z_set_colour,
|
|
&Processor::z_throw,
|
|
&Processor::__illegal__,
|
|
&Processor::__illegal__,
|
|
&Processor::__illegal__,
|
|
&Processor::z_call_s,
|
|
&Processor::z_storew,
|
|
&Processor::z_storeb,
|
|
&Processor::z_put_prop,
|
|
&Processor::z_read,
|
|
&Processor::z_print_char,
|
|
&Processor::z_print_num,
|
|
&Processor::z_random,
|
|
&Processor::z_push,
|
|
&Processor::z_pull,
|
|
&Processor::z_split_window,
|
|
&Processor::z_set_window,
|
|
&Processor::z_call_s,
|
|
&Processor::z_erase_window,
|
|
&Processor::z_erase_line,
|
|
&Processor::z_set_cursor,
|
|
&Processor::z_get_cursor,
|
|
&Processor::z_set_text_style,
|
|
&Processor::z_buffer_mode,
|
|
&Processor::z_output_stream,
|
|
&Processor::z_input_stream,
|
|
&Processor::z_sound_effect,
|
|
&Processor::z_read_char,
|
|
&Processor::z_scan_table,
|
|
&Processor::z_not,
|
|
&Processor::z_call_n,
|
|
&Processor::z_call_n,
|
|
&Processor::z_tokenise,
|
|
&Processor::z_encode_text,
|
|
&Processor::z_copy_table,
|
|
&Processor::z_print_table,
|
|
&Processor::z_check_arg_count
|
|
};
|
|
|
|
Opcode Processor::ext_opcodes[64] = {
|
|
&Processor::z_save,
|
|
&Processor::z_restore,
|
|
&Processor::z_log_shift,
|
|
&Processor::z_art_shift,
|
|
&Processor::z_set_font,
|
|
&Processor::z_draw_picture,
|
|
&Processor::z_picture_data,
|
|
&Processor::z_erase_picture,
|
|
&Processor::z_set_margins,
|
|
&Processor::z_save_undo,
|
|
&Processor::z_restore_undo,
|
|
&Processor::z_print_unicode,
|
|
&Processor::z_check_unicode,
|
|
&Processor::z_set_true_colour, // spec 1.1
|
|
&Processor::__illegal__,
|
|
&Processor::__illegal__,
|
|
&Processor::z_move_window,
|
|
&Processor::z_window_size,
|
|
&Processor::z_window_style,
|
|
&Processor::z_get_wind_prop,
|
|
&Processor::z_scroll_window,
|
|
&Processor::z_pop_stack,
|
|
&Processor::z_read_mouse,
|
|
&Processor::z_mouse_window,
|
|
&Processor::z_push_stack,
|
|
&Processor::z_put_wind_prop,
|
|
&Processor::z_print_form,
|
|
&Processor::z_make_menu,
|
|
&Processor::z_picture_table,
|
|
&Processor::z_buffer_screen // spec 1.1
|
|
};
|
|
|
|
Processor::Processor(OSystem *syst, const GlkGameDescription &gameDesc) :
|
|
GlkInterface(syst, gameDesc),
|
|
_finished(0), _sp(nullptr), _fp(nullptr), _frameCount(0),
|
|
zargc(0), _decoded(nullptr), _encoded(nullptr), _resolution(0),
|
|
_randomInterval(0), _randomCtr(0), first_restart(true), script_valid(false),
|
|
_bufPos(0), _locked(false), _prevC('\0'), script_width(0),
|
|
sfp(nullptr), rfp(nullptr), pfp(nullptr), ostream_screen(true), ostream_script(false),
|
|
ostream_memory(false), ostream_record(false), istream_replay(false), message(false) {
|
|
static const Opcode OP0_OPCODES[16] = {
|
|
&Processor::z_rtrue,
|
|
&Processor::z_rfalse,
|
|
&Processor::z_print,
|
|
&Processor::z_print_ret,
|
|
&Processor::z_nop,
|
|
&Processor::z_save,
|
|
&Processor::z_restore,
|
|
&Processor::z_restart,
|
|
&Processor::z_ret_popped,
|
|
&Processor::z_catch,
|
|
&Processor::z_quit,
|
|
&Processor::z_new_line,
|
|
&Processor::z_show_status,
|
|
&Processor::z_verify,
|
|
&Processor::__extended__,
|
|
&Processor::z_piracy
|
|
};
|
|
static const Opcode OP1_OPCODES[16] = {
|
|
&Processor::z_jz,
|
|
&Processor::z_get_sibling,
|
|
&Processor::z_get_child,
|
|
&Processor::z_get_parent,
|
|
&Processor::z_get_prop_len,
|
|
&Processor::z_inc,
|
|
&Processor::z_dec,
|
|
&Processor::z_print_addr,
|
|
&Processor::z_call_s,
|
|
&Processor::z_remove_obj,
|
|
&Processor::z_print_obj,
|
|
&Processor::z_ret,
|
|
&Processor::z_jump,
|
|
&Processor::z_print_paddr,
|
|
&Processor::z_load,
|
|
&Processor::z_call_n
|
|
};
|
|
|
|
op0_opcodes.resize(16);
|
|
op1_opcodes.resize(16);
|
|
Common::copy(&OP0_OPCODES[0], &OP0_OPCODES[16], &op0_opcodes[0]);
|
|
Common::copy(&OP1_OPCODES[0], &OP1_OPCODES[16], &op1_opcodes[0]);
|
|
Common::fill(&_stack[0], &_stack[STACK_SIZE], 0);
|
|
Common::fill(&zargs[0], &zargs[8], 0);
|
|
Common::fill(&_buffer[0], &_buffer[TEXT_BUFFER_SIZE], '\0');
|
|
Common::fill(&_errorCount[0], &_errorCount[ERR_NUM_ERRORS], 0);
|
|
}
|
|
|
|
void Processor::initialize() {
|
|
Mem::initialize();
|
|
GlkInterface::initialize();
|
|
|
|
if (h_version <= V4) {
|
|
op0_opcodes[9] = &Processor::z_pop;
|
|
op1_opcodes[15] = &Processor::z_not;
|
|
} else {
|
|
op0_opcodes[9] = &Processor::z_catch;
|
|
op1_opcodes[15] = &Processor::z_call_n;
|
|
}
|
|
}
|
|
|
|
void Processor::load_operand(zbyte type) {
|
|
zword value;
|
|
|
|
if (type & 2) {
|
|
// variable
|
|
zbyte variable;
|
|
|
|
CODE_BYTE(variable);
|
|
|
|
if (variable == 0)
|
|
value = *_sp++;
|
|
else if (variable < 16)
|
|
value = *(_fp - variable);
|
|
else {
|
|
zword addr = h_globals + 2 * (variable - 16);
|
|
LOW_WORD(addr, value);
|
|
}
|
|
} else if (type & 1) {
|
|
// small constant
|
|
zbyte bvalue;
|
|
|
|
CODE_BYTE(bvalue);
|
|
value = bvalue;
|
|
|
|
} else {
|
|
// large constant
|
|
CODE_WORD(value);
|
|
}
|
|
|
|
zargs[zargc++] = value;
|
|
}
|
|
|
|
void Processor::load_all_operands(zbyte specifier) {
|
|
for (int i = 6; i >= 0; i -= 2) {
|
|
zbyte type = (specifier >> i) & 0x03;
|
|
|
|
if (type == 3)
|
|
break;
|
|
|
|
load_operand(type);
|
|
}
|
|
}
|
|
|
|
void Processor::interpret() {
|
|
do {
|
|
zbyte opcode;
|
|
CODE_BYTE(opcode);
|
|
zargc = 0;
|
|
|
|
if (opcode < 0x80) {
|
|
// 2OP opcodes
|
|
load_operand((zbyte)(opcode & 0x40) ? 2 : 1);
|
|
load_operand((zbyte)(opcode & 0x20) ? 2 : 1);
|
|
|
|
(*this.*var_opcodes[opcode & 0x1f])();
|
|
|
|
} else if (opcode < 0xb0) {
|
|
// 1OP opcodes
|
|
load_operand((zbyte)(opcode >> 4));
|
|
|
|
(*this.*op1_opcodes[opcode & 0x0f])();
|
|
|
|
} else if (opcode < 0xc0) {
|
|
// 0OP opcodes
|
|
(*this.*op0_opcodes[opcode - 0xb0])();
|
|
|
|
} else {
|
|
// VAR opcodes
|
|
zbyte specifier1;
|
|
zbyte specifier2;
|
|
|
|
if (opcode == 0xec || opcode == 0xfa) { // opcodes 0xec
|
|
CODE_BYTE(specifier1); // and 0xfa are
|
|
CODE_BYTE(specifier2); // call opcodes
|
|
load_all_operands(specifier1); // with up to 8
|
|
load_all_operands(specifier2); // arguments
|
|
} else {
|
|
CODE_BYTE(specifier1);
|
|
load_all_operands(specifier1);
|
|
}
|
|
|
|
(*this.*var_opcodes[opcode - 0xc0])();
|
|
}
|
|
|
|
#if defined(DJGPP) && defined(SOUND_SUPPORT)
|
|
if (end_of_sound_flag)
|
|
end_of_sound();
|
|
#endif
|
|
} while (!shouldQuit() && !_finished);
|
|
|
|
_finished--;
|
|
}
|
|
|
|
void Processor::call(zword routine, int argc, zword *args, int ct) {
|
|
long pc;
|
|
zword value;
|
|
zbyte count;
|
|
int i;
|
|
|
|
if (_sp - _stack < 4)
|
|
runtimeError(ERR_STK_OVF);
|
|
|
|
GET_PC(pc);
|
|
|
|
*--_sp = (zword)(pc >> 9);
|
|
*--_sp = (zword)(pc & 0x1ff);
|
|
*--_sp = (zword)(_fp - _stack - 1);
|
|
*--_sp = (zword)(argc | (ct << (_quetzal ? 12 : 8)));
|
|
|
|
_fp = _sp;
|
|
_frameCount++;
|
|
|
|
// Calculate byte address of routine
|
|
if (h_version <= V3)
|
|
pc = (long)routine << 1;
|
|
else if (h_version <= V5)
|
|
pc = (long)routine << 2;
|
|
else if (h_version <= V7)
|
|
pc = ((long)routine << 2) + ((long)h_functions_offset << 3);
|
|
else if (h_version <= V8)
|
|
pc = (long)routine << 3;
|
|
else {
|
|
// h_version == V9
|
|
long indirect = (long)routine << 2;
|
|
HIGH_LONG(indirect, pc);
|
|
}
|
|
|
|
if ((uint)pc >= story_size)
|
|
runtimeError(ERR_ILL_CALL_ADDR);
|
|
|
|
SET_PC(pc);
|
|
|
|
// Initialise local variables
|
|
CODE_BYTE(count);
|
|
|
|
if (count > 15)
|
|
runtimeError(ERR_CALL_NON_RTN);
|
|
if (_sp - _stack < count)
|
|
runtimeError(ERR_STK_OVF);
|
|
|
|
if (_quetzal)
|
|
_fp[0] |= (zword)count << 8; // Save local var count for Quetzal.
|
|
|
|
value = 0;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (h_version <= V4) // V1 to V4 games provide default
|
|
CODE_WORD(value); // values for all local variables
|
|
|
|
*--_sp = (zword)((argc-- > 0) ? args[i] : value);
|
|
}
|
|
|
|
// Start main loop for direct calls
|
|
if (ct == 2)
|
|
interpret();
|
|
}
|
|
|
|
void Processor::ret(zword value) {
|
|
long pc;
|
|
int ct;
|
|
|
|
if (_sp > _fp)
|
|
runtimeError(ERR_STK_UNDF);
|
|
|
|
_sp = _fp;
|
|
|
|
ct = *_sp++ >> (_quetzal ? 12 : 8);
|
|
_frameCount--;
|
|
_fp = _stack + 1 + *_sp++;
|
|
pc = *_sp++;
|
|
pc = ((long)*_sp++ << 9) | pc;
|
|
|
|
SET_PC(pc);
|
|
|
|
// Handle resulting value
|
|
if (ct == 0)
|
|
store(value);
|
|
if (ct == 2)
|
|
*--_sp = value;
|
|
|
|
// Stop main loop for direct calls
|
|
if (ct == 2)
|
|
_finished++;
|
|
}
|
|
|
|
void Processor::branch(bool flag) {
|
|
long pc;
|
|
zword offset;
|
|
zbyte specifier;
|
|
zbyte off1;
|
|
zbyte off2;
|
|
|
|
CODE_BYTE(specifier);
|
|
off1 = specifier & 0x3f;
|
|
|
|
if (!flag)
|
|
specifier ^= 0x80;
|
|
|
|
if (!(specifier & 0x40)) {
|
|
// it's a long branch
|
|
if (off1 & 0x20) // propagate sign bit
|
|
off1 |= 0xc0;
|
|
|
|
CODE_BYTE(off2);
|
|
offset = (off1 << 8) | off2;
|
|
} else {
|
|
// It's a short branch
|
|
offset = off1;
|
|
}
|
|
|
|
if (specifier & 0x80) {
|
|
if (offset > 1) {
|
|
// normal branch
|
|
GET_PC(pc);
|
|
pc += (short)offset - 2;
|
|
SET_PC(pc);
|
|
} else {
|
|
// special case, return 0 or 1
|
|
ret(offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Processor::store(zword value) {
|
|
zbyte variable;
|
|
|
|
CODE_BYTE(variable);
|
|
|
|
if (variable == 0)
|
|
*--_sp = value;
|
|
else if (variable < 16)
|
|
*(_fp - variable) = value;
|
|
else {
|
|
zword addr = h_globals + 2 * (variable - 16);
|
|
SET_WORD(addr, value);
|
|
}
|
|
}
|
|
|
|
int Processor::direct_call(zword addr) {
|
|
zword saved_zargs[8];
|
|
int saved_zargc;
|
|
int i;
|
|
|
|
// Calls to address 0 return false
|
|
if (addr == 0)
|
|
return 0;
|
|
|
|
// Save operands and operand count
|
|
for (i = 0; i < 8; i++)
|
|
saved_zargs[i] = zargs[i];
|
|
|
|
saved_zargc = zargc;
|
|
|
|
// Call routine directly
|
|
call(addr, 0, 0, 2);
|
|
|
|
// Restore operands and operand count
|
|
for (i = 0; i < 8; i++)
|
|
zargs[i] = saved_zargs[i];
|
|
|
|
zargc = saved_zargc;
|
|
|
|
// Resulting value lies on top of the stack
|
|
return (short)*_sp++;
|
|
}
|
|
|
|
void Processor::seed_random(int value) {
|
|
if (value == 0) {
|
|
// Now using random values
|
|
_randomInterval = 0;
|
|
} else if (value < 1000) {
|
|
// special seed value
|
|
_randomCtr = 0;
|
|
_randomInterval = value;
|
|
} else {
|
|
// standard seed value
|
|
_random.setSeed(value);
|
|
_randomInterval = 0;
|
|
}
|
|
}
|
|
|
|
void Processor::__extended__() {
|
|
zbyte opcode;
|
|
zbyte specifier;
|
|
|
|
CODE_BYTE(opcode);
|
|
CODE_BYTE(specifier);
|
|
|
|
load_all_operands(specifier);
|
|
|
|
if (opcode < 0x1e) // extended opcodes from 0x1e on
|
|
(*this.*ext_opcodes[opcode])(); // are reserved for future spec'
|
|
}
|
|
|
|
void Processor::__illegal__() {
|
|
runtimeError(ERR_ILL_OPCODE);
|
|
}
|
|
|
|
void Processor::z_catch() {
|
|
store(_quetzal ? _frameCount : (zword)(_fp - _stack));
|
|
}
|
|
|
|
void Processor::z_throw() {
|
|
if (_quetzal) {
|
|
if (zargs[1] > _frameCount)
|
|
runtimeError(ERR_BAD_FRAME);
|
|
|
|
// Unwind the stack a frame at a time.
|
|
for (; _frameCount > zargs[1]; --_frameCount)
|
|
_fp = _stack + 1 + _fp[1];
|
|
} else {
|
|
if (zargs[1] > STACK_SIZE)
|
|
runtimeError(ERR_BAD_FRAME);
|
|
|
|
_fp = _stack + zargs[1];
|
|
}
|
|
|
|
ret(zargs[0]);
|
|
}
|
|
|
|
void Processor::z_call_n() {
|
|
if (zargs[0] != 0)
|
|
call(zargs[0], zargc - 1, zargs + 1, 1);
|
|
}
|
|
|
|
void Processor::z_call_s() {
|
|
if (zargs[0] != 0)
|
|
call(zargs[0], zargc - 1, zargs + 1, 0);
|
|
else
|
|
store(0);
|
|
}
|
|
|
|
void Processor::z_check_arg_count() {
|
|
if (_fp == _stack + STACK_SIZE)
|
|
branch(zargs[0] == 0);
|
|
else
|
|
branch(zargs[0] <= (*_fp & 0xff));
|
|
}
|
|
|
|
void Processor::z_jump() {
|
|
long pc;
|
|
GET_PC(pc);
|
|
|
|
pc += (short)zargs[0] - 2;
|
|
|
|
if ((uint)pc >= story_size)
|
|
runtimeError(ERR_ILL_JUMP_ADDR);
|
|
|
|
SET_PC(pc);
|
|
}
|
|
|
|
void Processor::z_nop() {
|
|
// Do nothing
|
|
}
|
|
|
|
void Processor::z_quit() {
|
|
_finished = 9999;
|
|
}
|
|
|
|
void Processor::z_ret() {
|
|
ret(zargs[0]);
|
|
}
|
|
|
|
void Processor::z_ret_popped() {
|
|
ret(*_sp++);
|
|
}
|
|
|
|
void Processor::z_rfalse() {
|
|
ret(0);
|
|
}
|
|
|
|
void Processor::z_rtrue() {
|
|
ret(1);
|
|
}
|
|
|
|
void Processor::z_random() {
|
|
if ((short) zargs[0] <= 0) {
|
|
// set random seed
|
|
seed_random(- (short) zargs[0]);
|
|
store(0);
|
|
|
|
} else {
|
|
// generate random number
|
|
zword result;
|
|
if (_randomInterval != 0) {
|
|
// ...in special mode
|
|
result = _randomCtr++;
|
|
if (_randomCtr == _randomInterval)
|
|
_randomCtr = 0;
|
|
} else {
|
|
// ...in standard mode
|
|
result = _random.getRandomNumber(0xffff);
|
|
}
|
|
|
|
store((zword)(result % zargs[0] + 1));
|
|
}
|
|
}
|
|
|
|
void Processor::z_sound_effect() {
|
|
zword number = zargs[0];
|
|
zword effect = zargs[1];
|
|
zword volume = zargs[2];
|
|
|
|
if (zargc < 1)
|
|
number = 0;
|
|
if (zargc < 2)
|
|
effect = EFFECT_PLAY;
|
|
if (zargc < 3)
|
|
volume = 8;
|
|
|
|
if (number >= 3 || number == 0) {
|
|
_soundLocked = true;
|
|
|
|
if (_storyId == LURKING_HORROR && (number == 9 || number == 16)) {
|
|
if (effect == EFFECT_PLAY) {
|
|
next_sample = number;
|
|
next_volume = volume;
|
|
|
|
_soundLocked = false;
|
|
|
|
if (!_soundPlaying)
|
|
start_next_sample();
|
|
} else {
|
|
_soundLocked = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
_soundPlaying = false;
|
|
|
|
switch (effect) {
|
|
case EFFECT_PREPARE:
|
|
os_prepare_sample(number);
|
|
break;
|
|
case EFFECT_PLAY:
|
|
start_sample(number, lo(volume), hi(volume), (zargc == 4) ? zargs[3] : 0);
|
|
break;
|
|
case EFFECT_STOP:
|
|
os_stop_sample (number);
|
|
break;
|
|
case EFFECT_FINISH_WITH:
|
|
os_finish_with_sample (number);
|
|
break;
|
|
}
|
|
|
|
_soundLocked = false;
|
|
} else {
|
|
os_beep(number);
|
|
}
|
|
}
|
|
|
|
void Processor::z_piracy() {
|
|
branch(!_piracy);
|
|
}
|
|
|
|
void Processor::z_save_undo(void) {
|
|
store((zword)save_undo());
|
|
}
|
|
|
|
void Processor::z_restore_undo(void) {
|
|
store((zword)restore_undo());
|
|
}
|
|
|
|
} // End of namespace Frotz
|
|
} // End of namespace Glk
|