/* 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 thread management component #include "saga.h" #include "reinherit.h" #include "yslib.h" #include "actor_mod.h" #include "console_mod.h" #include "text_mod.h" #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() { 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 byte *read_p) { return (unsigned long)(read_p - (unsigned char *)ScriptModule.current_script->bytecode->bytecode_p); } size_t GetReadLen(R_SCRIPT_THREAD *thread) { return ScriptModule.current_script->bytecode->bytecode_len - thread->i_offset; } 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() { 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; uint32 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; debug(2, "Executing thread offset: %lu", thread->i_offset); MemoryReadStream *readS = new MemoryReadStream(GetReadPtr(thread), GetReadLen(thread)); in_char = readS->readByte(); 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)readS->readUint16LE(); SSTACK_Push(thread->stack, param1); break; // Push word (PSHD) (dialogue string index) case 0x08: param1 = (SDataWord_T)readS->readUint16LE(); SSTACK_Push(thread->stack, param1); break; // DATA INSTRUCTIONS // Test flag (TSTF) case 0x0B: n_buf = readS->readByte(); param1 = (SDataWord_T)readS->readUint16LE(); SDATA_GetBit(n_buf, param1, &bitstate); SSTACK_Push(thread->stack, bitstate); break; // Get word (GETW) case 0x0C: n_buf = readS->readByte(); param1 = readS->readUint16LE(); SDATA_GetWord(n_buf, param1, &data); SSTACK_Push(thread->stack, data); break; // Modify flag (MODF) case 0x0F: n_buf = readS->readByte(); param1 = (SDataWord_T)readS->readUint16LE(); 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 = readS->readByte(); param1 = (SDataWord_T)readS->readUint16LE(); SSTACK_Top(thread->stack, &data); SDATA_PutWord(n_buf, param1, data); break; // Modify flag and pop (MDFP) case 0x13: n_buf = readS->readByte(); param1 = (SDataWord_T)readS->readUint16LE(); 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 = readS->readByte(); param1 = (SDataWord_T)readS->readUint16LE(); SSTACK_Top(thread->stack, &data); SDATA_PutWord(n_buf, param1, data); break; // CONTROL INSTRUCTIONS // (GOSB): Call subscript ? case 0x17: { int temp; int temp2; temp = readS->readByte(); temp2 = readS->readByte(); param1 = (SDataWord_T)readS->readUint16LE(); data = readS->tell(); //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; uint16 func_num; int FIXME_SHADOWED_result; SFunc_T sfunc; n_args = readS->readByte(); func_num = readS->readUint16LE(); 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 = readS->readUint16LE(); 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 = readS->readUint16LE(); thread->i_offset = (unsigned long)param1; break; // (JNZP): Jump if nonzero + POP case 0x1E: param1 = readS->readUint16LE(); SSTACK_Pop(thread->stack, &data); if (data) { thread->i_offset = (unsigned long)param1; } break; // (JZP): Jump if zero + POP case 0x1F: param1 = readS->readUint16LE(); SSTACK_Pop(thread->stack, &data); if (!data) { thread->i_offset = (unsigned long)param1; } break; // (JNZ): Jump if nonzero case 0x20: param1 = readS->readUint16LE(); SSTACK_Top(thread->stack, &data); if (data) { thread->i_offset = (unsigned long)param1; } break; // (JZ): Jump if zero case 0x21: param1 = readS->readUint16LE(); SSTACK_Top(thread->stack, &data); if (!data) { thread->i_offset = (unsigned long)param1; } break; // (JMPR): Relative jump case 0x57: // ignored? readS->readUint16LE(); readS->readUint16LE(); iparam1 = (long)readS->readByte(); 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 = readS->readUint16LE(); for (i = 0; i < n_switch; i++) { switch_num = readS->readUint16LE(); switch_jmp = readS->readUint16LE(); // 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 = readS->readUint16LE(); 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? readS->readUint16LE(); n_branch = readS->readUint16LE(); for (i = 0; i < n_branch; i++) { branch_wt = readS->readUint16LE(); branch_jmp = readS->readUint16LE(); 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("??? "); readS->readByte(); readS->readUint16LE(); break; // (?) case 0x29: unhandled = 1; printf("??? "); readS->readByte(); readS->readUint16LE(); break; // (?) case 0x2A: unhandled = 1; printf("??? "); readS->readByte(); readS->readUint16LE(); break; // (?) case 0x2B: unhandled = 1; printf("??? "); readS->readByte(); readS->readUint16LE(); 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; // (DIV): 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; // BITWISE INSTRUCTIONS // (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; // (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 = readS->readByte(); param1 = (SDataWord_T) readS->readUint16LE(); // ignored ? readS->readByte(); readS->readUint16LE(); 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 = readS->readByte(); FIXME_SHADOWED_param2 = readS->readByte(); printf("%02X %02X ", FIXME_SHADOWED_param1, FIXME_SHADOWED_param2); if (FIXME_SHADOWED_param2 > 0) { FIXME_SHADOWED_param3 = readS->readUint16LE(); 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; } // Set instruction offset only if a previous instruction didn't branch if (saved_offset == thread->i_offset) { thread->i_offset = readS->tell(); } 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