/* 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 3 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, see . * */ #include "common/memstream.h" #include "ultima/ultima8/usecode/uc_machine.h" #include "ultima/ultima8/usecode/uc_process.h" #include "ultima/ultima8/usecode/usecode.h" #include "ultima/ultima8/kernel/kernel.h" #include "ultima/ultima8/kernel/delay_process.h" #include "ultima/ultima8/ultima8.h" #include "ultima/ultima8/world/current_map.h" #include "ultima/ultima8/world/world.h" #include "ultima/ultima8/usecode/bit_set.h" #include "ultima/ultima8/usecode/byte_set.h" #include "ultima/ultima8/usecode/uc_list.h" #include "ultima/ultima8/misc/id_man.h" #include "ultima/ultima8/world/get_object.h" #include "ultima/ultima8/convert/u8/convert_usecode_u8.h" #include "ultima/ultima8/convert/crusader/convert_usecode_regret.h" #include "ultima/ultima8/world/actors/main_actor.h" namespace Ultima { namespace Ultima8 { //#define DEBUG_USECODE #ifdef DEBUG_USECODE #define TRACE_OP(format, ...) if (trace) debug(format, __VA_ARGS__) #else #define TRACE_OP(format, ...) #endif #ifdef DEBUG_USECODE static const char *print_bp(const int16 offset) { static char str[32]; snprintf(str, 32, "[BP%c%02Xh]", offset < 0 ? '-' : '+', offset < 0 ? -offset : offset); return str; } static const char *print_sp(const int16 offset) { static char str[32]; snprintf(str, 32, "[SP%c%02Xh]", offset < 0 ? '-' : '+', offset < 0 ? -offset : offset); return str; } #endif //#define DUMPHEAP enum UCSegments { SEG_STACK = 0x0000, SEG_STACK_FIRST = 0x0001, SEG_STACK_LAST = 0x7FFE, SEG_STRING = 0x8000, SEG_LIST = 0x8001, // I don't think this is used SEG_OBJ = 0x8002, SEG_GLOBAL = 0x8003 }; UCMachine *UCMachine::_ucMachine = nullptr; UCMachine::UCMachine(const Intrinsic *iset, unsigned int icount) { debug(1, "Creating UCMachine..."); _ucMachine = this; // zero _globals if (GAME_IS_U8) { _globals = new BitSet(0x1000); _convUse = new ConvertUsecodeU8(); } else if (GAME_IS_REMORSE) { _globals = new ByteSet(0x1000); // slight hack: set global 003C to start as avatar number. _globals->setEntries(0x3C, 2, 1); _convUse = new ConvertUsecodeCrusader(); } else { _globals = new ByteSet(0x1000); // slight hack: set global 001E to start as avatar number. _globals->setEntries(0x1E, 2, 1); _convUse = new ConvertUsecodeRegret(); } loadIntrinsics(iset, icount); //!... _listIDs = new idMan(1, 65534, 128); _stringIDs = new idMan(1, 65534, 256); _tracingEnabled = false; _traceAll = false; } UCMachine::~UCMachine() { debug(1, "Destroying UCMachine..."); _ucMachine = nullptr; delete _globals; delete _convUse; delete _listIDs; delete _stringIDs; } void UCMachine::reset() { debug(1, "Resetting UCMachine"); // clear _globals _globals->setSize(0x1000); // slight HACK: set global 003C (remorse) / 001E (regret) // to start as avatar number. if (GAME_IS_REMORSE) { _globals->setEntries(0x3C, 2, 1); } else if (GAME_IS_REGRET) { _globals->setEntries(0x1E, 2, 1); } // clear strings, lists for (auto &i : _listHeap) delete i._value; _listHeap.clear(); _stringHeap.clear(); } void UCMachine::loadIntrinsics(const Intrinsic *i, unsigned int icount) { _intrinsics = i; _intrinsicCount = icount; } void UCMachine::execProcess(UCProcess *p) { assert(p); uint32 base = p->_usecode->get_class_base_offset(p->_classId); Common::SeekableReadStream *cs = new Common::MemoryReadStream(p->_usecode->get_class(p->_classId) + base, p->_usecode->get_class_size(p->_classId) - base); cs->seek(p->_ip); bool trace = trace_show(p->_pid, p->_itemNum, p->_classId); if (trace) { debug("tick %u running process %u, item %u, type %u, class %u, offset %u", Kernel::get_instance()->getTickNum(), p->_pid, p->_itemNum, p->_type, p->_classId, p->_ip); } bool cede = false; bool error = false; bool go_until_cede = false; while (!cede && !error && !p->is_terminated()) { //! guard against reading past end of class //! guard against other error conditions uint8 opcode = cs->readByte(); #ifdef DEBUG_USECODE char op_info[32]; if (trace) { snprintf(op_info, 32, "sp = %02X; %04X:%04X: %02X", p->_stack.stacksize(), p->_classId, p->_ip, opcode); } #endif int8 si8a, si8b; uint8 ui8a; uint16 ui16a, ui16b; uint32 ui32a, ui32b; int16 si16a, si16b; int32 si32a, si32b; switch (opcode) { // POP opcodes case 0x00: // 00 xx // pop 16 bit int, and assign LS 8 bit int into bp+xx si8a = cs->readSByte(); ui16a = p->_stack.pop2(); p->_stack.assign1(p->_bp + si8a, static_cast(ui16a)); TRACE_OP("%s\tpop byte\t%s = %02Xh", op_info, print_bp(si8a), ui16a); break; case 0x01: // 01 xx // pop 16 bit int into bp+xx si8a = cs->readSByte(); ui16a = p->_stack.pop2(); p->_stack.assign2(p->_bp + si8a, ui16a); TRACE_OP("%s\tpop\t\t%s = %04Xh", op_info, print_bp(si8a), ui16a); break; case 0x02: // 02 xx // pop 32 bit int into bp+xx si8a = cs->readSByte(); ui32a = p->_stack.pop4(); p->_stack.assign4(p->_bp + si8a, ui32a); TRACE_OP("%s\tpop dword\t%s = %08Xh", op_info, print_bp(si8a), ui32a); break; case 0x03: { // 03 xx yy // pop yy bytes into bp+xx si8a = cs->readSByte(); uint8 size = cs->readByte(); uint8 buf[256]; p->_stack.pop(buf, size); p->_stack.assign(p->_bp + si8a, buf, size); TRACE_OP("%s\tpop huge\t%s %i", op_info, print_bp(si8a), size); break; } // 0x04 ASSIGN_MEMBER_CHAR (Unused) // 0x05 ASSIGN_MEMBER_INT (Unused) // 0x06 ASSIGN_MEMBER_LONG (Unused) // 0x07 ASSIGN_MEMBER_HUGE (Unused) case 0x08: // 08 // pop 32bits into process result register TRACE_OP("%s\tpop dword\tprocess result", op_info); p->_result = p->_stack.pop4(); break; case 0x09: { // 09 xx yy zz // pop yy bytes into an element of list bp+xx (or slist if zz set) si8a = cs->readSByte(); ui32a = cs->readByte(); si8b = cs->readSByte(); TRACE_OP("%s\tassign element\t%s (%02X) (slist==%02X)", op_info, print_bp(si8a), ui32a, si8b); ui16a = p->_stack.pop2() - 1; // index ui16b = p->_stack.access2(p->_bp + si8a); UCList *l = getList(ui16b); if (!l) { warning("assign element to an invalid list (%u)", ui16b); error = true; break; } if (si8b) { // slist? // what special behaviour do we need here? // probably just that the overwritten element has to be freed? if (ui32a != 2) { warning("Unhandled operand %u to pop slist", ui32a); error = true; // um? } l->assign(ui16a, p->_stack.access()); p->_stack.pop2(); // advance SP } else { l->assign(ui16a, p->_stack.access()); p->_stack.addSP(ui32a); } break; } // PUSH opcodes case 0x0A: // 0A xx // push sign-extended 8 bit xx onto the stack as 16 bit ui16a = cs->readSByte(); p->_stack.push2(ui16a); TRACE_OP("%s\tpush sbyte\t%04Xh", op_info, ui16a); break; case 0x0B: // 0B xx xx // push 16 bit xxxx onto the stack ui16a = cs->readUint16LE(); p->_stack.push2(ui16a); TRACE_OP("%s\tpush\t\t%04Xh", op_info, ui16a); break; case 0x0C: // 0C xx xx xx xx // push 32 bit xxxxxxxx onto the stack ui32a = cs->readUint32LE(); p->_stack.push4(ui32a); TRACE_OP("%s\tpush dword\t%08Xh", op_info, ui32a); break; case 0x0D: { // 0D xx xx yy ... yy 00 // push string (yy ... yy) of length xx xx onto the stack ui16a = cs->readUint16LE(); char *str = new char[ui16a + 1]; cs->read(str, ui16a); str[ui16a] = 0; // WORKAROUND: German U8: When the candles are not in the right positions // for a sorcery spell, the string does not match, causing a crash. // Original bug: https://sourceforge.net/p/pentagram/bugs/196/ if (GAME_IS_U8 && p->_classId == 0x7C) { if (!strcmp(str, " Irgendetwas stimmt nicht!")) { str[25] = '.'; // ! to . } } TRACE_OP("%s\tpush string\t\"%s\"", op_info, str); ui16b = cs->readByte(); if (ui16b != 0) { warning("Zero terminator missing in push string"); error = true; } p->_stack.push2(assignString(str)); delete[] str; break; } case 0x0E: { // 0E xx yy // pop yy values of size xx and push the resulting list // (list is created in reverse order) ui16a = cs->readByte(); ui16b = cs->readByte(); UCList *l = new UCList(ui16a, ui16b); p->_stack.addSP(ui16a * (ui16b - 1)); for (unsigned int i = 0; i < ui16b; i++) { l->append(p->_stack.access()); p->_stack.addSP(-ui16a); } p->_stack.addSP(ui16a * (ui16b + 1)); p->_stack.push2(assignList(l)); TRACE_OP("%s\tcreate list\t%02X (%02X)", op_info, ui16b, ui16a); break; } // Usecode function and intrinsic calls case 0x0F: { // 0F xx yyyy // intrinsic call. xx is number of argument bytes // (includes this pointer, if present) // NB: do not actually pop these argument bytes uint16 arg_bytes = cs->readByte(); uint16 func = cs->readUint16LE(); TRACE_OP("%s\tcalli\t\t%04Xh (%02Xh arg bytes) %s", op_info, func, arg_bytes, _convUse->intrinsics()[func]); // !constants if (func >= _intrinsicCount || _intrinsics[func] == 0) { Item *testItem = nullptr; p->_temp32 = 0; if (arg_bytes >= 4) { // HACKHACKHACK to check what the argument is. uint8 *argmem = new uint8[arg_bytes]; uint8 *args = argmem; p->_stack.pop(args, 4); p->_stack.addSP(-4); // don't really pop the args ARG_UC_PTR(iptr); uint16 testItemId = ptrToObject(iptr); testItem = getItem(testItemId); delete [] argmem; } Common::String info; if (testItem) { info = Common::String::format("item %u", testItem->getObjId()); if (arg_bytes > 4) info += Common::String::format(" + %u bytes", arg_bytes - 4); } else { info = Common::String::format("%u bytes", arg_bytes); } warning("Unhandled intrinsic %u \'%s\'? (%s) called", func, _convUse->intrinsics()[func], info.c_str()); if (testItem) { warning("%s", testItem->dumpInfo().c_str()); } } else { //!! hackish if (_intrinsics[func] == UCMachine::I_dummyProcess || _intrinsics[func] == UCMachine::I_true) { warning("Unhandled intrinsic %u \'%s\'? called", func, _convUse->intrinsics()[func]); } uint8 *argbuf = new uint8[arg_bytes]; p->_stack.pop(argbuf, arg_bytes); p->_stack.addSP(-arg_bytes); // don't really pop the args p->_temp32 = _intrinsics[func](argbuf, arg_bytes); delete[] argbuf; } // WORKAROUND: In U8, the flag 'startedConvo' [0000 01] which acts // as a mutex is set too late in the script, allowing two copies of // of the Ancient Ones script (each spawned by a different egg) to // run simultaneously. Set the flag when the avatar is put in stasis // to avoid this. // Original bug: https://sourceforge.net/p/pentagram/feature-requests/6/ if (GAME_IS_U8 && p->_classId == 0x48B && func == 0xD0) { // 0xD0 = setAvatarInStasis _globals->setEntries(0, 1, 1); } break; } // 0x10 NEAR_ROUTINE_CALL (Unused in U8 and Crusader) case 0x11: { // 11 xx xx yy yy // Ultima 8: // call the function at offset yy yy of class xx xx // Crusader: // call function number yy yy of class xx xx uint16 new_classid = cs->readUint16LE(); uint16 new_offset = cs->readUint16LE(); TRACE_OP("%s\tcall\t\t%04X:%04X", op_info, new_classid, new_offset); if (GAME_IS_CRUSADER) { new_offset = p->_usecode->get_class_event(new_classid, new_offset); } p->_ip = static_cast(cs->pos()); // Truncates!! p->call(new_classid, new_offset); // Update the code segment uint32 base_ = p->_usecode->get_class_base_offset(p->_classId); delete cs; cs = new Common::MemoryReadStream(p->_usecode->get_class(p->_classId) + base_, p->_usecode->get_class_size(p->_classId) - base_); cs->seek(p->_ip); // Resume execution break; } case 0x12: // 12 // pop 16bits into temp register p->_temp32 = p->_stack.pop2(); TRACE_OP("%s\tpop\t\ttemp = %04X", op_info, (p->_temp32 & 0xFFFF)); break; case 0x13: // 13 // pop 32bits into temp register. (Not actually used in U8 or Crusader) p->_temp32 = p->_stack.pop4(); TRACE_OP("%s\tpop long\t\ttemp = %08X", op_info, p->_temp32); break; // Arithmetic case 0x14: // 14 // 16 bit add si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); p->_stack.push2(static_cast(si16a + si16b)); TRACE_OP("%s\tadd", op_info); break; case 0x15: // 15 // 32 bit add si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); p->_stack.push4(static_cast(si32a + si32b)); TRACE_OP("%s\tadd long", op_info); break; case 0x16: // 16 // pop two strings from the stack and push the concatenation // (free the originals? order?) ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); if (ui16b == 0) { warning("Trying to append to string 0."); error = true; break; } _stringHeap[ui16b] += getString(ui16a); freeString(ui16a); p->_stack.push2(ui16b); TRACE_OP("%s\tconcat\t\t= %s", op_info, _stringHeap[ui16b].c_str()); break; case 0x17: { // 17 // pop two lists from the stack and push the 'sum' of the lists // (freeing the originals) ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); UCList *listA = getList(ui16a); UCList *listB = getList(ui16b); if (listB && listA) { if (listA->getElementSize() != listB->getElementSize()) { warning("Trying to append lists with different element sizes (%u != %u)", listB->getElementSize(), listA->getElementSize()); error = true; } else { listB->appendList(*listA); } // CHECKME: do we allow appending a list to itself? assert(ui16a != ui16b); freeList(ui16a); p->_stack.push2(ui16b); } else { // at least one of the lists didn't exist. Error or not? // for now: if one exists, push that one. // if neither exists, push 0. if (listA) { p->_stack.push2(ui16a); } else if (listB) { p->_stack.push2(ui16b); } else { p->_stack.push2(0); } } TRACE_OP("%s\tappend", op_info); break; } // 0x18 EXCLUSIVE_ADD_LIST (Unused in U8 and Crusader) case 0x19: { // 19 02 // add two stringlists, removing duplicates ui32a = cs->readByte(); if (ui32a != 2) { warning("Unhandled operand %u to union slist", ui32a); error = true; } ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); UCList *srclist = getList(ui16a); UCList *dstlist = getList(ui16b); if (!srclist || !dstlist) { warning("Invalid list param to union slist"); error = true; } else { dstlist->unionStringList(*srclist); freeStringList(ui16a); // contents are actually freed in unionSL } p->_stack.push2(ui16b); TRACE_OP("%s\tunion slist\t(%02X)", op_info, ui32a); break; } case 0x1A: { // 1A 02 // subtract string list ui32a = cs->readByte(); // elementsize (always 02) ui32a = 2; ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); UCList *srclist = getList(ui16a); UCList *dstlist = getList(ui16b); if (!srclist || !dstlist) { warning("Invalid list param to subtract slist"); error = true; } else { dstlist->subtractStringList(*srclist); freeStringList(ui16a); } p->_stack.push2(ui16b); TRACE_OP("%s\tremove slist\t(%02X)", op_info, ui32a); break; } case 0x1B: { // 1B xx // pop two lists from the stack of element size xx and // remove the 2nd from the 1st // (free the originals? order?) ui32a = cs->readByte(); // elementsize ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); UCList *srclist = getList(ui16a); UCList *dstlist = getList(ui16b); if (!srclist || !dstlist) { warning("Invalid list param to remove from slist"); error = true; } else { dstlist->subtractList(*srclist); freeList(ui16a); } p->_stack.push2(ui16b); TRACE_OP("%s\tremove list\t(%02X)", op_info, ui32a); break; } case 0x1C: // 1C // subtract two 16 bit integers si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); p->_stack.push2(static_cast(si16b - si16a)); TRACE_OP("%s\tsub", op_info); break; case 0x1D: // 1D // subtract two 32 bit integers si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); p->_stack.push4(static_cast(si32b - si32a)); TRACE_OP("%s\tsub long", op_info); break; case 0x1E: // 1E // multiply two 16 bit integers si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); p->_stack.push2(static_cast(si16a * si16b)); TRACE_OP("%s\tmul", op_info); break; case 0x1F: // 1F // multiply two 32 bit integers si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); p->_stack.push4(static_cast(si32a * si32b)); TRACE_OP("%s\tmul long", op_info); break; case 0x20: // 20 // divide two 16 bit integers si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16a != 0) { p->_stack.push2(static_cast(si16b / si16a)); } else { warning("0x20 division by zero."); p->_stack.push2(0); } TRACE_OP("%s\tdiv", op_info); break; case 0x21: // 21 // divide two 32 bit integers si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32a != 0) { p->_stack.push4(static_cast(si32b / si32a)); } else { warning("0x21 division by zero."); p->_stack.push4(0); } TRACE_OP("%s\tdiv", op_info); break; case 0x22: // 22 // 16 bit mod, b % a // Appears to be C-style % si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16a != 0) { p->_stack.push2(static_cast(si16b % si16a)); } else { warning("0x22 division by zero."); p->_stack.push2(0); } TRACE_OP("%s\tmod", op_info); break; case 0x23: // 23 // 32 bit mod si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32a != 0) { p->_stack.push4(static_cast(si32b % si32a)); } else { warning("0x23 division by zero."); p->_stack.push4(0); } TRACE_OP("%s\tmod long", op_info); break; case 0x24: // 24 // 16 bit cmp si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16a == si16b) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tcmp", op_info); break; case 0x25: // 25 // 32 bit cmp si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32a == si32b) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tcmp long", op_info); break; case 0x26: // 26 // compare two strings // (delete strings) ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); if (getString(ui16b) == getString(ui16a)) p->_stack.push2(1); else p->_stack.push2(0); freeString(ui16a); freeString(ui16b); TRACE_OP("%s\tstrcmp", op_info); break; // 0x27 EQUALS_HUGE (Unused in U8 and Crusader) case 0x28: // 28 // 16 bit less-than si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16b < si16a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tlt", op_info); break; case 0x29: // 29 // 32 bit less-than si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32b < si32a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tlt long", op_info); break; case 0x2A: // 2A // 16 bit less-or-equal si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16b <= si16a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tle", op_info); break; case 0x2B: // 2B // 32 bit less-or-equal si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32b <= si32a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tle long", op_info); break; case 0x2C: // 2C // 16 bit greater-than si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16b > si16a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tgt", op_info); break; case 0x2D: // 2D // 32 bit greater-than si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32b > si32a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tgt long", op_info); break; case 0x2E: // 2E // 16 bit greater-or-equal si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16b >= si16a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tge", op_info); break; case 0x2F: // 2F // 32 bit greater-or-equal si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32b >= si32a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tge long", op_info); break; case 0x30: // 30 // 16 bit boolean not ui16a = p->_stack.pop2(); if (!ui16a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tnot", op_info); break; case 0x31: // 31 // 32 bit boolean not (not used in U8 or Crusader) ui32a = p->_stack.pop4(); if (!ui32a) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tnot long", op_info); break; case 0x32: // 32 // 16 bit logical and ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); if (ui16a && ui16b) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tand", op_info); break; case 0x33: // 33 // 32 bit logical and (not used in U8 or Crusader) ui32a = p->_stack.pop4(); ui32b = p->_stack.pop4(); if (ui32a && ui32b) { p->_stack.push4(1); } else { p->_stack.push4(0); } TRACE_OP("%s\tand long", op_info); break; case 0x34: // 34 // 16 bit logical or ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); if (ui16a || ui16b) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tor", op_info); break; case 0x35: // 35 // 32 bit logical or (not used in U8 or Crusader) ui32a = p->_stack.pop4(); ui32b = p->_stack.pop4(); if (ui32a || ui32b) { p->_stack.push4(1); } else { p->_stack.push4(0); } TRACE_OP("%s\tor long", op_info); break; case 0x36: // 36 // 16 bit not-equal si16a = static_cast(p->_stack.pop2()); si16b = static_cast(p->_stack.pop2()); if (si16a != si16b) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tne", op_info); break; case 0x37: // 37 // 32 bit not-equal (only used in Crusader) si32a = static_cast(p->_stack.pop4()); si32b = static_cast(p->_stack.pop4()); if (si32a != si32b) { p->_stack.push2(1); } else { p->_stack.push2(0); } TRACE_OP("%s\tne long", op_info); break; case 0x38: { // 38 xx yy // is element (size xx) in list? (or slist if yy is true) // free list/slist afterwards ui16a = cs->readByte(); ui32a = cs->readByte(); ui16b = p->_stack.pop2(); UCList *l = getList(ui16b); if (!l) { warning("Invalid list id %u", ui16b); error = true; } else if (ui32a) { // stringlist if (ui16a != 2) { warning("Unhandled operand %u to in slist", ui16a); error = true; } if (l->stringInList(p->_stack.pop2())) p->_stack.push2(1); else p->_stack.push2(0); freeStringList(ui16b); } else { bool found = l->inList(p->_stack.access()); p->_stack.addSP(ui16a); if (found) p->_stack.push2(1); else p->_stack.push2(0); freeList(ui16b); } TRACE_OP("%s\tin list\t\t%s slist==%02X", op_info, print_bp(ui16a), ui32a); break; } case 0x39: // 39 // 16 bit bitwise and ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); p->_stack.push2(ui16a & ui16b); TRACE_OP("%s\tbit_and", op_info); break; case 0x3A: // 3A // 16 bit bitwise or ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); p->_stack.push2(ui16a | ui16b); TRACE_OP("%s\tbit_or", op_info); break; case 0x3B: // 3B // 16 bit bitwise not ui16a = p->_stack.pop2(); p->_stack.push2(~ui16a); TRACE_OP("%s\tbit_not", op_info); break; case 0x3C: // 3C // 16 bit left shift // operand order is different between U8 and crusader! if (GAME_IS_U8) { si16a = static_cast(p->_stack.pop2()); ui16b = static_cast(p->_stack.pop2()); } else { ui16b = static_cast(p->_stack.pop2()); si16a = static_cast(p->_stack.pop2()); } p->_stack.push2(static_cast(si16a << ui16b)); TRACE_OP("%s\tlsh\t%04Xh >> %xh = %xh", op_info, si16a, ui16b, si16a << ui16b); break; case 0x3D: // 3D // 16 bit right shift (sign-extended - game uses SAR opcode) // operand order is different between U8 and crusader! if (GAME_IS_U8) { si16a = static_cast(p->_stack.pop2()); ui16b = static_cast(p->_stack.pop2()); } else { ui16b = static_cast(p->_stack.pop2()); si16a = static_cast(p->_stack.pop2()); } p->_stack.push2(static_cast(si16a >> ui16b)); TRACE_OP("%s\trsh\t%04Xh >> %xh = %xh", op_info, si16a, ui16b, si16a >> ui16b); break; case 0x3E: // 3E xx // push the value of the sign-extended 8 bit local var xx as 16 bit int si8a = cs->readSByte(); ui16a = static_cast(static_cast(p->_stack.access1(p->_bp + si8a))); p->_stack.push2(ui16a); TRACE_OP("%s\tpush byte\t%s = %02Xh", op_info, print_bp(si8a), ui16a); break; case 0x3F: // 3F xx // push the value of the 16 bit local var xx si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_bp + si8a); p->_stack.push2(ui16a); TRACE_OP("%s\tpush\t\t%s = %04Xh", op_info, print_bp(si8a), ui16a); break; case 0x40: // 40 xx // push the value of the 32 bit local var xx si8a = cs->readSByte(); ui32a = p->_stack.access4(p->_bp + si8a); p->_stack.push4(ui32a); TRACE_OP("%s\tpush dword\t%s = %08Xh", op_info, print_bp(si8a), ui32a); break; case 0x41: { // 41 xx // push the string local var xx // duplicating the string? si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_bp + si8a); p->_stack.push2(duplicateString(ui16a)); TRACE_OP("%s\tpush string\t%s", op_info, print_bp(si8a)); break; } case 0x42: { // 42 xx yy // push the list (with yy size elements) at BP+xx // duplicating the list? si8a = cs->readSByte(); ui16a = cs->readByte(); ui16b = p->_stack.access2(p->_bp + si8a); UCList *l = new UCList(ui16a); if (getList(ui16b)) { l->copyList(*getList(ui16b)); } else { // trying to push non-existent list. Error or not? // Not: for example, function 01E3::0080, offset 0112 // warning("Pushing non-existent list"); // error = true; } uint16 newlistid = assignList(l); p->_stack.push2(newlistid); TRACE_OP("%s\tpush list\t%s (%04X, copy %04X, %d elements)", op_info, print_bp(si8a), ui16b, newlistid, l->getSize()); break; } case 0x43: { // 43 xx // push the stringlist local var xx // duplicating the list, duplicating the strings in the list si8a = cs->readSByte(); ui16a = 2; ui16b = p->_stack.access2(p->_bp + si8a); UCList *l = new UCList(ui16a); if (getList(ui16b)) { l->copyStringList(*getList(ui16b)); } else { // trying to push non-existent list. Error or not? // (Devon's talk code seems to use it; so no error for now) // warning("Pushing non-existent slist"); // error = true; } p->_stack.push2(assignList(l)); TRACE_OP("%s\tpush slist\t%s", op_info, print_bp(si8a)); break; } case 0x44: { // 44 xx yy // push element from the second last var pushed onto the stack // (a list/slist), indexed by the last element pushed onto the list // (a byte/word). XX is the size of the types contained in the list // YY is true if it's a slist (for garbage collection) // duplicate string if YY? yy = 1 only occurs // in two places in U8: once it pops into temp afterwards, // once it is indeed freed. So, guessing we should duplicate. ui32a = cs->readByte(); ui32b = cs->readByte(); ui16a = p->_stack.pop2() - 1; // index ui16b = p->_stack.pop2(); // list UCList *l = getList(ui16b); if (!l) { // warning("push element from invalid list (%u)", ui16b); // This is necessary for closing the backpack to work p->_stack.push0(ui32a); // error = true; } else { if (ui32b) { uint16 s = l->getStringIndex(ui16a); p->_stack.push2(duplicateString(s)); } else { if (ui16a < l->getSize()) { p->_stack.push((*l)[ui16a], ui32a); } else { // WORKAROUND warning("ignore 0x44 request to push %d from list len %d", ui16a, l->getSize()); } } } TRACE_OP("%s\tpush element\t%02X slist==%02X", op_info, ui32a, ui32b); break; } case 0x45: // 45 xx yy // push huge of size yy from BP+xx si8a = cs->readSByte(); ui16b = cs->readByte(); p->_stack.push(p->_stack.access(p->_bp + si8a), ui16b); TRACE_OP("%s\tpush huge\t%s %02X", op_info, print_bp(si8a), ui16b); break; // 0x46 BYTE_MEMBER_REFERENCE (Unused) // 0x47 INT_MEMBER_REFERENCE (Unused) // 0x48 LONG_MEMBER_REFERENCE (Unused) // 0x49 HUGE_MEMBER_REFERENCE (Unused) // 0x4a THIS_REFERENCE (Unused) case 0x4B: // 4B xx // push 32 bit pointer address of BP+XX si8a = cs->readSByte(); p->_stack.push4(stackToPtr(p->_pid, p->_bp + si8a)); TRACE_OP("%s\tpush addr\t%s", op_info, print_bp(si8a)); break; case 0x4C: { // 4C xx // indirect push, // pops a 32 bit pointer off the stack and pushes xx bytes // from the location referenced by the pointer ui16a = cs->readByte(); ui32a = p->_stack.pop4(); p->_stack.addSP(-ui16a); if (!dereferencePointer(ui32a, p->_stack.access(), ui16a)) { error = true; } if (!error && ui16a == 2) { TRACE_OP("%s\tpush indirect\t%02Xh bytes = %04Xh", op_info, ui16a, p->_stack.access2(p->_stack.getSP())); } else { TRACE_OP("%s\tpush indirect\t%02Xh bytes", op_info, ui16a); } break; } case 0x4D: { // 4D xx // indirect pop // pops a 32 bit pointer off the stack and pushes xx bytes // from the location referenced by the pointer ui16a = cs->readByte(); ui32a = p->_stack.pop4(); if (assignPointer(ui32a, p->_stack.access(), ui16a)) { p->_stack.addSP(ui16a); } else { error = true; } TRACE_OP("%s\tpop indirect\t%02Xh bytes", op_info, ui16a); break; } case 0x4E: // 4E xx xx yy // push global xxxx size yy bits ui16a = cs->readUint16LE(); ui16b = cs->readByte(); ui32a = _globals->getEntries(ui16a, ui16b); p->_stack.push2(static_cast(ui32a)); TRACE_OP("%s\tpush\t\tglobal [%04X %02X] = %02X", op_info, ui16a, ui16b, ui32a); break; case 0x4F: // 4F xx xx yy // pop value into global xxxx size yy bits ui16a = cs->readUint16LE(); // pos ui16b = cs->readByte(); // len ui32a = p->_stack.pop2(); // val _globals->setEntries(ui16a, ui16b, ui32a); if ((GAME_IS_U8 && (ui32a & ~(((1 << ui16b) - 1)))) || (GAME_IS_CRUSADER && (ui16b > 2))) { warning("Value popped into a flag it doesn't fit in (%04X %04X %04X)", ui16a, ui16b, ui32a); } // paranoid :-) if (GAME_IS_U8) { assert(_globals->getEntries(ui16a, ui16b) == (ui32a & ((1 << ui16b) - 1))); } else { assert(_globals->getEntries(ui16a, ui16b) == (ui32a & ((1 << (ui16b * 8)) - 1))); } TRACE_OP("%s\tpop\t\tglobal [%04X %02X] = %02X", op_info, ui16a, ui16b, ui32a); break; case 0x50: // 50 // return from function if (p->ret()) { // returning from process TRACE_OP("%s\tret\t\tfrom process", op_info); p->terminateDeferred(); // return value is going to be stored somewhere, // and some other process is probably waiting for it. // So, we can't delete ourselves just yet. } else { TRACE_OP("%s\tret\t\tto %04X:%04X", op_info, p->_classId, p->_ip); // return value is stored in _temp32 register // Update the code segment uint32 base_ = p->_usecode->get_class_base_offset(p->_classId); delete cs; cs = new Common::MemoryReadStream(p->_usecode->get_class(p->_classId) + base_, p->_usecode->get_class_size(p->_classId) - base_); cs->seek(p->_ip); } // Resume execution break; case 0x51: // 51 xx xx // relative jump to xxxx if false si16a = static_cast(cs->readUint16LE()); ui16b = p->_stack.pop2(); if (!ui16b) { ui16a = cs->pos() + si16a; cs->seek(ui16a); TRACE_OP("%s\tjne\t\t%04hXh\t(to %04X) (taken)", op_info, si16a, cs->pos()); } else { TRACE_OP("%s\tjne\t\t%04hXh\t(to %04X) (not taken)", op_info, si16a, cs->pos()); } break; case 0x52: // 52 xx xx // relative jump to xxxx si16a = static_cast(cs->readUint16LE()); ui16a = cs->pos() + si16a; cs->seek(ui16a); TRACE_OP("%s\tjmp\t\t%04hXh\t(to %04X)", op_info, si16a, cs->pos()); break; case 0x53: // 53 // suspend TRACE_OP("%s\tsuspend", op_info); go_until_cede = false; cede = true; break; case 0x54: { // 54 01 01 // implies // Links two processes (two pids are popped) a and b, meaning // b->waitfor(a) // // In the disassembly, '01 01' is the number of processes to // pop, but in practice only ever appears as 01 01. // // pid a is often the current pid in U8 // 'implies' seems to push a value too, although it is very // often ignored. It looks like it's a pid, but which one? // additionally, it is possible that 'implies' puts the result // of a process in the 'process result' variable, // or more likely, when a process finishes, it sets the result // value of the processes that were waiting for it. // 0x6D (push process result) only seems to occur soon after // an 'implies' cs->readUint16LE(); // skip the 01 01 ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); p->_stack.push2(ui16a); //!! which pid do we need to push!? TRACE_OP("%s\timplies", op_info); Process *proc = Kernel::get_instance()->getProcess(ui16b); Process *proc2 = Kernel::get_instance()->getProcess(ui16a); if (proc && proc2) { proc->waitFor(ui16a); // The proc is now marked suspended, but finish this execution // until we hit a suspend or return. go_until_cede = true; } else { if (!proc && !proc2) { warning("Non-existent process PID (%u, %u) in implies.", ui16a, ui16b); } else if (!proc) { warning("Non-existent process PID (%u) in implies.", ui16b); } else { warning("Non-existent process PID (%u) in implies.", ui16a); } // This condition triggers in 057C:1090 when talking // to a child (class 02C4), directly after the conversation // Specifically, it occurs because there is no // leaveFastArea usecode for class 02C4. // So currently we only regard this as an error when the // missing process wasn't PID 0. if ((ui16a && !proc2) || (ui16b && !proc)) error = true; } break; } // 0x55: AND_IMPLIES (only does push 0x402 in disasm, unused in U8 and Crusader) // 0x56: OR_IMPLIES (only does push 0x404 in disasm, unused in U8 and Crusader) case 0x57: { // 57 aa tt xx xx yy yy // spawn process function yyyy in class xxxx // aa = number of arg bytes pushed (not including this pointer which is 4 bytes) // tt = sizeof this pointer object // only remove the this pointer from stack (4 bytes) // put PID of spawned process in temp int arg_bytes = cs->readByte(); int this_size = cs->readByte(); uint16 classid = cs->readUint16LE(); uint16 offset = cs->readUint16LE(); uint32 thisptr = p->_stack.pop4(); TRACE_OP("%s\tspawn\t\t%02X %02X %04X:%04X", op_info, arg_bytes, this_size, classid, offset); if (GAME_IS_CRUSADER) { offset = p->_usecode->get_class_event(classid, offset); } UCProcess *newproc = new UCProcess(classid, offset, thisptr, this_size, p->_stack.access(), arg_bytes); // Note: order of execution of this process and the new one is // relevant. Currently, the spawned processes is executed once // immediately, after which the current process resumes p->_temp32 = Kernel::get_instance()->addProcessExec(newproc); if (trace) { debug("tick %u (still) running process %u, item %u, type %u, class %u, offset %u", Kernel::get_instance()->getTickNum(), p->_pid, p->_itemNum, p->_type, p->_classId, p->_ip); } break; } case 0x58: { // 58 xx xx yy yy zz zz tt uu // spawn inline process function yyyy in class xxxx at offset zzzz // tt = size of this pointer // uu = unknown (occurring values: 00, 02, 05) - seems unused in original uint16 classid = cs->readUint16LE(); uint16 offset = cs->readUint16LE(); uint16 delta = cs->readUint16LE(); int this_size = cs->readByte(); int unknown = cs->readByte(); // ?? // This only gets used in U8. If it were used in Crusader it would // need the offset translation done in 0x57. assert(GAME_IS_U8); TRACE_OP("%s\tspawn inline\t%04X:%04X+%04X=%04X %02X %02X", op_info, classid, offset, delta, offset + delta, this_size, unknown); // This also ensures that unknown variable is used when TRACE_OP is empty if (unknown != 0 && unknown != 2 && unknown != 5) { debug(10, "unknown unknown value: %02X", unknown); } uint32 thisptr = 0; if (this_size > 0) thisptr = p->_stack.access4(p->_bp + 6); UCProcess *newproc = new UCProcess(classid, offset + delta, thisptr, this_size); // as with 'spawn', run the spawned process once immediately uint16 newpid = Kernel::get_instance()->addProcessExec(newproc); if (trace) { debug("tick %u (still) running process %u, item %u, type %u, class %u, offset %u", Kernel::get_instance()->getTickNum(), p->_pid, p->_itemNum, p->_type, p->_classId, p->_ip); } p->_stack.push2(newpid); //! push pid of new proc break; } case 0x59: // 59 // push current process id p->_stack.push2(p->_pid); TRACE_OP("%s\tpush\t\tpid = %04Xh", op_info, p->_pid); break; case 0x5A: // 5A xx // init function. xx = local var size // sets xx bytes on stack to 0, moving sp ui16a = cs->readByte(); TRACE_OP("%s\tinit\t\t%02X", op_info, ui16a); if (ui16a & 1) ui16a++; // 16-bit align if (ui16a > 0) { p->_stack.push0(ui16a); } break; case 0x5B: // 5B xx xx // debug line no xx xx ui16a = cs->readUint16LE(); // source line number TRACE_OP("%s\tdebug\tline number %d", op_info, ui16a); break; case 0x5C: { // 5C xx xx char[9] // debug line no xx xx in class str ui16a = cs->readUint16LE(); // source line number char name[10] = {0}; for (int x = 0; x < 9; x++) { // skip over class name and null terminator name[x] = cs->readByte(); } TRACE_OP("%s\tdebug\tline number %d\t\"%s\"", op_info, ui16a, name); debug(10, "name: \"%s\"", name); // Ensures that name variable is used when TRACE_OP is empty break; } case 0x5D: // 5D // push 8 bit value returned from function call // (push temp8 as 16 bit value) p->_stack.push2(static_cast(p->_temp32 & 0xFF)); TRACE_OP("%s\tpush byte\tretval = %02Xh", op_info, (p->_temp32 & 0xFF)); break; case 0x5E: // 5E // push 16 bit value returned from function call // (push temp16) p->_stack.push2(static_cast(p->_temp32 & 0xFFFF)); TRACE_OP("%s\tpush\t\tretval = %04Xh", op_info, (p->_temp32 & 0xFFFF)); break; case 0x5F: // 5F // push 32 bit value returned from function call // (push _temp32) p->_stack.push4(p->_temp32); TRACE_OP("%s\tpush long\t\tretval = %08Xh", op_info, p->_temp32); break; case 0x60: // 60 // convert 16-bit to 32-bit int (sign extend) si32a = static_cast(p->_stack.pop2()); p->_stack.push4(si32a); TRACE_OP("%s\tint to long", op_info); break; case 0x61: // 61 // convert 32-bit to 16-bit int si16a = static_cast(p->_stack.pop4()); p->_stack.push2(si16a); TRACE_OP("%s\tlong to int", op_info); break; case 0x62: // 62 xx // free the string in var BP+xx si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_bp + si8a); freeString(ui16a); TRACE_OP("%s\tfree string\t%s = %04X", op_info, print_bp(si8a), ui16a); break; case 0x63: // 63 xx // free the stringlist in var BP+xx si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_bp + si8a); freeStringList(ui16a); TRACE_OP("%s\tfree slist\t%s = %04X", op_info, print_bp(si8a), ui16a); break; case 0x64: // 64 xx // free the list in var BP+xx si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_bp + si8a); freeList(ui16a); TRACE_OP("%s\tfree list\t%s = %04X", op_info, print_bp(si8a), ui16a); break; case 0x65: // 65 xx // free the string at SP+xx // NB: sometimes there's a 32-bit string pointer at SP+xx // However, the low word of this is exactly the 16bit ref si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_stack.getSP() + si8a); freeString(ui16a); TRACE_OP("%s\tfree string\t%s = %04X", op_info, print_sp(si8a), ui16a); break; case 0x66: // 66 xx // free the list at SP+xx si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_stack.getSP() + si8a); freeList(ui16a); TRACE_OP("%s\tfree list\t%s = %04X", op_info, print_sp(si8a), ui16a); break; case 0x67: // 67 xx // free the string list at SP+xx si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_stack.getSP() + si8a); freeStringList(ui16a); TRACE_OP("%s\tfree slist\t%s = %04x", op_info, print_sp(si8a), ui16a); break; // 0x68 COPY_STRING (unused in U8 and Crusader) case 0x69: // 69 xx // push the string in var BP+xx as 32 bit pointer si8a = cs->readSByte(); ui16a = p->_stack.access2(p->_bp + si8a); p->_stack.push4(stringToPtr(ui16a)); TRACE_OP("%s\tstr to ptr\t%s", op_info, print_bp(si8a)); break; // 0x6A Convert pointer to string (unused in U8 and Crusader) case 0x6B: // 6B // pop a string and push 32 bit pointer to string ui16a = p->_stack.pop2(); p->_stack.push4(stringToPtr(ui16a)); TRACE_OP("%s\tstr to ptr", op_info); break; case 0x6C: // 6C xx yy // yy = type (01 = string, 02 = slist, 03 = list) // copy the (string/slist/list) in BP+xx to the current process, // and add it to the "Free Me" list of the process si8a = cs->readByte(); // index ui8a = cs->readByte(); // type TRACE_OP("%s\tparam _pid chg\t%s, type=%u", op_info, print_bp(si8a), ui8a); ui16a = p->_stack.access2(p->_bp + si8a); switch (ui8a) { case 1: // string // copy string ui16b = duplicateString(ui16a); break; case 2: { // slist UCList *l = new UCList(2); const UCList *srclist = getList(ui16a); if (!srclist) { warning("Invalid src list passed to slist copy"); ui16b = 0; delete l; break; } l->copyStringList(*srclist); ui16b = assignList(l); } break; case 3: { // list const UCList *l = getList(ui16a); if (!l) { warning("Invalid src list passed to list copy"); ui16b = 0; break; } int elementsize = l->getElementSize(); UCList *l2 = new UCList(elementsize); l2->copyList(*l); ui16b = assignList(l2); } break; default: ui16b = 0; warning("Error: invalid param _pid change type (%u)", ui8a); error = true; } p->_stack.assign2(p->_bp + si8a, ui16b); // assign new index p->freeOnTerminate(ui16b, ui8a); // free new var when terminating break; case 0x6D: // 6D // push 32bit result of current process TRACE_OP("%s\tpush dword\tprocess result", op_info); p->_stack.push4(p->_result); break; case 0x6E: // 6E xx // subtract xx from stack pointer // (effect on SP is the same as popping xx bytes) si8a = cs->readSByte(); p->_stack.addSP(-si8a); TRACE_OP("%s\tmove sp\t\t%s%02Xh", op_info, si8a < 0 ? "-" : "", si8a < 0 ? -si8a : si8a); break; case 0x6F: // 6F xx // push 32 pointer address of SP-xx si8a = cs->readSByte(); p->_stack.push4(stackToPtr(p->_pid, static_cast(p->_stack.getSP() - si8a))); TRACE_OP("%s\tpush addr\t%s", op_info, print_sp(-si8a)); break; // loop-related opcodes // 0x70 has different types: // 02: search the area around an object // 03: search the area around an object, recursing into containers // 04: search a container // 05: search a container, recursing into containers // 06: something about looking for items on top of another (??) // each of these types allocate a rather large area on the stack // we expect SP to be at the end of that area when 0x73 is executed // a 'loop script' (created by 0x74) is used to select items case 0x70: { // 70 xx yy zz // loop something. Stores 'current object' in var xx // yy == num bytes in string // zz == type si16a = cs->readSByte(); uint32 scriptsize = cs->readByte(); uint32 searchtype = cs->readByte(); ui16a = p->_stack.pop2(); ui16b = p->_stack.pop2(); //!! This may not be the way the original did things... // We'll first create a list of all matching items. // Store the id of this list in the last two bytes // of our stack area. // Behind that we'll store an index into this list. // This is followed by the variable in which to store the item // After that we store the loopscript length followed by // the loopscript itself. // (Note that this puts a limit on the max. size of the // loopscript of 0x20 bytes) if (scriptsize > 0x20) { warning("Loopscript too long"); error = true; break; } uint8 *script = new uint8[scriptsize]; p->_stack.pop(script, scriptsize); uint32 stacksize = 0; bool recurse = false; // we'll put everything on the stack after stacksize is set UCList *itemlist = new UCList(2); World *world = World::get_instance(); switch (searchtype) { case 2: case 3: { // area search (3 = recursive) stacksize = GAME_IS_U8 ? 0x34 : 0x3A; if (searchtype == 3) recurse = true; // ui16a = item, ui16b = range const Item *item = getItem(ui16a); const uint16 range = GAME_IS_CRUSADER ? ui16b * 2 : ui16b; if (item) { Point3 pt = item->getLocationAbsolute(); world->getCurrentMap()->areaSearch(itemlist, script, scriptsize, nullptr, range, recurse, pt.x, pt.y); } else { // return error or return empty list? warning("Invalid item %u passed to area search", ui16a); } break; } case 4: case 5: { // container search (5 = recursive) stacksize = GAME_IS_U8 ? 0x28 : 0x2A; if (searchtype == 5) { stacksize += 2; recurse = true; } // ui16a = 0xFFFF (?), ui16b = container Container *container = getContainer(ui16b); if (ui16a != 0xFFFF) { warning("non-FFFF value passed to container search"); } if (container) { container->containerSearch(itemlist, script, scriptsize, recurse); } else { // return error or return empty list? warning("Invalid container %u passed to container search", ui16b); } break; } case 6: { // Surface search stacksize = GAME_IS_U8 ? 0x3D : 0x43; bool above = ui16a != 0xFFFF; bool below = ui16b != 0xFFFF; Item *item = getItem(below ? ui16b : ui16a); if (item) { world->getCurrentMap()->surfaceSearch(itemlist, script, scriptsize, item, above, below); } else { // return error or return empty list? warning("Invalid item passed to surface search"); } break; } default: warning("Unhandled search type %u", searchtype); error = true; delete[] script; script = nullptr; break; } if (script != nullptr) { p->_stack.push0(stacksize - scriptsize - 8); // filler p->_stack.push(script, scriptsize); p->_stack.push2(scriptsize); p->_stack.push2(static_cast(si16a)); p->_stack.push2(0); uint16 itemlistID = assignList(itemlist); p->_stack.push2(itemlistID); delete[] script; TRACE_OP("%s\tloop\t\t%s %02X %02X", op_info, print_bp(si16a), scriptsize, searchtype); } } // Intentional fall-through // 0x71 SEARCH_RECURSIVE (Unused) // 0x72 SEARCH_SURFACE (Unused) case 0x73: { // 73 // next loop object. pushes false if end reached unsigned int sp = p->_stack.getSP(); uint16 itemlistID = p->_stack.access2(sp); UCList *itemlist = getList(itemlistID); uint16 index = p->_stack.access2(sp + 2); si16a = static_cast(p->_stack.access2(sp + 4)); if (!itemlist) { warning("Invalid item list in loopnext"); error = true; break; } // see if there are still valid items left bool valid = false; do { if (index >= itemlist->getSize()) { break; } p->_stack.assign(p->_bp + si16a, (*itemlist)[index], 2); uint16 objid = p->_stack.access2(p->_bp + si16a); Item *item = getItem(objid); if (item) { valid = true; } if (!valid) index++; } while (!valid); if (!valid) { p->_stack.push2(0); // end of loop freeList(itemlistID); } else { p->_stack.push2(1); // increment index p->_stack.assign2(sp + 2, index + 1); } if (opcode == 0x73) { // because of the fall-through TRACE_OP("%s\tloopnext", op_info); } break; } case 0x74: // 74 xx // add xx to the current 'loopscript' ui8a = cs->readByte(); p->_stack.push1(ui8a); TRACE_OP("%s\tloopscr\t\t%02X \"%c\"", op_info, ui8a, static_cast(ui8a)); break; case 0x75: case 0x76: // 75 xx yy zz zz (foreach list) // 76 xx yy zz zz (foreach string list) // xx is the stack offset to store 'current' value from the list // (BP+xx) // yy is the 'datasize' of the list, identical to the second parameter // of the create list/slist opcodes // zzzz is the offset to jump to after it's finished iteration // (the opcode before is always a 'jmp' to the start of the loop) // 2 16 bit values are on the stack and left there during each // iteration: // - loop index (always starts at 0xffff), updated each iteration // - list id // 75 is for lists, 76 for slists // The only difference should be in the freeing afterwards. // Strings are _not_ duplicated when putting them in the loopvar // Lists _are_ freed afterwards si8a = cs->readByte(); // loop variable ui32a = cs->readByte(); // list size si16a = cs->readUint16LE(); // jump offset ui16a = p->_stack.access2(p->_stack.getSP()); // Loop index ui16b = p->_stack.access2(p->_stack.getSP() + 2); // Loop list if (opcode == 0x76 && ui32a != 2) { error = true; } if (opcode == 0x75) { TRACE_OP("%s\tfor each\t%s (%02X) %04hX", op_info, print_bp(si8a), ui32a, si16a); } else { TRACE_OP("%s\tfor each str\t%s (%02X) %04hX", op_info, print_bp(si8a), ui32a, si16a); } // Increment the counter if (ui16a == 0xFFFF) ui16a = 0; else ui16a++; if (ui16a >= getList(ui16b)->getSize()) { // loop done // free loop list if (opcode == 0x75) { freeList(ui16b); } else { freeStringList(ui16b); } p->_stack.addSP(4); // Pop list and counter // jump out ui16a = cs->pos() + si16a; cs->seek(ui16a); } else { // loop iteration // (not duplicating any strings) // updated loop index p->_stack.assign2(p->_stack.getSP(), ui16a); // place next element from list in [bp+si8a] p->_stack.assign(p->_bp + si8a, (*getList(ui16b))[ui16a], ui32a); } break; case 0x77: // 77 // set info // assigns item number and ProcessType p->setItemNum(p->_stack.pop2()); p->setType(p->_stack.pop2()); TRACE_OP("%s\tset info itemno: %d type: %d", op_info, p->getItemNum(), p->getType()); break; case 0x78: // 78 // process exclude // process gets 'exclusive access' to this (object,type) // Educated guess: // Check if any other processes have the same (object,type) info // set. If so, return from process. if (Kernel::get_instance()-> getNumProcesses(p->_itemNum, p->_type) > 1) { // another process with this (object,type) is already running p->terminateDeferred(); TRACE_OP("%s\tprocess exclude\t(terminating)", op_info); } else { TRACE_OP("%s\tprocess exclude", op_info); } break; case 0x79: // 79 // push address of global (Crusader only) ui16a = cs->readUint16LE(); // global address ui32a = globalToPtr(ui16a); p->_stack.push4(ui32a); TRACE_OP("%s\tpush global 0x%x (value: %x)", op_info, ui16a, ui32a); break; case 0x7A: // 7A // end of function // shouldn't happen TRACE_OP("%s\tend", op_info); warning("end of function opcode %02X reached", opcode); error = true; break; // 0x7B REGRESS (Unused) default: warning("unhandled opcode %02X", opcode); } // switch(opcode) // write back IP (but preserve IP if there was an error) if (!error) p->_ip = static_cast(cs->pos()); // TRUNCATES! // check if we suspended ourselves if ((p->_flags & Process::PROC_SUSPENDED) != 0 && !go_until_cede) cede = true; } // while(!cede && !error && !p->terminated && !p->terminate_deferred) delete cs; if (error) { warning("Process %d caused an error at %04X:%04X (item %d). Killing process.", p->_pid, p->_classId, p->_ip, p->_itemNum); p->terminateDeferred(); } } const Std::string &UCMachine::getString(uint16 str) const { static const Std::string emptystring(""); Common::HashMap::const_iterator iter = _stringHeap.find(str); if (iter != _stringHeap.end()) return iter->_value; return emptystring; } UCList *UCMachine::getList(uint16 l) { Common::HashMap::iterator iter = _listHeap.find(l); if (iter != _listHeap.end()) return iter->_value; return nullptr; } uint16 UCMachine::assignString(const char *str) { uint16 id = _stringIDs->getNewID(); if (id == 0) return 0; _stringHeap[id] = str; return id; } uint16 UCMachine::duplicateString(uint16 str) { return assignString(_stringHeap[str].c_str()); } uint16 UCMachine::assignList(UCList *l) { uint16 id = _listIDs->getNewID(); if (id == 0) return 0; assert(_listHeap.find(id) == _listHeap.end()); _listHeap[id] = l; return id; } void UCMachine::freeString(uint16 s) { //! There's still a semi-bug in some places that string 0 can be assigned //! (when something accesses _stringHeap[0]) //! This may not be desirable, but OTOH the created string will be //! empty, so not too much of a problem. Common::HashMap::iterator iter = _stringHeap.find(s); if (iter != _stringHeap.end()) { _stringHeap.erase(iter); _stringIDs->clearID(s); } } void UCMachine::freeList(uint16 l) { Common::HashMap::iterator iter = _listHeap.find(l); if (iter != _listHeap.end() && iter->_value) { iter->_value->free(); delete iter->_value; _listHeap.erase(iter); _listIDs->clearID(l); } } void UCMachine::freeStringList(uint16 l) { Common::HashMap::iterator iter = _listHeap.find(l); if (iter != _listHeap.end() && iter->_value) { iter->_value->freeStrings(); delete iter->_value; _listHeap.erase(iter); _listIDs->clearID(l); } } //static uint32 UCMachine::listToPtr(uint16 l) { uint32 ptr = SEG_LIST; ptr <<= 16; ptr += l; return ptr; } //static uint32 UCMachine::stringToPtr(uint16 s) { uint32 ptr = SEG_STRING; ptr <<= 16; ptr += s; return ptr; } //static uint32 UCMachine::stackToPtr(uint16 _pid, uint16 offset) { uint32 ptr = SEG_STACK + _pid; ptr <<= 16; ptr += offset; return ptr; } //static uint32 UCMachine::globalToPtr(uint16 offset) { uint32 ptr = SEG_GLOBAL; ptr <<= 16; ptr += offset; return ptr; } //static uint32 UCMachine::objectToPtr(uint16 objID) { uint32 ptr = SEG_OBJ; ptr <<= 16; ptr += objID; return ptr; } bool UCMachine::assignPointer(uint32 ptr, const uint8 *data, uint32 size) { // Only implemented the following: // * stack pointers // * global pointers //! range checking... uint16 segment = static_cast(ptr >> 16); uint16 offset = static_cast(ptr & 0xFFFF); if (segment >= SEG_STACK_FIRST && segment <= SEG_STACK_LAST) { UCProcess *proc = dynamic_cast (Kernel::get_instance()->getProcess(segment)); // reference to the stack of _pid 'segment' if (!proc) { // segfault :-) warning("Trying to access stack of non-existent process (pid: %u)", segment); return false; } else { proc->_stack.assign(offset, data, size); } } else if (segment == SEG_GLOBAL) { if (!GAME_IS_CRUSADER) warning("Global pointers not supported in U8"); if (size == 1) { _globals->setEntries(offset, 1, data[0]); } else if (size == 2) { uint16 val = ((data[1] << 8) | data[0]); _globals->setEntries(offset, 2, val); } else { warning("Global pointers must be size 1 or 2"); } } else { warning("Trying to access segment %04X", segment); return false; } return true; } bool UCMachine::dereferencePointer(uint32 ptr, uint8 *data, uint32 size) { // this one is a bit tricky. There's no way we can support // all possible pointers, so we're just going to do a few: // * stack pointers // * object pointers, as long as xx == 02. (i.e., get objref) // * global pointers //! range checking... uint16 segment = static_cast(ptr >> 16); uint16 offset = static_cast(ptr & 0xFFFF); if (segment >= SEG_STACK_FIRST && segment <= SEG_STACK_LAST) { UCProcess *proc = dynamic_cast (Kernel::get_instance()->getProcess(segment)); // reference to the stack of _pid 'segment' if (!proc) { // segfault :-) warning("Trying to access stack of non-existent process (pid: %u)", segment); return false; } else { memcpy(data, proc->_stack.access(offset), size); } } else if (segment == SEG_OBJ) { if (size != 2) { warning("Trying to read other than 2 bytes from objptr"); return false; } else { // push objref data[0] = static_cast(offset); data[1] = static_cast(offset >> 8); } } else if (segment == SEG_GLOBAL) { if (!GAME_IS_CRUSADER) warning("Global pointers not supported in U8"); if (size == 1) { data[0] = static_cast(_globals->getEntries(offset, 1)); } else if (size == 2) { uint16 val = _globals->getEntries(offset, 2); data[0] = static_cast(val); data[1] = static_cast(val >> 8); } else { warning("Global pointers must be size 1 or 2"); } } else { warning("Trying to access segment %04X", segment); return false; } return true; } //static uint16 UCMachine::ptrToObject(uint32 ptr) { //! This function is a bit of a misnomer, since it's more general than this uint16 segment = static_cast(ptr >> 16); uint16 offset = static_cast(ptr); if (segment >= SEG_STACK_FIRST && segment <= SEG_STACK_LAST) { UCProcess *proc = dynamic_cast (Kernel::get_instance()->getProcess(segment)); // reference to the stack of _pid 'segment' if (!proc) { // segfault :-) warning("Trying to access stack of non-existent process (pid: %u)", segment); return 0; } else if (proc->_stack.getSize() < (uint32)offset + 2) { warning("Trying to access past end of stack offset %u (size: %u) process (pid: %u)", offset, proc->_stack.getSize(), segment); return 0; } else { return proc->_stack.access2(offset); } } else if (segment == SEG_OBJ || segment == SEG_STRING) { return offset; } else if (segment == SEG_GLOBAL) { return get_instance()->_globals->getEntries(offset, 2); } else { warning("Trying to access segment %04X", segment); return 0; } } void UCMachine::usecodeStats() const { g_debugger->debugPrintf("Usecode Machine memory stats:\n"); g_debugger->debugPrintf("Strings : %u/65534\n", _stringHeap.size()); #ifdef DUMPHEAP for (const auto &i : _stringHeap) g_debugger->debugPrintf("%d:%s\n", i._key << ":" << i._value.c_str()); #endif g_debugger->debugPrintf("Lists : %u/65534\n", _listHeap.size()); #ifdef DUMPHEAP for (const auto &l : _listHeap) { if (!l._value) { g_debugger->debugPrintf("%d: \n", l._key); continue; } if (l._value->getElementSize() == 2) { g_debugger->debugPrintf("%d:", l._key); for (unsigned int i = 0; i < l._value->getSize(); ++i) { if (i > 0) g_debugger->debugPrintf(","); g_debugger->debugPrintf("%d", l._value->getuint16(i)); } g_debugger->debugPrintf("\n"); } else { g_debugger->debugPrintf("%d: %u elements of size %u\n", l._key, l._value->getSize(), l._value->getElementSize()); } } #endif } void UCMachine::saveGlobals(Common::WriteStream *ws) const { _globals->save(ws); } void UCMachine::saveStrings(Common::WriteStream *ws) const { _stringIDs->save(ws); ws->writeUint32LE(static_cast(_stringHeap.size())); for (const auto &i : _stringHeap) { ws->writeUint16LE(i._key); ws->writeUint32LE(i._value.size()); ws->write(i._value.c_str(), i._value.size()); } } void UCMachine::saveLists(Common::WriteStream *ws) const { _listIDs->save(ws); ws->writeUint32LE(_listHeap.size()); for (const auto &i : _listHeap) { ws->writeUint16LE(i._key); i._value->save(ws); } } bool UCMachine::loadGlobals(Common::ReadStream *rs, uint32 version) { return _globals->load(rs, version); } bool UCMachine::loadStrings(Common::ReadStream *rs, uint32 version) { if (!_stringIDs->load(rs, version)) return false; uint32 stringcount = rs->readUint32LE(); for (unsigned int i = 0; i < stringcount; ++i) { uint16 sid = rs->readUint16LE(); uint32 len = rs->readUint32LE(); if (len) { char *buf = new char[len + 1]; rs->read(buf, len); buf[len] = 0; _stringHeap[sid] = buf; delete[] buf; } else { _stringHeap[sid] = ""; } } return true; } bool UCMachine::loadLists(Common::ReadStream *rs, uint32 version) { if (!_listIDs->load(rs, version)) return false; uint32 listcount = rs->readUint32LE(); if (listcount > 65536) { warning("Improbable number of UC lists %d in save, corrupt save?", listcount); return false; } for (unsigned int i = 0; i < listcount; ++i) { uint16 lid = rs->readUint16LE(); UCList *l = new UCList(2); // the "2" will be ignored by load() bool ret = l->load(rs, version); if (!ret) { delete l; return false; } _listHeap[lid] = l; } return true; } uint32 UCMachine::I_true(const uint8 * /*args*/, unsigned int /*argsize*/) { return 1; } uint32 UCMachine::I_false(const uint8 * /*args*/, unsigned int /*argsize*/) { return 1; } uint32 UCMachine::I_dummyProcess(const uint8 * /*args*/, unsigned int /*argsize*/) { return Kernel::get_instance()->addProcess(new DelayProcess(4)); } uint32 UCMachine::I_getName(const uint8 * /*args*/, unsigned int /*argsize*/) { UCMachine *uc = UCMachine::get_instance(); MainActor *av = getMainActor(); // Note: assignString takes a copy return uc->assignString(av->getName().c_str()); } uint32 UCMachine::I_numToStr(const uint8 *args, unsigned int /*argsize*/) { ARG_SINT16(num); char buf[16]; // a 16 bit int should easily fit snprintf(buf, 16, "%d", num); return UCMachine::get_instance()->assignString(buf); } uint32 UCMachine::I_urandom(const uint8 *args, unsigned int /*argsize*/) { ARG_UINT16(num); if (num <= 1) return 0; // return random integer between 0 (incl.) to num (excl.) Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource(); return rs.getRandomNumber(num - 1); } uint32 UCMachine::I_rndRange(const uint8 *args, unsigned int /*argsize*/) { ARG_SINT16(lo); ARG_SINT16(hi); // return random integer between lo (incl.) to hi (incl.) if (hi <= lo) return lo; Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource(); return rs.getRandomNumberRng(lo, hi); } } // End of namespace Ultima8 } // End of namespace Ultima