diff options
Diffstat (limited to 'engines/sci')
81 files changed, 4975 insertions, 2784 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 9607a8e66d..5ae8245e5a 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -53,6 +53,7 @@ #include "video/avi_decoder.h" #include "sci/video/seq_decoder.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" #include "video/coktel_decoder.h" #include "sci/video/robot_decoder.h" #endif @@ -131,6 +132,10 @@ Console::Console(SciEngine *engine) : GUI::Debugger(), DCmd_Register("al", WRAP_METHOD(Console, cmdAnimateList)); // alias DCmd_Register("window_list", WRAP_METHOD(Console, cmdWindowList)); DCmd_Register("wl", WRAP_METHOD(Console, cmdWindowList)); // alias + DCmd_Register("plane_list", WRAP_METHOD(Console, cmdPlaneList)); + DCmd_Register("pl", WRAP_METHOD(Console, cmdPlaneList)); // alias + DCmd_Register("plane_items", WRAP_METHOD(Console, cmdPlaneItemList)); + DCmd_Register("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias DCmd_Register("saved_bits", WRAP_METHOD(Console, cmdSavedBits)); DCmd_Register("show_saved_bits", WRAP_METHOD(Console, cmdShowSavedBits)); // Segments @@ -245,20 +250,18 @@ void Console::postEnter() { #endif if (_videoFile.hasSuffix(".seq")) { - SeqDecoder *seqDecoder = new SeqDecoder(); - seqDecoder->setFrameDelay(_videoFrameDelay); - videoDecoder = seqDecoder; + videoDecoder = new SEQDecoder(_videoFrameDelay); #ifdef ENABLE_SCI32 } else if (_videoFile.hasSuffix(".vmd")) { - videoDecoder = new Video::VMDDecoder(g_system->getMixer()); + videoDecoder = new Video::AdvancedVMDDecoder(); } else if (_videoFile.hasSuffix(".rbt")) { - videoDecoder = new RobotDecoder(g_system->getMixer(), _engine->getPlatform() == Common::kPlatformMacintosh); + videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh); } else if (_videoFile.hasSuffix(".duk")) { duckMode = true; - videoDecoder = new Video::AviDecoder(g_system->getMixer()); + videoDecoder = new Video::AVIDecoder(); #endif } else if (_videoFile.hasSuffix(".avi")) { - videoDecoder = new Video::AviDecoder(g_system->getMixer()); + videoDecoder = new Video::AVIDecoder(); } else { warning("Unrecognized video type"); } @@ -365,7 +368,10 @@ bool Console::cmdHelp(int argc, const char **argv) { DebugPrintf(" pic_visualize - Enables visualization of the drawing process of EGA pictures\n"); DebugPrintf(" undither - Enable/disable undithering\n"); DebugPrintf(" play_video - Plays a SEQ, AVI, VMD, RBT or DUK video\n"); - DebugPrintf(" animate_object_list / al - Shows the current list of objects in kAnimate's draw list\n"); + DebugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n"); + DebugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n"); + DebugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n"); + DebugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n"); DebugPrintf(" saved_bits - List saved bits on the hunk\n"); DebugPrintf(" show_saved_bits - Display saved bits\n"); DebugPrintf("\n"); @@ -1209,12 +1215,33 @@ bool Console::cmdRestartGame(int argc, const char **argv) { return Cmd_Exit(0, 0); } +// The scripts get IDs ranging from 100->199, because the scripts require us to assign unique ids THAT EVEN STAY BETWEEN +// SAVES and the scripts also use "saves-count + 1" to create a new savedgame slot. +// SCI1.1 actually recycles ids, in that case we will currently get "0". +// This behavior is required especially for LSL6. In this game, it's possible to quick save. The scripts will use +// the last-used id for that feature. If we don't assign sticky ids, the feature will overwrite different saves all the +// time. And sadly we can't just use the actual filename ids directly, because of the creation method for new slots. + +extern void listSavegames(Common::Array<SavegameDesc> &saves); + +bool Console::cmdListSaves(int argc, const char **argv) { + Common::Array<SavegameDesc> saves; + listSavegames(saves); + + for (uint i = 0; i < saves.size(); i++) { + Common::String filename = g_sci->getSavegameName(saves[i].id); + DebugPrintf("%s: '%s'\n", filename.c_str(), saves[i].name); + } + + return true; +} + bool Console::cmdClassTable(int argc, const char **argv) { DebugPrintf("Available classes (parse a parameter to filter the table by a specific class):\n"); for (uint i = 0; i < _engine->_gamestate->_segMan->classTableSize(); i++) { Class temp = _engine->_gamestate->_segMan->_classTable[i]; - if (temp.reg.segment) { + if (temp.reg.getSegment()) { const char *className = _engine->_gamestate->_segMan->getObjectName(temp.reg); if (argc == 1 || (argc == 2 && !strcmp(className, argv[1]))) { DebugPrintf(" Class 0x%x (%s) at %04x:%04x (script %d)\n", i, @@ -1589,6 +1616,8 @@ bool Console::cmdAnimateList(int argc, const char **argv) { if (_engine->_gfxAnimate) { DebugPrintf("Animate list:\n"); _engine->_gfxAnimate->printAnimateList(this); + } else { + DebugPrintf("This SCI version does not have an animate list\n"); } return true; } @@ -1597,9 +1626,52 @@ bool Console::cmdWindowList(int argc, const char **argv) { if (_engine->_gfxPorts) { DebugPrintf("Window list:\n"); _engine->_gfxPorts->printWindowList(this); + } else { + DebugPrintf("This SCI version does not have a list of ports\n"); } return true; +} + +bool Console::cmdPlaneList(int argc, const char **argv) { +#ifdef ENABLE_SCI32 + if (_engine->_gfxFrameout) { + DebugPrintf("Plane list:\n"); + _engine->_gfxFrameout->printPlaneList(this); + } else { + DebugPrintf("This SCI version does not have a list of planes\n"); + } +#else + DebugPrintf("SCI32 isn't included in this compiled executable\n"); +#endif + return true; +} +bool Console::cmdPlaneItemList(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Shows the list of items for a plane\n"); + DebugPrintf("Usage: %s <plane address>\n", argv[0]); + return true; + } + + reg_t planeObject = NULL_REG; + + if (parse_reg_t(_engine->_gamestate, argv[1], &planeObject, false)) { + DebugPrintf("Invalid address passed.\n"); + DebugPrintf("Check the \"addresses\" command on how to use addresses\n"); + return true; + } + +#ifdef ENABLE_SCI32 + if (_engine->_gfxFrameout) { + DebugPrintf("Plane item list:\n"); + _engine->_gfxFrameout->printPlaneItemList(this, planeObject); + } else { + DebugPrintf("This SCI version does not have a list of plane items\n"); + } +#else + DebugPrintf("SCI32 isn't included in this compiled executable\n"); +#endif + return true; } bool Console::cmdSavedBits(int argc, const char **argv) { @@ -1614,7 +1686,7 @@ bool Console::cmdSavedBits(int argc, const char **argv) { Common::Array<reg_t> entries = hunks->listAllDeallocatable(id); for (uint i = 0; i < entries.size(); ++i) { - uint16 offset = entries[i].offset; + uint16 offset = entries[i].getOffset(); const Hunk& h = hunks->_table[offset]; if (strcmp(h.type, "SaveBits()") == 0) { byte* memoryPtr = (byte *)h.mem; @@ -1677,12 +1749,12 @@ bool Console::cmdShowSavedBits(int argc, const char **argv) { return true; } - if (memoryHandle.segment != id || !hunks->isValidOffset(memoryHandle.offset)) { + if (memoryHandle.getSegment() != id || !hunks->isValidOffset(memoryHandle.getOffset())) { DebugPrintf("Invalid address.\n"); return true; } - const Hunk& h = hunks->_table[memoryHandle.offset]; + const Hunk& h = hunks->_table[memoryHandle.getOffset()]; if (strcmp(h.type, "SaveBits()") != 0) { DebugPrintf("Invalid address.\n"); @@ -2192,16 +2264,16 @@ bool Console::cmdGCShowReachable(int argc, const char **argv) { return true; } - SegmentObj *mobj = _engine->_gamestate->_segMan->getSegmentObj(addr.segment); + SegmentObj *mobj = _engine->_gamestate->_segMan->getSegmentObj(addr.getSegment()); if (!mobj) { - DebugPrintf("Unknown segment : %x\n", addr.segment); + DebugPrintf("Unknown segment : %x\n", addr.getSegment()); return 1; } DebugPrintf("Reachable from %04x:%04x:\n", PRINT_REG(addr)); const Common::Array<reg_t> tmp = mobj->listAllOutgoingReferences(addr); for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) - if (it->segment) + if (it->getSegment()) g_sci->getSciDebugger()->DebugPrintf(" %04x:%04x\n", PRINT_REG(*it)); return true; @@ -2224,16 +2296,16 @@ bool Console::cmdGCShowFreeable(int argc, const char **argv) { return true; } - SegmentObj *mobj = _engine->_gamestate->_segMan->getSegmentObj(addr.segment); + SegmentObj *mobj = _engine->_gamestate->_segMan->getSegmentObj(addr.getSegment()); if (!mobj) { - DebugPrintf("Unknown segment : %x\n", addr.segment); + DebugPrintf("Unknown segment : %x\n", addr.getSegment()); return true; } - DebugPrintf("Freeable in segment %04x:\n", addr.segment); - const Common::Array<reg_t> tmp = mobj->listAllDeallocatable(addr.segment); + DebugPrintf("Freeable in segment %04x:\n", addr.getSegment()); + const Common::Array<reg_t> tmp = mobj->listAllDeallocatable(addr.getSegment()); for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) - if (it->segment) + if (it->getSegment()) g_sci->getSciDebugger()->DebugPrintf(" %04x:%04x\n", PRINT_REG(*it)); return true; @@ -2257,9 +2329,9 @@ bool Console::cmdGCNormalize(int argc, const char **argv) { return true; } - SegmentObj *mobj = _engine->_gamestate->_segMan->getSegmentObj(addr.segment); + SegmentObj *mobj = _engine->_gamestate->_segMan->getSegmentObj(addr.getSegment()); if (!mobj) { - DebugPrintf("Unknown segment : %x\n", addr.segment); + DebugPrintf("Unknown segment : %x\n", addr.getSegment()); return true; } @@ -2498,12 +2570,12 @@ bool Console::cmdViewReference(int argc, const char **argv) { DebugPrintf("%04x:%04x is of type 0x%x: ", PRINT_REG(reg), type_mask); - if (reg.segment == 0 && reg.offset == 0) { + if (reg.getSegment() == 0 && reg.getOffset() == 0) { DebugPrintf("Null.\n"); return true; } - if (reg_end.segment != reg.segment && reg_end != NULL_REG) { + if (reg_end.getSegment() != reg.getSegment() && reg_end != NULL_REG) { DebugPrintf("Ending segment different from starting segment. Assuming no bound on dump.\n"); reg_end = NULL_REG; } @@ -2539,7 +2611,7 @@ bool Console::cmdViewReference(int argc, const char **argv) { printObject(reg); break; case SIG_TYPE_REFERENCE: { - switch (_engine->_gamestate->_segMan->getSegmentType(reg.segment)) { + switch (_engine->_gamestate->_segMan->getSegmentType(reg.getSegment())) { #ifdef ENABLE_SCI32 case SEG_TYPE_STRING: { DebugPrintf("SCI32 string\n"); @@ -2555,21 +2627,20 @@ bool Console::cmdViewReference(int argc, const char **argv) { } #endif default: { - int size; const SegmentRef block = _engine->_gamestate->_segMan->dereference(reg); - size = block.maxSize; + uint16 size = block.maxSize; DebugPrintf("raw data\n"); - if (reg_end.segment != 0 && size < reg_end.offset - reg.offset) { + if (reg_end.getSegment() != 0 && (size < reg_end.getOffset() - reg.getOffset())) { DebugPrintf("Block end out of bounds (size %d). Resetting.\n", size); reg_end = NULL_REG; } - if (reg_end.segment != 0 && (size >= reg_end.offset - reg.offset)) - size = reg_end.offset - reg.offset; + if (reg_end.getSegment() != 0 && (size >= reg_end.getOffset() - reg.getOffset())) + size = reg_end.getOffset() - reg.getOffset(); - if (reg_end.segment != 0) + if (reg_end.getSegment() != 0) DebugPrintf("Block size less than or equal to %d\n", size); if (block.isRaw) @@ -2581,7 +2652,7 @@ bool Console::cmdViewReference(int argc, const char **argv) { break; } case SIG_TYPE_INTEGER: - DebugPrintf("arithmetic value\n %d (%04x)\n", (int16) reg.offset, reg.offset); + DebugPrintf("arithmetic value\n %d (%04x)\n", (int16) reg.getOffset(), reg.getOffset()); break; default: DebugPrintf("unknown type %d.\n", type); @@ -2651,7 +2722,7 @@ bool Console::cmdBacktrace(int argc, const char **argv) { switch (call.type) { case EXEC_STACK_TYPE_CALL: // Normal function if (call.type == EXEC_STACK_TYPE_CALL) - DebugPrintf(" %x: script %d - ", i, (*(Script *)_engine->_gamestate->_segMan->_heap[call.addr.pc.segment]).getScriptNumber()); + DebugPrintf(" %x: script %d - ", i, (*(Script *)_engine->_gamestate->_segMan->_heap[call.addr.pc.getSegment()]).getScriptNumber()); if (call.debugSelector != -1) { DebugPrintf("%s::%s(", objname, _engine->getKernel()->getSelectorName(call.debugSelector).c_str()); } else if (call.debugExportId != -1) { @@ -2811,17 +2882,17 @@ bool Console::cmdDisassemble(int argc, const char **argv) { reg_t addr = NULL_REG; if (!obj) { - DebugPrintf("Not an object."); + DebugPrintf("Not an object.\n"); return true; } if (selectorId < 0) { - DebugPrintf("Not a valid selector name."); + DebugPrintf("Not a valid selector name.\n"); return true; } if (lookupSelector(_engine->_gamestate->_segMan, objAddr, selectorId, NULL, &addr) != kSelectorMethod) { - DebugPrintf("Not a method."); + DebugPrintf("Not a method.\n"); return true; } @@ -2840,10 +2911,11 @@ bool Console::cmdDisassemble(int argc, const char **argv) { if (jumpTarget > farthestTarget) farthestTarget = jumpTarget; } - addr = disassemble(_engine->_gamestate, addr, printBWTag, printBytecode); + // TODO: Use a true 32-bit reg_t for the position (addr) + addr = disassemble(_engine->_gamestate, make_reg32(addr.getSegment(), addr.getOffset()), printBWTag, printBytecode); if (addr.isNull() && prevAddr < farthestTarget) addr = prevAddr + 1; // skip past the ret - } while (addr.offset > 0); + } while (addr.getOffset() > 0); return true; } @@ -2860,10 +2932,10 @@ bool Console::cmdDisassembleAddress(int argc, const char **argv) { } reg_t vpc = NULL_REG; - int opCount = 1; + uint opCount = 1; bool printBWTag = false; bool printBytes = false; - int size; + uint16 size; if (parse_reg_t(_engine->_gamestate, argv[1], &vpc, false)) { DebugPrintf("Invalid address passed.\n"); @@ -2872,7 +2944,7 @@ bool Console::cmdDisassembleAddress(int argc, const char **argv) { } SegmentRef ref = _engine->_gamestate->_segMan->dereference(vpc); - size = ref.maxSize + vpc.offset; // total segment size + size = ref.maxSize + vpc.getOffset(); // total segment size for (int i = 2; i < argc; i++) { if (!scumm_stricmp(argv[i], "bwt")) @@ -2887,14 +2959,10 @@ bool Console::cmdDisassembleAddress(int argc, const char **argv) { } } - if (opCount < 0) { - DebugPrintf("Invalid op_count\n"); - return true; - } - do { - vpc = disassemble(_engine->_gamestate, vpc, printBWTag, printBytes); - } while ((vpc.offset > 0) && (vpc.offset + 6 < size) && (--opCount)); + // TODO: Use a true 32-bit reg_t for the position (vpc) + vpc = disassemble(_engine->_gamestate, make_reg32(vpc.getSegment(), vpc.getOffset()), printBWTag, printBytes); + } while ((vpc.getOffset() > 0) && (vpc.getOffset() + 6 < size) && (--opCount)); return true; } @@ -2917,8 +2985,9 @@ void Console::printKernelCallsFound(int kernelFuncNum, bool showFoundScripts) { // Ignore specific leftover scripts, which require other non-existing scripts if ((_engine->getGameId() == GID_HOYLE3 && itr->getNumber() == 995) || (_engine->getGameId() == GID_KQ5 && itr->getNumber() == 980) || - (_engine->getGameId() == GID_SLATER && itr->getNumber() == 947) || - (_engine->getGameId() == GID_MOTHERGOOSE256 && itr->getNumber() == 980)) { + (_engine->getGameId() == GID_KQ7 && itr->getNumber() == 111) || + (_engine->getGameId() == GID_MOTHERGOOSE256 && itr->getNumber() == 980) || + (_engine->getGameId() == GID_SLATER && itr->getNumber() == 947)) { continue; } @@ -2937,7 +3006,7 @@ void Console::printKernelCallsFound(int kernelFuncNum, bool showFoundScripts) { // Now dissassemble each method of the script object for (uint16 i = 0; i < obj->getMethodCount(); i++) { reg_t fptr = obj->getFunction(i); - uint16 offset = fptr.offset; + uint16 offset = fptr.getOffset(); int16 opparams[4]; byte extOpcode; byte opcode; @@ -3584,10 +3653,14 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV relativeOffset = true; if (!scumm_strnicmp(str + 1, "PC", 2)) { - *dest = s->_executionStack.back().addr.pc; + // TODO: Handle 32-bit PC addresses + reg32_t pc = s->_executionStack.back().addr.pc; + *dest = make_reg(pc.getSegment(), (uint16)pc.getOffset()); offsetStr = str + 3; } else if (!scumm_strnicmp(str + 1, "P", 1)) { - *dest = s->_executionStack.back().addr.pc; + // TODO: Handle 32-bit PC addresses + reg32_t pc = s->_executionStack.back().addr.pc; + *dest = make_reg(pc.getSegment(), (uint16)pc.getOffset()); offsetStr = str + 2; } else if (!scumm_strnicmp(str + 1, "PREV", 4)) { *dest = s->r_prev; @@ -3625,8 +3698,8 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV return 1; // Now lookup the script's segment - dest->segment = s->_segMan->getScriptSegment(script_nr); - if (!dest->segment) { + dest->setSegment(s->_segMan->getScriptSegment(script_nr)); + if (!dest->getSegment()) { return 1; } @@ -3708,19 +3781,19 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV offsetStr = colon + 1; Common::String segmentStr(str, colon); - dest->segment = strtol(segmentStr.c_str(), &endptr, 16); + dest->setSegment(strtol(segmentStr.c_str(), &endptr, 16)); if (*endptr) return 1; } else { int val = 0; - dest->segment = 0; + dest->setSegment(0); if (charsCountNumber == charsCount) { // Only numbers in input, assume decimal value val = strtol(str, &endptr, 10); if (*endptr) return 1; // strtol failed? - dest->offset = val; + dest->setOffset(val); return 0; } else { // We also got letters, check if there were only hexadecimal letters and '0x' at the start or 'h' at the end @@ -3728,7 +3801,7 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV val = strtol(str, &endptr, 16); if ((*endptr != 'h') && (*endptr != 0)) return 1; - dest->offset = val; + dest->setOffset(val); return 0; } else { // Something else was in input, assume object name @@ -3777,9 +3850,9 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV int val = strtol(offsetStr, &endptr, 16); if (relativeOffset) - dest->offset += val; + dest->incOffset(val); else - dest->offset = val; + dest->setOffset(val); if (*endptr) return 1; @@ -3859,15 +3932,15 @@ void Console::printList(List *list) { while (!pos.isNull()) { Node *node; - NodeTable *nt = (NodeTable *)_engine->_gamestate->_segMan->getSegment(pos.segment, SEG_TYPE_NODES); + NodeTable *nt = (NodeTable *)_engine->_gamestate->_segMan->getSegment(pos.getSegment(), SEG_TYPE_NODES); - if (!nt || !nt->isValidEntry(pos.offset)) { + if (!nt || !nt->isValidEntry(pos.getOffset())) { DebugPrintf(" WARNING: %04x:%04x: Doesn't contain list node!\n", PRINT_REG(pos)); return; } - node = &(nt->_table[pos.offset]); + node = &(nt->_table[pos.getOffset()]); DebugPrintf("\t%04x:%04x : %04x:%04x -> %04x:%04x\n", PRINT_REG(pos), PRINT_REG(node->key), PRINT_REG(node->value)); @@ -3886,37 +3959,37 @@ void Console::printList(List *list) { } int Console::printNode(reg_t addr) { - SegmentObj *mobj = _engine->_gamestate->_segMan->getSegment(addr.segment, SEG_TYPE_LISTS); + SegmentObj *mobj = _engine->_gamestate->_segMan->getSegment(addr.getSegment(), SEG_TYPE_LISTS); if (mobj) { ListTable *lt = (ListTable *)mobj; List *list; - if (!lt->isValidEntry(addr.offset)) { + if (!lt->isValidEntry(addr.getOffset())) { DebugPrintf("Address does not contain a list\n"); return 1; } - list = &(lt->_table[addr.offset]); + list = &(lt->_table[addr.getOffset()]); DebugPrintf("%04x:%04x : first x last = (%04x:%04x, %04x:%04x)\n", PRINT_REG(addr), PRINT_REG(list->first), PRINT_REG(list->last)); } else { NodeTable *nt; Node *node; - mobj = _engine->_gamestate->_segMan->getSegment(addr.segment, SEG_TYPE_NODES); + mobj = _engine->_gamestate->_segMan->getSegment(addr.getSegment(), SEG_TYPE_NODES); if (!mobj) { - DebugPrintf("Segment #%04x is not a list or node segment\n", addr.segment); + DebugPrintf("Segment #%04x is not a list or node segment\n", addr.getSegment()); return 1; } nt = (NodeTable *)mobj; - if (!nt->isValidEntry(addr.offset)) { + if (!nt->isValidEntry(addr.getOffset())) { DebugPrintf("Address does not contain a node\n"); return 1; } - node = &(nt->_table[addr.offset]); + node = &(nt->_table[addr.getOffset()]); DebugPrintf("%04x:%04x : prev x next = (%04x:%04x, %04x:%04x); maps %04x:%04x -> %04x:%04x\n", PRINT_REG(addr), PRINT_REG(node->pred), PRINT_REG(node->succ), PRINT_REG(node->key), PRINT_REG(node->value)); @@ -3954,8 +4027,8 @@ int Console::printObject(reg_t pos) { reg_t val = obj->getVariable(i); DebugPrintf("%04x:%04x", PRINT_REG(val)); - if (!val.segment) - DebugPrintf(" (%d)", val.offset); + if (!val.getSegment()) + DebugPrintf(" (%d)", val.getOffset()); const Object *ref = s->_segMan->getObject(val); if (ref) @@ -3968,8 +4041,8 @@ int Console::printObject(reg_t pos) { reg_t fptr = obj->getFunction(i); DebugPrintf(" [%03x] %s = %04x:%04x\n", obj->getFuncSelector(i), _engine->getKernel()->getSelectorName(obj->getFuncSelector(i)).c_str(), PRINT_REG(fptr)); } - if (s->_segMan->_heap[pos.segment]->getType() == SEG_TYPE_SCRIPT) - DebugPrintf("\nOwner script: %d\n", s->_segMan->getScript(pos.segment)->getScriptNumber()); + if (s->_segMan->_heap[pos.getSegment()]->getType() == SEG_TYPE_SCRIPT) + DebugPrintf("\nOwner script: %d\n", s->_segMan->getScript(pos.getSegment())->getScriptNumber()); return 0; } diff --git a/engines/sci/console.h b/engines/sci/console.h index d943923ba1..1c54748842 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -33,7 +33,7 @@ namespace Sci { class SciEngine; struct List; -reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode); +reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printBytecode); bool isJumpOpcode(EngineState *s, reg_t pos, reg_t& jumpOffset); class Console : public GUI::Debugger { @@ -94,6 +94,8 @@ private: bool cmdPlayVideo(int argc, const char **argv); bool cmdAnimateList(int argc, const char **argv); bool cmdWindowList(int argc, const char **argv); + bool cmdPlaneList(int argc, const char **argv); + bool cmdPlaneItemList(int argc, const char **argv); bool cmdSavedBits(int argc, const char **argv); bool cmdShowSavedBits(int argc, const char **argv); // Segments diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index 78df3065b2..58ac5f1fa6 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -397,8 +397,8 @@ static const ADExtraGuiOptionsMap optionsList[] = { { GAMEOPTION_FB01_MIDI, { - _s("Use IMF/Yahama FB-01 for MIDI output"), - _s("Use an IBM Music Feature card or a Yahama FB-01 FM synth module for MIDI output"), + _s("Use IMF/Yamaha FB-01 for MIDI output"), + _s("Use an IBM Music Feature card or a Yamaha FB-01 FM synth module for MIDI output"), "native_fb01", false } @@ -762,9 +762,6 @@ SaveStateDescriptor SciMetaEngine::querySaveMetaInfos(const char *target, int sl Graphics::Surface *const thumbnail = Graphics::loadThumbnail(*in); desc.setThumbnail(thumbnail); - desc.setDeletableFlag(true); - desc.setWriteProtectedFlag(false); - int day = (meta.saveDate >> 24) & 0xFF; int month = (meta.saveDate >> 16) & 0xFF; int year = meta.saveDate & 0xFFFF; diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index d741bb801f..b978f40aba 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -445,7 +445,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "a4b73d5d2b55bdb6e44345e99c8fbdd0", 4804}, {"resource.000", 0, "d908dbef56816ac6c60dd145fdeafb2b", 3536046}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO1(GUIO_MIDIGM) }, + Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO4(GUIO_MIDIGM, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Eco Quest - English DOS Floppy // SCI interpreter version 1.000.510 @@ -1007,7 +1007,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "459f5b04467bc2107aec02f5c4b71b37", 4878}, {"resource.001", 0, "3876da2ce16fb7dea2f5d943d946fa84", 1652150}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO2(GUIO_MIDIGM, GAMEOPTION_JONES_CDAUDIO) }, + Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO4(GUIO_MIDIGM, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_FB01_MIDI, GAMEOPTION_JONES_CDAUDIO) }, // King's Quest 1 SCI Remake - English Amiga (from www.back2roots.org) // Executable scanning reports "1.003.007" @@ -1221,7 +1221,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.000", 0, "449471bfd77be52f18a3773c7f7d843d", 571368}, {"resource.001", 0, "b45a581ff8751e052c7e364f58d3617f", 16800210}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO1(GUIO_MIDIGM) }, + Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO4(GUIO_MIDIGM, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // King's Quest 5 - English DOS Floppy // SCI interpreter version 1.000.060 @@ -1238,6 +1238,24 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 5 - English DOS Floppy + // VERSION file reports "0.000.051" + // Supplied by misterhands in bug report #3536863. + // This is the original English version, which has been externally patched to + // Polish in the Polish release below. + {"kq5", "", { + {"resource.map", 0, "70010c20138541f89013bb5e1b30f16a", 7998}, + {"resource.000", 0, "a591bd4b879fc832b8095c0b3befe9e2", 276398}, + {"resource.001", 0, "c0f48d4a7ebeaa6aa074fc98d77423e9", 1018560}, + {"resource.002", 0, "7f188a95acdb60bbe32a8379ba299393", 1307048}, + {"resource.003", 0, "0860785af59518b94d54718dddcd6907", 1348500}, + {"resource.004", 0, "c4745dd1e261c22daa6477961d08bf6c", 1239887}, + {"resource.005", 0, "6556ff8e7c4d1acf6a78aea154daa76c", 1287869}, + {"resource.006", 0, "da82e4beb744731d0a151f1d4922fafa", 1170456}, + {"resource.007", 0, "431def14ca29cdb5e6a5e84d3f38f679", 1240176}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 5 - English DOS Floppy (supplied by omer_mor in bug report #3036996) // VERSION file reports "0.000.051" {"kq5", "", { @@ -1308,6 +1326,21 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 5 DOS Spanish Floppy 0.000.062 VGA (5 x 3.5" disks) + // Supplied by dianiu in bug report #3555646 + {"kq5", "", { + {"resource.map", 0, "c09896a2a30c9b002c5cbbc62f5a5c3a", 8169}, + {"resource.000", 0, "1f1d03aead44da46362ff40c0074a3ec", 335871}, + {"resource.001", 0, "d1803ad904127ae091edb274ee8c047f", 1180637}, + {"resource.002", 0, "d9cd5972016f650cc31fb7c2a2b0953a", 1102207}, + {"resource.003", 0, "829c8caeff793f3cfcea2cb01aaa4150", 965586}, + {"resource.004", 0, "0bd9e570ee04b025e43d3075998fae5b", 1117965}, + {"resource.005", 0, "4aaa2e9a69089b9afbaaccbbf2c4e647", 1202936}, + {"resource.006", 0, "65b520e60c4217e6a6572d9edf77193b", 1141985}, + {"resource.007", 0, "f42b0100f0a1c30806814f8648b6bc28", 1145583}, + AD_LISTEND}, + Common::ES_ESP, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 5 - German DOS Floppy (supplied by markcoolio in bug report #2727101, also includes english language) // SCI interpreter version 1.000.060 {"kq5", "", { @@ -1354,8 +1387,10 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::IT_ITA, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // King's Quest 5 - Polish DOS Floppy (supplied by jacek909 in bug report #2725722, includes english language?!) + // King's Quest 5 - Polish DOS Floppy (supplied by jacek909 in bug report #2725722) // SCI interpreter version 1.000.060 + // VERSION file reports "0.000.051". + // This is actually an English version with external text resource patches (bug #3536863). {"kq5", "", { {"resource.map", 0, "70010c20138541f89013bb5e1b30f16a", 7998}, {"resource.000", 0, "a591bd4b879fc832b8095c0b3befe9e2", 276398}, @@ -1366,6 +1401,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.005", 0, "6556ff8e7c4d1acf6a78aea154daa76c", 1287869}, {"resource.006", 0, "da82e4beb744731d0a151f1d4922fafa", 1170456}, {"resource.007", 0, "431def14ca29cdb5e6a5e84d3f38f679", 1240176}, + {"text.000", 0, "601aa35a3ddeb558e1280e0963e955a2", 1517}, AD_LISTEND}, Common::PL_POL, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, @@ -1447,6 +1483,16 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::DE_DEU, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 6 - Spanish DOS Floppy (from jvprat) + // Executable scanning reports "1.cfs.158", VERSION file reports "1.000.000, July 5, 1994" + // SCI interpreter version 1.001.055 + {"kq6", "", { + {"resource.map", 0, "a73a5ab04b8f60c4b75b946a4dccea5a", 8953}, + {"resource.000", 0, "4da3ad5868a775549a7cc4f72770a58e", 8537260}, + {"resource.msg", 0, "41eed2d3893e1ca6c3695deba4e9d2e8", 267102}, + AD_LISTEND}, + Common::ES_ESP, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 6 - English DOS CD (from the King's Quest Collection) // Executable scanning reports "1.cfs.158", VERSION file reports "1.034 9/11/94 - KQ6 version 1.000.00G" // SCI interpreter version 1.001.054 @@ -1465,16 +1511,6 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformWindows, ADGF_CD, GUIO5(GUIO_NOASPECT, GAMEOPTION_KQ6_WINDOWS_CURSORS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // King's Quest 6 - Spanish DOS CD (from jvprat) - // Executable scanning reports "1.cfs.158", VERSION file reports "1.000.000, July 5, 1994" - // SCI interpreter version 1.001.055 - {"kq6", "CD", { - {"resource.map", 0, "a73a5ab04b8f60c4b75b946a4dccea5a", 8953}, - {"resource.000", 0, "4da3ad5868a775549a7cc4f72770a58e", 8537260}, - {"resource.msg", 0, "41eed2d3893e1ca6c3695deba4e9d2e8", 267102}, - AD_LISTEND}, - Common::ES_ESP, Common::kPlatformPC, ADGF_CD, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // King's Quest 6 - English Macintosh Floppy // VERSION file reports "1.0" {"kq6", "", { @@ -2660,6 +2696,13 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::DE_DEU, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Police Quest 3 - Spanish DOS v1.000 - Supplied by dianiu in bug report #3555647 + {"pq3", "", { + {"resource.map", 0, "ffa0b4631c4e36d69631256d19ba29e7", 5421}, + {"resource.000", 0, "5ee460af3d70c06a745cc482b6c783ba", 5410263}, + AD_LISTEND}, + Common::ES_ESP, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Police Quest 3 EGA // Reported by musiclyinspired in bug report #3046573 {"pq3", "", { @@ -2777,6 +2820,31 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy v1.102 Int#0.000.629 (suppled by digitoxin1 in bug report #3554611) + {"qfg1", "", { + {"resource.map", 0, "b162dbd4632250d4d83bed46d0783c10", 6396}, + {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 78800}, + {"resource.001", 0, "a270012fa74445d74c044d1b65a9ff8c", 459835}, + {"resource.002", 0, "e64004e020fdf1813be52b639b08be89", 635561}, + {"resource.003", 0, "f0af87c60ec869946da442833aa5afa8", 640502}, + {"resource.004", 0, "f0af87c60ec869946da442833aa5afa8", 644575}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + + // Quest for Glory 1 / Hero's Quest - English DOS 5.25" Floppy v1.102 Int#0.000.629 (suppled by digitoxin1 in bug report #3554611) + {"qfg1", "", { + {"resource.map", 0, "5772a2c1bfae46f26582582c9901121e", 6858}, + {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 78800}, + {"resource.001", 0, "a270012fa74445d74c044d1b65a9ff8c", 75090}, + {"resource.002", 0, "d22695c53835dfdece056d86f26c251e", 271354}, + {"resource.003", 0, "3cd085e27078f269b3ece5838812ff41", 258084}, + {"resource.004", 0, "8927c7a04a78f1e76f342db3ccc9d879", 267835}, + {"resource.005", 0, "13d16cc9b90b51e2c8643cdf52a62957", 268807}, + {"resource.006", 0, "48b2b3c964dcbeccb68e984e6d4e97db", 278473}, + {"resource.007", 0, "f0af87c60ec869946da442833aa5afa8", 269237}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Quest for Glory 1 / Hero's Quest - English DOS 5.25" Floppy (supplied by markcoolio in bug report #2723843) // Executable scanning reports "0.000.566" {"qfg1", "", { @@ -2864,17 +2932,6 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Quest for Glory 1 (from abevi, bug report #2612718) - {"qfg1", "", { - {"resource.map", 0, "b162dbd4632250d4d83bed46d0783c10", 6396}, - {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 78800}, - {"resource.001", 0, "a270012fa74445d74c044d1b65a9ff8c", 459835}, - {"resource.002", 0, "e64004e020fdf1813be52b639b08be89", 635561}, - {"resource.003", 0, "f0af87c60ec869946da442833aa5afa8", 640502}, - {"resource.004", 0, "f0af87c60ec869946da442833aa5afa8", 644575}, - AD_LISTEND}, - Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Quest for Glory 1 - English DOS // SCI interpreter version 0.000.629 {"qfg1", "", { @@ -2925,7 +2982,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.006", 0, "ccf5dba33e5cab6d5872838c0f8db44c", 500039}, {"resource.007", 0, "4c9fc1587545879295cb9627f56a2cb8", 575056}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Quest for Glory 2 - English (supplied by ssburnout in bug report #3049193) // 1.000 5x5.25" (label: INT#10.31.90) @@ -2937,7 +2994,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.003", 0, "0790f67d87642132be515cab05026baa", 972144}, {"resource.004", 0, "2ac1e6fea9aa1f5b91a06693a67b9766", 982830}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Quest for Glory 2 - English (supplied by ssburnout in bug report #3049193) // 1.000 9x3.5" (label: INT#10.31.90) @@ -2952,7 +3009,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.006", 0, "5e9deacbdb17198ad844988e04833520", 498593}, {"resource.007", 0, "2ac1e6fea9aa1f5b91a06693a67b9766", 490151}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Quest for Glory 2 - English (from FRG) // Executable scanning reports "1.000.072" @@ -2964,7 +3021,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.003", 0, "b192607c42f6960ecdf2ad2e4f90e9bc", 972804}, {"resource.004", 0, "cd2de58e27665d5853530de93fae7cd6", 983617}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Quest for Glory 2 - English DOS // Executable scanning reports "1.000.072" @@ -2979,7 +3036,22 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.006", 0, "b1944bd664ddbd2859cdaa0c4a0d6281", 507489}, {"resource.007", 0, "cd2de58e27665d5853530de93fae7cd6", 490794}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + + // Quest for Glory 2 - English DOS (supplied by digitoxin1 in bug report #3554614) + // v1.102 9x3.5" (label: Int#11.20.90) + {"qfg2", "", { + {"resource.map", 0, "367023314ea33e3156297402f6c1da49", 8166}, + {"resource.000", 0, "a17e374c4d33b81208c862bc0ffc1a38", 212119}, + {"resource.001", 0, "e08d7887e30b12008c40f9570447711a", 331995}, + {"resource.002", 0, "df137dc7869cab07e1149ba2333c815c", 467461}, + {"resource.003", 0, "df137dc7869cab07e1149ba2333c815c", 502560}, + {"resource.004", 0, "df137dc7869cab07e1149ba2333c815c", 488532}, + {"resource.005", 0, "df137dc7869cab07e1149ba2333c815c", 478574}, + {"resource.006", 0, "b1944bd664ddbd2859cdaa0c4a0d6281", 507489}, + {"resource.007", 0, "cd2de58e27665d5853530de93fae7cd6", 490794}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Quest for Glory 2 - English DOS Non-Interactive Demo // Executable scanning reports "1.000.046" @@ -2987,7 +3059,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "e75eb86bdd517b3ef709058249986a87", 906}, {"resource.001", 0, "9b098f9e1008abe30e56c93b896494e6", 362123}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Quest for Glory 3 - English DOS Non-Interactive Demo (from FRG) // Executable scanning reports "1.001.021", VERSION file reports "1.000, 0.001.059, 6.12.92" @@ -3445,6 +3517,19 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Space Quest 4 1.000 - French DOS Floppy (supplied by misterhands in bug report #3515247) + {"sq4", "", { + + {"resource.map", 0, "1fd6f356f6a59ad2057686ce6573caeb", 6159}, + {"resource.000", 0, "8000a55aebc50a68b7cce07a8c33758c", 205287}, + {"resource.001", 0, "99a6df6d366b3f061271ff3450ac0d32", 1269850}, + {"resource.002", 0, "a6a8d7a24dbb7a266a26b084e7275e89", 1242668}, + {"resource.003", 0, "482a99c8103b4bcb5706e5969d1c1193", 1323083}, + {"resource.004", 0, "b2cca3afcf2e013b8ce86b64155af766", 1254353}, + {"resource.005", 0, "9e520577e035547c4b5149a6d12ef85b", 1098814}, + AD_LISTEND}, + Common::FR_FRA, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Space Quest 4 1.000 - English DOS Floppy (from abevi, bug report #2612718) {"sq4", "", { {"resource.map", 0, "8f08b97ca093f370c56d99715b015554", 6153}, @@ -3523,7 +3608,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "ed90a8e3ccc53af6633ff6ab58392bae", 7054}, {"resource.000", 0, "63247e3901ab8963d4eece73747832e0", 5157378}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO1(GAMEOPTION_SQ4_SILVER_CURSORS) }, + Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO5(GUIO_MIDIGM, GAMEOPTION_SQ4_SILVER_CURSORS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Space Quest 4 - English Windows CD (from the Space Quest Collection) // Executable scanning reports "1.001.064", VERSION file reports "1.0" @@ -3731,53 +3816,61 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 // Torin's Passage - English Windows Interactive Demo - // SCI interpreter version 2.100.002 (just a guess) + // SCI interpreter version 2.100.002 {"torin", "Demo", { {"resmap.000", 0, "9a3e172cde9963d0a969f26469318cec", 3403}, {"ressci.000", 0, "db3e290481c35c3224e9602e71e4a1f1", 5073868}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Torin's Passage - English Windows - // SCI interpreter version 2.100.002 (just a guess) + // Torin's Passage (Multilingual) - English Windows CD + // SCI interpreter version 2.100.002 {"torin", "", { {"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799}, {"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Torin's Passage - Spanish Windows (from jvprat) + // Torin's Passage (Multilingual) - Spanish Windows CD (from jvprat) // Executable scanning reports "2.100.002", VERSION file reports "1.0" {"torin", "", { {"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799}, {"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887}, // TODO: depend on one of the patches? AD_LISTEND}, - Common::ES_ESP, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::ES_ESP, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Torin's Passage - French Windows - // SCI interpreter version 2.100.002 (just a guess) + // Torin's Passage (Multilingual) - French Windows CD + // SCI interpreter version 2.100.002 {"torin", "", { {"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799}, {"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887}, AD_LISTEND}, - Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Torin's Passage - German Windows - // SCI interpreter version 2.100.002 (just a guess) + // Torin's Passage (Multilingual) - German Windows CD + // SCI interpreter version 2.100.002 {"torin", "", { {"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799}, {"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887}, AD_LISTEND}, - Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Torin's Passage - Italian Windows CD (from glorifindel) - // SCI interpreter version 2.100.002 (just a guess) + // Torin's Passage (Multilingual) - Italian Windows CD (from glorifindel) + // SCI interpreter version 2.100.002 {"torin", "", { {"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799}, {"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887}, AD_LISTEND}, Common::IT_ITA, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + + // Torin's Passage - French Windows (from LePhilousophe) + // SCI interpreter version 2.100.002 + {"torin", "", { + {"resmap.000", 0, "66ed46e3e56f487e688d52f05b33d0ba", 9787}, + {"ressci.000", 0, "118f9bec04bfe17c4f87bbb5ddb43c18", 56126981}, + AD_LISTEND}, + Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #endif // ENABLE_SCI32 // SCI Fanmade Games diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index cad95b1c18..22c0a1479d 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -45,6 +45,7 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan) _usesCdTrack = Common::File::exists("cdaudio.map"); if (!ConfMan.getBool("use_cdaudio")) _usesCdTrack = false; + _forceDOSTracks = false; } reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) { @@ -73,11 +74,11 @@ bool GameFeatures::autoDetectSoundType() { // Look up the script address reg_t addr = getDetectionAddr("Sound", SELECTOR(play)); - if (!addr.segment) + if (!addr.getSegment()) return false; - uint16 offset = addr.offset; - Script *script = _segMan->getScript(addr.segment); + uint16 offset = addr.getOffset(); + Script *script = _segMan->getScript(addr.getSegment()); uint16 intParam = 0xFFFF; bool foundTarget = false; @@ -220,11 +221,11 @@ bool GameFeatures::autoDetectLofsType(Common::String gameSuperClassName, int met // Look up the script address reg_t addr = getDetectionAddr(gameSuperClassName.c_str(), -1, methodNum); - if (!addr.segment) + if (!addr.getSegment()) return false; - uint16 offset = addr.offset; - Script *script = _segMan->getScript(addr.segment); + uint16 offset = addr.getOffset(); + Script *script = _segMan->getScript(addr.getSegment()); while (true) { int16 opparams[4]; @@ -319,11 +320,11 @@ bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) { // Look up the script address reg_t addr = getDetectionAddr("Rm", SELECTOR(overlay), methodNum); - if (!addr.segment) + if (!addr.getSegment()) return false; - uint16 offset = addr.offset; - Script *script = _segMan->getScript(addr.segment); + uint16 offset = addr.getOffset(); + Script *script = _segMan->getScript(addr.getSegment()); while (true) { int16 opparams[4]; @@ -473,11 +474,11 @@ bool GameFeatures::autoDetectSci21KernelType() { // Look up the script address reg_t addr = getDetectionAddr("Sound", SELECTOR(play)); - if (!addr.segment) + if (!addr.getSegment()) return false; - uint16 offset = addr.offset; - Script *script = _segMan->getScript(addr.segment); + uint16 offset = addr.getOffset(); + Script *script = _segMan->getScript(addr.getSegment()); while (true) { int16 opparams[4]; @@ -549,11 +550,11 @@ bool GameFeatures::autoDetectSci21StringFunctionType() { // Look up the script address reg_t addr = getDetectionAddr("Str", SELECTOR(size)); - if (!addr.segment) + if (!addr.getSegment()) return false; - uint16 offset = addr.offset; - Script *script = _segMan->getScript(addr.segment); + uint16 offset = addr.getOffset(); + Script *script = _segMan->getScript(addr.getSegment()); while (true) { int16 opparams[4]; @@ -586,11 +587,11 @@ bool GameFeatures::autoDetectMoveCountType() { // Look up the script address reg_t addr = getDetectionAddr("Motion", SELECTOR(doit)); - if (!addr.segment) + if (!addr.getSegment()) return false; - uint16 offset = addr.offset; - Script *script = _segMan->getScript(addr.segment); + uint16 offset = addr.getOffset(); + Script *script = _segMan->getScript(addr.getSegment()); bool foundTarget = false; while (true) { @@ -642,7 +643,7 @@ MoveCountType GameFeatures::detectMoveCountType() { } bool GameFeatures::useAltWinGMSound() { - if (g_sci && g_sci->getPlatform() == Common::kPlatformWindows && g_sci->isCD()) { + if (g_sci && g_sci->getPlatform() == Common::kPlatformWindows && g_sci->isCD() && !_forceDOSTracks) { SciGameId id = g_sci->getGameId(); return (id == GID_ECOQUEST || id == GID_JONES || diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 4592c5be9c..f6bb0b5759 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -117,6 +117,12 @@ public: */ bool useAltWinGMSound(); + /** + * Forces DOS soundtracks in Windows CD versions when the user hasn't + * selected a MIDI output device + */ + void forceDOSTracks() { _forceDOSTracks = true; } + private: reg_t getDetectionAddr(const Common::String &objName, Selector slc, int methodNum = -1); @@ -137,6 +143,7 @@ private: MoveCountType _moveCountType; bool _usesCdTrack; + bool _forceDOSTracks; SegManager *_segMan; Kernel *_kernel; diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp new file mode 100644 index 0000000000..3dc042389e --- /dev/null +++ b/engines/sci/engine/file.cpp @@ -0,0 +1,464 @@ +/* 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/savefile.h" +#include "common/stream.h" + +#include "sci/sci.h" +#include "sci/engine/file.h" +#include "sci/engine/kernel.h" +#include "sci/engine/savegame.h" +#include "sci/engine/selector.h" +#include "sci/engine/state.h" + +namespace Sci { + +/* + * Note on how file I/O is implemented: In ScummVM, one can not create/write + * arbitrary data files, simply because many of our target platforms do not + * support this. The only files one can create are savestates. But SCI has an + * opcode to create and write to seemingly 'arbitrary' files. This is mainly + * used in LSL3 for LARRY3.DRV (which is a game data file, not a driver, used + * for persisting the results of the "age quiz" across restarts) and in LSL5 + * for MEMORY.DRV (which is again a game data file and contains the game's + * password, XOR encrypted). + * To implement that opcode, we combine the SaveFileManager with regular file + * code, similarly to how the SCUMM HE engine does it. + * + * To handle opening a file called "foobar", what we do is this: First, we + * create an 'augmented file name', by prepending the game target and a dash, + * so if we running game target sq1sci, the name becomes "sq1sci-foobar". + * Next, we check if such a file is known to the SaveFileManager. If so, we + * we use that for reading/writing, delete it, whatever. + * + * If no such file is present but we were only asked to *read* the file, + * we fallback to looking for a regular file called "foobar", and open that + * for reading only. + */ + +reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename) { + Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH); + englishName.toLowercase(); + + Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName; + Common::SeekableReadStream *inFile = 0; + Common::WriteStream *outFile = 0; + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + + bool isCompressed = true; + const SciGameId gameId = g_sci->getGameId(); + if ((gameId == GID_QFG1 || gameId == GID_QFG1VGA || gameId == GID_QFG2 || gameId == GID_QFG3) + && englishName.hasSuffix(".sav")) { + // QFG Characters are saved via the CharSave object. + // We leave them uncompressed so that they can be imported in later QFG + // games. + // Rooms/Scripts: QFG1: 601, QFG2: 840, QFG3/4: 52 + isCompressed = false; + } + + if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { + // Try to open file, abort if not possible + inFile = saveFileMan->openForLoading(wrappedName); + // If no matching savestate exists: fall back to reading from a regular + // file + if (!inFile) + inFile = SearchMan.createReadStreamForMember(englishName); + + if (!inFile) + debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str()); + } else if (mode == _K_FILE_MODE_CREATE) { + // Create the file, destroying any content it might have had + outFile = saveFileMan->openForSaving(wrappedName, isCompressed); + if (!outFile) + debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); + } else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) { + // Try to open file, create it if it doesn't exist + outFile = saveFileMan->openForSaving(wrappedName, isCompressed); + if (!outFile) + debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); + + // QfG1 opens the character export file with _K_FILE_MODE_CREATE first, + // closes it immediately and opens it again with this here. Perhaps + // other games use this for read access as well. I guess changing this + // whole code into using virtual files and writing them after close + // would be more appropriate. + } else { + error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str()); + } + + if (!inFile && !outFile) { // Failed + debugC(kDebugLevelFile, " -> file_open() failed"); + return SIGNAL_REG; + } + + // Find a free file handle + uint handle = 1; // Ignore _fileHandles[0] + while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen()) + handle++; + + if (handle == s->_fileHandles.size()) { + // Hit size limit => Allocate more space + s->_fileHandles.resize(s->_fileHandles.size() + 1); + } + + s->_fileHandles[handle]._in = inFile; + s->_fileHandles[handle]._out = outFile; + s->_fileHandles[handle]._name = englishName; + + debugC(kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle); + return make_reg(0, handle); +} + +FileHandle *getFileFromHandle(EngineState *s, uint handle) { + if (handle == 0 || handle == VIRTUALFILE_HANDLE) { + error("Attempt to use invalid file handle (%d)", handle); + return 0; + } + + if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) { + warning("Attempt to use invalid/unused file handle %d", handle); + return 0; + } + + return &s->_fileHandles[handle]; +} + +int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) { + FileHandle *f = getFileFromHandle(s, handle); + if (!f) + return 0; + + if (!f->_in) { + error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str()); + return 0; + } + int readBytes = 0; + if (maxsize > 1) { + memset(dest, 0, maxsize); + f->_in->readLine(dest, maxsize); + readBytes = strlen(dest); // FIXME: sierra sci returned byte count and didn't react on NUL characters + // The returned string must not have an ending LF + if (readBytes > 0) { + if (dest[readBytes - 1] == 0x0A) + dest[readBytes - 1] = 0; + } + } else { + *dest = 0; + } + + debugC(kDebugLevelFile, " -> FGets'ed \"%s\"", dest); + return readBytes; +} + +static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) { + if (l.date != r.date) + return (l.date > r.date); + return (l.time > r.time); +} + +// Create a sorted array containing all found savedgames +void listSavegames(Common::Array<SavegameDesc> &saves) { + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + + // Load all saves + Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern()); + + for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) { + Common::String filename = *iter; + Common::SeekableReadStream *in; + if ((in = saveFileMan->openForLoading(filename))) { + SavegameMetadata meta; + if (!get_savegame_metadata(in, &meta) || meta.name.empty()) { + // invalid + delete in; + continue; + } + delete in; + + SavegameDesc desc; + desc.id = strtol(filename.end() - 3, NULL, 10); + desc.date = meta.saveDate; + // We need to fix date in here, because we save DDMMYYYY instead of + // YYYYMMDD, so sorting wouldn't work + desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24); + desc.time = meta.saveTime; + desc.version = meta.version; + + if (meta.name.lastChar() == '\n') + meta.name.deleteLastChar(); + + Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH); + + debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id); + + saves.push_back(desc); + } + } + + // Sort the list by creation date of the saves + Common::sort(saves.begin(), saves.end(), _savegame_sort_byDate); +} + +// Find a savedgame according to virtualId and return the position within our array +int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) { + for (uint saveNr = 0; saveNr < saves.size(); saveNr++) { + if (saves[saveNr].id == savegameId) + return saveNr; + } + return -1; +} + + +FileHandle::FileHandle() : _in(0), _out(0) { +} + +FileHandle::~FileHandle() { + close(); +} + +void FileHandle::close() { + delete _in; + delete _out; + _in = 0; + _out = 0; + _name.clear(); +} + +bool FileHandle::isOpen() const { + return _in || _out; +} + + +void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) { + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask); + if (!foundFiles.empty()) { + _files.push_back(title); + _virtualFiles.push_back(""); + Common::StringArray::iterator it; + Common::StringArray::iterator it_end = foundFiles.end(); + + for (it = foundFiles.begin(); it != it_end; it++) { + Common::String regularFilename = *it; + Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1); + + Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename); + int32 testfileSize = testfile->size(); + delete testfile; + if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game. + continue; // and we dont want to have those in the list + // We need to remove the prefix for display purposes + _files.push_back(wrappedFilename); + // but remember the actual name as well + _virtualFiles.push_back(regularFilename); + } + } +} + +Common::String DirSeeker::getVirtualFilename(uint fileNumber) { + if (fileNumber >= _virtualFiles.size()) + error("invalid virtual filename access"); + return _virtualFiles[fileNumber]; +} + +reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) { + // Verify that we are given a valid buffer + if (!buffer.getSegment()) { + error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str()); + return NULL_REG; + } + _outbuffer = buffer; + _files.clear(); + _virtualFiles.clear(); + + int QfGImport = g_sci->inQfGImportRoom(); + if (QfGImport) { + _files.clear(); + addAsVirtualFiles("-QfG1-", "qfg1-*"); + addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*"); + if (QfGImport > 2) + addAsVirtualFiles("-QfG2-", "qfg2-*"); + if (QfGImport > 3) + addAsVirtualFiles("-QfG3-", "qfg3-*"); + + if (QfGImport == 3) { + // QfG3 sorts the filelisting itself, we can't let that happen otherwise our + // virtual list would go out-of-sync + reg_t savedHeros = segMan->findObjectByName("savedHeros"); + if (!savedHeros.isNull()) + writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0); + } + + } else { + // Prefix the mask + const Common::String wrappedMask = g_sci->wrapFilename(mask); + + // Obtain a list of all files matching the given mask + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + _files = saveFileMan->listSavefiles(wrappedMask); + } + + // Reset the list iterator and write the first match to the output buffer, + // if any. + _iter = _files.begin(); + return nextFile(segMan); +} + +reg_t DirSeeker::nextFile(SegManager *segMan) { + if (_iter == _files.end()) { + return NULL_REG; + } + + Common::String string; + + if (_virtualFiles.empty()) { + // Strip the prefix, if we don't got a virtual filelisting + const Common::String wrappedString = *_iter; + string = g_sci->unwrapFilename(wrappedString); + } else { + string = *_iter; + } + if (string.size() > 12) + string = Common::String(string.c_str(), 12); + segMan->strcpy(_outbuffer, string.c_str()); + + // Return the result and advance the list iterator :) + ++_iter; + return _outbuffer; +} + + +#ifdef ENABLE_SCI32 + +VirtualIndexFile::VirtualIndexFile(Common::String fileName) : _fileName(fileName), _changed(false) { + Common::SeekableReadStream *inFile = g_sci->getSaveFileManager()->openForLoading(fileName); + + _bufferSize = inFile->size(); + _buffer = new char[_bufferSize]; + inFile->read(_buffer, _bufferSize); + _ptr = _buffer; + delete inFile; +} + +VirtualIndexFile::VirtualIndexFile(uint32 initialSize) : _changed(false) { + _bufferSize = initialSize; + _buffer = new char[_bufferSize]; + _ptr = _buffer; +} + +VirtualIndexFile::~VirtualIndexFile() { + close(); + + _bufferSize = 0; + delete[] _buffer; + _buffer = 0; +} + +uint32 VirtualIndexFile::read(char *buffer, uint32 size) { + uint32 curPos = _ptr - _buffer; + uint32 finalSize = MIN<uint32>(size, _bufferSize - curPos); + char *localPtr = buffer; + + for (uint32 i = 0; i < finalSize; i++) + *localPtr++ = *_ptr++; + + return finalSize; +} + +uint32 VirtualIndexFile::write(const char *buffer, uint32 size) { + _changed = true; + uint32 curPos = _ptr - _buffer; + + // Check if the buffer needs to be resized + if (curPos + size >= _bufferSize) { + _bufferSize = curPos + size + 1; + char *tmp = _buffer; + _buffer = new char[_bufferSize]; + _ptr = _buffer + curPos; + memcpy(_buffer, tmp, _bufferSize); + delete[] tmp; + } + + for (uint32 i = 0; i < size; i++) + *_ptr++ = *buffer++; + + return size; +} + +uint32 VirtualIndexFile::readLine(char *buffer, uint32 size) { + uint32 startPos = _ptr - _buffer; + uint32 bytesRead = 0; + char *localPtr = buffer; + + // This is not a full-blown implementation of readLine, but it + // suffices for Phantasmagoria + while (startPos + bytesRead < size) { + bytesRead++; + + if (*_ptr == 0 || *_ptr == 0x0A) { + _ptr++; + *localPtr = 0; + return bytesRead; + } else { + *localPtr++ = *_ptr++; + } + } + + return bytesRead; +} + +bool VirtualIndexFile::seek(int32 offset, int whence) { + uint32 startPos = _ptr - _buffer; + assert(offset >= 0); + + switch (whence) { + case SEEK_CUR: + assert(startPos + offset < _bufferSize); + _ptr += offset; + break; + case SEEK_SET: + assert(offset < (int32)_bufferSize); + _ptr = _buffer + offset; + break; + case SEEK_END: + assert((int32)_bufferSize - offset >= 0); + _ptr = _buffer + (_bufferSize - offset); + break; + } + + return true; +} + +void VirtualIndexFile::close() { + if (_changed && !_fileName.empty()) { + Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName); + outFile->write(_buffer, _bufferSize); + delete outFile; + } + + // Maintain the buffer, and seek to the beginning of it + _ptr = _buffer; +} + +#endif + +} // End of namespace Sci diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h new file mode 100644 index 0000000000..1c8e302d15 --- /dev/null +++ b/engines/sci/engine/file.h @@ -0,0 +1,140 @@ +/* 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 SCI_ENGINE_FILE_H +#define SCI_ENGINE_FILE_H + +#include "common/str-array.h" +#include "common/stream.h" + +namespace Sci { + +enum { + _K_FILE_MODE_OPEN_OR_CREATE = 0, + _K_FILE_MODE_OPEN_OR_FAIL = 1, + _K_FILE_MODE_CREATE = 2 +}; + +/* Maximum length of a savegame name (including terminator character). */ +#define SCI_MAX_SAVENAME_LENGTH 0x24 + +enum { + MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ +}; + +#define VIRTUALFILE_HANDLE 200 +#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir" + +struct SavegameDesc { + int16 id; + int virtualId; // straight numbered, according to id but w/o gaps + int date; + int time; + int version; + char name[SCI_MAX_SAVENAME_LENGTH]; +}; + +class FileHandle { +public: + Common::String _name; + Common::SeekableReadStream *_in; + Common::WriteStream *_out; + +public: + FileHandle(); + ~FileHandle(); + + void close(); + bool isOpen() const; +}; + + +class DirSeeker { +protected: + reg_t _outbuffer; + Common::StringArray _files; + Common::StringArray _virtualFiles; + Common::StringArray::const_iterator _iter; + +public: + DirSeeker() { + _outbuffer = NULL_REG; + _iter = _files.begin(); + } + + reg_t firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan); + reg_t nextFile(SegManager *segMan); + + Common::String getVirtualFilename(uint fileNumber); + +private: + void addAsVirtualFiles(Common::String title, Common::String fileMask); +}; + + +#ifdef ENABLE_SCI32 + +/** + * An implementation of a virtual file that supports basic read and write + * operations simultaneously. + * + * This class has been initially implemented for Phantasmagoria, which has its + * own custom save/load code. The load code keeps checking for the existence + * of the save index file and keeps closing and reopening it for each save + * slot. This is notoriously slow and clumsy, and introduces noticeable delays, + * especially for non-desktop systems. Also, its game scripts request to open + * the index file for reading and writing with the same parameters + * (SaveManager::setCurrentSave and SaveManager::getCurrentSave). Moreover, + * the game scripts reopen the index file for writing in order to update it + * and seek within it. We do not support seeking in writeable streams, and the + * fact that our saved games are ZIP files makes this operation even more + * expensive. Finally, the savegame index file is supposed to be expanded when + * a new save slot is added. + * For the aforementioned reasons, this class has been implemented, which offers + * the basic functionality needed by the game scripts in Phantasmagoria. + */ +class VirtualIndexFile { +public: + VirtualIndexFile(Common::String fileName); + VirtualIndexFile(uint32 initialSize); + ~VirtualIndexFile(); + + uint32 read(char *buffer, uint32 size); + uint32 readLine(char *buffer, uint32 size); + uint32 write(const char *buffer, uint32 size); + bool seek(int32 offset, int whence); + void close(); + +private: + char *_buffer; + uint32 _bufferSize; + char *_ptr; + + Common::String _fileName; + bool _changed; +}; + +#endif + +} // End of namespace Sci + +#endif // SCI_ENGINE_FILE_H diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp index 2d71878bda..9a30ff3e17 100644 --- a/engines/sci/engine/gc.cpp +++ b/engines/sci/engine/gc.cpp @@ -47,7 +47,7 @@ const char *segmentTypeNames[] = { #endif void WorklistManager::push(reg_t reg) { - if (!reg.segment) // No numbers + if (!reg.getSegment()) // No numbers return; debugC(kDebugLevelGC, "[GC] Adding %04x:%04x", PRINT_REG(reg)); @@ -69,7 +69,7 @@ static AddrSet *normalizeAddresses(SegManager *segMan, const AddrSet &nonnormal_ for (AddrSet::const_iterator i = nonnormal_map.begin(); i != nonnormal_map.end(); ++i) { reg_t reg = i->_key; - SegmentObj *mobj = segMan->getSegmentObj(reg.segment); + SegmentObj *mobj = segMan->getSegmentObj(reg.getSegment()); if (mobj) { reg = mobj->findCanonicAddress(segMan, reg); @@ -85,11 +85,11 @@ static void processWorkList(SegManager *segMan, WorklistManager &wm, const Commo while (!wm._worklist.empty()) { reg_t reg = wm._worklist.back(); wm._worklist.pop_back(); - if (reg.segment != stackSegment) { // No need to repeat this one + if (reg.getSegment() != stackSegment) { // No need to repeat this one debugC(kDebugLevelGC, "[GC] Checking %04x:%04x", PRINT_REG(reg)); - if (reg.segment < heap.size() && heap[reg.segment]) { + if (reg.getSegment() < heap.size() && heap[reg.getSegment()]) { // Valid heap object? Find its outgoing references! - wm.pushArray(heap[reg.segment]->listAllOutgoingReferences(reg)); + wm.pushArray(heap[reg.getSegment()]->listAllOutgoingReferences(reg)); } } } diff --git a/engines/sci/engine/gc.h b/engines/sci/engine/gc.h index 97aa6513b6..9e02bbd0bd 100644 --- a/engines/sci/engine/gc.h +++ b/engines/sci/engine/gc.h @@ -32,7 +32,7 @@ namespace Sci { struct reg_t_Hash { uint operator()(const reg_t& x) const { - return (x.segment << 3) ^ x.offset ^ (x.offset << 16); + return (x.getSegment() << 3) ^ x.getOffset() ^ (x.getOffset() << 16); } }; diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index c99bc4fe47..46051ef145 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -357,27 +357,27 @@ static uint16 *parseKernelSignature(const char *kernelName, const char *writtenS uint16 Kernel::findRegType(reg_t reg) { // No segment? Must be integer - if (!reg.segment) - return SIG_TYPE_INTEGER | (reg.offset ? 0 : SIG_TYPE_NULL); + if (!reg.getSegment()) + return SIG_TYPE_INTEGER | (reg.getOffset() ? 0 : SIG_TYPE_NULL); - if (reg.segment == 0xFFFF) + if (reg.getSegment() == 0xFFFF) return SIG_TYPE_UNINITIALIZED; // Otherwise it's an object - SegmentObj *mobj = _segMan->getSegmentObj(reg.segment); + SegmentObj *mobj = _segMan->getSegmentObj(reg.getSegment()); if (!mobj) return SIG_TYPE_ERROR; uint16 result = 0; - if (!mobj->isValidOffset(reg.offset)) + if (!mobj->isValidOffset(reg.getOffset())) result |= SIG_IS_INVALID; switch (mobj->getType()) { case SEG_TYPE_SCRIPT: - if (reg.offset <= (*(Script *)mobj).getBufSize() && - reg.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && - RAW_IS_OBJECT((*(Script *)mobj).getBuf(reg.offset)) ) { - result |= ((Script *)mobj)->getObject(reg.offset) ? SIG_TYPE_OBJECT : SIG_TYPE_REFERENCE; + if (reg.getOffset() <= (*(Script *)mobj).getBufSize() && + reg.getOffset() >= (uint)-SCRIPT_OBJECT_MAGIC_OFFSET && + (*(Script *)mobj).offsetIsObject(reg.getOffset())) { + result |= ((Script *)mobj)->getObject(reg.getOffset()) ? SIG_TYPE_OBJECT : SIG_TYPE_REFERENCE; } else result |= SIG_TYPE_REFERENCE; break; @@ -608,7 +608,7 @@ void Kernel::mapFunctions() { _kernelFuncs[id].workarounds = kernelMap->workarounds; if (kernelMap->subFunctions) { // Get version for subfunction identification - SciVersion mySubVersion = (SciVersion)kernelMap->function(NULL, 0, NULL).offset; + SciVersion mySubVersion = (SciVersion)kernelMap->function(NULL, 0, NULL).getOffset(); // Now check whats the highest subfunction-id for this version const SciKernelMapSubEntry *kernelSubMap = kernelMap->subFunctions; uint16 subFunctionCount = 0; @@ -757,13 +757,26 @@ bool Kernel::debugSetFunction(const char *kernelName, int logging, int breakpoin return true; } -void Kernel::setDefaultKernelNames(GameFeatures *features) { - _kernelNames = Common::StringArray(s_defaultKernelNames, ARRAYSIZE(s_defaultKernelNames)); +#ifdef ENABLE_SCI32 +enum { + kKernelEntriesSci2 = 0x8b, + kKernelEntriesGk2Demo = 0xa0, + kKernelEntriesSci21 = 0x9d, + kKernelEntriesSci3 = 0xa1 +}; +#endif + +void Kernel::loadKernelNames(GameFeatures *features) { + _kernelNames.clear(); - // Some (later) SCI versions replaced CanBeHere by CantBeHere - // If vocab.999 exists, the kernel function is still named CanBeHere - if (_selectorCache.cantBeHere != -1) - _kernelNames[0x4d] = "CantBeHere"; + if (getSciVersion() <= SCI_VERSION_1_1) { + _kernelNames = Common::StringArray(s_defaultKernelNames, ARRAYSIZE(s_defaultKernelNames)); + + // Some (later) SCI versions replaced CanBeHere by CantBeHere + // If vocab.999 exists, the kernel function is still named CanBeHere + if (_selectorCache.cantBeHere != -1) + _kernelNames[0x4d] = "CantBeHere"; + } switch (getSciVersion()) { case SCI_VERSION_0_EARLY: @@ -817,66 +830,60 @@ void Kernel::setDefaultKernelNames(GameFeatures *features) { _kernelNames[0x7c] = "Message"; break; - default: - // Use default table for the other versions - break; - } -} - #ifdef ENABLE_SCI32 + case SCI_VERSION_2: + _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2); + break; -enum { - kKernelEntriesSci2 = 0x8b, - kKernelEntriesGk2Demo = 0xa0, - kKernelEntriesSci21 = 0x9d, - kKernelEntriesSci3 = 0xa1 -}; - -void Kernel::setKernelNamesSci2() { - _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2); -} + case SCI_VERSION_2_1: + if (features->detectSci21KernelType() == SCI_VERSION_2) { + // Some early SCI2.1 games use a modified SCI2 kernel table instead of + // the SCI2.1 kernel table. We detect which version to use based on + // how kDoSound is called from Sound::play(). + // Known games that use this: + // GK2 demo + // KQ7 1.4 + // PQ4 SWAT demo + // LSL6 + // PQ4CD + // QFG4CD + + // This is interesting because they all have the same interpreter + // version (2.100.002), yet they would not be compatible with other + // games of the same interpreter. + + _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo); + // OnMe is IsOnMe here, but they should be compatible + _kernelNames[0x23] = "Robot"; // Graph in SCI2 + _kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2 + } else { + // Normal SCI2.1 kernel table + _kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci21); + } + break; -void Kernel::setKernelNamesSci21(GameFeatures *features) { - // Some SCI games use a modified SCI2 kernel table instead of the - // SCI2.1 kernel table. We detect which version to use based on - // how kDoSound is called from Sound::play(). - // Known games that use this: - // GK2 demo - // KQ7 1.4 - // PQ4 SWAT demo - // LSL6 - // PQ4CD - // QFG4CD - - // This is interesting because they all have the same interpreter - // version (2.100.002), yet they would not be compatible with other - // games of the same interpreter. - - if (getSciVersion() != SCI_VERSION_3 && features->detectSci21KernelType() == SCI_VERSION_2) { - _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo); - // OnMe is IsOnMe here, but they should be compatible - _kernelNames[0x23] = "Robot"; // Graph in SCI2 - _kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2 - } else if (getSciVersion() != SCI_VERSION_3) { - _kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci21); - } else if (getSciVersion() == SCI_VERSION_3) { + case SCI_VERSION_3: _kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci3); - } -} -#endif - -void Kernel::loadKernelNames(GameFeatures *features) { - _kernelNames.clear(); + // In SCI3, some kernel functions have been removed, and others have been added + _kernelNames[0x18] = "Dummy"; // AddMagnify in SCI2.1 + _kernelNames[0x19] = "Dummy"; // DeleteMagnify in SCI2.1 + _kernelNames[0x30] = "Dummy"; // SetScroll in SCI2.1 + _kernelNames[0x39] = "Dummy"; // ShowMovie in SCI2.1 + _kernelNames[0x4c] = "Dummy"; // ScrollWindow in SCI2.1 + _kernelNames[0x56] = "Dummy"; // VibrateMouse in SCI2.1 (only used in QFG4 floppy) + _kernelNames[0x64] = "Dummy"; // AvoidPath in SCI2.1 + _kernelNames[0x66] = "Dummy"; // MergePoly in SCI2.1 + _kernelNames[0x8d] = "MessageBox"; // Dummy in SCI2.1 + _kernelNames[0x9b] = "Minimize"; // Dummy in SCI2.1 -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1) - setKernelNamesSci21(features); - else if (getSciVersion() == SCI_VERSION_2) - setKernelNamesSci2(); - else + break; #endif - setDefaultKernelNames(features); + + default: + // Use default table for the other versions + break; + } mapFunctions(); } @@ -885,15 +892,15 @@ Common::String Kernel::lookupText(reg_t address, int index) { char *seeker; Resource *textres; - if (address.segment) + if (address.getSegment()) return _segMan->getString(address); int textlen; int _index = index; - textres = _resMan->findResource(ResourceId(kResourceTypeText, address.offset), 0); + textres = _resMan->findResource(ResourceId(kResourceTypeText, address.getOffset()), 0); if (!textres) { - error("text.%03d not found", address.offset); + error("text.%03d not found", address.getOffset()); return NULL; /* Will probably segfault */ } @@ -907,7 +914,7 @@ Common::String Kernel::lookupText(reg_t address, int index) { if (textlen) return seeker; - error("Index %d out of bounds in text.%03d", _index, address.offset); + error("Index %d out of bounds in text.%03d", _index, address.getOffset()); return NULL; } diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index e549c1f8ae..f985a69ebc 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -224,23 +224,6 @@ public: private: /** - * Sets the default kernel function names, based on the SCI version used. - */ - void setDefaultKernelNames(GameFeatures *features); - -#ifdef ENABLE_SCI32 - /** - * Sets the default kernel function names to the SCI2 kernel functions. - */ - void setKernelNamesSci2(); - - /** - * Sets the default kernel function names to the SCI2.1 kernel functions. - */ - void setKernelNamesSci21(GameFeatures *features); -#endif - - /** * Loads the kernel selector names. */ void loadSelectorNames(); @@ -276,9 +259,6 @@ private: const Common::String _invalid; }; -/* Maximum length of a savegame name (including terminator character). */ -#define SCI_MAX_SAVENAME_LENGTH 0x24 - /******************** Kernel functions ********************/ reg_t kStrLen(EngineState *s, int argc, reg_t *argv); @@ -326,10 +306,6 @@ reg_t kTimesCot(EngineState *s, int argc, reg_t *argv); reg_t kCosDiv(EngineState *s, int argc, reg_t *argv); reg_t kSinDiv(EngineState *s, int argc, reg_t *argv); reg_t kValidPath(EngineState *s, int argc, reg_t *argv); -reg_t kFOpen(EngineState *s, int argc, reg_t *argv); -reg_t kFPuts(EngineState *s, int argc, reg_t *argv); -reg_t kFGets(EngineState *s, int argc, reg_t *argv); -reg_t kFClose(EngineState *s, int argc, reg_t *argv); reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv); reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv); reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv); @@ -436,6 +412,7 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv); reg_t kString(EngineState *s, int argc, reg_t *argv); reg_t kMulDiv(EngineState *s, int argc, reg_t *argv); reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv); // "Screen items" in SCI32 are views reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv); @@ -458,10 +435,15 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv); reg_t kInPolygon(EngineState *s, int argc, reg_t *argv); reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv); reg_t kEditText(EngineState *s, int argc, reg_t *argv); +reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv); +reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv); +reg_t kSetScroll(EngineState *s, int argc, reg_t *argv); +reg_t kPalCycle(EngineState *s, int argc, reg_t *argv); // SCI2.1 Kernel Functions reg_t kText(EngineState *s, int argc, reg_t *argv); reg_t kSave(EngineState *s, int argc, reg_t *argv); +reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); reg_t kRobot(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); @@ -473,12 +455,16 @@ reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv); reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv); reg_t kWinHelp(EngineState *s, int argc, reg_t *argv); reg_t kGetConfig(EngineState *s, int argc, reg_t *argv); +reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv); reg_t kCelInfo(EngineState *s, int argc, reg_t *argv); reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv); reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv); reg_t kFont(EngineState *s, int argc, reg_t *argv); reg_t kBitmap(EngineState *s, int argc, reg_t *argv); +reg_t kAddLine(EngineState *s, int argc, reg_t *argv); +reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv); +reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv); // SCI3 Kernel functions reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv); @@ -556,6 +542,7 @@ reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv); reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv); reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv); reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv); +reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv); #endif //@} diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 622511c906..b6b36c47e7 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -239,12 +239,27 @@ static const SciKernelMapSubEntry kFileIO_subops[] = { { SIG_SCI32, 15, MAP_CALL(FileIOReadWord), "i", NULL }, { SIG_SCI32, 16, MAP_CALL(FileIOWriteWord), "ii", NULL }, { SIG_SCI32, 17, MAP_CALL(FileIOCreateSaveSlot), "ir", NULL }, - { SIG_SCI32, 19, MAP_CALL(Stub), "r", NULL }, // for Torin / Torin demo + { SIG_SCI32, 18, MAP_EMPTY(FileIOChangeDirectory), "r", NULL }, // for SQ6, when changing the savegame directory in the save/load dialog + { SIG_SCI32, 19, MAP_CALL(FileIOIsValidDirectory), "r", NULL }, // for Torin / Torin demo #endif SCI_SUBOPENTRY_TERMINATOR }; #ifdef ENABLE_SCI32 + +static const SciKernelMapSubEntry kSave_subops[] = { + { SIG_SCI32, 0, MAP_CALL(SaveGame), "[r0]i[r0](r0)", NULL }, + { SIG_SCI32, 1, MAP_CALL(RestoreGame), "[r0]i[r0]", NULL }, + { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r*)", NULL }, + { SIG_SCI32, 3, MAP_CALL(CheckSaveGame), ".*", NULL }, + // Subop 4 hasn't been encountered yet + { SIG_SCI32, 5, MAP_CALL(GetSaveFiles), "rrr", NULL }, + { SIG_SCI32, 6, MAP_CALL(MakeSaveCatName), "rr", NULL }, + { SIG_SCI32, 7, MAP_CALL(MakeSaveFileName), "rri", NULL }, + { SIG_SCI32, 8, MAP_CALL(AutoSave), "[o0]", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kList_subops[] = { { SIG_SCI21, 0, MAP_CALL(NewList), "", NULL }, @@ -337,10 +352,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(EditControl), SIG_EVERYWHERE, "[o0][o0]", NULL, NULL }, { MAP_CALL(Empty), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(EmptyList), SIG_EVERYWHERE, "l", NULL, NULL }, - { MAP_CALL(FClose), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(FGets), SIG_EVERYWHERE, "rii", NULL, NULL }, - { MAP_CALL(FOpen), SIG_EVERYWHERE, "ri", NULL, NULL }, - { MAP_CALL(FPuts), SIG_EVERYWHERE, "ir", NULL, NULL }, + { "FClose", kFileIOClose, SIG_EVERYWHERE, "i", NULL, NULL }, + { "FGets", kFileIOReadString, SIG_EVERYWHERE, "rii", NULL, NULL }, + { "FOpen", kFileIOOpen, SIG_EVERYWHERE, "ri", NULL, NULL }, + { "FPuts", kFileIOWriteString, SIG_EVERYWHERE, "ir", NULL, NULL }, { MAP_CALL(FileIO), SIG_EVERYWHERE, "i(.*)", kFileIO_subops, NULL }, { MAP_CALL(FindKey), SIG_EVERYWHERE, "l.", NULL, kFindKey_workarounds }, { MAP_CALL(FirstNode), SIG_EVERYWHERE, "[l0]", NULL, NULL }, @@ -404,7 +419,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL }, { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(RemapColors), SIG_EVERYWHERE, "i(i)(i)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", NULL, NULL }, +#endif { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, { MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL }, @@ -500,29 +518,23 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(ObjectIntersect), SIG_EVERYWHERE, "oo", NULL, NULL }, { MAP_CALL(EditText), SIG_EVERYWHERE, "o", NULL, NULL }, - - // SCI2 unmapped functions - TODO! - - // SetScroll - called by script 64909, Styler::doit() - // PalCycle - called by Game::newRoom. Related to RemapColors. - // VibrateMouse - used in QFG4 + { MAP_CALL(MakeSaveCatName), SIG_EVERYWHERE, "rr", NULL, NULL }, + { MAP_CALL(MakeSaveFileName), SIG_EVERYWHERE, "rri", NULL, NULL }, + { MAP_CALL(SetScroll), SIG_EVERYWHERE, "oiiiii(i)", NULL, NULL }, + { MAP_CALL(PalCycle), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // SCI2 Empty functions - + // Debug function used to track resources { MAP_EMPTY(ResourceTrack), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - - // SCI2 functions that are used in the original save/load menus. Marked as dummy, so - // that the engine errors out on purpose. TODO: Implement once the original save/load - // menus are implemented. - - // Creates the name of the save catalogue/directory to save into. - // TODO: Implement once the original save/load menus are implemented. - { MAP_DUMMY(MakeSaveCatName), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - - // Creates the name of the save file to save into - // TODO: Implement once the original save/load menus are implemented. - { MAP_DUMMY(MakeSaveFileName), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + // Future TODO: This call is used in the floppy version of QFG4 to add + // vibration to exotic mice with force feedback, such as the Logitech + // Cyberman and Wingman mice. Since this is only used for very exotic + // hardware and we have no direct and cross-platform way of communicating + // with them via SDL, plus we would probably need to make changes to common + // code, this call is mapped to an empty function for now as it's a rare + // feature not worth the effort. + { MAP_EMPTY(VibrateMouse), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // Unused / debug SCI2 unused functions, always mapped to kDummy @@ -549,6 +561,11 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + // SetScroll is called by script 64909, Styler::doit(), but it doesn't seem to + // be used at all (plus, it was then changed to a dummy function in SCI3). + // Since this is most likely unused, and we got no test case, error out when + // it is called in order to find an actual call to it. + { MAP_DUMMY(SetScroll), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, @@ -557,18 +574,22 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(Save), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(GetConfig), SIG_EVERYWHERE, "ro", NULL, NULL }, + { MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL }, { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL }, { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "io(.*)", NULL, NULL }, { MAP_CALL(SetFontRes), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL }, + { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL }, + { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "[r0]o", NULL, NULL }, // SCI2.1 Empty Functions @@ -582,11 +603,6 @@ static SciKernelMapEntry s_kernelMap[] = { // the game window in Phantasmagoria 2. We ignore these settings completely. { MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE, "ii", NULL, NULL }, - // Used by the Windows version of Phantasmagoria 1 to get the video speed setting. This is called after - // kGetConfig and overrides the setting obtained by it. It is a dummy function in the DOS Version. We can - // just use GetConfig and mark this one as empty, like the DOS version does. - { MAP_EMPTY(GetSierraProfileInt), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - // Debug function called whenever the current room changes { MAP_EMPTY(NewRoom), SIG_EVERYWHERE, "(.*)", NULL, NULL }, @@ -618,14 +634,14 @@ static SciKernelMapEntry s_kernelMap[] = { // SCI2.1 unmapped functions - TODO! + // SetHotRectangles - used by Phantasmagoria 1, script 64981 (used in the chase scene) + // <lskovlun> The idea, if I understand correctly, is that the engine generates events + // of a special HotRect type continuously when the mouse is on that rectangle + // MovePlaneItems - used by SQ6 to scroll through the inventory via the up/down buttons - // AddLine - used by Torin's Passage to highlight the chapter buttons - // DeleteLine - used by Torin's Passage to delete the highlight from the chapter buttons - // UpdateLine - used by LSL6 // SetPalStyleRange - 2 integer parameters, start and end. All styles from start-end // (inclusive) are set to 0 // MorphOn - used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270) - // SetHotRectangles - used by Phantasmagoria 1 // SCI3 Kernel Functions { MAP_CALL(PlayDuck), SIG_EVERYWHERE, "(.*)", NULL, NULL }, @@ -820,7 +836,7 @@ static const char *const sci2_default_knames[] = { /*0x20*/ "AddMagnify", /*0x21*/ "DeleteMagnify", /*0x22*/ "IsHiRes", - /*0x23*/ "Graph", + /*0x23*/ "Graph", // Robot in early SCI2.1 games with a SCI2 kernel table /*0x24*/ "InvertRect", // only in SCI2, not used in any SCI2 game /*0x25*/ "TextSize", /*0x26*/ "Message", @@ -831,7 +847,7 @@ static const char *const sci2_default_knames[] = { /*0x2b*/ "EditText", /*0x2c*/ "InputText", // unused function /*0x2d*/ "CreateTextBitmap", - /*0x2e*/ "DisposeTextBitmap", + /*0x2e*/ "DisposeTextBitmap", // Priority in early SCI2.1 games with a SCI2 kernel table /*0x2f*/ "GetEvent", /*0x30*/ "GlobalToLocal", /*0x31*/ "LocalToGlobal", @@ -1091,7 +1107,7 @@ static const char *const sci21_default_knames[] = { /*0x8a*/ "LoadChunk", /*0x8b*/ "SetPalStyleRange", /*0x8c*/ "AddPicAt", - /*0x8d*/ "MessageBox", // SCI3, was Dummy in SCI2.1 + /*0x8d*/ "Dummy", // MessageBox in SCI3 /*0x8e*/ "NewRoom", // debug function /*0x8f*/ "Dummy", /*0x90*/ "Priority", @@ -1105,7 +1121,7 @@ static const char *const sci21_default_knames[] = { /*0x98*/ "GetWindowsOption", // Windows only /*0x99*/ "WinDLL", // Windows only /*0x9a*/ "Dummy", - /*0x9b*/ "Minimize", // SCI3, was Dummy in SCI2.1 + /*0x9b*/ "Dummy", // Minimize in SCI3 /*0x9c*/ "DeletePic", // == SCI3 only =============== /*0x9d*/ "Dummy", @@ -1119,57 +1135,73 @@ static const char *const sci21_default_knames[] = { // Base set of opcode formats. They're copied and adjusted slightly in // script_adjust_opcode_format depending on SCI version. static const opcode_format g_base_opcode_formats[128][4] = { - /*00*/ + // 00 - 03 / bnot, add, sub, mul {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*04*/ + // 04 - 07 / div, mod, shr, shl {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*08*/ + // 08 - 0B / xor, and, or, neg {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*0C*/ + // 0C - 0F / not, eq, ne, gt {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*10*/ + // 10 - 13 / ge, lt, le, ugt {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*14*/ + // 14 - 17 / uge, ult, ule, bt {Script_None}, {Script_None}, {Script_None}, {Script_SRelative}, - /*18*/ + // 18 - 1B / bnt, jmp, ldi, push {Script_SRelative}, {Script_SRelative}, {Script_SVariable}, {Script_None}, - /*1C*/ + // 1C - 1F / pushi, toss, dup, link {Script_SVariable}, {Script_None}, {Script_None}, {Script_Variable}, - /*20*/ + // 20 - 23 / call, callk, callb, calle {Script_SRelative, Script_Byte}, {Script_Variable, Script_Byte}, {Script_Variable, Script_Byte}, {Script_Variable, Script_SVariable, Script_Byte}, - /*24 (24=ret)*/ + // 24 - 27 / ret, send, dummy, dummy {Script_End}, {Script_Byte}, {Script_Invalid}, {Script_Invalid}, - /*28*/ + // 28 - 2B / class, dummy, self, super {Script_Variable}, {Script_Invalid}, {Script_Byte}, {Script_Variable, Script_Byte}, - /*2C*/ + // 2C - 2F / rest, lea, selfID, dummy {Script_SVariable}, {Script_SVariable, Script_Variable}, {Script_None}, {Script_Invalid}, - /*30*/ + // 30 - 33 / pprev, pToa, aTop, pTos {Script_None}, {Script_Property}, {Script_Property}, {Script_Property}, - /*34*/ + // 34 - 37 / sTop, ipToa, dpToa, ipTos {Script_Property}, {Script_Property}, {Script_Property}, {Script_Property}, - /*38*/ + // 38 - 3B / dpTos, lofsa, lofss, push0 {Script_Property}, {Script_SRelative}, {Script_SRelative}, {Script_None}, - /*3C*/ + // 3C - 3F / push1, push2, pushSelf, line {Script_None}, {Script_None}, {Script_None}, {Script_Word}, - /*40-4F*/ + // ------------------------------------------------------------------------ + // 40 - 43 / lag, lal, lat, lap {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 44 - 47 / lsg, lsl, lst, lsp {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 48 - 4B / lagi, lali, lati, lapi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 4C - 4F / lsgi, lsli, lsti, lspi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, - /*50-5F*/ + // ------------------------------------------------------------------------ + // 50 - 53 / sag, sal, sat, sap {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 54 - 57 / ssg, ssl, sst, ssp {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 58 - 5B / sagi, sali, sati, sapi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 5C - 5F / ssgi, ssli, ssti, sspi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, - /*60-6F*/ + // ------------------------------------------------------------------------ + // 60 - 63 / plusag, plusal, plusat, plusap {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 64 - 67 / plussg, plussl, plusst, plussp {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 68 - 6B / plusagi, plusali, plusati, plusapi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 6C - 6F / plussgi, plussli, plussti, plusspi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, - /*70-7F*/ + // ------------------------------------------------------------------------ + // 70 - 73 / minusag, minusal, minusat, minusap {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 74 - 77 / minussg, minussl, minusst, minussp {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 78 - 7B / minusagi, minusali, minusati, minusapi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param}, + // 7C - 7F / minussgi, minussli, minussti, minusspi {Script_Global}, {Script_Local}, {Script_Temp}, {Script_Param} }; #undef END diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index df3b3efd57..34477cc23b 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -157,7 +157,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { s->r_acc = NULL_REG; } - if ((s->r_acc.offset) && (g_sci->_debugState.stopOnEvent)) { + if ((s->r_acc.getOffset()) && (g_sci->_debugState.stopOnEvent)) { g_sci->_debugState.stopOnEvent = false; // A SCI event occurred, and we have been asked to stop, so open the debug console @@ -248,7 +248,7 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; - if (obj.segment) { + if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); @@ -267,7 +267,7 @@ reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) { reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; - if (obj.segment) { + if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 312497720a..e977f15c0c 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -33,6 +33,7 @@ #include "gui/saveload.h" #include "sci/sci.h" +#include "sci/engine/file.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/savegame.h" @@ -40,209 +41,11 @@ namespace Sci { -struct SavegameDesc { - int16 id; - int virtualId; // straight numbered, according to id but w/o gaps - int date; - int time; - int version; - char name[SCI_MAX_SAVENAME_LENGTH]; -}; - -/* - * Note on how file I/O is implemented: In ScummVM, one can not create/write - * arbitrary data files, simply because many of our target platforms do not - * support this. The only files one can create are savestates. But SCI has an - * opcode to create and write to seemingly 'arbitrary' files. This is mainly - * used in LSL3 for LARRY3.DRV (which is a game data file, not a driver, used - * for persisting the results of the "age quiz" across restarts) and in LSL5 - * for MEMORY.DRV (which is again a game data file and contains the game's - * password, XOR encrypted). - * To implement that opcode, we combine the SaveFileManager with regular file - * code, similarly to how the SCUMM HE engine does it. - * - * To handle opening a file called "foobar", what we do is this: First, we - * create an 'augmented file name', by prepending the game target and a dash, - * so if we running game target sq1sci, the name becomes "sq1sci-foobar". - * Next, we check if such a file is known to the SaveFileManager. If so, we - * we use that for reading/writing, delete it, whatever. - * - * If no such file is present but we were only asked to *read* the file, - * we fallback to looking for a regular file called "foobar", and open that - * for reading only. - */ - - - -FileHandle::FileHandle() : _in(0), _out(0) { -} - -FileHandle::~FileHandle() { - close(); -} - -void FileHandle::close() { - delete _in; - delete _out; - _in = 0; - _out = 0; - _name.clear(); -} - -bool FileHandle::isOpen() const { - return _in || _out; -} - - - -enum { - _K_FILE_MODE_OPEN_OR_CREATE = 0, - _K_FILE_MODE_OPEN_OR_FAIL = 1, - _K_FILE_MODE_CREATE = 2 -}; - - - -reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename) { - Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH); - Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName; - Common::SeekableReadStream *inFile = 0; - Common::WriteStream *outFile = 0; - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - - if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { - // Try to open file, abort if not possible - inFile = saveFileMan->openForLoading(wrappedName); - // If no matching savestate exists: fall back to reading from a regular - // file - if (!inFile) - inFile = SearchMan.createReadStreamForMember(englishName); - - if (!inFile) - debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str()); - } else if (mode == _K_FILE_MODE_CREATE) { - // Create the file, destroying any content it might have had - outFile = saveFileMan->openForSaving(wrappedName); - if (!outFile) - debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); - } else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) { - // Try to open file, create it if it doesn't exist - outFile = saveFileMan->openForSaving(wrappedName); - if (!outFile) - debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); - // QfG1 opens the character export file with _K_FILE_MODE_CREATE first, - // closes it immediately and opens it again with this here. Perhaps - // other games use this for read access as well. I guess changing this - // whole code into using virtual files and writing them after close - // would be more appropriate. - } else { - error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str()); - } - - if (!inFile && !outFile) { // Failed - debugC(kDebugLevelFile, " -> file_open() failed"); - return SIGNAL_REG; - } - - // Find a free file handle - uint handle = 1; // Ignore _fileHandles[0] - while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen()) - handle++; - - if (handle == s->_fileHandles.size()) { - // Hit size limit => Allocate more space - s->_fileHandles.resize(s->_fileHandles.size() + 1); - } - - s->_fileHandles[handle]._in = inFile; - s->_fileHandles[handle]._out = outFile; - s->_fileHandles[handle]._name = englishName; - - debugC(kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle); - return make_reg(0, handle); -} - -reg_t kFOpen(EngineState *s, int argc, reg_t *argv) { - Common::String name = s->_segMan->getString(argv[0]); - int mode = argv[1].toUint16(); - - debugC(kDebugLevelFile, "kFOpen(%s,0x%x)", name.c_str(), mode); - return file_open(s, name, mode, true); -} - -static FileHandle *getFileFromHandle(EngineState *s, uint handle) { - if (handle == 0) { - error("Attempt to use file handle 0"); - return 0; - } - - if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) { - warning("Attempt to use invalid/unused file handle %d", handle); - return 0; - } - - return &s->_fileHandles[handle]; -} - -reg_t kFClose(EngineState *s, int argc, reg_t *argv) { - debugC(kDebugLevelFile, "kFClose(%d)", argv[0].toUint16()); - if (argv[0] != SIGNAL_REG) { - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); - if (f) - f->close(); - } - return s->r_acc; -} - -reg_t kFPuts(EngineState *s, int argc, reg_t *argv) { - int handle = argv[0].toUint16(); - Common::String data = s->_segMan->getString(argv[1]); - - FileHandle *f = getFileFromHandle(s, handle); - if (f) - f->_out->write(data.c_str(), data.size()); - - return s->r_acc; -} - -static int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) { - FileHandle *f = getFileFromHandle(s, handle); - if (!f) - return 0; - - if (!f->_in) { - error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str()); - return 0; - } - int readBytes = 0; - if (maxsize > 1) { - memset(dest, 0, maxsize); - f->_in->readLine(dest, maxsize); - readBytes = strlen(dest); // FIXME: sierra sci returned byte count and didn't react on NUL characters - // The returned string must not have an ending LF - if (readBytes > 0) { - if (dest[readBytes - 1] == 0x0A) - dest[readBytes - 1] = 0; - } - } else { - *dest = 0; - } - - debugC(kDebugLevelFile, " -> FGets'ed \"%s\"", dest); - return readBytes; -} - -reg_t kFGets(EngineState *s, int argc, reg_t *argv) { - int maxsize = argv[1].toUint16(); - char *buf = new char[maxsize]; - int handle = argv[2].toUint16(); - - debugC(kDebugLevelFile, "kFGets(%d, %d)", handle, maxsize); - int readBytes = fgets_wrapper(s, buf, maxsize, handle); - s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); - delete[] buf; - return readBytes ? argv[0] : NULL_REG; -} +extern reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename); +extern FileHandle *getFileFromHandle(EngineState *s, uint handle); +extern int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle); +extern void listSavegames(Common::Array<SavegameDesc> &saves); +extern int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId); /** * Writes the cwd to the supplied address and returns the address in acc. @@ -258,9 +61,6 @@ reg_t kGetCWD(EngineState *s, int argc, reg_t *argv) { return argv[0]; } -static void listSavegames(Common::Array<SavegameDesc> &saves); -static int findSavegame(Common::Array<SavegameDesc> &saves, int16 saveId); - enum { K_DEVICE_INFO_GET_DEVICE = 0, K_DEVICE_INFO_GET_CURRENT_DEVICE = 1, @@ -331,7 +131,7 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) { s->_segMan->strcpy(argv[1], "__throwaway"); debug(3, "K_DEVICE_INFO_GET_SAVEFILE_NAME(%s,%d) -> %s", game_prefix.c_str(), virtualId, "__throwaway"); if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) - error("kDeviceInfo(deleteSave): invalid savegame-id specified"); + error("kDeviceInfo(deleteSave): invalid savegame ID specified"); uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; Common::Array<SavegameDesc> saves; listSavegames(saves); @@ -352,18 +152,6 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv) { -#ifdef ENABLE_SCI32 - // SCI32 uses a parameter here. It is used to modify a string, stored in a - // global variable, so that game scripts store the save directory. We - // don't really set a save game directory, thus not setting the string to - // anything is the correct thing to do here. - //if (argc > 0) - // warning("kGetSaveDir called with %d parameter(s): %04x:%04x", argc, PRINT_REG(argv[0])); -#endif - return s->_segMan->getSaveDirPtr(); -} - reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { if (argc > 1) { // SCI1.1/SCI32 @@ -394,354 +182,43 @@ reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { return make_reg(0, 1); } -static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) { - if (l.date != r.date) - return (l.date > r.date); - return (l.time > r.time); -} - -// Create a sorted array containing all found savedgames -static void listSavegames(Common::Array<SavegameDesc> &saves) { - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - - // Load all saves - Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern()); - - for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) { - Common::String filename = *iter; - Common::SeekableReadStream *in; - if ((in = saveFileMan->openForLoading(filename))) { - SavegameMetadata meta; - if (!get_savegame_metadata(in, &meta) || meta.name.empty()) { - // invalid - delete in; - continue; - } - delete in; - - SavegameDesc desc; - desc.id = strtol(filename.end() - 3, NULL, 10); - desc.date = meta.saveDate; - // We need to fix date in here, because we save DDMMYYYY instead of - // YYYYMMDD, so sorting wouldn't work - desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24); - desc.time = meta.saveTime; - desc.version = meta.version; - - if (meta.name.lastChar() == '\n') - meta.name.deleteLastChar(); - - Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH); - - debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id); - - saves.push_back(desc); - } - } - - // Sort the list by creation date of the saves - Common::sort(saves.begin(), saves.end(), _savegame_sort_byDate); -} - -// Find a savedgame according to virtualId and return the position within our array -static int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) { - for (uint saveNr = 0; saveNr < saves.size(); saveNr++) { - if (saves[saveNr].id == savegameId) - return saveNr; - } - return -1; -} - -// The scripts get IDs ranging from 100->199, because the scripts require us to assign unique ids THAT EVEN STAY BETWEEN -// SAVES and the scripts also use "saves-count + 1" to create a new savedgame slot. -// SCI1.1 actually recycles ids, in that case we will currently get "0". -// This behavior is required especially for LSL6. In this game, it's possible to quick save. The scripts will use -// the last-used id for that feature. If we don't assign sticky ids, the feature will overwrite different saves all the -// time. And sadly we can't just use the actual filename ids directly, because of the creation method for new slots. - -bool Console::cmdListSaves(int argc, const char **argv) { - Common::Array<SavegameDesc> saves; - listSavegames(saves); - - for (uint i = 0; i < saves.size(); i++) { - Common::String filename = g_sci->getSavegameName(saves[i].id); - DebugPrintf("%s: '%s'\n", filename.c_str(), saves[i].name); - } - - return true; -} - -reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { - Common::String game_id = s->_segMan->getString(argv[0]); - uint16 virtualId = argv[1].toUint16(); - - debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), virtualId); - - Common::Array<SavegameDesc> saves; - listSavegames(saves); - - // we allow 0 (happens in QfG2 when trying to restore from an empty saved game list) and return false in that case - if (virtualId == 0) - return NULL_REG; - - // Find saved-game - if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) - error("kCheckSaveGame: called with invalid savegameId"); - uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; - int savegameNr = findSavegame(saves, savegameId); - if (savegameNr == -1) - return NULL_REG; - - // Check for compatible savegame version - int ver = saves[savegameNr].version; - if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION) - return NULL_REG; - - // Otherwise we assume the savegame is OK - return TRUE_REG; -} - -reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { - Common::String game_id = s->_segMan->getString(argv[0]); - - debug(3, "kGetSaveFiles(%s)", game_id.c_str()); - - // Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really - // mean new slot instead of overwriting the old one - s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START; - - Common::Array<SavegameDesc> saves; - listSavegames(saves); - uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR); - - reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves); - - if (!slot) { - warning("kGetSaveFiles: %04X:%04X invalid or too small to hold slot data", PRINT_REG(argv[2])); - totalSaves = 0; - } - - const uint bufSize = (totalSaves * SCI_MAX_SAVENAME_LENGTH) + 1; - char *saveNames = new char[bufSize]; - char *saveNamePtr = saveNames; - - for (uint i = 0; i < totalSaves; i++) { - *slot++ = make_reg(0, saves[i].id + SAVEGAMEID_OFFICIALRANGE_START); // Store the virtual savegame-id ffs. see above - strcpy(saveNamePtr, saves[i].name); - saveNamePtr += SCI_MAX_SAVENAME_LENGTH; - } - - *saveNamePtr = 0; // Terminate list - - s->_segMan->memcpy(argv[1], (byte *)saveNames, bufSize); - delete[] saveNames; - - return make_reg(0, totalSaves); -} - -reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { - Common::String game_id; - int16 virtualId = argv[1].toSint16(); - int16 savegameId = -1; - Common::String game_description; - Common::String version; - - if (argc > 3) - version = s->_segMan->getString(argv[3]); - - // We check here, we don't want to delete a users save in case we are within a kernel function - if (s->executionStackBase) { - warning("kSaveGame - won't save from within kernel function"); - return NULL_REG; - } - - if (argv[0].isNull()) { - // Direct call, from a patched Game::save - if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull())) - error("kSaveGame: assumed patched call isn't accurate"); - - // we are supposed to show a dialog for the user and let him choose where to save - g_sci->_soundCmd->pauseAll(true); // pause music - const EnginePlugin *plugin = NULL; - EngineMan.findGame(g_sci->getGameIdStr(), &plugin); - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save")); - dialog->setSaveMode(true); - savegameId = dialog->runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName()); - game_description = dialog->getResultString(); - if (game_description.empty()) { - // create our own description for the saved game, the user didnt enter it - #if defined(USE_SAVEGAME_TIMESTAMP) - TimeDate curTime; - g_system->getTimeAndDate(curTime); - curTime.tm_year += 1900; // fixup year - curTime.tm_mon++; // fixup month - game_description = Common::String::format("%04d.%02d.%02d / %02d:%02d:%02d", curTime.tm_year, curTime.tm_mon, curTime.tm_mday, curTime.tm_hour, curTime.tm_min, curTime.tm_sec); - #else - game_description = Common::String::format("Save %d", savegameId + 1); - #endif - } - delete dialog; - g_sci->_soundCmd->pauseAll(false); // unpause music ( we can't have it paused during save) - if (savegameId < 0) - return NULL_REG; - - } else { - // Real call from script - game_id = s->_segMan->getString(argv[0]); - if (argv[2].isNull()) - error("kSaveGame: called with description being NULL"); - game_description = s->_segMan->getString(argv[2]); - - debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), virtualId, game_description.c_str(), version.c_str()); - - Common::Array<SavegameDesc> saves; - listSavegames(saves); - - if ((virtualId >= SAVEGAMEID_OFFICIALRANGE_START) && (virtualId <= SAVEGAMEID_OFFICIALRANGE_END)) { - // savegameId is an actual Id, so search for it just to make sure - savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; - if (findSavegame(saves, savegameId) == -1) - return NULL_REG; - } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) { - // virtualId is low, we assume that scripts expect us to create new slot - if (virtualId == s->_lastSaveVirtualId) { - // if last virtual id is the same as this one, we assume that caller wants to overwrite last save - savegameId = s->_lastSaveNewId; - } else { - uint savegameNr; - // savegameId is in lower range, scripts expect us to create a new slot - for (savegameId = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; savegameId++) { - for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) { - if (savegameId == saves[savegameNr].id) - break; - } - if (savegameNr == saves.size()) - break; - } - if (savegameId == SAVEGAMEID_OFFICIALRANGE_START) - error("kSavegame: no more savegame slots available"); - } - } else { - error("kSaveGame: invalid savegameId used"); - } - - // Save in case caller wants to overwrite last newly created save - s->_lastSaveVirtualId = virtualId; - s->_lastSaveNewId = savegameId; - } - - s->r_acc = NULL_REG; - - Common::String filename = g_sci->getSavegameName(savegameId); - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - Common::OutSaveFile *out; - - out = saveFileMan->openForSaving(filename); - if (!out) { - warning("Error opening savegame \"%s\" for writing", filename.c_str()); - } else { - if (!gamestate_save(s, out, game_description, version)) { - warning("Saving the game failed"); - } else { - s->r_acc = TRUE_REG; // save successful - } +reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { + Common::String path = s->_segMan->getString(argv[0]); - out->finalize(); - if (out->err()) { - warning("Writing the savegame failed"); - s->r_acc = NULL_REG; // write failure - } - delete out; - } + debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.getOffset()); - return s->r_acc; + // Always return true + return make_reg(0, 1); } -reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { - Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : ""; - int16 savegameId = argv[1].toSint16(); - bool pausedMusic = false; - - debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); +#ifdef ENABLE_SCI32 - if (argv[0].isNull()) { - // Direct call, either from launcher or from a patched Game::restore - if (savegameId == -1) { - // we are supposed to show a dialog for the user and let him choose a saved game - g_sci->_soundCmd->pauseAll(true); // pause music - const EnginePlugin *plugin = NULL; - EngineMan.findGame(g_sci->getGameIdStr(), &plugin); - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore")); - dialog->setSaveMode(false); - savegameId = dialog->runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName()); - delete dialog; - if (savegameId < 0) { - g_sci->_soundCmd->pauseAll(false); // unpause music - return s->r_acc; - } - pausedMusic = true; - } - // don't adjust ID of the saved game, it's already correct - } else { - if (argv[2].isNull()) - error("kRestoreGame: called with parameter 2 being NULL"); - // Real call from script, we need to adjust ID - if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) { - warning("Savegame ID %d is not allowed", savegameId); +reg_t kCD(EngineState *s, int argc, reg_t *argv) { + // TODO: Stub + switch (argv[0].toUint16()) { + case 0: + if (argc == 1) { + // Check if a disc is in the drive return TRUE_REG; - } - savegameId -= SAVEGAMEID_OFFICIALRANGE_START; - } - - s->r_acc = NULL_REG; // signals success - - Common::Array<SavegameDesc> saves; - listSavegames(saves); - if (findSavegame(saves, savegameId) == -1) { - s->r_acc = TRUE_REG; - warning("Savegame ID %d not found", savegameId); - } else { - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - Common::String filename = g_sci->getSavegameName(savegameId); - Common::SeekableReadStream *in; - - in = saveFileMan->openForLoading(filename); - if (in) { - // found a savegame file - - gamestate_restore(s, in); - delete in; - - if (g_sci->getGameId() == GID_MOTHERGOOSE256) { - // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for - // saving a previously restored game. - // We set the current savedgame-id directly and remove the script - // code concerning this via script patch. - s->variables[VAR_GLOBAL][0xB3].offset = SAVEGAMEID_OFFICIALRANGE_START + savegameId; - } } else { - s->r_acc = TRUE_REG; - warning("Savegame #%d not found", savegameId); + // Check if the specified disc is in the drive + // and return the current disc number. We just + // return the requested disc number. + return argv[1]; } + case 1: + // Return the current CD number + return make_reg(0, 1); + default: + warning("CD(%d)", argv[0].toUint16()); } - if (!s->r_acc.isNull()) { - // no success? - if (pausedMusic) - g_sci->_soundCmd->pauseAll(false); // unpause music - } - - return s->r_acc; + return NULL_REG; } -reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { - Common::String path = s->_segMan->getString(argv[0]); - - debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.offset); +#endif - // Always return true - return make_reg(0, 1); -} +// ---- FileIO operations ----------------------------------------------------- reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { if (!s) @@ -779,6 +256,73 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { } debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); +#ifdef ENABLE_SCI32 + if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { + if (s->_virtualIndexFile) { + return make_reg(0, VIRTUALFILE_HANDLE); + } else { + Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH); + Common::String wrappedName = g_sci->wrapFilename(englishName); + if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) { + s->_virtualIndexFile = new VirtualIndexFile(wrappedName); + return make_reg(0, VIRTUALFILE_HANDLE); + } + } + } + + // Shivers is trying to store savegame descriptions and current spots in + // separate .SG files, which are hardcoded in the scripts. + // Essentially, there is a normal save file, created by the executable + // and an extra hardcoded save file, created by the game scripts, probably + // because they didn't want to modify the save/load code to add the extra + // information. + // Each slot in the book then has two strings, the save description and a + // description of the current spot that the player is at. Currently, the + // spot strings are always empty (probably related to the unimplemented + // kString subop 14, which gets called right before this call). + // For now, we don't allow the creation of these files, which means that + // all the spot descriptions next to each slot description will be empty + // (they are empty anyway). Until a viable solution is found to handle these + // extra files and until the spot description strings are initialized + // correctly, we resort to virtual files in order to make the load screen + // useable. Without this code it is unusable, as the extra information is + // always saved to 0.SG for some reason, but on restore the correct file is + // used. Perhaps the virtual ID is not taken into account when saving. + // + // Future TODO: maintain spot descriptions and show them too, ideally without + // having to return to this logic of extra hardcoded files. + if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) { + if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { + // Game scripts are trying to create a file with the save + // description, stop them here + debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); + return SIGNAL_REG; + } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { + // Create a virtual file containing the save game description + // and slot number, as the game scripts expect. + int slotNumber; + sscanf(name.c_str(), "%d.SG", &slotNumber); + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START); + + if (!s->_virtualIndexFile) { + // Make the virtual file buffer big enough to avoid having it grow dynamically. + // 50 bytes should be more than enough. + s->_virtualIndexFile = new VirtualIndexFile(50); + } + + s->_virtualIndexFile->seek(0, SEEK_SET); + s->_virtualIndexFile->write(saves[savegameNr].name, strlen(saves[savegameNr].name)); + s->_virtualIndexFile->write("\0", 1); + s->_virtualIndexFile->write("\0", 1); // Spot description (empty) + s->_virtualIndexFile->seek(0, SEEK_SET); + return make_reg(0, VIRTUALFILE_HANDLE); + } + } +#endif + // QFG import rooms get a virtual filelisting instead of an actual one if (g_sci->inQfGImportRoom()) { // We need to find out what the user actually selected, "savedHeroes" is @@ -794,44 +338,82 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelFile, "kFileIO(close): %d", argv[0].toUint16()); - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (argv[0] == SIGNAL_REG) + return s->r_acc; + + uint16 handle = argv[0].toUint16(); + +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) { + s->_virtualIndexFile->close(); + return SIGNAL_REG; + } +#endif + + FileHandle *f = getFileFromHandle(s, handle); if (f) { f->close(); + if (getSciVersion() <= SCI_VERSION_0_LATE) + return s->r_acc; // SCI0 semantics: no value returned return SIGNAL_REG; } + + if (getSciVersion() <= SCI_VERSION_0_LATE) + return s->r_acc; // SCI0 semantics: no value returned return NULL_REG; } reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) { - int handle = argv[0].toUint16(); - int size = argv[2].toUint16(); + uint16 handle = argv[0].toUint16(); + uint16 size = argv[2].toUint16(); int bytesRead = 0; char *buf = new char[size]; debugC(kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size); - FileHandle *f = getFileFromHandle(s, handle); - if (f) { - bytesRead = f->_in->read(buf, size); - s->_segMan->memcpy(argv[1], (const byte*)buf, size); +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) { + bytesRead = s->_virtualIndexFile->read(buf, size); + } else { +#endif + FileHandle *f = getFileFromHandle(s, handle); + if (f) + bytesRead = f->_in->read(buf, size); +#ifdef ENABLE_SCI32 } +#endif + + // TODO: What happens if less bytes are read than what has + // been requested? (i.e. if bytesRead is non-zero, but still + // less than size) + if (bytesRead > 0) + s->_segMan->memcpy(argv[1], (const byte*)buf, size); delete[] buf; return make_reg(0, bytesRead); } reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { - int handle = argv[0].toUint16(); - int size = argv[2].toUint16(); + uint16 handle = argv[0].toUint16(); + uint16 size = argv[2].toUint16(); char *buf = new char[size]; bool success = false; s->_segMan->memcpy((byte *)buf, argv[1], size); debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size); - FileHandle *f = getFileFromHandle(s, handle); - if (f) { - f->_out->write(buf, size); +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) { + s->_virtualIndexFile->write(buf, size); success = true; + } else { +#endif + FileHandle *f = getFileFromHandle(s, handle); + if (f) { + f->_out->write(buf, size); + success = true; + } +#ifdef ENABLE_SCI32 } +#endif delete[] buf; if (success) @@ -863,6 +445,20 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { int savedir_nr = saves[slotNum].id; name = g_sci->getSavegameName(savedir_nr); result = saveFileMan->removeSavefile(name); + } else if (getSciVersion() >= SCI_VERSION_2) { + // The file name may be already wrapped, so check both cases + result = saveFileMan->removeSavefile(name); + if (!result) { + const Common::String wrappedName = g_sci->wrapFilename(name); + result = saveFileMan->removeSavefile(wrappedName); + } + +#ifdef ENABLE_SCI32 + if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { + delete s->_virtualIndexFile; + s->_virtualIndexFile = 0; + } +#endif } else { const Common::String wrappedName = g_sci->wrapFilename(name); result = saveFileMan->removeSavefile(wrappedName); @@ -875,15 +471,22 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { } reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) { - int size = argv[1].toUint16(); - char *buf = new char[size]; - int handle = argv[2].toUint16(); - debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, size); + uint16 maxsize = argv[1].toUint16(); + char *buf = new char[maxsize]; + uint16 handle = argv[2].toUint16(); + debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize); + uint32 bytesRead; + +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) + bytesRead = s->_virtualIndexFile->readLine(buf, maxsize); + else +#endif + bytesRead = fgets_wrapper(s, buf, maxsize, handle); - int readBytes = fgets_wrapper(s, buf, size, handle); - s->_segMan->memcpy(argv[0], (const byte*)buf, size); + s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); delete[] buf; - return readBytes ? argv[0] : NULL_REG; + return bytesRead ? argv[0] : NULL_REG; } reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { @@ -891,126 +494,55 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { Common::String str = s->_segMan->getString(argv[1]); debugC(kDebugLevelFile, "kFileIO(writeString): %d", handle); +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) { + s->_virtualIndexFile->write(str.c_str(), str.size()); + return NULL_REG; + } +#endif + FileHandle *f = getFileFromHandle(s, handle); if (f) { f->_out->write(str.c_str(), str.size()); + if (getSciVersion() <= SCI_VERSION_0_LATE) + return s->r_acc; // SCI0 semantics: no value returned return NULL_REG; } + if (getSciVersion() <= SCI_VERSION_0_LATE) + return s->r_acc; // SCI0 semantics: no value returned return make_reg(0, 6); // DOS - invalid handle } reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) { - int handle = argv[0].toUint16(); - int offset = argv[1].toUint16(); - int whence = argv[2].toUint16(); + uint16 handle = argv[0].toUint16(); + uint16 offset = ABS<int16>(argv[1].toSint16()); // can be negative + uint16 whence = argv[2].toUint16(); debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); - FileHandle *f = getFileFromHandle(s, handle); - - if (f) - s->r_acc = make_reg(0, f->_in->seek(offset, whence)); - - return SIGNAL_REG; -} - -void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) { - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask); - if (!foundFiles.empty()) { - _files.push_back(title); - _virtualFiles.push_back(""); - Common::StringArray::iterator it; - Common::StringArray::iterator it_end = foundFiles.end(); - - for (it = foundFiles.begin(); it != it_end; it++) { - Common::String regularFilename = *it; - Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1); - - Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename); - int32 testfileSize = testfile->size(); - delete testfile; - if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game. - continue; // and we dont want to have those in the list - // We need to remove the prefix for display purposes - _files.push_back(wrappedFilename); - // but remember the actual name as well - _virtualFiles.push_back(regularFilename); - } - } -} +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) + return make_reg(0, s->_virtualIndexFile->seek(offset, whence)); +#endif -Common::String DirSeeker::getVirtualFilename(uint fileNumber) { - if (fileNumber >= _virtualFiles.size()) - error("invalid virtual filename access"); - return _virtualFiles[fileNumber]; -} + FileHandle *f = getFileFromHandle(s, handle); -reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) { - // Verify that we are given a valid buffer - if (!buffer.segment) { - error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str()); - return NULL_REG; - } - _outbuffer = buffer; - _files.clear(); - _virtualFiles.clear(); - - int QfGImport = g_sci->inQfGImportRoom(); - if (QfGImport) { - _files.clear(); - addAsVirtualFiles("-QfG1-", "qfg1-*"); - addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*"); - if (QfGImport > 2) - addAsVirtualFiles("-QfG2-", "qfg2-*"); - if (QfGImport > 3) - addAsVirtualFiles("-QfG3-", "qfg3-*"); - - if (QfGImport == 3) { - // QfG3 sorts the filelisting itself, we can't let that happen otherwise our - // virtual list would go out-of-sync - reg_t savedHeros = segMan->findObjectByName("savedHeros"); - if (!savedHeros.isNull()) - writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0); + if (f && f->_in) { + // Backward seeking isn't supported in zip file streams, thus adapt the + // parameters accordingly if games ask for such a seek mode. A known + // case where this is requested is the save file manager in Phantasmagoria + if (whence == SEEK_END) { + whence = SEEK_SET; + offset = f->_in->size() - offset; } - } else { - // Prefix the mask - const Common::String wrappedMask = g_sci->wrapFilename(mask); - - // Obtain a list of all files matching the given mask - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - _files = saveFileMan->listSavefiles(wrappedMask); + return make_reg(0, f->_in->seek(offset, whence)); + } else if (f && f->_out) { + error("kFileIOSeek: Unsupported seek operation on a writeable stream (offset: %d, whence: %d)", offset, whence); } - // Reset the list iterator and write the first match to the output buffer, - // if any. - _iter = _files.begin(); - return nextFile(segMan); -} - -reg_t DirSeeker::nextFile(SegManager *segMan) { - if (_iter == _files.end()) { - return NULL_REG; - } - - Common::String string; - - if (_virtualFiles.empty()) { - // Strip the prefix, if we don't got a virtual filelisting - const Common::String wrappedString = *_iter; - string = g_sci->unwrapFilename(wrappedString); - } else { - string = *_iter; - } - if (string.size() > 12) - string = Common::String(string.c_str(), 12); - segMan->strcpy(_outbuffer, string.c_str()); - - // Return the result and advance the list iterator :) - ++_iter; - return _outbuffer; + return SIGNAL_REG; } reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv) { @@ -1033,6 +565,14 @@ reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); +#ifdef ENABLE_SCI32 + // Cache the file existence result for the Phantasmagoria + // save index file, as the game scripts keep checking for + // its existence. + if (name == PHANTASMAGORIA_SAVEGAME_INDEX && s->_virtualIndexFile) + return TRUE_REG; +#endif + bool exists = false; // Check for regular file @@ -1084,7 +624,7 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { // Special case for KQ6 Mac: The game checks for two video files to see // if they exist before it plays them. Since we support multiple naming // schemes for resource fork files, we also need to support that here in - // case someone has a "HalfDome.bin" file, etc. + // case someone has a "HalfDome.bin" file, etc. if (!exists && g_sci->getGameId() == GID_KQ6 && g_sci->getPlatform() == Common::kPlatformMacintosh && (name == "HalfDome" || name == "Kq6Movie")) exists = Common::MacResManager::exists(name); @@ -1155,51 +695,328 @@ reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) { return TRUE_REG; // slot creation was successful } -reg_t kCD(EngineState *s, int argc, reg_t *argv) { - // TODO: Stub - switch (argv[0].toUint16()) { - case 0: - // Return whether the contents of disc argv[1] is available. - return TRUE_REG; - default: - warning("CD(%d)", argv[0].toUint16()); +reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv) { + // Used in Torin's Passage and LSL7 to determine if the directory passed as + // a parameter (usually the save directory) is valid. We always return true + // here. + return TRUE_REG; +} + +#endif + +// ---- Save operations ------------------------------------------------------- + +#ifdef ENABLE_SCI32 + +reg_t kSave(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +#endif + +reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { + Common::String game_id; + int16 virtualId = argv[1].toSint16(); + int16 savegameId = -1; + Common::String game_description; + Common::String version; + + if (argc > 3) + version = s->_segMan->getString(argv[3]); + + // We check here, we don't want to delete a users save in case we are within a kernel function + if (s->executionStackBase) { + warning("kSaveGame - won't save from within kernel function"); + return NULL_REG; } - return NULL_REG; + if (argv[0].isNull()) { + // Direct call, from a patched Game::save + if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull())) + error("kSaveGame: assumed patched call isn't accurate"); + + // we are supposed to show a dialog for the user and let him choose where to save + g_sci->_soundCmd->pauseAll(true); // pause music + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); + savegameId = dialog->runModalWithCurrentTarget(); + game_description = dialog->getResultString(); + if (game_description.empty()) { + // create our own description for the saved game, the user didnt enter it + game_description = dialog->createDefaultSaveDescription(savegameId); + } + delete dialog; + g_sci->_soundCmd->pauseAll(false); // unpause music ( we can't have it paused during save) + if (savegameId < 0) + return NULL_REG; + + } else { + // Real call from script + game_id = s->_segMan->getString(argv[0]); + if (argv[2].isNull()) + error("kSaveGame: called with description being NULL"); + game_description = s->_segMan->getString(argv[2]); + + debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), virtualId, game_description.c_str(), version.c_str()); + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + + if ((virtualId >= SAVEGAMEID_OFFICIALRANGE_START) && (virtualId <= SAVEGAMEID_OFFICIALRANGE_END)) { + // savegameId is an actual Id, so search for it just to make sure + savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; + if (findSavegame(saves, savegameId) == -1) + return NULL_REG; + } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) { + // virtualId is low, we assume that scripts expect us to create new slot + if (virtualId == s->_lastSaveVirtualId) { + // if last virtual id is the same as this one, we assume that caller wants to overwrite last save + savegameId = s->_lastSaveNewId; + } else { + uint savegameNr; + // savegameId is in lower range, scripts expect us to create a new slot + for (savegameId = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; savegameId++) { + for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) { + if (savegameId == saves[savegameNr].id) + break; + } + if (savegameNr == saves.size()) + break; + } + if (savegameId == SAVEGAMEID_OFFICIALRANGE_START) + error("kSavegame: no more savegame slots available"); + } + } else { + error("kSaveGame: invalid savegameId used"); + } + + // Save in case caller wants to overwrite last newly created save + s->_lastSaveVirtualId = virtualId; + s->_lastSaveNewId = savegameId; + } + + s->r_acc = NULL_REG; + + Common::String filename = g_sci->getSavegameName(savegameId); + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + Common::OutSaveFile *out; + + out = saveFileMan->openForSaving(filename); + if (!out) { + warning("Error opening savegame \"%s\" for writing", filename.c_str()); + } else { + if (!gamestate_save(s, out, game_description, version)) { + warning("Saving the game failed"); + } else { + s->r_acc = TRUE_REG; // save successful + } + + out->finalize(); + if (out->err()) { + warning("Writing the savegame failed"); + s->r_acc = NULL_REG; // write failure + } + delete out; + } + + return s->r_acc; } -reg_t kSave(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: - return kSaveGame(s, argc - 1,argv + 1); - case 1: - return kRestoreGame(s, argc - 1,argv + 1); - case 2: - return kGetSaveDir(s, argc - 1, argv + 1); - case 3: - return kCheckSaveGame(s, argc - 1, argv + 1); - case 5: - return kGetSaveFiles(s, argc - 1, argv + 1); - case 6: - // This is used in Shivers to delete saved games, however it - // always passes the same file name (SHIVER), so it doesn't - // actually delete anything... - // TODO: Check why this happens - // argv[1] is a string (most likely the save game directory) - return kFileIOUnlink(s, argc - 2, argv + 2); - case 8: - // TODO - // This is a timer callback, with 1 parameter: the timer object - // (e.g. "timers"). - // It's used for auto-saving (i.e. save every X minutes, by checking - // the elapsed time from the timer object) - - // This function has to return something other than 0 to proceed - return s->r_acc; - default: - kStub(s, argc, argv); +reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { + Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : ""; + int16 savegameId = argv[1].toSint16(); + bool pausedMusic = false; + + debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); + + if (argv[0].isNull()) { + // Direct call, either from launcher or from a patched Game::restore + if (savegameId == -1) { + // we are supposed to show a dialog for the user and let him choose a saved game + g_sci->_soundCmd->pauseAll(true); // pause music + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + savegameId = dialog->runModalWithCurrentTarget(); + delete dialog; + if (savegameId < 0) { + g_sci->_soundCmd->pauseAll(false); // unpause music + return s->r_acc; + } + pausedMusic = true; + } + // don't adjust ID of the saved game, it's already correct + } else { + if (argv[2].isNull()) + error("kRestoreGame: called with parameter 2 being NULL"); + // Real call from script, we need to adjust ID + if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) { + warning("Savegame ID %d is not allowed", savegameId); + return TRUE_REG; + } + savegameId -= SAVEGAMEID_OFFICIALRANGE_START; + } + + s->r_acc = NULL_REG; // signals success + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + if (findSavegame(saves, savegameId) == -1) { + s->r_acc = TRUE_REG; + warning("Savegame ID %d not found", savegameId); + } else { + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + Common::String filename = g_sci->getSavegameName(savegameId); + Common::SeekableReadStream *in; + + in = saveFileMan->openForLoading(filename); + if (in) { + // found a savegame file + + gamestate_restore(s, in); + delete in; + + if (g_sci->getGameId() == GID_MOTHERGOOSE256) { + // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for + // saving a previously restored game. + // We set the current savedgame-id directly and remove the script + // code concerning this via script patch. + s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); + } + } else { + s->r_acc = TRUE_REG; + warning("Savegame #%d not found", savegameId); + } + } + + if (!s->r_acc.isNull()) { + // no success? + if (pausedMusic) + g_sci->_soundCmd->pauseAll(false); // unpause music + } + + return s->r_acc; +} + +reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv) { +#ifdef ENABLE_SCI32 + // SCI32 uses a parameter here. It is used to modify a string, stored in a + // global variable, so that game scripts store the save directory. We + // don't really set a save game directory, thus not setting the string to + // anything is the correct thing to do here. + //if (argc > 0) + // warning("kGetSaveDir called with %d parameter(s): %04x:%04x", argc, PRINT_REG(argv[0])); +#endif + return s->_segMan->getSaveDirPtr(); +} + +reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { + Common::String game_id = s->_segMan->getString(argv[0]); + uint16 virtualId = argv[1].toUint16(); + + debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), virtualId); + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + + // we allow 0 (happens in QfG2 when trying to restore from an empty saved game list) and return false in that case + if (virtualId == 0) return NULL_REG; + + // Find saved-game + if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) + error("kCheckSaveGame: called with invalid savegameId"); + uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; + int savegameNr = findSavegame(saves, savegameId); + if (savegameNr == -1) + return NULL_REG; + + // Check for compatible savegame version + int ver = saves[savegameNr].version; + if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION) + return NULL_REG; + + // Otherwise we assume the savegame is OK + return TRUE_REG; +} + +reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { + Common::String game_id = s->_segMan->getString(argv[0]); + + debug(3, "kGetSaveFiles(%s)", game_id.c_str()); + + // Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really + // mean new slot instead of overwriting the old one + s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START; + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR); + + reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves); + + if (!slot) { + warning("kGetSaveFiles: %04X:%04X invalid or too small to hold slot data", PRINT_REG(argv[2])); + totalSaves = 0; } + + const uint bufSize = (totalSaves * SCI_MAX_SAVENAME_LENGTH) + 1; + char *saveNames = new char[bufSize]; + char *saveNamePtr = saveNames; + + for (uint i = 0; i < totalSaves; i++) { + *slot++ = make_reg(0, saves[i].id + SAVEGAMEID_OFFICIALRANGE_START); // Store the virtual savegame ID ffs. see above + strcpy(saveNamePtr, saves[i].name); + saveNamePtr += SCI_MAX_SAVENAME_LENGTH; + } + + *saveNamePtr = 0; // Terminate list + + s->_segMan->memcpy(argv[1], (byte *)saveNames, bufSize); + delete[] saveNames; + + return make_reg(0, totalSaves); +} + +#ifdef ENABLE_SCI32 + +reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) { + // Normally, this creates the name of the save catalogue/directory to save into. + // First parameter is the string to save the result into. Second is a string + // with game parameters. We don't have a use for this at all, as we have our own + // savegame directory management, thus we always return an empty string. + return argv[0]; +} + +reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) { + // Creates a savegame name from a slot number. Used when deleting saved games. + // Param 0: the output buffer (same as in kMakeSaveCatName) + // Param 1: a string with game parameters, ignored + // Param 2: the selected slot + + SciString *resultString = s->_segMan->lookupString(argv[0]); + uint16 virtualId = argv[2].toUint16(); + if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) + error("kMakeSaveFileName: invalid savegame ID specified"); + uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START; + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + + Common::String filename = g_sci->getSavegameName(saveSlot); + resultString->fromString(filename); + + return argv[0]; +} + +reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) { + // TODO + // This is a timer callback, with 1 parameter: the timer object + // (e.g. "timers"). + // It's used for auto-saving (i.e. save every X minutes, by checking + // the elapsed time from the timer object) + + // This function has to return something other than 0 to proceed + return TRUE_REG; } #endif diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index caae562d67..da377319c0 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -25,12 +25,10 @@ #include "engines/util.h" #include "graphics/cursorman.h" #include "graphics/surface.h" -#include "graphics/palette.h" // temporary, for the fadeIn()/fadeOut() functions below #include "gui/message.h" #include "sci/sci.h" -#include "sci/debug.h" // for g_debug_sleeptime_factor #include "sci/event.h" #include "sci/resource.h" #include "sci/engine/features.h" @@ -50,10 +48,7 @@ #include "sci/graphics/text16.h" #include "sci/graphics/view.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/controls32.h" -#include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class #include "sci/graphics/text32.h" -#include "sci/graphics/frameout.h" #endif namespace Sci { @@ -342,7 +337,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { Common::String sep_str; const char *sep = NULL; - if ((argc > 4) && (argv[4].segment)) { + if ((argc > 4) && (argv[4].getSegment())) { sep_str = s->_segMan->getString(argv[4]); sep = sep_str.c_str(); } @@ -650,6 +645,20 @@ reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { if (paletteChanged) g_sci->_gfxPalette->kernelAnimateSet(); + // WORKAROUND: The game scripts in SQ4 floppy count the number of elapsed + // cycles in the intro from the number of successive kAnimate calls during + // the palette cycling effect, while showing the SQ4 logo. This worked in + // older computers because each animate call took awhile to complete. + // Normally, such scripts are handled automatically by our speed throttler, + // however in this case there are no calls to kGameIsRestarting (where the + // speed throttler gets called) between the different palette animation calls. + // Thus, we add a small delay between each animate call to make the whole + // palette animation effect slower and visible, and not have the logo screen + // get skipped because the scripts don't wait between animation steps. Fixes + // bug #3537232. + if (g_sci->getGameId() == GID_SQ4 && !g_sci->isCD() && s->currentRoomNumber() == 1) + g_sci->sleep(10); + return s->r_acc; } @@ -794,7 +803,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { Common::Rect rect; TextAlignment alignment; int16 mode, maxChars, cursorPos, upperPos, listCount, i; - int16 upperOffset, cursorOffset; + uint16 upperOffset, cursorOffset; GuiResourceId viewId; int16 loopNo; int16 celNo; @@ -876,7 +885,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { listCount = 0; listSeeker = textReference; while (s->_segMan->strlen(listSeeker) > 0) { listCount++; - listSeeker.offset += maxChars; + listSeeker.incOffset(maxChars); } // TODO: This is rather convoluted... It would be a lot cleaner @@ -890,11 +899,11 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { for (i = 0; i < listCount; i++) { listStrings[i] = s->_segMan->getString(listSeeker); listEntries[i] = listStrings[i].c_str(); - if (listSeeker.offset == upperOffset) + if (listSeeker.getOffset() == upperOffset) upperPos = i; - if (listSeeker.offset == cursorOffset) + if (listSeeker.getOffset() == cursorOffset) cursorPos = i; - listSeeker.offset += maxChars; + listSeeker.incOffset(maxChars); } } @@ -941,8 +950,9 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) { } } if (objName == "savedHeros") { - // Import of QfG character files dialog is shown - // display additional popup information before letting user use it + // Import of QfG character files dialog is shown. + // Display additional popup information before letting user use it. + // For the SCI32 version of this, check kernelAddPlane(). reg_t changeDirButton = s->_segMan->findObjectByName("changeDirItem"); if (!changeDirButton.isNull()) { // check if checkDirButton is still enabled, in that case we are called the first time during that room @@ -955,6 +965,8 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) { "for Quest for Glory 2. Example: 'qfg2-thief.sav'."); } } + + // For the SCI32 version of this, check kListAt(). s->_chosenQfGImportItem = readSelectorValue(s->_segMan, controlObject, SELECTOR(mark)); } @@ -1109,7 +1121,7 @@ reg_t kNewWindow(EngineState *s, int argc, reg_t *argv) { rect2 = Common::Rect (argv[5].toSint16(), argv[4].toSint16(), argv[7].toSint16(), argv[6].toSint16()); Common::String title; - if (argv[4 + argextra].segment) { + if (argv[4 + argextra].getSegment()) { title = s->_segMan->getString(argv[4 + argextra]); title = g_sci->strSplit(title.c_str(), NULL); } @@ -1148,7 +1160,7 @@ reg_t kDisplay(EngineState *s, int argc, reg_t *argv) { Common::String text; - if (textp.segment) { + if (textp.getSegment()) { argc--; argv++; text = s->_segMan->getString(textp); } else { @@ -1209,688 +1221,33 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +// Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4 reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { uint16 operation = argv[0].toUint16(); switch (operation) { - case 0: { // Set remapping to base. 0 turns remapping off. - int16 base = (argc >= 2) ? argv[1].toSint16() : 0; - if (base != 0) // 0 is the default behavior when changing rooms in GK1, thus silencing the warning - warning("kRemapColors: Set remapping to base %d", base); - } - break; - case 1: { // unknown - // The demo of QFG4 calls this with 1+3 parameters, thus there are differences here - //int16 unk1 = argv[1].toSint16(); - //int16 unk2 = argv[2].toSint16(); - //int16 unk3 = argv[3].toSint16(); - //uint16 unk4 = argv[4].toUint16(); - //uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0; - kStub(s, argc, argv); - } - break; - case 2: { // remap by percent - // This adjusts the alpha value of a specific color, and it operates on - // an RGBA palette. Since we're operating on an RGB palette, we just - // modify the color intensity instead - // TODO: From what I understand, palette remapping should be placed - // separately, so that it can be reset by case 0 above. Thus, we - // should adjust the functionality of the Palette class accordingly. - int16 color = argv[1].toSint16(); - if (color >= 10) - color -= 10; - uint16 percent = argv[2].toUint16(); // 0 - 100 - if (argc >= 4) - warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); - g_sci->_gfxPalette->kernelSetIntensity(color, 255, percent, false); - } - break; - case 3: { // remap to gray - // NOTE: This adjusts the alpha value of a specific color, and it operates on - // an RGBA palette - int16 color = argv[1].toSint16(); // this is subtracted from a maximum color value, and can be offset by 10 - int16 percent = argv[2].toSint16(); // 0 - 100 - uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0; - warning("kRemapColors: RemapToGray color %d by %d percent (unk3 = %d)", color, percent, unk3); - } - break; - case 4: { // unknown - //int16 unk1 = argv[1].toSint16(); - //uint16 unk2 = argv[2].toUint16(); - //uint16 unk3 = argv[3].toUint16(); - //uint16 unk4 = (argc >= 5) ? argv[4].toUint16() : 0; - kStub(s, argc, argv); - } - break; - case 5: { // increment color - //int16 unk1 = argv[1].toSint16(); - //uint16 unk2 = argv[2].toUint16(); - kStub(s, argc, argv); - } - break; - default: - break; - } - - return s->r_acc; -} - -#ifdef ENABLE_SCI32 - -reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { - // Returns 0 if the screen width or height is less than 640 or 400, - // respectively. - if (g_system->getWidth() < 640 || g_system->getHeight() < 400) - return make_reg(0, 0); - - return make_reg(0, 1); -} - -// SCI32 variant, can't work like sci16 variants -reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) { - // TODO -// reg_t curObject = argv[0]; -// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; - - return NULL_REG; -} - -reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { - if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL) - g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); - else - g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); - return s->r_acc; -} - -reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); - return s->r_acc; -} - -reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]); - return s->r_acc; -} - -reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelAddPlane(argv[0]); - return s->r_acc; -} - -reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelDeletePlane(argv[0]); - return s->r_acc; -} - -reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]); - return s->r_acc; -} - -reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) { - reg_t planeObj = argv[0]; - GuiResourceId pictureId = argv[1].toUint16(); - int16 pictureX = argv[2].toSint16(); - int16 pictureY = argv[3].toSint16(); - - g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY); - return s->r_acc; -} - -reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, g_sci->_gfxFrameout->kernelGetHighPlanePri()); -} - -reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelFrameout(); - return NULL_REG; -} - -reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { - Common::Rect objRect1 = g_sci->_gfxCompare->getNSRect(argv[0]); - Common::Rect objRect2 = g_sci->_gfxCompare->getNSRect(argv[1]); - return make_reg(0, objRect1.intersects(objRect2)); -} - -// Tests if the coordinate is on the passed object -reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { - uint16 x = argv[0].toUint16(); - uint16 y = argv[1].toUint16(); - reg_t targetObject = argv[2]; - uint16 illegalBits = argv[3].offset; - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(targetObject, true); - - // we assume that x, y are local coordinates - - bool contained = nsRect.contains(x, y); - if (contained && illegalBits) { - // If illegalbits are set, we check the color of the pixel that got clicked on - // for now, we return false if the pixel is transparent - // although illegalBits may get differently set, don't know yet how this really works out - uint16 viewId = readSelectorValue(s->_segMan, targetObject, SELECTOR(view)); - int16 loopNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(loop)); - int16 celNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(cel)); - if (g_sci->_gfxCompare->kernelIsItSkip(viewId, loopNo, celNo, Common::Point(x - nsRect.left, y - nsRect.top))) - contained = false; - } - return make_reg(0, contained); -} - -reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: { - if (argc != 4) { - warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[3]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debugC(kDebugLevelStrings, "kCreateTextBitmap case 0 (%04x:%04x, %04x:%04x, %04x:%04x)", - PRINT_REG(argv[1]), PRINT_REG(argv[2]), PRINT_REG(argv[3])); - debugC(kDebugLevelStrings, "%s", text.c_str()); - uint16 maxWidth = argv[1].toUint16(); // nsRight - nsLeft + 1 - uint16 maxHeight = argv[2].toUint16(); // nsBottom - nsTop + 1 - return g_sci->_gfxText32->createTextBitmap(object, maxWidth, maxHeight); - } - case 1: { - if (argc != 2) { - warning("kCreateTextBitmap(1): expected 2 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[1]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debugC(kDebugLevelStrings, "kCreateTextBitmap case 1 (%04x:%04x)", PRINT_REG(argv[1])); - debugC(kDebugLevelStrings, "%s", text.c_str()); - return g_sci->_gfxText32->createTextBitmap(object); - } - default: - warning("CreateTextBitmap(%d)", argv[0].toUint16()); - return NULL_REG; - } -} - -reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxText32->disposeTextBitmap(argv[0]); - return s->r_acc; -} - -reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv) { - uint16 windowsOption = argv[0].toUint16(); - switch (windowsOption) { - case 0: - // Title bar on/off in Phantasmagoria, we return 0 (off) - return NULL_REG; - default: - warning("GetWindowsOption: Unknown option %d", windowsOption); - return NULL_REG; - } -} - -reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 1: - // Load a help file - // Maybe in the future we can implement this, but for now this message should suffice - showScummVMDialog("Please use an external viewer to open the game's help file: " + s->_segMan->getString(argv[1])); - break; - case 2: - // Looks like some init function - break; - default: - warning("Unknown kWinHelp subop %d", argv[0].toUint16()); - } - - return s->r_acc; -} - -// Taken from the SCI16 GfxTransitions class -static void fadeOut() { - byte oldPalette[3 * 256], workPalette[3 * 256]; - int16 stepNr, colorNr; - // Sierra did not fade in/out color 255 for sci1.1, but they used it in - // several pictures (e.g. qfg3 demo/intro), so the fading looked weird - int16 tillColorNr = getSciVersion() >= SCI_VERSION_1_1 ? 255 : 254; - - g_system->getPaletteManager()->grabPalette(oldPalette, 0, 256); - - for (stepNr = 100; stepNr >= 0; stepNr -= 10) { - for (colorNr = 1; colorNr <= tillColorNr; colorNr++) { - if (g_sci->_gfxPalette->colorIsFromMacClut(colorNr)) { - workPalette[colorNr * 3 + 0] = oldPalette[colorNr * 3]; - workPalette[colorNr * 3 + 1] = oldPalette[colorNr * 3 + 1]; - workPalette[colorNr * 3 + 2] = oldPalette[colorNr * 3 + 2]; - } else { - workPalette[colorNr * 3 + 0] = oldPalette[colorNr * 3] * stepNr / 100; - workPalette[colorNr * 3 + 1] = oldPalette[colorNr * 3 + 1] * stepNr / 100; - workPalette[colorNr * 3 + 2] = oldPalette[colorNr * 3 + 2] * stepNr / 100; - } - } - g_system->getPaletteManager()->setPalette(workPalette + 3, 1, tillColorNr); - g_sci->getEngineState()->wait(2); - } -} - -// Taken from the SCI16 GfxTransitions class -static void fadeIn() { - int16 stepNr; - // Sierra did not fade in/out color 255 for sci1.1, but they used it in - // several pictures (e.g. qfg3 demo/intro), so the fading looked weird - int16 tillColorNr = getSciVersion() >= SCI_VERSION_1_1 ? 255 : 254; - - for (stepNr = 0; stepNr <= 100; stepNr += 10) { - g_sci->_gfxPalette->kernelSetIntensity(1, tillColorNr + 1, stepNr, true); - g_sci->getEngineState()->wait(2); - } -} - -/** - * Used for scene transitions, replacing (but reusing parts of) the old - * transition code. - */ -reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - // Can be called with 7 or 8 parameters - // The style defines which transition to perform. Related to the transition - // tables inside graphics/transitions.cpp - uint16 showStyle = argv[0].toUint16(); // 0 - 15 - reg_t planeObj = argv[1]; // the affected plane - uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts - uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff - int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out - uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts - uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out - int16 divisions; - - // If the game has the pFadeArray selector, another parameter is used here, - // before the optional last parameter - bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0; - if (hasFadeArray) { - // argv[7] - divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?) - } else { - divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?) - } - - if (showStyle > 15) { - warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj)); - return s->r_acc; - } - - // TODO: Proper implementation. This is a very basic version. I'm not even - // sure if the rest of the styles will work with this mechanism. - - // Check if the passed parameters are the ones we expect - if (showStyle == 13 || showStyle == 14) { // fade out / fade in - if (seconds != 1) - warning("kSetShowStyle(fade): seconds isn't 1, it's %d", seconds); - if (backColor != 0 && backColor != 0xFFFF) - warning("kSetShowStyle(fade): backColor isn't 0 or 0xFFFF, it's %d", backColor); - if (priority != 200) - warning("kSetShowStyle(fade): priority isn't 200, it's %d", priority); - if (animate != 0) - warning("kSetShowStyle(fade): animate isn't 0, it's %d", animate); - if (refFrame != 0) - warning("kSetShowStyle(fade): refFrame isn't 0, it's %d", refFrame); - if (divisions >= 0 && divisions != 20) - warning("kSetShowStyle(fade): divisions isn't 20, it's %d", divisions); - } - - // TODO: Check if the plane is in the list of planes to draw - - switch (showStyle) { - //case 0: // no transition, perhaps? (like in the previous SCI versions) - case 13: // fade out - // TODO: Temporary implementation, which ignores all additional parameters - fadeOut(); - break; - case 14: // fade in - // TODO: Temporary implementation, which ignores all additional parameters - g_sci->_gfxFrameout->kernelFrameout(); // draw new scene before fading in - fadeIn(); - break; - default: - // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now - kStub(s, argc, argv); - break; - } - - return s->r_acc; -} - -reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { - // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board - // are occupied by pieces already - - switch (argv[0].toUint16()) { // subops 0 - 4 - // 0 - return the view - // 1 - return the loop - // 2, 3 - nop - case 4: { - GuiResourceId viewId = argv[1].toSint16(); - int16 loopNo = argv[2].toSint16(); - int16 celNo = argv[3].toSint16(); - int16 x = argv[4].toUint16(); - int16 y = argv[5].toUint16(); - byte color = g_sci->_gfxCache->kernelViewGetColorAtCoordinate(viewId, loopNo, celNo, x, y); - return make_reg(0, color); + case 0: { // remap by percent + uint16 percent = argv[1].toUint16(); + g_sci->_gfxPalette->resetRemapping(); + g_sci->_gfxPalette->setRemappingPercent(254, percent); } - default: { - kStub(s, argc, argv); - return s->r_acc; - } - } -} - -reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { - // Used by Phantasmagoria 1 and SQ6. In SQ6, it is used for the messages - // shown in the scroll window at the bottom of the screen. - - // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now - kStub(s, argc, argv); - - switch (argv[0].toUint16()) { - case 0: // Init - // 2 parameters - // argv[1] points to the scroll object (e.g. textScroller in SQ6) - // argv[2] is an integer (e.g. 0x32) - break; - case 1: // Show message - // 5 or 6 parameters - // Seems to be called with 5 parameters when the narrator speaks, and - // with 6 when Roger speaks - // argv[1] unknown (usually 0) - // argv[2] the text to show - // argv[3] a small integer (e.g. 0x32) - // argv[4] a small integer (e.g. 0x54) - // argv[5] optional, unknown (usually 0) - warning("kScrollWindow: '%s'", s->_segMan->getString(argv[2]).c_str()); - break; - case 2: // Clear - // 2 parameters - // TODO - break; - case 3: // Page up - // 2 parameters - // TODO - break; - case 4: // Page down - // 2 parameters - // TODO - break; - case 5: // Up arrow - // 2 parameters - // TODO - break; - case 6: // Down arrow - // 2 parameters - // TODO - break; - case 7: // Home - // 2 parameters - // TODO - break; - case 8: // End - // 2 parameters - // TODO - break; - case 9: // Resize - // 3 parameters - // TODO - break; - case 10: // Where - // 3 parameters - // TODO - break; - case 11: // Go - // 4 parameters - // TODO - break; - case 12: // Insert - // 7 parameters - // TODO break; - case 13: // Delete - // 3 parameters - // TODO - break; - case 14: // Modify - // 7 or 8 parameters - // TODO - break; - case 15: // Hide - // 2 parameters - // TODO - break; - case 16: // Show - // 2 parameters - // TODO - break; - case 17: // Destroy - // 2 parameters - // TODO - break; - case 18: // Text - // 2 parameters - // TODO - break; - case 19: // Reconstruct - // 3 parameters - // TODO - break; - default: - error("kScrollWindow: unknown subop %d", argv[0].toUint16()); - break; - } - - return s->r_acc; -} - -reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { - // TODO: This defines the resolution that the fonts are supposed to be displayed - // in. Currently, this is only used for showing high-res fonts in GK1 Mac, but - // should be extended to handle other font resolutions such as those - - int xResolution = argv[0].toUint16(); - //int yResolution = argv[1].toUint16(); - - g_sci->_gfxScreen->setFontIsUpscaled(xResolution == 640 && - g_sci->_gfxScreen->getUpscaledHires() != GFX_SCREEN_UPSCALED_DISABLED); - - return s->r_acc; -} - -reg_t kFont(EngineState *s, int argc, reg_t *argv) { - // Handle font settings for SCI2.1 - - switch (argv[0].toUint16()) { - case 1: - // Set font resolution - return kSetFontRes(s, argc - 1, argv + 1); - default: - warning("kFont: unknown subop %d", argv[0].toUint16()); - } - - return s->r_acc; -} - -// TODO: Eventually, all of the kBitmap operations should be put -// in a separate class - -#define BITMAP_HEADER_SIZE 46 - -reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { - // Used for bitmap operations in SCI2.1 and SCI3. - // This is the SCI2.1 version, the functionality seems to have changed in SCI3. - - switch (argv[0].toUint16()) { - case 0: // init bitmap surface - { - // 6 params, called e.g. from TextView::init() in Torin's Passage, - // script 64890 and TransView::init() in script 64884 - uint16 width = argv[1].toUint16(); - uint16 height = argv[2].toUint16(); - //uint16 skip = argv[3].toUint16(); - uint16 back = argv[4].toUint16(); // usually equals skip - //uint16 width2 = (argc >= 6) ? argv[5].toUint16() : 0; - //uint16 height2 = (argc >= 7) ? argv[6].toUint16() : 0; - //uint16 transparentFlag = (argc >= 8) ? argv[7].toUint16() : 0; - - // TODO: skip, width2, height2, transparentFlag - // (used for transparent bitmaps) - int entrySize = width * height + BITMAP_HEADER_SIZE; - reg_t memoryId = s->_segMan->allocateHunkEntry("Bitmap()", entrySize); - byte *memoryPtr = s->_segMan->getHunkPointer(memoryId); - memset(memoryPtr, 0, BITMAP_HEADER_SIZE); // zero out the bitmap header - memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height); - // Save totalWidth, totalHeight - // TODO: Save the whole bitmap header, like SSCI does - WRITE_LE_UINT16(memoryPtr, width); - WRITE_LE_UINT16(memoryPtr + 2, height); - return memoryId; + case 1: { // remap by range + uint16 from = argv[1].toUint16(); + uint16 to = argv[2].toUint16(); + uint16 base = argv[3].toUint16(); + g_sci->_gfxPalette->resetRemapping(); + g_sci->_gfxPalette->setRemappingRange(254, from, to, base); } break; - case 1: // dispose text bitmap surface - return kDisposeTextBitmap(s, argc - 1, argv + 1); - case 2: // dispose bitmap surface, with extra param - // 2 params, called e.g. from MenuItem::dispose in Torin's Passage, - // script 64893 - warning("kBitmap(2), unk1 %d, bitmap ptr %04x:%04x", argv[1].toUint16(), PRINT_REG(argv[2])); - break; - case 3: // tiled surface - { - // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage, - // script 64869 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - // The tiled view seems to always have 2 loops. - // These loops need to have 1 cel in loop 0 and 8 cels in loop 1. - uint16 viewNum = argv[2].toUint16(); // vTiles selector - uint16 loop = argv[3].toUint16(); - uint16 cel = argv[4].toUint16(); - uint16 x = argv[5].toUint16(); - uint16 y = argv[6].toUint16(); - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - GfxView *view = g_sci->_gfxCache->getView(viewNum); - uint16 tileWidth = view->getWidth(loop, cel); - uint16 tileHeight = view->getHeight(loop, cel); - const byte *tileBitmap = view->getBitmap(loop, cel); - uint16 width = MIN<uint16>(totalWidth - x, tileWidth); - uint16 height = MIN<uint16>(totalHeight - y, tileHeight); - - for (uint16 curY = 0; curY < height; curY++) { - for (uint16 curX = 0; curX < width; curX++) { - bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX]; - } - } - - } - break; - case 4: // add text to bitmap - { - // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage, - // script 64894 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - Common::String text = s->_segMan->getString(argv[2]); - uint16 textX = argv[3].toUint16(); - uint16 textY = argv[4].toUint16(); - //reg_t unk5 = argv[5]; - //reg_t unk6 = argv[6]; - //reg_t unk7 = argv[7]; // skip? - //reg_t unk8 = argv[8]; // back? - //reg_t unk9 = argv[9]; - uint16 fontId = argv[10].toUint16(); - //uint16 mode = argv[11].toUint16(); - uint16 dimmed = argv[12].toUint16(); - //warning("kBitmap(4): bitmap ptr %04x:%04x, font %d, mode %d, dimmed %d - text: \"%s\"", - // PRINT_REG(bitmapPtr), font, mode, dimmed, text.c_str()); - uint16 foreColor = 255; // TODO - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - GfxFont *font = g_sci->_gfxCache->getFont(fontId); - - int16 charCount = 0; - uint16 curX = textX, curY = textY; - const char *txt = text.c_str(); - - while (*txt) { - charCount = g_sci->_gfxText32->GetLongest(txt, totalWidth, font); - if (charCount == 0) - break; - - for (int i = 0; i < charCount; i++) { - unsigned char curChar = txt[i]; - font->drawToBuffer(curChar, curY, curX, foreColor, dimmed, bitmap, totalWidth, totalHeight); - curX += font->getCharWidth(curChar); - } - - curX = textX; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces - } - - } - break; - case 5: // fill with color - { - // 6 params, called e.g. from TextView::init() and TextView::draw() - // in Torin's Passage, script 64890 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - uint16 x = argv[2].toUint16(); - uint16 y = argv[3].toUint16(); - uint16 fillWidth = argv[4].toUint16(); // width - 1 - uint16 fillHeight = argv[5].toUint16(); // height - 1 - uint16 back = argv[6].toUint16(); - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - uint16 width = MIN<uint16>(totalWidth - x, fillWidth); - uint16 height = MIN<uint16>(totalHeight - y, fillHeight); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - for (uint16 curY = 0; curY < height; curY++) { - for (uint16 curX = 0; curX < width; curX++) { - bitmap[(curY + y) * totalWidth + (curX + x)] = back; - } - } - - } + case 2: // turn remapping off (unused) + error("Unused subop kRemapColors(2) has been called"); break; default: - kStub(s, argc, argv); break; } return s->r_acc; } -// Used for edit boxes in save/load dialogs. It's a rewritten version of kEditControl, -// but it handles events on its own, using an internal loop, instead of using SCI -// scripts for event management like kEditControl does. Called by script 64914, -// DEdit::hilite(). -reg_t kEditText(EngineState *s, int argc, reg_t *argv) { - reg_t controlObject = argv[0]; - - if (!controlObject.isNull()) { - g_sci->_gfxControls32->kernelTexteditChange(controlObject); - } - - return s->r_acc; -} - -#endif - } // End of namespace Sci diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp new file mode 100644 index 0000000000..8b3afeef99 --- /dev/null +++ b/engines/sci/engine/kgraphics32.cpp @@ -0,0 +1,812 @@ +/* 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/system.h" + +#include "engines/util.h" +#include "graphics/cursorman.h" +#include "graphics/surface.h" + +#include "gui/message.h" + +#include "sci/sci.h" +#include "sci/event.h" +#include "sci/resource.h" +#include "sci/engine/features.h" +#include "sci/engine/state.h" +#include "sci/engine/selector.h" +#include "sci/engine/kernel.h" +#include "sci/graphics/animate.h" +#include "sci/graphics/cache.h" +#include "sci/graphics/compare.h" +#include "sci/graphics/controls16.h" +#include "sci/graphics/coordadjuster.h" +#include "sci/graphics/cursor.h" +#include "sci/graphics/palette.h" +#include "sci/graphics/paint16.h" +#include "sci/graphics/picture.h" +#include "sci/graphics/ports.h" +#include "sci/graphics/screen.h" +#include "sci/graphics/text16.h" +#include "sci/graphics/view.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/controls32.h" +#include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class +#include "sci/graphics/text32.h" +#include "sci/graphics/frameout.h" +#endif + +namespace Sci { +#ifdef ENABLE_SCI32 + +extern void showScummVMDialog(const Common::String &message); + +reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { + // Returns 0 if the screen width or height is less than 640 or 400, + // respectively. + if (g_system->getWidth() < 640 || g_system->getHeight() < 400) + return make_reg(0, 0); + + return make_reg(0, 1); +} + +// SCI32 variant, can't work like sci16 variants +reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) { + // TODO +// reg_t curObject = argv[0]; +// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; + + return NULL_REG; +} + +reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { + if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL) + g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); + else + g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); + return s->r_acc; +} + +reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); + return s->r_acc; +} + +reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]); + return s->r_acc; +} + +reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelAddPlane(argv[0]); + return s->r_acc; +} + +reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelDeletePlane(argv[0]); + return s->r_acc; +} + +reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]); + return s->r_acc; +} + +reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) { + reg_t planeObj = argv[0]; + GuiResourceId pictureId = argv[1].toUint16(); + int16 pictureX = argv[2].toSint16(); + int16 pictureY = argv[3].toSint16(); + + g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY); + return s->r_acc; +} + +reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_gfxFrameout->kernelGetHighPlanePri()); +} + +reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelFrameout(); + return NULL_REG; +} + +reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { + Common::Rect objRect1 = g_sci->_gfxCompare->getNSRect(argv[0]); + Common::Rect objRect2 = g_sci->_gfxCompare->getNSRect(argv[1]); + return make_reg(0, objRect1.intersects(objRect2)); +} + +// Tests if the coordinate is on the passed object +reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { + uint16 x = argv[0].toUint16(); + uint16 y = argv[1].toUint16(); + reg_t targetObject = argv[2]; + uint16 illegalBits = argv[3].getOffset(); + Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(targetObject, true); + + // we assume that x, y are local coordinates + + bool contained = nsRect.contains(x, y); + if (contained && illegalBits) { + // If illegalbits are set, we check the color of the pixel that got clicked on + // for now, we return false if the pixel is transparent + // although illegalBits may get differently set, don't know yet how this really works out + uint16 viewId = readSelectorValue(s->_segMan, targetObject, SELECTOR(view)); + int16 loopNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(loop)); + int16 celNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(cel)); + if (g_sci->_gfxCompare->kernelIsItSkip(viewId, loopNo, celNo, Common::Point(x - nsRect.left, y - nsRect.top))) + contained = false; + } + return make_reg(0, contained); +} + +reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { + switch (argv[0].toUint16()) { + case 0: { + if (argc != 4) { + warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); + return NULL_REG; + } + reg_t object = argv[3]; + Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); + debugC(kDebugLevelStrings, "kCreateTextBitmap case 0 (%04x:%04x, %04x:%04x, %04x:%04x)", + PRINT_REG(argv[1]), PRINT_REG(argv[2]), PRINT_REG(argv[3])); + debugC(kDebugLevelStrings, "%s", text.c_str()); + int16 maxWidth = argv[1].toUint16(); + int16 maxHeight = argv[2].toUint16(); + g_sci->_gfxCoordAdjuster->fromScriptToDisplay(maxHeight, maxWidth); + // These values can be larger than the screen in the SQ6 demo, room 100 + // TODO: Find out why. For now, don't show any text in that room. + if (g_sci->getGameId() == GID_SQ6 && g_sci->isDemo() && s->currentRoomNumber() == 100) + return NULL_REG; + return g_sci->_gfxText32->createTextBitmap(object, maxWidth, maxHeight); + } + case 1: { + if (argc != 2) { + warning("kCreateTextBitmap(1): expected 2 arguments, got %i", argc); + return NULL_REG; + } + reg_t object = argv[1]; + Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); + debugC(kDebugLevelStrings, "kCreateTextBitmap case 1 (%04x:%04x)", PRINT_REG(argv[1])); + debugC(kDebugLevelStrings, "%s", text.c_str()); + return g_sci->_gfxText32->createTextBitmap(object); + } + default: + warning("CreateTextBitmap(%d)", argv[0].toUint16()); + return NULL_REG; + } +} + +reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->disposeTextBitmap(argv[0]); + return s->r_acc; +} + +reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { + switch (argv[0].toUint16()) { + case 1: + // Load a help file + // Maybe in the future we can implement this, but for now this message should suffice + showScummVMDialog("Please use an external viewer to open the game's help file: " + s->_segMan->getString(argv[1])); + break; + case 2: + // Looks like some init function + break; + default: + warning("Unknown kWinHelp subop %d", argv[0].toUint16()); + } + + return s->r_acc; +} + +/** + * Used for scene transitions, replacing (but reusing parts of) the old + * transition code. + */ +reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { + // Can be called with 7 or 8 parameters + // The style defines which transition to perform. Related to the transition + // tables inside graphics/transitions.cpp + uint16 showStyle = argv[0].toUint16(); // 0 - 15 + reg_t planeObj = argv[1]; // the affected plane + Common::String planeObjName = s->_segMan->getObjectName(planeObj); + uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts + uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff + int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out + uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts + uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out + int16 divisions; + + // If the game has the pFadeArray selector, another parameter is used here, + // before the optional last parameter + bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0; + if (hasFadeArray) { + // argv[7] + divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?) + } else { + divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?) + } + + if (showStyle > 15) { + warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj)); + return s->r_acc; + } + + // GK1 calls fadeout (13) / fadein (14) with the following parameters: + // seconds: 1 + // backColor: 0 / -1 + // fade: 200 + // animate: 0 + // refFrame: 0 + // divisions: 0 / 20 + + // TODO: Check if the plane is in the list of planes to draw + + Common::String effectName = "unknown"; + + switch (showStyle) { + case 0: // no transition / show + effectName = "show"; + break; + case 13: // fade out + effectName = "fade out"; + // TODO + break; + case 14: // fade in + effectName = "fade in"; + // TODO + break; + default: + // TODO + break; + } + + warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, " + "back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d", + showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(), + seconds, backColor, priority, animate, refFrame, divisions); + return s->r_acc; +} + +reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { + // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board + // are occupied by pieces already + + switch (argv[0].toUint16()) { // subops 0 - 4 + // 0 - return the view + // 1 - return the loop + // 2, 3 - nop + case 4: { + GuiResourceId viewId = argv[1].toSint16(); + int16 loopNo = argv[2].toSint16(); + int16 celNo = argv[3].toSint16(); + int16 x = argv[4].toUint16(); + int16 y = argv[5].toUint16(); + byte color = g_sci->_gfxCache->kernelViewGetColorAtCoordinate(viewId, loopNo, celNo, x, y); + return make_reg(0, color); + } + default: { + kStub(s, argc, argv); + return s->r_acc; + } + } +} + +reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { + // Used by SQ6 and LSL6 hires for the text area in the bottom of the + // screen. The relevant scripts also exist in Phantasmagoria 1, but they're + // unused. This is always called by scripts 64906 (ScrollerWindow) and + // 64907 (ScrollableWindow). + + reg_t kWindow = argv[1]; + uint16 op = argv[0].toUint16(); + switch (op) { + case 0: // Init + g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems + g_sci->_gfxFrameout->clearScrollTexts(); + return argv[1]; // kWindow + case 1: // Show message, called by ScrollableWindow::addString + case 14: // Modify message, called by ScrollableWindow::modifyString + // 5 or 6 parameters + // Seems to be called with 5 parameters when the narrator speaks, and + // with 6 when Roger speaks + { + Common::String text = s->_segMan->getString(argv[2]); + uint16 x = 0;//argv[3].toUint16(); // TODO: can't be x (values are all wrong) + uint16 y = 0;//argv[4].toUint16(); // TODO: can't be y (values are all wrong) + // TODO: argv[5] is an optional unknown parameter (an integer set to 0) + g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14)); + } + break; + case 2: // Clear, called by ScrollableWindow::erase + g_sci->_gfxFrameout->clearScrollTexts(); + break; + case 3: // Page up, called by ScrollableWindow::scrollTo + // TODO + kStub(s, argc, argv); + break; + case 4: // Page down, called by ScrollableWindow::scrollTo + // TODO + kStub(s, argc, argv); + break; + case 5: // Up arrow, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->prevScrollText(); + break; + case 6: // Down arrow, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->nextScrollText(); + break; + case 7: // Home, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->firstScrollText(); + break; + case 8: // End, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->lastScrollText(); + break; + case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize + // TODO + kStub(s, argc, argv); + break; + case 10: // Where, called by ScrollableWindow::where + // TODO + // argv[2] is an unknown integer + // Silenced the warnings because of the high amount of console spam + //kStub(s, argc, argv); + break; + case 11: // Go, called by ScrollableWindow::scrollTo + // 2 extra parameters here + // TODO + kStub(s, argc, argv); + break; + case 12: // Insert, called by ScrollableWindow::insertString + // 3 extra parameters here + // TODO + kStub(s, argc, argv); + break; + // case 13 (Delete) is handled below + // case 14 (Modify) is handled above + case 15: // Hide, called by ScrollableWindow::hide + g_sci->_gfxFrameout->toggleScrollText(false); + break; + case 16: // Show, called by ScrollableWindow::show + g_sci->_gfxFrameout->toggleScrollText(true); + break; + case 17: // Destroy, called by ScrollableWindow::dispose + g_sci->_gfxFrameout->clearScrollTexts(); + break; + case 13: // Delete, unused + case 18: // Text, unused + case 19: // Reconstruct, unused + error("kScrollWindow: Unused subop %d invoked", op); + break; + default: + error("kScrollWindow: unknown subop %d", op); + break; + } + + return s->r_acc; +} + +reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { + // TODO: This defines the resolution that the fonts are supposed to be displayed + // in. Currently, this is only used for showing high-res fonts in GK1 Mac, but + // should be extended to handle other font resolutions such as those + + int xResolution = argv[0].toUint16(); + //int yResolution = argv[1].toUint16(); + + g_sci->_gfxScreen->setFontIsUpscaled(xResolution == 640 && + g_sci->_gfxScreen->getUpscaledHires() != GFX_SCREEN_UPSCALED_DISABLED); + + return s->r_acc; +} + +reg_t kFont(EngineState *s, int argc, reg_t *argv) { + // Handle font settings for SCI2.1 + + switch (argv[0].toUint16()) { + case 1: + // Set font resolution + return kSetFontRes(s, argc - 1, argv + 1); + default: + warning("kFont: unknown subop %d", argv[0].toUint16()); + } + + return s->r_acc; +} + +// TODO: Eventually, all of the kBitmap operations should be put +// in a separate class + +#define BITMAP_HEADER_SIZE 46 + +reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { + // Used for bitmap operations in SCI2.1 and SCI3. + // This is the SCI2.1 version, the functionality seems to have changed in SCI3. + + switch (argv[0].toUint16()) { + case 0: // init bitmap surface + { + // 6 params, called e.g. from TextView::init() in Torin's Passage, + // script 64890 and TransView::init() in script 64884 + uint16 width = argv[1].toUint16(); + uint16 height = argv[2].toUint16(); + //uint16 skip = argv[3].toUint16(); + uint16 back = argv[4].toUint16(); // usually equals skip + //uint16 width2 = (argc >= 6) ? argv[5].toUint16() : 0; + //uint16 height2 = (argc >= 7) ? argv[6].toUint16() : 0; + //uint16 transparentFlag = (argc >= 8) ? argv[7].toUint16() : 0; + + // TODO: skip, width2, height2, transparentFlag + // (used for transparent bitmaps) + int entrySize = width * height + BITMAP_HEADER_SIZE; + reg_t memoryId = s->_segMan->allocateHunkEntry("Bitmap()", entrySize); + byte *memoryPtr = s->_segMan->getHunkPointer(memoryId); + memset(memoryPtr, 0, BITMAP_HEADER_SIZE); // zero out the bitmap header + memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height); + // Save totalWidth, totalHeight + // TODO: Save the whole bitmap header, like SSCI does + WRITE_LE_UINT16(memoryPtr, width); + WRITE_LE_UINT16(memoryPtr + 2, height); + return memoryId; + } + break; + case 1: // dispose text bitmap surface + return kDisposeTextBitmap(s, argc - 1, argv + 1); + case 2: // dispose bitmap surface, with extra param + // 2 params, called e.g. from MenuItem::dispose in Torin's Passage, + // script 64893 + warning("kBitmap(2), unk1 %d, bitmap ptr %04x:%04x", argv[1].toUint16(), PRINT_REG(argv[2])); + break; + case 3: // tiled surface + { + // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage, + // script 64869 + reg_t hunkId = argv[1]; // obtained from kBitmap(0) + // The tiled view seems to always have 2 loops. + // These loops need to have 1 cel in loop 0 and 8 cels in loop 1. + uint16 viewNum = argv[2].toUint16(); // vTiles selector + uint16 loop = argv[3].toUint16(); + uint16 cel = argv[4].toUint16(); + uint16 x = argv[5].toUint16(); + uint16 y = argv[6].toUint16(); + + byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); + // Get totalWidth, totalHeight + uint16 totalWidth = READ_LE_UINT16(memoryPtr); + uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); + byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; + + GfxView *view = g_sci->_gfxCache->getView(viewNum); + uint16 tileWidth = view->getWidth(loop, cel); + uint16 tileHeight = view->getHeight(loop, cel); + const byte *tileBitmap = view->getBitmap(loop, cel); + uint16 width = MIN<uint16>(totalWidth - x, tileWidth); + uint16 height = MIN<uint16>(totalHeight - y, tileHeight); + + for (uint16 curY = 0; curY < height; curY++) { + for (uint16 curX = 0; curX < width; curX++) { + bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX]; + } + } + + } + break; + case 4: // add text to bitmap + { + // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage, + // script 64894 + reg_t hunkId = argv[1]; // obtained from kBitmap(0) + Common::String text = s->_segMan->getString(argv[2]); + uint16 textX = argv[3].toUint16(); + uint16 textY = argv[4].toUint16(); + //reg_t unk5 = argv[5]; + //reg_t unk6 = argv[6]; + //reg_t unk7 = argv[7]; // skip? + //reg_t unk8 = argv[8]; // back? + //reg_t unk9 = argv[9]; + uint16 fontId = argv[10].toUint16(); + //uint16 mode = argv[11].toUint16(); + uint16 dimmed = argv[12].toUint16(); + //warning("kBitmap(4): bitmap ptr %04x:%04x, font %d, mode %d, dimmed %d - text: \"%s\"", + // PRINT_REG(bitmapPtr), font, mode, dimmed, text.c_str()); + uint16 foreColor = 255; // TODO + + byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); + // Get totalWidth, totalHeight + uint16 totalWidth = READ_LE_UINT16(memoryPtr); + uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); + byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; + + GfxFont *font = g_sci->_gfxCache->getFont(fontId); + + int16 charCount = 0; + uint16 curX = textX, curY = textY; + const char *txt = text.c_str(); + + while (*txt) { + charCount = g_sci->_gfxText32->GetLongest(txt, totalWidth, font); + if (charCount == 0) + break; + + for (int i = 0; i < charCount; i++) { + unsigned char curChar = txt[i]; + font->drawToBuffer(curChar, curY, curX, foreColor, dimmed, bitmap, totalWidth, totalHeight); + curX += font->getCharWidth(curChar); + } + + curX = textX; + curY += font->getHeight(); + txt += charCount; + while (*txt == ' ') + txt++; // skip over breaking spaces + } + + } + break; + case 5: // fill with color + { + // 6 params, called e.g. from TextView::init() and TextView::draw() + // in Torin's Passage, script 64890 + reg_t hunkId = argv[1]; // obtained from kBitmap(0) + uint16 x = argv[2].toUint16(); + uint16 y = argv[3].toUint16(); + uint16 fillWidth = argv[4].toUint16(); // width - 1 + uint16 fillHeight = argv[5].toUint16(); // height - 1 + uint16 back = argv[6].toUint16(); + + byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); + // Get totalWidth, totalHeight + uint16 totalWidth = READ_LE_UINT16(memoryPtr); + uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); + uint16 width = MIN<uint16>(totalWidth - x, fillWidth); + uint16 height = MIN<uint16>(totalHeight - y, fillHeight); + byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; + + for (uint16 curY = 0; curY < height; curY++) { + for (uint16 curX = 0; curX < width; curX++) { + bitmap[(curY + y) * totalWidth + (curX + x)] = back; + } + } + + } + break; + default: + kStub(s, argc, argv); + break; + } + + return s->r_acc; +} + +// Used for edit boxes in save/load dialogs. It's a rewritten version of kEditControl, +// but it handles events on its own, using an internal loop, instead of using SCI +// scripts for event management like kEditControl does. Called by script 64914, +// DEdit::hilite(). +reg_t kEditText(EngineState *s, int argc, reg_t *argv) { + reg_t controlObject = argv[0]; + + if (!controlObject.isNull()) { + g_sci->_gfxControls32->kernelTexteditChange(controlObject); + } + + return s->r_acc; +} + +reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { + reg_t plane = argv[0]; + Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16()); + Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16()); + // argv[5] is unknown (a number, usually 200) + byte color = (byte)argv[6].toUint16(); + byte priority = (byte)argv[7].toUint16(); + byte control = (byte)argv[8].toUint16(); + // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps? + return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control); +} + +reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { + reg_t hunkId = argv[0]; + reg_t plane = argv[1]; + Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16()); + Common::Point endPoint(argv[4].toUint16(), argv[5].toUint16()); + // argv[6] is unknown (a number, usually 200) + byte color = (byte)argv[7].toUint16(); + byte priority = (byte)argv[8].toUint16(); + byte control = (byte)argv[9].toUint16(); + // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps? + g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control); + return s->r_acc; +} +reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) { + reg_t hunkId = argv[0]; + reg_t plane = argv[1]; + g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); + return s->r_acc; +} + +reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { + // Called in the intro of LSL6 hires (room 110) + // The end effect of this is the same as the old screen scroll transition + + // 7 parameters + reg_t planeObject = argv[0]; + //int16 x = argv[1].toSint16(); + //int16 y = argv[2].toSint16(); + uint16 pictureId = argv[3].toUint16(); + // param 4: int (0 in LSL6, probably scroll direction? The picture in LSL6 scrolls down) + // param 5: int (first call is 1, then the subsequent one is 0 in LSL6) + // param 6: optional int (0 in LSL6) + + // Set the new picture directly for now + //writeSelectorValue(s->_segMan, planeObject, SELECTOR(left), x); + //writeSelectorValue(s->_segMan, planeObject, SELECTOR(top), y); + writeSelectorValue(s->_segMan, planeObject, SELECTOR(picture), pictureId); + // and update our draw list + g_sci->_gfxFrameout->kernelUpdatePlane(planeObject); + + // TODO + return kStub(s, argc, argv); +} + +reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) { + // Examples: GK1 room 480 (Bayou ritual), LSL6 room 100 (title screen) + + switch (argv[0].toUint16()) { + case 0: { // Palette animation initialization + // 3 or 4 extra params + // Case 1 sends fromColor and speed again, so we don't need them here. + // Only toColor is stored + //uint16 fromColor = argv[1].toUint16(); + s->_palCycleToColor = argv[2].toUint16(); + //uint16 speed = argv[3].toUint16(); + + // Invalidate the picture, so that the palette steps calls (case 1 + // below) can update its palette without it being overwritten by the + // view/picture palettes. + g_sci->_gfxScreen->_picNotValid = 1; + + // TODO: The fourth optional parameter is an unknown integer, and is 0 by default + if (argc == 5) { + // When this variant is used, picNotValid doesn't seem to be set + // (e.g. GK1 room 480). In this case, the animation step calls are + // not made, so perhaps this signifies the palette cycling steps + // to make. + // GK1 sets this to 6 (6 palette steps?) + g_sci->_gfxScreen->_picNotValid = 0; + } + kStub(s, argc, argv); + } + break; + case 1: { // Palette animation step + // This is the same as the old kPaletteAnimate call, with 1 set of colors. + // The end color is set up during initialization in case 0 above. + + // 1 or 2 extra params + uint16 fromColor = argv[1].toUint16(); + uint16 speed = (argc == 2) ? 1 : argv[2].toUint16(); + // TODO: For some reason, this doesn't set the color correctly + // (e.g. LSL6 intro, room 100, Sierra logo) + if (g_sci->_gfxPalette->kernelAnimate(fromColor, s->_palCycleToColor, speed)) + g_sci->_gfxPalette->kernelAnimateSet(); + } + // No kStub() call here, as this gets called loads of times, like kPaletteAnimate + break; + // case 2 hasn't been encountered + // case 3 hasn't been encountered + case 4: // reset any palette cycling and make the picture valid again + // Gets called when changing rooms and after palette cycling animations finish + // 0 or 1 extra params + if (argc == 1) { + g_sci->_gfxScreen->_picNotValid = 0; + // TODO: This also seems to perform more steps + } else { + // The variant with the 1 extra param resets remapping to base + // TODO + } + kStub(s, argc, argv); + break; + default: + // TODO + kStub(s, argc, argv); + break; + } + + return s->r_acc; +} + +reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) { + uint16 operation = argv[0].toUint16(); + + switch (operation) { + case 0: { // turn remapping off + // WORKAROUND: Game scripts in QFG4 erroneously turn remapping off in room + // 140 (the character point allocation screen) and never turn it back on, + // even if it's clearly used in that screen. + if (g_sci->getGameId() == GID_QFG4 && s->currentRoomNumber() == 140) + return s->r_acc; + + int16 base = (argc >= 2) ? argv[1].toSint16() : 0; + if (base > 0) + warning("kRemapColors(0) called with base %d", base); + g_sci->_gfxPalette->resetRemapping(); + } + break; + case 1: { // remap by range + uint16 color = argv[1].toUint16(); + uint16 from = argv[2].toUint16(); + uint16 to = argv[3].toUint16(); + uint16 base = argv[4].toUint16(); + uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0; + if (unk5 > 0) + warning("kRemapColors(1) called with 6 parameters, unknown parameter is %d", unk5); + g_sci->_gfxPalette->setRemappingRange(color, from, to, base); + } + break; + case 2: { // remap by percent + uint16 color = argv[1].toUint16(); + uint16 percent = argv[2].toUint16(); // 0 - 100 + if (argc >= 4) + warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); + g_sci->_gfxPalette->setRemappingPercent(color, percent); + } + break; + case 3: { // remap to gray + // Example call: QFG4 room 490 (Baba Yaga's hut) - params are color 253, 75% and 0. + // In this room, it's used for the cloud before Baba Yaga appears. + int16 color = argv[1].toSint16(); + int16 percent = argv[2].toSint16(); // 0 - 100 + if (argc >= 4) + warning("RemapToGray called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); + g_sci->_gfxPalette->setRemappingPercentGray(color, percent); + } + break; + case 4: { // remap to percent gray + // Example call: QFG4 rooms 530/535 (swamp) - params are 253, 100%, 200 + int16 color = argv[1].toSint16(); + int16 percent = argv[2].toSint16(); // 0 - 100 + // argv[3] is unknown (a number, e.g. 200) - start color, perhaps? + if (argc >= 5) + warning("RemapToGrayPercent called with 5 parameters, unknown parameter is %d", argv[4].toUint16()); + g_sci->_gfxPalette->setRemappingPercentGray(color, percent); + } + break; + case 5: { // don't map to range + //int16 mapping = argv[1].toSint16(); + uint16 intensity = argv[2].toUint16(); + // HACK for PQ4 + if (g_sci->getGameId() == GID_PQ4) + g_sci->_gfxPalette->kernelSetIntensity(0, 255, intensity, true); + + kStub(s, argc, argv); + } + break; + default: + break; + } + + return s->r_acc; +} + +#endif + +} // End of namespace Sci diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp index 2a33df26bc..342fa95eda 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -409,10 +409,14 @@ int sort_temp_cmp(const void *p1, const void *p2) { const sort_temp_t *st1 = (const sort_temp_t *)p1; const sort_temp_t *st2 = (const sort_temp_t *)p2; - if (st1->order.segment < st2->order.segment || (st1->order.segment == st2->order.segment && st1->order.offset < st2->order.offset)) + if (st1->order.getSegment() < st2->order.getSegment() || + (st1->order.getSegment() == st2->order.getSegment() && + st1->order.getOffset() < st2->order.getOffset())) return -1; - if (st1->order.segment > st2->order.segment || (st1->order.segment == st2->order.segment && st1->order.offset > st2->order.offset)) + if (st1->order.getSegment() > st2->order.getSegment() || + (st1->order.getSegment() == st2->order.getSegment() && + st1->order.getOffset() > st2->order.getOffset())) return 1; return 0; @@ -502,6 +506,11 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv) { curIndex++; } + // Update the virtual file selected in the character import screen of QFG4. + // For the SCI0-SCI1.1 version of this, check kDrawControl(). + if (g_sci->inQfGImportRoom() && !strcmp(s->_segMan->getObjectName(curObject), "SelectorDText")) + s->_chosenQfGImportItem = listIndex; + return curObject; } @@ -575,8 +584,11 @@ reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv) { // First, check if the target selector is a variable if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { - // Can this happen with variable selectors? - error("kListFirstTrue: Attempted to access a variable selector"); + // If it's a variable selector, check its value. + // Example: script 64893 in Torin, MenuHandler::isHilited checks + // all children for variable selector 0x03ba (bHilited). + if (!readSelector(s->_segMan, curObject, slc).isNull()) + return curObject; } else { invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); @@ -609,16 +621,16 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) { // First, check if the target selector is a variable if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { - // Can this happen with variable selectors? - error("kListAllTrue: Attempted to access a variable selector"); + // If it's a variable selector, check its value + s->r_acc = readSelector(s->_segMan, curObject, slc); } else { invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); - - // Check if the result isn't true - if (s->r_acc.isNull()) - break; } + // Check if the result isn't true + if (s->r_acc.isNull()) + break; + curNode = s->_segMan->lookupNode(nextNode); } @@ -662,15 +674,15 @@ reg_t kArray(EngineState *s, int argc, reg_t *argv) { if (argv[2].toUint16() == 3) return kString(s, argc, argv); } else { - if (s->_segMan->getSegmentType(argv[1].segment) == SEG_TYPE_STRING || - s->_segMan->getSegmentType(argv[1].segment) == SEG_TYPE_SCRIPT) { + if (s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_STRING || + s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_SCRIPT) { return kString(s, argc, argv); } #if 0 if (op == 6) { - if (s->_segMan->getSegmentType(argv[3].segment) == SEG_TYPE_STRING || - s->_segMan->getSegmentType(argv[3].segment) == SEG_TYPE_SCRIPT) { + if (s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_STRING || + s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_SCRIPT) { return kString(s, argc, argv); } } @@ -789,7 +801,7 @@ reg_t kArray(EngineState *s, int argc, reg_t *argv) { #endif return NULL_REG; } - if (s->_segMan->getSegmentObj(argv[1].segment)->getType() != SEG_TYPE_ARRAY) + if (s->_segMan->getSegmentObj(argv[1].getSegment())->getType() != SEG_TYPE_ARRAY) error("kArray(Dup): Request to duplicate a segment which isn't an array"); reg_t arrayHandle; diff --git a/engines/sci/engine/kmath.cpp b/engines/sci/engine/kmath.cpp index 7570856dff..4b8fadbb84 100644 --- a/engines/sci/engine/kmath.cpp +++ b/engines/sci/engine/kmath.cpp @@ -77,7 +77,18 @@ reg_t kSqrt(EngineState *s, int argc, reg_t *argv) { return make_reg(0, (int16) sqrt((float) ABS(argv[0].toSint16()))); } +/** + * Returns the angle (in degrees) between the two points determined by (x1, y1) + * and (x2, y2). The angle ranges from 0 to 359 degrees. + * What this function does is pretty simple but apparently the original is not + * accurate. + */ uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) { + // SCI1 games (QFG2 and newer) use a simple atan implementation. SCI0 games + // use a somewhat less accurate calculation (below). + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) + return (int16)(360 - atan2((double)(x1 - x2), (double)(y1 - y2)) * 57.2958) % 360; + int16 xRel = x2 - x1; int16 yRel = y1 - y2; // y-axis is mirrored. int16 angle; diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index a32480c168..8b7fc4ffec 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -128,7 +128,7 @@ reg_t kMemoryInfo(EngineState *s, int argc, reg_t *argv) { // fragmented const uint16 size = 0x7fea; - switch (argv[0].offset) { + switch (argv[0].getOffset()) { case K_MEMORYINFO_LARGEST_HEAP_BLOCK: // In order to prevent "Memory fragmented" dialogs from // popping up in some games, we must return FREE_HEAP - 2 here. @@ -140,7 +140,7 @@ reg_t kMemoryInfo(EngineState *s, int argc, reg_t *argv) { return make_reg(0, size); default: - error("Unknown MemoryInfo operation: %04x", argv[0].offset); + error("Unknown MemoryInfo operation: %04x", argv[0].getOffset()); } return NULL_REG; @@ -304,7 +304,7 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { break; } case K_MEMORY_PEEK : { - if (!argv[1].segment) { + if (!argv[1].getSegment()) { // This occurs in KQ5CD when interacting with certain objects warning("Attempt to peek invalid memory at %04x:%04x", PRINT_REG(argv[1])); return s->r_acc; @@ -334,11 +334,11 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { } if (ref.isRaw) { - if (argv[2].segment) { + if (argv[2].getSegment()) { error("Attempt to poke memory reference %04x:%04x to %04x:%04x", PRINT_REG(argv[2]), PRINT_REG(argv[1])); return s->r_acc; } - WRITE_SCIENDIAN_UINT16(ref.raw, argv[2].offset); // Amiga versions are BE + WRITE_SCIENDIAN_UINT16(ref.raw, argv[2].getOffset()); // Amiga versions are BE } else { if (ref.skipByte) error("Attempt to poke memory at odd offset %04X:%04X", PRINT_REG(argv[1])); @@ -356,10 +356,81 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) { Common::String setting = s->_segMan->getString(argv[0]); reg_t data = readSelector(s->_segMan, argv[1], SELECTOR(data)); - warning("Get config setting %s", setting.c_str()); - s->_segMan->strcpy(data, ""); + // This function is used to get the benchmarked results stored in the + // resource.cfg configuration file in Phantasmagoria 1. Normally, + // the configuration file contains values stored by the installer + // regarding audio and video settings, which are then used by the + // executable. In Phantasmagoria, two extra executable files are used + // to perform system benchmarks: + // - CPUID for the CPU benchmarks, sets the cpu and cpuspeed settings + // - HDDTEC for the graphics and CD-ROM benchmarks, sets the videospeed setting + // + // These settings are then used by the game scripts directly to modify + // the game speed and graphics output. The result of this call is stored + // in global 178. The scripts check these values against the value 425. + // Anything below that makes Phantasmagoria awfully sluggish, so we're + // setting everything to 500, which makes the game playable. + + setting.toLowercase(); + + if (setting == "videospeed") { + s->_segMan->strcpy(data, "500"); + } else if (setting == "cpu") { + // We always return the fastest CPU setting that CPUID can detect + // (i.e. 586). + s->_segMan->strcpy(data, "586"); + } else if (setting == "cpuspeed") { + s->_segMan->strcpy(data, "500"); + } else if (setting == "language") { + Common::String languageId = Common::String::format("%d", g_sci->getSciLanguage()); + s->_segMan->strcpy(data, languageId.c_str()); + } else if (setting == "torindebug") { + // Used to enable the debug mode in Torin's Passage (French). + // If true, the debug mode is enabled. + s->_segMan->strcpy(data, ""); + } else if (setting == "leakdump") { + // An unknown setting in LSL7. Likely used for debugging. + s->_segMan->strcpy(data, ""); + } else if (setting == "startroom") { + // Debug setting in LSL7, specifies the room to start from. + s->_segMan->strcpy(data, ""); + } else { + error("GetConfig: Unknown configuration setting %s", setting.c_str()); + } + return argv[1]; } + +// Likely modelled after the Windows 3.1 function GetPrivateProfileInt: +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724345%28v=vs.85%29.aspx +reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv) { + Common::String category = s->_segMan->getString(argv[0]); // always "config" + category.toLowercase(); + Common::String setting = s->_segMan->getString(argv[1]); + setting.toLowercase(); + // The third parameter is the default value returned if the configuration key is missing + + if (category == "config" && setting == "videospeed") { + // We return the same fake value for videospeed as with kGetConfig + return make_reg(0, 500); + } + + warning("kGetSierraProfileInt: Returning default value %d for unknown setting %s.%s", argv[2].toSint16(), category.c_str(), setting.c_str()); + return argv[2]; +} + +reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv) { + uint16 windowsOption = argv[0].toUint16(); + switch (windowsOption) { + case 0: + // Title bar on/off in Phantasmagoria, we return 0 (off) + return NULL_REG; + default: + warning("GetWindowsOption: Unknown option %d", windowsOption); + return NULL_REG; + } +} + #endif // kIconBar is really a subop of kMacPlatform for SCI1.1 Mac diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp index 59694cb6ee..5e861daaa4 100644 --- a/engines/sci/engine/kparse.cpp +++ b/engines/sci/engine/kparse.cpp @@ -48,7 +48,7 @@ reg_t kSaid(EngineState *s, int argc, reg_t *argv) { const int debug_parser = 0; #endif - if (!heap_said_block.segment) + if (!heap_said_block.getSegment()) return NULL_REG; said_block = (byte *)s->_segMan->derefBulkPtr(heap_said_block, 0); diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 089b325a7f..4061795f82 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -31,6 +31,9 @@ #include "common/debug-channels.h" #include "common/list.h" #include "common/system.h" +#include "common/math.h" + +//#define DEBUG_MERGEPOLY namespace Sci { @@ -71,11 +74,25 @@ enum { struct FloatPoint { FloatPoint() : x(0), y(0) {} FloatPoint(float x_, float y_) : x(x_), y(y_) {} + FloatPoint(Common::Point p) : x(p.x), y(p.y) {} Common::Point toPoint() { return Common::Point((int16)(x + 0.5), (int16)(y + 0.5)); } + float operator*(const FloatPoint &p) const { + return x*p.x + y*p.y; + } + FloatPoint operator*(float l) const { + return FloatPoint(l*x, l*y); + } + FloatPoint operator-(const FloatPoint &p) const { + return FloatPoint(x-p.x, y-p.y); + } + float norm() const { + return x*x+y*y; + } + float x, y; }; @@ -135,15 +152,20 @@ public: return _head; } - void insertHead(Vertex *elm) { + void insertAtEnd(Vertex *elm) { if (_head == NULL) { elm->_next = elm->_prev = elm; + _head = elm; } else { elm->_next = _head; elm->_prev = _head->_prev; _head->_prev = elm; elm->_prev->_next = elm; } + } + + void insertHead(Vertex *elm) { + insertAtEnd(elm); _head = elm; } @@ -367,7 +389,7 @@ static void draw_input(EngineState *s, reg_t poly_list, Common::Point start, Com draw_point(s, start, 1, width, height); draw_point(s, end, 0, width, height); - if (!poly_list.segment) + if (!poly_list.getSegment()) return; list = s->_segMan->lookupList(poly_list); @@ -423,7 +445,7 @@ static void print_input(EngineState *s, reg_t poly_list, Common::Point start, Co debug("End point: (%i, %i)", end.x, end.y); debug("Optimization level: %i", opt); - if (!poly_list.segment) + if (!poly_list.getSegment()) return; list = s->_segMan->lookupList(poly_list); @@ -788,10 +810,10 @@ int PathfindingState::findNearPoint(const Common::Point &p, Polygon *polygon, Co * including the vertices themselves) * Parameters: (const Common::Point &) a, b: The line segment (a, b) * (Vertex *) vertex: The first vertex of the edge - * Returns : (int) FP_OK on success, PF_ERROR otherwise + * Returns : (int) PF_OK on success, PF_ERROR otherwise * (FloatPoint) *ret: The intersection point */ -static int intersection(const Common::Point &a, const Common::Point &b, Vertex *vertex, FloatPoint *ret) { +static int intersection(const Common::Point &a, const Common::Point &b, const Vertex *vertex, FloatPoint *ret) { // Parameters of parametric equations float s, t; // Numerator and denominator of equations @@ -1180,7 +1202,7 @@ static PathfindingState *convert_polygon_set(EngineState *s, reg_t poly_list, Co PathfindingState *pf_s = new PathfindingState(width, height); // Convert all polygons - if (poly_list.segment) { + if (poly_list.getSegment()) { List *list = s->_segMan->lookupList(poly_list); Node *node = s->_segMan->lookupNode(list->first); @@ -1503,7 +1525,7 @@ reg_t kAvoidPath(EngineState *s, int argc, reg_t *argv) { draw_point(s, start, 1, width, height); draw_point(s, end, 0, width, height); - if (poly_list.segment) { + if (poly_list.getSegment()) { print_input(s, poly_list, start, end, opt); draw_input(s, poly_list, start, end, opt, width, height); } @@ -1783,39 +1805,619 @@ reg_t kIntersections(EngineState *s, int argc, reg_t *argv) { } } +// ========================================================================== +// kMergePoly utility functions + +// Compute square of the distance of p to the segment a-b. +static float pointSegDistance(const Common::Point &a, const Common::Point &b, + const Common::Point &p) { + FloatPoint ba(b-a); + FloatPoint pa(p-a); + FloatPoint bp(b-p); + + // Check if the projection of p on the line a-b lies between a and b + if (ba*pa >= 0.0f && ba*bp >= 0.0f) { + // If yes, return the (squared) distance of p to the line a-b: + // translate a to origin, project p and subtract + float linedist = (ba*((ba*pa)/(ba*ba)) - pa).norm(); + + return linedist; + } else { + // If no, return the (squared) distance to either a or b, whichever + // is closest. + + // distance to a: + float adist = pa.norm(); + // distance to b: + float bdist = FloatPoint(p-b).norm(); + + return MIN(adist, bdist); + } +} + +// find intersection between edges of two polygons. +// endpoints count, except v2->_next +static bool segSegIntersect(const Vertex *v1, const Vertex *v2, Common::Point &intp) { + const Common::Point &a = v1->v; + const Common::Point &b = v1->_next->v; + const Common::Point &c = v2->v; + const Common::Point &d = v2->_next->v; + + // First handle the endpoint cases manually + + if (collinear(a, b, c) && collinear(a, b, d)) + return false; + + if (collinear(a, b, c)) { + // a, b, c collinear + // return true/c if c is between a and b + intp = c; + if (a.x != b.x) { + if ((a.x <= c.x && c.x <= b.x) || (b.x <= c.x && c.x <= a.x)) + return true; + } else { + if ((a.y <= c.y && c.y <= b.y) || (b.y <= c.y && c.y <= a.y)) + return true; + } + } + + if (collinear(a, b, d)) { + intp = d; + // a, b, d collinear + // return false/d if d is between a and b + if (a.x != b.x) { + if ((a.x <= d.x && d.x <= b.x) || (b.x <= d.x && d.x <= a.x)) + return false; + } else { + if ((a.y <= d.y && d.y <= b.y) || (b.y <= d.y && d.y <= a.y)) + return false; + } + } + + int len_dc = c.sqrDist(d); + + if (!len_dc) error("zero length edge in polygon"); + + if (pointSegDistance(c, d, a) <= 2.0f) { + intp = a; + return true; + } + + if (pointSegDistance(c, d, b) <= 2.0f) { + intp = b; + return true; + } + + // If not an endpoint, call the generic intersection function + + FloatPoint p; + if (intersection(a, b, v2, &p) == PF_OK) { + intp = p.toPoint(); + return true; + } else { + return false; + } +} + +// For intersecting polygon segments, determine if +// * the v2 edge enters polygon 1 at this intersection: positive return value +// * the v2 edge and the v1 edges are parallel: zero return value +// * the v2 edge exits polygon 1 at this intersection: negative return value +static int intersectDir(const Vertex *v1, const Vertex *v2) { + Common::Point p1 = v1->_next->v - v1->v; + Common::Point p2 = v2->_next->v - v2->v; + return (p1.x*p2.y - p2.x*p1.y); +} + +// Direction of edge in degrees from pos. x-axis, between -180 and 180 +static int edgeDir(const Vertex *v) { + Common::Point p = v->_next->v - v->v; + int deg = (int)Common::rad2deg(atan2((double)p.y, (double)p.x)); + if (deg < -180) deg += 360; + if (deg > 180) deg -= 360; + return deg; +} + +// For points p1, p2 on the polygon segment v, determine if +// * p1 lies before p2: negative return value +// * p1 and p2 are the same: zero return value +// * p1 lies after p2: positive return value +static int liesBefore(const Vertex *v, const Common::Point &p1, const Common::Point &p2) { + return v->v.sqrDist(p1) - v->v.sqrDist(p2); +} + +// Structure describing an "extension" to the work polygon following edges +// of the polygon being merged. + +// The patch begins on the point intersection1, being the intersection +// of the edges starting at indexw1/vertexw1 on the work polygon, and at +// indexp1/vertexp1 on the polygon being merged. +// It ends with the point intersection2, being the analogous intersection. +struct Patch { + unsigned int indexw1; + unsigned int indexp1; + const Vertex *vertexw1; + const Vertex *vertexp1; + Common::Point intersection1; + + unsigned int indexw2; + unsigned int indexp2; + const Vertex *vertexw2; + const Vertex *vertexp2; + Common::Point intersection2; + + bool disabled; // If true, this Patch was made superfluous by another Patch +}; + + +// Check if the given vertex on the work polygon is bypassed by this patch. +static bool isVertexCovered(const Patch &p, unsigned int wi) { + + // / v (outside) + // ---w1--1----p----w2--2---- + // ^ \ (inside) + if (wi > p.indexw1 && wi <= p.indexw2) + return true; + + // v / (outside) + // ---w2--2----p----w1--1---- + // \ ^ (inside) + if (p.indexw1 > p.indexw2 && (wi <= p.indexw2 || wi > p.indexw1)) + return true; + + // v / (outside) + // ---w1--2--1-------p----- + // w2 \ ^ (inside) + if (p.indexw1 == p.indexw2 && liesBefore(p.vertexw1, p.intersection1, p.intersection2) > 0) + return true; // This patch actually covers _all_ vertices on work + + return false; +} + +// Check if patch p1 makes patch p2 superfluous. +static bool isPatchCovered(const Patch &p1, const Patch &p2) { + + // Same exit and entry points + if (p1.intersection1 == p2.intersection1 && p1.intersection2 == p2.intersection2) + return true; + + // / * v (outside) + // ---p1w1--1----p2w1-1---p1w2--2---- + // ^ * \ (inside) + if (p1.indexw1 < p2.indexw1 && p2.indexw1 < p1.indexw2) + return true; + if (p1.indexw1 > p1.indexw2 && (p2.indexw1 > p1.indexw1 || p2.indexw1 < p1.indexw2)) + return true; + + + // / * v (outside) + // ---p1w1--11----p2w2-2---p1w2--12---- + // ^ * \ (inside) + if (p1.indexw1 < p2.indexw2 && p2.indexw2 < p1.indexw2) + return true; + if (p1.indexw1 > p1.indexw2 && (p2.indexw2 > p1.indexw1 || p2.indexw2 < p1.indexw2)) + return true; + + // Opposite of two above situations + if (p2.indexw1 < p1.indexw1 && p1.indexw1 < p2.indexw2) + return false; + if (p2.indexw1 > p2.indexw2 && (p1.indexw1 > p2.indexw1 || p1.indexw1 < p2.indexw2)) + return false; + + if (p2.indexw1 < p1.indexw2 && p1.indexw2 < p2.indexw2) + return false; + if (p2.indexw1 > p2.indexw2 && (p1.indexw2 > p2.indexw1 || p1.indexw2 < p2.indexw2)) + return false; + + + // The above checks covered the cases where one patch covers the other and + // the intersections of the patches are on different edges. + + // So, if we passed the above checks, we have to check the order of + // intersections on edges. + + + if (p1.indexw1 != p1.indexw2) { + + // / * v (outside) + // ---p1w1--11---21--------p1w2--2---- + // p2w1 ^ * \ (inside) + if (p1.indexw1 == p2.indexw1) + return (liesBefore(p1.vertexw1, p1.intersection1, p2.intersection1) < 0); + + // / * v (outside) + // ---p1w1--11---------p1w2--21---12---- + // ^ p2w1 * \ (inside) + if (p1.indexw2 == p2.indexw1) + return (liesBefore(p1.vertexw2, p1.intersection2, p2.intersection1) > 0); + + // If neither of the above, then the intervals of the polygon + // covered by patch1 and patch2 are disjoint + return false; + } + + // p1w1 == p1w2 + // Also, p1w1/p1w2 isn't strictly between p2 + + + // v / * (outside) + // ---p1w1--12--11-------p2w1-21---- + // p1w2 \ ^ * (inside) + + // v / / (outside) + // ---p1w1--12--21--11--------- + // p1w2 \ ^ ^ (inside) + // p2w1 + if (liesBefore(p1.vertexw1, p1.intersection1, p1.intersection2) > 0) + return (p1.indexw1 != p2.indexw1); + + // CHECKME: This is meaningless if p2w1 != p2w2 ?? + if (liesBefore(p2.vertexw1, p2.intersection1, p2.intersection2) > 0) + return false; + + // CHECKME: This is meaningless if p1w1 != p2w1 ?? + if (liesBefore(p2.vertexw1, p2.intersection1, p1.intersection1) <= 0) + return false; + + // CHECKME: This is meaningless if p1w2 != p2w1 ?? + if (liesBefore(p2.vertexw1, p2.intersection1, p1.intersection2) >= 0) + return false; + + return true; +} + +// Merge a single polygon into the work polygon. +// If there is an intersection between work and polygon, this function +// returns true, and replaces the vertex list of work by an extended version, +// that covers polygon. +// +// NOTE: The strategy used matches qfg1new closely, and is a bit error-prone. +// A more robust strategy would be inserting all intersection points directly +// into both vertex lists as a first pass. This would make finding the merged +// polygon a much more straightforward edge-walk, and avoid cases where SSCI's +// algorithm mixes up the order of multiple intersections on a single edge. +bool mergeSinglePolygon(Polygon &work, const Polygon &polygon) { +#ifdef DEBUG_MERGEPOLY + const Vertex *vertex; + debugN("work:"); + CLIST_FOREACH(vertex, &(work.vertices)) { + debugN(" (%d,%d) ", vertex->v.x, vertex->v.y); + } + debugN("\n"); + debugN("poly:"); + CLIST_FOREACH(vertex, &(polygon.vertices)) { + debugN(" (%d,%d) ", vertex->v.x, vertex->v.y); + } + debugN("\n"); +#endif + uint workSize = work.vertices.size(); + uint polygonSize = polygon.vertices.size(); + + int patchCount = 0; + Patch patchList[8]; + + const Vertex *workv = work.vertices._head; + const Vertex *polyv = polygon.vertices._head; + for (uint wi = 0; wi < workSize; ++wi, workv = workv->_next) { + for (uint pi = 0; pi < polygonSize; ++pi, polyv = polyv->_next) { + Common::Point intersection1; + Common::Point intersection2; + + bool intersects = segSegIntersect(workv, polyv, intersection1); + if (!intersects) + continue; + +#ifdef DEBUG_MERGEPOLY + debug("mergePoly: intersection at work %d, poly %d", wi, pi); +#endif + + if (intersectDir(workv, polyv) >= 0) + continue; + +#ifdef DEBUG_MERGEPOLY + debug("mergePoly: intersection in right direction"); +#endif + + int angle = 0; + int baseAngle = edgeDir(workv); + + // We now found the point where an edge of 'polygon' left 'work'. + // Now find the re-entry point. + + // NOTE: The order in which this searches does not always work + // properly if the correct patch would only use a single partial + // edge of poly. Because it starts at polyv->_next, it will skip + // the correct re-entry and proceed to the next. + + const Vertex *workv2; + const Vertex *polyv2 = polyv->_next; + + intersects = false; + + uint pi2, wi2; + for (pi2 = 0; pi2 < polygonSize; ++pi2, polyv2 = polyv2->_next) { + + int newAngle = edgeDir(polyv2); + + int relAngle = newAngle - baseAngle; + if (relAngle > 180) relAngle -= 360; + if (relAngle < -180) relAngle += 360; + + angle += relAngle; + baseAngle = newAngle; + + workv2 = workv; + for (wi2 = 0; wi2 < workSize; ++wi2, workv2 = workv2->_next) { + intersects = segSegIntersect(workv2, polyv2, intersection2); + if (!intersects) + continue; +#ifdef DEBUG_MERGEPOLY + debug("mergePoly: re-entry intersection at work %d, poly %d", (wi + wi2) % workSize, (pi + 1 + pi2) % polygonSize); +#endif + + if (intersectDir(workv2, polyv2) > 0) { +#ifdef DEBUG_MERGEPOLY + debug("mergePoly: re-entry intersection in right direction, angle = %d", angle); +#endif + break; // found re-entry point + } + + } + + if (intersects) + break; + + } + + if (!intersects || angle < 0) + continue; + + + if (patchCount >= 8) + error("kMergePoly: Too many patches"); + + // convert relative to absolute vertex indices + pi2 = (pi + 1 + pi2) % polygonSize; + wi2 = (wi + wi2) % workSize; + + Patch &newPatch = patchList[patchCount]; + newPatch.indexw1 = wi; + newPatch.vertexw1 = workv; + newPatch.indexp1 = pi; + newPatch.vertexp1 = polyv; + newPatch.intersection1 = intersection1; + + newPatch.indexw2 = wi2; + newPatch.vertexw2 = workv2; + newPatch.indexp2 = pi2; + newPatch.vertexp2 = polyv2; + newPatch.intersection2 = intersection2; + newPatch.disabled = false; + +#ifdef DEBUG_MERGEPOLY + debug("mergePoly: adding patch at work %d, poly %d", wi, pi); +#endif + + if (patchCount == 0) { + patchCount++; + continue; + } + + bool necessary = true; + for (int i = 0; i < patchCount; ++i) { + if (isPatchCovered(patchList[i], newPatch)) { + necessary = false; + break; + } + } + + if (!necessary) + continue; + + patchCount++; + + if (patchCount > 1) { + // check if this patch makes other patches superfluous + for (int i = 0; i < patchCount-1; ++i) + if (isPatchCovered(newPatch, patchList[i])) + patchList[i].disabled = true; + } + } + } + + + if (patchCount == 0) + return false; // nothing changed + + + // Determine merged work by doing a walk over the edges + // of work, crossing over to polygon when encountering a patch. + + Polygon output(0); + + workv = work.vertices._head; + for (uint wi = 0; wi < workSize; ++wi, workv = workv->_next) { + + bool covered = false; + for (int p = 0; p < patchCount; ++p) { + if (patchList[p].disabled) continue; + if (isVertexCovered(patchList[p], wi)) { + covered = true; + break; + } + } + + if (!covered) { + // Add vertex to output + output.vertices.insertAtEnd(new Vertex(workv->v)); + } + + + // CHECKME: Why is this the correct order in which to process + // the patches? (What if two of them start on this line segment + // in the opposite order?) + + for (int p = 0; p < patchCount; ++p) { + + const Patch &patch = patchList[p]; + if (patch.disabled) continue; + if (patch.indexw1 != wi) continue; + if (patch.intersection1 != workv->v) { + // Add intersection point to output + output.vertices.insertAtEnd(new Vertex(patch.intersection1)); + } + + // Add vertices from polygon between vertexp1 (excl) and vertexp2 (incl) + for (polyv = patch.vertexp1->_next; polyv != patch.vertexp2; polyv = polyv->_next) + output.vertices.insertAtEnd(new Vertex(polyv->v)); + + output.vertices.insertAtEnd(new Vertex(patch.vertexp2->v)); + + if (patch.intersection2 != patch.vertexp2->v) { + // Add intersection point to output + output.vertices.insertAtEnd(new Vertex(patch.intersection2)); + } + + // TODO: We could continue after the re-entry point here? + } + } + // Remove last vertex if it's the same as the first vertex + if (output.vertices._head->v == output.vertices._head->_prev->v) + output.vertices.remove(output.vertices._head->_prev); + + + // Slight hack: swap vertex lists of output and work polygons. + SWAP(output.vertices._head, work.vertices._head); + + return true; +} + + /** * This is a quite rare kernel function. An example of when it's called * is in QFG1VGA, after killing any monster. + * + * It takes a polygon, and extends it to also cover any polygons from the + * input list with which it intersects. Any of those polygons so covered + * from the input list are marked by adding 0x10 to their type field. */ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) { -#if 0 // 3 parameters: raw polygon data, polygon list, list size reg_t polygonData = argv[0]; List *list = s->_segMan->lookupList(argv[1]); - Node *node = s->_segMan->lookupNode(list->first); - // List size is not needed - Polygon *polygon; - int count = 0; + // The size of the "work" point list SSCI uses. We use a dynamic one instead + //reg_t listSize = argv[2]; + + SegmentRef pointList = s->_segMan->dereference(polygonData); + if (!pointList.isValid() || pointList.skipByte) { + warning("kMergePoly: Polygon data pointer is invalid"); + return make_reg(0, 0); + } + + Node *node; + +#ifdef DEBUG_MERGEPOLY + node = s->_segMan->lookupNode(list->first); + while (node) { + draw_polygon(s, node->value, 320, 190); + node = s->_segMan->lookupNode(node->succ); + } + Common::Point prev, first; + prev = first = readPoint(pointList, 0); + for (int i = 1; readPoint(pointList, i).x != 0x7777; i++) { + Common::Point point = readPoint(pointList, i); + draw_line(s, prev, point, 1, 320, 190); + prev = point; + } + draw_line(s, prev, first, 1, 320, 190); + // Update the whole screen + g_sci->_gfxScreen->copyToScreen(); + g_system->updateScreen(); + g_system->delayMillis(1000); +#endif + + // The work polygon which we're going to merge with the polygons in list + Polygon work(0); + + for (int i = 0; true; ++i) { + Common::Point p = readPoint(pointList, i); + if (p.x == POLY_LAST_POINT) + break; + Vertex *vertex = new Vertex(p); + work.vertices.insertAtEnd(vertex); + } + + // TODO: Check behaviour for single-vertex polygons + node = s->_segMan->lookupNode(list->first); while (node) { - polygon = convert_polygon(s, node->value); + Polygon *polygon = convert_polygon(s, node->value); if (polygon) { - count += readSelectorValue(s->_segMan, node->value, SELECTOR(size)); + // CHECKME: Confirm vertex order that convert_polygon and + // fix_vertex_order output. For now, we re-reverse the order since + // convert_polygon reads the vertices reversed, and fix up head. + polygon->vertices.reverse(); + polygon->vertices._head = polygon->vertices._head->_next; + + // Merge this polygon into the work polygon if there is an + // intersection. + bool intersected = mergeSinglePolygon(work, *polygon); + + // If so, flag it + if (intersected) { + writeSelectorValue(s->_segMan, node->value, + SELECTOR(type), polygon->type + 0x10); +#ifdef DEBUG_MERGEPOLY + debugN("Merged polygon: "); + // Iterate over edges + Vertex *vertex; + CLIST_FOREACH(vertex, &(work.vertices)) { + debugN(" (%d,%d) ", vertex->v.x, vertex->v.y); + } + debugN("\n"); +#endif + } } node = s->_segMan->lookupNode(node->succ); } -#endif - // TODO: actually merge the polygon. We return an empty polygon for now. - // In QFG1VGA, you can walk over enemy bodies after killing them, since - // this is a stub. - reg_t output = allocateOutputArray(s->_segMan, 1); + + // Allocate output array + reg_t output = allocateOutputArray(s->_segMan, work.vertices.size()+1); SegmentRef arrayRef = s->_segMan->dereference(output); - writePoint(arrayRef, 0, Common::Point(POLY_LAST_POINT, POLY_LAST_POINT)); - warning("Stub: kMergePoly"); + + // Copy work.vertices into arrayRef + Vertex *vertex; + unsigned int n = 0; + CLIST_FOREACH(vertex, &work.vertices) { + if (vertex == work.vertices._head || vertex->v != vertex->_prev->v) + writePoint(arrayRef, n++, vertex->v); + } + + writePoint(arrayRef, n, Common::Point(POLY_LAST_POINT, POLY_LAST_POINT)); + +#ifdef DEBUG_MERGEPOLY + prev = first = readPoint(arrayRef, 0); + for (int i = 1; readPoint(arrayRef, i).x != 0x7777; i++) { + Common::Point point = readPoint(arrayRef, i); + draw_line(s, prev, point, 3, 320, 190); + prev = point; + } + + draw_line(s, prev, first, 3, 320, 190); + + // Update the whole screen + g_sci->_gfxScreen->copyToScreen(); + g_system->updateScreen(); + if (!g_sci->_gfxPaint16) + g_system->delayMillis(1000); + + debug("kMergePoly done"); +#endif + return output; } diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index 9b0e490d7e..2c115be500 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -140,7 +140,7 @@ reg_t kClone(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelMemory, "Attempting to clone from %04x:%04x", PRINT_REG(parentAddr)); - uint16 infoSelector = parentObj->getInfoSelector().offset; + uint16 infoSelector = parentObj->getInfoSelector().getOffset(); cloneObj = s->_segMan->allocateClone(&cloneAddr); if (!cloneObj) { @@ -169,8 +169,8 @@ reg_t kClone(EngineState *s, int argc, reg_t *argv) { cloneObj->setSpeciesSelector(cloneObj->getPos()); if (parentObj->isClass()) cloneObj->setSuperClassSelector(parentObj->getPos()); - s->_segMan->getScript(parentObj->getPos().segment)->incrementLockers(); - s->_segMan->getScript(cloneObj->getPos().segment)->incrementLockers(); + s->_segMan->getScript(parentObj->getPos().getSegment())->incrementLockers(); + s->_segMan->getScript(cloneObj->getPos().getSegment())->incrementLockers(); return cloneAddr; } @@ -191,7 +191,7 @@ reg_t kDisposeClone(EngineState *s, int argc, reg_t *argv) { // At least kq4early relies on this behavior. The scripts clone "Sound", then set bit 1 manually // and call kDisposeClone later. In that case we may not free it, otherwise we will run into issues // later, because kIsObject would then return false and Sound object wouldn't get checked. - uint16 infoSelector = object->getInfoSelector().offset; + uint16 infoSelector = object->getInfoSelector().getOffset(); if ((infoSelector & 3) == kInfoFlagClone) object->markAsFreed(); @@ -203,7 +203,7 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { int script = argv[0].toUint16(); uint16 index = (argc > 1) ? argv[1].toUint16() : 0; - if (argv[0].segment) + if (argv[0].getSegment()) return argv[0]; SegmentId scriptSeg = s->_segMan->getScriptSegment(script, SCRIPT_GET_LOAD); @@ -226,11 +226,6 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } - if (index > scr->getExportsNr()) { - error("Dispatch index too big: %d > %d", index, scr->getExportsNr()); - return NULL_REG; - } - uint16 address = scr->validateExportFunc(index, true); // Point to the heap for SCI1.1 - SCI2.1 games @@ -251,12 +246,12 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { } reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv) { - int script = argv[0].offset; + int script = argv[0].getOffset(); SegmentId id = s->_segMan->getScriptSegment(script); Script *scr = s->_segMan->getScriptIfLoaded(id); if (scr && !scr->isMarkedAsDeleted()) { - if (s->_executionStack.back().addr.pc.segment != id) + if (s->_executionStack.back().addr.pc.getSegment() != id) scr->setLockers(1); } @@ -273,7 +268,7 @@ reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv) { } reg_t kIsObject(EngineState *s, int argc, reg_t *argv) { - if (argv[0].offset == SIGNAL_OFFSET) // Treated specially + if (argv[0].getOffset() == SIGNAL_OFFSET) // Treated specially return NULL_REG; else return make_reg(0, s->_segMan->isHeapObject(argv[0])); diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index c469f775f9..0633267db4 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -140,8 +140,14 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { ((argv[3].toUint16() & 0xff) << 16) | ((argv[4].toUint16() & 0xff) << 8) | (argv[5].toUint16() & 0xff); - if (argc == 8) - warning("kDoAudio: Play called with SQ6 extra parameters"); + // Removed warning because of the high amount of console spam + /*if (argc == 8) { + // TODO: Handle the extra 2 SCI21 params + // argv[6] is always 1 + // argv[7] is the contents of global 229 (0xE5) + warning("kDoAudio: Play called with SCI2.1 extra parameters: %04x:%04x and %04x:%04x", + PRINT_REG(argv[6]), PRINT_REG(argv[7])); + }*/ } else { warning("kDoAudio: Play called with an unknown number of parameters (%d)", argc); return NULL_REG; @@ -240,6 +246,11 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { // Used in Pharkas whenever a speech sample starts (takes no params) //warning("kDoAudio: Unhandled case 13, %d extra arguments passed", argc - 1); break; + case 17: + // Seems to be some sort of audio sync, used in SQ6. Silenced the + // warning due to the high level of spam it produces. (takes no params) + //warning("kDoAudio: Unhandled case 17, %d extra arguments passed", argc - 1); + break; default: warning("kDoAudio: Unhandled case %d, %d extra arguments passed", argv[0].toUint16(), argc - 1); } diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index fe8d631497..c4db0b891c 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -33,7 +33,7 @@ namespace Sci { reg_t kStrEnd(EngineState *s, int argc, reg_t *argv) { reg_t address = argv[0]; - address.offset += s->_segMan->strlen(address); + address.incOffset(s->_segMan->strlen(address)); return address; } @@ -96,7 +96,7 @@ reg_t kStrAt(EngineState *s, int argc, reg_t *argv) { byte value; byte newvalue = 0; - unsigned int offset = argv[1].toUint16(); + uint16 offset = argv[1].toUint16(); if (argc > 2) newvalue = argv[2].toSint16(); @@ -123,18 +123,22 @@ reg_t kStrAt(EngineState *s, int argc, reg_t *argv) { oddOffset = !oddOffset; if (!oddOffset) { - value = tmp.offset & 0x00ff; + value = tmp.getOffset() & 0x00ff; if (argc > 2) { /* Request to modify this char */ - tmp.offset &= 0xff00; - tmp.offset |= newvalue; - tmp.segment = 0; + uint16 tmpOffset = tmp.toUint16(); + tmpOffset &= 0xff00; + tmpOffset |= newvalue; + tmp.setOffset(tmpOffset); + tmp.setSegment(0); } } else { - value = tmp.offset >> 8; + value = tmp.getOffset() >> 8; if (argc > 2) { /* Request to modify this char */ - tmp.offset &= 0x00ff; - tmp.offset |= newvalue << 8; - tmp.segment = 0; + uint16 tmpOffset = tmp.toUint16(); + tmpOffset &= 0x00ff; + tmpOffset |= newvalue << 8; + tmp.setOffset(tmpOffset); + tmp.setSegment(0); } } } @@ -161,6 +165,7 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { // do clipping. In SQ4 we get the door code in here and that's even // larger than uint32! if (*source == '-') { + // FIXME: Setting result to -1 does _not_ negate the output. result = -1; source++; } @@ -204,7 +209,7 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { int strLength = 0; /* Used for stuff like "%13s" */ bool unsignedVar = false; - if (position.segment) + if (position.getSegment()) startarg = 2; else { // WORKAROUND: QFG1 VGA Mac calls this without the first parameter (dest). It then @@ -291,7 +296,7 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { if (extralen < 0) extralen = 0; - if (reg.segment) /* Heap address? */ + if (reg.getSegment()) /* Heap address? */ paramindex++; else paramindex += 2; /* No, text resource address */ @@ -653,10 +658,16 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) { case 1: // Size return make_reg(0, s->_segMan->getString(argv[1]).size()); case 2: { // At (return value at an index) - if (argv[1].segment == s->_segMan->getStringSegmentId()) - return make_reg(0, s->_segMan->lookupString(argv[1])->getRawData()[argv[2].toUint16()]); - - return make_reg(0, s->_segMan->getString(argv[1])[argv[2].toUint16()]); + // Note that values are put in bytes to avoid sign extension + if (argv[1].getSegment() == s->_segMan->getStringSegmentId()) { + SciString *string = s->_segMan->lookupString(argv[1]); + byte val = string->getRawData()[argv[2].toUint16()]; + return make_reg(0, val); + } else { + Common::String string = s->_segMan->getString(argv[1]); + byte val = string[argv[2].toUint16()]; + return make_reg(0, val); + } } case 3: { // Atput (put value at an index) SciString *string = s->_segMan->lookupString(argv[1]); @@ -699,7 +710,7 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) { uint32 string2Size = 0; Common::String string; - if (argv[3].segment == s->_segMan->getStringSegmentId()) { + if (argv[3].getSegment() == s->_segMan->getStringSegmentId()) { SciString *sstr; sstr = s->_segMan->lookupString(argv[3]); string2 = sstr->getRawData(); @@ -749,7 +760,7 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) { SciString *dupString = s->_segMan->allocateString(&stringHandle); - if (argv[1].segment == s->_segMan->getStringSegmentId()) { + if (argv[1].getSegment() == s->_segMan->getStringSegmentId()) { *dupString = *s->_segMan->lookupString(argv[1]); } else { dupString->fromString(s->_segMan->getString(argv[1])); diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index c9cf652013..9b0cb38f51 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -32,6 +32,7 @@ #include "common/str.h" #include "common/system.h" #include "common/textconsole.h" +#include "graphics/palette.h" #include "graphics/pixelformat.h" #include "graphics/surface.h" #include "video/video_decoder.h" @@ -49,6 +50,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { if (!videoDecoder) return; + videoDecoder->start(); + byte *scaleBuffer = 0; byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel; uint16 width = videoDecoder->getWidth(); @@ -86,9 +89,12 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { } bool skipVideo = false; + EngineState *s = g_sci->getEngineState(); - if (videoDecoder->hasDirtyPalette()) - videoDecoder->setSystemPalette(); + if (videoDecoder->hasDirtyPalette()) { + const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; + g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + } while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { if (videoDecoder->needsUpdate()) { @@ -100,11 +106,13 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { g_sci->_gfxScreen->scale2x((byte *)frame->pixels, scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight(), bytesPerPixel); g_system->copyRectToScreen(scaleBuffer, pitch, x, y, width, height); } else { - g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, width, height); + g_system->copyRectToScreen(frame->pixels, frame->pitch, x, y, width, height); } - if (videoDecoder->hasDirtyPalette()) - videoDecoder->setSystemPalette(); + if (videoDecoder->hasDirtyPalette()) { + const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; + g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + } g_system->updateScreen(); } @@ -135,7 +143,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { Video::VideoDecoder *videoDecoder = 0; - if (argv[0].segment != 0) { + if (argv[0].getSegment() != 0) { Common::String filename = s->_segMan->getString(argv[0]); if (g_sci->getPlatform() == Common::kPlatformMacintosh) { @@ -156,9 +164,8 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { } else { // DOS SEQ // SEQ's are called with no subops, just the string and delay - SeqDecoder *seqDecoder = new SeqDecoder(); - seqDecoder->setFrameDelay(argv[1].toUint16()); // Time between frames in ticks - videoDecoder = seqDecoder; + // Time is specified as ticks + videoDecoder = new SEQDecoder(argv[1].toUint16()); if (!videoDecoder->loadFile(filename)) { warning("Failed to open movie file %s", filename.c_str()); @@ -184,7 +191,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: { Common::String filename = s->_segMan->getString(argv[1]); - videoDecoder = new Video::AviDecoder(g_system->getMixer()); + videoDecoder = new Video::AVIDecoder(); if (filename.equalsIgnoreCase("gk2a.avi")) { // HACK: Switch to 16bpp graphics for Indeo3. @@ -203,6 +210,8 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { warning("Failed to open movie file %s", filename.c_str()); delete videoDecoder; videoDecoder = 0; + } else { + s->_videoState.fileName = filename; } break; } @@ -244,6 +253,7 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { int16 y = argv[5].toUint16(); warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y); g_sci->_robotDecoder->load(id); + g_sci->_robotDecoder->start(); g_sci->_robotDecoder->setPos(x, y); } break; @@ -259,12 +269,13 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { warning("kRobot(%d)", subop); break; case 8: // sync - if ((uint32)g_sci->_robotDecoder->getCurFrame() != g_sci->_robotDecoder->getFrameCount() - 1) { - writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG); - } else { + //if (true) { // debug: automatically skip all robot videos + if (g_sci->_robotDecoder->endOfVideo()) { g_sci->_robotDecoder->close(); // Signal the engine scripts that the video is done writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG); + } else { + writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG); } break; default: @@ -302,7 +313,7 @@ reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { // with subfx 21. The subtleness has to do with creation of temporary // planes and positioning relative to such planes. - uint16 flags = argv[3].offset; + uint16 flags = argv[3].getOffset(); Common::String flagspec; if (argc > 3) { @@ -330,16 +341,22 @@ reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { s->_videoState.flags = flags; } - warning("x, y: %d, %d", argv[1].offset, argv[2].offset); - s->_videoState.x = argv[1].offset; - s->_videoState.y = argv[2].offset; + warning("x, y: %d, %d", argv[1].getOffset(), argv[2].getOffset()); + s->_videoState.x = argv[1].getOffset(); + s->_videoState.y = argv[2].getOffset(); if (argc > 4 && flags & 16) - warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].offset, argv[5].offset, argv[6].offset); + warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].getOffset(), argv[5].getOffset(), argv[6].getOffset()); break; } case 6: // Play - videoDecoder = new Video::VMDDecoder(g_system->getMixer()); + videoDecoder = new Video::AdvancedVMDDecoder(); + + if (s->_videoState.fileName.empty()) { + // Happens in Lighthouse + warning("kPlayVMD: Empty filename passed"); + return s->r_acc; + } if (!videoDecoder->loadFile(s->_videoState.fileName)) { warning("Could not open VMD %s", s->_videoState.fileName.c_str()); @@ -354,6 +371,10 @@ reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { if (reshowCursor) g_sci->_gfxCursor->kernelShow(); break; + case 23: // set video palette range + s->_vmdPalStart = argv[1].toUint16(); + s->_vmdPalEnd = argv[2].toUint16(); + break; case 14: // Takes an additional integer parameter (e.g. 3) case 16: @@ -387,7 +408,7 @@ reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv) { s->_videoState.reset(); s->_videoState.fileName = Common::String::format("%d.duk", argv[1].toUint16()); - videoDecoder = new Video::AviDecoder(g_system->getMixer()); + videoDecoder = new Video::AVIDecoder(); if (!videoDecoder->loadFile(s->_videoState.fileName)) { warning("Could not open Duck %s", s->_videoState.fileName.c_str()); diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index cddd01e10c..a92d572d35 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -400,11 +400,21 @@ Common::String MessageState::processString(const char *s) { void MessageState::outputString(reg_t buf, const Common::String &str) { #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { - SciString *sciString = _segMan->lookupString(buf); - sciString->setSize(str.size() + 1); - for (uint32 i = 0; i < str.size(); i++) - sciString->setValue(i, str.c_str()[i]); - sciString->setValue(str.size(), 0); + if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_STRING) { + SciString *sciString = _segMan->lookupString(buf); + sciString->setSize(str.size() + 1); + for (uint32 i = 0; i < str.size(); i++) + sciString->setValue(i, str.c_str()[i]); + sciString->setValue(str.size(), 0); + } else if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_ARRAY) { + // Happens in the intro of LSL6, we are asked to write the string + // into an array + SciArray<reg_t> *sciString = _segMan->lookupArray(buf); + sciString->setSize(str.size() + 1); + for (uint32 i = 0; i < str.size(); i++) + sciString->setValue(i, make_reg(0, str.c_str()[i])); + sciString->setValue(str.size(), NULL_REG); + } } else { #endif SegmentRef buffer_r = _segMan->dereference(buf); diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp index 78e216cdb5..b28026b71f 100644 --- a/engines/sci/engine/object.cpp +++ b/engines/sci/engine/object.cpp @@ -44,15 +44,15 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); return false; } - block[idx].segment = segment; // Perform relocation + block[idx].setSegment(segment); // Perform relocation if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) - block[idx].offset += scriptSize; + block[idx].incOffset(scriptSize); return true; } void Object::init(byte *buf, reg_t obj_pos, bool initVariables) { - byte *data = buf + obj_pos.offset; + byte *data = buf + obj_pos.getOffset(); _baseObj = data; _pos = obj_pos; @@ -109,16 +109,16 @@ int Object::locateVarSelector(SegManager *segMan, Selector slc) const { } bool Object::relocateSci0Sci21(SegmentId segment, int location, size_t scriptSize) { - return relocateBlock(_variables, getPos().offset, segment, location, scriptSize); + return relocateBlock(_variables, getPos().getOffset(), segment, location, scriptSize); } -bool Object::relocateSci3(SegmentId segment, int location, int offset, size_t scriptSize) { +bool Object::relocateSci3(SegmentId segment, uint32 location, int offset, size_t scriptSize) { assert(_propertyOffsetsSci3); for (uint i = 0; i < _variables.size(); ++i) { if (location == _propertyOffsetsSci3[i]) { - _variables[i].segment = segment; - _variables[i].offset += offset; + _variables[i].setSegment(segment); + _variables[i].incOffset(offset); return true; } } @@ -148,21 +148,21 @@ int Object::propertyOffsetToId(SegManager *segMan, int propertyOffset) const { } void Object::initSpecies(SegManager *segMan, reg_t addr) { - uint16 speciesOffset = getSpeciesSelector().offset; + uint16 speciesOffset = getSpeciesSelector().getOffset(); if (speciesOffset == 0xffff) // -1 setSpeciesSelector(NULL_REG); // no species else - setSpeciesSelector(segMan->getClassAddress(speciesOffset, SCRIPT_GET_LOCK, addr)); + setSpeciesSelector(segMan->getClassAddress(speciesOffset, SCRIPT_GET_LOCK, addr.getSegment())); } void Object::initSuperClass(SegManager *segMan, reg_t addr) { - uint16 superClassOffset = getSuperClassSelector().offset; + uint16 superClassOffset = getSuperClassSelector().getOffset(); if (superClassOffset == 0xffff) // -1 setSuperClassSelector(NULL_REG); // no superclass else - setSuperClassSelector(segMan->getClassAddress(superClassOffset, SCRIPT_GET_LOCK, addr)); + setSuperClassSelector(segMan->getClassAddress(superClassOffset, SCRIPT_GET_LOCK, addr.getSegment())); } bool Object::initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass) { @@ -187,7 +187,7 @@ bool Object::initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClas // The effect is that a number of its method selectors may be // treated as variable selectors, causing unpredictable effects. - int objScript = segMan->getScript(_pos.segment)->getScriptNumber(); + int objScript = segMan->getScript(_pos.getSegment())->getScriptNumber(); // We have to do a little bit of work to get the name of the object // before any relocations are done. @@ -196,7 +196,7 @@ bool Object::initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClas if (nameReg.isNull()) { name = "<no name>"; } else { - nameReg.segment = _pos.segment; + nameReg.setSegment(_pos.getSegment()); name = segMan->derefString(nameReg); if (!name) name = "<invalid name>"; @@ -286,7 +286,7 @@ void Object::initSelectorsSci3(const byte *buf) { _variables.resize(properties); uint16 *propertyIds = (uint16 *)malloc(sizeof(uint16) * properties); // uint16 *methodOffsets = (uint16 *)malloc(sizeof(uint16) * 2 * methods); - uint16 *propertyOffsets = (uint16 *)malloc(sizeof(uint16) * properties); + uint32 *propertyOffsets = (uint32 *)malloc(sizeof(uint32) * properties); int propertyCounter = 0; int methodCounter = 0; @@ -314,7 +314,8 @@ void Object::initSelectorsSci3(const byte *buf) { WRITE_SCI11ENDIAN_UINT16(&propertyIds[propertyCounter], groupBaseId + bit); _variables[propertyCounter] = make_reg(0, value); - propertyOffsets[propertyCounter] = (seeker + bit * 2) - buf; + uint32 propertyOffset = (seeker + bit * 2) - buf; + propertyOffsets[propertyCounter] = propertyOffset; ++propertyCounter; } else if (value != 0xffff) { // Method _baseMethod.push_back(groupBaseId + bit); diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index 0ca16b48a2..1ea9ae449a 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -168,7 +168,7 @@ public: uint16 offset = (getSciVersion() < SCI_VERSION_1_1) ? _methodCount + 1 + i : i * 2 + 2; if (getSciVersion() == SCI_VERSION_3) offset--; - return make_reg(_pos.segment, _baseMethod[offset]); + return make_reg(_pos.getSegment(), _baseMethod[offset]); } Selector getFuncSelector(uint16 i) const { @@ -198,7 +198,7 @@ public: */ int locateVarSelector(SegManager *segMan, Selector slc) const; - bool isClass() const { return (getInfoSelector().offset & kInfoFlagClass); } + bool isClass() const { return (getInfoSelector().getOffset() & kInfoFlagClass); } const Object *getClass(SegManager *segMan) const; void markAsFreed() { _flags |= OBJECT_FLAG_FREED; } @@ -223,7 +223,7 @@ public: } bool relocateSci0Sci21(SegmentId segment, int location, size_t scriptSize); - bool relocateSci3(SegmentId segment, int location, int offset, size_t scriptSize); + bool relocateSci3(SegmentId segment, uint32 location, int offset, size_t scriptSize); int propertyOffsetToId(SegManager *segMan, int propertyOffset) const; @@ -238,7 +238,7 @@ private: const byte *_baseObj; /**< base + object offset within base */ const uint16 *_baseVars; /**< Pointer to the varselector area for this object */ Common::Array<uint16> _baseMethod; /**< Pointer to the method selector area for this object */ - uint16 *_propertyOffsetsSci3; /**< This is used to enable relocation of property valuesa in SCI3 */ + uint32 *_propertyOffsetsSci3; /**< This is used to enable relocation of property valuesa in SCI3 */ Common::Array<reg_t> _variables; uint16 _methodCount; diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 404bea799d..ff3f19b53d 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -115,8 +115,9 @@ void syncArray(Common::Serializer &s, Common::Array<T> &arr) { template<> void syncWithSerializer(Common::Serializer &s, reg_t &obj) { - s.syncAsUint16LE(obj.segment); - s.syncAsUint16LE(obj.offset); + // Segment and offset are accessed directly here + s.syncAsUint16LE(obj._segment); + s.syncAsUint16LE(obj._offset); } template<> @@ -189,7 +190,7 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { assert(mobj); - // Let the object sync custom data + // Let the object sync custom data. Scripts are loaded at this point. mobj->saveLoadWithSerializer(s); if (type == SEG_TYPE_SCRIPT) { @@ -200,12 +201,9 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { // Hook the script up in the script->segment map _scriptSegMap[scr->getScriptNumber()] = i; - // Now, load the script itself - scr->load(g_sci->getResMan()); - ObjMap objects = scr->getObjectMap(); for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it) - it->_value.syncBaseObject(scr->getBuf(it->_value.getPos().offset)); + it->_value.syncBaseObject(scr->getBuf(it->_value.getPos().getOffset())); } @@ -467,7 +465,7 @@ void Script::syncStringHeap(Common::Serializer &s) { break; } while (1); - } else { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1){ // Strings in SCI1.1 come after the object instances byte *buf = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; @@ -477,6 +475,8 @@ void Script::syncStringHeap(Common::Serializer &s) { // Now, sync everything till the end of the buffer s.syncBytes(buf, _heapSize - (buf - _heapStart)); + } else if (getSciVersion() == SCI_VERSION_3) { + warning("TODO: syncStringHeap(): Implement SCI3 variant"); } } @@ -484,7 +484,7 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(_nr); if (s.isLoading()) - init(_nr, g_sci->getResMan()); + load(_nr, g_sci->getResMan()); s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _bufSize s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _scriptSize s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _heapSize @@ -508,7 +508,7 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) { Object tmp; for (uint i = 0; i < numObjs; ++i) { syncWithSerializer(s, tmp); - _objects[tmp.getPos().offset] = tmp; + _objects[tmp.getPos().getOffset()] = tmp; } } else { ObjMap::iterator it; @@ -817,7 +817,7 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false); meta.script0Size = script0->size; - meta.gameObjectOffset = g_sci->getGameObject().offset; + meta.gameObjectOffset = g_sci->getGameObject().getOffset(); // Checking here again if (s->executionStackBase) { @@ -868,7 +868,7 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (meta.gameObjectOffset > 0 && meta.script0Size > 0) { Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false); - if (script0->size != meta.script0Size || g_sci->getGameObject().offset != meta.gameObjectOffset) { + if (script0->size != meta.script0Size || g_sci->getGameObject().getOffset() != meta.gameObjectOffset) { //warning("This saved game was created with a different version of the game, unable to load it"); showScummVMDialog("This saved game was created with a different version of the game, unable to load it"); diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 8b26969f4a..36d2841b07 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -32,58 +32,48 @@ namespace Sci { -Script::Script() : SegmentObj(SEG_TYPE_SCRIPT) { +Script::Script() : SegmentObj(SEG_TYPE_SCRIPT), _buf(NULL) { + freeScript(); +} + +Script::~Script() { + freeScript(); +} + +void Script::freeScript() { _nr = 0; + + free(_buf); _buf = NULL; _bufSize = 0; _scriptSize = 0; + _heapStart = NULL; _heapSize = 0; - _synonyms = NULL; - _heapStart = NULL; _exportTable = NULL; + _numExports = 0; + _synonyms = NULL; + _numSynonyms = 0; _localsOffset = 0; _localsSegment = 0; _localsBlock = NULL; _localsCount = 0; + _lockers = 1; _markedAsDeleted = false; + _objects.clear(); } -Script::~Script() { +void Script::load(int script_nr, ResourceManager *resMan) { freeScript(); -} - -void Script::freeScript() { - free(_buf); - _buf = NULL; - _bufSize = 0; - - _objects.clear(); -} -void Script::init(int script_nr, ResourceManager *resMan) { Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0); - if (!script) error("Script %d not found", script_nr); - _localsOffset = 0; - _localsBlock = NULL; - _localsCount = 0; - - _markedAsDeleted = false; - _nr = script_nr; - _buf = 0; - _heapStart = 0; - - _scriptSize = script->size; - _bufSize = script->size; - _heapSize = 0; - - _lockers = 1; + _bufSize = _scriptSize = script->size; if (getSciVersion() == SCI_VERSION_0_EARLY) { _bufSize += READ_LE_UINT16(script->data) * 2; @@ -115,16 +105,18 @@ void Script::init(int script_nr, ResourceManager *resMan) { // scheme. We need an overlaying mechanism, or a mechanism to split script parts // in different segments to handle these. For now, simply stop when such a script // is found. + // + // Known large SCI 3 scripts are: + // Lighthouse: 9, 220, 270, 351, 360, 490, 760, 765, 800 + // LSL7: 240, 511, 550 + // Phantasmagoria 2: none (hooray!) + // RAMA: 70 + // // TODO: Remove this once such a mechanism is in place if (script->size > 65535) error("TODO: SCI script %d is over 64KB - it's %d bytes long. This can't " "be handled at the moment, thus stopping", script_nr, script->size); } -} - -void Script::load(ResourceManager *resMan) { - Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0); - assert(script != 0); uint extraLocalsWorkaround = 0; if (g_sci->getGameId() == GID_FANMADE && _nr == 1 && script->size == 11140) { @@ -152,15 +144,10 @@ void Script::load(ResourceManager *resMan) { _heapStart = _buf + _scriptSize; - assert(_bufSize - _scriptSize <= heap->size); + assert(_bufSize - _scriptSize >= heap->size); memcpy(_heapStart, heap->data, heap->size); } - _exportTable = 0; - _numExports = 0; - _synonyms = 0; - _numSynonyms = 0; - if (getSciVersion() <= SCI_VERSION_1_LATE) { _exportTable = (const uint16 *)findBlockSCI0(SCI_OBJ_EXPORTS); if (_exportTable) { @@ -212,7 +199,7 @@ void Script::load(ResourceManager *resMan) { _localsOffset = 0; if (_localsOffset + _localsCount * 2 + 1 >= (int)_bufSize) { - error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, _bufSize); + error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, (int)_bufSize); //_localsCount = (_bufSize - _localsOffset) >> 1; } } @@ -252,14 +239,14 @@ const Object *Script::getObject(uint16 offset) const { Object *Script::scriptObjInit(reg_t obj_pos, bool fullObjectInit) { if (getSciVersion() < SCI_VERSION_1_1 && fullObjectInit) - obj_pos.offset += 8; // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) + obj_pos.incOffset(8); // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) - if (obj_pos.offset >= _bufSize) + if (obj_pos.getOffset() >= _bufSize) error("Attempt to initialize object beyond end of script"); // Get the object at the specified position and init it. This will // automatically "allocate" space for it in the _objects map if necessary. - Object *obj = &_objects[obj_pos.offset]; + Object *obj = &_objects[obj_pos.getOffset()]; obj->init(_buf, obj_pos, fullObjectInit); return obj; @@ -282,9 +269,9 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); return false; } - block[idx].segment = segment; // Perform relocation + block[idx].setSegment(segment); // Perform relocation if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) - block[idx].offset += scriptSize; + block[idx].incOffset(scriptSize); return true; } @@ -323,23 +310,23 @@ void Script::relocateSci0Sci21(reg_t block) { heapOffset = _scriptSize; } - if (block.offset >= (uint16)heapSize || - READ_SCI11ENDIAN_UINT16(heap + block.offset) * 2 + block.offset >= (uint16)heapSize) + if (block.getOffset() >= (uint16)heapSize || + READ_SCI11ENDIAN_UINT16(heap + block.getOffset()) * 2 + block.getOffset() >= (uint16)heapSize) error("Relocation block outside of script"); - int count = READ_SCI11ENDIAN_UINT16(heap + block.offset); + int count = READ_SCI11ENDIAN_UINT16(heap + block.getOffset()); int exportIndex = 0; int pos = 0; for (int i = 0; i < count; i++) { - pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset; + pos = READ_SCI11ENDIAN_UINT16(heap + block.getOffset() + 2 + (exportIndex * 2)) + heapOffset; // This occurs in SCI01/SCI1 games where usually one export value is // zero. It seems that in this situation, we should skip the export and // move to the next one, though the total count of valid exports remains // the same if (!pos) { exportIndex++; - pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset; + pos = READ_SCI11ENDIAN_UINT16(heap + block.getOffset() + 2 + (exportIndex * 2)) + heapOffset; if (!pos) error("Script::relocate(): Consecutive zero exports found"); } @@ -348,12 +335,12 @@ void Script::relocateSci0Sci21(reg_t block) { // We only relocate locals and objects here, and ignore relocation of // code blocks. In SCI1.1 and newer versions, only locals and objects // are relocated. - if (!relocateLocal(block.segment, pos)) { + if (!relocateLocal(block.getSegment(), pos)) { // Not a local? It's probably an object or code block. If it's an // object, relocate it. const ObjMap::iterator end = _objects.end(); for (ObjMap::iterator it = _objects.begin(); it != end; ++it) - if (it->_value.relocateSci0Sci21(block.segment, pos, _scriptSize)) + if (it->_value.relocateSci0Sci21(block.getSegment(), pos, _scriptSize)) break; } @@ -370,7 +357,7 @@ void Script::relocateSci3(reg_t block) { const byte *seeker = relocStart; while (seeker < _buf + _bufSize) { // TODO: Find out what UINT16 at (seeker + 8) means - it->_value.relocateSci3(block.segment, + it->_value.relocateSci3(block.getSegment(), READ_SCI11ENDIAN_UINT32(seeker), READ_SCI11ENDIAN_UINT32(seeker + 4), _scriptSize); @@ -398,7 +385,7 @@ void Script::setLockers(int lockers) { _lockers = lockers; } -uint16 Script::validateExportFunc(int pubfunct, bool relocate) { +uint32 Script::validateExportFunc(int pubfunct, bool relocSci3) { bool exportsAreWide = (g_sci->_features->detectLofsType() == SCI_VERSION_1_MIDDLE); if (_numExports <= pubfunct) { @@ -409,17 +396,17 @@ uint16 Script::validateExportFunc(int pubfunct, bool relocate) { if (exportsAreWide) pubfunct *= 2; - uint16 offset; + uint32 offset; - if (getSciVersion() != SCI_VERSION_3 || !relocate) { + if (getSciVersion() != SCI_VERSION_3) { offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct); } else { - offset = relocateOffsetSci3(pubfunct * 2 + 22); + if (!relocSci3) + offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct) + getCodeBlockOffsetSci3(); + else + offset = relocateOffsetSci3(pubfunct * 2 + 22); } - if (offset >= _bufSize) - error("Invalid export function pointer"); - // Check if the offset found points to a second export table (e.g. script 912 // in Camelot and script 306 in KQ4). Such offsets are usually small (i.e. < 10), // thus easily distinguished from actual code offsets. @@ -432,11 +419,16 @@ uint16 Script::validateExportFunc(int pubfunct, bool relocate) { if (secondExportTable) { secondExportTable += 3; // skip header plus 2 bytes (secondExportTable is a uint16 pointer) offset = READ_SCI11ENDIAN_UINT16(secondExportTable + pubfunct); - if (offset >= _bufSize) - error("Invalid export function pointer"); } } + // Note that it's perfectly normal to return a zero offset, especially in + // SCI1.1 and newer games. Examples include script 64036 in Torin's Passage, + // script 64908 in the demo of RAMA and script 1013 in KQ6 floppy. + + if (offset >= _bufSize) + error("Invalid export function pointer"); + return offset; } @@ -479,7 +471,7 @@ bool Script::isValidOffset(uint16 offset) const { } SegmentRef Script::dereference(reg_t pointer) { - if (pointer.offset > _bufSize) { + if (pointer.getOffset() > _bufSize) { error("Script::dereference(): Attempt to dereference invalid pointer %04x:%04x into script segment (script size=%d)", PRINT_REG(pointer), (uint)_bufSize); return SegmentRef(); @@ -487,8 +479,8 @@ SegmentRef Script::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = true; - ret.maxSize = _bufSize - pointer.offset; - ret.raw = _buf + pointer.offset; + ret.maxSize = _bufSize - pointer.getOffset(); + ret.raw = _buf + pointer.getOffset(); return ret; } @@ -553,7 +545,7 @@ void Script::initializeClasses(SegManager *segMan) { uint16 marker; bool isClass = false; - uint16 classpos; + uint32 classpos; int16 species = 0; while (true) { @@ -564,7 +556,7 @@ void Script::initializeClasses(SegManager *segMan) { if (getSciVersion() <= SCI_VERSION_1_LATE && !marker) break; - if (getSciVersion() >= SCI_VERSION_1_1 && marker != 0x1234) + if (getSciVersion() >= SCI_VERSION_1_1 && marker != SCRIPT_OBJECT_MAGIC_NUMBER) break; if (getSciVersion() <= SCI_VERSION_1_LATE) { @@ -666,7 +658,7 @@ void Script::initializeObjectsSci11(SegManager *segMan, SegmentId segmentId) { // Copy base from species class, as we need its selector IDs obj->setSuperClassSelector( - segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); + segMan->getClassAddress(obj->getSuperClassSelector().getOffset(), SCRIPT_GET_LOCK, 0)); // If object is instance, get -propDict- from class and set it for this // object. This is needed for ::isMemberOf() to work. @@ -699,7 +691,7 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) { reg_t reg = make_reg(segmentId, seeker - _buf); Object *obj = scriptObjInit(reg); - obj->setSuperClassSelector(segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); + obj->setSuperClassSelector(segMan->getClassAddress(obj->getSuperClassSelector().getOffset(), SCRIPT_GET_LOCK, 0)); seeker += READ_SCI11ENDIAN_UINT16(seeker + 2); } @@ -716,7 +708,7 @@ void Script::initializeObjects(SegManager *segMan, SegmentId segmentId) { } reg_t Script::findCanonicAddress(SegManager *segMan, reg_t addr) const { - addr.offset = 0; + addr.setOffset(0); return addr; } @@ -738,8 +730,8 @@ Common::Array<reg_t> Script::listAllDeallocatable(SegmentId segId) const { Common::Array<reg_t> Script::listAllOutgoingReferences(reg_t addr) const { Common::Array<reg_t> tmp; - if (addr.offset <= _bufSize && addr.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && RAW_IS_OBJECT(_buf + addr.offset)) { - const Object *obj = getObject(addr.offset); + if (addr.getOffset() <= _bufSize && addr.getOffset() >= (uint)-SCRIPT_OBJECT_MAGIC_OFFSET && offsetIsObject(addr.getOffset())) { + const Object *obj = getObject(addr.getOffset()); if (obj) { // Note all local variables, if we have a local variable environment if (_localsSegment) @@ -774,4 +766,8 @@ Common::Array<reg_t> Script::listObjectReferences() const { return tmp; } +bool Script::offsetIsObject(uint16 offset) const { + return (READ_SCI11ENDIAN_UINT16((const byte *)_buf + offset + SCRIPT_OBJECT_MAGIC_OFFSET) == SCRIPT_OBJECT_MAGIC_NUMBER); +} + } // End of namespace Sci diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h index 1ebae3b7a8..0b499203c2 100644 --- a/engines/sci/engine/script.h +++ b/engines/sci/engine/script.h @@ -57,7 +57,7 @@ private: int _lockers; /**< Number of classes and objects that require this script */ size_t _scriptSize; size_t _heapSize; - uint16 _bufSize; + size_t _bufSize; const uint16 *_exportTable; /**< Abs. offset of the export table or 0 if not present */ uint16 _numExports; /**< Number of entries in the exports table */ @@ -89,14 +89,14 @@ public: void syncLocalsBlock(SegManager *segMan); ObjMap &getObjectMap() { return _objects; } const ObjMap &getObjectMap() const { return _objects; } + bool offsetIsObject(uint16 offset) const; public: Script(); ~Script(); void freeScript(); - void init(int script_nr, ResourceManager *resMan); - void load(ResourceManager *resMan); + void load(int script_nr, ResourceManager *resMan); void matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uint32 scriptSize); int32 findSignature(const SciScriptSignature *signature, const byte *scriptData, const uint32 scriptSize); @@ -197,11 +197,11 @@ public: * Validate whether the specified public function is exported by * the script in the specified segment. * @param pubfunct Index of the function to validate - * @param relocate Decide whether to relocate this public function or not + * @param relocSci3 Decide whether to relocate this SCI3 public function or not * @return NULL if the public function is invalid, its * offset into the script's segment otherwise */ - uint16 validateExportFunc(int pubfunct, bool relocate); + uint32 validateExportFunc(int pubfunct, bool relocSci3); /** * Marks the script as deleted. @@ -249,7 +249,7 @@ public: /** * Gets an offset to the beginning of the code block in a SCI3 script */ - int getCodeBlockOffset() { return READ_SCI11ENDIAN_UINT32(_buf); } + int getCodeBlockOffsetSci3() { return READ_SCI11ENDIAN_UINT32(_buf); } private: /** diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 187f1ce021..8454be514a 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -775,7 +775,7 @@ const uint16 mothergoose256PatchReplay[] = { PATCH_END }; -// when saving, it also checks if the savegame-id is below 13. +// when saving, it also checks if the savegame ID is below 13. // we change this to check if below 113 instead const byte mothergoose256SignatureSaveLimit[] = { 5, @@ -915,9 +915,53 @@ const uint16 qfg3PatchImportDialog[] = { PATCH_END }; + + +// =========================================================================== +// Patch for the Woo dialog option in Uhura's conversation. Bug #3040722 +// Problem: The Woo dialog option (0xffb5) is negative, and therefore +// treated as an option opening a submenu. This leads to uhuraTell::doChild +// being called, which calls hero::solvePuzzle and then proceeds with +// Teller::doChild to open the submenu. However, there is no actual submenu +// defined for option -75 since -75 does not show up in uhuraTell::keys. +// This will cause Teller::doChild to run out of bounds while scanning through +// uhuraTell::keys. +// Strategy: there is another conversation option in uhuraTell::doChild calling +// hero::solvePuzzle (0xfffc) which does a ret afterwards without going to +// Teller::doChild. We jump to this call of hero::solvePuzzle to get that same +// behaviour. + +const byte qfg3SignatureWooDialog[] = { + 30, + 0x67, 0x12, // pTos 12 (query) + 0x35, 0xb6, // ldi b6 + 0x1a, // eq? + 0x2f, 0x05, // bt 05 + 0x67, 0x12, // pTos 12 (query) + 0x35, 0x9b, // ldi 9b + 0x1a, // eq? + 0x31, 0x0c, // bnt 0c + 0x38, 0x97, 0x02, // pushi 0297 + 0x7a, // push2 + 0x38, 0x0c, 0x01, // pushi 010c + 0x7a, // push2 + 0x81, 0x00, // lag 00 + 0x4a, 0x08, // send 08 + 0x67, 0x12, // pTos 12 (query) + 0x35, 0xb5, // ldi b5 + 0 +}; + +const uint16 qfg3PatchWooDialog[] = { + PATCH_ADDTOOFFSET | +0x29, + 0x33, 0x11, // jmp to 0x6a2, the call to hero::solvePuzzle for 0xFFFC + PATCH_END +}; + // script, description, magic DWORD, adjust const SciScriptSignature qfg3Signatures[] = { { 944, "import dialog continuous calls", 1, PATCH_MAGICDWORD(0x2a, 0x31, 0x0b, 0x7a), -1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, + { 440, "dialog crash when asking about Woo", 1, PATCH_MAGICDWORD(0x67, 0x12, 0x35, 0xb5), -26, qfg3SignatureWooDialog, qfg3PatchWooDialog }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -978,7 +1022,27 @@ const uint16 sq4CdPatchTextOptionsButton[] = { PATCH_END }; -// Patch 2: Add the ability to toggle among the three available options, +// Patch 2: Adjust a check in babbleIcon::init, which handles the babble icon +// (e.g. the two guys from Andromeda) shown when dying/quitting. +// Fixes bug #3538418. +const byte sq4CdSignatureBabbleIcon[] = { + 7, + 0x89, 0x5a, // lsg 5a + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x26, // bnt 26 [02a7] + 0 +}; + +const uint16 sq4CdPatchBabbleIcon[] = { + 0x89, 0x5a, // lsg 5a + 0x35, 0x01, // ldi 01 + 0x1a, // eq? + 0x2f, 0x26, // bt 26 [02a7] + PATCH_END +}; + +// Patch 3: Add the ability to toggle among the three available options, // when the text options button is clicked: "Speech", "Text" and "Both". // Refer to the patch above for additional details. // iconTextSwitch::doit (called when the text options button is clicked) @@ -1030,6 +1094,7 @@ const SciScriptSignature sq4Signatures[] = { { 298, "Floppy: endless flight", 1, PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x44), -3, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight }, { 298, "Floppy (German): endless flight", 1, PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x4c), -3, sq4FloppySignatureEndlessFlightGerman, sq4FloppyPatchEndlessFlight }, { 818, "CD: Speech and subtitles option", 1, PATCH_MAGICDWORD(0x89, 0x5a, 0x3c, 0x35), 0, sq4CdSignatureTextOptions, sq4CdPatchTextOptions }, + { 0, "CD: Babble icon speech and subtitles fix", 1, PATCH_MAGICDWORD(0x89, 0x5a, 0x35, 0x02), 0, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon }, { 818, "CD: Speech and subtitles option button", 1, PATCH_MAGICDWORD(0x35, 0x01, 0xa1, 0x53), 0, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton }, SCI_SIGNATUREENTRY_TERMINATOR }; diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index ef61b2e28a..b2f22aa985 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -50,7 +50,7 @@ const char *opcodeNames[] = { "lea", "selfID", "dummy", "pprev", "pToa", "aTop", "pTos", "sTop", "ipToa", "dpToa", "ipTos", "dpTos", "lofsa", "lofss", "push0", - "push1", "push2", "pushSelf", "dummy", "lag", + "push1", "push2", "pushSelf", "line", "lag", "lal", "lat", "lap", "lsg", "lsl", "lst", "lsp", "lagi", "lali", "lati", "lapi", "lsgi", "lsli", "lsti", "lspi", @@ -68,18 +68,18 @@ const char *opcodeNames[] = { #endif // REDUCE_MEMORY_USAGE // Disassembles one command from the heap, returns address of next command or 0 if a ret was encountered. -reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode) { - SegmentObj *mobj = s->_segMan->getSegment(pos.segment, SEG_TYPE_SCRIPT); +reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printBytecode) { + SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT); Script *script_entity = NULL; const byte *scr; - int scr_size; - reg_t retval = make_reg(pos.segment, pos.offset + 1); + uint32 scr_size; + reg_t retval = make_reg(pos.getSegment(), pos.getOffset() + 1); uint16 param_value = 0xffff; // Suppress GCC warning by setting default value, chose value as invalid to getKernelName etc. - int i = 0; + uint i = 0; Kernel *kernel = g_sci->getKernel(); if (!mobj) { - warning("Disassembly failed: Segment %04x non-existant or not a script", pos.segment); + warning("Disassembly failed: Segment %04x non-existant or not a script", pos.getSegment()); return retval; } else script_entity = (Script *)mobj; @@ -87,28 +87,37 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode scr = script_entity->getBuf(); scr_size = script_entity->getBufSize(); - if (pos.offset >= scr_size) { + if (pos.getOffset() >= scr_size) { warning("Trying to disassemble beyond end of script"); return NULL_REG; } int16 opparams[4]; byte opsize; - int bytecount = readPMachineInstruction(scr + pos.offset, opsize, opparams); + uint bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams); const byte opcode = opsize >> 1; - opsize &= 1; // byte if true, word if false - debugN("%04x:%04x: ", PRINT_REG(pos)); + if (opcode == op_pushSelf) { // 0x3e (62) + if ((opsize & 1) && g_sci->getGameId() != GID_FANMADE) { + // Debug opcode op_file + debugN("file \"%s\"\n", scr + pos.getOffset() + 1); // +1: op_pushSelf size + retval.incOffset(bytecount - 1); + return retval; + } + } + + opsize &= 1; // byte if true, word if false + if (printBytecode) { - if (pos.offset + bytecount > scr_size) { + if (pos.getOffset() + bytecount > scr_size) { warning("Operation arguments extend beyond end of script"); return retval; } for (i = 0; i < bytecount; i++) - debugN("%02x ", scr[pos.offset + i]); + debugN("%02x ", scr[pos.getOffset() + i]); for (i = bytecount; i < 5; i++) debugN(" "); @@ -130,16 +139,16 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode case Script_SByte: case Script_Byte: - param_value = scr[retval.offset]; - debugN(" %02x", scr[retval.offset]); - retval.offset++; + param_value = scr[retval.getOffset()]; + debugN(" %02x", scr[retval.getOffset()]); + retval.incOffset(1); break; case Script_Word: case Script_SWord: - param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.offset]); - debugN(" %04x", READ_SCI11ENDIAN_UINT16(&scr[retval.offset])); - retval.offset += 2; + param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); + debugN(" %04x", READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()])); + retval.incOffset(2); break; case Script_SVariable: @@ -149,11 +158,12 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode case Script_Local: case Script_Temp: case Script_Param: - if (opsize) - param_value = scr[retval.offset++]; - else { - param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.offset]); - retval.offset += 2; + if (opsize) { + param_value = scr[retval.getOffset()]; + retval.incOffset(1); + } else { + param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); + retval.incOffset(2); } if (opcode == op_callk) @@ -166,24 +176,26 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode break; case Script_Offset: - if (opsize) - param_value = scr[retval.offset++]; - else { - param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.offset]); - retval.offset += 2; + if (opsize) { + param_value = scr[retval.getOffset()]; + retval.incOffset(1); + } else { + param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); + retval.incOffset(2); } debugN(opsize ? " %02x" : " %04x", param_value); break; case Script_SRelative: if (opsize) { - int8 offset = (int8)scr[retval.offset++]; - debugN(" %02x [%04x]", 0xff & offset, 0xffff & (retval.offset + offset)); + int8 offset = (int8)scr[retval.getOffset()]; + retval.incOffset(1); + debugN(" %02x [%04x]", 0xff & offset, 0xffff & (retval.getOffset() + offset)); } else { - int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.offset]); - retval.offset += 2; - debugN(" %04x [%04x]", 0xffff & offset, 0xffff & (retval.offset + offset)); + int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); + retval.incOffset(2); + debugN(" %04x [%04x]", 0xffff & offset, 0xffff & (retval.getOffset() + offset)); } break; @@ -216,8 +228,8 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode if (opcode == op_callk) { - int stackframe = (scr[pos.offset + 2] >> 1) + (s->r_rest); - int argc = ((s->xs->sp)[- stackframe - 1]).offset; + int stackframe = (scr[pos.getOffset() + 2] >> 1) + (s->r_rest); + int argc = ((s->xs->sp)[- stackframe - 1]).getOffset(); bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); if (!oldScriptHeader) @@ -233,13 +245,13 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode debugN(")\n"); } else if ((opcode == op_send) || (opcode == op_self)) { int restmod = s->r_rest; - int stackframe = (scr[pos.offset + 1] >> 1) + restmod; + int stackframe = (scr[pos.getOffset() + 1] >> 1) + restmod; reg_t *sb = s->xs->sp; uint16 selector; reg_t fun_ref; while (stackframe > 0) { - int argc = sb[- stackframe + 1].offset; + int argc = sb[- stackframe + 1].getOffset(); const char *name = NULL; reg_t called_obj_addr = s->xs->objp; @@ -248,7 +260,7 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode else if (opcode == op_self) called_obj_addr = s->xs->objp; - selector = sb[- stackframe].offset; + selector = sb[- stackframe].getOffset(); name = s->_segMan->getObjectName(called_obj_addr); @@ -290,20 +302,20 @@ reg_t disassemble(EngineState *s, reg_t pos, bool printBWTag, bool printBytecode } bool isJumpOpcode(EngineState *s, reg_t pos, reg_t& jumpTarget) { - SegmentObj *mobj = s->_segMan->getSegment(pos.segment, SEG_TYPE_SCRIPT); + SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT); if (!mobj) return false; Script *script_entity = (Script *)mobj; const byte *scr = script_entity->getBuf(); - int scr_size = script_entity->getScriptSize(); + uint scr_size = script_entity->getScriptSize(); - if (pos.offset >= scr_size) + if (pos.getOffset() >= scr_size) return false; int16 opparams[4]; byte opsize; - int bytecount = readPMachineInstruction(scr + pos.offset, opsize, opparams); + int bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams); const byte opcode = opsize >> 1; switch (opcode) { @@ -313,7 +325,7 @@ bool isJumpOpcode(EngineState *s, reg_t pos, reg_t& jumpTarget) { { reg_t jmpTarget = pos + bytecount + opparams[0]; // QFG2 has invalid jumps outside the script buffer in script 260 - if (jmpTarget.offset >= scr_size) + if (jmpTarget.getOffset() >= scr_size) return false; jumpTarget = jmpTarget; } @@ -335,17 +347,17 @@ void SciEngine::scriptDebug() { } if (_debugState.seeking != kDebugSeekNothing) { - const reg_t pc = s->xs->addr.pc; - SegmentObj *mobj = s->_segMan->getSegment(pc.segment, SEG_TYPE_SCRIPT); + const reg32_t pc = s->xs->addr.pc; + SegmentObj *mobj = s->_segMan->getSegment(pc.getSegment(), SEG_TYPE_SCRIPT); if (mobj) { Script *scr = (Script *)mobj; const byte *code_buf = scr->getBuf(); - int code_buf_size = scr->getBufSize(); - int opcode = pc.offset >= code_buf_size ? 0 : code_buf[pc.offset]; + uint16 code_buf_size = scr->getBufSize(); // TODO: change to a 32-bit integer for large SCI3 scripts + int opcode = pc.getOffset() >= code_buf_size ? 0 : code_buf[pc.getOffset()]; int op = opcode >> 1; - int paramb1 = pc.offset + 1 >= code_buf_size ? 0 : code_buf[pc.offset + 1]; - int paramf1 = (opcode & 1) ? paramb1 : (pc.offset + 2 >= code_buf_size ? 0 : (int16)READ_SCI11ENDIAN_UINT16(code_buf + pc.offset + 1)); + uint16 paramb1 = pc.getOffset() + 1 >= code_buf_size ? 0 : code_buf[pc.getOffset() + 1]; + uint16 paramf1 = (opcode & 1) ? paramb1 : (pc.getOffset() + 2 >= code_buf_size ? 0 : (int16)READ_SCI11ENDIAN_UINT16(code_buf + pc.getOffset() + 1)); switch (_debugState.seeking) { case kDebugSeekSpecialCallk: @@ -502,7 +514,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { if (!objType) { debugN("End of script object (#0) encountered.\n"); - debugN("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)", + debugN("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)\n", objectctr[6], objectctr[1], objectctr[7], objectctr[10]); return; } @@ -515,7 +527,8 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { _seeker += objsize; - objectctr[objType]++; + if (objType >= 0 && objType < ARRAYSIZE(objectctr)) + objectctr[objType]++; switch (objType) { case SCI_OBJ_OBJECT: @@ -714,7 +727,7 @@ void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *ke else if (regType & SIG_IS_INVALID) debugN("INVALID"); else if (regType & SIG_TYPE_INTEGER) - debugN("%d", argv[parmNr].offset); + debugN("%d", argv[parmNr].getOffset()); else { debugN("%04x:%04x", PRINT_REG(argv[parmNr])); switch (regType) { @@ -723,12 +736,12 @@ void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *ke break; case SIG_TYPE_REFERENCE: { - SegmentObj *mobj = s->_segMan->getSegmentObj(argv[parmNr].segment); + SegmentObj *mobj = s->_segMan->getSegmentObj(argv[parmNr].getSegment()); switch (mobj->getType()) { case SEG_TYPE_HUNK: { HunkTable *ht = (HunkTable *)mobj; - int index = argv[parmNr].offset; + int index = argv[parmNr].getOffset(); if (ht->isValidEntry(index)) { // NOTE: This ", deleted" isn't as useful as it could // be because it prints the status _after_ the kernel @@ -765,7 +778,7 @@ void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *ke if (result.isPointer()) debugN(" = %04x:%04x\n", PRINT_REG(result)); else - debugN(" = %d\n", result.offset); + debugN(" = %d\n", result.getOffset()); } } // End of namespace Sci diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index cc127c8dbc..04c1dab158 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -81,7 +81,7 @@ void SegManager::initSysStrings() { if (getSciVersion() <= SCI_VERSION_1_1) { // We need to allocate system strings in one segment, for compatibility reasons allocDynmem(512, "system strings", &_saveDirPtr); - _parserPtr = make_reg(_saveDirPtr.segment, _saveDirPtr.offset + 256); + _parserPtr = make_reg(_saveDirPtr.getSegment(), _saveDirPtr.getOffset() + 256); #ifdef ENABLE_SCI32 } else { SciString *saveDirString = allocateString(&_saveDirPtr); @@ -143,16 +143,27 @@ Script *SegManager::allocateScript(int script_nr, SegmentId *segid) { } void SegManager::deallocate(SegmentId seg) { - if (!check(seg)) - error("SegManager::deallocate(): invalid segment ID"); + if (seg < 1 || (uint)seg >= _heap.size()) + error("Attempt to deallocate an invalid segment ID"); SegmentObj *mobj = _heap[seg]; + if (!mobj) + error("Attempt to deallocate an already freed segment"); if (mobj->getType() == SEG_TYPE_SCRIPT) { Script *scr = (Script *)mobj; _scriptSegMap.erase(scr->getScriptNumber()); - if (scr->getLocalsSegment()) - deallocate(scr->getLocalsSegment()); + if (scr->getLocalsSegment()) { + // Check if the locals segment has already been deallocated. + // If the locals block has been stored in a segment with an ID + // smaller than the segment ID of the script itself, it will be + // already freed at this point. This can happen when scripts are + // uninstantiated and instantiated again: they retain their own + // segment ID, but are allocated a new locals segment, which can + // have an ID smaller than the segment of the script itself. + if (_heap[scr->getLocalsSegment()]) + deallocate(scr->getLocalsSegment()); + } } delete mobj; @@ -163,7 +174,7 @@ bool SegManager::isHeapObject(reg_t pos) const { const Object *obj = getObject(pos); if (obj == NULL || (obj && obj->isFreed())) return false; - Script *scr = getScriptIfLoaded(pos.segment); + Script *scr = getScriptIfLoaded(pos.getSegment()); return !(scr && scr->isMarkedAsDeleted()); } @@ -214,21 +225,21 @@ SegmentObj *SegManager::getSegment(SegmentId seg, SegmentType type) const { } Object *SegManager::getObject(reg_t pos) const { - SegmentObj *mobj = getSegmentObj(pos.segment); + SegmentObj *mobj = getSegmentObj(pos.getSegment()); Object *obj = NULL; if (mobj != NULL) { if (mobj->getType() == SEG_TYPE_CLONES) { CloneTable *ct = (CloneTable *)mobj; - if (ct->isValidEntry(pos.offset)) - obj = &(ct->_table[pos.offset]); + if (ct->isValidEntry(pos.getOffset())) + obj = &(ct->_table[pos.getOffset()]); else warning("getObject(): Trying to get an invalid object"); } else if (mobj->getType() == SEG_TYPE_SCRIPT) { Script *scr = (Script *)mobj; - if (pos.offset <= scr->getBufSize() && pos.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET - && RAW_IS_OBJECT(scr->getBuf(pos.offset))) { - obj = scr->getObject(pos.offset); + if (pos.getOffset() <= scr->getBufSize() && pos.getOffset() >= (uint)-SCRIPT_OBJECT_MAGIC_OFFSET + && scr->offsetIsObject(pos.getOffset())) { + obj = scr->getObject(pos.getOffset()); } } } @@ -246,7 +257,7 @@ const char *SegManager::getObjectName(reg_t pos) { return "<no name>"; const char *name = 0; - if (nameReg.segment) + if (nameReg.getSegment()) name = derefString(nameReg); if (!name) return "<invalid name>"; @@ -272,7 +283,7 @@ reg_t SegManager::findObjectByName(const Common::String &name, int index) { const Script *scr = (const Script *)mobj; const ObjMap &objects = scr->getObjectMap(); for (ObjMap::const_iterator it = objects.begin(); it != objects.end(); ++it) { - objpos.offset = it->_value.getPos().offset; + objpos.setOffset(it->_value.getPos().getOffset()); if (name == getObjectName(objpos)) result.push_back(objpos); } @@ -283,7 +294,7 @@ reg_t SegManager::findObjectByName(const Common::String &name, int index) { if (!ct->isValidEntry(idx)) continue; - objpos.offset = idx; + objpos.setOffset(idx); if (name == getObjectName(objpos)) result.push_back(objpos); } @@ -307,21 +318,6 @@ reg_t SegManager::findObjectByName(const Common::String &name, int index) { return result[index]; } -// validate the seg -// return: -// false - invalid seg -// true - valid seg -bool SegManager::check(SegmentId seg) { - if (seg < 1 || (uint)seg >= _heap.size()) { - return false; - } - if (!_heap[seg]) { - warning("SegManager: seg %x is removed from memory, but not removed from hash_map", seg); - return false; - } - return true; -} - // return the seg if script_id is valid and in the map, else 0 SegmentId SegManager::getScriptSegment(int script_id) const { return _scriptSegMap.getVal(script_id, 0); @@ -364,14 +360,14 @@ void SegManager::freeHunkEntry(reg_t addr) { return; } - HunkTable *ht = (HunkTable *)getSegment(addr.segment, SEG_TYPE_HUNK); + HunkTable *ht = (HunkTable *)getSegment(addr.getSegment(), SEG_TYPE_HUNK); if (!ht) { warning("Attempt to free Hunk from address %04x:%04x: Invalid segment type", PRINT_REG(addr)); return; } - ht->freeEntryContents(addr.offset); + ht->freeEntryContents(addr.getOffset()); } reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) { @@ -398,14 +394,14 @@ reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) { } byte *SegManager::getHunkPointer(reg_t addr) { - HunkTable *ht = (HunkTable *)getSegment(addr.segment, SEG_TYPE_HUNK); + HunkTable *ht = (HunkTable *)getSegment(addr.getSegment(), SEG_TYPE_HUNK); - if (!ht || !ht->isValidEntry(addr.offset)) { + if (!ht || !ht->isValidEntry(addr.getOffset())) { // Valid SCI behavior, e.g. when loading/quitting return NULL; } - return (byte *)ht->_table[addr.offset].mem; + return (byte *)ht->_table[addr.getOffset()].mem; } Clone *SegManager::allocateClone(reg_t *addr) { @@ -462,35 +458,35 @@ reg_t SegManager::newNode(reg_t value, reg_t key) { } List *SegManager::lookupList(reg_t addr) { - if (getSegmentType(addr.segment) != SEG_TYPE_LISTS) { + if (getSegmentType(addr.getSegment()) != SEG_TYPE_LISTS) { error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } - ListTable *lt = (ListTable *)_heap[addr.segment]; + ListTable *lt = (ListTable *)_heap[addr.getSegment()]; - if (!lt->isValidEntry(addr.offset)) { + if (!lt->isValidEntry(addr.getOffset())) { error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } - return &(lt->_table[addr.offset]); + return &(lt->_table[addr.getOffset()]); } Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { if (addr.isNull()) return NULL; // Non-error null - SegmentType type = getSegmentType(addr.segment); + SegmentType type = getSegmentType(addr.getSegment()); if (type != SEG_TYPE_NODES) { error("Attempt to use non-node %04x:%04x (type %d) as list node", PRINT_REG(addr), type); return NULL; } - NodeTable *nt = (NodeTable *)_heap[addr.segment]; + NodeTable *nt = (NodeTable *)_heap[addr.getSegment()]; - if (!nt->isValidEntry(addr.offset)) { + if (!nt->isValidEntry(addr.getOffset())) { if (!stopOnDiscarded) return NULL; @@ -498,19 +494,19 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { return NULL; } - return &(nt->_table[addr.offset]); + return &(nt->_table[addr.getOffset()]); } SegmentRef SegManager::dereference(reg_t pointer) { SegmentRef ret; - if (!pointer.segment || (pointer.segment >= _heap.size()) || !_heap[pointer.segment]) { + if (!pointer.getSegment() || (pointer.getSegment() >= _heap.size()) || !_heap[pointer.getSegment()]) { // This occurs in KQ5CD when interacting with certain objects warning("SegManager::dereference(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); return ret; /* Invalid */ } - SegmentObj *mobj = _heap[pointer.segment]; + SegmentObj *mobj = _heap[pointer.getSegment()]; return mobj->dereference(pointer); } @@ -522,7 +518,7 @@ static void *derefPtr(SegManager *segMan, reg_t pointer, int entries, bool wantR if (ret.isRaw != wantRaw) { warning("Dereferencing pointer %04x:%04x (type %d) which is %s, but expected %s", PRINT_REG(pointer), - segMan->getSegmentType(pointer.segment), + segMan->getSegmentType(pointer.getSegment()), ret.isRaw ? "raw" : "not raw", wantRaw ? "raw" : "not raw"); } @@ -565,15 +561,15 @@ static inline char getChar(const SegmentRef &ref, uint offset) { // segment 0xFFFF means that the scripts are using uninitialized temp-variable space // we can safely ignore this, if it isn't one of the first 2 chars. // foreign lsl3 uses kFileIO(readraw) and then immediately uses kReadNumber right at the start - if (val.segment != 0) - if (!((val.segment == 0xFFFF) && (offset > 1))) + if (val.getSegment() != 0) + if (!((val.getSegment() == 0xFFFF) && (offset > 1))) warning("Attempt to read character from non-raw data"); bool oddOffset = offset & 1; if (g_sci->isBE()) oddOffset = !oddOffset; - return (oddOffset ? val.offset >> 8 : val.offset & 0xff); + return (oddOffset ? val.getOffset() >> 8 : val.getOffset() & 0xff); } static inline void setChar(const SegmentRef &ref, uint offset, byte value) { @@ -582,16 +578,16 @@ static inline void setChar(const SegmentRef &ref, uint offset, byte value) { reg_t *val = ref.reg + offset / 2; - val->segment = 0; + val->setSegment(0); bool oddOffset = offset & 1; if (g_sci->isBE()) oddOffset = !oddOffset; if (oddOffset) - val->offset = (val->offset & 0x00ff) | (value << 8); + val->setOffset((val->getOffset() & 0x00ff) | (value << 8)); else - val->offset = (val->offset & 0xff00) | value; + val->setOffset((val->getOffset() & 0xff00) | value); } // TODO: memcpy, strcpy and strncpy could maybe be folded into a single function @@ -829,10 +825,11 @@ byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) { } bool SegManager::freeDynmem(reg_t addr) { - if (addr.segment < 1 || addr.segment >= _heap.size() || !_heap[addr.segment] || _heap[addr.segment]->getType() != SEG_TYPE_DYNMEM) + if (addr.getSegment() < 1 || addr.getSegment() >= _heap.size() || + !_heap[addr.getSegment()] || _heap[addr.getSegment()]->getType() != SEG_TYPE_DYNMEM) return false; // error - deallocate(addr.segment); + deallocate(addr.getSegment()); return true; // OK } @@ -854,28 +851,28 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { } SciArray<reg_t> *SegManager::lookupArray(reg_t addr) { - if (_heap[addr.segment]->getType() != SEG_TYPE_ARRAY) + if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - ArrayTable *arrayTable = (ArrayTable *)_heap[addr.segment]; + ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()]; - if (!arrayTable->isValidEntry(addr.offset)) + if (!arrayTable->isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - return &(arrayTable->_table[addr.offset]); + return &(arrayTable->_table[addr.getOffset()]); } void SegManager::freeArray(reg_t addr) { - if (_heap[addr.segment]->getType() != SEG_TYPE_ARRAY) + if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - ArrayTable *arrayTable = (ArrayTable *)_heap[addr.segment]; + ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()]; - if (!arrayTable->isValidEntry(addr.offset)) + if (!arrayTable->isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - arrayTable->_table[addr.offset].destroy(); - arrayTable->freeEntry(addr.offset); + arrayTable->_table[addr.getOffset()].destroy(); + arrayTable->freeEntry(addr.getOffset()); } SciString *SegManager::allocateString(reg_t *addr) { @@ -894,28 +891,28 @@ SciString *SegManager::allocateString(reg_t *addr) { } SciString *SegManager::lookupString(reg_t addr) { - if (_heap[addr.segment]->getType() != SEG_TYPE_STRING) + if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING) error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - StringTable *stringTable = (StringTable *)_heap[addr.segment]; + StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.offset)) + if (!stringTable->isValidEntry(addr.getOffset())) error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - return &(stringTable->_table[addr.offset]); + return &(stringTable->_table[addr.getOffset()]); } void SegManager::freeString(reg_t addr) { - if (_heap[addr.segment]->getType() != SEG_TYPE_STRING) + if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING) error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - StringTable *stringTable = (StringTable *)_heap[addr.segment]; + StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.offset)) + if (!stringTable->isValidEntry(addr.getOffset())) error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - stringTable->_table[addr.offset].destroy(); - stringTable->freeEntry(addr.offset); + stringTable->_table[addr.getOffset()].destroy(); + stringTable->freeEntry(addr.getOffset()); } #endif @@ -939,7 +936,7 @@ void SegManager::createClassTable() { _resMan->unlockResource(vocab996); } -reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) { +reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment) { if (classnr == 0xffff) return NULL_REG; @@ -948,16 +945,16 @@ reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller return NULL_REG; } else { Class *the_class = &_classTable[classnr]; - if (!the_class->reg.segment) { + if (!the_class->reg.getSegment()) { getScriptSegment(the_class->script, lock); - if (!the_class->reg.segment) { + if (!the_class->reg.getSegment()) { error("[VM] Trying to instantiate class %x by instantiating script 0x%x (%03d) failed;", classnr, the_class->script, the_class->script); return NULL_REG; } } else - if (caller.segment != the_class->reg.segment) - getScript(the_class->reg.segment)->incrementLockers(); + if (callerSegment != the_class->reg.getSegment()) + getScript(the_class->reg.getSegment())->incrementLockers(); return the_class->reg; } @@ -977,8 +974,7 @@ int SegManager::instantiateScript(int scriptNum) { scr = allocateScript(scriptNum, &segmentId); } - scr->init(scriptNum, _resMan); - scr->load(_resMan); + scr->load(scriptNum, _resMan); scr->initializeLocals(this); scr->initializeClasses(this); scr->initializeObjects(this, segmentId); @@ -1003,7 +999,7 @@ void SegManager::uninstantiateScript(int script_nr) { // Free all classtable references to this script for (uint i = 0; i < classTableSize(); i++) - if (getClass(i).reg.segment == segmentId) + if (getClass(i).reg.getSegment() == segmentId) setClassOffset(i, NULL_REG); if (getSciVersion() < SCI_VERSION_1_1) @@ -1027,18 +1023,18 @@ void SegManager::uninstantiateScriptSci0(int script_nr) { // Make a pass over the object in order to uninstantiate all superclasses do { - reg.offset += objLength; // Step over the last checked object + reg.incOffset(objLength); // Step over the last checked object - objType = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.offset)); + objType = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.getOffset())); if (!objType) break; - objLength = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.offset + 2)); + objLength = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.getOffset() + 2)); - reg.offset += 4; // Step over header + reg.incOffset(4); // Step over header if ((objType == SCI_OBJ_OBJECT) || (objType == SCI_OBJ_CLASS)) { // object or class? - reg.offset += 8; // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) - int16 superclass = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.offset + 2)); + reg.incOffset(8); // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) + int16 superclass = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.getOffset() + 2)); if (superclass >= 0) { int superclass_script = getClass(superclass).script; @@ -1052,10 +1048,10 @@ void SegManager::uninstantiateScriptSci0(int script_nr) { // Recurse to assure that the superclass lockers number gets decreased } - reg.offset += SCRIPT_OBJECT_MAGIC_OFFSET; + reg.incOffset(SCRIPT_OBJECT_MAGIC_OFFSET); } // if object or class - reg.offset -= 4; // Step back on header + reg.incOffset(-4); // Step back on header } while (objType != 0); } diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index 62e711e686..074d3f6b0a 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -125,7 +125,7 @@ private: public: // TODO: document this - reg_t getClassAddress(int classnr, ScriptLoadType lock, reg_t caller); + reg_t getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment); /** * Return a pointer to the specified script. @@ -471,14 +471,6 @@ private: void createClassTable(); SegmentId findFreeSegment() const; - - /** - * Check segment validity - * @param[in] seg The segment to validate - * @return false if 'seg' is an invalid segment, true if - * 'seg' is a valid segment - */ - bool check(SegmentId seg); }; } // End of namespace Sci diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index 36b7d92c07..a7f147a15a 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -93,11 +93,11 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { Common::Array<reg_t> tmp; // assert(addr.segment == _segId); - if (!isValidEntry(addr.offset)) { + if (!isValidEntry(addr.getOffset())) { error("Unexpected request for outgoing references from clone at %04x:%04x", PRINT_REG(addr)); } - const Clone *clone = &(_table[addr.offset]); + const Clone *clone = &(_table[addr.getOffset()]); // Emit all member variables (including references to the 'super' delegate) for (uint i = 0; i < clone->getVarCount(); i++) @@ -112,7 +112,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) { #ifdef GC_DEBUG - Object *victim_obj = &(_table[addr.offset]); + Object *victim_obj = &(_table[addr.getOffset()]); if (!(victim_obj->_flags & OBJECT_FLAG_FREED)) warning("[GC] Clone %04x:%04x not reachable and not freed (freeing now)", PRINT_REG(addr)); @@ -124,7 +124,7 @@ void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) { #endif #endif - freeEntry(addr.offset); + freeEntry(addr.getOffset()); } @@ -133,15 +133,15 @@ void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) { SegmentRef LocalVariables::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = false; // reg_t based data! - ret.maxSize = (_locals.size() - pointer.offset / 2) * 2; + ret.maxSize = (_locals.size() - pointer.getOffset() / 2) * 2; - if (pointer.offset & 1) { + if (pointer.getOffset() & 1) { ret.maxSize -= 1; ret.skipByte = true; } if (ret.maxSize > 0) { - ret.reg = &_locals[pointer.offset / 2]; + ret.reg = &_locals[pointer.getOffset() / 2]; } else { if ((g_sci->getEngineState()->currentRoomNumber() == 160 || g_sci->getEngineState()->currentRoomNumber() == 220) @@ -181,14 +181,14 @@ Common::Array<reg_t> LocalVariables::listAllOutgoingReferences(reg_t addr) const SegmentRef DataStack::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = false; // reg_t based data! - ret.maxSize = (_capacity - pointer.offset / 2) * 2; + ret.maxSize = (_capacity - pointer.getOffset() / 2) * 2; - if (pointer.offset & 1) { + if (pointer.getOffset() & 1) { ret.maxSize -= 1; ret.skipByte = true; } - ret.reg = &_entries[pointer.offset / 2]; + ret.reg = &_entries[pointer.getOffset() / 2]; return ret; } @@ -204,11 +204,11 @@ Common::Array<reg_t> DataStack::listAllOutgoingReferences(reg_t object) const { Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const { Common::Array<reg_t> tmp; - if (!isValidEntry(addr.offset)) { + if (!isValidEntry(addr.getOffset())) { error("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const List *list = &(_table[addr.offset]); + const List *list = &(_table[addr.getOffset()]); tmp.push_back(list->first); tmp.push_back(list->last); @@ -222,10 +222,10 @@ Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const { Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const { Common::Array<reg_t> tmp; - if (!isValidEntry(addr.offset)) { + if (!isValidEntry(addr.getOffset())) { error("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const Node *node = &(_table[addr.offset]); + const Node *node = &(_table[addr.getOffset()]); // We need all four here. Can't just stick with 'pred' OR 'succ' because node operations allow us // to walk around from any given node @@ -242,8 +242,8 @@ Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const { SegmentRef DynMem::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = true; - ret.maxSize = _size - pointer.offset; - ret.raw = _buf + pointer.offset; + ret.maxSize = _size - pointer.getOffset(); + ret.raw = _buf + pointer.getOffset(); return ret; } @@ -252,27 +252,27 @@ SegmentRef DynMem::dereference(reg_t pointer) { SegmentRef ArrayTable::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = false; - ret.maxSize = _table[pointer.offset].getSize() * 2; - ret.reg = _table[pointer.offset].getRawData(); + ret.maxSize = _table[pointer.getOffset()].getSize() * 2; + ret.reg = _table[pointer.getOffset()].getRawData(); return ret; } void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.offset].destroy(); - freeEntry(sub_addr.offset); + _table[sub_addr.getOffset()].destroy(); + freeEntry(sub_addr.getOffset()); } Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const { Common::Array<reg_t> tmp; - if (!isValidEntry(addr.offset)) { + if (!isValidEntry(addr.getOffset())) { error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const SciArray<reg_t> *array = &(_table[addr.offset]); + const SciArray<reg_t> *array = &(_table[addr.getOffset()]); for (uint32 i = 0; i < array->getSize(); i++) { reg_t value = array->getValue(i); - if (value.segment != 0) + if (value.getSegment() != 0) tmp.push_back(value); } @@ -305,8 +305,8 @@ void SciString::fromString(const Common::String &string) { SegmentRef StringTable::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = true; - ret.maxSize = _table[pointer.offset].getSize(); - ret.raw = (byte *)_table[pointer.offset].getRawData(); + ret.maxSize = _table[pointer.getOffset()].getSize(); + ret.raw = (byte *)_table[pointer.getOffset()].getRawData(); return ret; } diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index 54cf7b98af..0d54b651e1 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -175,7 +175,7 @@ public: } virtual SegmentRef dereference(reg_t pointer); virtual reg_t findCanonicAddress(SegManager *segMan, reg_t addr) const { - return make_reg(addr.segment, 0); + return make_reg(addr.getSegment(), 0); } virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; @@ -291,7 +291,7 @@ struct NodeTable : public SegmentObjTable<Node> { NodeTable() : SegmentObjTable<Node>(SEG_TYPE_NODES) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - freeEntry(sub_addr.offset); + freeEntry(sub_addr.getOffset()); } virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; @@ -304,7 +304,7 @@ struct ListTable : public SegmentObjTable<List> { ListTable() : SegmentObjTable<List>(SEG_TYPE_LISTS) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - freeEntry(sub_addr.offset); + freeEntry(sub_addr.getOffset()); } virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; @@ -333,7 +333,7 @@ struct HunkTable : public SegmentObjTable<Hunk> { } virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - freeEntry(sub_addr.offset); + freeEntry(sub_addr.getOffset()); } virtual void saveLoadWithSerializer(Common::Serializer &ser); @@ -358,7 +358,7 @@ public: } virtual SegmentRef dereference(reg_t pointer); virtual reg_t findCanonicAddress(SegManager *segMan, reg_t addr) const { - return make_reg(addr.segment, 0); + return make_reg(addr.getSegment(), 0); } virtual Common::Array<reg_t> listAllDeallocatable(SegmentId segId) const { const reg_t r = make_reg(segId, 0); @@ -502,8 +502,8 @@ struct StringTable : public SegmentObjTable<SciString> { StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.offset].destroy(); - freeEntry(sub_addr.offset); + _table[sub_addr.getOffset()].destroy(); + freeEntry(sub_addr.getOffset()); } void saveLoadWithSerializer(Common::Serializer &ser); diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index a8b1cf7ec2..2f6b4d58dd 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -181,6 +181,7 @@ void Kernel::mapSelectors() { FIND_SELECTOR(skip); FIND_SELECTOR(fixPriority); FIND_SELECTOR(mirrored); + FIND_SELECTOR(visible); FIND_SELECTOR(useInsetRect); FIND_SELECTOR(inTop); FIND_SELECTOR(inLeft); diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index 4b913a866a..5d3d0752ac 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -149,6 +149,7 @@ struct SelectorCache { Selector fixPriority; Selector mirrored; + Selector visible; Selector useInsetRect; Selector inTop, inLeft, inBottom, inRight; @@ -170,7 +171,7 @@ struct SelectorCache { * SelectorCache and mapped in script.cpp. */ reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId); -#define readSelectorValue(segMan, _obj_, _slc_) (readSelector(segMan, _obj_, _slc_).offset) +#define readSelectorValue(segMan, _obj_, _slc_) (readSelector(segMan, _obj_, _slc_).getOffset()) /** * Writes a selector value to an object. diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 28818cddef..0f0c8dcd66 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -26,6 +26,7 @@ #include "sci/debug.h" // for g_debug_sleeptime_factor #include "sci/event.h" +#include "sci/engine/file.h" #include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" @@ -68,21 +69,26 @@ static const uint16 s_halfWidthSJISMap[256] = { }; EngineState::EngineState(SegManager *segMan) -: _segMan(segMan), _dirseeker() { +: _segMan(segMan), +#ifdef ENABLE_SCI32 + _virtualIndexFile(0), +#endif + _dirseeker() { reset(false); } EngineState::~EngineState() { delete _msgState; +#ifdef ENABLE_SCI32 + delete _virtualIndexFile; +#endif } void EngineState::reset(bool isRestoring) { if (!isRestoring) { _memorySegmentSize = 0; - _fileHandles.resize(5); - abortScriptProcessing = kAbortNone; } @@ -116,6 +122,11 @@ void EngineState::reset(bool isRestoring) { _videoState.reset(); _syncedAudioOptions = false; + + _vmdPalStart = 0; + _vmdPalEnd = 256; + + _palCycleToColor = 255; } void EngineState::speedThrottler(uint32 neededSleep) { diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index dcffe6dbb3..81090876c7 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -34,6 +34,7 @@ class WriteStream; } #include "sci/sci.h" +#include "sci/engine/file.h" #include "sci/engine/seg_manager.h" #include "sci/parser/vocabulary.h" @@ -42,9 +43,12 @@ class WriteStream; namespace Sci { +class FileHandle; +class DirSeeker; class EventManager; class MessageState; class SoundCommandParser; +class VirtualIndexFile; enum AbortGameState { kAbortNone = 0, @@ -53,32 +57,6 @@ enum AbortGameState { kAbortQuitGame = 3 }; -class DirSeeker { -protected: - reg_t _outbuffer; - Common::StringArray _files; - Common::StringArray _virtualFiles; - Common::StringArray::const_iterator _iter; - -public: - DirSeeker() { - _outbuffer = NULL_REG; - _iter = _files.begin(); - } - - reg_t firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan); - reg_t nextFile(SegManager *segMan); - - Common::String getVirtualFilename(uint fileNumber); - -private: - void addAsVirtualFiles(Common::String title, Common::String fileMask); -}; - -enum { - MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ -}; - // We assume that scripts give us savegameId 0->99 for creating a new save slot // and savegameId 100->199 for existing save slots ffs. kfile.cpp enum { @@ -92,20 +70,6 @@ enum { GAMEISRESTARTING_RESTORE = 2 }; -class FileHandle { -public: - Common::String _name; - Common::SeekableReadStream *_in; - Common::WriteStream *_out; - -public: - FileHandle(); - ~FileHandle(); - - void close(); - bool isOpen() const; -}; - enum VideoFlags { kNone = 0, kDoubled = 1 << 0, @@ -163,6 +127,10 @@ public: int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween int16 _lastSaveNewId; // last newly created filename-id by kSaveGame +#ifdef ENABLE_SCI32 + VirtualIndexFile *_virtualIndexFile; +#endif + uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms bool _cursorWorkaroundActive; // ffs. GfxCursor::setPosition() @@ -228,8 +196,11 @@ public: byte _memorySegment[kMemorySegmentMax]; VideoState _videoState; + uint16 _vmdPalStart, _vmdPalEnd; bool _syncedAudioOptions; + uint16 _palCycleToColor; + /** * Resets the engine state. */ diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 162dce9fcc..ef8f165084 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -119,7 +119,7 @@ extern const char *opcodeNames[]; // from scriptdebug.cpp static reg_t read_var(EngineState *s, int type, int index) { if (validate_variable(s->variables[type], s->stack_base, type, s->variablesMax[type], index)) { - if (s->variables[type][index].segment == 0xffff) { + if (s->variables[type][index].getSegment() == 0xffff) { switch (type) { case VAR_TEMP: { // Uninitialized read on a temp @@ -195,8 +195,8 @@ static void write_var(EngineState *s, int type, int index, reg_t value) { // this happens at least in sq1/room 44 (slot-machine), because a send is missing parameters, then // those parameters are taken from uninitialized stack and afterwards they are copied back into temps // if we don't remove the segment, we would get false-positive uninitialized reads later - if (type == VAR_TEMP && value.segment == 0xffff) - value.segment = 0; + if (type == VAR_TEMP && value.getSegment() == 0xffff) + value.setSegment(0); s->variables[type][index] = value; @@ -225,30 +225,15 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP scr = s->_segMan->getScript(seg); } - int temp = scr->validateExportFunc(pubfunct, false); - - if (getSciVersion() == SCI_VERSION_3) - temp += scr->getCodeBlockOffset(); - - if (!temp) { -#ifdef ENABLE_SCI32 - if (g_sci->getGameId() == GID_TORIN && script == 64036) { - // Script 64036 in Torin's Passage is empty and contains an invalid - // (empty) export - } else if (g_sci->getGameId() == GID_RAMA && script == 64908) { - // Script 64908 in the demo of RAMA contains an invalid (empty) - // export - } else -#endif - error("Request for invalid exported function 0x%x of script %d", pubfunct, script); + uint32 exportAddr = scr->validateExportFunc(pubfunct, false); + if (!exportAddr) return NULL; - } // Check if a breakpoint is set on this method g_sci->checkExportBreakpoint(script, pubfunct); ExecStack xstack(calling_obj, calling_obj, sp, argc, argp, - seg, make_reg(seg, temp), -1, pubfunct, -1, + seg, make_reg32(seg, exportAddr), -1, pubfunct, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_CALL); s->_executionStack.push_back(xstack); return &(s->_executionStack.back()); @@ -304,11 +289,12 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt ExecStackType stackType = EXEC_STACK_TYPE_VARSELECTOR; StackPtr curSP = NULL; - reg_t curFP = NULL_REG; + reg32_t curFP = make_reg32(0, 0); if (selectorType == kSelectorMethod) { stackType = EXEC_STACK_TYPE_CALL; curSP = sp; - curFP = funcp; + // TODO: Will this offset suffice for large SCI3 scripts? + curFP = make_reg32(funcp.getSegment(), funcp.getOffset()); sp = CALL_SP_CARRY; // Destroy sp, as it will be carried over } @@ -342,7 +328,7 @@ static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, // Add stack frame to indicate we're executing a callk. // This is useful in debugger backtraces if this // kernel function calls a script itself. - ExecStack xstack(NULL_REG, NULL_REG, NULL, argc, argv - 1, 0xFFFF, NULL_REG, + ExecStack xstack(NULL_REG, NULL_REG, NULL, argc, argv - 1, 0xFFFF, make_reg32(0, 0), kernelCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL); s->_executionStack.push_back(xstack); } @@ -578,16 +564,16 @@ void run_vm(EngineState *s) { int var_type; // See description below int var_number; - g_sci->_debugState.old_pc_offset = s->xs->addr.pc.offset; + g_sci->_debugState.old_pc_offset = s->xs->addr.pc.getOffset(); g_sci->_debugState.old_sp = s->xs->sp; if (s->abortScriptProcessing != kAbortNone) return; // Stop processing if (s->_executionStackPosChanged) { - scr = s->_segMan->getScriptIfLoaded(s->xs->addr.pc.segment); + scr = s->_segMan->getScriptIfLoaded(s->xs->addr.pc.getSegment()); if (!scr) - error("No script in segment %d", s->xs->addr.pc.segment); + error("No script in segment %d", s->xs->addr.pc.getSegment()); s->xs = &(s->_executionStack.back()); s->_executionStackPosChanged = false; @@ -624,13 +610,13 @@ void run_vm(EngineState *s) { s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp; - if (s->xs->addr.pc.offset >= scr->getBufSize()) + if (s->xs->addr.pc.getOffset() >= scr->getBufSize()) error("run_vm(): program counter gone astray, addr: %d, code buffer size: %d", - s->xs->addr.pc.offset, scr->getBufSize()); + s->xs->addr.pc.getOffset(), scr->getBufSize()); // Get opcode byte extOpcode; - s->xs->addr.pc.offset += readPMachineInstruction(scr->getBuf() + s->xs->addr.pc.offset, extOpcode, opparams); + s->xs->addr.pc.incOffset(readPMachineInstruction(scr->getBuf(s->xs->addr.pc.getOffset()), extOpcode, opparams)); const byte opcode = extOpcode >> 1; //debug("%s: %d, %d, %d, %d, acc = %04x:%04x, script %d, local script %d", opcodeNames[opcode], opparams[0], opparams[1], opparams[2], opparams[3], PRINT_REG(s->r_acc), scr->getScriptNumber(), local_script->getScriptNumber()); @@ -705,7 +691,7 @@ void run_vm(EngineState *s) { break; case op_not: // 0x0c (12) - s->r_acc = make_reg(0, !(s->r_acc.offset || s->r_acc.segment)); + s->r_acc = make_reg(0, !(s->r_acc.getOffset() || s->r_acc.getSegment())); // Must allow pointers to be negated, as this is used for checking whether objects exist break; @@ -765,30 +751,30 @@ void run_vm(EngineState *s) { case op_bt: // 0x17 (23) // Branch relative if true - if (s->r_acc.offset || s->r_acc.segment) - s->xs->addr.pc.offset += opparams[0]; + if (s->r_acc.getOffset() || s->r_acc.getSegment()) + s->xs->addr.pc.incOffset(opparams[0]); - if (s->xs->addr.pc.offset >= local_script->getScriptSize()) + if (s->xs->addr.pc.getOffset() >= local_script->getScriptSize()) error("[VM] op_bt: request to jump past the end of script %d (offset %d, script is %d bytes)", - local_script->getScriptNumber(), s->xs->addr.pc.offset, local_script->getScriptSize()); + local_script->getScriptNumber(), s->xs->addr.pc.getOffset(), local_script->getScriptSize()); break; case op_bnt: // 0x18 (24) // Branch relative if not true - if (!(s->r_acc.offset || s->r_acc.segment)) - s->xs->addr.pc.offset += opparams[0]; + if (!(s->r_acc.getOffset() || s->r_acc.getSegment())) + s->xs->addr.pc.incOffset(opparams[0]); - if (s->xs->addr.pc.offset >= local_script->getScriptSize()) + if (s->xs->addr.pc.getOffset() >= local_script->getScriptSize()) error("[VM] op_bnt: request to jump past the end of script %d (offset %d, script is %d bytes)", - local_script->getScriptNumber(), s->xs->addr.pc.offset, local_script->getScriptSize()); + local_script->getScriptNumber(), s->xs->addr.pc.getOffset(), local_script->getScriptSize()); break; case op_jmp: // 0x19 (25) - s->xs->addr.pc.offset += opparams[0]; + s->xs->addr.pc.incOffset(opparams[0]); - if (s->xs->addr.pc.offset >= local_script->getScriptSize()) + if (s->xs->addr.pc.getOffset() >= local_script->getScriptSize()) error("[VM] op_jmp: request to jump past the end of script %d (offset %d, script is %d bytes)", - local_script->getScriptNumber(), s->xs->addr.pc.offset, local_script->getScriptSize()); + local_script->getScriptNumber(), s->xs->addr.pc.getOffset(), local_script->getScriptSize()); break; case op_ldi: // 0x1a (26) @@ -831,13 +817,13 @@ void run_vm(EngineState *s) { int argc = (opparams[1] >> 1) // Given as offset, but we need count + 1 + s->r_rest; StackPtr call_base = s->xs->sp - argc; - s->xs->sp[1].offset += s->r_rest; + s->xs->sp[1].incOffset(s->r_rest); - uint16 localCallOffset = s->xs->addr.pc.offset + opparams[0]; + uint32 localCallOffset = s->xs->addr.pc.getOffset() + opparams[0]; ExecStack xstack(s->xs->objp, s->xs->objp, s->xs->sp, (call_base->requireUint16()) + s->r_rest, call_base, - s->xs->local_segment, make_reg(s->xs->addr.pc.segment, localCallOffset), + s->xs->local_segment, make_reg32(s->xs->addr.pc.getSegment(), localCallOffset), NULL_SELECTOR, -1, localCallOffset, s->_executionStack.size() - 1, EXEC_STACK_TYPE_CALL); @@ -894,9 +880,9 @@ void run_vm(EngineState *s) { s_temp = s->xs->sp; s->xs->sp -= temp; - s->xs->sp[0].offset += s->r_rest; + s->xs->sp[0].incOffset(s->r_rest); xs_new = execute_method(s, 0, opparams[0], s_temp, s->xs->objp, - s->xs->sp[0].offset, s->xs->sp); + s->xs->sp[0].getOffset(), s->xs->sp); s->r_rest = 0; // Used up the &rest adjustment if (xs_new) // in case of error, keep old stack s->_executionStackPosChanged = true; @@ -908,11 +894,10 @@ void run_vm(EngineState *s) { s_temp = s->xs->sp; s->xs->sp -= temp; - s->xs->sp[0].offset += s->r_rest; + s->xs->sp[0].incOffset(s->r_rest); xs_new = execute_method(s, opparams[0], opparams[1], s_temp, s->xs->objp, - s->xs->sp[0].offset, s->xs->sp); + s->xs->sp[0].getOffset(), s->xs->sp); s->r_rest = 0; // Used up the &rest adjustment - if (xs_new) // in case of error, keep old stack s->_executionStackPosChanged = true; break; @@ -965,7 +950,7 @@ void run_vm(EngineState *s) { s_temp = s->xs->sp; s->xs->sp -= ((opparams[0] >> 1) + s->r_rest); // Adjust stack - s->xs->sp[1].offset += s->r_rest; + s->xs->sp[1].incOffset(s->r_rest); xs_new = send_selector(s, s->r_acc, s->r_acc, s_temp, (int)(opparams[0] >> 1) + (uint16)s->r_rest, s->xs->sp); @@ -996,7 +981,7 @@ void run_vm(EngineState *s) { case op_class: // 0x28 (40) // Get class address s->r_acc = s->_segMan->getClassAddress((unsigned)opparams[0], SCRIPT_GET_LOCK, - s->xs->addr.pc); + s->xs->addr.pc.getSegment()); break; case 0x29: // (41) @@ -1008,7 +993,7 @@ void run_vm(EngineState *s) { s_temp = s->xs->sp; s->xs->sp -= ((opparams[0] >> 1) + s->r_rest); // Adjust stack - s->xs->sp[1].offset += s->r_rest; + s->xs->sp[1].incOffset(s->r_rest); xs_new = send_selector(s, s->xs->objp, s->xs->objp, s_temp, (int)(opparams[0] >> 1) + (uint16)s->r_rest, s->xs->sp); @@ -1021,7 +1006,7 @@ void run_vm(EngineState *s) { case op_super: // 0x2b (43) // Send to any class - r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc); + r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc.getSegment()); if (!r_temp.isPointer()) error("[VM]: Invalid superclass in object"); @@ -1029,7 +1014,7 @@ void run_vm(EngineState *s) { s_temp = s->xs->sp; s->xs->sp -= ((opparams[1] >> 1) + s->r_rest); // Adjust stack - s->xs->sp[1].offset += s->r_rest; + s->xs->sp[1].incOffset(s->r_rest); xs_new = send_selector(s, r_temp, s->xs->objp, s_temp, (int)(opparams[1] >> 1) + (uint16)s->r_rest, s->xs->sp); @@ -1058,14 +1043,14 @@ void run_vm(EngineState *s) { var_number = temp & 0x03; // Get variable type // Get variable block offset - r_temp.segment = s->variablesSegment[var_number]; - r_temp.offset = s->variables[var_number] - s->variablesBase[var_number]; + r_temp.setSegment(s->variablesSegment[var_number]); + r_temp.setOffset(s->variables[var_number] - s->variablesBase[var_number]); if (temp & 0x08) // Add accumulator offset if requested - r_temp.offset += s->r_acc.requireSint16(); + r_temp.incOffset(s->r_acc.requireSint16()); - r_temp.offset += opparams[1]; // Add index - r_temp.offset *= 2; // variables are 16 bit + r_temp.incOffset(opparams[1]); // Add index + r_temp.setOffset(r_temp.getOffset() * 2); // variables are 16 bit // That's the immediate address now s->r_acc = r_temp; break; @@ -1129,28 +1114,28 @@ void run_vm(EngineState *s) { case op_lofsa: // 0x39 (57) case op_lofss: // 0x3a (58) // Load offset to accumulator or push to stack - r_temp.segment = s->xs->addr.pc.segment; + r_temp.setSegment(s->xs->addr.pc.getSegment()); switch (g_sci->_features->detectLofsType()) { case SCI_VERSION_0_EARLY: - r_temp.offset = s->xs->addr.pc.offset + opparams[0]; + r_temp.setOffset((uint16)s->xs->addr.pc.getOffset() + opparams[0]); break; case SCI_VERSION_1_MIDDLE: - r_temp.offset = opparams[0]; + r_temp.setOffset(opparams[0]); break; case SCI_VERSION_1_1: - r_temp.offset = opparams[0] + local_script->getScriptSize(); + r_temp.setOffset(opparams[0] + local_script->getScriptSize()); break; case SCI_VERSION_3: // In theory this can break if the variant with a one-byte argument is // used. For now, assume it doesn't happen. - r_temp.offset = local_script->relocateOffsetSci3(s->xs->addr.pc.offset-2); + r_temp.setOffset(local_script->relocateOffsetSci3(s->xs->addr.pc.getOffset() - 2)); break; default: error("Unknown lofs type"); } - if (r_temp.offset >= scr->getBufSize()) + if (r_temp.getOffset() >= scr->getBufSize()) error("VM: lofsa/lofss operation overflowed: %04x:%04x beyond end" " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize()); @@ -1188,6 +1173,7 @@ void run_vm(EngineState *s) { case op_line: // 0x3f (63) // Debug opcode (line number) + //debug("Script %d, line %d", scr->getScriptNumber(), opparams[0]); break; case op_lag: // 0x40 (64) diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 334d224baf..8b38faa013 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -61,8 +61,6 @@ struct Class { reg_t reg; ///< offset; script-relative offset, segment: 0 if not instantiated }; -#define RAW_IS_OBJECT(datablock) (READ_SCI11ENDIAN_UINT16(((const byte *) datablock) + SCRIPT_OBJECT_MAGIC_OFFSET) == SCRIPT_OBJECT_MAGIC_NUMBER) - // A reference to an object's variable. // The object is stored as a reg_t, the variable as an index into _variables struct ObjVarRef { @@ -84,7 +82,7 @@ struct ExecStack { union { ObjVarRef varp; // Variable pointer for r/w access - reg_t pc; // Pointer to the initial program counter. Not accurate for the TOS element + reg32_t pc; // Pointer to the initial program counter. Not accurate for the TOS element } addr; StackPtr fp; // Frame pointer @@ -104,7 +102,7 @@ struct ExecStack { reg_t* getVarPointer(SegManager *segMan) const; ExecStack(reg_t objp_, reg_t sendp_, StackPtr sp_, int argc_, StackPtr argp_, - SegmentId localsSegment_, reg_t pc_, Selector debugSelector_, + SegmentId localsSegment_, reg32_t pc_, Selector debugSelector_, int debugExportId_, int debugLocalCallOffset_, int debugOrigin_, ExecStackType type_) { objp = objp_; @@ -118,7 +116,7 @@ struct ExecStack { if (localsSegment_ != 0xFFFF) local_segment = localsSegment_; else - local_segment = pc_.segment; + local_segment = pc_.getSegment(); debugSelector = debugSelector_; debugExportId = debugExportId_; debugLocalCallOffset = debugLocalCallOffset_; @@ -139,7 +137,7 @@ enum { GC_INTERVAL = 0x8000 }; -enum sci_opcodes { +enum SciOpcodes { op_bnot = 0x00, // 000 op_add = 0x01, // 001 op_sub = 0x02, // 002 @@ -204,6 +202,7 @@ enum sci_opcodes { op_push2 = 0x3d, // 061 op_pushSelf = 0x3e, // 062 op_line = 0x3f, // 063 + // op_lag = 0x40, // 064 op_lal = 0x41, // 065 op_lat = 0x42, // 066 @@ -220,6 +219,7 @@ enum sci_opcodes { op_lsli = 0x4d, // 077 op_lsti = 0x4e, // 078 op_lspi = 0x4f, // 079 + // op_sag = 0x50, // 080 op_sal = 0x51, // 081 op_sat = 0x52, // 082 @@ -236,6 +236,7 @@ enum sci_opcodes { op_ssli = 0x5d, // 093 op_ssti = 0x5e, // 094 op_sspi = 0x5f, // 095 + // op_plusag = 0x60, // 096 op_plusal = 0x61, // 097 op_plusat = 0x62, // 098 @@ -252,6 +253,7 @@ enum sci_opcodes { op_plussli = 0x6d, // 109 op_plussti = 0x6e, // 110 op_plusspi = 0x6f, // 111 + // op_minusag = 0x70, // 112 op_minusal = 0x71, // 113 op_minusat = 0x72, // 114 diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index b95fd58129..27015d9be4 100644 --- a/engines/sci/engine/vm_types.cpp +++ b/engines/sci/engine/vm_types.cpp @@ -43,17 +43,17 @@ reg_t reg_t::lookForWorkaround(const reg_t right) const { reg_t reg_t::operator+(const reg_t right) const { if (isPointer() && right.isNumber()) { // Pointer arithmetics. Only some pointer types make sense here - SegmentObj *mobj = g_sci->getEngineState()->_segMan->getSegmentObj(segment); + SegmentObj *mobj = g_sci->getEngineState()->_segMan->getSegmentObj(getSegment()); if (!mobj) - error("[VM]: Attempt to add %d to invalid pointer %04x:%04x", right.offset, PRINT_REG(*this)); + error("[VM]: Attempt to add %d to invalid pointer %04x:%04x", right.getOffset(), PRINT_REG(*this)); switch (mobj->getType()) { case SEG_TYPE_LOCALS: case SEG_TYPE_SCRIPT: case SEG_TYPE_STACK: case SEG_TYPE_DYNMEM: - return make_reg(segment, offset + right.toSint16()); + return make_reg(getSegment(), getOffset() + right.toSint16()); default: return lookForWorkaround(right); } @@ -69,12 +69,12 @@ reg_t reg_t::operator+(const reg_t right) const { } reg_t reg_t::operator-(const reg_t right) const { - if (segment == right.segment) { + if (getSegment() == right.getSegment()) { // We can subtract numbers, or pointers with the same segment, // an operation which will yield a number like in C return make_reg(0, toSint16() - right.toSint16()); } else { - return *this + make_reg(right.segment, -right.offset); + return *this + make_reg(right.getSegment(), -right.toSint16()); } } @@ -174,7 +174,7 @@ reg_t reg_t::operator^(const reg_t right) const { } int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { - if (segment == right.segment) { // can compare things in the same segment + if (getSegment() == right.getSegment()) { // can compare things in the same segment if (treatAsUnsigned || !isNumber()) return toUint16() - right.toUint16(); else @@ -218,7 +218,7 @@ bool reg_t::pointerComparisonWithInteger(const reg_t right) const { // SQ1, room 28, when throwing water at the Orat // SQ1, room 58, when giving the ID card to the robot // SQ4 CD, at the first game screen, when the narrator is about to speak - return (isPointer() && right.isNumber() && right.offset <= 2000 && getSciVersion() <= SCI_VERSION_1_1); + return (isPointer() && right.isNumber() && right.getOffset() <= 2000 && getSciVersion() <= SCI_VERSION_1_1); } } // End of namespace Sci diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index 7b155a4532..9a7589e9a7 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -31,43 +31,64 @@ namespace Sci { typedef uint16 SegmentId; struct reg_t { - SegmentId segment; - uint16 offset; + // Segment and offset. These should never be accessed directly + SegmentId _segment; + uint16 _offset; + + inline SegmentId getSegment() const { + return _segment; + } + + inline void setSegment(SegmentId segment) { + _segment = segment; + } + + inline uint16 getOffset() const { + return _offset; + } + + inline void setOffset(uint16 offset) { + _offset = offset; + } + + inline void incOffset(int16 offset) { + setOffset(getOffset() + offset); + } inline bool isNull() const { - return (offset | segment) == 0; + return (_offset | getSegment()) == 0; } inline uint16 toUint16() const { - return offset; + return _offset; } inline int16 toSint16() const { - return (int16)offset; + return (int16)_offset; } bool isNumber() const { - return segment == 0; + return getSegment() == 0; } bool isPointer() const { - return segment != 0 && segment != 0xFFFF; + return getSegment() != 0 && getSegment() != 0xFFFF; } uint16 requireUint16() const; int16 requireSint16() const; inline bool isInitialized() const { - return segment != 0xFFFF; + return getSegment() != 0xFFFF; } // Comparison operators bool operator==(const reg_t &x) const { - return (offset == x.offset) && (segment == x.segment); + return (getOffset() == x.getOffset()) && (getSegment() == x.getSegment()); } bool operator!=(const reg_t &x) const { - return (offset != x.offset) || (segment != x.segment); + return (getOffset() != x.getOffset()) || (getSegment() != x.getSegment()); } bool operator>(const reg_t right) const { @@ -141,12 +162,55 @@ private: static inline reg_t make_reg(SegmentId segment, uint16 offset) { reg_t r; - r.offset = offset; - r.segment = segment; + r.setSegment(segment); + r.setOffset(offset); return r; } -#define PRINT_REG(r) (0xffff) & (unsigned) (r).segment, (unsigned) (r).offset +#define PRINT_REG(r) (0xffff) & (unsigned) (r).getSegment(), (unsigned) (r).getOffset() + +// A true 32-bit reg_t +struct reg32_t { + // Segment and offset. These should never be accessed directly + SegmentId _segment; + uint32 _offset; + + inline SegmentId getSegment() const { + return _segment; + } + + inline void setSegment(SegmentId segment) { + _segment = segment; + } + + inline uint32 getOffset() const { + return _offset; + } + + inline void setOffset(uint32 offset) { + _offset = offset; + } + + inline void incOffset(int32 offset) { + setOffset(getOffset() + offset); + } + + // Comparison operators + bool operator==(const reg32_t &x) const { + return (getOffset() == x.getOffset()) && (getSegment() == x.getSegment()); + } + + bool operator!=(const reg32_t &x) const { + return (getOffset() != x.getOffset()) || (getSegment() != x.getSegment()); + } +}; + +static inline reg32_t make_reg32(SegmentId segment, uint32 offset) { + reg32_t r; + r.setSegment(segment); + r.setOffset(offset); + return r; +} // Stack pointer type typedef reg_t *StackPtr; diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 4a0aea81ff..9fa0368784 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -36,13 +36,15 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = { { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xcc6, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #3034464 { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xce0, 0, { WORKAROUND_FAKE, 0 } }, // Same as above, for the Spanish version - bug #3313962 { GID_FANMADE, 516, 983, 0, "Wander", "setTarget", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_mul: The Legend of the Lost Jewel Demo (fan made): called with object as second parameter when attacked by insects - bug #3038913 + { GID_GK1, 800,64992, 0, "Fwd", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when Mosely finds Gabriel and Grace near the end of the game, compares the Grooper object with 7 { GID_ICEMAN, 199, 977, 0, "Grooper", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_add: While dancing with the girl { GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_and: constantly during the game (SCI1 version) { GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when going north and reaching the castle (rooms 4 and 37) - bug #3038228 { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue. + { GID_PHANTASMAGORIA, 902, 0, 0, "", "export 7", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_shr: when starting a chapter in Phantasmagoria { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer bug #3039879 - { GID_GK1, 800,64992, 0, "Fwd", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when Mosely finds Gabriel and Grace near the end of the game, compares the Grooper object with 7 + { GID_QFG4, 710,64941, 0, "RandCycle", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -162,11 +164,12 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_SQ1, -1, 703, 0, "", "export 1", -1, 0, { WORKAROUND_FAKE, 0 } }, // sub that's called from several objects while on sarien battle cruiser { GID_SQ1, -1, 703, 0, "firePulsar", "changeState", 0x18a, 0, { WORKAROUND_FAKE, 0 } }, // export 1, but called locally (when shooting at aliens) { GID_SQ4, -1, 398, 0, "showBox", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD: called when rummaging in Software Excess bargain bin - { GID_SQ4, -1, 928, 0, "Narrator", "startText", -1, 1000, { WORKAROUND_FAKE, 1 } }, // CD: method returns this to the caller + { GID_SQ4, -1, 928, -1, "Narrator", "startText", -1, 1000, { WORKAROUND_FAKE, 1 } }, // CD: happens in the options dialog and in-game when speech and subtitles are used simultaneously { GID_SQ5, 201, 201, 0, "buttonPanel", "doVerb", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking at the orange or red button - bug #3038563 { GID_SQ6, -1, 0, 0, "SQ6", "init", -1, 2, { WORKAROUND_FAKE, 0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100) - { GID_SQ6, 100, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu + { GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places { GID_SQ6, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game + { GID_TORIN, -1, 64017, 0, "oFlags", "clear", -1, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -176,6 +179,7 @@ const SciWorkaroundEntry kAbs_workarounds[] = { { GID_HOYLE1, 2, 2, 0, "room2", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // old maid - called with objects instead of integers { GID_HOYLE1, 3, 3, 0, "room3", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // hearts - called with objects instead of integers { GID_QFG1VGA, -1, -1, 0, NULL, "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // when the game is patched with the NRS patch + { GID_QFG3 , -1, -1, 0, NULL, "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // when the game is patched with the NRS patch (bugs #3528416, #3528542) SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -321,8 +325,9 @@ const SciWorkaroundEntry kGraphRedrawBox_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = { { GID_ECOQUEST2, 100, 333, 0, "showEcorder", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // necessary workaround for our ecorder script patch, because there isn't enough space to patch the function - { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #3038077 { GID_PQ3, 202, 202, 0, "MapEdit", "addPt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #3038077 + { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #3038077 + { GID_PQ3, 202, 202, 0, "MapEdit", "dispose", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -394,6 +399,7 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = { { GID_LSL6, 740, 740, 0, "showCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident { GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident { GID_SQ1, 43, 303, 0, "slotGuy", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error + { GID_QFG4, -1, 110, 0, "dreamer", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during the dream sequence, a 3rd parameter is passed by accident SCI_WORKAROUNDENTRY_TERMINATOR }; diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index 378e88b7df..14443db1e2 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -102,8 +102,8 @@ const MouseEventConversion mouseEventMappings[] = { { Common::EVENT_RBUTTONDOWN, SCI_EVENT_MOUSE_PRESS, 2 }, { Common::EVENT_MBUTTONDOWN, SCI_EVENT_MOUSE_PRESS, 3 }, { Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 1 }, - { Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 2 }, - { Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 3 } + { Common::EVENT_RBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 2 }, + { Common::EVENT_MBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 3 } }; EventManager::EventManager(bool fontIsExtended) : _fontIsExtended(fontIsExtended) { diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp index 983e697481..ee28c5ca31 100644 --- a/engines/sci/graphics/animate.cpp +++ b/engines/sci/graphics/animate.cpp @@ -723,7 +723,7 @@ void GfxAnimate::printAnimateList(Console *con) { const AnimateList::iterator end = _list.end(); for (it = _list.begin(); it != end; ++it) { - Script *scr = _s->_segMan->getScriptIfLoaded(it->object.segment); + Script *scr = _s->_segMan->getScriptIfLoaded(it->object.getSegment()); int16 scriptNo = scr ? scr->getScriptNumber() : -1; con->DebugPrintf("%04x:%04x (%s), script %d, view %d (%d, %d), pal %d, " diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index ad1d9e8623..5535a7408a 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -68,7 +68,7 @@ void GfxControls32::kernelTexteditChange(reg_t controlObject) { while (captureEvents) { curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); - + if (curEvent.type == SCI_EVENT_NONE) { eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event } else { @@ -170,11 +170,11 @@ void GfxControls32::kernelTexteditChange(reg_t controlObject) { // Note: the following checkAltInput call might make the text // too wide to fit, but SSCI fails to check that too. } - + reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap)); Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject); //texteditCursorErase(); // TODO: Cursor - + // Write back string _segMan->strcpy(textReference, text.c_str()); // Modify the buffer and show it diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index 71f4598afc..ce77cf6ed3 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -148,11 +148,13 @@ void GfxCursor::kernelSetShape(GuiResourceId resourceId) { colorMapping[1] = _screen->getColorWhite(); // White is also hardcoded colorMapping[2] = SCI_CURSOR_SCI0_TRANSPARENCYCOLOR; colorMapping[3] = _palette->matchColor(170, 170, 170); // Grey - // Special case for the magnifier cursor in LB1 (bug #3487092). - // No other SCI0 game has a cursor resource of 1, so this is handled - // specifically for LB1. + // TODO: Figure out if the grey color is hardcoded + // HACK for the magnifier cursor in LB1, fixes its color (bug #3487092) if (g_sci->getGameId() == GID_LAURABOW && resourceId == 1) colorMapping[3] = _screen->getColorWhite(); + // HACK for Longbow cursors, fixes the shade of grey they're using (bug #3489101) + if (g_sci->getGameId() == GID_LONGBOW) + colorMapping[3] = _palette->matchColor(223, 223, 223); // Light Grey // Seek to actual data resourceData += 4; @@ -409,7 +411,7 @@ void GfxCursor::refreshPosition() { } } - CursorMan.replaceCursor((const byte *)_cursorSurface, cursorCelInfo->width, cursorCelInfo->height, cursorHotspot.x, cursorHotspot.y, cursorCelInfo->clearKey); + CursorMan.replaceCursor(_cursorSurface, cursorCelInfo->width, cursorCelInfo->height, cursorHotspot.x, cursorHotspot.y, cursorCelInfo->clearKey); } } diff --git a/engines/sci/graphics/font.cpp b/engines/sci/graphics/font.cpp index fcdd057509..30184cc091 100644 --- a/engines/sci/graphics/font.cpp +++ b/engines/sci/graphics/font.cpp @@ -54,7 +54,7 @@ GfxFontFromResource::GfxFontFromResource(ResourceManager *resMan, GfxScreen *scr } GfxFontFromResource::~GfxFontFromResource() { - delete []_chars; + delete[] _chars; _resMan->unlockResource(_resource); } diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index b12413ab69..8b7fa2c384 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -28,9 +28,11 @@ #include "common/system.h" #include "common/textconsole.h" #include "engines/engine.h" +#include "graphics/palette.h" #include "graphics/surface.h" #include "sci/sci.h" +#include "sci/console.h" #include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" @@ -52,12 +54,18 @@ namespace Sci { // TODO/FIXME: This is all guesswork +enum SciSpeciaPlanelPictureCodes { + kPlaneTranslucent = 0xfffe, // -2 + kPlanePlainColored = 0xffff // -1 +}; + GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette *palette, GfxPaint32 *paint32) : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32) { _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; - _scriptsRunningWidth = 320; - _scriptsRunningHeight = 200; + _curScrollText = -1; + _showScrollText = false; + _maxScrollTexts = 0; } GfxFrameout::~GfxFrameout() { @@ -68,32 +76,88 @@ void GfxFrameout::clear() { deletePlaneItems(NULL_REG); _planes.clear(); deletePlanePictures(NULL_REG); + clearScrollTexts(); +} + +void GfxFrameout::clearScrollTexts() { + _scrollTexts.clear(); + _curScrollText = -1; +} + +void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) { + //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow); + // HACK: We set the container dimensions manually + reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70); + ScrollTextEntry textEntry; + textEntry.bitmapHandle = bitmapHandle; + textEntry.kWindow = kWindow; + textEntry.x = x; + textEntry.y = y; + if (!replace || _scrollTexts.size() == 0) { + if (_scrollTexts.size() > _maxScrollTexts) { + _scrollTexts.remove_at(0); + _curScrollText--; + } + _scrollTexts.push_back(textEntry); + _curScrollText++; + } else { + _scrollTexts.pop_back(); + _scrollTexts.push_back(textEntry); + } +} + +void GfxFrameout::showCurrentScrollText() { + if (!_showScrollText || _curScrollText < 0) + return; + + uint16 size = (uint16)_scrollTexts.size(); + if (size > 0) { + assert(_curScrollText < size); + ScrollTextEntry textEntry = _scrollTexts[_curScrollText]; + g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y); + } } +extern void showScummVMDialog(const Common::String &message); + void GfxFrameout::kernelAddPlane(reg_t object) { PlaneEntry newPlane; if (_planes.empty()) { // There has to be another way for sierra sci to do this or maybe script resolution is compiled into // interpreter (TODO) - uint16 tmpRunningWidth = readSelectorValue(_segMan, object, SELECTOR(resX)); - uint16 tmpRunningHeight = readSelectorValue(_segMan, object, SELECTOR(resY)); + uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX)); + uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY)); - // The above can be 0 in SCI3 (e.g. Phantasmagoria 2) - if (tmpRunningWidth > 0 && tmpRunningHeight > 0) { - _scriptsRunningWidth = tmpRunningWidth; - _scriptsRunningHeight = tmpRunningHeight; + // Phantasmagoria 2 doesn't specify a script width/height + if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { + scriptWidth = 640; + scriptHeight = 480; } - _coordAdjuster->setScriptsResolution(_scriptsRunningWidth, _scriptsRunningHeight); + assert(scriptWidth > 0 && scriptHeight > 0); + _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight); + } + + // Import of QfG character files dialog is shown in QFG4. + // Display additional popup information before letting user use it. + // For the SCI0-SCI1.1 version of this, check kDrawControl(). + if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) { + showScummVMDialog("Characters saved inside ScummVM are shown " + "automatically. Character files saved in the original " + "interpreter need to be put inside ScummVM's saved games " + "directory and a prefix needs to be added depending on which " + "game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' " + "for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. " + "Example: 'qfg2-thief.sav'."); } newPlane.object = object; newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - newPlane.lastPriority = 0xFFFF; // hidden + newPlane.lastPriority = -1; // hidden newPlane.planeOffsetX = 0; newPlane.planeOffsetY = 0; - newPlane.pictureId = 0xFFFF; + newPlane.pictureId = kPlanePlainColored; newPlane.planePictureMirrored = false; newPlane.planeBack = 0; _planes.push_back(newPlane); @@ -111,7 +175,8 @@ void GfxFrameout::kernelUpdatePlane(reg_t object) { if (lastPictureId != it->pictureId) { // picture got changed, load new picture deletePlanePictures(object); - if ((it->pictureId != 0xFFFF) && (it->pictureId != 0xFFFE)) { + // Draw the plane's picture if it's not a translucent/plane colored frame + if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) { // SQ6 gives us a bad picture number for the control menu if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId))) addPlanePicture(object, it->pictureId, 0); @@ -122,11 +187,8 @@ void GfxFrameout::kernelUpdatePlane(reg_t object) { it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom)); it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right)); - Common::Rect screenRect(_screen->getWidth(), _screen->getHeight()); - it->planeRect.top = (it->planeRect.top * screenRect.height()) / _scriptsRunningHeight; - it->planeRect.left = (it->planeRect.left * screenRect.width()) / _scriptsRunningWidth; - it->planeRect.bottom = (it->planeRect.bottom * screenRect.height()) / _scriptsRunningHeight; - it->planeRect.right = (it->planeRect.right * screenRect.width()) / _scriptsRunningWidth; + _coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left); + _coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right); // We get negative left in kq7 in scrolling rooms if (it->planeRect.left < 0) { @@ -135,7 +197,7 @@ void GfxFrameout::kernelUpdatePlane(reg_t object) { } else { it->planeOffsetX = 0; } - + if (it->planeRect.top < 0) { it->planeOffsetY = -it->planeRect.top; it->planeRect.top = 0; @@ -191,11 +253,9 @@ void GfxFrameout::kernelDeletePlane(reg_t object) { planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom)); planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right)); - Common::Rect screenRect(_screen->getWidth(), _screen->getHeight()); - planeRect.top = (planeRect.top * screenRect.height()) / _scriptsRunningHeight; - planeRect.left = (planeRect.left * screenRect.width()) / _scriptsRunningWidth; - planeRect.bottom = (planeRect.bottom * screenRect.height()) / _scriptsRunningHeight; - planeRect.right = (planeRect.right * screenRect.width()) / _scriptsRunningWidth; + _coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left); + _coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right); + // Blackout removed plane rect _paint32->fillRect(planeRect, 0); return; @@ -204,6 +264,9 @@ void GfxFrameout::kernelDeletePlane(reg_t object) { } void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) { + if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent) // sanity check + return; + PlanePictureEntry newPicture; newPicture.object = object; newPicture.pictureId = pictureId; @@ -228,15 +291,76 @@ void GfxFrameout::deletePlanePictures(reg_t object) { } } +// Provides the same functionality as kGraph(DrawLine) +reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { + for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { + if (it->object == object) { + PlaneLineEntry line; + line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1); // we basically use this for a unique ID + line.startPoint = startPoint; + line.endPoint = endPoint; + line.color = color; + line.priority = priority; + line.control = control; + it->lines.push_back(line); + return line.hunkId; + } + } + + return NULL_REG; +} + +void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { + // Check if we're asked to update a line that was never added + if (hunkId.isNull()) + return; + + for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { + if (it->object == object) { + for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { + if (it2->hunkId == hunkId) { + it2->startPoint = startPoint; + it2->endPoint = endPoint; + it2->color = color; + it2->priority = priority; + it2->control = control; + return; + } + } + } + } +} + +void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) { + // Check if we're asked to delete a line that was never added (happens during the intro of LSL6) + if (hunkId.isNull()) + return; + + for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { + if (it->object == object) { + for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { + if (it2->hunkId == hunkId) { + _segMan->freeHunkEntry(hunkId); + it2 = it->lines.erase(it2); + return; + } + } + } + } +} + void GfxFrameout::kernelAddScreenItem(reg_t object) { // Ignore invalid items - if (!_segMan->isObject(object)) + if (!_segMan->isObject(object)) { + warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object)); return; + } FrameoutEntry *itemEntry = new FrameoutEntry(); memset(itemEntry, 0, sizeof(FrameoutEntry)); itemEntry->object = object; itemEntry->givenOrderNr = _screenItems.size(); + itemEntry->visible = true; _screenItems.push_back(itemEntry); kernelUpdateScreenItem(object); @@ -244,8 +368,10 @@ void GfxFrameout::kernelAddScreenItem(reg_t object) { void GfxFrameout::kernelUpdateScreenItem(reg_t object) { // Ignore invalid items - if (!_segMan->isObject(object)) + if (!_segMan->isObject(object)) { + warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object)); return; + } FrameoutEntry *itemEntry = findScreenItem(object); if (!itemEntry) { @@ -266,14 +392,18 @@ void GfxFrameout::kernelUpdateScreenItem(reg_t object) { itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal)); itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX)); itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY)); + itemEntry->visible = true; + + // Check if the entry can be hidden + if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone) + itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible)); } void GfxFrameout::kernelDeleteScreenItem(reg_t object) { FrameoutEntry *itemEntry = findScreenItem(object); - if (!itemEntry) { - warning("kernelDeleteScreenItem: invalid object %04x:%04x", PRINT_REG(object)); + // If the item could not be found, it may already have been deleted + if (!itemEntry) return; - } _screenItems.remove(itemEntry); delete itemEntry; @@ -290,7 +420,7 @@ void GfxFrameout::deletePlaneItems(reg_t planeObject) { } else { objectMatches = true; } - + if (objectMatches) { FrameoutEntry *itemEntry = *listIterator; listIterator = _screenItems.erase(listIterator); @@ -330,15 +460,10 @@ bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) { } bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) { -// SegManager *segMan = g_sci->getEngineState()->_segMan; - -// uint16 plane1Priority = readSelectorValue(segMan, entry1, SELECTOR(priority)); -// uint16 plane2Priority = readSelectorValue(segMan, entry2, SELECTOR(priority)); - - if (entry1.priority == 0xffff) + if (entry1.priority < 0) return true; - if (entry2.priority == 0xffff) + if (entry2.priority < 0) return false; return entry1.priority < entry2.priority; @@ -357,23 +482,6 @@ void GfxFrameout::sortPlanes() { Common::sort(_planes.begin(), _planes.end(), planeSortHelper); } -int16 GfxFrameout::upscaleHorizontalCoordinate(int16 coordinate) { - return ((coordinate * _screen->getWidth()) / _scriptsRunningWidth); -} - -int16 GfxFrameout::upscaleVerticalCoordinate(int16 coordinate) { - return ((coordinate * _screen->getHeight()) / _scriptsRunningHeight); -} - -Common::Rect GfxFrameout::upscaleRect(Common::Rect &rect) { - rect.top = (rect.top * _scriptsRunningHeight) / _screen->getHeight(); - rect.left = (rect.left * _scriptsRunningWidth) / _screen->getWidth(); - rect.bottom = (rect.bottom * _scriptsRunningHeight) / _screen->getHeight(); - rect.right = (rect.right * _scriptsRunningWidth) / _screen->getWidth(); - - return rect; -} - void GfxFrameout::showVideo() { bool skipVideo = false; RobotDecoder *videoDecoder = g_sci->_robotDecoder; @@ -381,16 +489,16 @@ void GfxFrameout::showVideo() { uint16 y = videoDecoder->getPos().y; if (videoDecoder->hasDirtyPalette()) - videoDecoder->setSystemPalette(); + g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { if (videoDecoder->needsUpdate()) { const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); if (frame) { - g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); + g_system->copyRectToScreen(frame->pixels, frame->pitch, x, y, frame->w, frame->h); if (videoDecoder->hasDirtyPalette()) - videoDecoder->setSystemPalette(); + g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); g_system->updateScreen(); } @@ -433,6 +541,7 @@ void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) picEntry->x = planePicture->getSci32celX(pictureCelNr); picEntry->picStartX = pictureIt->startX; picEntry->picStartY = pictureIt->startY; + picEntry->visible = true; picEntry->priority = planePicture->getSci32celPriority(pictureCelNr); @@ -507,13 +616,26 @@ void GfxFrameout::kernelFrameout() { for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) { reg_t planeObject = it->object; - uint16 planeLastPriority = it->lastPriority; + + // Draw any plane lines, if they exist + // These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires) + // FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires). + // Perhaps something is painted over them? + for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { + Common::Point startPoint = it2->startPoint; + Common::Point endPoint = it2->endPoint; + _coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object); + _coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object); + _screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control); + } + + int16 planeLastPriority = it->lastPriority; // Update priority here, sq6 sets it w/o UpdatePlane - uint16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority)); + int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority)); it->lastPriority = planePriority; - if (planePriority == 0xffff) { // Plane currently not meant to be shown + if (planePriority < 0) { // Plane currently not meant to be shown // If plane was shown before, delete plane rect if (planePriority != planeLastPriority) _paint32->fillRect(it->planeRect, 0); @@ -523,44 +645,40 @@ void GfxFrameout::kernelFrameout() { // There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX. // Since I first wrote the patch, the race has stopped occurring for me though. // I'll leave this for investigation later, when someone can reproduce. - //if (it->pictureId == 0xffff) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes) - if (it->planeBack) + //if (it->pictureId == kPlanePlainColored) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes) + if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1)) _paint32->fillRect(it->planeRect, it->planeBack); - GuiResourceId planeMainPictureId = it->pictureId; - _coordAdjuster->pictureSetDisplayArea(it->planeRect); - _palette->drewPicture(planeMainPictureId); + _palette->drewPicture(it->pictureId); FrameoutList itemList; createPlaneItemList(planeObject, itemList); -// warning("Plane %s", _segMan->getObjectName(planeObject)); - for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) { FrameoutEntry *itemEntry = *listIterator; + if (!itemEntry->visible) + continue; + if (itemEntry->object.isNull()) { // Picture cel data - itemEntry->x = upscaleHorizontalCoordinate(itemEntry->x); - itemEntry->y = upscaleVerticalCoordinate(itemEntry->y); - itemEntry->picStartX = upscaleHorizontalCoordinate(itemEntry->picStartX); - itemEntry->picStartY = upscaleVerticalCoordinate(itemEntry->picStartY); + _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x); + _coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX); if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY)) drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored); } else { GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL; - + int16 dummyX = 0; + if (view && view->isSci2Hires()) { - int16 dummyX = 0; view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x); view->adjustToUpscaledCoordinates(itemEntry->z, dummyX); - } else if (getSciVersion() == SCI_VERSION_2_1) { - itemEntry->x = upscaleHorizontalCoordinate(itemEntry->x); - itemEntry->y = upscaleVerticalCoordinate(itemEntry->y); - itemEntry->z = upscaleVerticalCoordinate(itemEntry->z); + } else if (getSciVersion() >= SCI_VERSION_2_1) { + _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x); + _coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX); } // Adjust according to current scroll position @@ -581,13 +699,13 @@ void GfxFrameout::kernelFrameout() { // TODO: maybe we should clip the cels rect with this, i'm not sure // the only currently known usage is game menu of gk1 } else if (view) { - if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->getCelRect(itemEntry->loopNo, itemEntry->celNo, - itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect); - else - view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo, - itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX, - itemEntry->scaleY, itemEntry->celRect); + if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) + view->getCelRect(itemEntry->loopNo, itemEntry->celNo, + itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect); + else + view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo, + itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX, + itemEntry->scaleY, itemEntry->celRect); Common::Rect nsRect = itemEntry->celRect; // Translate back to actual coordinate within scrollable plane @@ -596,8 +714,9 @@ void GfxFrameout::kernelFrameout() { if (view && view->isSci2Hires()) { view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left); view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right); - } else if (getSciVersion() == SCI_VERSION_2_1) { - nsRect = upscaleRect(nsRect); + } else if (getSciVersion() >= SCI_VERSION_2_1) { + _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left); + _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right); } if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { @@ -610,17 +729,11 @@ void GfxFrameout::kernelFrameout() { g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); } - int16 screenHeight = _screen->getHeight(); - int16 screenWidth = _screen->getWidth(); - if (view && view->isSci2Hires()) { - screenHeight = _screen->getDisplayHeight(); - screenWidth = _screen->getDisplayWidth(); - } - - if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= screenHeight) - continue; - - if (itemEntry->celRect.right < 0 || itemEntry->celRect.left >= screenWidth) + // Don't attempt to draw sprites that are outside the visible + // screen area. An example is the random people walking in + // Jackson Square in GK1. + if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= _screen->getDisplayHeight() || + itemEntry->celRect.right < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth()) continue; Common::Rect clipRect, translatedClipRect; @@ -631,6 +744,9 @@ void GfxFrameout::kernelFrameout() { translatedClipRect = clipRect; translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top); } else { + // QFG4 passes invalid rectangles when a battle is starting + if (!clipRect.isValidRect()) + continue; clipRect.clip(it->planeClipRect); translatedClipRect = clipRect; translatedClipRect.translate(it->planeRect.left, it->planeRect.top); @@ -639,10 +755,10 @@ void GfxFrameout::kernelFrameout() { if (view) { if (!clipRect.isEmpty()) { if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->draw(itemEntry->celRect, clipRect, translatedClipRect, + view->draw(itemEntry->celRect, clipRect, translatedClipRect, itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires()); else - view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect, + view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect, itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY); } } @@ -662,9 +778,61 @@ void GfxFrameout::kernelFrameout() { } } + showCurrentScrollText(); + _screen->copyToScreen(); g_sci->getEngineState()->_throttleTrigger = true; } +void GfxFrameout::printPlaneList(Console *con) { + for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) { + PlaneEntry p = *it; + Common::String curPlaneName = _segMan->getObjectName(p.object); + Common::Rect r = p.upscaledPlaneRect; + Common::Rect cr = p.upscaledPlaneClipRect; + + con->DebugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n", + PRINT_REG(p.object), curPlaneName.c_str(), + (int16)p.priority, (int16)p.lastPriority, + p.planeOffsetX, p.planeOffsetY, p.pictureId, + p.planePictureMirrored, p.planeBack); + con->DebugPrintf(" rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n", + r.left, r.top, r.right, r.bottom, + cr.left, cr.top, cr.right, cr.bottom); + + if (p.pictureId != 0xffff && p.pictureId != 0xfffe) { + con->DebugPrintf("Pictures:\n"); + + for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { + if (pictureIt->object == p.object) { + con->DebugPrintf(" Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY); + } + } + } + } +} + +void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) { + for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { + FrameoutEntry *e = *listIterator; + reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane)); + + if (planeObject == itemPlane) { + Common::String curItemName = _segMan->getObjectName(e->object); + Common::Rect icr = e->celRect; + GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0; + + con->DebugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, " + "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), " + "pic %d, picX %d, picY %d, visible %d\n", + e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(), + e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z, + e->signal, e->scaleSignal, e->scaleX, e->scaleY, + icr.left, icr.top, icr.right, icr.bottom, + picId, e->picStartX, e->picStartY, e->visible); + } + } +} + } // End of namespace Sci diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 8c3cc261d5..5fd2824224 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -27,10 +27,21 @@ namespace Sci { class GfxPicture; +struct PlaneLineEntry { + reg_t hunkId; + Common::Point startPoint; + Common::Point endPoint; + byte color; + byte priority; + byte control; +}; + +typedef Common::List<PlaneLineEntry> PlaneLineList; + struct PlaneEntry { reg_t object; - uint16 priority; - uint16 lastPriority; + int16 priority; + int16 lastPriority; int16 planeOffsetX; int16 planeOffsetY; GuiResourceId pictureId; @@ -40,6 +51,7 @@ struct PlaneEntry { Common::Rect upscaledPlaneClipRect; bool planePictureMirrored; byte planeBack; + PlaneLineList lines; }; typedef Common::List<PlaneEntry> PlaneList; @@ -60,6 +72,7 @@ struct FrameoutEntry { GfxPicture *picture; int16 picStartX; int16 picStartY; + bool visible; }; typedef Common::List<FrameoutEntry *> FrameoutList; @@ -75,6 +88,15 @@ struct PlanePictureEntry { typedef Common::List<PlanePictureEntry> PlanePictureList; +struct ScrollTextEntry { + reg_t bitmapHandle; + reg_t kWindow; + uint16 x; + uint16 y; +}; + +typedef Common::Array<ScrollTextEntry> ScrollTextList; + class GfxCache; class GfxCoordAdjuster32; class GfxPaint32; @@ -102,16 +124,30 @@ public: void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0); void deletePlanePictures(reg_t object); + reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control); + void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control); + void deletePlaneLine(reg_t object, reg_t hunkId); void clear(); + // Scroll text functions + void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace); + void showCurrentScrollText(); + void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; } + void clearScrollTexts(); + void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; } + void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; } + void prevScrollText() { if (_curScrollText > 0) _curScrollText--; } + void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; } + void toggleScrollText(bool show) { _showScrollText = show; } + + void printPlaneList(Console *con); + void printPlaneItemList(Console *con, reg_t planeObject); + private: void showVideo(); void createPlaneItemList(reg_t planeObject, FrameoutList &itemList); bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY); void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored); - int16 upscaleHorizontalCoordinate(int16 coordinate); - int16 upscaleVerticalCoordinate(int16 coordinate); - Common::Rect upscaleRect(Common::Rect &rect); SegManager *_segMan; ResourceManager *_resMan; @@ -124,11 +160,12 @@ private: FrameoutList _screenItems; PlaneList _planes; PlanePictureList _planePictures; + ScrollTextList _scrollTexts; + int16 _curScrollText; + bool _showScrollText; + uint16 _maxScrollTexts; void sortPlanes(); - - uint16 _scriptsRunningWidth; - uint16 _scriptsRunningHeight; }; } // End of namespace Sci diff --git a/engines/sci/graphics/maciconbar.cpp b/engines/sci/graphics/maciconbar.cpp index 7ecba5a24d..dfb50b0edb 100644 --- a/engines/sci/graphics/maciconbar.cpp +++ b/engines/sci/graphics/maciconbar.cpp @@ -129,7 +129,7 @@ void GfxMacIconBar::drawIcon(uint16 iconIndex, bool selected) { void GfxMacIconBar::drawEnabledImage(Graphics::Surface *surface, const Common::Rect &rect) { if (surface) - g_system->copyRectToScreen((byte *)surface->pixels, surface->pitch, rect.left, rect.top, rect.width(), rect.height()); + g_system->copyRectToScreen(surface->pixels, surface->pitch, rect.left, rect.top, rect.width(), rect.height()); } void GfxMacIconBar::drawDisabledImage(Graphics::Surface *surface, const Common::Rect &rect) { @@ -153,7 +153,7 @@ void GfxMacIconBar::drawDisabledImage(Graphics::Surface *surface, const Common:: *((byte *)newSurf.getBasePtr(j, i)) = 0; } - g_system->copyRectToScreen((byte *)newSurf.pixels, newSurf.pitch, rect.left, rect.top, rect.width(), rect.height()); + g_system->copyRectToScreen(newSurf.pixels, newSurf.pitch, rect.left, rect.top, rect.width(), rect.height()); newSurf.free(); } diff --git a/engines/sci/graphics/menu.cpp b/engines/sci/graphics/menu.cpp index 47f34cf99d..bfecc296a2 100644 --- a/engines/sci/graphics/menu.cpp +++ b/engines/sci/graphics/menu.cpp @@ -219,7 +219,7 @@ void GfxMenu::kernelAddEntry(Common::String title, Common::String content, reg_t } } itemEntry->textVmPtr = contentVmPtr; - itemEntry->textVmPtr.offset += beginPos; + itemEntry->textVmPtr.incOffset(beginPos); if (rightAlignedPos) { rightAlignedPos++; @@ -297,13 +297,13 @@ void GfxMenu::kernelSetAttribute(uint16 menuId, uint16 itemId, uint16 attributeI // We assume here that no script ever creates a separatorLine dynamically break; case SCI_MENU_ATTRIBUTE_KEYPRESS: - itemEntry->keyPress = tolower(value.offset); + itemEntry->keyPress = tolower(value.getOffset()); itemEntry->keyModifier = 0; // TODO: Find out how modifier is handled - debug("setAttr keypress %X %X", value.segment, value.offset); + debug("setAttr keypress %X %X", value.getSegment(), value.getOffset()); break; case SCI_MENU_ATTRIBUTE_TAG: - itemEntry->tag = value.offset; + itemEntry->tag = value.getOffset(); break; default: // Happens when loading a game in LSL3 - attribute 1A diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index c951f3349d..d20aa80c77 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -491,10 +491,10 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { // processing codes in argv while (argc > 0) { displayArg = argv[0]; - if (displayArg.segment) - displayArg.offset = 0xFFFF; + if (displayArg.getSegment()) + displayArg.setOffset(0xFFFF); argc--; argv++; - switch (displayArg.offset) { + switch (displayArg.getOffset()) { case SCI_DISPLAY_MOVEPEN: _ports->moveTo(argv[0].toUint16(), argv[1].toUint16()); argc -= 2; argv += 2; @@ -547,9 +547,9 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { if (!(g_sci->getGameId() == GID_LONGBOW && g_sci->isDemo()) && !(g_sci->getGameId() == GID_QFG1 && g_sci->isDemo()) && !(g_sci->getGameId() == GID_PQ2)) - error("Unknown kDisplay argument %d", displayArg.offset); + error("Unknown kDisplay argument %d", displayArg.getOffset()); - if (displayArg.offset == SCI_DISPLAY_DUMMY2) { + if (displayArg.getOffset() == SCI_DISPLAY_DUMMY2) { if (!argc) error("No parameter left for kDisplay(115)"); argc--; argv++; @@ -559,8 +559,8 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { SciTrackOriginReply originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kDisplay_workarounds, &originReply); if (solution.type == WORKAROUND_NONE) - error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)", - PRINT_REG(displayArg), originReply.objectName.c_str(), originReply.methodName.c_str(), + error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)", + PRINT_REG(displayArg), originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); assert(solution.type == WORKAROUND_IGNORE); break; diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp index 47d1647c6c..53d69cdcca 100644 --- a/engines/sci/graphics/palette.cpp +++ b/engines/sci/graphics/palette.cpp @@ -100,6 +100,9 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen) default: error("GfxPalette: Unknown view type"); } + + _remapOn = false; + resetRemapping(); } GfxPalette::~GfxPalette() { @@ -140,8 +143,9 @@ void GfxPalette::createFromData(byte *data, int bytesLeft, Palette *paletteOut) memset(paletteOut, 0, sizeof(Palette)); // Setup 1:1 mapping - for (colorNo = 0; colorNo < 256; colorNo++) + for (colorNo = 0; colorNo < 256; colorNo++) { paletteOut->mapping[colorNo] = colorNo; + } if (bytesLeft < 37) { // This happens when loading palette of picture 0 in sq5 - the resource is broken and doesn't contain a full @@ -329,6 +333,79 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) { } } +byte GfxPalette::remapColor(byte remappedColor, byte screenColor) { + assert(_remapOn); + if (_remappingType[remappedColor] == kRemappingByRange) + return _remappingByRange[screenColor]; + else if (_remappingType[remappedColor] == kRemappingByPercent) + return _remappingByPercent[screenColor]; + else + error("remapColor(): Color %d isn't remapped", remappedColor); + + return 0; // should never reach here +} + +void GfxPalette::resetRemapping() { + _remapOn = false; + _remappingPercentToSet = 0; + + for (int i = 0; i < 256; i++) { + _remappingType[i] = kRemappingNone; + _remappingByPercent[i] = i; + _remappingByRange[i] = i; + } +} + +void GfxPalette::setRemappingPercent(byte color, byte percent) { + _remapOn = true; + + // We need to defer the setup of the remapping table every time the screen + // palette is changed, so that kernelFindColor() can find the correct + // colors. Set it once here, in case the palette stays the same and update + // it on each palette change by copySysPaletteToScreen(). + _remappingPercentToSet = percent; + + for (int i = 0; i < 256; i++) { + byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100; + byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100; + byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100; + _remappingByPercent[i] = kernelFindColor(r, g, b); + } + + _remappingType[color] = kRemappingByPercent; +} + +void GfxPalette::setRemappingPercentGray(byte color, byte percent) { + _remapOn = true; + + // We need to defer the setup of the remapping table every time the screen + // palette is changed, so that kernelFindColor() can find the correct + // colors. Set it once here, in case the palette stays the same and update + // it on each palette change by copySysPaletteToScreen(). + _remappingPercentToSet = percent; + + // Note: This is not what the original does, but the results are the same visually + for (int i = 0; i < 256; i++) { + byte rComponent = _sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100; + byte gComponent = _sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100; + byte bComponent = _sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100; + byte luminosity = rComponent + gComponent + bComponent; + _remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity); + } + + _remappingType[color] = kRemappingByPercent; +} + +void GfxPalette::setRemappingRange(byte color, byte from, byte to, byte base) { + _remapOn = true; + + for (int i = from; i <= to; i++) { + _remappingByRange[i] = i + base; + } + + _remappingType[color] = kRemappingByRange; +} + bool GfxPalette::insert(Palette *newPalette, Palette *destPalette) { bool paletteChanged = false; @@ -491,6 +568,16 @@ void GfxPalette::copySysPaletteToScreen() { } } + // Check if we need to reset remapping by percent with the new colors. + if (_remappingPercentToSet) { + for (int i = 0; i < 256; i++) { + byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100; + byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100; + byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100; + _remappingByPercent[i] = kernelFindColor(r, g, b); + } + } + g_system->getPaletteManager()->setPalette(bpal, 0, 256); } @@ -698,7 +785,7 @@ void GfxPalette::palVaryInit() { } bool GfxPalette::palVaryLoadTargetPalette(GuiResourceId resourceId) { - _palVaryResourceId = resourceId; + _palVaryResourceId = (resourceId != 65535) ? resourceId : -1; Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); if (palResource) { // Load and initialize destination palette @@ -999,8 +1086,9 @@ bool GfxPalette::loadClut(uint16 clutId) { memset(&pal, 0, sizeof(Palette)); // Setup 1:1 mapping - for (int i = 0; i < 256; i++) + for (int i = 0; i < 256; i++) { pal.mapping[i] = i; + } // Now load in the palette for (int i = 1; i <= 236; i++) { diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h index a9ea1c32de..e974781d49 100644 --- a/engines/sci/graphics/palette.h +++ b/engines/sci/graphics/palette.h @@ -31,6 +31,12 @@ namespace Sci { class ResourceManager; class GfxScreen; +enum ColorRemappingType { + kRemappingNone = 0, + kRemappingByRange = 1, + kRemappingByPercent = 2 +}; + /** * Palette class, handles palette operations like changing intensity, setting up the palette, merging different palettes */ @@ -53,6 +59,15 @@ public: void getSys(Palette *pal); uint16 getTotalColorCount() const { return _totalScreenColors; } + void resetRemapping(); + void setRemappingPercent(byte color, byte percent); + void setRemappingPercentGray(byte color, byte percent); + void setRemappingRange(byte color, byte from, byte to, byte base); + bool isRemapped(byte color) const { + return _remapOn && (_remappingType[color] != kRemappingNone); + } + byte remapColor(byte remappedColor, byte screenColor); + void setOnScreen(); void copySysPaletteToScreen(); @@ -123,6 +138,12 @@ private: int _palVarySignal; uint16 _totalScreenColors; + bool _remapOn; + ColorRemappingType _remappingType[256]; + byte _remappingByPercent[256]; + byte _remappingByRange[256]; + uint16 _remappingPercentToSet; + void loadMacIconBarPalette(); byte *_macClut; diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp index 6b4c8180bf..8acdeed763 100644 --- a/engines/sci/graphics/ports.cpp +++ b/engines/sci/graphics/ports.cpp @@ -380,17 +380,50 @@ Window *GfxPorts::addWindow(const Common::Rect &dims, const Common::Rect *restor int16 oldtop = pwnd->dims.top; int16 oldleft = pwnd->dims.left; - if (wmprect.top > pwnd->dims.top) + // WORKAROUND: We also adjust the restore rect when adjusting the window + // rect. + // SSCI does not do this. It wasn't necessary in the original interpreter, + // but it is needed for Freddy Pharkas CD. This version does not normally + // have text, but we allow this by modifying the text/speech setting + // according to what is set in the ScummVM GUI (refer to syncIngameAudioOptions() + // in sci.cpp). Since the text used in Freddy Pharkas CD is quite large in + // some cases, it ends up being offset in order to fit inside the screen, + // but the associated restore rect isn't adjusted accordingly, leading to + // artifacts being left on screen when some text boxes are removed. The + // fact that the restore rect wasn't ever adjusted doesn't make sense, and + // adjusting it shouldn't have any negative side-effects (it *should* be + // adjusted, normally, but SCI doesn't do it). The big text boxes are still + // odd-looking, because the text rect is drawn outside the text window rect, + // but at least there aren't any leftover textbox artifacts left when the + // boxes are removed. Adjusting the text window rect would require more + // invasive changes than this one, thus it's not really worth the effort + // for a feature that was not present in the original game, and its + // implementation is buggy in the first place. + // Adjusting the restore rect properly fixes bug #3575276. + + if (wmprect.top > pwnd->dims.top) { pwnd->dims.moveTo(pwnd->dims.left, wmprect.top); + if (restoreRect) + pwnd->restoreRect.moveTo(pwnd->restoreRect.left, wmprect.top); + } - if (wmprect.bottom < pwnd->dims.bottom) + if (wmprect.bottom < pwnd->dims.bottom) { pwnd->dims.moveTo(pwnd->dims.left, wmprect.bottom - pwnd->dims.bottom + pwnd->dims.top); + if (restoreRect) + pwnd->restoreRect.moveTo(pwnd->restoreRect.left, wmprect.bottom - pwnd->restoreRect.bottom + pwnd->restoreRect.top); + } - if (wmprect.right < pwnd->dims.right) + if (wmprect.right < pwnd->dims.right) { pwnd->dims.moveTo(wmprect.right + pwnd->dims.left - pwnd->dims.right, pwnd->dims.top); + if (restoreRect) + pwnd->restoreRect.moveTo(wmprect.right + pwnd->restoreRect.left - pwnd->restoreRect.right, pwnd->restoreRect.top); + } - if (wmprect.left > pwnd->dims.left) + if (wmprect.left > pwnd->dims.left) { pwnd->dims.moveTo(wmprect.left, pwnd->dims.top); + if (restoreRect) + pwnd->restoreRect.moveTo(wmprect.left, pwnd->restoreRect.top); + } pwnd->rect.moveTo(pwnd->rect.left + pwnd->dims.left - oldleft, pwnd->rect.top + pwnd->dims.top - oldtop); diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index 4020518b72..246b6bfff9 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -53,23 +53,35 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { #ifdef ENABLE_SCI32 // GK1 Mac uses a 640x480 resolution too - if (g_sci->getGameId() == GID_GK1 && g_sci->getPlatform() == Common::kPlatformMacintosh) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; + if (g_sci->getPlatform() == Common::kPlatformMacintosh) { + if (g_sci->getGameId() == GID_GK1) + _upscaledHires = GFX_SCREEN_UPSCALED_640x480; + } #endif if (_resMan->detectHires()) { _width = 640; + _pitch = 640; _height = 480; } else { _width = 320; + _pitch = 320; _height = getLowResScreenHeight(); } +#ifdef ENABLE_SCI32 + // Phantasmagoria 1 sets a window area of 630x450 + if (g_sci->getGameId() == GID_PHANTASMAGORIA) { + _width = 630; + _height = 450; + } +#endif + // Japanese versions of games use hi-res font on upscaled version of the game. if ((g_sci->getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1)) _upscaledHires = GFX_SCREEN_UPSCALED_640x400; - _pixels = _width * _height; + _pixels = _pitch * _height; switch (_upscaledHires) { case GFX_SCREEN_UPSCALED_640x400: @@ -91,7 +103,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { _upscaledMapping[i] = (i * 12) / 5; break; default: - _displayWidth = _width; + _displayWidth = _pitch; _displayHeight = _height; memset(&_upscaledMapping, 0, sizeof(_upscaledMapping) ); break; @@ -207,7 +219,7 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) { } void GfxScreen::putPixel(int x, int y, byte drawMask, byte color, byte priority, byte control) { - int offset = y * _width + x; + int offset = y * _pitch + x; if (drawMask & GFX_SCREEN_MASK_VISUAL) { _visualScreen[offset] = color; @@ -240,7 +252,7 @@ void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) { // Do not scale ourselves, but put it on the display directly putPixelOnDisplay(x, y + startingY, color); } else { - int offset = (startingY + y) * _width + x; + int offset = (startingY + y) * _pitch + x; _visualScreen[offset] = color; if (!_upscaledHires) { @@ -342,19 +354,19 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u } byte GfxScreen::getVisual(int x, int y) { - return _visualScreen[y * _width + x]; + return _visualScreen[y * _pitch + x]; } byte GfxScreen::getPriority(int x, int y) { - return _priorityScreen[y * _width + x]; + return _priorityScreen[y * _pitch + x]; } byte GfxScreen::getControl(int x, int y) { - return _controlScreen[y * _width + x]; + return _controlScreen[y * _pitch + x]; } byte GfxScreen::isFillMatch(int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) { - int offset = y * _width + x; + int offset = y * _pitch + x; byte match = 0; if (screenMask & GFX_SCREEN_MASK_VISUAL) { @@ -415,14 +427,14 @@ void GfxScreen::bitsSave(Common::Rect rect, byte mask, byte *memoryPtr) { memcpy(memoryPtr, (void *)&mask, sizeof(mask)); memoryPtr += sizeof(mask); if (mask & GFX_SCREEN_MASK_VISUAL) { - bitsSaveScreen(rect, _visualScreen, _width, memoryPtr); + bitsSaveScreen(rect, _visualScreen, _pitch, memoryPtr); bitsSaveDisplayScreen(rect, memoryPtr); } if (mask & GFX_SCREEN_MASK_PRIORITY) { - bitsSaveScreen(rect, _priorityScreen, _width, memoryPtr); + bitsSaveScreen(rect, _priorityScreen, _pitch, memoryPtr); } if (mask & GFX_SCREEN_MASK_CONTROL) { - bitsSaveScreen(rect, _controlScreen, _width, memoryPtr); + bitsSaveScreen(rect, _controlScreen, _pitch, memoryPtr); } if (mask & GFX_SCREEN_MASK_DISPLAY) { if (!_upscaledHires) @@ -475,14 +487,14 @@ void GfxScreen::bitsRestore(byte *memoryPtr) { memcpy((void *)&mask, memoryPtr, sizeof(mask)); memoryPtr += sizeof(mask); if (mask & GFX_SCREEN_MASK_VISUAL) { - bitsRestoreScreen(rect, memoryPtr, _visualScreen, _width); + bitsRestoreScreen(rect, memoryPtr, _visualScreen, _pitch); bitsRestoreDisplayScreen(rect, memoryPtr); } if (mask & GFX_SCREEN_MASK_PRIORITY) { - bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _width); + bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _pitch); } if (mask & GFX_SCREEN_MASK_CONTROL) { - bitsRestoreScreen(rect, memoryPtr, _controlScreen, _width); + bitsRestoreScreen(rect, memoryPtr, _controlScreen, _pitch); } if (mask & GFX_SCREEN_MASK_DISPLAY) { if (!_upscaledHires) @@ -560,7 +572,7 @@ void GfxScreen::dither(bool addToFlag) { if (!_unditheringEnabled) { // Do dithering on visual and display-screen for (y = 0; y < _height; y++) { - for (x = 0; x < _width; x++) { + for (x = 0; x < _pitch; x++) { color = *visualPtr; if (color & 0xF0) { color ^= color << 4; @@ -585,7 +597,7 @@ void GfxScreen::dither(bool addToFlag) { memset(&_ditheredPicColors, 0, sizeof(_ditheredPicColors)); // Do dithering on visual screen and put decoded but undithered byte onto display-screen for (y = 0; y < _height; y++) { - for (x = 0; x < _width; x++) { + for (x = 0; x < _pitch; x++) { color = *visualPtr; if (color & 0xF0) { color ^= color << 4; diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index 73ea596ba1..01fb899edb 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -132,6 +132,7 @@ public: private: uint16 _width; + uint16 _pitch; uint16 _height; uint _pixels; uint16 _displayWidth; diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index 7894c7109c..f14ae2ef0b 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -49,9 +49,12 @@ GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) GfxText32::~GfxText32() { } +reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { + return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); + +} reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text)); - // The object in the text selector of the item can be either a raw string // or a Str object. In the latter case, we need to access the object's data // selector to get the raw string. @@ -59,6 +62,11 @@ reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxH stringObject = readSelector(_segMan, stringObject, SELECTOR(data)); Common::String text = _segMan->getString(stringObject); + + return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); +} + +reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { // HACK: The character offsets of the up and down arrow buttons are off by one // in GK1, for some unknown reason. Fix them here. if (text.size() == 1 && (text[0] == 29 || text[0] == 30)) { @@ -91,7 +99,11 @@ reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxH reg_t memoryId = NULL_REG; if (prevHunk.isNull()) { memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize); - writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId); + + // Scroll text objects have no bitmap selector! + ObjVarRef varp; + if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable) + writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId); } else { memoryId = prevHunk; } @@ -153,10 +165,24 @@ reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxH warning("Invalid alignment %d used in TextBox()", alignment); } + byte curChar; + for (int i = 0; i < charCount; i++) { - unsigned char curChar = txt[i]; - font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height); - curX += font->getCharWidth(curChar); + curChar = txt[i]; + + switch (curChar) { + case 0x0A: + case 0x0D: + case 0: + break; + case 0x7C: + warning("Code processing isn't implemented in SCI32"); + break; + default: + font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height); + curX += font->getCharWidth(curChar); + break; + } } curX = 0; @@ -175,7 +201,25 @@ void GfxText32::disposeTextBitmap(reg_t hunkId) { void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) { reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap)); - uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back)); + drawTextBitmapInternal(x, y, planeRect, textObject, hunkId); +} + +void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) { + /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane)); + Common::Rect planeRect; + planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top)); + planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left)); + planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom)); + planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right)); + + drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/ + + // HACK: we pretty much ignore the plane rect and x, y... + drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId); +} + +void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) { + int16 backColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(back)); // Sanity check: Check if the hunk is set. If not, either the game scripts // didn't set it, or an old saved game has been loaded, where it wasn't set. if (hunkId.isNull()) @@ -187,13 +231,17 @@ void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t t byte *memoryPtr = _segMan->getHunkPointer(hunkId); - if (!memoryPtr) - error("Attempt to draw an invalid text bitmap"); + if (!memoryPtr) { + // Happens when restoring in some SCI32 games (e.g. SQ6). + // Commented out to reduce console spam + //warning("Attempt to draw an invalid text bitmap"); + return; + } byte *surface = memoryPtr + BITMAP_HEADER_SIZE; int curByte = 0; - uint16 skipColor = readSelectorValue(_segMan, textObject, SELECTOR(skip)); + int16 skipColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(skip)); uint16 textX = planeRect.left + x; uint16 textY = planeRect.top + y; // Get totalWidth, totalHeight @@ -206,10 +254,13 @@ void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t t textY = textY * _screen->getDisplayHeight() / _screen->getHeight(); } + bool translucent = (skipColor == -1 && backColor == -1); + for (int curY = 0; curY < height; curY++) { for (int curX = 0; curX < width; curX++) { byte pixel = surface[curByte++]; - if (pixel != skipColor && pixel != backColor) + if ((!translucent && pixel != skipColor && pixel != backColor) || + (translucent && pixel != 0xFF)) _screen->putFontPixel(textY, curX + textX, curY, pixel); } } @@ -265,7 +316,7 @@ void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWi } void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fontId, int16 &textWidth, int16 &textHeight, bool restoreFont) { - uint16 curChar; + byte curChar; textWidth = 0; textHeight = 0; GfxFont *font = _cache->getFont(fontId); @@ -277,7 +328,6 @@ void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fon switch (curChar) { case 0x0A: case 0x0D: - case 0x9781: // this one is used by SQ4/japanese as line break as well textHeight = MAX<int16> (textHeight, font->getHeight()); break; case 0x7C: diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 3505de85eb..ce78003fdf 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -33,13 +33,17 @@ public: GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen); ~GfxText32(); reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); - void disposeTextBitmap(reg_t hunkId); + reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject); + void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y); + void disposeTextBitmap(reg_t hunkId); int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font); void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); private: + reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId); + void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId); int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth); void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont); void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight); diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index a77bcccc52..36aaae9232 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -720,6 +720,19 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const bitmap += (clipRect.top - rect.top) * celWidth + (clipRect.left - rect.left); + // WORKAROUND: EcoQuest French and German draw the fish and anemone sprites + // with priority 15 in scene 440. Afterwards, a dialog is shown on top of + // these sprites with priority 15 as well. This is undefined behavior + // actually, as the sprites and dialog share the same priority, so in our + // implementation the sprites get drawn incorrectly on top of the dialog. + // Perhaps this worked by mistake in SSCI because of subtle differences in + // how sprites are drawn. We compensate for this by resetting the priority + // of all sprites that have a priority of 15 in scene 440 to priority 14, + // so that the speech bubble can be drawn correctly on top of them. Fixes + // bug #3040625. + if (g_sci->getGameId() == GID_ECOQUEST && g_sci->getEngineState()->currentRoomNumber() == 440 && priority == 15) + priority = 14; + if (!_EGAmapping) { for (y = 0; y < height; y++, bitmap += celWidth) { for (x = 0; x < width; x++) { @@ -728,8 +741,14 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const const int x2 = clipRectTranslated.left + x; const int y2 = clipRectTranslated.top + y; if (!upscaledHires) { - if (priority >= _screen->getPriority(x2, y2)) - _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); + if (priority >= _screen->getPriority(x2, y2)) { + if (!_palette->isRemapped(palette->mapping[color])) { + _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); + } else { + byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2)); + _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0); + } + } } else { // UpscaledHires means view is hires and is supposed to // get drawn onto lowres screen. @@ -838,7 +857,12 @@ void GfxView::drawScaled(const Common::Rect &rect, const Common::Rect &clipRect, const int x2 = clipRectTranslated.left + x; const int y2 = clipRectTranslated.top + y; if (color != clearKey && priority >= _screen->getPriority(x2, y2)) { - _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); + if (!_palette->isRemapped(palette->mapping[color])) { + _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); + } else { + byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2)); + _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0); + } } } } diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 90a0f33f06..6b6058c819 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -10,6 +10,7 @@ MODULE_OBJS := \ sci.o \ util.o \ engine/features.o \ + engine/file.o \ engine/gc.o \ engine/kernel.o \ engine/kevent.o \ @@ -79,6 +80,7 @@ MODULE_OBJS := \ ifdef ENABLE_SCI32 MODULE_OBJS += \ + engine/kgraphics32.o \ graphics/controls32.o \ graphics/frameout.o \ graphics/paint32.o \ diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 77a6a40a92..c9c0d25bc6 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -93,7 +93,7 @@ const char *getSciVersionDesc(SciVersion version) { //#define SCI_VERBOSE_RESMAN 1 -static const char *const sci_error_types[] = { +static const char *const s_errorDescriptions[] = { "No error", "I/O error", "Resource is empty (size 0)", @@ -101,10 +101,8 @@ static const char *const sci_error_types[] = { "resource.map file not found", "No resource files found", "Unknown compression method", - "Decompression failed: Decompression buffer overflow", "Decompression failed: Sanity check failed", - "Decompression failed: Resource too big", - "SCI version is unsupported" + "Decompression failed: Resource too big" }; static const char *const s_resourceTypeNames[] = { @@ -576,7 +574,7 @@ void ResourceSource::loadResource(ResourceManager *resMan, Resource *res) { if (error) { warning("Error %d occurred while reading %s from resource file %s: %s", error, res->_id.toString().c_str(), res->getResourceLocation().c_str(), - sci_error_types[error]); + s_errorDescriptions[error]); res->unalloc(); } @@ -1876,6 +1874,9 @@ int Resource::readResourceInfo(ResVersion volVersion, Common::SeekableReadStream uint32 wCompression, szUnpacked; ResourceType type; + if (file->size() == 0) + return SCI_ERROR_EMPTY_RESOURCE; + switch (volVersion) { case kResVersionSci0Sci1Early: case kResVersionSci1Middle: @@ -1920,7 +1921,7 @@ int Resource::readResourceInfo(ResVersion volVersion, Common::SeekableReadStream break; #endif default: - return SCI_ERROR_INVALID_RESMAP_ENTRY; + return SCI_ERROR_RESMAP_INVALID_ENTRY; } // check if there were errors while reading @@ -1961,7 +1962,7 @@ int Resource::readResourceInfo(ResVersion volVersion, Common::SeekableReadStream compression = kCompUnknown; } - return compression == kCompUnknown ? SCI_ERROR_UNKNOWN_COMPRESSION : 0; + return (compression == kCompUnknown) ? SCI_ERROR_UNKNOWN_COMPRESSION : SCI_ERROR_NONE; } int Resource::decompress(ResVersion volVersion, Common::SeekableReadStream *file) { @@ -2589,7 +2590,7 @@ Common::String ResourceManager::findSierraGameId() { if (!heap) return ""; - int16 gameObjectOffset = findGameObject(false).offset; + int16 gameObjectOffset = findGameObject(false).getOffset(); if (!gameObjectOffset) return ""; diff --git a/engines/sci/resource.h b/engines/sci/resource.h index 294a4672e2..4baf39c67f 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -54,17 +54,17 @@ enum ResourceStatus { kResStatusLocked /**< Allocated and in use */ }; -/** Initialization result types */ -enum { +/** Resource error codes. Should be in sync with s_errorDescriptions */ +enum ResourceErrorCodes { + SCI_ERROR_NONE = 0, SCI_ERROR_IO_ERROR = 1, - SCI_ERROR_INVALID_RESMAP_ENTRY = 2, /**< Invalid resource.map entry */ - SCI_ERROR_RESMAP_NOT_FOUND = 3, - SCI_ERROR_NO_RESOURCE_FILES_FOUND = 4, /**< No resource at all was found */ - SCI_ERROR_UNKNOWN_COMPRESSION = 5, - SCI_ERROR_DECOMPRESSION_ERROR = 6, /**< sanity checks failed during decompression */ + SCI_ERROR_EMPTY_RESOURCE = 2, + SCI_ERROR_RESMAP_INVALID_ENTRY = 3, /**< Invalid resource.map entry */ + SCI_ERROR_RESMAP_NOT_FOUND = 4, + SCI_ERROR_NO_RESOURCE_FILES_FOUND = 5, /**< No resource at all was found */ + SCI_ERROR_UNKNOWN_COMPRESSION = 6, + SCI_ERROR_DECOMPRESSION_ERROR = 7, /**< sanity checks failed during decompression */ SCI_ERROR_RESOURCE_TOO_BIG = 8 /**< Resource size exceeds SCI_MAX_RESOURCE_SIZE */ - - /* the first critical error number */ }; enum { diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 9b0ee6924b..15b18ce8e6 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -385,7 +385,7 @@ bool SciEngine::gameHasFanMadePatch() { { GID_PQ3, 994, 4686, 1291, 0x78 }, // English { GID_PQ3, 994, 4734, 1283, 0x78 }, // German { GID_QFG1VGA, 994, 4388, 0, 0x00 }, - { GID_QFG3, 994, 4714, 0, 0x00 }, + { GID_QFG3, 33, 260, 0, 0x00 }, // TODO: Disabled, as it fixes a whole lot of bugs which can't be tested till SCI2.1 support is finished //{ GID_QFG4, 710, 11477, 0, 0x00 }, { GID_SQ1, 994, 4740, 0, 0x00 }, @@ -453,8 +453,8 @@ static byte patchGameRestoreSaveSci21[] = { }; static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, byte id) { - Script *script = segMan->getScript(methodAddress.segment); - byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.offset)); + Script *script = segMan->getScript(methodAddress.getSegment()); + byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset())); if (getSciVersion() <= SCI_VERSION_1_1) memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave)); else // SCI2+ @@ -463,8 +463,8 @@ static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, by } static void patchGameSaveRestoreCodeSci21(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) { - Script *script = segMan->getScript(methodAddress.segment); - byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.offset)); + Script *script = segMan->getScript(methodAddress.getSegment()); + byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset())); memcpy(patchPtr, patchGameRestoreSaveSci21, sizeof(patchGameRestoreSaveSci21)); if (doRestore) patchPtr[2] = 0x78; // push1 @@ -632,7 +632,7 @@ void SciEngine::initGraphics() { _gfxPaint = _gfxPaint32; _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen); _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxText32); - _robotDecoder = new RobotDecoder(g_system->getMixer(), getPlatform() == Common::kPlatformMacintosh); + _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette, _gfxPaint32); } else { #endif @@ -740,7 +740,7 @@ GUI::Debugger *SciEngine::getDebugger() { if (_gamestate) { ExecStack *xs = &(_gamestate->_executionStack.back()); if (xs) { - xs->addr.pc.offset = _debugState.old_pc_offset; + xs->addr.pc.setOffset(_debugState.old_pc_offset); xs->sp = _debugState.old_sp; } } @@ -876,6 +876,7 @@ void SciEngine::syncIngameAudioOptions() { if (getGameId() == GID_SQ4 || getGameId() == GID_FREDDYPHARKAS || getGameId() == GID_ECOQUEST + || getGameId() == GID_LSL6 // TODO: The following need script patches for simultaneous speech and subtitles //|| getGameId() == GID_KQ6 //|| getGameId() == GID_LAURABOW2 diff --git a/engines/sci/sci.h b/engines/sci/sci.h index 9f18219cb7..3441e26c01 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -228,6 +228,26 @@ public: bool canLoadGameStateCurrently(); bool canSaveGameStateCurrently(); void syncSoundSettings(); + + /** + * Syncs the audio options of the ScummVM launcher (speech, subtitles or + * both) with the in-game audio options of certain CD game versions. For + * some games, this allows simultaneous playing of speech and subtitles, + * even if the original games didn't support this feature. + * + * SCI1.1 games which support simultaneous speech and subtitles: + * - EcoQuest 1 CD + * - Leisure Suit Larry 6 CD + * SCI1.1 games which don't support simultaneous speech and subtitles, + * and we add this functionality in ScummVM: + * - Space Quest 4 CD + * - Freddy Pharkas CD + * SCI1.1 games which don't support simultaneous speech and subtitles, + * and we haven't added any extra functionality in ScummVM because extra + * script patches are needed: + * - Laura Bow 2 CD + * - King's Quest 6 CD + */ void syncIngameAudioOptions(); const SciGameId &getGameId() const { return _gameId; } diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index 123dd21894..528bb51393 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -67,7 +67,8 @@ int AudioPlayer::startAudio(uint16 module, uint32 number) { if (audioStream) { _wPlayFlag = false; - _mixer->playStream(Audio::Mixer::kSpeechSoundType, &_audioHandle, audioStream); + Audio::Mixer::SoundType soundType = (module == 65535) ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType; + _mixer->playStream(soundType, &_audioHandle, audioStream); return sampleLen; } else { // Don't throw a warning in this case. getAudioStream() already has. Some games diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index db9317e071..191e13db0a 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -807,6 +807,9 @@ int MidiPlayer_AdLib::open(ResourceManager *resMan) { int size = f.size(); const uint patchSize = 1344; + // Note: Funseeker's Guide also has another version of adl.drv, 8803 bytes. + // This isn't supported, but it's not really used anywhere, as that demo + // doesn't have sound anyway. if ((size == 5684) || (size == 5720) || (size == 5727)) { byte *buf = new byte[patchSize]; diff --git a/engines/sci/sound/drivers/fmtowns.cpp b/engines/sci/sound/drivers/fmtowns.cpp index 6d8bb2e525..21cb2f1e43 100644 --- a/engines/sci/sound/drivers/fmtowns.cpp +++ b/engines/sci/sound/drivers/fmtowns.cpp @@ -52,8 +52,8 @@ public: uint16 _duration; private: - uint8 _id; - uint8 _velo; + uint8 _id; + uint8 _velo; uint8 _program; MidiDriver_FMTowns *_drv; @@ -76,7 +76,7 @@ public: void addChannels(int num); void dropChannels(int num); - + uint8 currentProgram() const; private: @@ -132,7 +132,7 @@ private: TownsMidiPart **_parts; TownsChannel **_out; - + uint8 _masterVolume; bool _soundOn; @@ -590,7 +590,7 @@ void MidiDriver_FMTowns::addMissingChannels() { avlChan -= _parts[i]->_chanMissing; uint8 m = _parts[i]->_chanMissing; _parts[i]->_chanMissing = 0; - _parts[i]->addChannels(m); + _parts[i]->addChannels(m); } else { _parts[i]->_chanMissing -= avlChan; _parts[i]->addChannels(avlChan); @@ -601,7 +601,7 @@ void MidiDriver_FMTowns::addMissingChannels() { void MidiDriver_FMTowns::updateParser() { if (_timerProc) - _timerProc(_timerProcPara); + _timerProc(_timerProcPara); } void MidiDriver_FMTowns::updateChannels() { diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp index 422948f975..4e54797960 100644 --- a/engines/sci/sound/midiparser_sci.cpp +++ b/engines/sci/sound/midiparser_sci.cpp @@ -98,7 +98,7 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in midiMixChannels(); } - _num_tracks = 1; + _numTracks = 1; _tracks[0] = _mixedData; if (_pSnd) setTrack(0); @@ -144,7 +144,7 @@ byte *MidiParser_SCI::midiMixChannels() { _mixedData = outData; long ticker = 0; byte channelNr, curDelta; - byte midiCommand = 0, midiParam, global_prev = 0; + byte midiCommand = 0, midiParam, globalPrev = 0; long newDelta; SoundResource::Channel *channel; @@ -190,13 +190,13 @@ byte *MidiParser_SCI::midiMixChannels() { byte midiChannel = midiCommand & 0xF; _channelUsed[midiChannel] = true; - if (midiCommand != global_prev) + if (midiCommand != globalPrev) *outData++ = midiCommand; *outData++ = midiParam; if (nMidiParams[(midiCommand >> 4) - 8] == 2) *outData++ = channel->data[channel->curPos++]; channel->prev = midiCommand; - global_prev = midiCommand; + globalPrev = midiCommand; } } @@ -372,8 +372,8 @@ void MidiParser_SCI::unloadMusic() { resetTracking(); allNotesOff(); } - _num_tracks = 0; - _active_track = 255; + _numTracks = 0; + _activeTrack = 255; _resetOnPause = false; if (_mixedData) { @@ -454,26 +454,26 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { debugC(4, kDebugLevelSound, "signal %04x", _signalToSet); } - info.start = _position._play_pos; + info.start = _position._playPos; info.delta = 0; - while (*_position._play_pos == 0xF8) { + while (*_position._playPos == 0xF8) { info.delta += 240; - _position._play_pos++; + _position._playPos++; } - info.delta += *(_position._play_pos++); + info.delta += *(_position._playPos++); // Process the next info. - if ((_position._play_pos[0] & 0xF0) >= 0x80) - info.event = *(_position._play_pos++); + if ((_position._playPos[0] & 0xF0) >= 0x80) + info.event = *(_position._playPos++); else - info.event = _position._running_status; + info.event = _position._runningStatus; if (info.event < 0x80) return; - _position._running_status = info.event; + _position._runningStatus = info.event; switch (info.command()) { case 0xC: - info.basic.param1 = *(_position._play_pos++); + info.basic.param1 = *(_position._playPos++); info.basic.param2 = 0; if (info.channel() == 0xF) {// SCI special case if (info.basic.param1 != kSetSignalLoop) { @@ -488,23 +488,23 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { // in glitches (e.g. the intro of LB1 Amiga gets stuck - bug // #3297883). Refer to MusicEntry::setSignal() in sound/music.cpp. if (_soundVersion <= SCI_VERSION_0_LATE || - _position._play_tick || info.delta) { + _position._playTick || info.delta) { _signalSet = true; _signalToSet = info.basic.param1; } } else { - _loopTick = _position._play_tick + info.delta; + _loopTick = _position._playTick + info.delta; } } break; case 0xD: - info.basic.param1 = *(_position._play_pos++); + info.basic.param1 = *(_position._playPos++); info.basic.param2 = 0; break; case 0xB: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); + info.basic.param1 = *(_position._playPos++); + info.basic.param2 = *(_position._playPos++); // Reference for some events: // http://wiki.scummvm.org/index.php/SCI/Specifications/Sound/SCI0_Resource_Format#Status_Reference @@ -588,8 +588,8 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { case 0x9: case 0xA: case 0xE: - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); + info.basic.param1 = *(_position._playPos++); + info.basic.param2 = *(_position._playPos++); if (info.command() == 0x9 && info.basic.param2 == 0) info.event = info.channel() | 0x80; info.length = 0; @@ -598,12 +598,12 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { case 0xF: // System Common, Meta or SysEx event switch (info.event & 0x0F) { case 0x2: // Song Position Pointer - info.basic.param1 = *(_position._play_pos++); - info.basic.param2 = *(_position._play_pos++); + info.basic.param1 = *(_position._playPos++); + info.basic.param2 = *(_position._playPos++); break; case 0x3: // Song Select - info.basic.param1 = *(_position._play_pos++); + info.basic.param1 = *(_position._playPos++); info.basic.param2 = 0; break; @@ -617,16 +617,16 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { break; case 0x0: // SysEx - info.length = readVLQ(_position._play_pos); - info.ext.data = _position._play_pos; - _position._play_pos += info.length; + info.length = readVLQ(_position._playPos); + info.ext.data = _position._playPos; + _position._playPos += info.length; break; case 0xF: // META event - info.ext.type = *(_position._play_pos++); - info.length = readVLQ(_position._play_pos); - info.ext.data = _position._play_pos; - _position._play_pos += info.length; + info.ext.type = *(_position._playPos++); + info.length = readVLQ(_position._playPos); + info.ext.data = _position._playPos; + _position._playPos += info.length; if (info.ext.type == 0x2F) {// end of track reached if (_pSnd->loop) _pSnd->loop--; @@ -677,21 +677,21 @@ void MidiParser_SCI::allNotesOff() { // Turn off all active notes for (i = 0; i < 128; ++i) { for (j = 0; j < 16; ++j) { - if ((_active_notes[i] & (1 << j)) && (_channelRemap[j] != -1)){ + if ((_activeNotes[i] & (1 << j)) && (_channelRemap[j] != -1)){ sendToDriver(0x80 | j, i, 0); } } } // Turn off all hanging notes - for (i = 0; i < ARRAYSIZE(_hanging_notes); i++) { - byte midiChannel = _hanging_notes[i].channel; - if ((_hanging_notes[i].time_left) && (_channelRemap[midiChannel] != -1)) { - sendToDriver(0x80 | midiChannel, _hanging_notes[i].note, 0); - _hanging_notes[i].time_left = 0; + for (i = 0; i < ARRAYSIZE(_hangingNotes); i++) { + byte midiChannel = _hangingNotes[i].channel; + if ((_hangingNotes[i].timeLeft) && (_channelRemap[midiChannel] != -1)) { + sendToDriver(0x80 | midiChannel, _hangingNotes[i].note, 0); + _hangingNotes[i].timeLeft = 0; } } - _hanging_notes_count = 0; + _hangingNotesCount = 0; // To be sure, send an "All Note Off" event (but not all MIDI devices // support this...). @@ -703,7 +703,7 @@ void MidiParser_SCI::allNotesOff() { } } - memset(_active_notes, 0, sizeof(_active_notes)); + memset(_activeNotes, 0, sizeof(_activeNotes)); } void MidiParser_SCI::setMasterVolume(byte masterVolume) { diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h index 82f34070a4..d3fd337644 100644 --- a/engines/sci/sound/midiparser_sci.h +++ b/engines/sci/sound/midiparser_sci.h @@ -65,7 +65,7 @@ public: void setMasterVolume(byte masterVolume); void setVolume(byte volume); void stop() { - _abort_parse = true; + _abortParse = true; allNotesOff(); } void pause() { diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 09cab75c39..a8a65d2aa4 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -88,6 +88,12 @@ void SciMusic::init() { uint32 dev = MidiDriver::detectDevice(deviceFlags); _musicType = MidiDriver::getMusicType(dev); + if (g_sci->_features->useAltWinGMSound() && _musicType != MT_GM) { + warning("A Windows CD version with an alternate MIDI soundtrack has been chosen, " + "but no MIDI music device has been selected. Reverting to the DOS soundtrack"); + g_sci->_features->forceDOSTracks(); + } + switch (_musicType) { case MT_ADLIB: // FIXME: There's no Amiga sound option, so we hook it up to AdLib @@ -119,7 +125,13 @@ void SciMusic::init() { _pMidiDrv->setTimerCallback(this, &miditimerCallback); _dwTempo = _pMidiDrv->getBaseTempo(); } else { - error("Failed to initialize sound driver"); + if (g_sci->getGameId() == GID_FUNSEEKER) { + // HACK: The Fun Seeker's Guide demo doesn't have patch 3 and the version + // of the Adlib driver (adl.drv) that it includes is unsupported. That demo + // doesn't have any sound anyway, so this shouldn't be fatal. + } else { + error("Failed to initialize sound driver"); + } } // Find out what the first possible channel is (used, when doing channel diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index 05bb90332a..5d32f40f18 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -61,7 +61,7 @@ reg_t SoundCommandParser::kDoSoundInit(int argc, reg_t *argv, reg_t acc) { } int SoundCommandParser::getSoundResourceId(reg_t obj) { - int resourceId = obj.segment ? readSelectorValue(_segMan, obj, SELECTOR(number)) : -1; + int resourceId = obj.getSegment() ? readSelectorValue(_segMan, obj, SELECTOR(number)) : -1; // Modify the resourceId for the Windows versions that have an alternate MIDI soundtrack, like SSCI did. if (g_sci && g_sci->_features->useAltWinGMSound()) { // Check if the alternate MIDI song actually exists... @@ -96,7 +96,7 @@ void SoundCommandParser::initSoundResource(MusicEntry *newSound) { if (_useDigitalSFX || !newSound->soundRes) { int sampleLen; newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen); - newSound->soundType = Audio::Mixer::kSpeechSoundType; + newSound->soundType = Audio::Mixer::kSFXSoundType; } } @@ -291,7 +291,7 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) { reg_t obj = argv[0]; uint16 value = argc > 1 ? argv[1].toUint16() : 0; - if (!obj.segment) { // pause the whole playlist + if (!obj.getSegment()) { // pause the whole playlist _music->pauseAll(value); } else { // pause a playlist slot MusicEntry *musicSlot = _music->getSlot(obj); @@ -367,24 +367,36 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { case 4: // SCI01+ case 5: // SCI1+ (SCI1 late sound scheme), with fade and continue - musicSlot->fadeTo = CLIP<uint16>(argv[1].toUint16(), 0, MUSIC_VOLUME_MAX); - // sometimes we get objects in that position, fix it up (ffs. workarounds) - if (!argv[1].segment) - musicSlot->fadeStep = volume > musicSlot->fadeTo ? -argv[3].toUint16() : argv[3].toUint16(); - else - musicSlot->fadeStep = volume > musicSlot->fadeTo ? -5 : 5; - musicSlot->fadeTickerStep = argv[2].toUint16() * 16667 / _music->soundGetTempo(); - musicSlot->fadeTicker = 0; - if (argc == 5) { // TODO: We currently treat this argument as a boolean, but may // have to handle different non-zero values differently. (e.g., - // some KQ6 scripts pass 3 here) - musicSlot->stopAfterFading = (argv[4].toUint16() != 0); + // some KQ6 scripts pass 3 here). + // There is a script bug in KQ6, room 460 (the room with the flying + // books). An object is passed here, which should not be treated as + // a true flag. Fixes bugs #3555404 and #3291115. + musicSlot->stopAfterFading = (argv[4].isNumber() && argv[4].toUint16() != 0); } else { musicSlot->stopAfterFading = false; } + musicSlot->fadeTo = CLIP<uint16>(argv[1].toUint16(), 0, MUSIC_VOLUME_MAX); + // Check if the song is already at the requested volume. If it is, don't + // perform any fading. Happens for example during the intro of Longbow. + if (musicSlot->fadeTo != musicSlot->volume) { + // sometimes we get objects in that position, fix it up (ffs. workarounds) + if (!argv[1].getSegment()) + musicSlot->fadeStep = volume > musicSlot->fadeTo ? -argv[3].toUint16() : argv[3].toUint16(); + else + musicSlot->fadeStep = volume > musicSlot->fadeTo ? -5 : 5; + musicSlot->fadeTickerStep = argv[2].toUint16() * 16667 / _music->soundGetTempo(); + } else { + // Stop the music, if requested. Fixes bug #3555404. + if (musicSlot->stopAfterFading) + processStopSound(obj, false); + } + + musicSlot->fadeTicker = 0; + // WORKAROUND/HACK: In the labyrinth in KQ6, when falling in the pit and // lighting the lantern, the game scripts perform a fade in of the game // music, but set it to stop after fading. Remove that flag here. This is @@ -497,12 +509,7 @@ void SoundCommandParser::processUpdateCues(reg_t obj) { // fireworks). // It is also needed in other games, e.g. LSL6 when talking to the // receptionist (bug #3192166). - if (g_sci->getGameId() == GID_LONGBOW && g_sci->getEngineState()->currentRoomNumber() == 95) { - // HACK: Don't set a signal here in the intro of Longbow, as that makes some dialog - // boxes disappear too soon (bug #3044844). - } else { - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - } + writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); if (_soundVersion <= SCI_VERSION_0_LATE) { processStopSound(obj, false); } else { diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp index 77f45e0788..0337a8d306 100644 --- a/engines/sci/video/robot_decoder.cpp +++ b/engines/sci/video/robot_decoder.cpp @@ -22,11 +22,13 @@ #include "common/archive.h" #include "common/stream.h" +#include "common/substream.h" #include "common/system.h" #include "common/textconsole.h" #include "common/util.h" #include "graphics/surface.h" +#include "audio/audiostream.h" #include "audio/decoders/raw.h" #include "sci/resource.h" @@ -63,46 +65,26 @@ namespace Sci { // our graphics engine, it looks just like a part of the room. A RBT can move // around the screen and go behind other objects. (...) -#ifdef ENABLE_SCI32 - -enum robotPalTypes { +enum RobotPalTypes { kRobotPalVariable = 0, kRobotPalConstant = 1 }; -RobotDecoder::RobotDecoder(Audio::Mixer *mixer, bool isBigEndian) { - _surface = 0; - _width = 0; - _height = 0; +RobotDecoder::RobotDecoder(bool isBigEndian) { _fileStream = 0; - _audioStream = 0; - _dirtyPalette = false; _pos = Common::Point(0, 0); - _mixer = mixer; _isBigEndian = isBigEndian; + _frameTotalSize = 0; } RobotDecoder::~RobotDecoder() { close(); } -bool RobotDecoder::load(GuiResourceId id) { - Common::String fileName = Common::String::format("%d.rbt", id); - Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); - - if (!stream) { - warning("Unable to open robot file %s", fileName.c_str()); - return false; - } - - return loadStream(stream); -} - bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) { close(); _fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES); - _surface = new Graphics::Surface(); readHeaderChunk(); @@ -114,131 +96,60 @@ bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) { if (_header.version < 4 || _header.version > 6) error("Unknown robot version: %d", _header.version); - if (_header.hasSound) { - _audioStream = Audio::makeQueuingAudioStream(11025, false); - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_audioHandle, _audioStream); - } + RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount); + addTrack(videoTrack); - readPaletteChunk(_header.paletteDataSize); - readFrameSizesChunk(); - calculateVideoDimensions(); - _surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8()); + if (_header.hasSound) + addTrack(new RobotAudioTrack()); + videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize); + readFrameSizesChunk(); + videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize); return true; } -void RobotDecoder::readHeaderChunk() { - // Header (60 bytes) - _fileStream->skip(6); - _header.version = _fileStream->readUint16(); - _header.audioChunkSize = _fileStream->readUint16(); - _header.audioSilenceSize = _fileStream->readUint16(); - _fileStream->skip(2); - _header.frameCount = _fileStream->readUint16(); - _header.paletteDataSize = _fileStream->readUint16(); - _header.unkChunkDataSize = _fileStream->readUint16(); - _fileStream->skip(5); - _header.hasSound = _fileStream->readByte(); - _fileStream->skip(34); - - // Some videos (e.g. robot 1305 in Phantasmagoria and - // robot 184 in Lighthouse) have an unknown chunk before - // the palette chunk (probably used for sound preloading). - // Skip it here. - if (_header.unkChunkDataSize) - _fileStream->skip(_header.unkChunkDataSize); -} - -void RobotDecoder::readPaletteChunk(uint16 chunkSize) { - byte *paletteData = new byte[chunkSize]; - _fileStream->read(paletteData, chunkSize); +bool RobotDecoder::load(GuiResourceId id) { + // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) - + // its drawn at odd coordinates. SV can't play it either (along with some + // others), so it must be some new functionality added in RAMA's robot + // videos. Skip it for now. + if (g_sci->getGameId() == GID_RAMA && id == 1003) + return false; - // SCI1.1 palette - byte palFormat = paletteData[32]; - uint16 palColorStart = paletteData[25]; - uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29); + // TODO: The robot video in the Lighthouse demo gets stuck + if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16) + return false; - int palOffset = 37; - memset(_palette, 0, 256 * 3); + Common::String fileName = Common::String::format("%d.rbt", id); + Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); - for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) { - if (palFormat == kRobotPalVariable) - palOffset++; - _palette[colorNo * 3 + 0] = paletteData[palOffset++]; - _palette[colorNo * 3 + 1] = paletteData[palOffset++]; - _palette[colorNo * 3 + 2] = paletteData[palOffset++]; + if (!stream) { + warning("Unable to open robot file %s", fileName.c_str()); + return false; } - _dirtyPalette = true; - delete[] paletteData; + return loadStream(stream); } +void RobotDecoder::close() { + VideoDecoder::close(); -void RobotDecoder::readFrameSizesChunk() { - // The robot video file contains 2 tables, with one entry for each frame: - // - A table containing the size of the image in each video frame - // - A table containing the total size of each video frame. - // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots, - // they contain 32-bit integers. - - _frameTotalSize = new uint32[_header.frameCount]; - - // TODO: The table reading code can probably be removed once the - // audio chunk size is figured out (check the TODO inside processNextFrame()) -#if 0 - // We don't need any of the two tables to play the video, so we ignore - // both of them. - uint16 wordSize = _header.version == 6 ? 4 : 2; - _fileStream->skip(_header.frameCount * wordSize * 2); -#else - switch (_header.version) { - case 4: - case 5: // sizes are 16-bit integers - // Skip table with frame image sizes, as we don't need it - _fileStream->skip(_header.frameCount * 2); - for (int i = 0; i < _header.frameCount; ++i) - _frameTotalSize[i] = _fileStream->readUint16(); - break; - case 6: // sizes are 32-bit integers - // Skip table with frame image sizes, as we don't need it - _fileStream->skip(_header.frameCount * 4); - for (int i = 0; i < _header.frameCount; ++i) - _frameTotalSize[i] = _fileStream->readUint32(); - break; - default: - error("Can't yet handle index table for robot version %d", _header.version); - } -#endif - - // 2 more unknown tables - _fileStream->skip(1024 + 512); + delete _fileStream; + _fileStream = 0; - // Pad to nearest 2 kilobytes - uint32 curPos = _fileStream->pos(); - if (curPos & 0x7ff) - _fileStream->seek((curPos & ~0x7ff) + 2048); + delete[] _frameTotalSize; + _frameTotalSize = 0; } -void RobotDecoder::calculateVideoDimensions() { - // This is an O(n) operation, as each frame has a different size. - // We need to know the actual frame size to have a constant video size. - uint32 pos = _fileStream->pos(); - - for (uint32 curFrame = 0; curFrame < _header.frameCount; curFrame++) { - _fileStream->skip(4); - uint16 frameWidth = _fileStream->readUint16(); - uint16 frameHeight = _fileStream->readUint16(); - if (frameWidth > _width) - _width = frameWidth; - if (frameHeight > _height) - _height = frameHeight; - _fileStream->skip(_frameTotalSize[curFrame] - 8); - } +void RobotDecoder::readNextPacket() { + // Get our track + RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0); + videoTrack->increaseCurFrame(); + Graphics::Surface *surface = videoTrack->getSurface(); - _fileStream->seek(pos); -} + if (videoTrack->endOfTrack()) + return; -const Graphics::Surface *RobotDecoder::decodeNextFrame() { // Read frame image header (24 bytes) _fileStream->skip(3); byte frameScale = _fileStream->readByte(); @@ -247,23 +158,28 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() { _fileStream->skip(4); // unknown, almost always 0 uint16 frameX = _fileStream->readUint16(); uint16 frameY = _fileStream->readUint16(); + // TODO: In v4 robot files, frameX and frameY have a different meaning. // Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up // correctly. if (_header.version == 4) frameX = frameY = 0; + uint16 compressedSize = _fileStream->readUint16(); uint16 frameFragments = _fileStream->readUint16(); _fileStream->skip(4); // unknown uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100; + // FIXME: A frame's height + position can go off limits... why? With the // following, we cut the contents to fit the frame - uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, _height - frameY); + uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY); + // FIXME: Same goes for the frame's width + position. In this case, we // modify the position to fit the contents on screen. - if (frameWidth + frameX > _width) - frameX = _width - frameWidth; - assert (frameWidth + frameX <= _width && scaledHeight + frameY <= _height); + if (frameWidth + frameX > surface->w) + frameX = surface->w - frameWidth; + + assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h); DecompressorLZS lzs; byte *decompressedFrame = new byte[decompressedSize]; @@ -294,24 +210,23 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() { // Copy over the decompressed frame byte *inFrame = decompressedFrame; - byte *outFrame = (byte *)_surface->pixels; + byte *outFrame = (byte *)surface->pixels; // Black out the surface - memset(outFrame, 0, _width * _height); + memset(outFrame, 0, surface->w * surface->h); // Move to the correct y coordinate - outFrame += _width * frameY; + outFrame += surface->w * frameY; for (uint16 y = 0; y < scaledHeight; y++) { memcpy(outFrame + frameX, inFrame, frameWidth); inFrame += frameWidth; - outFrame += _width; + outFrame += surface->w; } delete[] decompressedFrame; - // +1 because we start with frame number -1 - uint32 audioChunkSize = _frameTotalSize[_curFrame + 1] - (24 + compressedSize); + uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize); // TODO: The audio chunk size below is usually correct, but there are some // exceptions (e.g. robot 4902 in Phantasmagoria, towards its end) @@ -326,41 +241,166 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() { // Queue the next audio frame // FIXME: For some reason, there are audio hiccups/gaps if (_header.hasSound) { - _fileStream->skip(8); // header - _audioStream->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize - 8), - (audioChunkSize - 8) * 2, DisposeAfterUse::NO, - Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); + RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1); + _fileStream->skip(8); // header + audioChunkSize -= 8; + audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2); } else { _fileStream->skip(audioChunkSize); } +} - if (_curFrame == -1) - _startTime = g_system->getMillis(); - - _curFrame++; +void RobotDecoder::readHeaderChunk() { + // Header (60 bytes) + _fileStream->skip(6); + _header.version = _fileStream->readUint16(); + _header.audioChunkSize = _fileStream->readUint16(); + _header.audioSilenceSize = _fileStream->readUint16(); + _fileStream->skip(2); + _header.frameCount = _fileStream->readUint16(); + _header.paletteDataSize = _fileStream->readUint16(); + _header.unkChunkDataSize = _fileStream->readUint16(); + _fileStream->skip(5); + _header.hasSound = _fileStream->readByte(); + _fileStream->skip(34); - return _surface; + // Some videos (e.g. robot 1305 in Phantasmagoria and + // robot 184 in Lighthouse) have an unknown chunk before + // the palette chunk (probably used for sound preloading). + // Skip it here. + if (_header.unkChunkDataSize) + _fileStream->skip(_header.unkChunkDataSize); } -void RobotDecoder::close() { - if (!_fileStream) - return; +void RobotDecoder::readFrameSizesChunk() { + // The robot video file contains 2 tables, with one entry for each frame: + // - A table containing the size of the image in each video frame + // - A table containing the total size of each video frame. + // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots, + // they contain 32-bit integers. - delete _fileStream; - _fileStream = 0; + _frameTotalSize = new uint32[_header.frameCount]; + + // TODO: The table reading code can probably be removed once the + // audio chunk size is figured out (check the TODO inside processNextFrame()) +#if 0 + // We don't need any of the two tables to play the video, so we ignore + // both of them. + uint16 wordSize = _header.version == 6 ? 4 : 2; + _fileStream->skip(_header.frameCount * wordSize * 2); +#else + switch (_header.version) { + case 4: + case 5: // sizes are 16-bit integers + // Skip table with frame image sizes, as we don't need it + _fileStream->skip(_header.frameCount * 2); + for (int i = 0; i < _header.frameCount; ++i) + _frameTotalSize[i] = _fileStream->readUint16(); + break; + case 6: // sizes are 32-bit integers + // Skip table with frame image sizes, as we don't need it + _fileStream->skip(_header.frameCount * 4); + for (int i = 0; i < _header.frameCount; ++i) + _frameTotalSize[i] = _fileStream->readUint32(); + break; + default: + error("Can't yet handle index table for robot version %d", _header.version); + } +#endif + // 2 more unknown tables + _fileStream->skip(1024 + 512); + + // Pad to nearest 2 kilobytes + uint32 curPos = _fileStream->pos(); + if (curPos & 0x7ff) + _fileStream->seek((curPos & ~0x7ff) + 2048); +} + +RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) { + _surface = new Graphics::Surface(); + _curFrame = -1; + _dirtyPalette = false; +} + +RobotDecoder::RobotVideoTrack::~RobotVideoTrack() { _surface->free(); delete _surface; - _surface = 0; +} - if (_header.hasSound) { - _mixer->stopHandle(_audioHandle); - //delete _audioStream; _audioStream = 0; +uint16 RobotDecoder::RobotVideoTrack::getWidth() const { + return _surface->w; +} + +uint16 RobotDecoder::RobotVideoTrack::getHeight() const { + return _surface->h; +} + +Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const { + return _surface->format; +} + +void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) { + byte *paletteData = new byte[chunkSize]; + stream->read(paletteData, chunkSize); + + // SCI1.1 palette + byte palFormat = paletteData[32]; + uint16 palColorStart = paletteData[25]; + uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29); + + int palOffset = 37; + memset(_palette, 0, 256 * 3); + + for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) { + if (palFormat == kRobotPalVariable) + palOffset++; + _palette[colorNo * 3 + 0] = paletteData[palOffset++]; + _palette[colorNo * 3 + 1] = paletteData[palOffset++]; + _palette[colorNo * 3 + 2] = paletteData[palOffset++]; } - reset(); + _dirtyPalette = true; + delete[] paletteData; } -#endif +void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) { + // This is an O(n) operation, as each frame has a different size. + // We need to know the actual frame size to have a constant video size. + uint32 pos = stream->pos(); + + uint16 width = 0, height = 0; + + for (int curFrame = 0; curFrame < _frameCount; curFrame++) { + stream->skip(4); + uint16 frameWidth = stream->readUint16(); + uint16 frameHeight = stream->readUint16(); + if (frameWidth > width) + width = frameWidth; + if (frameHeight > height) + height = frameHeight; + stream->skip(frameSizes[curFrame] - 8); + } + + stream->seek(pos); + + _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); +} + +RobotDecoder::RobotAudioTrack::RobotAudioTrack() { + _audioStream = Audio::makeQueuingAudioStream(11025, false); +} + +RobotDecoder::RobotAudioTrack::~RobotAudioTrack() { + delete _audioStream; +} + +void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) { + _audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); +} + +Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const { + return _audioStream; +} } // End of namespace Sci diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h index 3f93582418..437954f7fb 100644 --- a/engines/sci/video/robot_decoder.h +++ b/engines/sci/video/robot_decoder.h @@ -25,79 +25,103 @@ #include "common/rational.h" #include "common/rect.h" -#include "common/stream.h" -#include "common/substream.h" -#include "audio/audiostream.h" -#include "audio/mixer.h" -#include "graphics/pixelformat.h" #include "video/video_decoder.h" -namespace Sci { +namespace Audio { +class QueuingAudioStream; +} -#ifdef ENABLE_SCI32 - -struct RobotHeader { - // 6 bytes, identifier bytes - uint16 version; - uint16 audioChunkSize; - uint16 audioSilenceSize; - // 2 bytes, unknown - uint16 frameCount; - uint16 paletteDataSize; - uint16 unkChunkDataSize; - // 5 bytes, unknown - byte hasSound; - // 34 bytes, unknown -}; +namespace Common { +class SeekableSubReadStreamEndian; +} -class RobotDecoder : public Video::FixedRateVideoDecoder { +namespace Sci { + +class RobotDecoder : public Video::VideoDecoder { public: - RobotDecoder(Audio::Mixer *mixer, bool isBigEndian); + RobotDecoder(bool isBigEndian); virtual ~RobotDecoder(); bool loadStream(Common::SeekableReadStream *stream); bool load(GuiResourceId id); void close(); - bool isVideoLoaded() const { return _fileStream != 0; } - uint16 getWidth() const { return _width; } - uint16 getHeight() const { return _height; } - uint32 getFrameCount() const { return _header.frameCount; } - const Graphics::Surface *decodeNextFrame(); - Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } - const byte *getPalette() { _dirtyPalette = false; return _palette; } - bool hasDirtyPalette() const { return _dirtyPalette; } void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); } Common::Point getPos() const { return _pos; } protected: - Common::Rational getFrameRate() const { return Common::Rational(60, 10); } + void readNextPacket(); private: + class RobotVideoTrack : public FixedRateVideoTrack { + public: + RobotVideoTrack(int frameCount); + ~RobotVideoTrack(); + + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _frameCount; } + const Graphics::Surface *decodeNextFrame() { return _surface; } + const byte *getPalette() const { _dirtyPalette = false; return _palette; } + bool hasDirtyPalette() const { return _dirtyPalette; } + + void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize); + void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes); + Graphics::Surface *getSurface() { return _surface; } + void increaseCurFrame() { _curFrame++; } + + protected: + Common::Rational getFrameRate() const { return Common::Rational(60, 10); } + + private: + int _frameCount; + int _curFrame; + byte _palette[256 * 3]; + mutable bool _dirtyPalette; + Graphics::Surface *_surface; + }; + + class RobotAudioTrack : public AudioTrack { + public: + RobotAudioTrack(); + ~RobotAudioTrack(); + + Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; } + + void queueBuffer(byte *buffer, int size); + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + Audio::QueuingAudioStream *_audioStream; + }; + + struct RobotHeader { + // 6 bytes, identifier bytes + uint16 version; + uint16 audioChunkSize; + uint16 audioSilenceSize; + // 2 bytes, unknown + uint16 frameCount; + uint16 paletteDataSize; + uint16 unkChunkDataSize; + // 5 bytes, unknown + byte hasSound; + // 34 bytes, unknown + } _header; + void readHeaderChunk(); - void readPaletteChunk(uint16 chunkSize); void readFrameSizesChunk(); - void calculateVideoDimensions(); - void freeData(); - - RobotHeader _header; Common::Point _pos; bool _isBigEndian; + uint32 *_frameTotalSize; Common::SeekableSubReadStreamEndian *_fileStream; - - uint16 _width; - uint16 _height; - uint32 *_frameTotalSize; - byte _palette[256 * 3]; - bool _dirtyPalette; - Graphics::Surface *_surface; - Audio::QueuingAudioStream *_audioStream; - Audio::SoundHandle _audioHandle; - Audio::Mixer *_mixer; }; -#endif } // End of namespace Sci diff --git a/engines/sci/video/seq_decoder.cpp b/engines/sci/video/seq_decoder.cpp index abd64911a7..a7b6346eca 100644 --- a/engines/sci/video/seq_decoder.cpp +++ b/engines/sci/video/seq_decoder.cpp @@ -41,33 +41,44 @@ enum seqFrameTypes { kSeqFrameDiff = 1 }; -SeqDecoder::SeqDecoder() { - _fileStream = 0; - _surface = 0; - _dirtyPalette = false; +SEQDecoder::SEQDecoder(uint frameDelay) : _frameDelay(frameDelay) { } -SeqDecoder::~SeqDecoder() { +SEQDecoder::~SEQDecoder() { close(); } -bool SeqDecoder::loadStream(Common::SeekableReadStream *stream) { +bool SEQDecoder::loadStream(Common::SeekableReadStream *stream) { close(); + addTrack(new SEQVideoTrack(stream, _frameDelay)); + + return true; +} + +SEQDecoder::SEQVideoTrack::SEQVideoTrack(Common::SeekableReadStream *stream, uint frameDelay) { + assert(stream); + assert(frameDelay != 0); _fileStream = stream; + _frameDelay = frameDelay; + _curFrame = -1; + _surface = new Graphics::Surface(); _surface->create(SEQ_SCREEN_WIDTH, SEQ_SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8()); _frameCount = _fileStream->readUint16LE(); - // Set palette - int paletteChunkSize = _fileStream->readUint32LE(); - readPaletteChunk(paletteChunkSize); + // Set initial palette + readPaletteChunk(_fileStream->readUint32LE()); +} - return true; +SEQDecoder::SEQVideoTrack::~SEQVideoTrack() { + delete _fileStream; + _surface->free(); + delete _surface; } -void SeqDecoder::readPaletteChunk(uint16 chunkSize) { +void SEQDecoder::SEQVideoTrack::readPaletteChunk(uint16 chunkSize) { byte *paletteData = new byte[chunkSize]; _fileStream->read(paletteData, chunkSize); @@ -91,23 +102,7 @@ void SeqDecoder::readPaletteChunk(uint16 chunkSize) { delete[] paletteData; } -void SeqDecoder::close() { - if (!_fileStream) - return; - - _frameDelay = 0; - - delete _fileStream; - _fileStream = 0; - - _surface->free(); - delete _surface; - _surface = 0; - - reset(); -} - -const Graphics::Surface *SeqDecoder::decodeNextFrame() { +const Graphics::Surface *SEQDecoder::SEQVideoTrack::decodeNextFrame() { int16 frameWidth = _fileStream->readUint16LE(); int16 frameHeight = _fileStream->readUint16LE(); int16 frameLeft = _fileStream->readUint16LE(); @@ -142,9 +137,6 @@ const Graphics::Surface *SeqDecoder::decodeNextFrame() { delete[] buf; } - if (_curFrame == -1) - _startTime = g_system->getMillis(); - _curFrame++; return _surface; } @@ -159,7 +151,7 @@ const Graphics::Surface *SeqDecoder::decodeNextFrame() { } \ memcpy(dest + writeRow * SEQ_SCREEN_WIDTH + writeCol, litData + litPos, n); -bool SeqDecoder::decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey) { +bool SEQDecoder::SEQVideoTrack::decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey) { int writeRow = 0; int writeCol = left; int litPos = 0; @@ -237,4 +229,9 @@ bool SeqDecoder::decodeFrame(byte *rleData, int rleSize, byte *litData, int litS return true; } +const byte *SEQDecoder::SEQVideoTrack::getPalette() const { + _dirtyPalette = false; + return _palette; +} + } // End of namespace Sci diff --git a/engines/sci/video/seq_decoder.h b/engines/sci/video/seq_decoder.h index 800a3c9024..890f349feb 100644 --- a/engines/sci/video/seq_decoder.h +++ b/engines/sci/video/seq_decoder.h @@ -40,44 +40,49 @@ namespace Sci { /** * Implementation of the Sierra SEQ decoder, used in KQ6 DOS floppy/CD and GK1 DOS */ -class SeqDecoder : public Video::FixedRateVideoDecoder { +class SEQDecoder : public Video::VideoDecoder { public: - SeqDecoder(); - virtual ~SeqDecoder(); + SEQDecoder(uint frameDelay); + virtual ~SEQDecoder(); bool loadStream(Common::SeekableReadStream *stream); - void close(); - - void setFrameDelay(int frameDelay) { _frameDelay = frameDelay; } - - bool isVideoLoaded() const { return _fileStream != 0; } - uint16 getWidth() const { return SEQ_SCREEN_WIDTH; } - uint16 getHeight() const { return SEQ_SCREEN_HEIGHT; } - uint32 getFrameCount() const { return _frameCount; } - const Graphics::Surface *decodeNextFrame(); - Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } - const byte *getPalette() { _dirtyPalette = false; return _palette; } - bool hasDirtyPalette() const { return _dirtyPalette; } - -protected: - Common::Rational getFrameRate() const { assert(_frameDelay); return Common::Rational(60, _frameDelay); } private: - enum { - SEQ_SCREEN_WIDTH = 320, - SEQ_SCREEN_HEIGHT = 200 + class SEQVideoTrack : public FixedRateVideoTrack { + public: + SEQVideoTrack(Common::SeekableReadStream *stream, uint frameDelay); + ~SEQVideoTrack(); + + uint16 getWidth() const { return SEQ_SCREEN_WIDTH; } + uint16 getHeight() const { return SEQ_SCREEN_HEIGHT; } + Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _frameCount; } + const Graphics::Surface *decodeNextFrame(); + const byte *getPalette() const; + bool hasDirtyPalette() const { return _dirtyPalette; } + + protected: + Common::Rational getFrameRate() const { return Common::Rational(60, _frameDelay); } + + private: + enum { + SEQ_SCREEN_WIDTH = 320, + SEQ_SCREEN_HEIGHT = 200 + }; + + void readPaletteChunk(uint16 chunkSize); + bool decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey); + + Common::SeekableReadStream *_fileStream; + int _curFrame, _frameCount; + byte _palette[256 * 3]; + mutable bool _dirtyPalette; + Graphics::Surface *_surface; + uint _frameDelay; }; - void readPaletteChunk(uint16 chunkSize); - bool decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey); - - uint16 _width, _height; - uint16 _frameDelay; - Common::SeekableReadStream *_fileStream; - byte _palette[256 * 3]; - bool _dirtyPalette; - uint32 _frameCount; - Graphics::Surface *_surface; + uint _frameDelay; }; } // End of namespace Sci |