diff options
-rw-r--r-- | engines/mohawk/livingbooks.cpp | 18 | ||||
-rw-r--r-- | engines/mohawk/livingbooks_code.cpp | 904 | ||||
-rw-r--r-- | engines/mohawk/livingbooks_code.h | 169 |
3 files changed, 792 insertions, 299 deletions
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp index 3f53eae4b8..3d22683739 100644 --- a/engines/mohawk/livingbooks.cpp +++ b/engines/mohawk/livingbooks.cpp @@ -2690,24 +2690,6 @@ void LBItem::setNextTime(uint16 min, uint16 max, uint32 start) { debug(9, "nextTime is now %d frames away", _nextTime - (uint)(_vm->_system->getMillis() / 16)); } -bool LBValue::operator==(const LBValue &x) const { - if (type != x.type) return false; - - switch (type) { - case kLBValueString: - return string == x.string; - - case kLBValueInteger: - return integer == x.integer; - default: - error("Unknown type when testing for equality"); - } -} - -bool LBValue::operator!=(const LBValue &x) const { - return !(*this == x); -} - enum LBTokenType { kLBNoToken, kLBNameToken, diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp index a11ac1cfe9..c0718d941a 100644 --- a/engines/mohawk/livingbooks_code.cpp +++ b/engines/mohawk/livingbooks_code.cpp @@ -28,18 +28,76 @@ namespace Mohawk { +bool LBValue::operator==(const LBValue &x) const { + if (type != x.type) { + if (isNumeric() && x.isNumeric()) + return toDouble() == x.toDouble(); + else + return false; + } + + switch (type) { + case kLBValueString: + return string == x.string; + case kLBValueInteger: + return integer == x.integer; + case kLBValueReal: + return real == x.real; + case kLBValuePoint: + return point == x.point; + case kLBValueRect: + return rect == x.rect; + case kLBValueItemPtr: + return item == x.item; + default: + error("Unknown type when testing for equality"); + } +} + +bool LBValue::operator!=(const LBValue &x) const { + return !(*this == x); +} + +bool LBValue::isNumeric() const { + if (type == kLBValueInteger || type == kLBValueReal) + return true; + + // TODO: string checks + + return false; +} + +bool LBValue::isZero() const { + return toInt() == 0; // FIXME +} + +int LBValue::toInt() const { + return integer; // FIXME +} + +double LBValue::toDouble() const { + return real; // FIXME +} + LBCode::LBCode(MohawkEngine_LivingBooks *vm) : _vm(vm) { Common::SeekableSubReadStreamEndian *bcodStream = _vm->wrapStreamEndian(ID_BCOD, 1000); + uint32 totalSize = bcodStream->readUint32(); if (totalSize != (uint32)bcodStream->size()) error("BCOD had size %d, but claimed to be of size %d", bcodStream->size(), totalSize); - size = bcodStream->readUint32(); - if (size + 8 > totalSize) - error("BCOD code was of size %d, beyond size %d", size, totalSize); - data = new byte[size]; - bcodStream->read(data, size); + _size = bcodStream->readUint32(); + if (_size + 8 > totalSize) + error("BCOD code was of size %d, beyond size %d", _size, totalSize); + + _data = new byte[_size]; + bcodStream->read(_data, _size); + uint16 pos = 0; while (bcodStream->pos() < bcodStream->size()) { + if (bcodStream->pos() + 1 == bcodStream->size()) { + warning("ran out of bytes while reading strings"); + break; + } uint16 unknown = bcodStream->readUint16(); if (unknown != 0) { warning("unknown was %04x, not zero, while reading strings", unknown); @@ -48,287 +106,661 @@ LBCode::LBCode(MohawkEngine_LivingBooks *vm) : _vm(vm) { break; } Common::String string = _vm->readString(bcodStream); - strings[pos] = string; + _strings[pos] = string; debug(2, "read '%s' from BCOD at 0x%04x", string.c_str(), pos); pos += 2 + string.size() + 1; } } LBCode::~LBCode() { - delete[] data; + delete[] _data; } -Common::Array<LBValue> LBCode::readParams(LBItem *src, uint32 &offset) { - Common::Array<LBValue> params; - - if (offset + 1 >= size) - error("went off the end of code"); +LBValue LBCode::runCode(LBItem *src, uint32 offset) { + // TODO: re-entrancy issues? + _currSource = src; + _currOffset = offset; - byte numParams = data[offset]; - offset++; + return runCode(kTokenEndOfFile); +} - if (!numParams) { - debugN("()\n"); - return params; +void LBCode::nextToken() { + if (_currOffset + 1 >= _size) { + // TODO + warning("went off the end of code"); + _currToken = kTokenEndOfFile; + _currValue = LBValue(); + return; } - byte nextToken = data[offset]; - offset++; - if (nextToken != kLBCodeTokenOpenBracket) - error("missing ( before code parameter list (got %02x)", nextToken); - debugN("("); + _currToken = _data[_currOffset++]; - for (uint i = 0; i < numParams; i++) { - if (i != 0) { - nextToken = data[offset]; - offset++; - if (nextToken != ',') - error("missing , between code parameters (got %02x)", nextToken); - debugN(", "); + // We slurp any value associated with the parameter here too, to simplify things. + switch (_currToken) { + case kTokenIdentifier: + { + uint16 offset = READ_BE_UINT16(_data + _currOffset); + // TODO: check string exists + _currValue = _strings[offset]; + _currOffset += 2; } + break; - nextToken = data[offset]; - offset++; - - LBValue nextValue; - - switch (nextToken) { - case kLBCodeTokenLiteral: - { - byte literalType = data[offset]; - offset++; - if (literalType == kLBCodeLiteralInteger) { - uint16 intValue = READ_BE_UINT16(data + offset); - offset += 2; - nextValue.type = kLBValueInteger; - nextValue.integer = intValue; - debugN("%d", nextValue.integer); - } else - error("unknown literal type %02x in code", literalType); - } + case kTokenLiteral: + { + byte literalType = _data[_currOffset++]; + switch (literalType) { + case kLBCodeLiteralInteger: + _currValue = READ_BE_UINT16(_data + _currOffset); + _currOffset += 2; break; + default: + error("unknown kTokenLiteral type %02x", literalType); + } + } + break; - case kLBCodeTokenString: - { - uint16 stringOffset = READ_BE_UINT16(data + offset); - offset += 2; - // TODO: check string exists - nextValue.type = kLBValueString; - nextValue.string = strings[stringOffset]; - debugN("\"%s\"", nextValue.string.c_str()); - } - break; + case kTokenConstMode: + case kTokenConstEventId: + case 0x5e: // TODO: ?? + case kTokenKeycode: + _currValue = READ_BE_UINT16(_data + _currOffset); + _currOffset += 2; + break; - case kLBCodeTokenChar: - { - uint16 stringOffset = READ_BE_UINT16(data + offset); - offset += 2; - // TODO: check string exists - nextValue.type = kLBValueString; - nextValue.string = strings[stringOffset]; - debugN("'%s'", nextValue.string.c_str()); - } - break; + case kTokenGeneralCommand: + case kTokenItemCommand: + case kTokenNotifyCommand: + case kTokenPropListCommand: + case kTokenRectCommand: + _currValue = _data[_currOffset++]; + //_currValue = READ_BE_UINT16(_data + _currOffset); + //_currOffset += 2; + break; - case kLBCodeTokenLong: // FIXME: wrong? - { - uint32 intValue = READ_BE_UINT32(data + offset); - offset += 4; - nextValue.type = kLBValueInteger; - nextValue.integer = intValue; - debugN("%d", nextValue.integer); - } - break; + case kTokenString: + { + uint16 offset = READ_BE_UINT16(_data + _currOffset); + // TODO: check string exists + _currValue = _strings[offset]; + _currOffset += 2; + } + break; - case 0x31: - { - // TODO - uint16 intValue = READ_BE_UINT16(data + offset); - offset += 2; - nextValue.type = kLBValueInteger; - nextValue.integer = intValue; - debugN("%d", nextValue.integer); - } - break; + default: + _currValue = LBValue(); + break; + } +} - case 0x4d: - // TODO - runCodeCommand(src, offset); - break; +LBValue LBCode::runCode(byte terminator) { + LBValue result; - case 0x5f: - // keycode - nextValue.type = kLBValueInteger; - nextValue.integer = data[offset]; - debugN("%d", nextValue.integer); - offset++; - offset++; // TODO + while (true) { + nextToken(); + if (_currToken == kTokenEndOfFile) break; + parseStatement(); + if (_stack.size()) + result = _stack.pop(); + if (_currToken == terminator || _currToken == kTokenEndOfFile) + break; + if (_currToken != kTokenEndOfStatement && _currToken != kTokenEndOfFile) + error("missing EOS"); + debugN("\n"); + } - default: - error("unknown token %02x in code parameter", nextToken); - } + return result; +} - params.push_back(nextValue); +void LBCode::parseStatement() { + // FIXME: logical operators + parseComparisons(); +} + +void LBCode::parseComparisons() { + parseConcat(); + + if (_currToken != kTokenEquals && _currToken != kTokenLessThan && _currToken != kTokenGreaterThan && + _currToken != kTokenLessThanEq && _currToken != kTokenGreaterThanEq && _currToken != kTokenNotEq) + return; + byte comparison = _currToken; + switch (comparison) { + case kTokenEquals: + debugN(" == "); + break; + case kTokenLessThan: + debugN(" < "); + break; + case kTokenGreaterThan: + debugN(" > "); + break; + case kTokenLessThanEq: + debugN(" <= "); + break; + case kTokenGreaterThanEq: + debugN(" >= "); + break; + case kTokenNotEq: + debugN(" != "); + break; } - nextToken = data[offset]; - offset++; - if (nextToken != kLBCodeTokenCloseBracket) - error("missing ) after code parameter list (got %02x)", nextToken); - debugN(")"); + nextToken(); + parseConcat(); + + if (_stack.size() < 2) + error("comparison didn't get enough values"); + LBValue val2 = _stack.pop(); + LBValue val1 = _stack.pop(); + bool result; + // FIXME: should work for non-integers!! + switch (comparison) { + case kTokenEquals: + result = (val1 == val2); + break; + case kTokenLessThan: + result = (val1.integer < val2.integer); + break; + case kTokenGreaterThan: + result = (val1.integer > val2.integer); + break; + case kTokenLessThanEq: + result = (val1.integer <= val2.integer); + break; + case kTokenGreaterThanEq: + result = (val1.integer >= val2.integer); + break; + case kTokenNotEq: + result = (val1 != val2); + break; + } - return params; + debugN(" [--> %s]", result ? "true" : "false"); + _stack.push(result ? 1 : 0); } -void LBCode::runCodeCommand(LBItem *src, uint32 &offset) { - if (offset + 1 >= size) - error("went off the end of code"); +void LBCode::parseConcat() { + parseArithmetic1(); + // FIXME: string concat +} - byte commandType = data[offset]; - offset++; +void LBCode::parseArithmetic1() { + parseArithmetic2(); + // FIXME: -/+ math operators +} - switch (commandType) { - case 0x23: - { - debugN("setViewOrigin"); - Common::Array<LBValue> params = readParams(src, offset); - // TODO - } - break; +void LBCode::parseArithmetic2() { + // FIXME: other math operators + parseMain(); +} - case 0x36: - { - debugN("setWorld"); - Common::Array<LBValue> params = readParams(src, offset); - // TODO - } - break; +void LBCode::parseMain() { + byte prefix = 0; + if (_currToken == kTokenMinus || _currToken == kTokenPlus) { + debugN("%s", _currToken == kTokenMinus ? "-" : "+"); + prefix = _currToken; + nextToken(); + } - case 0x42: + switch (_currToken) { + case kTokenIdentifier: + assert(_currValue.type == kLBValueString); { - debugN("setPlayParams"); - Common::Array<LBValue> params = readParams(src, offset); - if (params.size() > 8) - error("too many parameters (%d) to setPlayParams", params.size()); - if (!params.size()) - error("no target for setPlayParams"); - LBItem *target; - if (params[0].string.equalsIgnoreCase("self")) { - target = src; + Common::String varname = _currValue.string; + debugN("%s", varname.c_str()); + nextToken(); + if (varname == "self") { + _stack.push(LBValue(_currSource)); + if (_currToken == kTokenAssign) + error("attempted assignment to self"); + } else if (_currToken == kTokenAssign) { + debugN(" = "); + nextToken(); + parseStatement(); + if (!_stack.size()) + error("assignment failed"); + LBValue *val = &_vm->_variables[varname]; + *val = _stack.pop(); + _stack.push(*val); } else { - error("didn't understand target '%s'", params[0].string.c_str()); - } - // TODO: type-checking - switch (params.size()) { - case 8: - target->_soundMode = params[7].integer; - case 7: - target->_controlMode = params[6].integer; - case 6: - // TODO: _relocPoint? - case 5: - // TODO: _periodMin/Max - case 4: - target->_timingMode = params[3].integer; - case 3: - // TODO: _delayMin/Max - case 2: - target->_loopMode = params[1].integer; + _stack.push(_vm->_variables[varname]); } + // FIXME: pre/postincrement } break; - case 0x50: - { - debugN("setKeyEvent"); - Common::Array<LBValue> params = readParams(src, offset); - if (params.size() != 2) - error("incorrect number of parameters (%d) to setKeyEvent", params.size()); - // FIXME: params[0] is key, params[1] is opcode id - } + case kTokenLiteral: + case kTokenConstMode: + case kTokenConstEventId: + case 0x5e: // TODO: ?? + case kTokenKeycode: + assert(_currValue.type == kLBValueInteger); + debugN("%d", _currValue.integer); + _stack.push(_currValue); + nextToken(); break; - case 0x51: - { - debugN("setHitTest"); - Common::Array<LBValue> params = readParams(src, offset); - if (params.size() > 2) - error("incorrect number of parameters (%d) to setHitTest", params.size()); - // TODO - } + case kTokenString: + assert(_currValue.type == kLBValueString); + debugN("\"%s\"", _currValue.string.c_str()); + _stack.push(_currValue); + nextToken(); break; - case 0x52: - { - debugN("key"); - Common::Array<LBValue> params = readParams(src, offset); - // TODO - } + case kTokenOpenBracket: + debugN("("); + nextToken(); + parseStatement(); + if (_currToken != kTokenCloseBracket) + error("no kTokenCloseBracket (%02x), multiple entries?", _currToken); + debugN(")"); + nextToken(); break; - case 0x5E: - { - debugN("setPageFade"); - Common::Array<LBValue> params = readParams(src, offset); - // TODO - } + case kTokenNot: + debugN("!"); + nextToken(); + // not parseStatement, ! takes predecence over logical ops + parseComparisons(); + if (!_stack.size()) + error("not op failed"); + _stack.push(_stack.pop().isZero() ? 1 : 0); + break; + + case kTokenGeneralCommand: + runGeneralCommand(); + break; + + case kTokenItemCommand: + runItemCommand(); + break; + + case kTokenNotifyCommand: + runNotifyCommand(); break; default: - error("unknown command %02x in code", commandType); + error("unknown token %02x in code", _currToken); + } + + if (prefix) { + if (!_stack.size()) + error("+/- prefix failed"); + LBValue val = _stack.pop(); + assert(val.isNumeric()); + // FIXME + if (prefix == kTokenMinus) + val.integer--; + else + val.integer++; + _stack.push(val); } } -void LBCode::runCodeItemCommand(LBItem *src, uint32 &offset) { - if (offset + 1 >= size) +Common::Array<LBValue> LBCode::readParams() { + Common::Array<LBValue> params; + + if (_currOffset + 1 >= _size) error("went off the end of code"); - byte commandType = data[offset]; - offset++; + byte numParams = _data[_currOffset++]; - switch (commandType) { - case 0x1d: - { - debugN("setParent"); - Common::Array<LBValue> params = readParams(src, offset); - if (params.size() > 2) - error("incorrect number of parameters (%d) to setParent", params.size()); - // TODO + if (!numParams) { + debugN("()\n"); + nextToken(); + return params; + } + + nextToken(); + if (_currToken != kTokenOpenBracket) + error("missing ( before code parameter list (got %02x)", _currToken); + nextToken(); + debugN("("); + + for (uint i = 0; i < numParams; i++) { + if (i != 0) { + if (_currToken != ',') + error("missing , between code parameters (got %02x)", _currToken); + debugN(", "); + nextToken(); } - break; - default: - error("unknown item command %02x in code", commandType); + parseStatement(); + if (!_stack.size()) + error("stack empty"); + LBValue nextValue = _stack.pop(); + + params.push_back(nextValue); } + + if (_currToken != kTokenCloseBracket) + error("missing ) after code parameter list (got %02x)", _currToken); + nextToken(); + debugN(")"); + + return params; } -void LBCode::runCodeNotifyCommand(LBItem *src, uint32 &offset) { - if (offset + 1 >= size) - error("went off the end of code"); +struct CodeCommandInfo { + const char *name; + typedef void (LBCode::*CommandFunc)(const Common::Array<LBValue> ¶ms); + CommandFunc func; +}; + +#define NUM_GENERAL_COMMANDS 129 +CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = { + { "eval", 0 }, + { "random", 0 }, + { "stringLen", 0 }, + { "substring", 0 }, + { "max", 0 }, + { "min", 0 }, + { "abs", 0 }, + { "getRect", 0 }, // also "makeRect" + { "makePt", 0 }, // also "makePair" + { "topleft", 0 }, + { "bottomright", 0 }, + { "mousePos", 0 }, + { "top", 0 }, + { "left", 0 }, + { "bottom", 0 }, + // 0x10 + { "right", 0 }, + { "xpos", 0 }, + { "ypos", 0 }, + { "playFrom", 0 }, + { "move", 0 }, + { 0, 0 }, + { 0, 0 }, + { "setDragParams", 0 }, + { "resetDragParams", 0 }, + { "enableRollover", &LBCode::cmdUnimplemented /* FIXME */ }, + { "setCursor", 0 }, + { "width", 0 }, + { "height", 0 }, + { "getFrameBounds", 0 }, // also "getFrameRect" + { "traceRect", 0 }, + { "sqrt", 0 }, + // 0x20 + { "deleteVar", 0 }, + { "saveVars", 0 }, + { "scriptLink", 0 }, + { "setViewOrigin", &LBCode::cmdUnimplemented }, + { "rectSect", 0 }, + { "getViewOrigin", 0 }, + { "getViewRect", 0 }, + { "getPage", 0 }, + { "getWorldRect", 0 }, + { "isWorldWrap", 0 }, + { "newList", 0 }, + { "deleteList", 0 }, + { "add", 0 }, + { 0, 0 }, + { "addAt", 0 }, + { "getAt", 0 }, + // 0x30 + { 0, 0 }, + { "getIndex", 0 }, + { "setAt", 0 }, + { "listLen", 0 }, + { "deleteAt", 0 }, + { "clearList", 0 }, + { "setWorld", 0 }, + { "setProperty", 0 }, + { "getProperty", 0 }, + { "copyList", 0 }, + { "invoke", 0 }, + { "exec", 0 }, + { "return", 0 }, + { "sendSync", 0 }, + { "moveViewOrigin", 0 }, + { "addToGroup", 0 }, + // 0x40 + { "removeFromGroup", 0 }, + { "clearGroup", 0 }, + { "setPlayParams", &LBCode::cmdSetPlayParams }, + { "autoEvent", 0 }, + { 0, 0 }, + { 0, 0 }, + { "getID", 0 }, + { "setCursorPosition", 0 }, + { "getTime", 0 }, + { "logWriteLn", 0 }, + { "logWrite", 0 }, + { "getLanguage", 0 }, + { "setLanguage", 0 }, + { "getSequence", 0 }, + { "setSequence", 0 }, + { "getFileSpec", 0 }, + // 0x50 + { "setKeyEvent", &LBCode::cmdSetKeyEvent }, + { "setHitTest", &LBCode::cmdSetHitTest }, + { "key", &LBCode::cmdKey }, + { "deleteKeyEvent", 0 }, + { "setDisplay", &LBCode::cmdUnimplemented }, + { "getDisplay", 0 }, + { 0, 0 }, + { "lbxCreate", 0 }, + { "lbxFunc", 0 }, + { "waitCursor", 0 }, + { "debugBreak", 0 }, + { "menuItemEnable", 0 }, + { "showChannel", 0 }, + { "hideChannel", 0 }, + { "setPageFade", 0 }, + { "normalize", 0 }, + // 0x60 (v5+) + { "addEvent", 0 }, + { "setCueEvent", 0 }, + { 0, 0 }, + { 0, 0 }, + { "getName", 0 }, + { "getProperties", 0 }, + { "createItem", 0 }, + { "setProperties", 0 }, + { "alert", 0 }, + { "getUniqueID", 0 }, + { "isNumeric", 0 }, + { "setKeyFocus", 0 }, + { "getKeyFocus", 0 }, + { "isItem", 0 }, + { "itemHit", 0 }, + { "getItem ", 0 }, + // 0x70 + { 0, 0 }, + { "setCascade", 0 }, + { "getCascade", 0 }, + { "getRes", 0 }, + { "setRes", 0 }, + { "getFilename", 0 }, + { "resEnumNames", 0 }, + { "isList", 0 }, + { "resetRect", 0 }, + { "setVolume", 0 }, + { "getVolume", 0 }, + { "pause", 0 }, + { "getTextWidth", 0 }, + { "setItemVolume", 0 }, + { "setSoundLoop", 0 }, + // 0x80 + { "setClipboard", 0 }, + { "getResDuration", 0 } +}; + +void LBCode::runGeneralCommand() { + byte commandType = _currValue.integer; + + if (commandType == 0 || commandType > NUM_GENERAL_COMMANDS) + error("bad command type 0x%02x in runGeneralCommand", commandType); + + CodeCommandInfo &info = generalCommandInfo[commandType - 1]; + debugN("%s", info.name); + Common::Array<LBValue> params = readParams(); + + if (!info.func) + error("general command '%s' (0x%02x) unimplemented", info.name, commandType); + (this->*(info.func))(params); +} + +void LBCode::cmdUnimplemented(const Common::Array<LBValue> ¶ms) { + warning("unimplemented command called"); +} + +void LBCode::cmdSetPlayParams(const Common::Array<LBValue> ¶ms) { + if (params.size() > 8) + error("too many parameters (%d) to setPlayParams", params.size()); + if (!params.size()) + error("no target for setPlayParams"); + + if (params[0].type != kLBValueItemPtr) + error("first param to setPlayParams wasn't item"); + LBItem *target = params[0].item; + + // TODO: type-checking + switch (params.size()) { + case 8: + target->_soundMode = params[7].integer; + case 7: + target->_controlMode = params[6].integer; + case 6: + // TODO: _relocPoint? + case 5: + // TODO: _periodMin/Max + case 4: + target->_timingMode = params[3].integer; + case 3: + // TODO: _delayMin/Max + case 2: + target->_loopMode = params[1].integer; + } +} + +void LBCode::cmdSetKeyEvent(const Common::Array<LBValue> ¶ms) { + if (params.size() != 2) + error("incorrect number of parameters (%d) to setKeyEvent", params.size()); + + // FIXME: params[0] is key, params[1] is opcode id + warning("ignoring setKeyEvent"); +} + +void LBCode::cmdSetHitTest(const Common::Array<LBValue> ¶ms) { + if (params.size() > 2) + error("incorrect number of parameters (%d) to setHitTest", params.size()); + warning("ignoring setHitTest"); +} + +void LBCode::cmdKey(const Common::Array<LBValue> ¶ms) { + _stack.push(0); // FIXME + warning("ignoring Key"); +} + +#define NUM_ITEM_COMMANDS 34 +CodeCommandInfo itemCommandInfo[NUM_ITEM_COMMANDS] = { + { "clone", 0 }, + { "destroy", 0 }, + { "dragBeginFrom", 0 }, + { "dragEnd", 0 }, + { "enableLocal", 0 }, + { "enable", 0 }, + { "showLocal", 0 }, + { "show", 0 }, + { "getFrame", 0 }, + { "getParent", 0 }, + { "getPosition" , 0 }, + { "getText", 0 }, + { "getZNext", 0 }, + { "getZPrev", 0 }, + { "hitTest", 0 }, + // 0x10 + { "isAmbient", 0 }, + { "isEnabled", 0 }, + { "isMuted", 0 }, + { "isPlaying", &LBCode::itemIsPlaying }, + { "isVisible", 0 }, + { "isLoaded", 0 }, + { "isDragging", 0 }, + { "load", 0 }, + { "moveTo", 0 }, + { "mute", 0 }, + { "play", 0 }, + { "seek", 0 }, + { "seekToFrame", 0 }, + { "setParent", &LBCode::itemSetParent }, + { "setZOrder", 0 }, + { "setText", 0 }, + // 0x20 + { "stop", 0 }, + { "unload", 0 }, + { "unloadSync", 0} +}; + +void LBCode::runItemCommand() { + byte commandType = _currValue.integer; + + if (commandType == 0 || commandType > NUM_ITEM_COMMANDS) + error("bad command type 0x%02x in runItemCommand", commandType); + + CodeCommandInfo &info = itemCommandInfo[commandType - 1]; + debugN("%s", info.name); + Common::Array<LBValue> params = readParams(); + + if (!info.func) + error("item command '%s' (0x%02x) unimplemented", info.name, commandType); + (this->*(info.func))(params); +} + +void LBCode::itemIsPlaying(const Common::Array<LBValue> ¶ms) { + // TODO + warning("ignoring isPlaying"); + _stack.push(0); +} + +void LBCode::itemSetParent(const Common::Array<LBValue> ¶ms) { + if (params.size() > 2) + error("incorrect number of parameters (%d) to setParent", params.size()); + // TODO + warning("ignoring setParent"); +} - byte commandType = data[offset]; - offset++; +void LBCode::runNotifyCommand() { + byte commandType = _currValue.integer; switch (commandType) { case kLBNotifyChangePage: { debugN("goto"); - Common::Array<LBValue> params = readParams(src, offset); + Common::Array<LBValue> params = readParams(); // TODO: type-checking + NotifyEvent notifyEvent(kLBNotifyChangePage, 0); switch (params.size()) { - case 1: - _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, params[0].integer)); + case 4: + notifyEvent.type = kLBNotifyChangeMode; // FIXME: type 8? + notifyEvent.newUnknown = params[0].integer; // FIXME: this is newLanguage + notifyEvent.newMode = params[1].integer; + notifyEvent.newPage = params[2].integer; + notifyEvent.newSubpage = params[3].integer; break; case 2: - // FIXME - case 4: - // FIXME + notifyEvent.type = kLBNotifyChangeMode; + // FIXME: newPage and newSubpage? + error("can't handle goto with 2 params"); + break; + + case 1: + notifyEvent.param = params[0].integer; + break; + + case 0: + // FIXME: use cur page? + error("can't handle goto with 0 params"); + break; default: error("incorrect number of parameters (%d) to goto", params.size()); } + _vm->addNotifyEvent(notifyEvent); } break; @@ -336,7 +768,7 @@ void LBCode::runCodeNotifyCommand(LBItem *src, uint32 &offset) { case kLBNotifyGotoQuit: { debugN(commandType == kLBNotifyGoToControls ? "gotocontrol" : "gotoquit"); - Common::Array<LBValue> params = readParams(src, offset); + Common::Array<LBValue> params = readParams(); if (params.size() != 0) error("incorrect number of parameters (%d) to notify", params.size()); _vm->addNotifyEvent(NotifyEvent(commandType, 0)); @@ -346,7 +778,7 @@ void LBCode::runCodeNotifyCommand(LBItem *src, uint32 &offset) { case kLBNotifyIntroDone: { debugN("startphasemain"); - Common::Array<LBValue> params = readParams(src, offset); + Common::Array<LBValue> params = readParams(); if (params.size() != 0) error("incorrect number of parameters (%d) to startphasemain", params.size()); _vm->addNotifyEvent(NotifyEvent(kLBNotifyIntroDone, 1)); @@ -358,50 +790,4 @@ void LBCode::runCodeNotifyCommand(LBItem *src, uint32 &offset) { } } -void LBCode::runCode(LBItem *src, uint32 offset) { - while (true) { - if (offset + 1 >= size) { - warning("went off the end of code"); - return; - } - - byte tokenType = data[offset]; - offset++; - - switch (tokenType) { - case 0x01: // FIXME - case kLBCodeTokenEndOfFile: - return; - - case 0x4D: - runCodeCommand(src, offset); - break; - - case 0x4E: - runCodeItemCommand(src, offset); - break; - - case 0x4F: - runCodeNotifyCommand(src, offset); - break; - - default: - debugN("at %04x: %02x ", offset - 1, tokenType); - for (uint i = 0; i < size; i++) - debugN("%02x ", data[offset++]); - debugN("\n"); - error("unknown token %02x in code", tokenType); - } - - byte nextToken = data[offset]; - offset++; - if (nextToken != kLBCodeTokenEndOfStatement) - warning("missing EndOfStatement after code statement (got %04x)", nextToken); - if (nextToken == kLBCodeTokenEndOfFile) - return; - - debugN("\n"); - } -} - } // End of namespace Mohawk diff --git a/engines/mohawk/livingbooks_code.h b/engines/mohawk/livingbooks_code.h index 7960e79035..71174cc09e 100644 --- a/engines/mohawk/livingbooks_code.h +++ b/engines/mohawk/livingbooks_code.h @@ -26,6 +26,8 @@ #ifndef MOHAWK_LIVINGBOOKS_CODE_H #define MOHAWK_LIVINGBOOKS_CODE_H +#include "common/rect.h" +#include "common/stack.h" #include "common/substream.h" namespace Mohawk { @@ -35,18 +37,70 @@ class LBItem; enum LBValueType { kLBValueString, - kLBValueInteger + kLBValueInteger, + kLBValueReal, + kLBValuePoint, + kLBValueRect, + kLBValueItemPtr }; struct LBValue { - LBValue() { type = kLBValueInteger; integer = 0; } + LBValue() { + type = kLBValueInteger; + integer = 0; + } + LBValue(int val) { + type = kLBValueInteger; + integer = val; + } + LBValue(const Common::String &str) { + type = kLBValueString; + string = str; + } + LBValue(LBItem *itm) { + type = kLBValueItemPtr; + item = itm; + } + LBValue(const LBValue &val) { + type = val.type; + switch (type) { + case kLBValueString: + string = val.string; + break; + case kLBValueInteger: + integer = val.integer; + break; + case kLBValueReal: + real = val.real; + break; + case kLBValuePoint: + point = val.point; + break; + case kLBValueRect: + rect = val.rect; + break; + case kLBValueItemPtr: + item = val.item; + break; + } + } LBValueType type; Common::String string; int integer; + double real; + Common::Point point; + Common::Rect rect; + LBItem *item; bool operator==(const LBValue &x) const; bool operator!=(const LBValue &x) const; + + bool isNumeric() const; + bool isZero() const; + + int toInt() const; + double toDouble() const; }; enum { @@ -54,18 +108,63 @@ enum { }; enum { - kLBCodeTokenString = 0x1, - kLBCodeTokenLiteral = 0x5, - kLBCodeTokenChar = 0x6, - kLBCodeTokenEndOfStatement = 0x7, - kLBCodeTokenEndOfFile = 0x8, - kLBCodeTokenOpenBracket = 0xf, - kLBCodeTokenCloseBracket = 0x10, - kLBCodeTokenLong = 0x11, - - kLBCodeTokenEquals = 0x22, // TODO: maybe.. - kLBCodeTokenQuote = 0x27, // "'" - kLBCodeTokenComma = 0x2c // "," + kTokenIdentifier = 0x1, + kTokenLiteral = 0x5, + kTokenString = 0x6, + kTokenEndOfStatement = 0x7, + kTokenEndOfFile = 0x8, + kTokenConcat = 0xb, + kTokenSingleQuote = 0xc, // ?? + kTokenDoubleQuote = 0xd, // ?? + kTokenMultiply = 0xe, + kTokenOpenBracket = 0xf, + kTokenCloseBracket = 0x10, + kTokenMinus = 0x11, + kTokenMinusMinus = 0x12, + kTokenPlusEquals = 0x13, + kTokenPlus = 0x14, + kTokenPlusPlus = 0x15, + kTokenEquals = 0x16, + kTokenMinusEquals = 0x17, + kTokenMultiplyEquals = 0x18, + kTokenDivideEquals = 0x19, + kTokenListStart = 0x1a, + kTokenListEnd = 0x1b, + kTokenColon = 0x1c, // ?? + kTokenLessThan = 0x1d, + kTokenGreaterThan = 0x1e, + kTokenAndEquals = 0x1f, + kTokenDotOperator = 0x20, + kTokenDivide = 0x21, + kTokenAssign = 0x22, + kTokenLessThanEq = 0x23, + kTokenGreaterThanEq = 0x24, + kTokenNotEq = 0x25, + kTokenQuote = 0x27, // ?? + kTokenAnd = 0x2a, + kTokenComma = 0x2c, + kTokenConstMode = 0x31, + kTokenIntDivide = 0x32, + kTokenModulo = 0x34, + kTokenNot = 0x35, + kTokenOr = 0x37, + kTokenTrue = 0x39, + kTokenFalse = 0x3a, + kTokenConstDataType = 0x3b, // ?? + kTokenConstItemType = 0x3c, // ?? + kTokenConstEventId = 0x42, + kTokenConstScriptOpcode = 0x43, // ?? + kTokenConstScriptParam = 0x44, // ?? + kTokenGeneralCommand = 0x4d, + kTokenItemCommand = 0x4e, + kTokenNotifyCommand = 0x4f, + // 0x5e?! + kTokenKeycode = 0x5f, + + // v5 only: + kTokenLocal = 0x61, + kTokenPropListCommand = 0x70, + kTokenRectCommand = 0x71 }; class LBCode { @@ -73,20 +172,46 @@ public: LBCode(MohawkEngine_LivingBooks *vm); ~LBCode(); - void runCode(LBItem *src, uint32 offset); + LBValue runCode(LBItem *src, uint32 offset); protected: MohawkEngine_LivingBooks *_vm; - uint32 size; - byte *data; + uint32 _size; + byte *_data; + Common::HashMap<uint16, Common::String> _strings; + + uint32 _currOffset; + LBItem *_currSource; + + Common::Stack<LBValue> _stack; + byte _currToken; + LBValue _currValue; - Common::HashMap<uint16, Common::String> strings; + void nextToken(); - Common::Array<LBValue> readParams(LBItem *src, uint32 &offset); - void runCodeCommand(LBItem *src, uint32 &offset); - void runCodeItemCommand(LBItem *src, uint32 &offset); - void runCodeNotifyCommand(LBItem *src, uint32 &offset); + LBValue runCode(byte terminator); + void parseStatement(); + void parseComparisons(); + void parseConcat(); + void parseArithmetic1(); + void parseArithmetic2(); + void parseMain(); + + Common::Array<LBValue> readParams(); + void runGeneralCommand(); + void runItemCommand(); + void runNotifyCommand(); + +public: + void cmdUnimplemented(const Common::Array<LBValue> ¶ms); + void cmdSetPlayParams(const Common::Array<LBValue> ¶ms); + void cmdSetKeyEvent(const Common::Array<LBValue> ¶ms); + void cmdSetHitTest(const Common::Array<LBValue> ¶ms); + void cmdKey(const Common::Array<LBValue> ¶ms); + + void itemSetParent(const Common::Array<LBValue> ¶ms); + void itemIsPlaying(const Common::Array<LBValue> ¶ms); }; } // End of namespace Mohawk |