mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-23 02:11:38 +00:00
c64c7eb4d4
console. Some things are likely to have broken. For instance, I believe the old console was could be visible during gameplay, while ScummVM's is modal. So any place where we output something to the console during gameplay should probably be removed. Some things I've stubbed out. Basically any code that registers a variable. Most of the debugging commands are untested. Syntax may have changed because of different command-line parsing. (I never actually used the old console, so I don't know for sure. :-) Actually, I'm not that interested in reproducing the old console commands faithfully. What we should do now is to make the immediately useful console stuff work. Anything that remains unimplemented should probably be removed. svn-id: r15976
537 lines
14 KiB
C++
537 lines
14 KiB
C++
/* ScummVM - Scumm Interpreter
|
|
* Copyright (C) 2004 The ScummVM project
|
|
*
|
|
* The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* $Header$
|
|
*
|
|
*/
|
|
|
|
// Scripting module: Script resource handling functions
|
|
#include "saga/saga.h"
|
|
#include "saga/yslib.h"
|
|
|
|
#include "saga/gfx.h"
|
|
#include "saga/rscfile_mod.h"
|
|
#include "saga/game_mod.h"
|
|
#include "saga/console.h"
|
|
|
|
#include "saga/script.h"
|
|
|
|
namespace Saga {
|
|
|
|
// Initializes the scripting module.
|
|
// Loads script resource look-up table, initializes script data system
|
|
Script::Script() {
|
|
GAME_RESOURCEDESC gr_desc;
|
|
RSCFILE_CONTEXT *s_lut_ctxt;
|
|
byte *rsc_ptr;
|
|
size_t rsc_len;
|
|
int prevTell;
|
|
int result;
|
|
int i, j;
|
|
|
|
//initialize member variables
|
|
_dbg_thread = 0;
|
|
_scriptContext = 0;
|
|
_voiceLUTPresent = false;
|
|
_scriptLUTEntryLen = 0;
|
|
_currentScript = 0;
|
|
_threadList = 0;
|
|
_abortEnabled = true;
|
|
_skipSpeeches = false;
|
|
memset(_dataBuf, 0, sizeof(_dataBuf));
|
|
|
|
GAME_GetResourceInfo(&gr_desc);
|
|
|
|
debug(0, "Initializing scripting subsystem");
|
|
// Load script resource file context
|
|
_scriptContext = GAME_GetFileContext(GAME_SCRIPTFILE, 0);
|
|
if (_scriptContext == NULL) {
|
|
error("Couldn't get script file context");
|
|
}
|
|
|
|
// Load script LUT resource
|
|
s_lut_ctxt = GAME_GetFileContext(GAME_RESOURCEFILE, 0);
|
|
if (s_lut_ctxt == NULL) {
|
|
error("Couldn't get resource file context");
|
|
}
|
|
|
|
debug(0, "Loading script LUT from resource %u.", gr_desc.script_lut_rn);
|
|
result = RSC_LoadResource(s_lut_ctxt, gr_desc.script_lut_rn, &rsc_ptr, &rsc_len);
|
|
if (result != SUCCESS) {
|
|
error("Error: Couldn't load script resource look-up table");
|
|
}
|
|
|
|
// Create logical script LUT from resource
|
|
if (rsc_len % S_LUT_ENTRYLEN_ITECD == 0) {
|
|
_scriptLUTEntryLen = S_LUT_ENTRYLEN_ITECD;
|
|
} else if (rsc_len % S_LUT_ENTRYLEN_ITEDISK == 0) {
|
|
_scriptLUTEntryLen = S_LUT_ENTRYLEN_ITEDISK;
|
|
} else {
|
|
error("Error: Invalid script lookup table length");
|
|
}
|
|
|
|
// Calculate number of entries
|
|
_scriptLUTMax = rsc_len / _scriptLUTEntryLen;
|
|
|
|
debug(0, "LUT has %d entries.", _scriptLUTMax);
|
|
|
|
// Allocate space for logical LUT
|
|
_scriptLUT = (SCRIPT_LUT_ENTRY *)malloc(_scriptLUTMax * sizeof(SCRIPT_LUT_ENTRY));
|
|
if (_scriptLUT == NULL) {
|
|
error("Error: Couldn't allocate memory for script resource look-up table");
|
|
}
|
|
|
|
// Convert LUT resource to logical LUT
|
|
MemoryReadStream scriptS(rsc_ptr, rsc_len);
|
|
for (i = 0; i < _scriptLUTMax; i++) {
|
|
prevTell = scriptS.pos();
|
|
_scriptLUT[i].script_rn = scriptS.readUint16LE();
|
|
_scriptLUT[i].diag_list_rn = scriptS.readUint16LE();
|
|
_scriptLUT[i].voice_lut_rn = scriptS.readUint16LE();
|
|
|
|
// Skip the unused portion of the structure
|
|
for (j = scriptS.pos(); j < prevTell + _scriptLUTEntryLen; j++) {
|
|
if (scriptS.readByte() != 0)
|
|
warning("Unused scriptLUT part isn't really unused for LUT %d (pos: %d)", i, j);
|
|
}
|
|
}
|
|
|
|
RSC_FreeResource(rsc_ptr);
|
|
|
|
// Any voice lookup table resources present?
|
|
for (i = 0; i < _scriptLUTMax; i++) {
|
|
if (_scriptLUT[i].voice_lut_rn) {
|
|
_voiceLUTPresent = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Initialize script submodules
|
|
_threadList = ys_dll_create();
|
|
|
|
setupScriptFuncList();
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
// Shut down script module gracefully; free all allocated module resources
|
|
Script::~Script() {
|
|
YS_DL_NODE *thread_node;
|
|
SCRIPT_THREAD *thread;
|
|
|
|
if (!_initialized) {
|
|
error("Script not initialized");
|
|
}
|
|
|
|
debug(0, "Shutting down scripting subsystem.");
|
|
|
|
// Free script lookup table
|
|
free(_scriptLUT);
|
|
|
|
// Stop all threads and destroy them
|
|
|
|
for (thread_node = ys_dll_head(_threadList); thread_node != NULL;
|
|
thread_node = ys_dll_next(thread_node)) {
|
|
thread = (SCRIPT_THREAD *)ys_dll_get_data(thread_node);
|
|
SThreadDestroy(thread);
|
|
}
|
|
|
|
_initialized = false;
|
|
}
|
|
|
|
// Loads a script; including script bytecode and dialogue list
|
|
int Script::loadScript(int script_num) {
|
|
SCRIPTDATA *script_data;
|
|
byte *bytecode_p;
|
|
size_t bytecode_len;
|
|
uint32 scriptl_rn;
|
|
byte *diagl_p;
|
|
size_t diagl_len;
|
|
uint32 diagl_rn;
|
|
byte *voicelut_p;
|
|
size_t voicelut_len;
|
|
uint32 voicelut_rn;
|
|
int result;
|
|
|
|
if (GAME_GetGameType() == GID_IHNM) {
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Validate script number
|
|
if ((script_num < 0) || (script_num > _scriptLUTMax)) {
|
|
warning("Script::loadScript(): Invalid script number");
|
|
return FAILURE;
|
|
}
|
|
|
|
// Release old script data if present
|
|
freeScript();
|
|
|
|
// Initialize script data structure
|
|
debug(0, "Loading script data for script #%d", script_num);
|
|
|
|
script_data = (SCRIPTDATA *)malloc(sizeof *script_data);
|
|
if (script_data == NULL) {
|
|
warning("Memory allocation failed");
|
|
return MEM;
|
|
}
|
|
|
|
script_data->loaded = 0;
|
|
|
|
// Initialize script pointers
|
|
script_data->diag = NULL;
|
|
script_data->bytecode = NULL;
|
|
script_data->voice = NULL;
|
|
|
|
// Load script bytecode
|
|
scriptl_rn = _scriptLUT[script_num].script_rn;
|
|
|
|
result = RSC_LoadResource(_scriptContext, scriptl_rn, &bytecode_p, &bytecode_len);
|
|
if (result != SUCCESS) {
|
|
warning("Error loading script bytecode resource");
|
|
free(script_data);
|
|
return FAILURE;
|
|
}
|
|
|
|
script_data->bytecode = loadBytecode(bytecode_p, bytecode_len);
|
|
|
|
if (script_data->bytecode == NULL) {
|
|
warning("Error interpreting script bytecode resource");
|
|
free(script_data);
|
|
RSC_FreeResource(bytecode_p);
|
|
return FAILURE;
|
|
}
|
|
|
|
// Load script dialogue list
|
|
diagl_rn = _scriptLUT[script_num].diag_list_rn;
|
|
|
|
// Load dialogue list resource
|
|
result = RSC_LoadResource(_scriptContext, diagl_rn, &diagl_p, &diagl_len);
|
|
if (result != SUCCESS) {
|
|
warning("Error loading dialogue list resource");
|
|
free(script_data);
|
|
RSC_FreeResource(bytecode_p);
|
|
return FAILURE;
|
|
}
|
|
|
|
// Convert dialogue list resource to logical dialogue list
|
|
script_data->diag = loadDialogue(diagl_p, diagl_len);
|
|
if (script_data->diag == NULL) {
|
|
warning("Error interpreting dialogue list resource");
|
|
free(script_data);
|
|
RSC_FreeResource(bytecode_p);
|
|
RSC_FreeResource(diagl_p);
|
|
return FAILURE;
|
|
}
|
|
|
|
// Load voice resource lookup table
|
|
if (_voiceLUTPresent) {
|
|
voicelut_rn = _scriptLUT[script_num].voice_lut_rn;
|
|
|
|
// Load voice LUT resource
|
|
result = RSC_LoadResource(_scriptContext, voicelut_rn, &voicelut_p, &voicelut_len);
|
|
if (result != SUCCESS) {
|
|
warning("Error loading voice LUT resource");
|
|
free(script_data);
|
|
RSC_FreeResource(bytecode_p);
|
|
RSC_FreeResource(diagl_p);
|
|
return FAILURE;
|
|
}
|
|
|
|
// Convert voice LUT resource to logical voice LUT
|
|
script_data->voice = loadVoiceLUT(voicelut_p, voicelut_len, script_data);
|
|
if (script_data->voice == NULL) {
|
|
warning("Error interpreting voice LUT resource");
|
|
free(script_data);
|
|
RSC_FreeResource(bytecode_p);
|
|
RSC_FreeResource(diagl_p);
|
|
RSC_FreeResource(voicelut_p);
|
|
return FAILURE;
|
|
}
|
|
}
|
|
|
|
// Finish initialization
|
|
script_data->loaded = 1;
|
|
_currentScript = script_data;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Frees all resources associated with current script.
|
|
int Script::freeScript() {
|
|
if (_currentScript == NULL) {
|
|
return FAILURE;
|
|
}
|
|
|
|
if (!_currentScript->loaded) {
|
|
return FAILURE;
|
|
}
|
|
|
|
debug(0, "Releasing script data.");
|
|
|
|
// Finish initialization
|
|
if (_currentScript->diag != NULL) {
|
|
free(_currentScript->diag->str);
|
|
free(_currentScript->diag->str_off);
|
|
}
|
|
free(_currentScript->diag);
|
|
|
|
if (_currentScript->bytecode != NULL) {
|
|
free(_currentScript->bytecode->entrypoints);
|
|
RSC_FreeResource(_currentScript->bytecode->bytecode_p);
|
|
}
|
|
|
|
free(_currentScript->bytecode);
|
|
|
|
if (_voiceLUTPresent) {
|
|
free(_currentScript->voice->voices);
|
|
free(_currentScript->voice);
|
|
}
|
|
|
|
free(_currentScript);
|
|
|
|
_currentScript = NULL;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Reads the entrypoint table from a script bytecode resource in memory.
|
|
// Returns NULL on failure.
|
|
SCRIPT_BYTECODE *Script::loadBytecode(byte *bytecode_p, size_t bytecode_len) {
|
|
PROC_TBLENTRY *bc_ep_tbl = NULL;
|
|
SCRIPT_BYTECODE *bc_new_data = NULL;
|
|
|
|
unsigned long n_entrypoints; // Number of entrypoints
|
|
size_t ep_tbl_offset; // Offset of bytecode entrypoint table
|
|
unsigned long i;
|
|
|
|
debug(0, "Loading script bytecode...");
|
|
|
|
MemoryReadStream scriptS(bytecode_p, bytecode_len);
|
|
|
|
// The first two uint32 values are the number of entrypoints, and the
|
|
// offset to the entrypoint table, respectively.
|
|
n_entrypoints = scriptS.readUint32LE();
|
|
ep_tbl_offset = scriptS.readUint32LE();
|
|
|
|
// Check that the entrypoint table offset is valid.
|
|
if ((bytecode_len - ep_tbl_offset) < (n_entrypoints * SCRIPT_TBLENTRY_LEN)) {
|
|
warning("Invalid table offset");
|
|
return NULL;
|
|
}
|
|
|
|
if (n_entrypoints > SCRIPT_MAX) {
|
|
warning("Script limit exceeded");
|
|
return NULL;
|
|
}
|
|
|
|
// Allocate a new bytecode resource information structure and table of
|
|
// entrypoints
|
|
|
|
bc_new_data = (SCRIPT_BYTECODE *)malloc(sizeof *bc_new_data);
|
|
if (bc_new_data == NULL) {
|
|
warning("Memory allocation failure loading script bytecode");
|
|
return NULL;
|
|
}
|
|
|
|
bc_ep_tbl = (PROC_TBLENTRY *)malloc(n_entrypoints * sizeof *bc_ep_tbl);
|
|
if (bc_ep_tbl == NULL) {
|
|
warning("Memory allocation failure creating script entrypoint table");
|
|
free(bc_new_data);
|
|
return NULL;
|
|
}
|
|
|
|
// Read in the entrypoint table
|
|
|
|
while (scriptS.pos() < ep_tbl_offset)
|
|
scriptS.readByte();
|
|
|
|
for (i = 0; i < n_entrypoints; i++) {
|
|
// First uint16 is the offset of the entrypoint name from the start
|
|
// of the bytecode resource, second uint16 is the offset of the
|
|
// bytecode itself for said entrypoint
|
|
bc_ep_tbl[i].name_offset = scriptS.readUint16LE();
|
|
bc_ep_tbl[i].offset = scriptS.readUint16LE();
|
|
|
|
// Perform a simple range check on offset values
|
|
if ((bc_ep_tbl[i].name_offset > bytecode_len) || (bc_ep_tbl[i].offset > bytecode_len)) {
|
|
warning("Invalid offset encountered in script entrypoint table");
|
|
free(bc_new_data);
|
|
free(bc_ep_tbl);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
bc_new_data->bytecode_p = (byte *) bytecode_p;
|
|
bc_new_data->bytecode_len = bytecode_len;
|
|
|
|
bc_new_data->n_entrypoints = n_entrypoints;
|
|
bc_new_data->entrypoints = bc_ep_tbl;
|
|
bc_new_data->ep_tbl_offset = ep_tbl_offset;
|
|
|
|
return bc_new_data;
|
|
}
|
|
|
|
// Reads a logical dialogue list from a dialogue list resource in memory.
|
|
// Returns NULL on failure.
|
|
DIALOGUE_LIST *Script::loadDialogue(const byte *dialogue_p, size_t dialogue_len) {
|
|
DIALOGUE_LIST *dialogue_list;
|
|
uint16 n_dialogue;
|
|
uint16 i;
|
|
size_t offset;
|
|
|
|
debug(0, "Loading dialogue list...");
|
|
|
|
// Allocate dialogue list structure
|
|
dialogue_list = (DIALOGUE_LIST *)malloc(sizeof *dialogue_list);
|
|
if (dialogue_list == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
MemoryReadStream scriptS(dialogue_p, dialogue_len);
|
|
|
|
// First uint16 is the offset of the first string
|
|
offset = scriptS.readUint16LE();
|
|
if (offset > dialogue_len) {
|
|
warning("Error, invalid string offset");
|
|
return NULL;
|
|
}
|
|
|
|
// Calculate table length
|
|
n_dialogue = offset / 2;
|
|
dialogue_list->n_dialogue = n_dialogue;
|
|
|
|
// Allocate table of string pointers
|
|
dialogue_list->str = (const char **)malloc(n_dialogue * sizeof(const char *));
|
|
if (dialogue_list->str == NULL) {
|
|
free(dialogue_list);
|
|
return NULL;
|
|
}
|
|
|
|
// Allocate table of string offsets
|
|
dialogue_list->str_off = (size_t *)malloc(n_dialogue * sizeof(size_t));
|
|
if (dialogue_list->str_off == NULL) {
|
|
free(dialogue_list->str);
|
|
free(dialogue_list);
|
|
return NULL;
|
|
}
|
|
|
|
// Read in tables from dialogue list resource
|
|
scriptS.seek(0);
|
|
for (i = 0; i < n_dialogue; i++) {
|
|
offset = scriptS.readUint16LE();
|
|
if (offset > dialogue_len) {
|
|
warning("Error, invalid string offset");
|
|
free(dialogue_list->str);
|
|
free(dialogue_list->str_off);
|
|
free(dialogue_list);
|
|
return NULL;
|
|
}
|
|
dialogue_list->str[i] = (const char *)dialogue_p + offset;
|
|
dialogue_list->str_off[i] = offset;
|
|
}
|
|
|
|
return dialogue_list;
|
|
}
|
|
|
|
// Reads a logical voice LUT from a voice LUT resource in memory.
|
|
// Returns NULL on failure.
|
|
VOICE_LUT *Script::loadVoiceLUT(const byte *voicelut_p, size_t voicelut_len, SCRIPTDATA *script) {
|
|
VOICE_LUT *voice_lut;
|
|
|
|
uint16 n_voices;
|
|
uint16 i;
|
|
|
|
voice_lut = (VOICE_LUT *)malloc(sizeof *voice_lut);
|
|
if (voice_lut == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
n_voices = voicelut_len / 2;
|
|
if (n_voices != script->diag->n_dialogue) {
|
|
warning("Error: Voice LUT entries do not match dialogue entries");
|
|
return NULL;
|
|
}
|
|
|
|
voice_lut->voices = (int *)malloc(n_voices * sizeof *voice_lut->voices);
|
|
if (voice_lut->voices == NULL) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MemoryReadStream scriptS(voicelut_p, voicelut_len);
|
|
|
|
for (i = 0; i < n_voices; i++) {
|
|
voice_lut->voices[i] = scriptS.readUint16LE();
|
|
}
|
|
|
|
return voice_lut;
|
|
}
|
|
|
|
void Script::scriptInfo() {
|
|
uint32 n_entrypoints;
|
|
uint32 i;
|
|
char *name_ptr;
|
|
|
|
if (currentScript() == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!currentScript()->loaded) {
|
|
return;
|
|
}
|
|
|
|
n_entrypoints = currentScript()->bytecode->n_entrypoints;
|
|
|
|
_vm->_console->DebugPrintf("Current script contains %d entrypoints:\n", n_entrypoints);
|
|
|
|
for (i = 0; i < n_entrypoints; i++) {
|
|
name_ptr = (char *)currentScript()->bytecode->bytecode_p +
|
|
currentScript()->bytecode->entrypoints[i].name_offset;
|
|
_vm->_console->DebugPrintf("%lu: %s\n", i, name_ptr);
|
|
}
|
|
}
|
|
|
|
void Script::scriptExec(int argc, const char **argv) {
|
|
uint16 ep_num;
|
|
|
|
ep_num = atoi(argv[1]);
|
|
|
|
if (_dbg_thread == NULL) {
|
|
_vm->_console->DebugPrintf("Creating debug thread...\n");
|
|
_dbg_thread = SThreadCreate();
|
|
if (_dbg_thread == NULL) {
|
|
_vm->_console->DebugPrintf("Thread creation failed.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ep_num >= currentScript()->bytecode->n_entrypoints) {
|
|
_vm->_console->DebugPrintf("Invalid entrypoint.\n");
|
|
return;
|
|
}
|
|
|
|
SThreadExecute(_dbg_thread, ep_num);
|
|
}
|
|
|
|
void Script::CF_script_togglestep() {
|
|
_dbg_singlestep = !_dbg_singlestep;
|
|
}
|
|
|
|
} // End of namespace Saga
|