/* 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 thread management component Notes: */ #include "reinherit.h" #include "yslib.h" #include /* * Uses the following modules: \*--------------------------------------------------------------------------*/ #include "actor_mod.h" #include "console_mod.h" #include "text_mod.h" /* * Begin module component \*--------------------------------------------------------------------------*/ #include "script.h" #include "script_mod.h" #include "sdata.h" #include "sstack.h" #include "sthread.h" #include "sfuncs.h" namespace Saga { R_SCRIPT_THREAD *STHREAD_Create(void) { YS_DL_NODE *new_node; R_SCRIPT_THREAD *new_thread; int result; if (!ScriptModule.initialized) { return NULL; } new_thread = (R_SCRIPT_THREAD *)calloc(1, sizeof *new_thread); if (new_thread == NULL) { return NULL; } result = SSTACK_Create(&(new_thread->stack), R_DEF_THREAD_STACKSIZE, STACK_GROW); if (result != STACK_SUCCESS) { return NULL; } new_node = ys_dll_add_head(ScriptModule.thread_list, new_thread, sizeof *new_thread); free(new_thread); return (R_SCRIPT_THREAD *)ys_dll_get_data(new_node); } int STHREAD_Destroy(R_SCRIPT_THREAD * thread) { if (thread == NULL) { return R_FAILURE; } SSTACK_Destroy(thread->stack); return R_SUCCESS; } int STHREAD_ExecThreads(int msec) { YS_DL_NODE *walk_p; R_SCRIPT_THREAD *thread; if (!ScriptModule.initialized) { return R_FAILURE; } for (walk_p = ys_dll_head(ScriptModule.thread_list); walk_p != NULL; walk_p = ys_dll_next(walk_p)) { thread = (R_SCRIPT_THREAD *)ys_dll_get_data(walk_p); if (thread->executing) { STHREAD_Run(thread, STHREAD_DEF_INSTR_COUNT, msec); } } return R_SUCCESS; } int STHREAD_SetEntrypoint(R_SCRIPT_THREAD * thread, int ep_num) { R_SCRIPT_BYTECODE *bytecode; int max_entrypoint; assert(ScriptModule.initialized); bytecode = ScriptModule.current_script->bytecode; max_entrypoint = bytecode->n_entrypoints; if ((ep_num < 0) || (ep_num >= max_entrypoint)) { return R_FAILURE; } thread->ep_num = ep_num; thread->ep_offset = bytecode->entrypoints[ep_num].offset; return R_SUCCESS; } int STHREAD_Execute(R_SCRIPT_THREAD * thread, int ep_num) { assert(ScriptModule.initialized); if ((ScriptModule.current_script == NULL) || (!ScriptModule.current_script->loaded)) { return R_FAILURE; } STHREAD_SetEntrypoint(thread, ep_num); thread->i_offset = thread->ep_offset; thread->executing = 1; return R_SUCCESS; } unsigned char *GetReadPtr(R_SCRIPT_THREAD * thread) { return ScriptModule.current_script->bytecode->bytecode_p + thread->i_offset; } unsigned long GetReadOffset(const uchar * read_p) { return (unsigned long)(read_p - (unsigned char *) ScriptModule.current_script->bytecode->bytecode_p); } int STHREAD_HoldSem(R_SEMAPHORE * sem) { if (sem == NULL) { return R_FAILURE; } sem->hold_count++; return R_SUCCESS; } int STHREAD_ReleaseSem(R_SEMAPHORE * sem) { if (sem == NULL) { return R_FAILURE; } sem->hold_count--; if (sem->hold_count < 0) { sem->hold_count = 0; } return R_SUCCESS; } int STHREAD_DebugStep(void) { if (ScriptModule.dbg_singlestep) { ScriptModule.dbg_dostep = 1; } return R_SUCCESS; } int STHREAD_Run(R_SCRIPT_THREAD * thread, int instr_limit, int msec) { int instr_count; const uchar *read_p; ulong saved_offset; SDataWord_T param1; SDataWord_T param2; long iparam1; long iparam2; long iresult; SDataWord_T data; int debug_print = 0; int n_buf; int bitstate; int result; int in_char; int i; int unhandled = 0; /* Handle debug single-stepping */ if ((thread == ScriptModule.dbg_thread) && ScriptModule.dbg_singlestep) { if (ScriptModule.dbg_dostep) { debug_print = 1; thread->sleep_time = 0; instr_limit = 1; ScriptModule.dbg_dostep = 0; } else { return R_SUCCESS; } } for (instr_count = 0; instr_count < instr_limit; instr_count++) { if ((!thread->executing) || (thread->sem.hold_count)) { break; } thread->sleep_time -= msec; if (thread->sleep_time < 0) { thread->sleep_time = 0; } if (thread->sleep_time) { break; } saved_offset = thread->i_offset; #if 0 R_printf(R_STDOUT, "Executing thread offset: %lu", thread->i_offset); #endif read_p = GetReadPtr(thread); in_char = ys_read_u8(read_p, &read_p); switch (in_char) { /* Align (ALGN) */ case 0x01: break; /*-------------------------------------------------------------*\ * STACK INSTRUCTIONS \*-------------------------------------------------------------*/ /* Push nothing (PSHN) */ case 0x02: SSTACK_PushNull(thread->stack); break; /* Pop nothing (POPN) */ case 0x03: SSTACK_Pop(thread->stack, NULL); break; /* Push false (PSHF) */ case 0x04: SSTACK_Push(thread->stack, 0); break; /* Push true (PSHT) */ case 0x05: SSTACK_Push(thread->stack, 1); break; /* Push word (PUSH) */ case 0x06: param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); SSTACK_Push(thread->stack, param1); break; /* Push word (PSHD) (dialogue string index) */ case 0x08: param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); SSTACK_Push(thread->stack, param1); break; /*-------------------------------------------------------------*\ * DATA INSTRUCTIONS \*-------------------------------------------------------------*/ /* Test flag (TSTF) */ case 0x0B: n_buf = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); SDATA_GetBit(n_buf, param1, &bitstate); SSTACK_Push(thread->stack, bitstate); break; /* Get word (GETW) */ case 0x0C: n_buf = *read_p++; param1 = ys_read_u16_le(read_p, &read_p); SDATA_GetWord(n_buf, param1, &data); SSTACK_Push(thread->stack, data); break; /* Modify flag (MODF) */ case 0x0F: n_buf = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); bitstate = SDATA_ReadWordU(param1); SSTACK_Top(thread->stack, &data); if (bitstate) { SDATA_SetBit(n_buf, data, 1); } else { SDATA_SetBit(n_buf, data, 0); } break; /* Put word (PUTW) */ case 0x10: n_buf = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); SSTACK_Top(thread->stack, &data); SDATA_PutWord(n_buf, param1, data); break; /* Modify flag and pop (MDFP) */ case 0x13: n_buf = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); SSTACK_Pop(thread->stack, ¶m1); bitstate = SDATA_ReadWordU(param1); if (bitstate) { SDATA_SetBit(n_buf, param1, 1); } else { SDATA_SetBit(n_buf, param1, 0); } break; /* Put word and pop (PTWP) */ case 0x14: n_buf = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); SSTACK_Top(thread->stack, &data); SDATA_PutWord(n_buf, param1, data); break; /*-------------------------------------------------------------*\ * CONTROL INSTRUCTIONS \*-------------------------------------------------------------*/ /* (GOSB): Call subscript ? */ case 0x17: { int temp; int temp2; temp = *read_p++; temp2 = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); data = GetReadOffset(read_p); /*SSTACK_Push( thread->stack, (SDataWord_T)temp ); */ SSTACK_Push(thread->stack, data); thread->i_offset = (unsigned long)param1; } break; /* (CALL): Call function */ case 0x19: case 0x18: { int n_args; uint func_num; int FIXME_SHADOWED_result; SFunc_T sfunc; n_args = ys_read_u8(read_p, &read_p); func_num = ys_read_u16_le(read_p, &read_p); if (func_num >= R_SFUNC_NUM) { CON_Print(S_ERROR_PREFIX "Invalid script function number: (%X)\n", func_num); thread->executing = 0; break; } sfunc = SFuncList[func_num].sfunc_fp; if (sfunc == NULL) { CON_Print(S_WARN_PREFIX "%X: Undefined script function number: (%X)\n", thread->i_offset, func_num); CON_Print(S_WARN_PREFIX "Removing %d operand(s) from stack.\n", n_args); for (i = 0; i < n_args; i++) { SSTACK_Pop(thread->stack, NULL); } } else { FIXME_SHADOWED_result = sfunc(thread); if (FIXME_SHADOWED_result != R_SUCCESS) { CON_Print(S_WARN_PREFIX "%X: Script function %d failed.\n", thread->i_offset, func_num); } } } break; /* (ENTR) Enter the dragon */ case 0x1A: param1 = ys_read_u16_le(read_p, &read_p); break; /* (?) Unknown */ case 0x1B: unhandled = 1; break; /* (EXIT) End subscript */ case 0x1C: result = SSTACK_Pop(thread->stack, &data); if (result != STACK_SUCCESS) { CON_Print("Script execution complete."); thread->executing = 0; } else { thread->i_offset = data; } break; /*-------------------------------------------------------------*\ * BRANCH INSTRUCTIONS \*-------------------------------------------------------------*/ /* (JMP): Unconditional jump */ case 0x1D: param1 = ys_read_u16_le(read_p, &read_p); thread->i_offset = (unsigned long)param1; break; /* (JNZP): Jump if nonzero + POP */ case 0x1E: param1 = ys_read_u16_le(read_p, &read_p); SSTACK_Pop(thread->stack, &data); if (data) { thread->i_offset = (unsigned long)param1; } break; /* (JZP): Jump if zero + POP */ case 0x1F: param1 = ys_read_u16_le(read_p, &read_p); SSTACK_Pop(thread->stack, &data); if (!data) { thread->i_offset = (unsigned long)param1; } break; /* (JNZ): Jump if nonzero */ case 0x20: param1 = ys_read_u16_le(read_p, &read_p); SSTACK_Top(thread->stack, &data); if (data) { thread->i_offset = (unsigned long)param1; } break; /* (JZ): Jump if zero */ case 0x21: param1 = ys_read_u16_le(read_p, &read_p); SSTACK_Top(thread->stack, &data); if (!data) { thread->i_offset = (unsigned long)param1; } break; /* (JMPR): Relative jump */ case 0x57: /* ignored? */ ys_read_u16_le(read_p, &read_p); ys_read_u16_le(read_p, &read_p); iparam1 = (long)*read_p++; thread->i_offset += iparam1; break; /* (SWCH): Switch */ case 0x22: { int n_switch; unsigned int switch_num; unsigned int switch_jmp; unsigned int default_jmp; int case_found = 0; SSTACK_Pop(thread->stack, &data); n_switch = ys_read_u16_le(read_p, &read_p); for (i = 0; i < n_switch; i++) { switch_num = ys_read_u16_le(read_p, &read_p); switch_jmp = ys_read_u16_le(read_p, &read_p); /* Found the specified case */ if (data == (SDataWord_T) switch_num) { thread->i_offset = switch_jmp; case_found = 1; break; } } /* Jump to default case */ if (!case_found) { default_jmp = ys_read_u16_le(read_p, &read_p); thread->i_offset = default_jmp; } } break; /* (RJMP): Random branch */ case 0x24: { int n_branch; unsigned int branch_wt; unsigned int branch_jmp; int rand_sel = 0; int branch_found = 0; /* Ignored? */ ys_read_u16_le(read_p, &read_p); n_branch = ys_read_u16_le(read_p, &read_p); for (i = 0; i < n_branch; i++) { branch_wt = ys_read_u16_le(read_p, &read_p); branch_jmp = ys_read_u16_le(read_p, &read_p); if (rand_sel == i) { thread->i_offset = branch_jmp; branch_found = 1; break; } } if (!branch_found) { CON_Print(S_ERROR_PREFIX "%X: Random jump target out of " "bounds.", thread->i_offset); } } break; /*-------------------------------------------------------------*\ * MISC. INSTRUCTIONS \*-------------------------------------------------------------*/ /* (NEG) Negate stack by 2's complement */ case 0x25: SSTACK_Pop(thread->stack, &data); data = ~data; data++; SSTACK_Push(thread->stack, data); break; /* (TSTZ) Test for zero */ case 0x26: SSTACK_Pop(thread->stack, &data); data = data ? 0 : 1; SSTACK_Push(thread->stack, data); break; /* (NOT) Binary not */ case 0x27: SSTACK_Pop(thread->stack, &data); data = ~data; SSTACK_Push(thread->stack, data); break; /* (?) */ case 0x28: unhandled = 1; printf("??? "); read_p++; ys_read_u16_le(read_p, &read_p); break; /* (?) */ case 0x29: unhandled = 1; printf("??? "); read_p++; ys_read_u16_le(read_p, &read_p); break; /* (?) */ case 0x2A: unhandled = 1; printf("??? "); read_p++; ys_read_u16_le(read_p, &read_p); break; /* (?) */ case 0x2B: unhandled = 1; printf("??? "); read_p++; ys_read_u16_le(read_p, &read_p); break; /*-------------------------------------------------------------*\ * ARITHMETIC INSTRUCTIONS \*-------------------------------------------------------------*/ /* (ADD): Addition */ case 0x2C: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; iresult = iparam1 + iparam2; SSTACK_Push(thread->stack, (SDataWord_T) iresult); break; /* (SUB): Subtraction */ case 0x2D: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; iresult = iparam1 - iparam2; SSTACK_Push(thread->stack, (SDataWord_T) iresult); break; /* (MULT): Integer multiplication */ case 0x2E: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; iresult = iparam1 * iparam2; SSTACK_Push(thread->stack, (SDataWord_T) iresult); break; /* (DIB): Integer division */ case 0x2F: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; iresult = iparam1 / iparam2; SSTACK_Push(thread->stack, (SDataWord_T) iresult); break; /* (MOD) Modulus */ case 0x30: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; iresult = iparam1 % iparam2; SSTACK_Push(thread->stack, (SDataWord_T) iresult); break; /* (EQU) Test equality */ case 0x33: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; data = (iparam1 == iparam2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (NEQU) Test inequality */ case 0x34: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; data = (iparam1 != iparam2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (GRT) Test Greater-than */ case 0x35: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; data = (iparam1 > iparam2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (LST) Test Less-than */ case 0x36: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; data = (iparam1 < iparam2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (GRTE) Test Greater-than or Equal to */ case 0x37: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; data = (iparam1 >= iparam2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (LSTE) Test Less-than or Equal to */ case 0x38: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; iparam1 = (long)param1; data = (iparam1 <= iparam2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (SHR): Arithmetic binary shift right */ case 0x3F: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); iparam2 = (long)param2; /* Preserve most significant bit */ data = (0x01 << ((sizeof param1 * CHAR_BIT) - 1)) & param1; for (i = 0; i < (int)iparam2; i++) { param1 >>= 1; param1 |= data; } SSTACK_Push(thread->stack, param1); break; /*-------------------------------------------------------------*\ * BITWISE INSTRUCTIONS \*-------------------------------------------------------------*/ /* (SHL) Binary shift left */ case 0x40: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); param1 <<= param2; SSTACK_Push(thread->stack, param1); break; /* (AND) Binary AND */ case 0x41: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); param1 &= param2; SSTACK_Push(thread->stack, param1); break; /* (OR) Binary OR */ case 0x42: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); param1 |= param2; SSTACK_Push(thread->stack, param1); break; /* (XOR) Binary XOR */ case 0x43: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); param1 ^= param2; SSTACK_Push(thread->stack, param1); break; /*-------------------------------------------------------------*\ * BOOLEAN LOGIC INSTRUCTIONS \*-------------------------------------------------------------*/ /* (LAND): Logical AND */ case 0x44: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); data = (param1 && param2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (LOR): Logical OR */ case 0x45: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); data = (param1 || param2) ? 1 : 0; SSTACK_Push(thread->stack, data); break; /* (LXOR): Logical XOR */ case 0x46: SSTACK_Pop(thread->stack, ¶m2); SSTACK_Pop(thread->stack, ¶m1); data = ((param1) ? !(param2) : !!(param2)); SSTACK_Push(thread->stack, data); break; /*-------------------------------------------------------------*\ * GAME INSTRUCTIONS \*-------------------------------------------------------------*/ /* (DLGP): Play Character Dialogue */ case 0x53: { int n_voices; int a_index; int voice_rn; n_voices = *read_p++; param1 = (SDataWord_T) ys_read_u16_le(read_p, &read_p); /* ignored ? */ *read_p++; ys_read_u16_le(read_p, &read_p); a_index = ACTOR_GetActorIndex(param1); if (a_index < 0) { CON_Print(S_WARN_PREFIX "%X: DLGP Actor id not found.", thread->i_offset); } for (i = 0; i < n_voices; i++) { SSTACK_Pop(thread->stack, &data); if (a_index < 0) continue; if (!ScriptModule.voice_lut_present) { voice_rn = -1; } else { voice_rn = ScriptModule. current_script->voice-> voices[data]; } ACTOR_Speak(a_index, ScriptModule.current_script->diag-> str[data], voice_rn, &thread->sem); } } break; /* (DLGS): Initialize dialogue interface */ case 0x54: break; /* (DLGX): Run dialogue interface */ case 0x55: break; /* (DLGO): Add a dialogue option to interface */ case 0x56: { int FIXME_SHADOWED_param1; int FIXME_SHADOWED_param2; int FIXME_SHADOWED_param3; printf("DLGO | "); FIXME_SHADOWED_param1 = *read_p++; FIXME_SHADOWED_param2 = *read_p++; printf("%02X %02X ", FIXME_SHADOWED_param1, FIXME_SHADOWED_param2); if (FIXME_SHADOWED_param2 > 0) { FIXME_SHADOWED_param3 = ys_read_u16_le(read_p, &read_p); printf("%04X", FIXME_SHADOWED_param3); } } break; /*-------------------------------------------------------------*\ * End instruction list \*-------------------------------------------------------------*/ default: CON_Print(S_ERROR_PREFIX "%X: Invalid opcode encountered: " "(%X).\n", thread->i_offset, in_char); thread->executing = 0; break; } /* end switch( in_char ) */ /* Set instruction offset only if a previous instruction didn't * branch */ if (saved_offset == thread->i_offset) { thread->i_offset = GetReadOffset(read_p); } if (unhandled) { CON_Print(S_ERROR_PREFIX "%X: Unhandled opcode.\n", thread->i_offset); thread->executing = 0; } if (thread->executing && debug_print) { SDEBUG_PrintInstr(thread); } } return R_SUCCESS; } } // End of namespace Saga