diff options
Diffstat (limited to 'saga/script.cpp')
-rw-r--r-- | saga/script.cpp | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/saga/script.cpp b/saga/script.cpp new file mode 100644 index 0000000000..4c84a9e069 --- /dev/null +++ b/saga/script.cpp @@ -0,0 +1,684 @@ +/* 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$ + * + */ +/* + + Description: + + Scripting module: Script resource handling functions + + Notes: +*/ + +#include "reinherit.h" + +#include "yslib.h" + +/* + * Uses the following modules: +\*--------------------------------------------------------------------------*/ +#include "rscfile_mod.h" +#include "game_mod.h" +#include "text_mod.h" +#include "console_mod.h" +#include "cvar_mod.h" + +/* + * Begin module component +\*--------------------------------------------------------------------------*/ +#include "script_mod.h" +#include "script.h" +#include "sstack.h" +#include "sthread.h" + +namespace Saga { + +R_SCRIPT_MODULE ScriptModule; + +int SCRIPT_Register(void) +{ + + CVAR_RegisterFunc(CF_script_info, + "script_info", NULL, R_CVAR_NONE, 0, 0); + + CVAR_RegisterFunc(CF_script_exec, + "script_exec", "<Script number>", R_CVAR_NONE, 1, 1); + + CVAR_RegisterFunc(CF_script_togglestep, + "script_togglestep", NULL, R_CVAR_NONE, 0, 0); + + return R_SUCCESS; +} + +int SCRIPT_Init(void) +/*--------------------------------------------------------------------------*\ + * Initializes the scripting module. + * Loads script resource look-up table, initializes script data system +\*--------------------------------------------------------------------------*/ +{ + R_RSCFILE_CONTEXT *s_lut_ctxt; + + uchar *rsc_ptr; + size_t rsc_len; + + const uchar *read_ptr; + const uchar *read_ptr2; + + int result; + int i; + + R_printf(R_STDOUT, "Initializing scripting subsystem.\n"); + + /* Load script resource file context + * \*---------------------------------------------------------------------- */ + result = GAME_GetFileContext(&ScriptModule.script_ctxt, + R_GAME_SCRIPTFILE, 0); + if (result != R_SUCCESS) { + + R_printf(R_STDERR, "Couldn't get script file context.\n"); + + return R_FAILURE; + } + + /* Load script LUT resource + * \*---------------------------------------------------------------------- */ + result = GAME_GetFileContext(&s_lut_ctxt, R_GAME_RESOURCEFILE, 0); + if (result != R_SUCCESS) { + + R_printf(R_STDERR, "Couldn't get resource file context.\n"); + + return R_FAILURE; + } + + result = RSC_LoadResource(s_lut_ctxt, + ITE_SCRIPT_LUT, &rsc_ptr, &rsc_len); + if (result != R_SUCCESS) { + + R_printf(R_STDERR, + "Error: Couldn't load script resource look-up table.\n"); + + return R_FAILURE; + } + + /* Create logical script LUT from resource + * \*---------------------------------------------------------------------- */ + if (rsc_len % R_S_LUT_ENTRYLEN_ITECD == 0) { + + ScriptModule.script_lut_entrylen = R_S_LUT_ENTRYLEN_ITECD; + } else if (rsc_len % R_S_LUT_ENTRYLEN_ITEDISK == 0) { + + ScriptModule.script_lut_entrylen = R_S_LUT_ENTRYLEN_ITEDISK; + } else { + R_printf(R_STDERR, + "Error: Invalid script lookup table length.\n"); + return R_FAILURE; + } + + /* Calculate number of entries */ + ScriptModule.script_lut_max = + rsc_len / ScriptModule.script_lut_entrylen; + + /* Allocate space for logical LUT */ + ScriptModule.script_lut = (R_SCRIPT_LUT_ENTRY *)malloc(ScriptModule.script_lut_max * + sizeof(R_SCRIPT_LUT_ENTRY)); + if (ScriptModule.script_lut == NULL) { + R_printf(R_STDERR, + "Error: Couldn't allocate memory for script resource " + "look-up table.\n"); + return R_MEM; + } + + /* Convert LUT resource to logical LUT */ + read_ptr = rsc_ptr; + for (i = 0; i < ScriptModule.script_lut_max; i++) { + + read_ptr2 = read_ptr; + + ScriptModule.script_lut[i].script_rn = + ys_read_u16_le(read_ptr2, &read_ptr2); + + ScriptModule.script_lut[i].diag_list_rn = + ys_read_u16_le(read_ptr2, &read_ptr2); + + ScriptModule.script_lut[i].voice_lut_rn = + ys_read_u16_le(read_ptr2, &read_ptr2); + + /* Skip the unused portion of the structure */ + read_ptr += ScriptModule.script_lut_entrylen; + } + + RSC_FreeResource(rsc_ptr); + + /* Any voice lookup table resources present? */ + for (i = 0; i < ScriptModule.script_lut_max; i++) { + + if (ScriptModule.script_lut[i].voice_lut_rn) { + + ScriptModule.voice_lut_present = 1; + break; + } + } + + /* Initialize script submodules + * \*---------------------------------------------------------------------- */ + ScriptModule.thread_list = ys_dll_create(); + + if (SDATA_Init() != R_SUCCESS) { + free(ScriptModule.script_lut); + return R_FAILURE; + } + + ScriptModule.initialized = 1; + return R_SUCCESS; +} + +int SCRIPT_Shutdown(void) +/*--------------------------------------------------------------------------*\ + * Shut down script module gracefully; free all allocated module resources +\*--------------------------------------------------------------------------*/ +{ + YS_DL_NODE *thread_node; + R_SCRIPT_THREAD *thread; + + if (!ScriptModule.initialized) { + + return R_FAILURE; + } + + R_printf(R_STDOUT, "Shutting down scripting subsystem.\n"); + + /* Free script lookup table */ + free(ScriptModule.script_lut); + + /* Stop all threads and destroy them */ + + for (thread_node = ys_dll_head(ScriptModule.thread_list); + thread_node != NULL; thread_node = ys_dll_next(thread_node)) { + + thread = (R_SCRIPT_THREAD *)ys_dll_get_data(thread_node); + + STHREAD_Destroy(thread); + } + + ScriptModule.initialized = 0; + + return R_SUCCESS; +} + +int SCRIPT_Load(int script_num) +/*--------------------------------------------------------------------------*\ + * Loads a script; including script bytecode and dialogue list +\*--------------------------------------------------------------------------*/ +{ + + R_SCRIPTDATA *script_data; + + uchar *bytecode_p; + size_t bytecode_len; + ulong scriptl_rn; + + uchar *diagl_p; + size_t diagl_len; + ulong diagl_rn; + + uchar *voicelut_p; + size_t voicelut_len; + ulong voicelut_rn; + + int result; + + if (GAME_GetGameType() == R_GAMETYPE_IHNM) { + return R_SUCCESS; + } + + /* Validate script number */ + if ((script_num < 0) || (script_num > ScriptModule.script_lut_max)) { + R_printf(R_STDERR, "SCRIPT_Load(): Invalid script number!\n"); + return R_FAILURE; + } + + /* Release old script data if present */ + SCRIPT_Free(); + + /* Initialize script data structure + * \*---------------------------------------------------------------------- */ + R_printf(R_STDOUT, "Loading script data for script #%d.\n", + script_num); + + script_data = (R_SCRIPTDATA *)malloc(sizeof *script_data); + if (script_data == NULL) { + R_printf(R_STDERR, "Memory allocation failed.\n"); + return R_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 = ScriptModule.script_lut[script_num].script_rn; + + result = RSC_LoadResource(ScriptModule.script_ctxt, + scriptl_rn, &bytecode_p, &bytecode_len); + if (result != R_SUCCESS) { + R_printf(R_STDERR, + "Error loading script bytecode resource.\n"); + free(script_data); + return R_FAILURE; + } + + script_data->bytecode = SCRIPT_LoadBytecode(bytecode_p, bytecode_len); + + if (script_data->bytecode == NULL) { + R_printf(R_STDERR, + "Error interpreting script bytecode resource.\n"); + free(script_data); + RSC_FreeResource(bytecode_p); + return R_FAILURE; + } + + /* Load script dialogue list + * \*---------------------------------------------------------------------- */ + diagl_rn = ScriptModule.script_lut[script_num].diag_list_rn; + + /* Load dialogue list resource */ + result = RSC_LoadResource(ScriptModule.script_ctxt, + diagl_rn, &diagl_p, &diagl_len); + if (result != R_SUCCESS) { + R_printf(R_STDERR, "Error loading dialogue list resource.\n"); + free(script_data); + RSC_FreeResource(bytecode_p); + return R_FAILURE; + } + + /* Convert dialogue list resource to logical dialogue list */ + script_data->diag = SCRIPT_LoadDialogue(diagl_p, diagl_len); + if (script_data->diag == NULL) { + R_printf(R_STDERR, + "Error interpreting dialogue list resource.\n"); + free(script_data); + RSC_FreeResource(bytecode_p); + RSC_FreeResource(diagl_p); + return R_FAILURE; + } + + /* Load voice resource lookup table + * \*---------------------------------------------------------------------- */ + if (ScriptModule.voice_lut_present) { + + voicelut_rn = ScriptModule.script_lut[script_num].voice_lut_rn; + + /* Load voice LUT resource */ + result = RSC_LoadResource(ScriptModule.script_ctxt, + voicelut_rn, &voicelut_p, &voicelut_len); + if (result != R_SUCCESS) { + + R_printf(R_STDERR, + "Error loading voice LUT resource.\n"); + + free(script_data); + RSC_FreeResource(bytecode_p); + RSC_FreeResource(diagl_p); + + return R_FAILURE; + } + + /* Convert voice LUT resource to logical voice LUT */ + script_data->voice = SCRIPT_LoadVoiceLUT(voicelut_p, + voicelut_len, script_data); + if (script_data->voice == NULL) { + R_printf(R_STDERR, + "Error interpreting voice LUT resource.\n"); + + free(script_data); + RSC_FreeResource(bytecode_p); + RSC_FreeResource(diagl_p); + RSC_FreeResource(voicelut_p); + + return R_FAILURE; + } + + } + + /* Finish initialization + * \*---------------------------------------------------------------------- */ + script_data->loaded = 1; + ScriptModule.current_script = script_data; + + return R_SUCCESS; +} + +int SCRIPT_Free(void) +/*--------------------------------------------------------------------------*\ + * Frees all resources associated with current script. +\*--------------------------------------------------------------------------*/ +{ + + if (ScriptModule.current_script == NULL) { + return R_FAILURE; + } + + if (!ScriptModule.current_script->loaded) { + return R_FAILURE; + } + + R_printf(R_STDOUT, "Releasing script data.\n"); + + /* Finish initialization + * \*---------------------------------------------------------------------- */ + if (ScriptModule.current_script->diag != NULL) { + free(ScriptModule.current_script->diag->str); + free(ScriptModule.current_script->diag->str_off); + } + free(ScriptModule.current_script->diag); + + if (ScriptModule.current_script->bytecode != NULL) { + free(ScriptModule.current_script->bytecode->entrypoints); + RSC_FreeResource(ScriptModule.current_script->bytecode-> + bytecode_p); + } + + free(ScriptModule.current_script->bytecode); + + if (ScriptModule.voice_lut_present) { + free(ScriptModule.current_script->voice->voices); + free(ScriptModule.current_script->voice); + } + + free(ScriptModule.current_script); + + ScriptModule.current_script = NULL; + + return R_SUCCESS; +} + +R_SCRIPT_BYTECODE *SCRIPT_LoadBytecode(const uchar * bytecode_p, + size_t bytecode_len) +/*--------------------------------------------------------------------------*\ + * Reads the entrypoint table from a script bytecode resource in memory. + * Returns NULL on failure. +\*--------------------------------------------------------------------------*/ +{ + + const uchar *read_p = bytecode_p; + R_PROC_TBLENTRY *bc_ep_tbl = NULL; + R_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; + + R_printf(R_STDOUT, "Loading script bytecode...\n"); + + /* The first two uint32 values are the number of entrypoints, and the + * offset to the entrypoint table, respectively. */ + + n_entrypoints = ys_read_u32_le(read_p, &read_p); + ep_tbl_offset = ys_read_u32_le(read_p, &read_p); + + /* Check that the entrypoint table offset is valid. */ + if ((bytecode_len - ep_tbl_offset) < + (n_entrypoints * R_SCRIPT_TBLENTRY_LEN)) { + + R_printf(R_STDERR, "Invalid table offset.\n"); + return NULL; + } + + if (n_entrypoints > R_SCRIPT_MAX) { + R_printf(R_STDERR, "Script limit exceeded.\n"); + return NULL; + } + + /* Allocate a new bytecode resource information structure and table of + * entrypoints */ + + bc_new_data = (R_SCRIPT_BYTECODE *)malloc(sizeof *bc_new_data); + if (bc_new_data == NULL) { + R_printf(R_STDERR, + "Memory allocation failure loading script bytecode.\n"); + return NULL; + } + + bc_ep_tbl = (R_PROC_TBLENTRY *)malloc(n_entrypoints * sizeof *bc_ep_tbl); + if (bc_ep_tbl == NULL) { + R_printf(R_STDERR, + "Memory allocation failure creating script entrypoint table.\n"); + free(bc_new_data); + return NULL; + } + + /* Read in the entrypoint table */ + + read_p = bytecode_p + ep_tbl_offset; + + 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 = ys_read_u16_le(read_p, &read_p); + bc_ep_tbl[i].offset = ys_read_u16_le(read_p, &read_p); + + /* Perform a simple range check on offset values */ + if ((bc_ep_tbl[i].name_offset > bytecode_len) || + (bc_ep_tbl[i].offset > bytecode_len)) { + + R_printf(R_STDERR, + "Invalid offset encountered in script entrypoint table.\n"); + free(bc_new_data); + free(bc_ep_tbl); + return NULL; + } + } + + bc_new_data->bytecode_p = (uchar *) 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; +} + +R_DIALOGUE_LIST *SCRIPT_LoadDialogue(const uchar * dialogue_p, + size_t dialogue_len) +/*--------------------------------------------------------------------------*\ + * Reads a logical dialogue list from a dialogue list resource in memory. + * Returns NULL on failure. +\*--------------------------------------------------------------------------*/ +{ + const uchar *read_p = dialogue_p; + + R_DIALOGUE_LIST *dialogue_list; + uint n_dialogue; + + uint i; + size_t offset; + + R_printf(R_STDOUT, "Loading dialogue list...\n"); + + /* Allocate dialogue list structure */ + dialogue_list = (R_DIALOGUE_LIST *)malloc(sizeof *dialogue_list); + if (dialogue_list == NULL) { + return NULL; + } + + /* First uint16 is the offset of the first string */ + offset = ys_read_u16_le(read_p, &read_p); + if (offset > dialogue_len) { + R_printf(R_STDERR, "Error, invalid string offset.\n"); + return NULL; + } + + /* Calculate table length */ + n_dialogue = offset / 2; + dialogue_list->n_dialogue = n_dialogue; + + /* Allocate table of string pointers */ + dialogue_list->str = (char **)malloc(n_dialogue * sizeof(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 */ + read_p = dialogue_p; + for (i = 0; i < n_dialogue; i++) { + offset = ys_read_u16_le(read_p, &read_p); + + if (offset > dialogue_len) { + R_printf(R_STDERR, "Error, invalid string offset.\n"); + free(dialogue_list->str); + free(dialogue_list->str_off); + free(dialogue_list); + return NULL; + } + dialogue_list->str[i] = (char *)dialogue_p + offset; + dialogue_list->str_off[i] = offset; + } + + return dialogue_list; +} + +R_VOICE_LUT *SCRIPT_LoadVoiceLUT(const uchar * voicelut_p, + size_t voicelut_len, R_SCRIPTDATA * script) +/*--------------------------------------------------------------------------*\ + * Reads a logical voice LUT from a voice LUT resource in memory. + * Returns NULL on failure. +\*--------------------------------------------------------------------------*/ +{ + const uchar *read_p = voicelut_p; + + R_VOICE_LUT *voice_lut; + + uint n_voices; + uint i; + + voice_lut = (R_VOICE_LUT *)malloc(sizeof *voice_lut); + if (voice_lut == NULL) { + return NULL; + } + + n_voices = voicelut_len / 2; + if (n_voices != script->diag->n_dialogue) { + R_printf(R_STDERR, "Error: Voice LUT entries do not match " + "dialogue entries.\n"); + return NULL; + } + + voice_lut->voices = (int *)malloc(n_voices * sizeof *voice_lut->voices); + if (voice_lut->voices == NULL) { + + return NULL; + } + + for (i = 0; i < n_voices; i++) { + + voice_lut->voices[i] = ys_read_u16_le(read_p, &read_p); + } + + return voice_lut; +} + +void CF_script_info(int argc, char *argv[]) +{ + + ulong n_entrypoints; + ulong i; + char *name_ptr; + + if (ScriptModule.current_script == NULL) { + return; + } + + if (!ScriptModule.current_script->loaded) { + return; + } + + n_entrypoints = ScriptModule.current_script->bytecode->n_entrypoints; + + CON_Print("Current script contains %d entrypoints:", n_entrypoints); + + for (i = 0; i < n_entrypoints; i++) { + name_ptr = (char *) + ScriptModule.current_script->bytecode->bytecode_p + + ScriptModule.current_script->bytecode->entrypoints[i]. + name_offset; + + CON_Print("%lu: %s", i, name_ptr); + } + + return; +} + +void CF_script_exec(int argc, char *argv[]) +{ + uint ep_num; + + if (argc < 1) { + return; + } + + ep_num = atoi(argv[0]); + + if (ScriptModule.dbg_thread == NULL) { + + CON_Print("Creating debug thread..."); + ScriptModule.dbg_thread = STHREAD_Create(); + + if (ScriptModule.dbg_thread == NULL) { + CON_Print("Thread creation failed."); + return; + } + } + + if (ep_num >= ScriptModule.current_script->bytecode->n_entrypoints) { + CON_Print("Invalid entrypoint."); + return; + } + + STHREAD_Execute(ScriptModule.dbg_thread, ep_num); + + return; +} + +void CF_script_togglestep(int argc, char *argv[]) +{ + ScriptModule.dbg_singlestep = !ScriptModule.dbg_singlestep; + + return; +} + +} // End of namespace Saga |