diff options
author | Eugene Sandulenko | 2016-07-24 23:03:19 +0300 |
---|---|---|
committer | GitHub | 2016-07-24 23:03:19 +0300 |
commit | 5f6c79db0335e0a9c3df49cf306e7305b3804cbc (patch) | |
tree | ea543fc6ab56219792c238f35fa3776509394697 | |
parent | bed2141369bad43c567364209d3f085824787f28 (diff) | |
parent | 3724918626bcdb0fc8a177cead9fe35b5530ca60 (diff) | |
download | scummvm-rg350-5f6c79db0335e0a9c3df49cf306e7305b3804cbc.tar.gz scummvm-rg350-5f6c79db0335e0a9c3df49cf306e7305b3804cbc.tar.bz2 scummvm-rg350-5f6c79db0335e0a9c3df49cf306e7305b3804cbc.zip |
Merge pull request #687 from tobiatesan/wme_debugger_rewrite
WME: Debugger for WME, 2016 rewrite
60 files changed, 2702 insertions, 49 deletions
diff --git a/engines/wintermute/ad/ad_entity.cpp b/engines/wintermute/ad/ad_entity.cpp index 1bbadeb7f7..0909d7ef91 100644 --- a/engines/wintermute/ad/ad_entity.cpp +++ b/engines/wintermute/ad/ad_entity.cpp @@ -1134,4 +1134,7 @@ bool AdEntity::setSprite(const char *filename) { } } +Common::String AdEntity::debuggerToString() const { + return Common::String::format("%p: Entity \"%s\"; (X,Y): (%d, %d), rotate(%d): %f deg, scale(%d): (%f, %f)%%", (const void *)this, getName(), _posX, _posY, _rotatable, _rotate, _zoomable, _scaleX, _scaleY); +} } // End of namespace Wintermute diff --git a/engines/wintermute/ad/ad_entity.h b/engines/wintermute/ad/ad_entity.h index 7e1525b7c1..678608af36 100644 --- a/engines/wintermute/ad/ad_entity.h +++ b/engines/wintermute/ad/ad_entity.h @@ -60,6 +60,7 @@ public: virtual bool scSetProperty(const char *name, ScValue *value) override; virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) override; virtual const char *scToString() override; + Common::String debuggerToString() const override; private: int32 _walkToX; int32 _walkToY; diff --git a/engines/wintermute/ad/ad_game.cpp b/engines/wintermute/ad/ad_game.cpp index df0328ce5e..088184b0f6 100644 --- a/engines/wintermute/ad/ad_game.cpp +++ b/engines/wintermute/ad/ad_game.cpp @@ -2280,4 +2280,7 @@ bool AdGame::onScriptShutdown(ScScript *script) { return STATUS_OK; } +Common::String AdGame::debuggerToString() const { + return Common::String::format("%p: Game \"%s\"", (const void *)this, getName()); +} } // End of namespace Wintermute diff --git a/engines/wintermute/ad/ad_game.h b/engines/wintermute/ad/ad_game.h index ebb37e9a07..0e5abc9b3b 100644 --- a/engines/wintermute/ad/ad_game.h +++ b/engines/wintermute/ad/ad_game.h @@ -130,6 +130,7 @@ public: virtual bool scSetProperty(const char *name, ScValue *value) override; virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) override; bool validMouse(); + Common::String debuggerToString() const override; private: virtual bool externalCall(ScScript *script, ScStack *stack, ScStack *thisStack, char *name) override; diff --git a/engines/wintermute/ad/ad_scene.cpp b/engines/wintermute/ad/ad_scene.cpp index 02a6aeb801..b57faef69b 100644 --- a/engines/wintermute/ad/ad_scene.cpp +++ b/engines/wintermute/ad/ad_scene.cpp @@ -2998,4 +2998,9 @@ bool AdScene::getRegionObjects(AdRegion *region, BaseArray<AdObject *> &objects, return STATUS_OK; } + +Common::String AdScene::debuggerToString() const { + return Common::String::format("%p: Scene \"%s\", paralax: %d, autoscroll: %d", (const void *)this, getName(), _paralaxScrolling, _autoScroll); +} } // End of namespace Wintermute + diff --git a/engines/wintermute/ad/ad_scene.h b/engines/wintermute/ad/ad_scene.h index 1ca52bdda9..71567d2475 100644 --- a/engines/wintermute/ad/ad_scene.h +++ b/engines/wintermute/ad/ad_scene.h @@ -160,7 +160,7 @@ public: virtual bool scSetProperty(const char *name, ScValue *value) override; virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) override; virtual const char *scToString() override; - + virtual Common::String debuggerToString() const override; private: bool persistState(bool saving = true); diff --git a/engines/wintermute/base/base_frame.cpp b/engines/wintermute/base/base_frame.cpp index 471185f2d2..910ab64a76 100644 --- a/engines/wintermute/base/base_frame.cpp +++ b/engines/wintermute/base/base_frame.cpp @@ -764,4 +764,7 @@ const char *BaseFrame::scToString() { return "[frame]"; } +Common::String BaseFrame::debuggerToString() const { + return Common::String::format("%p: Frame \"%s\": #subframes %d ", (const void *)this, getName(), _subframes.size()); +} } // End of namespace Wintermute diff --git a/engines/wintermute/base/base_frame.h b/engines/wintermute/base/base_frame.h index ff9e67a166..8d261c9e71 100644 --- a/engines/wintermute/base/base_frame.h +++ b/engines/wintermute/base/base_frame.h @@ -65,6 +65,8 @@ public: virtual bool scSetProperty(const char *name, ScValue *value) override; virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) override; virtual const char *scToString() override; + virtual Common::String debuggerToString() const override; + private: bool _keyframe; bool _editorExpanded; diff --git a/engines/wintermute/base/base_game.cpp b/engines/wintermute/base/base_game.cpp index 668053bb3a..ce4c5fdda5 100644 --- a/engines/wintermute/base/base_game.cpp +++ b/engines/wintermute/base/base_game.cpp @@ -71,6 +71,10 @@ #include "common/system.h" #include "common/file.h" +#if EXTENDED_DEBUGGER_ENABLED == true +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h" +#endif + namespace Wintermute { ////////////////////////////////////////////////////////////////////// @@ -398,7 +402,11 @@ bool BaseGame::initialize1() { break; } +#if EXTENDED_DEBUGGER_ENABLED == true + _scEngine = new DebuggableScEngine(this); +#else _scEngine = new ScEngine(this); +#endif if (_scEngine == nullptr) { break; } diff --git a/engines/wintermute/base/base_game.h b/engines/wintermute/base/base_game.h index e535cc9618..409cc20ba4 100644 --- a/engines/wintermute/base/base_game.h +++ b/engines/wintermute/base/base_game.h @@ -34,7 +34,11 @@ #include "engines/wintermute/persistent.h" #include "engines/wintermute/coll_templ.h" #include "engines/wintermute/math/rect32.h" +#include "engines/wintermute/debugger.h" #include "common/events.h" +#if EXTENDED_DEBUGGER_ENABLED == true +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h" +#endif namespace Wintermute { @@ -148,7 +152,11 @@ public: BaseRenderer *_renderer; BaseSoundMgr *_soundMgr; +#if EXTENDED_DEBUGGER_ENABLED == true + DebuggableScEngine *_scEngine; +#else ScEngine *_scEngine; +#endif BaseScriptable *_mathClass; BaseSurfaceStorage *_surfaceStorage; BaseFontStorage *_fontStorage; diff --git a/engines/wintermute/base/base_region.cpp b/engines/wintermute/base/base_region.cpp index 9a31f5cd66..02ab365eff 100644 --- a/engines/wintermute/base/base_region.cpp +++ b/engines/wintermute/base/base_region.cpp @@ -532,4 +532,7 @@ bool BaseRegion::mimic(BaseRegion *region, float scale, int x, int y) { return createRegion() ? STATUS_OK : STATUS_FAILED; } +Common::String BaseRegion::debuggerToString() const { + return Common::String::format("%p: Region \"%s\": Rect (top, right, bottom, left): (%d, %d, %d, %d), active: %d ", (const void *)this, getName(), _rect.top, _rect.right, _rect.bottom, _rect.left, _active); +} } // End of namespace Wintermute diff --git a/engines/wintermute/base/base_region.h b/engines/wintermute/base/base_region.h index fc3389c501..4cb5dd85d6 100644 --- a/engines/wintermute/base/base_region.h +++ b/engines/wintermute/base/base_region.h @@ -59,6 +59,8 @@ public: virtual bool scSetProperty(const char *name, ScValue *value) override; virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) override; virtual const char *scToString() override; + virtual Common::String debuggerToString() const override; + private: float _lastMimicScale; int32 _lastMimicX; diff --git a/engines/wintermute/base/base_script_holder.cpp b/engines/wintermute/base/base_script_holder.cpp index 5b1c961479..7427a9b082 100644 --- a/engines/wintermute/base/base_script_holder.cpp +++ b/engines/wintermute/base/base_script_holder.cpp @@ -42,7 +42,7 @@ IMPLEMENT_PERSISTENT(BaseScriptHolder, false) ////////////////////////////////////////////////////////////////////// BaseScriptHolder::BaseScriptHolder(BaseGame *inGame) : BaseScriptable(inGame) { setName("<unnamed>"); - + _ready = false; _freezable = true; _filename = nullptr; } @@ -312,7 +312,11 @@ bool BaseScriptHolder::addScript(const char *filename) { if (!scr) { if (_gameRef->_editorForceScripts) { // editor hack +#if EXTENDED_DEBUGGER_ENABLED + scr = new DebuggableScript(_gameRef, _gameRef->_scEngine); +#else scr = new ScScript(_gameRef, _gameRef->_scEngine); +#endif scr->_filename = new char[strlen(filename) + 1]; strcpy(scr->_filename, filename); scr->_state = SCRIPT_ERROR; @@ -462,8 +466,15 @@ void BaseScriptHolder::makeFreezable(bool freezable) { ScScript *BaseScriptHolder::invokeMethodThread(const char *methodName) { for (int i = _scripts.size() - 1; i >= 0; i--) { if (_scripts[i]->canHandleMethod(methodName)) { - +#if EXTENDED_DEBUGGER_ENABLED == true + DebuggableScEngine* debuggableEngine; + debuggableEngine = dynamic_cast<DebuggableScEngine*>(_scripts[i]->_engine); + // TODO: Not pretty + assert(debuggableEngine); + ScScript *thread = new DebuggableScript(_gameRef, debuggableEngine); +#else ScScript *thread = new ScScript(_gameRef, _scripts[i]->_engine); +#endif if (thread) { bool ret = thread->createMethodThread(_scripts[i], methodName); if (DID_SUCCEED(ret)) { diff --git a/engines/wintermute/base/base_scriptable.cpp b/engines/wintermute/base/base_scriptable.cpp index c65d30d941..01f6f9e02f 100644 --- a/engines/wintermute/base/base_scriptable.cpp +++ b/engines/wintermute/base/base_scriptable.cpp @@ -188,4 +188,9 @@ ScScript *BaseScriptable::invokeMethodThread(const char *methodName) { return nullptr; } +Common::String BaseScriptable::debuggerToString() const { + return Common::String::format("%p: BaseScriptable %s", (const void *)this, getName()); +} + + } // End of namespace Wintermute diff --git a/engines/wintermute/base/base_scriptable.h b/engines/wintermute/base/base_scriptable.h index b32668d6c8..7b4f269871 100644 --- a/engines/wintermute/base/base_scriptable.h +++ b/engines/wintermute/base/base_scriptable.h @@ -63,6 +63,7 @@ public: virtual void scSetBool(bool val); virtual int scCompare(BaseScriptable *val); virtual void scDebuggerDesc(char *buf, int bufSize); + virtual Common::String debuggerToString() const; int32 _refCount; ScValue *_scValue; ScValue *_scProp; diff --git a/engines/wintermute/base/base_sprite.cpp b/engines/wintermute/base/base_sprite.cpp index 09e138a1fd..f282004a59 100644 --- a/engines/wintermute/base/base_sprite.cpp +++ b/engines/wintermute/base/base_sprite.cpp @@ -826,4 +826,7 @@ bool BaseSprite::killAllSounds() { return STATUS_OK; } +Common::String BaseSprite::debuggerToString() const { + return Common::String::format("%p: Sprite \"%s\"", (const void *)this, getName()); +} } // End of namespace Wintermute diff --git a/engines/wintermute/base/base_sprite.h b/engines/wintermute/base/base_sprite.h index ec71512ec9..2313b7b3dc 100644 --- a/engines/wintermute/base/base_sprite.h +++ b/engines/wintermute/base/base_sprite.h @@ -69,6 +69,7 @@ public: virtual bool scSetProperty(const char *name, ScValue *value) override; virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) override; virtual const char *scToString() override; + Common::String debuggerToString() const override; private: BaseObject *_owner; bool _canBreak; diff --git a/engines/wintermute/base/base_sub_frame.cpp b/engines/wintermute/base/base_sub_frame.cpp index 6d0c48ff17..8068e61168 100644 --- a/engines/wintermute/base/base_sub_frame.cpp +++ b/engines/wintermute/base/base_sub_frame.cpp @@ -673,4 +673,8 @@ bool BaseSubFrame::setSurfaceSimple() { } } +Common::String BaseSubFrame::debuggerToString() const { + return Common::String::format("%p: BaseSubFrame \"%s\" - Mirror:(%d, %d), Hotspot:(%d, %d), ", (const void *)this, getName(), _mirrorX, _mirrorY, _hotspotX, _hotspotY); +} + } // End of namespace Wintermute diff --git a/engines/wintermute/base/base_sub_frame.h b/engines/wintermute/base/base_sub_frame.h index f156c332d6..0fd38f9548 100644 --- a/engines/wintermute/base/base_sub_frame.h +++ b/engines/wintermute/base/base_sub_frame.h @@ -86,6 +86,7 @@ public: virtual bool scSetProperty(const char *name, ScValue *value); virtual bool scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name); virtual const char *scToString(); + Common::String debuggerToString() const override; }; diff --git a/engines/wintermute/base/base_viewport.cpp b/engines/wintermute/base/base_viewport.cpp index bf3700a14e..aed0355eb9 100644 --- a/engines/wintermute/base/base_viewport.cpp +++ b/engines/wintermute/base/base_viewport.cpp @@ -96,4 +96,7 @@ int BaseViewport::getHeight() const { return _rect.bottom - _rect.top; } +Common::String BaseViewport::debuggerToString() const { + return Common::String::format("%p: BaseViewport: (top, right, bottom, left): (%d, %d, %d, %d)", (const void *)this, _rect.top, _rect.right, _rect.bottom, _rect.left); +} } // End of namespace Wintermute diff --git a/engines/wintermute/base/base_viewport.h b/engines/wintermute/base/base_viewport.h index eae756f9c6..d8f1ed117f 100644 --- a/engines/wintermute/base/base_viewport.h +++ b/engines/wintermute/base/base_viewport.h @@ -48,6 +48,7 @@ public: BaseObject *_mainObject; BaseViewport(BaseGame *inGame = nullptr); virtual ~BaseViewport(); + virtual Common::String debuggerToString() const override; private: Rect32 _rect; }; diff --git a/engines/wintermute/base/scriptables/debuggable/debuggable_script.cpp b/engines/wintermute/base/scriptables/debuggable/debuggable_script.cpp new file mode 100644 index 0000000000..5a2291894f --- /dev/null +++ b/engines/wintermute/base/scriptables/debuggable/debuggable_script.cpp @@ -0,0 +1,148 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/tokenizer.h" +#include "debuggable_script.h" +#include "engines/wintermute/base/scriptables/script_stack.h" +#include "engines/wintermute/base/scriptables/script_value.h" +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h" +#include "engines/wintermute/debugger/breakpoint.h" +#include "engines/wintermute/debugger/script_monitor.h" +#include "engines/wintermute/debugger/watch_instance.h" + +namespace Wintermute { + +DebuggableScript::DebuggableScript(BaseGame *inGame, DebuggableScEngine *engine) : ScScript(inGame, engine), _engine(engine), _stepDepth(kDefaultStepDepth) { + _engine->_watches.subscribe(this); + for (uint i = 0; i < _engine->_watches.size(); i++) { + _watchInstances.push_back(new WatchInstance(_engine->_watches[i], this)); + } +} + +DebuggableScript::~DebuggableScript() { + for (uint i = 0; i < _watchInstances.size(); i++) { + delete _watchInstances[i]; + } + _engine->_watches.unsubscribe(this); +} +void DebuggableScript::preInstHook(uint32 inst) {} + +void DebuggableScript::postInstHook(uint32 inst) { + if (inst == II_DBG_LINE) { + for (uint j = 0; j < _engine->_breakpoints.size(); j++) { + _engine->_breakpoints[j]->evaluate(this); + } + + if (_callStack->_sP <= _stepDepth) { + _engine->_monitor->notifyStep(this); + } + } + + for (uint i = 0; i < _watchInstances.size(); i++) { + this->_watchInstances[i]->evaluate(); + } + +} + +void DebuggableScript::setStepDepth(int depth) { + _stepDepth = depth; +} + +void DebuggableScript::step() { + setStepDepth(_callStack->_sP); + // TODO double check +} + +void DebuggableScript::stepContinue() { + setStepDepth(kDefaultStepDepth); +} + +void DebuggableScript::stepFinish() { + setStepDepth(_callStack->_sP - 1); +} + +ScValue *DebuggableScript::resolveName(const Common::String &name) { + + Common::String trimmed = name; + trimmed.trim(); + Common::StringTokenizer st = Common::StringTokenizer(trimmed.c_str(), "."); + Common::String nextToken; + + nextToken = st.nextToken(); + + + char cstr[256]; // TODO not pretty + Common::strlcpy(cstr, nextToken.c_str(), nextToken.size() + 1); + cstr[255] = '\0'; // We 0-terminate it just in case it's > 256 chars. + + ScValue *value = getVar(cstr); + ScValue *res = new ScValue(_gameRef); + + if (value == nullptr) { + return res; + } + + nextToken = st.nextToken(); + + while (nextToken.size() > 0 && (value->isObject() || value->isNative())) { + value = value->getProp(nextToken.c_str()); + nextToken = st.nextToken(); + if (value == nullptr) { + return res; + } + } + + res->copy(value); + + return res; +} + +uint DebuggableScript::dbgGetLine() const { + return _currentLine; +} + +Common::String DebuggableScript::dbgGetFilename() const { + return _filename; +} + +void DebuggableScript::updateWatches() { + // We drop obsolete watches + for (uint i = 0; i < _watchInstances.size(); i++) { + Watch *findMe = _watchInstances[i]->_watch; + if (Common::find(_engine->_watches.begin(), _engine->_watches.end(), findMe) == _engine->_watches.end()) { + // Not found on engine-wide list, must have been removed from watches. Must remove it from local list. + delete _watchInstances[i]; + _watchInstances.remove_at(i); + } + } + + // We add any new watches + for (uint i = 0; i < _engine->_watches.size(); i++) { + Watch *findMe = _engine->_watches[i]; + if (Common::find(_engine->_watches.begin(), _engine->_watches.end(), findMe) == _engine->_watches.end()) { + // Not found on local list, must be a new one. + _watchInstances.push_back(new WatchInstance(_engine->_watches[i], this)); + } + } +} +} // End of namespace Wintermute + diff --git a/engines/wintermute/base/scriptables/debuggable/debuggable_script.h b/engines/wintermute/base/scriptables/debuggable/debuggable_script.h new file mode 100644 index 0000000000..b32a5ca4af --- /dev/null +++ b/engines/wintermute/base/scriptables/debuggable/debuggable_script.h @@ -0,0 +1,67 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef DEBUGGABLE_SCRIPT_H_ +#define DEBUGGABLE_SCRIPT_H_ +#include "engines/wintermute/base/scriptables/script.h" + +namespace Wintermute { +class ScriptMonitor; +class Watch; +class WatchInstance; +class DebuggableScEngine; + +class DebuggableScript : public ScScript { + static const int kDefaultStepDepth = -2; + int32 _stepDepth; + DebuggableScEngine *_engine; + BaseArray<WatchInstance *> _watchInstances; + virtual void preInstHook(uint32 inst) override; + virtual void postInstHook(uint32 inst) override; + void setStepDepth(int depth); +public: + DebuggableScript(BaseGame *inGame, DebuggableScEngine *engine); + virtual ~DebuggableScript(); + ScValue *resolveName(const Common::String &name); + /** + * Return argument to last II_DBG_LINE encountered + */ + virtual uint dbgGetLine() const; + virtual Common::String dbgGetFilename() const; + /** + * Execute one more instruction + */ + void step(); + /** + * Continue execution + */ + void stepContinue(); + /** + * Continue execution until the activation record on top of the stack is popped + */ + void stepFinish(); + void updateWatches(); +}; + +} // End of namespace Wintermute + +#endif /* DEBUGGABLE_SCRIPT_H_ */ diff --git a/engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.cpp b/engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.cpp new file mode 100644 index 0000000000..28a00cd4ae --- /dev/null +++ b/engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.cpp @@ -0,0 +1,35 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "debuggable_script_engine.h" +#include "debuggable_script.h" +#include "engines/wintermute/debugger/watch_instance.h" + +namespace Wintermute { + +DebuggableScEngine::DebuggableScEngine(BaseGame *inGame) : ScEngine(inGame), _monitor(nullptr) {} + +void DebuggableScEngine::attachMonitor(ScriptMonitor *monitor) { + _monitor = monitor; +} + +} // End of namespace Wintermute diff --git a/engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h b/engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h new file mode 100644 index 0000000000..a4d9d2bfe7 --- /dev/null +++ b/engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h @@ -0,0 +1,110 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef DEBUGGABLE_SCRIPT_ENGINE_H_ +#define DEBUGGABLE_SCRIPT_ENGINE_H_ +#include "engines/wintermute/base/scriptables/script_engine.h" +#include "engines/wintermute/coll_templ.h" +#include "common/algorithm.h" +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" + +namespace Wintermute { + +class Breakpoint; +class Watch; +class DebuggableScript; +class DebuggableScEngine; +class ScriptMonitor; + +class PublisherWArray : private Common::Array<Watch *> { + Common::Array<DebuggableScript *> _subscribers; + void notifySubscribers() { + for (uint i = 0; i < _subscribers.size(); i++) { + _subscribers[i]->updateWatches(); + } + } +public: + void subscribe(DebuggableScript *script) { + if (Common::find(_subscribers.begin(), _subscribers.end(), script) == _subscribers.end()) { + // If not already contained + _subscribers.push_back(script); + } + } + + void unsubscribe(DebuggableScript *script) { + int location = -1; + for (uint i = 0; i < _subscribers.size() && location == -1; i++) { + if (_subscribers[i] == script) { + location = i; + } + } + if (location >= 0) { + _subscribers.remove_at(location); + } else { + // TODO: If this happens... it's funny. Some script out there forgot to subscribe. + } + } + + void push_back(Watch *newElement) { + Common::Array<Watch *>::push_back(newElement); + notifySubscribers(); + } + + size_type size() { + return Common::Array<Watch *>::size(); + } + + iterator begin() { + return Common::Array<Watch *>::begin(); + } + + iterator end() { + return Common::Array<Watch *>::end(); + } + + Watch *&operator[](size_type idx) { + return Common::Array<Watch *>::operator[](idx); + } + Watch *remove_at(size_type idx) { + Watch *res = Common::Array<Watch *>::remove_at(idx); + notifySubscribers(); + return res; + } +}; + +class DebuggableScEngine : public ScEngine { + Common::Array<Breakpoint *> _breakpoints; + PublisherWArray _watches; + ScriptMonitor *_monitor; +public: + DebuggableScEngine(BaseGame *inGame); + void attachMonitor(ScriptMonitor *); + + friend class DebuggerController; + friend class DebuggableScript; + friend class ScScript; + friend class WatchableScriptArray; +}; + +} // End of namespace Wintermute + +#endif /* DEBUGGABLE_SCRIPT_ENGINE_H_ */ diff --git a/engines/wintermute/base/scriptables/script.cpp b/engines/wintermute/base/scriptables/script.cpp index 44fd117e61..938ec031da 100644 --- a/engines/wintermute/base/scriptables/script.cpp +++ b/engines/wintermute/base/scriptables/script.cpp @@ -32,7 +32,9 @@ #include "engines/wintermute/base/scriptables/script_engine.h" #include "engines/wintermute/base/scriptables/script_stack.h" #include "common/memstream.h" - +#if EXTENDED_DEBUGGER_ENABLED == true +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" +#endif namespace Wintermute { IMPLEMENT_PERSISTENT(ScScript, false) @@ -522,6 +524,9 @@ bool ScScript::executeInstruction() { ScValue *op2; uint32 inst = getDWORD(); + + preInstHook(inst); + switch (inst) { case II_DEF_VAR: @@ -1092,6 +1097,7 @@ bool ScScript::executeInstruction() { ret = STATUS_FAILED; } // switch(instruction) + postInstHook(inst); //delete op; return ret; @@ -1314,8 +1320,15 @@ ScScript *ScScript::invokeEventHandler(const Common::String &eventName, bool unb if (!pos) { return nullptr; } - +#if EXTENDED_DEBUGGER_ENABLED == true + // TODO: Not pretty + DebuggableScEngine* debuggableEngine; + debuggableEngine = dynamic_cast<DebuggableScEngine*>(_engine); + assert(debuggableEngine); + ScScript *thread = new DebuggableScript(_gameRef, debuggableEngine); +#else ScScript *thread = new ScScript(_gameRef, _engine); +#endif if (thread) { bool ret = thread->createThread(this, pos, eventName); if (DID_SUCCEED(ret)) { @@ -1434,18 +1447,6 @@ bool ScScript::finishThreads() { return STATUS_OK; } - -////////////////////////////////////////////////////////////////////////// -// IWmeDebugScript interface implementation -int ScScript::dbgGetLine() { - return _currentLine; -} - -////////////////////////////////////////////////////////////////////////// -const char *ScScript::dbgGetFilename() { - return _filename; -} - ////////////////////////////////////////////////////////////////////////// void ScScript::afterLoad() { if (_buffer == nullptr) { @@ -1466,4 +1467,8 @@ void ScScript::afterLoad() { } } +void ScScript::preInstHook(uint32 inst) {} + +void ScScript::postInstHook(uint32 inst) {} + } // End of namespace Wintermute diff --git a/engines/wintermute/base/scriptables/script.h b/engines/wintermute/base/scriptables/script.h index 1edeae5b55..c1d1cce4ee 100644 --- a/engines/wintermute/base/scriptables/script.h +++ b/engines/wintermute/base/scriptables/script.h @@ -33,12 +33,15 @@ #include "engines/wintermute/base/base.h" #include "engines/wintermute/base/scriptables/dcscript.h" // Added by ClassView #include "engines/wintermute/coll_templ.h" +#include "engines/wintermute/persistent.h" namespace Wintermute { class BaseScriptHolder; class BaseObject; class ScEngine; class ScStack; +class ScValue; + class ScScript : public BaseClass { public: BaseArray<int> _breakpoints; @@ -50,7 +53,7 @@ public: bool copyParameters(ScStack *stack); void afterLoad(); -private: +protected: ScValue *_operand; ScValue *_reg1; public: @@ -125,7 +128,7 @@ public: ScValue *_globals; ScEngine *_engine; int32 _currentLine; - bool executeInstruction(); + virtual bool executeInstruction(); char *getString(); uint32 getDWORD(); double getFloat(); @@ -162,11 +165,8 @@ private: bool initScript(); bool initTables(); - -// IWmeDebugScript interface implementation -public: - virtual int dbgGetLine(); - virtual const char *dbgGetFilename(); + virtual void preInstHook(uint32 inst); + virtual void postInstHook(uint32 inst); }; } // End of namespace Wintermute diff --git a/engines/wintermute/base/scriptables/script_engine.cpp b/engines/wintermute/base/scriptables/script_engine.cpp index cdf55a304c..26122094f1 100644 --- a/engines/wintermute/base/scriptables/script_engine.cpp +++ b/engines/wintermute/base/scriptables/script_engine.cpp @@ -144,7 +144,15 @@ ScScript *ScEngine::runScript(const char *filename, BaseScriptHolder *owner) { } // add new script +#if EXTENDED_DEBUGGER_ENABLED == true + DebuggableScEngine* debuggableEngine; + debuggableEngine = dynamic_cast<DebuggableScEngine*>(this); + // TODO: Not pretty + assert(debuggableEngine); + ScScript *script = new DebuggableScript(_gameRef, debuggableEngine); +#else ScScript *script = new ScScript(_gameRef, this); +#endif bool ret = script->create(filename, compBuffer, compSize, owner); if (DID_FAIL(ret)) { _gameRef->LOG(ret, "Error running script '%s'...", filename); diff --git a/engines/wintermute/base/scriptables/script_engine.h b/engines/wintermute/base/scriptables/script_engine.h index bdb139e1f8..8b7e4acd19 100644 --- a/engines/wintermute/base/scriptables/script_engine.h +++ b/engines/wintermute/base/scriptables/script_engine.h @@ -66,20 +66,6 @@ public: Common::String _filename; }; - class CScBreakpoint { - public: - CScBreakpoint(const char *filename) { - _filename = filename; - } - - ~CScBreakpoint() { - _lines.clear(); - } - - Common::String _filename; - BaseArray<int> _lines; - }; - public: bool clearGlobals(bool includingNatives = false); bool tickUnbreakable(); diff --git a/engines/wintermute/debugger.cpp b/engines/wintermute/debugger.cpp index 5b617d9db9..c643c33246 100644 --- a/engines/wintermute/debugger.cpp +++ b/engines/wintermute/debugger.cpp @@ -21,29 +21,289 @@ */ #include "engines/wintermute/debugger.h" -#include "engines/wintermute/wintermute.h" #include "engines/wintermute/base/base_engine.h" #include "engines/wintermute/base/base_file_manager.h" -#include "engines/wintermute/base/base_game.h" +#include "engines/wintermute/base/scriptables/script_value.h" +#include "engines/wintermute/debugger/debugger_controller.h" +#include "engines/wintermute/wintermute.h" + +#define CONTROLLER _engineRef->_dbgController namespace Wintermute { Console::Console(WintermuteEngine *vm) : GUI::Debugger(), _engineRef(vm) { registerCmd("show_fps", WRAP_METHOD(Console, Cmd_ShowFps)); registerCmd("dump_file", WRAP_METHOD(Console, Cmd_DumpFile)); + registerCmd("show_fps", WRAP_METHOD(Console, Cmd_ShowFps)); + registerCmd("dump_file", WRAP_METHOD(Console, Cmd_DumpFile)); + registerCmd("help", WRAP_METHOD(Console, Cmd_Help)); + // Actual (script) debugger commands + registerCmd(STEP_CMD, WRAP_METHOD(Console, Cmd_Step)); + registerCmd(CONTINUE_CMD, WRAP_METHOD(Console, Cmd_Continue)); + registerCmd(FINISH_CMD, WRAP_METHOD(Console, Cmd_Finish)); + registerCmd(WATCH_CMD, WRAP_METHOD(Console, Cmd_Watch)); + registerCmd(BREAK_CMD, WRAP_METHOD(Console, Cmd_AddBreakpoint)); + registerCmd(LIST_CMD, WRAP_METHOD(Console, Cmd_List)); + registerCmd(REMOVE_BREAKPOINT_CMD, WRAP_METHOD(Console, Cmd_RemoveBreakpoint)); + registerCmd(DISABLE_BREAKPOINT_CMD, WRAP_METHOD(Console, Cmd_DisableBreakpoint)); + registerCmd(ENABLE_BREAKPOINT_CMD, WRAP_METHOD(Console, Cmd_EnableBreakpoint)); + registerCmd(REMOVE_WATCH_CMD, WRAP_METHOD(Console, Cmd_RemoveWatch)); + registerCmd(DISABLE_WATCH_CMD, WRAP_METHOD(Console, Cmd_DisableWatch)); + registerCmd(ENABLE_WATCH_CMD, WRAP_METHOD(Console, Cmd_EnableWatch)); + registerCmd(PRINT_CMD, WRAP_METHOD(Console, Cmd_Print)); + registerCmd(SET_CMD, WRAP_METHOD(Console, Cmd_Set)); + registerCmd(INFO_CMD, WRAP_METHOD(Console, Cmd_Info)); + registerCmd(SET_PATH_CMD, WRAP_METHOD(Console, Cmd_SourcePath)); + registerCmd(TOP_CMD, WRAP_METHOD(Console, Cmd_Top)); } Console::~Console(void) { +} + +bool Console::Cmd_Help(int argc, const char **argv) { + if (argc == 1) { + // Debugger::Cmd_Help(argc, argv); + debugPrintf("\nType help somecommand to get specific help.\n"); + } else { + printUsage(argv[1]); + } + return true; +} +void Console::printUsage(const Common::String &command) { + // TODO: This is horrible and would probably benefit from a map or something. + if (command.equals(BREAK_CMD)) { + debugPrintf("Usage: %s <file path> <line> to break at line <line> of file <file path>\n", command.c_str()); + } else if (command.equals(REMOVE_BREAKPOINT_CMD)) { + debugPrintf("Usage: %s <id> to remove breakpoint #id\n", command.c_str()); + } else if (command.equals(ENABLE_BREAKPOINT_CMD)) { + debugPrintf("Usage: %s <id> to enable breakpoint #id\n", command.c_str()); + } else if (command.equals(DISABLE_BREAKPOINT_CMD)) { + debugPrintf("Usage: %s <id> to disable breakpoint #id\n", command.c_str()); + } else if (command.equals(REMOVE_WATCH_CMD)) { + debugPrintf("Usage: %s <id> to remove watchpoint #id\n", command.c_str()); + } else if (command.equals(ENABLE_WATCH_CMD)) { + debugPrintf("Usage: %s <id> to enable watchpoint #id\n", command.c_str()); + } else if (command.equals(DISABLE_WATCH_CMD)) { + debugPrintf("Usage: %s <id> to disable watchpoint #id\n", command.c_str()); + } else if (command.equals(INFO_CMD)) { + debugPrintf("Usage: %s [watch|breakpoints]\n", command.c_str()); + } else if (command.equals(WATCH_CMD)) { + debugPrintf("Usage: %s <file path> <name> to watch for <name> in file <file path>\n", command.c_str()); + } else if (command.equals(STEP_CMD)) { + debugPrintf("Usage: %s to step\n", command.c_str()); + } else if (command.equals(CONTINUE_CMD)) { + debugPrintf("Usage: %s to continue\n", command.c_str()); + } else if (command.equals(FINISH_CMD)) { + debugPrintf("Usage: %s to finish\n", command.c_str()); + } else if (command.equals(PRINT_CMD)) { + debugPrintf("Usage: %s <name> to print value of <name>\n", command.c_str()); + } else if (command.equals(SET_CMD)) { + debugPrintf("Usage: %s <name> = <value> to set <name> to <value>\n", command.c_str()); + } else { + debugPrintf("No help about this command, sorry."); + } +} + +bool Console::Cmd_AddBreakpoint(int argc, const char **argv) { + if (argc == 3) { + Wintermute::Error error = CONTROLLER->addBreakpoint(argv[1], atoi(argv[2])); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_RemoveBreakpoint(int argc, const char **argv) { + if (argc == 2) { + Error error = CONTROLLER->removeBreakpoint(atoi(argv[1])); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_EnableBreakpoint(int argc, const char **argv) { + if (argc == 2) { + Error error = CONTROLLER->enableBreakpoint(atoi(argv[1])); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_DisableBreakpoint(int argc, const char **argv) { + if (argc == 2) { + Error error = CONTROLLER->disableBreakpoint(atoi(argv[1])); + debugPrintf("%s: %s\n", argv[0], error.getErrorDisplayStr().c_str()); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_RemoveWatch(int argc, const char **argv) { + if (argc == 2) { + Error error = CONTROLLER->removeWatchpoint(atoi(argv[1])); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + + return true; +} + +bool Console::Cmd_EnableWatch(int argc, const char **argv) { + if (argc == 2) { + Error error = CONTROLLER->enableWatchpoint(atoi(argv[1])); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_DisableWatch(int argc, const char **argv) { + if (argc == 2) { + Error error = CONTROLLER->disableWatchpoint(atoi(argv[1])); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_Watch(int argc, const char **argv) { + if (argc == 3) { + Error error = CONTROLLER->addWatch(argv[1], argv[2]); + printError(argv[0], error); + } else { + printUsage(argv[0]); + } + return true; +} + +bool Console::Cmd_Info(int argc, const char **argv) { + if (argc == 2 && !strncmp(argv[1], "breakpoints", 10)) { + Common::Array<BreakpointInfo> breakpoints = CONTROLLER->getBreakpoints(); + for (uint i = 0; i < breakpoints.size(); i++) { + debugPrintf("%d %s:%d x%d, enabled: %d \n", i, breakpoints[i]._filename.c_str(), breakpoints[i]._line, breakpoints[i]._hits, breakpoints[i]._enabled); + } + return 1; + } else if (argc == 2 && !strncmp(argv[1], WATCH_CMD, 5)) { + Common::Array<WatchInfo>watchlist = CONTROLLER->getWatchlist(); + for (uint i = 0; i < watchlist.size(); i++) { + debugPrintf("%d %s:%s x%d \n", i, watchlist[i]._filename.c_str(), watchlist[i]._symbol.c_str(), watchlist[i]._hits); + } + return 1; + } else { + printUsage(argv[0]); + return 1; + } +} + +bool Console::Cmd_Step(int argc, const char **argv) { + if (argc == 1) { + Error error = CONTROLLER->step(); + if (error.getErrorLevel() == SUCCESS) { + return false; + } else { + printError(argv[0], error); + return true; + } + } else { + printUsage(argv[0]); + return true; + } +} + +bool Console::Cmd_Continue(int argc, const char **argv) { + if (argc == 1) { + Error error = CONTROLLER->stepContinue(); + if (error.getErrorLevel() == SUCCESS) { + return false; + } else { + printError(argv[0], error); + return true; + } + } else { + printUsage(argv[0]); + return true; + } +} + +bool Console::Cmd_Finish(int argc, const char **argv) { + if (argc == 1) { + Error error = CONTROLLER->stepFinish(); + printError(argv[0], error); + if (error.getErrorLevel() == SUCCESS) { + return false; + } else { + printError(argv[0], error); + return true; + } + } else { + printUsage(argv[0]); + return true; + } +} + +bool Console::Cmd_List(int argc, const char **argv) { + Error error = printSource(); + if (error.getErrorLevel() != SUCCESS) { + printError(argv[0], error); + } + return true; +} + +bool Console::Cmd_Print(int argc, const char **argv) { + if (argc == 2) { + Error error = Error(SUCCESS, OK, 0); + Common::String temp = CONTROLLER->readValue(argv[1], &error); + if (error.getErrorLevel() == SUCCESS) { + debugPrintf("%s = %s \n", argv[1], temp.c_str()); + return true; + } else { + printError(argv[0], error); + return true; + } + } else { + printUsage(argv[0]); + return true; + } +} + + +bool Console::Cmd_Set(int argc, const char **argv) { + if (argc == 4 && !strncmp("=", argv[2], 1)) { + ScValue *val = nullptr; + Error error = CONTROLLER->setValue(argv[1], argv[3], val); + if (error.getErrorLevel() == SUCCESS) { + assert(val); + debugPrintf("%s = %s\n", argv[1], val->getString()); + } else { + printError(argv[0], error); + } + } else { + printUsage(argv[0]); + } + return true; } bool Console::Cmd_ShowFps(int argc, const char **argv) { - if (argc > 1) { + if (argc == 2) { if (Common::String(argv[1]) == "true") { - _engineRef->_game->setShowFPS(true); + CONTROLLER->showFps(true); } else if (Common::String(argv[1]) == "false") { - _engineRef->_game->setShowFPS(false); + CONTROLLER->showFps(false); + } else { + debugPrintf("%s: argument 1 must be \"true\" or \"false\"\n", argv[0]); } + } else { + debugPrintf("Usage: %s [true|false]\n", argv[0]); } return true; } @@ -81,4 +341,80 @@ bool Console::Cmd_DumpFile(int argc, const char **argv) { return true; } + +bool Console::Cmd_SourcePath(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Usage: %s <source path>\n", argv[0]); + return true; + } else { + if (CONTROLLER->setSourcePath(Common::String(argv[1])).getErrorCode() == OK) { + debugPrintf("Source path set to '%s'\n", CONTROLLER->getSourcePath().c_str()); + } else { + debugPrintf("Error setting source path. Note that \"\" is illegal."); + } + return true; + } +} + +void Console::notifyBreakpoint(const char *filename, int line) { + debugPrintf("Breakpoint hit %s: %d\n", filename, line); + printSource(0); + attach(); + onFrame(); +} + +void Console::notifyStep(const char *filename, int line) { + debugPrintf("Step: %s:%d\n", filename, line); + printSource(0); + attach(); + onFrame(); +} + +void Console::notifyWatch(const char *filename, const char *symbol, const char *newValue) { + debugPrintf("Watch: %s:%s <---- %s\n", filename, symbol, newValue); + printSource(0); + attach(); + onFrame(); +} + +Error Console::printSource(int n) { + + Error* error = nullptr; + Listing *listing = CONTROLLER->getListing(error); + Error err(*error); + delete error; + + if (err.getErrorLevel() == SUCCESS || err.getErrorLevel() == WARNING) { + Common::Array<ListingLine> lines = listing->getLines(CONTROLLER->getLastLine(), n/2, n/2); + for (uint i = 0; i < lines.size(); i++) { + if (lines[i].number == CONTROLLER->getLastLine()) { + debugPrintf(" -> "); + } else { + debugPrintf(" "); + } + debugPrintf("%d", lines[i].number); + debugPrintf("%s", lines[i].text.c_str()); + debugPrintf("\n"); + } + } + + delete listing; + return err; +} + +bool Console::Cmd_Top(int argc, const char **argv) { + Common::Array<TopEntry> entries = CONTROLLER->getTop(); + for (uint i = 0; i < entries.size(); i++) { + if (entries[i].current) { + debugPrintf("%d*: %s\n", i, entries[i].filename.c_str()); + } else { + debugPrintf("%d: %s\n", i, entries[i].filename.c_str()); + } + } + return true; +} + +void Console::printError(const Common::String &command, Error error) { + debugPrintf("%s: %s\n", command.c_str(), error.getErrorDisplayStr().c_str()); +} } // End of namespace Wintermute diff --git a/engines/wintermute/debugger.h b/engines/wintermute/debugger.h index 625da0ce41..e5008bee3b 100644 --- a/engines/wintermute/debugger.h +++ b/engines/wintermute/debugger.h @@ -23,20 +23,134 @@ #ifndef WINTERMUTE_DEBUGGER_H #define WINTERMUTE_DEBUGGER_H +#define EXTENDED_DEBUGGER_ENABLED true + #include "gui/debugger.h" -namespace Wintermute { +#if EXTENDED_DEBUGGER_ENABLED == true +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" +#else +#include "engines/wintermute/base/scriptables/script.h" +#endif + +#define DEFAULT_SOURCE_PADDING 5 + +#define STEP_CMD "step" +#define CONTINUE_CMD "continue" +#define FINISH_CMD "finish" +#define WATCH_CMD "watch" +#define BREAK_CMD "break" +#define LIST_CMD "list" +#define REMOVE_BREAKPOINT_CMD "del" +#define DISABLE_BREAKPOINT_CMD "disable" +#define ENABLE_BREAKPOINT_CMD "enable" +#define REMOVE_WATCH_CMD "delw" +#define DISABLE_WATCH_CMD "disablew" +#define ENABLE_WATCH_CMD "enablew" +#define INFO_CMD "info" +#define SET_CMD "set" +#define PRINT_CMD "print" +#define SET_PATH_CMD "set_path" +#define TOP_CMD "top" + +namespace Wintermute { class WintermuteEngine; +class Adapter; +class DebuggerController; +class Error; + class Console : public GUI::Debugger { public: Console(WintermuteEngine *vm); virtual ~Console(); - + /* + * Debug commands + */ + bool Cmd_Help(int argc, const char **argv); bool Cmd_ShowFps(int argc, const char **argv); bool Cmd_DumpFile(int argc, const char **argv); + +#if EXTENDED_DEBUGGER_ENABLED == true + /** + * Step - break again on next line + */ + bool Cmd_Step(int argc, const char **argv); + /** + * Continue execution + */ + bool Cmd_Continue(int argc, const char **argv); + /** + * Only break again when the current function is finished + * (activation record is popped) + */ + bool Cmd_Finish(int argc, const char **argv); + bool Cmd_Print(int argc, const char **argv); + bool Cmd_Set(int argc, const char **argv); + // Breakpoints + bool Cmd_AddBreakpoint(int argc, const char **argv); + bool Cmd_RemoveBreakpoint(int argc, const char **argv); + bool Cmd_EnableBreakpoint(int argc, const char **argv); + bool Cmd_DisableBreakpoint(int argc, const char **argv); + /** + * Add a watch. + * + * It monitors the value of some variable x against its + * last known state and it breaks if it has changed since. + * + */ + bool Cmd_Watch(int argc, const char **argv); + bool Cmd_RemoveWatch(int argc, const char **argv); + bool Cmd_EnableWatch(int argc, const char **argv); + bool Cmd_DisableWatch(int argc, const char **argv); + /** + * Print info re:watch and breakpoints. + * This differs from e.g. gdb in that we have separate lists. + */ + bool Cmd_Info(int argc, const char **argv); + /** + * Print source + */ + bool Cmd_List(int argc, const char **argv); + /** + * Set (DOS-style) source path for debugging. + * This is where you will (optionally) put your sources + * to enable printing of sources as you step through the + * scripts. + * + * Please note that we have no checksum or anything + * to make sure your source files are up to date. + * + * YOU HAVE to make sure of that. + * + * You have been warned! :) + */ + bool Cmd_SourcePath(int argc, const char **argv); + + /** + * Top + */ + bool Cmd_Top(int argc, const char **argv); + + Error printSource(int n = DEFAULT_SOURCE_PADDING); + + /** + * Hooks for the controller to open the console + */ + void notifyBreakpoint(const char *filename, int line); + void notifyStep(const char *filename, int line); + /** + * To be called by the adapter when a watched variable + * is changed. + * Opens a console and prints info and listing if available. + */ + void notifyWatch(const char *filename, const char *symbol, const char *newValue); +#endif + private: - WintermuteEngine *_engineRef; + const WintermuteEngine *_engineRef; + void printError(const Common::String &command, Error error); + void printUsage(const Common::String &command); }; } diff --git a/engines/wintermute/debugger/breakpoint.cpp b/engines/wintermute/debugger/breakpoint.cpp new file mode 100644 index 0000000000..7f2a02b0ea --- /dev/null +++ b/engines/wintermute/debugger/breakpoint.cpp @@ -0,0 +1,68 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "breakpoint.h" +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" +#include "script_monitor.h" + +namespace Wintermute { + +Breakpoint::Breakpoint(const Common::String &filename, uint line, ScriptMonitor *monitor) : + _filename(filename), _line(line), _monitor(monitor), _enabled(0), _hits(0) {} + +void Breakpoint::hit(DebuggableScript *script) { + _hits++; + _monitor->onBreakpoint(this, script); +} + +Common::String Breakpoint::getFilename() const { + return _filename; +} +int Breakpoint::getLine() const { + return _line; +} +int Breakpoint::getHits() const { + return _hits; +} +bool Breakpoint::isEnabled() const { + return _enabled; +} +void Breakpoint::enable() { + _enabled = true; +} +void Breakpoint::disable() { + _enabled = false; +} + +void Breakpoint::evaluate(DebuggableScript *script) { + if (isEnabled() && + getLine() == script->_currentLine && + !getFilename().compareTo(script->_filename)) { + hit(script); + } +} + +Breakpoint::~Breakpoint() { + // Nothing to take care of in here +} + +} // End of namespace Wintermute diff --git a/engines/wintermute/debugger/breakpoint.h b/engines/wintermute/debugger/breakpoint.h new file mode 100644 index 0000000000..3757791ba3 --- /dev/null +++ b/engines/wintermute/debugger/breakpoint.h @@ -0,0 +1,58 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BREAKPOINT_H_ +#define BREAKPOINT_H_ +#include "common/str.h" + +namespace Wintermute { + +class ScriptMonitor; +class DebuggableScript; + +class Breakpoint { + const Common::String _filename; + const uint _line; + uint _hits; + bool _enabled; + ScriptMonitor *_monitor; + void hit(DebuggableScript *script); +public: + Breakpoint(const Common::String &filename, uint line, ScriptMonitor *monitor); + /** + * This should be called inside the interpreter; the breakpoint is evaluated + * in the context of script, and, if it is enabled and filename & line match, + * the attached ScriptMonitor is notified. + */ + void evaluate(DebuggableScript* script); + Common::String getFilename() const; + int getLine() const; + int getHits() const; + bool isEnabled() const; + void enable(); + void disable(); + virtual ~Breakpoint(); +}; + +} // End of namespace Wintermute + +#endif /* BREAKPOINT_H_ */ diff --git a/engines/wintermute/debugger/debugger_controller.cpp b/engines/wintermute/debugger/debugger_controller.cpp new file mode 100644 index 0000000000..2e021530f8 --- /dev/null +++ b/engines/wintermute/debugger/debugger_controller.cpp @@ -0,0 +1,325 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/algorithm.h" +#include "common/str.h" +#include "common/tokenizer.h" +#include "engines/wintermute/debugger.h" +#include "engines/wintermute/base/base_file_manager.h" +#include "engines/wintermute/base/base_engine.h" +#include "engines/wintermute/base/base_game.h" +#include "engines/wintermute/base/scriptables/script.h" +#include "engines/wintermute/base/scriptables/script_value.h" +#include "engines/wintermute/base/scriptables/script_stack.h" +#include "engines/wintermute/debugger/breakpoint.h" +#include "engines/wintermute/debugger/debugger_controller.h" +#include "engines/wintermute/debugger/watch.h" +#include "engines/wintermute/debugger/listing_providers/blank_listing_provider.h" +#include "engines/wintermute/debugger/listing_providers/cached_source_listing_provider.h" +#include "engines/wintermute/debugger/listing_providers/source_listing.h" +#define SCENGINE _engine->_game->_scEngine +#define DEBUGGER _engine->_debugger + +namespace Wintermute { + +DebuggerController::~DebuggerController() { + delete _sourceListingProvider; +} + +DebuggerController::DebuggerController(WintermuteEngine *vm) : _engine(vm) { + _sourceListingProvider = new CachedSourceListingProvider(); + clear(); +} + +bool DebuggerController::bytecodeExists(const Common::String &filename) { + uint32 compSize; + byte *compBuffer = SCENGINE->getCompiledScript(filename.c_str(), &compSize); + if (!compBuffer) { + return false; + } else { + return true; + } +} + +Error DebuggerController::addBreakpoint(const char *filename, int line) { + assert(SCENGINE); + if (bytecodeExists(filename)) { + SCENGINE->_breakpoints.push_back(new Breakpoint(filename, line, this)); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BYTECODE); + } +} + +Error DebuggerController::removeBreakpoint(uint id) { + assert(SCENGINE); + if (SCENGINE->_breakpoints.size() > id) { + SCENGINE->_breakpoints.remove_at(id); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BREAKPOINT, id); + } +} + +Error DebuggerController::disableBreakpoint(uint id) { + assert(SCENGINE); + if (SCENGINE->_breakpoints.size() > id) { + SCENGINE->_breakpoints[id]->disable(); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BREAKPOINT, id); + } +} + +Error DebuggerController::enableBreakpoint(uint id) { + assert(SCENGINE); + if (SCENGINE->_breakpoints.size() > id) { + SCENGINE->_breakpoints[id]->enable(); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BREAKPOINT, id); + } +} + +Error DebuggerController::removeWatchpoint(uint id) { + assert(SCENGINE); + if (SCENGINE->_watches.size() > id) { + SCENGINE->_watches.remove_at(id); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BREAKPOINT, id); + } +} + + +Error DebuggerController::disableWatchpoint(uint id) { + assert(SCENGINE); + if (SCENGINE->_watches.size() > id) { + SCENGINE->_watches[id]->disable(); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BREAKPOINT, id); + } +} + +Error DebuggerController::enableWatchpoint(uint id) { + assert(SCENGINE); + if (SCENGINE->_watches.size() > id) { + SCENGINE->_watches[id]->enable(); + return Error(SUCCESS, OK); + } else { + return Error(ERROR, NO_SUCH_BREAKPOINT, id); + } + +} + +Error DebuggerController::addWatch(const char *filename, const char *symbol) { + assert(SCENGINE); + if (!bytecodeExists(filename)) { + return Error(ERROR, NO_SUCH_BYTECODE, filename); + } + SCENGINE->_watches.push_back(new Watch(filename, symbol, this)); + return Error(SUCCESS, OK, "Watchpoint added"); +} + +void DebuggerController::onBreakpoint(const Breakpoint *breakpoint, DebuggableScript *script) { + _lastScript = script; + _lastLine = script->_currentLine; + DEBUGGER->notifyBreakpoint(script->dbgGetFilename().c_str(), script->_currentLine); +} + +void DebuggerController::notifyStep(DebuggableScript *script) override { + _lastScript = script; + _lastLine = script->_currentLine; + DEBUGGER->notifyStep(script->dbgGetFilename().c_str(), script->_currentLine); +} + +void DebuggerController::onWatch(const Watch *watch, DebuggableScript *script) { + _lastScript = script; // If script has changed do we still care? + _lastLine = script->_currentLine; + Common::String symbol = watch->getSymbol(); + DEBUGGER->notifyWatch(script->dbgGetFilename().c_str(), symbol.c_str(), script->resolveName(symbol)->getString()); +} + +Error DebuggerController::step() { + if (!_lastScript) { + return Error(ERROR, NOT_ALLOWED); + } + _lastScript->step(); + clear(); + return Error(SUCCESS, OK); +} + +Error DebuggerController::stepContinue() { + if (!_lastScript) { + return Error(ERROR, NOT_ALLOWED); + } + _lastScript->stepContinue(); + return Error(SUCCESS, OK); +} + +Error DebuggerController::stepFinish() { + if (!_lastScript) { + return Error(ERROR, NOT_ALLOWED); + } + _lastScript->stepFinish(); + clear(); + return Error(SUCCESS, OK); +} + +void DebuggerController::clear() { + _lastScript = nullptr; + _lastLine = -1; +} + +Common::String DebuggerController::readValue(const Common::String &name, Error *error) { + if (!_lastScript) { + delete error; + error = new Error(ERROR, NOT_ALLOWED); + return Common::String(); + } + char cstr[256]; // TODO not pretty + Common::strlcpy(cstr, name.c_str(), name.size() + 1); + cstr[255] = '\0'; // We 0-terminate it just in case it's longer than 255. + return _lastScript->resolveName(cstr)->getString(); +} + +Error DebuggerController::setValue(const Common::String &name, const Common::String &value, ScValue *&var) { + if (!_lastScript) { + return Error(ERROR, NOT_ALLOWED); + } + + Common::String trimmed = value; + trimmed.trim(); + char cstr[256]; + Common::strlcpy(cstr, name.c_str(), name.size() + 1); // TODO not pretty + + var = _lastScript->getVar(cstr); + if (var->_type == VAL_INT) { + char *endptr; + int res = strtol(trimmed.c_str(), &endptr, 10); // TODO: Hex too? + if (endptr == trimmed.c_str()) { + return Error(ERROR, PARSE_ERROR); + } else if (endptr == trimmed.c_str() + trimmed.size()) { + // We've parsed all of it, have we? + var->setInt(res); + } else { + assert(false); + return Error(ERROR, PARSE_ERROR); + // Something funny happened here. + } + } else if (var->_type == VAL_FLOAT) { + char *endptr; + float res = (float)strtod(trimmed.c_str(), &endptr); + if (endptr == trimmed.c_str()) { + return Error(ERROR, PARSE_ERROR); + } else if (endptr == trimmed.c_str() + trimmed.size()) { + // We've parsed all of it, have we? + var->setFloat(res); + } else { + return Error(ERROR, PARSE_ERROR); + assert(false); + // Something funny happened here. + } + } else if (var->_type == VAL_BOOL) { + Common::String str = Common::String(trimmed); + bool valAsBool; + if (Common::parseBool(trimmed, valAsBool)) { + var->setBool(valAsBool); + } else { + return Error(ERROR, PARSE_ERROR); + } + } else if (var->_type == VAL_STRING) { + var->setString(trimmed); + } else { + return Error(ERROR, NOT_YET_IMPLEMENTED); + } + return Error(SUCCESS, OK); +} + +void DebuggerController::showFps(bool show) { + _engine->_game->setShowFPS(show); +} + +Common::Array<BreakpointInfo> DebuggerController::getBreakpoints() const { + assert(SCENGINE); + Common::Array<BreakpointInfo> breakpoints; + for (uint i = 0; i < SCENGINE->_breakpoints.size(); i++) { + BreakpointInfo bpInfo; + bpInfo._filename = SCENGINE->_breakpoints[i]->getFilename(); + bpInfo._line = SCENGINE->_breakpoints[i]->getLine(); + bpInfo._hits = SCENGINE->_breakpoints[i]->getHits(); + bpInfo._enabled = SCENGINE->_breakpoints[i]->isEnabled(); + breakpoints.push_back(bpInfo); + } + return breakpoints; +} + +Common::Array<WatchInfo> DebuggerController::getWatchlist() const { + Common::Array<WatchInfo> watchlist; + for (uint i = 0; i < SCENGINE->_watches.size(); i++) { + WatchInfo watchInfo; + watchInfo._filename = SCENGINE->_watches[i]->getFilename(); + watchInfo._symbol = SCENGINE->_watches[i]->getSymbol(); + watchlist.push_back(watchInfo); + } + return watchlist; +} + +uint32 DebuggerController::getLastLine() const { + return _lastLine; +} + +Common::String DebuggerController::getSourcePath() const { + return _sourceListingProvider->getPath(); +} + +Error DebuggerController::setSourcePath(const Common::String &sourcePath) { + ErrorCode err = _sourceListingProvider->setPath(sourcePath); + return Error((err == OK ? SUCCESS : ERROR), err); +} + +Listing* DebuggerController::getListing(Error* &error) { + delete (error); + if (_lastScript == nullptr) { + error = new Error(ERROR, NOT_ALLOWED); + return nullptr; + } + ErrorCode err; + Listing* res = _sourceListingProvider->getListing(SCENGINE->_currentScript->_filename, err); + error = new Error(err == OK ? SUCCESS : ERROR, err); + return res; +} + +Common::Array<TopEntry> DebuggerController::getTop() const { + Common::Array<TopEntry> res; + assert(SCENGINE); + for (uint i = 0; i < SCENGINE->_scripts.size(); i++) { + TopEntry entry; + entry.filename = SCENGINE->_scripts[i]->_filename; + entry.current = (SCENGINE->_scripts[i] == SCENGINE->_currentScript); + res.push_back(entry); + } + return res; +} + +} // end of namespace Wintermute diff --git a/engines/wintermute/debugger/debugger_controller.h b/engines/wintermute/debugger/debugger_controller.h new file mode 100644 index 0000000000..8c720281db --- /dev/null +++ b/engines/wintermute/debugger/debugger_controller.h @@ -0,0 +1,119 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WINTERMUTE_DEBUGGER_ADAPTER_H +#define WINTERMUTE_DEBUGGER_ADAPTER_H + +#include "common/str.h" +#include "engines/wintermute/coll_templ.h" +#include "engines/wintermute/wintermute.h" +#include "engines/wintermute/debugger/listing_providers/source_listing_provider.h" +#include "script_monitor.h" +#include "error.h" +#include "listing.h" +namespace Wintermute { + +class ScScript; +class DebuggableScript; +class ScValue; + +struct BreakpointInfo { + Common::String _filename; + int _line; + int _hits; + bool _enabled; +}; + +struct WatchInfo { + Common::String _filename; + Common::String _symbol; + int _hits; + bool _enabled; +}; + +struct TopEntry { + bool current; + Common::String filename; + int watches; + int breakpointInfo; +}; + +class DebuggerController : public ScriptMonitor { + SourceListingProvider *_sourceListingProvider; + const WintermuteEngine *_engine; + DebuggableScript *_lastScript; + uint32 _lastDepth; + uint32 _lastLine; + void clear(); + bool bytecodeExists(const Common::String &filename); +public: + DebuggerController(WintermuteEngine *vm); + ~DebuggerController(); + Common::Array<TopEntry> getTop() const; + /** + * Get the last line # we've stopped at + */ + uint32 getLastLine() const; + Error addBreakpoint(const char *filename, int line); + Error removeBreakpoint(uint id); + Error disableBreakpoint(uint id); + Error enableBreakpoint(uint id); + Error addWatch(const char *filename, const char *symbol); + Error removeWatchpoint(uint id); + Error disableWatchpoint(uint id); + Error enableWatchpoint(uint id); + Common::Array<BreakpointInfo> getBreakpoints() const; + Common::Array<WatchInfo> getWatchlist() const; + /** + * @brief step one instruction + */ + Error step(); + /** + * @brief continue execution and don't step until next breakpoint + */ + Error stepContinue(); + /** + * @brief continue execution and don't step until the current activation record is popped + */ + Error stepFinish(); + /** + * @brief read value for a variable accessible from within the current scope. + */ + Common::String readValue(const Common::String &name, Error *error); + /** + * @brief set value for a variable accessible from within the current scope. + */ + Error setValue(const Common::String &name, const Common::String &value, ScValue*&var); + Error setSourcePath(const Common::String &sourcePath); + Common::String getSourcePath() const; + Listing *getListing(Error* &err); + void showFps(bool show); + /** + * Inherited from ScriptMonitor + */ + void onBreakpoint(const Breakpoint *breakpoint, DebuggableScript *script); + void onWatch(const Watch *watch, DebuggableScript *script); + void notifyStep(DebuggableScript *script); +}; +} + +#endif // WINTERMUTE_DEBUGGER_H diff --git a/engines/wintermute/debugger/error.cpp b/engines/wintermute/debugger/error.cpp new file mode 100644 index 0000000000..dd6e41c7bc --- /dev/null +++ b/engines/wintermute/debugger/error.cpp @@ -0,0 +1,137 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "error.h" +#include "engines/wintermute/debugger.h" + +namespace Wintermute { + +Error::Error(ErrorLevel errorLevel, + ErrorCode errorCode, + Common::String errorExtraString, + int errorExtraInt) : + _errorLevel(errorLevel), + _errorCode(errorCode), + _errorExtraInt(errorExtraInt), + _errorExtraString(errorExtraString){} + +Error::Error(ErrorLevel errorLevel, + ErrorCode errorCode, + int errorExtraInt) : + _errorLevel(errorLevel), + _errorCode(errorCode), + _errorExtraInt(errorExtraInt), + _errorExtraString(""){} + +Error::Error(ErrorLevel errorLevel, + ErrorCode errorCode) : + _errorLevel(errorLevel), + _errorCode(errorCode), + _errorExtraInt(0), + _errorExtraString(""){} + +Error::Error(ErrorLevel errorLevel, + ErrorCode errorCode, + Common::String errorExtraString) : + _errorLevel(errorLevel), + _errorCode(errorCode), + _errorExtraInt(0), + _errorExtraString(errorExtraString){} + +ErrorLevel Error::getErrorLevel() const { + return _errorLevel; +} + +ErrorCode Error::getErrorCode() const { + return _errorCode; +} + +Common::String Error::getErrorLevelStr() const { + switch (this->_errorLevel) { + case SUCCESS: + return "SUCCESS"; + break; + case NOTICE: + return "NOTICE"; + break; + case WARNING: + return "WARNING"; + break; + case ERROR: + return "ERROR"; + break; + } + return "SUCCESS"; +} + +Common::String Error::getErrorDisplayStr() const { + + Common::String errorStr; + + switch (this->_errorLevel) { + case SUCCESS: + errorStr += "OK!"; + break; + case WARNING: + errorStr += "WARNING: "; + break; + case ERROR: + errorStr += "ERROR: "; + break; + case NOTICE: + errorStr += "NOTICE: "; + break; + default: + // Um... + break; + } + + switch (this->_errorCode) { + case OK: + break; + case NOT_ALLOWED: + errorStr += "Could not execute requested operation. This is allowed only after a break."; + break; + case NO_SUCH_SOURCE: + errorStr += Common::String::format("Can't find source for %s. Double check you source path.", this->_errorExtraString.c_str()); + break; + case NO_SUCH_BYTECODE: + errorStr += Common::String::format("No such script: %s. Can't find bytecode; double check the script path.", this->_errorExtraString.c_str()); + break; + case SOURCE_PATH_NOT_SET: + errorStr += Common::String("Source path not set. Source won't be displayed. Try 'help " + Common::String(SET_PATH_CMD) + "'."); + break; + case NO_SUCH_BREAKPOINT: + errorStr += Common::String::format("No such breakpoint %d.", this->_errorExtraInt); + break; + case WRONG_TYPE: + errorStr += Common::String::format("Incompatible type: %s.", this->_errorExtraString.c_str()); + break; + default: + errorStr += Common::String::format("Unknown condition %d", this->_errorCode); + break; + } + + return errorStr; +} + +} // End namespace Wintermute diff --git a/engines/wintermute/debugger/error.h b/engines/wintermute/debugger/error.h new file mode 100644 index 0000000000..4e5b973445 --- /dev/null +++ b/engines/wintermute/debugger/error.h @@ -0,0 +1,73 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ERROR_H_ +#define ERROR_H_ + +#include "common/str.h" + +namespace Wintermute { + +enum ErrorLevel { + SUCCESS, + NOTICE, + WARNING, + ERROR +}; + +enum ErrorCode { + OK, + NO_SUCH_SOURCE, + COULD_NOT_OPEN, + NO_SUCH_LINE, + NOT_ALLOWED, + NO_SUCH_BYTECODE, + DUPLICATE_BREAKPOINT, + NO_SUCH_BREAKPOINT, + WRONG_TYPE, + PARSE_ERROR, + NOT_YET_IMPLEMENTED, + SOURCE_PATH_NOT_SET, + ILLEGAL_PATH, + UNKNOWN_ERROR +}; + + +class Error { + const ErrorLevel _errorLevel; + const ErrorCode _errorCode; + const int _errorExtraInt; + const Common::String _errorExtraString; +public: + Error(ErrorLevel, ErrorCode); + Error(ErrorLevel, ErrorCode, int errorExtraInt); + Error(ErrorLevel, ErrorCode, Common::String errorExtraString); + Error(ErrorLevel, ErrorCode, Common::String errorExtraString, int errorExtraInt); + ErrorLevel getErrorLevel() const; + ErrorCode getErrorCode() const; + Common::String getErrorLevelStr() const; + Common::String getErrorDisplayStr() const; +}; + +} // End of namespace Wintermute + +#endif /* ERROR_H_ */ diff --git a/engines/wintermute/debugger/listing.cpp b/engines/wintermute/debugger/listing.cpp new file mode 100644 index 0000000000..b8707fb842 --- /dev/null +++ b/engines/wintermute/debugger/listing.cpp @@ -0,0 +1,46 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "listing.h" +#include "common/array.h" + +namespace Wintermute { + +Common::Array<ListingLine> Listing::getLines(uint begin, uint end) { + assert(begin <= end); + Common::Array<ListingLine> ret; + for (uint i = begin; i <= end; i++) { + ListingLine listingline; + listingline.number = i; + listingline.text = getLine(i); + ret.push_back(listingline); + } + return ret; +} + +Common::Array<ListingLine> Listing::getLines(uint centre, uint before, uint after) { + uint begin = MAX(centre - before, (uint)1); // Line numbers start from 1 + uint end = MIN(centre + after, (uint)(getLength() - 1)); // Line numbers start from 1 + return getLines(begin, end); +} + +} // End of namespace Wintermute diff --git a/engines/wintermute/debugger/listing.h b/engines/wintermute/debugger/listing.h new file mode 100644 index 0000000000..2ef21b702d --- /dev/null +++ b/engines/wintermute/debugger/listing.h @@ -0,0 +1,64 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef LISTING_H_ +#define LISTING_H_ + +#include "common/array.h" + + +namespace Common { + +class String; + +} + +namespace Wintermute { + +struct ListingLine { + uint number; + Common::String text; +}; + +class Listing { +public: + virtual ~Listing() {}; + /** + * @brief get the listing length (in lines) + */ + virtual uint getLength() const = 0; + /** + * @brief return a specific line from a listing + * @param n line number + */ + virtual Common::String getLine(uint n) = 0; + /** + * @brief shorthand to get a lump of lines instead of calling getLine a number of times + * Generally you won't need to redefine these + */ + virtual Common::Array<ListingLine> getLines(uint centre, uint before, uint after); + virtual Common::Array<ListingLine> getLines(uint beginning, uint end); +}; + +} // End of namespace Wintermute + +#endif /* LISTING_H_ */ diff --git a/engines/wintermute/debugger/listing_provider.h b/engines/wintermute/debugger/listing_provider.h new file mode 100644 index 0000000000..b5ea23e49b --- /dev/null +++ b/engines/wintermute/debugger/listing_provider.h @@ -0,0 +1,42 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef LISTING_PROVIDER_H_ +#define LISTING_PROVIDER_H_ + +#include "listing.h" +#include "engines/wintermute/debugger/error.h" + +namespace Wintermute { + +class ListingProvider { +public: + virtual ~ListingProvider() {}; + /** + * Get a listing. When implementing this, the result should be safe to delete for the caller. + */ + virtual Listing *getListing(const Common::String &filename, ErrorCode &error) = 0; +}; + +} // End of namespace Wintermute + +#endif /* LISTING_PROVIDER_H_ */ diff --git a/engines/wintermute/debugger/listing_providers/basic_source_listing_provider.cpp b/engines/wintermute/debugger/listing_providers/basic_source_listing_provider.cpp new file mode 100644 index 0000000000..30d29ee23e --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/basic_source_listing_provider.cpp @@ -0,0 +1,92 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "basic_source_listing_provider.h" +#include "engines/wintermute/base/base_file_manager.h" + +namespace Wintermute { +BasicSourceListingProvider::BasicSourceListingProvider() : _fsDirectory(nullptr) { +} + +BasicSourceListingProvider::~BasicSourceListingProvider() { +} + +SourceListing *BasicSourceListingProvider::getListing(const Common::String &filename, ErrorCode &_err) { + _err = OK; + if (!_fsDirectory) { + _err = SOURCE_PATH_NOT_SET; + return nullptr; + }; + + Common::String unixFilename; + + for (uint i = 0; i < filename.size(); i++) { + if (filename[i] == '\\') { + unixFilename.insertChar('/', unixFilename.size()); + } else { + unixFilename.insertChar(filename[i], unixFilename.size()); + } + } + + Common::SeekableReadStream *file = _fsDirectory->createReadStreamForMember(unixFilename); + Common::Array<Common::String> strings; + + if (!file) { + _err = NO_SUCH_SOURCE; + } else { + if (file->err()) { + _err = UNKNOWN_ERROR; + } + while (!file->eos()) { + strings.push_back(file->readLine()); + if (file->err()) { + _err = UNKNOWN_ERROR; + } + } + } + + if (_err == OK) { + return new SourceListing(strings); + } else { + return nullptr; + } +} + +ErrorCode BasicSourceListingProvider::setPath(const Common::String &path) { + if (path == "") + return ILLEGAL_PATH; + delete _fsDirectory; + Common::FSNode node(path); + if (node.exists() && node.isDirectory()) { + _fsDirectory = new Common::FSDirectory(node, 64); + return OK; + } else { + return COULD_NOT_OPEN; + } +} + +Common::String BasicSourceListingProvider::getPath() const { + if (!_fsDirectory) return ""; + return _fsDirectory->getFSNode().getPath(); +} + +} diff --git a/engines/wintermute/debugger/listing_providers/basic_source_listing_provider.h b/engines/wintermute/debugger/listing_providers/basic_source_listing_provider.h new file mode 100644 index 0000000000..e242205578 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/basic_source_listing_provider.h @@ -0,0 +1,44 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BASIC_SOURCE_LISTING_PROVIDER_H_ +#define BASIC_SOURCE_LISTING_PROVIDER_H_ + +#include "engines/wintermute/debugger/listing_provider.h" +#include "source_listing_provider.h" +#include "source_listing.h" +#include "common/fs.h" + +namespace Wintermute { + +class BasicSourceListingProvider : public SourceListingProvider { + Common::FSDirectory *_fsDirectory; +public: + BasicSourceListingProvider(); + virtual ~BasicSourceListingProvider(); + SourceListing *getListing(const Common::String &filename, ErrorCode &err); + ErrorCode setPath(const Common::String &path); + Common::String getPath() const; +}; + +} +#endif /* BASIC_SOURCE_LISTING_PROVIDER_H_ */ diff --git a/engines/wintermute/debugger/listing_providers/blank_listing.cpp b/engines/wintermute/debugger/listing_providers/blank_listing.cpp new file mode 100644 index 0000000000..928c91dc7f --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/blank_listing.cpp @@ -0,0 +1,38 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "blank_listing.h" +#include "limits.h" + +namespace Wintermute { + +BlankListing::BlankListing(const Common::String filename) : _filename(filename) {} + +uint BlankListing::getLength() const { return UINT_MAX; } + +Common::String BlankListing::getLine(uint n) { + return "<no source for " + _filename + " ~~~ line: " + Common::String::format("%d", n) + ">"; +} +BlankListing::~BlankListing() {} + +} + diff --git a/engines/wintermute/debugger/listing_providers/blank_listing.h b/engines/wintermute/debugger/listing_providers/blank_listing.h new file mode 100644 index 0000000000..8c5ea19aa7 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/blank_listing.h @@ -0,0 +1,39 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BLANK_LISTING_H_ +#define BLANK_LISTING_H_ +#include "engines/wintermute/debugger/listing.h" + +namespace Wintermute { +class BlankListing : public Listing { + const Common::String _filename; +public: + BlankListing(const Common::String filename); + virtual ~BlankListing(); + virtual uint getLength() const; + virtual Common::String getLine(uint n); +}; + +} // End of namespace Wintermute + +#endif diff --git a/engines/wintermute/debugger/listing_providers/blank_listing_provider.cpp b/engines/wintermute/debugger/listing_providers/blank_listing_provider.cpp new file mode 100644 index 0000000000..58e9e7e156 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/blank_listing_provider.cpp @@ -0,0 +1,35 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "blank_listing_provider.h" +#include "blank_listing.h" +namespace Wintermute { +BlankListingProvider::BlankListingProvider() {} + +BlankListingProvider::~BlankListingProvider() {} + +Listing *BlankListingProvider::getListing(const Common::String &filename, ErrorCode &error) { + Listing *l = new BlankListing(filename); + error = OK; + return l; // Delete this sometime please. +} +} // End of namespace Wintermute diff --git a/engines/wintermute/debugger/listing_providers/blank_listing_provider.h b/engines/wintermute/debugger/listing_providers/blank_listing_provider.h new file mode 100644 index 0000000000..e583455c92 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/blank_listing_provider.h @@ -0,0 +1,38 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BLANK_LISTING_PROVIDER_H_ +#define BLANK_LISTING_PROVIDER_H_ + +#include "engines/wintermute/debugger/listing.h" +#include "engines/wintermute/debugger/listing_provider.h" +#include "engines/wintermute/debugger/error.h" + +namespace Wintermute { +class BlankListingProvider : public ListingProvider { +public: + BlankListingProvider(); + ~BlankListingProvider(); + Listing *getListing(const Common::String &filename, ErrorCode &error); +}; +} // End of namespace Wintermute +#endif diff --git a/engines/wintermute/debugger/listing_providers/cached_source_listing_provider.cpp b/engines/wintermute/debugger/listing_providers/cached_source_listing_provider.cpp new file mode 100644 index 0000000000..20fe708380 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/cached_source_listing_provider.cpp @@ -0,0 +1,80 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "cached_source_listing_provider.h" +#include "basic_source_listing_provider.h" +#include "blank_listing_provider.h" +#include "source_listing.h" + +namespace Wintermute { + +CachedSourceListingProvider::CachedSourceListingProvider() { + _sourceListingProvider = new BasicSourceListingProvider(); + _fallbackListingProvider = new BlankListingProvider(); +} + +CachedSourceListingProvider::~CachedSourceListingProvider() { + delete _sourceListingProvider; + delete _fallbackListingProvider; + for (Common::HashMap<Common::String, SourceListing*>::iterator it = _cached.begin(); + it != _cached.end(); it++) { + delete (it->_value); + } +} + +Listing *CachedSourceListingProvider::getListing(const Common::String &filename, Wintermute::ErrorCode &error) { + if (_cached.contains(filename)) { + error = OK; + SourceListing *copy = new SourceListing(*_cached.getVal(filename)); + return copy; + } else { + ErrorCode inner; + SourceListing *res = _sourceListingProvider->getListing(filename, inner); + if (inner == OK) { + SourceListing *copy = new SourceListing(*res); + _cached.setVal(filename, copy); // The cached copy is deleted on destruction + return res; + } else { + delete res; + return _fallbackListingProvider->getListing(filename, error); + } + } +} + +void CachedSourceListingProvider::invalidateCache() { + for (Common::HashMap<Common::String, SourceListing*>::iterator it = _cached.begin(); + it != _cached.end(); it++) { + delete (it->_value); + } + _cached.clear(); +} + +ErrorCode CachedSourceListingProvider::setPath(const Common::String &path) { + invalidateCache(); + return _sourceListingProvider->setPath(path); +} + +Common::String CachedSourceListingProvider::getPath() const { + return _sourceListingProvider->getPath(); +} + +} // End of namespace Wintermute diff --git a/engines/wintermute/debugger/listing_providers/cached_source_listing_provider.h b/engines/wintermute/debugger/listing_providers/cached_source_listing_provider.h new file mode 100644 index 0000000000..6e4925f2b4 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/cached_source_listing_provider.h @@ -0,0 +1,52 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef CACHED_LISTING_PROVIDER_H_ +#define CACHED_LISTING_PROVIDER_H_ + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "engines/wintermute/debugger/error.h" +#include "source_listing_provider.h" + +namespace Wintermute { + +class BasicSourceListingProvider; +class BlankListingProvider; +class Listing; + +class CachedSourceListingProvider : public SourceListingProvider { + BasicSourceListingProvider *_sourceListingProvider; + BlankListingProvider *_fallbackListingProvider; + Common::HashMap<Common::String, SourceListing *> _cached; + void invalidateCache(); +public: + CachedSourceListingProvider(); + virtual ~CachedSourceListingProvider(); + ErrorCode setPath(const Common::String &path); + Common::String getPath() const; + Listing *getListing(const Common::String &filename, ErrorCode &err); +}; + +} // End of namespace Wintermute + +#endif /* CACHED_LISTING_PROVIDER_H_ */ diff --git a/engines/wintermute/debugger/listing_providers/source_listing.cpp b/engines/wintermute/debugger/listing_providers/source_listing.cpp new file mode 100644 index 0000000000..ff81e20f02 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/source_listing.cpp @@ -0,0 +1,57 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "source_listing.h" + +namespace Wintermute { + +SourceListing::SourceListing(const Common::Array<Common::String> &strings) : _strings(strings) {} + +SourceListing::~SourceListing() {} + +uint SourceListing::getLength() const { + return _strings.size(); +} + +Common::String SourceListing::getLine(uint n) { + uint index = n - 1; // Line numbers start from 1, arrays from 0 + /* + * Clients should not ask for a line number that + * is not in the source file. + * 0 is undefined, n - 1 is undefined. + * It is easy for the client to check that n > 0 + * and n < getLength(), so it should just not happen. + * We return '^', after vim, to misbehaving clients. + */ + if (n == 0) { + return Common::String("^"); + } + if (index < getLength()) { + return _strings[index]; + } else { + return Common::String("^"); + } +} + +} // End of namespace Wintermute + + diff --git a/engines/wintermute/debugger/listing_providers/source_listing.h b/engines/wintermute/debugger/listing_providers/source_listing.h new file mode 100644 index 0000000000..bf08578218 --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/source_listing.h @@ -0,0 +1,37 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SOURCE_LISTING_H_ +#define SOURCE_LISTING_H_ +#include "engines/wintermute/debugger/listing.h" + +namespace Wintermute { +class SourceListing : public Listing { + const Common::Array<Common::String> _strings; +public: + SourceListing(const Common::Array<Common::String> &strings); + virtual ~SourceListing(); + virtual uint getLength() const; + virtual Common::String getLine(uint n); +}; +} +#endif /* DUMMY_LISTING_H_ */ diff --git a/engines/wintermute/debugger/listing_providers/source_listing_provider.h b/engines/wintermute/debugger/listing_providers/source_listing_provider.h new file mode 100644 index 0000000000..18f05e56ed --- /dev/null +++ b/engines/wintermute/debugger/listing_providers/source_listing_provider.h @@ -0,0 +1,49 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SOURCE_LISTING_PROVIDER_H_ +#define SOURCE_LISTING_PROVIDER_H_ + +#include "engines/wintermute/debugger/error.h" +#include "engines/wintermute/debugger/listing_provider.h" +#include "common/str.h" + +namespace Wintermute { + +class SourceListing; +class Listing; + +class SourceListingProvider : ListingProvider { +public: + virtual ~SourceListingProvider() {}; + /** + * Get a listing. When implementing this, the result should be safe to delete for the caller. + */ + virtual Listing *getListing(const Common::String &filename, ErrorCode &err) = 0; + virtual ErrorCode setPath(const Common::String &path) = 0; + virtual Common::String getPath() const = 0; + +}; + +} // End of namespace Wintermute + +#endif /* SOURCE_LISTING_PROVIDER_H_ */ diff --git a/engines/wintermute/debugger/script_monitor.cpp b/engines/wintermute/debugger/script_monitor.cpp new file mode 100644 index 0000000000..2e9370c923 --- /dev/null +++ b/engines/wintermute/debugger/script_monitor.cpp @@ -0,0 +1,26 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "script_monitor.h" + +namespace Wintermute { +} diff --git a/engines/wintermute/debugger/script_monitor.h b/engines/wintermute/debugger/script_monitor.h new file mode 100644 index 0000000000..e9559e2ade --- /dev/null +++ b/engines/wintermute/debugger/script_monitor.h @@ -0,0 +1,43 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCRIPTMONITOR_H_ +#define SCRIPTMONITOR_H_ + +namespace Wintermute { + +class DebuggableScript; +class Breakpoint; +class Watch; + +class ScriptMonitor { +public: + + virtual ~ScriptMonitor() {}; + virtual void notifyStep(DebuggableScript* script) = 0; + virtual void onBreakpoint(const Breakpoint* breakpoint, DebuggableScript* script) = 0; + virtual void onWatch(const Watch* watch, DebuggableScript* script) = 0; +}; + +} // End of namespace Wintermute + +#endif /* SCRIPTMONITOR_H_ */ diff --git a/engines/wintermute/debugger/watch.cpp b/engines/wintermute/debugger/watch.cpp new file mode 100644 index 0000000000..410756fdc8 --- /dev/null +++ b/engines/wintermute/debugger/watch.cpp @@ -0,0 +1,42 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "watch.h" +#include "watch_instance.h" +#include "script_monitor.h" + +namespace Wintermute { + +Watch::Watch(const Common::String &filename, const Common::String &symbol, ScriptMonitor* monitor) : _enabled(false), _filename(filename), _symbol(symbol), _monitor(monitor) {} + +Watch::~Watch() { /* Nothing to take care of in here */ } + +void Watch::trigger(WatchInstance* instance) { + _monitor->onWatch(this, instance->_script); +} + +Common::String Watch::getFilename() const { return _filename; } +Common::String Watch::getSymbol() const { return _symbol; } +bool Watch::isEnabled() const { return _enabled; } +void Watch::enable() { _enabled = true; } +void Watch::disable() { _enabled = false; } +} diff --git a/engines/wintermute/debugger/watch.h b/engines/wintermute/debugger/watch.h new file mode 100644 index 0000000000..cbffe43b41 --- /dev/null +++ b/engines/wintermute/debugger/watch.h @@ -0,0 +1,51 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WATCH_H_ +#define WATCH_H_ + +#include "common/str.h" + +namespace Wintermute { + +class ScValue; +class ScScript; +class WatchInstance; +class ScriptMonitor; + +class Watch { + const Common::String _filename; + const Common::String _symbol; + int _enabled; + ScriptMonitor *_monitor; +public: + Watch(const Common::String &filename, const Common::String &symbol, ScriptMonitor*); + Common::String getFilename() const; + Common::String getSymbol() const; + bool isEnabled() const; + void enable(); + void disable(); + void trigger(WatchInstance*); + virtual ~Watch(); +}; +} +#endif /* WATCH_H_ */ diff --git a/engines/wintermute/debugger/watch_instance.cpp b/engines/wintermute/debugger/watch_instance.cpp new file mode 100644 index 0000000000..2d31221ad4 --- /dev/null +++ b/engines/wintermute/debugger/watch_instance.cpp @@ -0,0 +1,53 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "watch_instance.h" +#include "engines/wintermute/base/scriptables/script_value.h" +#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" +#include "engines/wintermute/debugger/watch.h" + +namespace Wintermute { + +WatchInstance::WatchInstance(Watch* watch, DebuggableScript* script) : _watch(watch), _script(script), _lastValue(nullptr) {} +WatchInstance::~WatchInstance() { delete _lastValue; } + +void WatchInstance::evaluate() { + if (_watch->isEnabled()) { + if (!_watch->getFilename().compareTo(_script->_filename)) { + + if(_lastValue == nullptr) { + _lastValue = new ScValue(_script->_gameRef); + // ^^ This here is NULL by default + } + ScValue* currentValue = _script->resolveName(_watch->getSymbol()); + if(ScValue::compare( + currentValue, + _lastValue + )) { + _lastValue->copy(currentValue); + _watch->trigger(this); + } + delete currentValue; + } + } +} +} // End of namespace Wintermute diff --git a/engines/wintermute/debugger/watch_instance.h b/engines/wintermute/debugger/watch_instance.h new file mode 100644 index 0000000000..84fb62968d --- /dev/null +++ b/engines/wintermute/debugger/watch_instance.h @@ -0,0 +1,44 @@ +/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WATCH_INSTANCE_H_ +#define WATCH_INSTANCE_H_ + +namespace Wintermute { +class Watch; +class ScValue; +class DebuggableScript; + +class WatchInstance { + Watch* _watch; + ScValue *_lastValue; + DebuggableScript* _script; +public: + WatchInstance (Watch* watch, DebuggableScript* script); + ~WatchInstance(); + void evaluate(); +friend class DebuggableScript; +friend class Watch; +}; +} // End of namespace Wintermute + +#endif /* WATCH_INSTANCE_H_ */ diff --git a/engines/wintermute/module.mk b/engines/wintermute/module.mk index 4c95314a02..2c9c2e0180 100644 --- a/engines/wintermute/module.mk +++ b/engines/wintermute/module.mk @@ -27,6 +27,8 @@ MODULE_OBJS := \ ad/ad_talk_holder.o \ ad/ad_talk_node.o \ ad/ad_waypoint_group.o \ + base/scriptables/debuggable/debuggable_script.o \ + base/scriptables/debuggable/debuggable_script_engine.o \ base/scriptables/script.o \ base/scriptables/script_engine.o \ base/scriptables/script_stack.o \ @@ -88,6 +90,18 @@ MODULE_OBJS := \ base/saveload.o \ base/save_thumb_helper.o \ base/timer.o \ + debugger/breakpoint.o \ + debugger/debugger_controller.o \ + debugger/error.o \ + debugger/listing_providers/blank_listing.o \ + debugger/listing_providers/blank_listing_provider.o \ + debugger/listing_providers/basic_source_listing_provider.o \ + debugger/listing_providers/cached_source_listing_provider.o \ + debugger/listing_providers/source_listing.o \ + debugger/listing.o \ + debugger/script_monitor.o \ + debugger/watch.o \ + debugger/watch_instance.o \ detection.o \ math/math_util.o \ math/matrix4.o \ diff --git a/engines/wintermute/wintermute.cpp b/engines/wintermute/wintermute.cpp index 955f2dc1c2..05dfb961cb 100644 --- a/engines/wintermute/wintermute.cpp +++ b/engines/wintermute/wintermute.cpp @@ -41,6 +41,7 @@ #include "engines/wintermute/base/base_file_manager.h" #include "engines/wintermute/base/gfx/base_renderer.h" #include "engines/wintermute/base/scriptables/script_engine.h" +#include "engines/wintermute/debugger/debugger_controller.h" namespace Wintermute { @@ -49,6 +50,7 @@ namespace Wintermute { WintermuteEngine::WintermuteEngine() : Engine(g_system) { _game = new AdGame(""); _debugger = nullptr; + _dbgController = nullptr; _trigDebug = false; _gameDescription = nullptr; } @@ -76,6 +78,7 @@ WintermuteEngine::WintermuteEngine(OSystem *syst, const WMEGameDescription *desc _game = nullptr; _debugger = nullptr; + _dbgController = nullptr; _trigDebug = false; } @@ -112,6 +115,7 @@ Common::Error WintermuteEngine::run() { } // Create debugger console. It requires GFX to be initialized + _dbgController = new DebuggerController(this); _debugger = new Console(this); // DebugMan.enableDebugChannel("enginelog"); @@ -171,7 +175,6 @@ int WintermuteEngine::init() { } _game->initialize3(); - // initialize sound manager (non-fatal if we fail) ret = _game->_soundMgr->initialize(); if (DID_FAIL(ret)) { @@ -200,6 +203,8 @@ int WintermuteEngine::init() { _game->loadGame(slot); } + _game->_scEngine->attachMonitor(_dbgController); + // all set, ready to go return 0; } diff --git a/engines/wintermute/wintermute.h b/engines/wintermute/wintermute.h index f61a809b22..a8f9a18530 100644 --- a/engines/wintermute/wintermute.h +++ b/engines/wintermute/wintermute.h @@ -33,6 +33,8 @@ namespace Wintermute { class Console; class BaseGame; class SystemClassRegistry; +class DebuggerController; + // our engine debug channels enum { kWintermuteDebugLog = 1 << 0, // The debug-logs from the original engine @@ -49,7 +51,7 @@ public: WintermuteEngine(); ~WintermuteEngine(); - virtual GUI::Debugger *getDebugger() { return _debugger; } + virtual Wintermute::Console *getConsole() { return _debugger; } void trigDebugger() { _trigDebug = true; } virtual Common::Error run(); @@ -66,11 +68,13 @@ private: int init(); void deinit(); int messageLoop(); - GUI::Debugger *_debugger; + Wintermute::Console *_debugger; BaseGame *_game; + Wintermute::DebuggerController *_dbgController; const WMEGameDescription *_gameDescription; friend class Console; + friend class DebuggerController; }; } // End of namespace Wintermute |