Files
scummvm-cursorfix/engines/agds/process.cpp
2026-02-02 04:50:13 +01:00

535 lines
14 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<int32>(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