/* 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 "agds/process.h" #include "agds/agds.h" #include "agds/animation.h" #include "common/debug.h" #include "common/system.h" namespace AGDS { Process::Process(AGDSEngine *engine, const ObjectPtr &object, unsigned ip, int version) : _engine(engine), _parentScreen(engine->getCurrentScreenName()), _object(object), _entryPoint(ip), _ip(ip), _lastIp(ip), _status(kStatusActive), _exited(false), _exitCode(kExitCodeDestroy), _tileWidth(16), _tileHeight(16), _tileResource(-1), _tileIndex(0), _timer(0), _animationCycles(1), _animationLoop(false), _animationZ(0), _animationDelay(-1), _animationRandom(0), _phaseVarControlled(false), _animationSpeed(100), _samplePeriodic(false), _sampleAmbient(false), _sampleVolume(100), _filmSubtitlesResource(-1), _version(version) { updateWithCurrentMousePosition(); } void Process::debug(const char *str, ...) { va_list va; va_start(va, str); Common::String format = Common::String::format("%s %04x[%u]: %s", _object->getName().c_str(), _lastIp, _stack.size(), str); Common::String buf = Common::String::vformat(format.c_str(), va); buf += '\n'; if (g_system) g_system->logMessage(LogMessageType::kDebug, buf.c_str()); va_end(va); } // fixme: remove copy-paste void Process::error(const char *str, ...) { va_list va; va_start(va, str); Common::String format = Common::String::format("WARNING: %s %04x[%u]: %s", _object->getName().c_str(), _lastIp + 7, _stack.size(), str); Common::String buf = Common::String::vformat(format.c_str(), va); buf += '\n'; if (g_system) g_system->logMessage(LogMessageType::kWarning, buf.c_str()); va_end(va); _status = kStatusError; } void Process::suspend(ProcessExitCode exitCode, const Common::String &arg1, const Common::String &arg2) { debug("suspend %d", exitCode); _exited = true; _exitCode = exitCode; _exitIntArg1 = 0; _exitIntArg2 = 0; _exitArg1 = arg1; _exitArg2 = arg2; } void Process::suspend(ProcessExitCode exitCode, int arg1, int arg2) { debug("suspend %d", exitCode); _exited = true; _exitCode = exitCode; _exitIntArg1 = arg1; _exitIntArg2 = arg2; _exitArg1.clear(); _exitArg2.clear(); } uint8 Process::next() { const Object::CodeType &code = _object->getCode(); if (_ip < code.size()) { return code[_ip++]; } else { _status = kStatusError; return 0; } } void Process::jump(int16 delta) { _ip += delta; } void Process::jumpz(int16 delta) { int value = pop(); if (value == 0) { debug("jumpz %d %+d", value, delta); _ip += delta; } else debug("jumpz ignored %d %+d", value, delta); } void Process::suspend() { suspend(kExitCodeSuspend); } void Process::suspendIfPassive() { if (passive()) suspend(); } int32 Process::pop() { if (_stack.empty()) { error("stack underflow at %s:%04x", _object->getName().c_str(), _lastIp + 7); return 0; } return _stack.pop(); } int32 Process::top() { if (_stack.empty()) { error("stack underflow, %s:%04x", _object->getName().c_str(), _lastIp + 7); return 0; } return _stack.top(); } Common::String Process::getString(int id) { if (id == -1) return Common::String(); else if (id <= -2 && id > -12) return _engine->getSharedStorage(id); else return _object->getString(id).string; } Common::String Process::popText() { return _engine->loadText(popString()); } uint32 Process::popColor() { int b = pop(); int g = pop(); int r = pop(); return _engine->pixelFormat().RGBToColor(r, g, b); } void Process::updateWithCurrentMousePosition() { setMousePosition(_engine->mousePosition()); } void Process::setupAnimation(const AnimationPtr &animation) { animation->position(_animationPosition); animation->z(_animationZ); animation->process(getName()); animation->phaseVar(_phaseVar); animation->loop(_animationLoop); animation->cycles(_animationCycles); animation->delay(_animationDelay); animation->setRandom(_animationRandom); animation->phaseVarControlled(_phaseVarControlled); if (_phaseVar.empty()) suspend(); else if (_phaseVarControlled) _engine->setGlobal(_phaseVar, 0); } void Process::removeProcessAnimation() { if (_processAnimation) { auto *screen = _engine->getCurrentScreen(); if (screen) screen->remove(_processAnimation); _processAnimation = nullptr; } } void Process::activate() { switch (status()) { case kStatusPassive: _status = Process::kStatusActive; break; case kStatusDone: case kStatusError: debug("finished"); break; default: break; } } void Process::deactivate() { switch (status()) { case kStatusActive: debug("deactivated"); _status = Process::kStatusPassive; break; case kStatusDone: case kStatusError: break; default: break; } } void Process::done() { if (_status != kStatusDone) { debug("done"); _status = kStatusDone; if (!_stack.empty()) warning("process %s finished with non-empty stack", getName().c_str()); } } void Process::fail() { _status = kStatusError; } void Process::run() { bool restart = true; while (_status != kStatusDone && _status != kStatusError && restart) { restart = false; ProcessExitCode code = resume(); switch (code) { case kExitCodeDestroy: debug("process %s returned destroy exit code", getName().c_str()); done(); if (!getObject()->alive() && !_engine->hasActiveProcesses(getName())) { removeScreenObject(getName()); } break; case kExitCodeLoadScreenObjectAs: case kExitCodeLoadScreenObject: deactivate(); _object->lock(); _engine->runObject(getExitArg1(), getExitArg2()); _object->unlock(); activate(); restart = true; break; case kExitCodeRunDialog: deactivate(); _object->lock(); _engine->runObject(getExitArg1()); _object->unlock(); break; case kExitCodeSetNextScreen: case kExitCodeSetNextScreenSaveOrLoad: done(); debug("process %s launches screen: %s, saveorload: ", getName().c_str(), getExitArg1().c_str(), code == kExitCodeSetNextScreenSaveOrLoad); _engine->setNextScreenName(getExitArg1(), code == kExitCodeSetNextScreenSaveOrLoad ? ScreenLoadingType::SaveOrLoad : ScreenLoadingType::Normal); break; case kExitCodeMouseAreaChange: deactivate(); _object->lock(); _engine->changeMouseArea(getExitIntArg1(), getExitIntArg2()); _object->unlock(); activate(); restart = true; break; case kExitCodeLoadInventoryObject: deactivate(); _object->lock(); _engine->inventory().add(getExitArg1()); _object->unlock(); activate(); restart = true; break; case kExitCodeCloseInventory: _object->lock(); _engine->inventory().enable(false); updateWithCurrentMousePosition(); _object->unlock(); break; case kExitCodeSuspend: updateWithCurrentMousePosition(); break; case kExitCodeNewGame: deactivate(); _object->lock(); debug("exitProcessNewGame"); _engine->newGame(); _object->unlock(); activate(); restart = true; break; case kExitCodeLoadGame: deactivate(); _object->lock(); if (_engine->loadGameState(getExitIntArg1()).getCode() == Common::kNoError) { done(); } else { debug("save loading failed, resuming execution..."); } _object->unlock(); activate(); restart = true; break; case kExitCodeSaveGame: break; default: error("unknown process exit code %d", code); } } } #define UNARY_OP(NAME, OP) \ void Process::NAME() { \ int32 arg = pop(); \ debug(#NAME " %d", arg); \ push(OP arg); \ } #define BINARY_OP(NAME, OP) \ void Process::NAME() { \ int32 arg2 = pop(); \ int32 arg1 = pop(); \ debug(" %d " #NAME " %d", arg1, arg2); \ push(arg1 OP arg2); \ } UNARY_OP(boolNot, !) UNARY_OP(bitNot, ~) UNARY_OP(negate, -) BINARY_OP(shl, <<) BINARY_OP(shr, >>) BINARY_OP(boolOr, ||) BINARY_OP(boolAnd, &&) BINARY_OP(equals, ==) BINARY_OP(notEquals, !=) BINARY_OP(greater, >) BINARY_OP(greaterOrEquals, >=) BINARY_OP(less, <) BINARY_OP(lessOrEquals, <=) BINARY_OP(add, +) BINARY_OP(sub, -) BINARY_OP(mul, *) BINARY_OP(div, /) BINARY_OP(mod, %) BINARY_OP(bitAnd, &) BINARY_OP(bitOr, |) BINARY_OP(bitXor, ^) #undef UNARY_OP #undef BINARY_OP // fixme: add trace here #define AGDS_OP(NAME, METHOD) \ case NAME: \ METHOD(); \ break; #define AGDS_OP_C(NAME, METHOD) \ case NAME: { \ int8 arg = next(); \ METHOD(arg); \ } break; #define AGDS_OP_B(NAME, METHOD) \ case NAME: { \ uint8 arg = next(); \ METHOD(arg); \ } break; #define AGDS_OP_W(NAME, METHOD) \ case NAME: { \ int16 arg = next16(); \ METHOD(arg); \ } break; #define AGDS_OP_U(NAME, METHOD) \ case NAME: { \ uint16 arg = next16(); \ METHOD(arg); \ } break; #define AGDS_OP_UU(NAME, METHOD) \ case NAME: { \ uint16 arg1 = next16(); \ uint16 arg2 = next16(); \ METHOD(arg1, arg2); \ } break; #define AGDS_OP_UD(NAME, METHOD) \ case NAME: { \ uint16 arg1 = next16(); \ uint32 arg2 = next16(); \ METHOD(static_cast(arg1 | (arg2 << 16))); \ } break; uint16 Process::nextOpcode() { uint16 op = next(); switch (_version) { case 0: return op + 5; case 2: if (op & 1) { op |= next() << 8; op >>= 1; } return op - 7995; default: return op; } } ProcessExitCode Process::resume() { _exitCode = kExitCodeDestroy; if (_timer) { --_timer; return kExitCodeSuspend; } const Object::CodeType &code = _object->getCode(); while (!_exited && _ip < code.size()) { if (_timer) { return kExitCodeSuspend; } _lastIp = _ip; auto op = nextOpcode(); // debug("CODE %04x: %u", _lastIp, (uint)op); switch (op) { AGDS_OPCODE_LIST( AGDS_OP, AGDS_OP_C, AGDS_OP_B, AGDS_OP_W, AGDS_OP_U, AGDS_OP_UD, AGDS_OP_UU) default: error("%s: %08x: unknown opcode 0x%02x (%u)", _object->getName().c_str(), _lastIp, (unsigned)op, (unsigned)op); fail(); break; } } _exited = false; return _exitCode; } #define AGDS_DIS(NAME, METHOD) \ case NAME: \ source += Common::String::format("%s\n", #NAME); \ break; #define AGDS_DIS_C(NAME, METHOD) \ case NAME: { \ int8 arg = code[ip++]; \ source += Common::String::format("%s %d\n", #NAME, (int)arg); \ } break; #define AGDS_DIS_B(NAME, METHOD) \ case NAME: { \ uint8 arg = code[ip++]; \ source += Common::String::format("%s %u\n", #NAME, (uint)arg); \ } break; #define AGDS_DIS_W(NAME, METHOD) \ case NAME: { \ int16 arg = code[ip++]; \ arg |= ((int16)code[ip++]) << 8; \ source += Common::String::format("%s %d\n", #NAME, (int)arg); \ } break; #define AGDS_DIS_U(NAME, METHOD) \ case NAME: { \ uint16 arg = code[ip++]; \ arg |= ((uint16)code[ip++]) << 8; \ source += Common::String::format("%s %u\n", #NAME, (uint)arg); \ } break; #define AGDS_DIS_UU(NAME, METHOD) \ case NAME: { \ uint16 arg1 = code[ip++]; \ arg1 |= ((uint16)code[ip++]) << 8; \ uint16 arg2 = code[ip++]; \ arg2 |= ((uint16)code[ip++]) << 8; \ source += Common::String::format("%s %u %u\n", #NAME, (uint)arg1, (uint)arg2); \ } break; #define AGDS_DIS_UD(NAME, METHOD) \ case NAME: { \ uint16 arg1 = code[ip++]; \ arg1 |= ((uint16)code[ip++]) << 8; \ uint16 arg2 = code[ip++]; \ arg2 |= ((uint16)code[ip++]) << 8; \ source += Common::String::format("%s %u\n", #NAME, (uint)(arg1 | (arg2 << 16))); \ } break; Common::String Process::disassemble(const ObjectPtr &object, int version) { Common::String source = Common::String::format("Object %s disassembly:\n", object->getName().c_str()); const auto &code = object->getCode(); uint ip = 0; while (ip < code.size()) { uint16 op = code[ip++]; if (version == 2) { if (op & 1) { op |= code[ip++] << 8; op >>= 1; } } else if (version == 0) { op += 5; } source += Common::String::format("%04x: %02x: ", ip - 1, op); switch (op) { AGDS_OPCODE_LIST( AGDS_DIS, AGDS_DIS_C, AGDS_DIS_B, AGDS_DIS_W, AGDS_DIS_U, AGDS_DIS_UD, AGDS_DIS_UU) default: source += Common::String::format("unknown opcode 0x%02x (%u)\n", (unsigned)op, (unsigned)op); break; } } source += Common::String::format("Object %s disassembly end\n", object->getName().c_str()); return source; } } // namespace AGDS