diff options
author | Alejandro Marzini | 2010-07-13 04:31:15 +0000 |
---|---|---|
committer | Alejandro Marzini | 2010-07-13 04:31:15 +0000 |
commit | 609e08d5dbae3179eddf981abe73d69009432de4 (patch) | |
tree | cddbd0a0e69eaa53b85f98f96dc410a307773f08 /engines/sci | |
parent | 8b6a670391f1b5103e3761d78eef8f41d64cf8cd (diff) | |
parent | 03c0faa5d76f547603ee6389cdf958e2a6f0f43d (diff) | |
download | scummvm-rg350-609e08d5dbae3179eddf981abe73d69009432de4.tar.gz scummvm-rg350-609e08d5dbae3179eddf981abe73d69009432de4.tar.bz2 scummvm-rg350-609e08d5dbae3179eddf981abe73d69009432de4.zip |
Merged from trunk, from Rev 49499 to HEAD
svn-id: r50840
Diffstat (limited to 'engines/sci')
118 files changed, 9758 insertions, 12452 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 814fbff636..33aa5514f2 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -31,23 +31,19 @@ #include "sci/event.h" #include "sci/resource.h" #include "sci/engine/state.h" +#include "sci/engine/kernel.h" #include "sci/engine/selector.h" #include "sci/engine/savegame.h" #include "sci/engine/gc.h" #include "sci/engine/features.h" -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/songlib.h" // for SongLibrary -#include "sci/sound/iterator/iterator.h" // for SCI_SONG_ITERATOR_TYPE_SCI0 -#else +#include "sci/sound/midiparser_sci.h" #include "sci/sound/music.h" -#endif #include "sci/sound/drivers/mididriver.h" -#include "sci/graphics/gui.h" -#include "sci/graphics/gui32.h" #include "sci/graphics/cursor.h" #include "sci/graphics/screen.h" #include "sci/graphics/paint.h" #include "sci/graphics/paint16.h" +#include "sci/graphics/paint32.h" #include "sci/graphics/palette.h" #include "sci/parser/vocabulary.h" @@ -70,15 +66,15 @@ bool g_debug_track_mouse_clicks = false; // Refer to the "addresses" command on how to pass address parameters static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeValue); -Console::Console(SciEngine *engine) : GUI::Debugger() { - _engine = engine; +Console::Console(SciEngine *engine) : GUI::Debugger(), + _engine(engine), _debugState(engine->_debugState) { // Variables DVar_Register("sleeptime_factor", &g_debug_sleeptime_factor, DVAR_INT, 0); - DVar_Register("gc_interval", &engine->_gamestate->script_gc_interval, DVAR_INT, 0); + DVar_Register("gc_interval", &engine->_gamestate->scriptGCInterval, DVAR_INT, 0); DVar_Register("simulated_key", &g_debug_simulated_key, DVAR_INT, 0); DVar_Register("track_mouse_clicks", &g_debug_track_mouse_clicks, DVAR_BOOL, 0); - DVar_Register("script_abort_flag", &_engine->_gamestate->script_abort_flag, DVAR_INT, 0); + DVar_Register("script_abort_flag", &_engine->_gamestate->abortScriptProcessing, DVAR_INT, 0); // General DCmd_Register("help", WRAP_METHOD(Console, cmdHelp)); @@ -106,6 +102,7 @@ Console::Console(SciEngine *engine) : GUI::Debugger() { DCmd_Register("list", WRAP_METHOD(Console, cmdList)); DCmd_Register("hexgrep", WRAP_METHOD(Console, cmdHexgrep)); DCmd_Register("verify_scripts", WRAP_METHOD(Console, cmdVerifyScripts)); + DCmd_Register("show_instruments", WRAP_METHOD(Console, cmdShowInstruments)); // Game DCmd_Register("save_game", WRAP_METHOD(Console, cmdSaveGame)); DCmd_Register("restore_game", WRAP_METHOD(Console, cmdRestoreGame)); @@ -151,15 +148,17 @@ Console::Console(SciEngine *engine) : GUI::Debugger() { DCmd_Register("addresses", WRAP_METHOD(Console, cmdAddresses)); DCmd_Register("registers", WRAP_METHOD(Console, cmdRegisters)); DCmd_Register("dissect_script", WRAP_METHOD(Console, cmdDissectScript)); - DCmd_Register("set_acc", WRAP_METHOD(Console, cmdSetAccumulator)); DCmd_Register("backtrace", WRAP_METHOD(Console, cmdBacktrace)); DCmd_Register("bt", WRAP_METHOD(Console, cmdBacktrace)); // alias - DCmd_Register("step", WRAP_METHOD(Console, cmdStep)); - DCmd_Register("s", WRAP_METHOD(Console, cmdStep)); // alias + DCmd_Register("trace", WRAP_METHOD(Console, cmdTrace)); + DCmd_Register("t", WRAP_METHOD(Console, cmdTrace)); // alias + DCmd_Register("s", WRAP_METHOD(Console, cmdTrace)); // alias + DCmd_Register("stepover", WRAP_METHOD(Console, cmdStepOver)); + DCmd_Register("p", WRAP_METHOD(Console, cmdStepOver)); // alias + DCmd_Register("step_ret", WRAP_METHOD(Console, cmdStepRet)); + DCmd_Register("pret", WRAP_METHOD(Console, cmdStepRet)); // alias DCmd_Register("step_event", WRAP_METHOD(Console, cmdStepEvent)); DCmd_Register("se", WRAP_METHOD(Console, cmdStepEvent)); // alias - DCmd_Register("step_ret", WRAP_METHOD(Console, cmdStepRet)); - DCmd_Register("sret", WRAP_METHOD(Console, cmdStepRet)); // alias DCmd_Register("step_global", WRAP_METHOD(Console, cmdStepGlobal)); DCmd_Register("sg", WRAP_METHOD(Console, cmdStepGlobal)); // alias DCmd_Register("step_callk", WRAP_METHOD(Console, cmdStepCallk)); @@ -171,8 +170,10 @@ Console::Console(SciEngine *engine) : GUI::Debugger() { // Breakpoints DCmd_Register("bp_list", WRAP_METHOD(Console, cmdBreakpointList)); DCmd_Register("bplist", WRAP_METHOD(Console, cmdBreakpointList)); // alias + DCmd_Register("bl", WRAP_METHOD(Console, cmdBreakpointList)); // alias DCmd_Register("bp_del", WRAP_METHOD(Console, cmdBreakpointDelete)); DCmd_Register("bpdel", WRAP_METHOD(Console, cmdBreakpointDelete)); // alias + DCmd_Register("bc", WRAP_METHOD(Console, cmdBreakpointDelete)); // alias DCmd_Register("bp_exec_method", WRAP_METHOD(Console, cmdBreakpointExecMethod)); DCmd_Register("bpx", WRAP_METHOD(Console, cmdBreakpointExecMethod)); // alias DCmd_Register("bp_exec_function", WRAP_METHOD(Console, cmdBreakpointExecFunction)); @@ -180,48 +181,42 @@ Console::Console(SciEngine *engine) : GUI::Debugger() { // VM DCmd_Register("script_steps", WRAP_METHOD(Console, cmdScriptSteps)); DCmd_Register("vm_varlist", WRAP_METHOD(Console, cmdVMVarlist)); - DCmd_Register("vmvarlist", WRAP_METHOD(Console, cmdVMVarlist)); // alias + DCmd_Register("vmvarlist", WRAP_METHOD(Console, cmdVMVarlist)); // alias + DCmd_Register("vl", WRAP_METHOD(Console, cmdVMVarlist)); // alias DCmd_Register("vm_vars", WRAP_METHOD(Console, cmdVMVars)); - DCmd_Register("vmvars", WRAP_METHOD(Console, cmdVMVars)); // alias + DCmd_Register("vmvars", WRAP_METHOD(Console, cmdVMVars)); // alias + DCmd_Register("vv", WRAP_METHOD(Console, cmdVMVars)); // alias DCmd_Register("stack", WRAP_METHOD(Console, cmdStack)); DCmd_Register("value_type", WRAP_METHOD(Console, cmdValueType)); DCmd_Register("view_listnode", WRAP_METHOD(Console, cmdViewListNode)); DCmd_Register("view_reference", WRAP_METHOD(Console, cmdViewReference)); - DCmd_Register("vr", WRAP_METHOD(Console, cmdViewReference)); // alias + DCmd_Register("vr", WRAP_METHOD(Console, cmdViewReference)); // alias DCmd_Register("view_object", WRAP_METHOD(Console, cmdViewObject)); - DCmd_Register("vo", WRAP_METHOD(Console, cmdViewObject)); // alias + DCmd_Register("vo", WRAP_METHOD(Console, cmdViewObject)); // alias DCmd_Register("active_object", WRAP_METHOD(Console, cmdViewActiveObject)); DCmd_Register("acc_object", WRAP_METHOD(Console, cmdViewAccumulatorObject)); - g_debugState.seeking = kDebugSeekNothing; - g_debugState.seekLevel = 0; - g_debugState.runningStep = 0; - g_debugState.stopOnEvent = false; - g_debugState.debugging = false; - g_debugState.breakpointWasHit = false; - g_debugState._breakpoints.clear(); // No breakpoints defined - g_debugState._activeBreakpointTypes = 0; + _debugState.seeking = kDebugSeekNothing; + _debugState.seekLevel = 0; + _debugState.runningStep = 0; + _debugState.stopOnEvent = false; + _debugState.debugging = false; + _debugState.breakpointWasHit = false; + _debugState._breakpoints.clear(); // No breakpoints defined + _debugState._activeBreakpointTypes = 0; } Console::~Console() { } void Console::preEnter() { -#ifdef USE_OLD_MUSIC_FUNCTIONS - if (_engine->_gamestate) - _engine->_gamestate->_sound.sfx_suspend(true); -#endif - if (_engine->_gamestate && _engine->_gamestate->_soundCmd) - _engine->_gamestate->_soundCmd->pauseAll(true); + if (g_sci && g_sci->_soundCmd) + g_sci->_soundCmd->pauseAll(true); } void Console::postEnter() { -#ifdef USE_OLD_MUSIC_FUNCTIONS - if (_engine->_gamestate) - _engine->_gamestate->_sound.sfx_suspend(false); -#endif - if (_engine->_gamestate && _engine->_gamestate->_soundCmd) - _engine->_gamestate->_soundCmd->pauseAll(false); + if (g_sci && g_sci->_soundCmd) + g_sci->_soundCmd->pauseAll(false); if (!_videoFile.empty()) { _engine->_gfxCursor->kernelHide(); @@ -243,8 +238,9 @@ void Console::postEnter() { if (videoDecoder && videoDecoder->loadFile(_videoFile)) { uint16 x = (g_system->getWidth() - videoDecoder->getWidth()) / 2; uint16 y = (g_system->getHeight() - videoDecoder->getHeight()) / 2; + bool skipVideo = false; - while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo()) { + while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { if (videoDecoder->needsUpdate()) { Graphics::Surface *frame = videoDecoder->decodeNextFrame(); if (frame) { @@ -258,8 +254,10 @@ void Console::postEnter() { } Common::Event event; - while (g_system->getEventManager()->pollEvent(event)) - ; + while (g_system->getEventManager()->pollEvent(event)) { + if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) + skipVideo = true; + } g_system->delayMillis(10); } @@ -274,14 +272,6 @@ void Console::postEnter() { } } - -#if 0 -// Unused -#define LOOKUP_SPECIES(species) (\ - (species >= 1000) ? species : *(s->_classTable[species].scriptposp) \ - + s->_classTable[species].class_offset) -#endif - bool Console::cmdHelp(int argc, const char **argv) { DebugPrintf("\n"); DebugPrintf("Variables\n"); @@ -326,6 +316,7 @@ bool Console::cmdHelp(int argc, const char **argv) { DebugPrintf(" list - Lists all the resources of a given type\n"); DebugPrintf(" hexgrep - Searches some resources for a particular sequence of bytes, represented as hexadecimal numbers\n"); DebugPrintf(" verify_scripts - Performs sanity checks on SCI1.1-SCI2.1 game scripts (e.g. if they're up to 64KB in total)\n"); + DebugPrintf(" show_instruments - Shows the instruments of a specific song, or all songs\n"); DebugPrintf("\n"); DebugPrintf("Game:\n"); DebugPrintf(" save_game - Saves the current game state to the hard disk\n"); @@ -370,11 +361,11 @@ bool Console::cmdHelp(int argc, const char **argv) { DebugPrintf(" addresses - Provides information on how to pass addresses\n"); DebugPrintf(" registers - Shows the current register values\n"); DebugPrintf(" dissect_script - Examines a script\n"); - DebugPrintf(" set_acc - Sets the accumulator\n"); DebugPrintf(" backtrace / bt - Dumps the send/self/super/call/calle/callb stack\n"); - DebugPrintf(" step / s - Executes one operation (no parameters) or several operations (specified as a parameter) \n"); + DebugPrintf(" trace / t / s - Executes one operation (no parameters) or several operations (specified as a parameter) \n"); + DebugPrintf(" stepover / p - Executes one operation, skips over call/send\n"); + DebugPrintf(" step_ret / pret - Steps forward until ret is called on the current execution stack level.\n"); DebugPrintf(" step_event / se - Steps forward until a SCI event is received.\n"); - DebugPrintf(" step_ret / sret - Steps forward until ret is called on the current execution stack level.\n"); DebugPrintf(" step_global / sg - Steps until the global variable with the specified index is modified.\n"); DebugPrintf(" step_callk / snk - Steps forward until it hits the next callk operation, or a specific callk (specified as a parameter)\n"); DebugPrintf(" disasm - Disassembles a method by name\n"); @@ -383,15 +374,15 @@ bool Console::cmdHelp(int argc, const char **argv) { DebugPrintf(" go - Executes the script\n"); DebugPrintf("\n"); DebugPrintf("Breakpoints:\n"); - DebugPrintf(" bp_list / bplist - Lists the current breakpoints\n"); - DebugPrintf(" bp_del / bpdel - Deletes a breakpoint with the specified index\n"); + DebugPrintf(" bp_list / bplist / bl - Lists the current breakpoints\n"); + DebugPrintf(" bp_del / bpdel / bc - Deletes a breakpoint with the specified index\n"); DebugPrintf(" bp_exec_method / bpx - Sets a breakpoint on the execution of the specified method\n"); DebugPrintf(" bp_exec_function / bpe - Sets a breakpoint on the execution of the specified exported function\n"); DebugPrintf("\n"); DebugPrintf("VM:\n"); DebugPrintf(" script_steps - Shows the number of executed SCI operations\n"); - DebugPrintf(" vm_varlist / vmvarlist - Shows the addresses of variables in the VM\n"); - DebugPrintf(" vm_vars / vmvars - Displays or changes variables in the VM\n"); + DebugPrintf(" vm_varlist / vmvarlist / vl - Shows the addresses of variables in the VM\n"); + DebugPrintf(" vm_vars / vmvars / vv - Displays or changes variables in the VM\n"); DebugPrintf(" stack - Lists the specified number of stack elements\n"); DebugPrintf(" value_type - Determines the type of a value\n"); DebugPrintf(" view_listnode - Examines the list node at the given address\n"); @@ -414,19 +405,12 @@ ResourceType parseResourceType(const char *resid) { return res; } -const char *selector_name(EngineState *s, int selector) { - if (selector >= 0 && selector < (int)g_sci->getKernel()->getSelectorNamesSize()) - return g_sci->getKernel()->getSelectorName(selector).c_str(); - else - return "--INVALID--"; -} - bool Console::cmdGetVersion(int argc, const char **argv) { const char *viewTypeDesc[] = { "Unknown", "EGA", "VGA", "VGA SCI1.1", "Amiga" }; bool hasVocab997 = g_sci->getResMan()->testResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_SELECTORS)) ? true : false; - DebugPrintf("Game ID: %s\n", _engine->getGameID()); + DebugPrintf("Game ID: %s\n", _engine->getGameIdStr()); DebugPrintf("Emulated interpreter version: %s\n", getSciVersionDesc(getSciVersion())); DebugPrintf("\n"); DebugPrintf("Detected features:\n"); @@ -434,9 +418,10 @@ bool Console::cmdGetVersion(int argc, const char **argv) { DebugPrintf("Sound type: %s\n", getSciVersionDesc(_engine->_features->detectDoSoundType())); DebugPrintf("Graphics functions type: %s\n", getSciVersionDesc(_engine->_features->detectGfxFunctionsType())); DebugPrintf("Lofs type: %s\n", getSciVersionDesc(_engine->_features->detectLofsType())); - DebugPrintf("Move count type: %s\n", (_engine->_features->detectMoveCountType() == kIncrementMoveCount) ? "increment" : "ignore"); + DebugPrintf("Move count type: %s\n", (_engine->_features->handleMoveCount()) ? "increment" : "ignore"); DebugPrintf("SetCursor type: %s\n", getSciVersionDesc(_engine->_features->detectSetCursorType())); DebugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]); + DebugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette->isMerging() ? "yes" : "no"); DebugPrintf("Resource volume version: %s\n", g_sci->getResMan()->getVolVersionDesc()); DebugPrintf("Resource map version: %s\n", g_sci->getResMan()->getMapVersionDesc()); DebugPrintf("Contains selector vocabulary (vocab.997): %s\n", hasVocab997 ? "yes" : "no"); @@ -482,14 +467,14 @@ bool Console::cmdSelector(int argc, const char **argv) { return true; } - for (uint seeker = 0; seeker < _engine->getKernel()->getSelectorNamesSize(); seeker++) { - if (!scumm_stricmp(_engine->getKernel()->getSelectorName(seeker).c_str(), argv[1])) { - DebugPrintf("Selector %s found at %03x (%d)\n", _engine->getKernel()->getSelectorName(seeker).c_str(), seeker, seeker); - return true; - } + Common::String name = argv[1]; + int seeker = _engine->getKernel()->findSelector(name.c_str()); + if (seeker >= 0) { + DebugPrintf("Selector %s found at %03x (%d)\n", name.c_str(), seeker, seeker); + return true; } - DebugPrintf("Selector %s wasn't found\n", argv[1]); + DebugPrintf("Selector %s wasn't found\n", name.c_str()); return true; } @@ -597,7 +582,7 @@ bool Console::cmdSetParseNodes(int argc, const char **argv) { bool Console::cmdRegisters(int argc, const char **argv) { EngineState *s = _engine->_gamestate; DebugPrintf("Current register values:\n"); - DebugPrintf("acc=%04x:%04x prev=%04x:%04x &rest=%x\n", PRINT_REG(s->r_acc), PRINT_REG(s->r_prev), s->restAdjustCur); + DebugPrintf("acc=%04x:%04x prev=%04x:%04x &rest=%x\n", PRINT_REG(s->r_acc), PRINT_REG(s->r_prev), s->restAdjust); if (!s->_executionStack.empty()) { DebugPrintf("pc=%04x:%04x obj=%04x:%04x fp=ST:%04x sp=ST:%04x\n", @@ -820,7 +805,7 @@ bool Console::cmdVerifyScripts(int argc, const char **argv) { } Common::List<ResourceId> *resources = _engine->getResMan()->listResources(kResourceTypeScript); - sort(resources->begin(), resources->end(), ResourceIdLess()); + Common::sort(resources->begin(), resources->end()); Common::List<ResourceId>::iterator itr = resources->begin(); DebugPrintf("%d SCI1.1-SCI2.1 scripts found, performing sanity checks...\n", resources->size()); @@ -829,15 +814,15 @@ bool Console::cmdVerifyScripts(int argc, const char **argv) { while (itr != resources->end()) { script = _engine->getResMan()->findResource(*itr, false); if (!script) - DebugPrintf("Error: script %d couldn't be loaded\n", itr->number); + DebugPrintf("Error: script %d couldn't be loaded\n", itr->getNumber()); - heap = _engine->getResMan()->findResource(*itr, false); + heap = _engine->getResMan()->findResource(ResourceId(kResourceTypeHeap, itr->getNumber()), false); if (!heap) - DebugPrintf("Error: script %d doesn't have a corresponding heap\n", itr->number); + DebugPrintf("Error: script %d doesn't have a corresponding heap\n", itr->getNumber()); if (script && heap && (script->size + heap->size > 65535)) DebugPrintf("Error: script and heap %d together are larger than 64KB (%d bytes)\n", - itr->number, script->size + heap->size); + itr->getNumber(), script->size + heap->size); ++itr; } @@ -847,6 +832,167 @@ bool Console::cmdVerifyScripts(int argc, const char **argv) { return true; } +bool Console::cmdShowInstruments(int argc, const char **argv) { + int songNumber = -1; + + if (argc == 2) + songNumber = atoi(argv[1]); + + SciVersion doSoundVersion = _engine->_features->detectDoSoundType(); + MidiPlayer *player = MidiPlayer_Midi_create(doSoundVersion); + MidiParser_SCI *parser = new MidiParser_SCI(doSoundVersion, 0); + parser->setMidiDriver(player); + + Common::List<ResourceId> *resources = _engine->getResMan()->listResources(kResourceTypeSound); + Common::sort(resources->begin(), resources->end()); + Common::List<ResourceId>::iterator itr = resources->begin(); + int instruments[128]; + bool instrumentsSongs[128][1000]; + + for (int i = 0; i < 128; i++) + instruments[i] = 0; + + for (int i = 0; i < 128; i++) + for (int j = 0; j < 1000; j++) + instrumentsSongs[i][j] = false; + + if (songNumber == -1) { + DebugPrintf("%d sounds found, checking their instrument mappings...\n", resources->size()); + DebugPrintf("Instruments:\n"); + DebugPrintf("============\n"); + } + + SoundResource *sound; + + while (itr != resources->end()) { + if (songNumber >= 0 && itr->getNumber() != songNumber) { + ++itr; + continue; + } + + sound = new SoundResource(itr->getNumber(), _engine->getResMan(), doSoundVersion); + int channelFilterMask = sound->getChannelFilterMask(player->getPlayId(), player->hasRhythmChannel()); + SoundResource::Track *track = sound->getTrackByType(player->getPlayId()); + if (track->digitalChannelNr != -1) { + // Skip digitized sound effects + delete sound; + ++itr; + continue; + } + + parser->loadMusic(track, NULL, channelFilterMask, doSoundVersion); + const byte *channelData = parser->getMixedData(); + + byte curEvent = 0, prevEvent = 0, command = 0; + bool endOfTrack = false; + bool firstOneShown = false; + + DebugPrintf("Song %d: ", itr->getNumber()); + + do { + while (*channelData == 0xF8) + channelData++; + + channelData++; // delta + + if ((*channelData & 0xF0) >= 0x80) + curEvent = *(channelData++); + else + curEvent = prevEvent; + if (curEvent < 0x80) + continue; + + prevEvent = curEvent; + command = curEvent >> 4; + + byte channel; + + switch (command) { + case 0xC: // program change + channel = curEvent & 0x0F; + if (channel != 15) { // SCI special + byte instrument = *channelData++; + if (!firstOneShown) + firstOneShown = true; + else + DebugPrintf(","); + + DebugPrintf(" %d", instrument); + instruments[instrument]++; + instrumentsSongs[instrument][itr->getNumber()] = true; + } + break; + case 0xD: + channelData++; // param1 + break; + case 0xB: + channelData++; // param1 + channelData++; // param2 + break; + case 0x8: + case 0x9: + case 0xA: + case 0xE: + channelData++; // param1 + channelData++; // param2 + break; + case 0xF: + if ((curEvent & 0x0F) == 0x2) { + channelData++; // param1 + channelData++; // param2 + } else if ((curEvent & 0x0F) == 0x3) { + channelData++; // param1 + } else if ((curEvent & 0x0F) == 0xF) { // META + byte type = *channelData++; + if (type == 0x2F) {// end of track reached + endOfTrack = true; + } else { + // no further processing necessary + } + } + break; + default: + break; + } + } while (!endOfTrack); + + DebugPrintf("\n"); + + delete sound; + ++itr; + } + + delete parser; + delete player; + + DebugPrintf("\n"); + + if (songNumber == -1) { + DebugPrintf("Used instruments: "); + for (int i = 0; i < 128; i++) { + if (instruments[i] > 0) + DebugPrintf("%d, ", i); + } + DebugPrintf("\n\n"); + + DebugPrintf("Used instruments in songs:\n"); + for (int i = 0; i < 128; i++) { + if (instruments[i] > 0) { + DebugPrintf("Instrument %d: ", i); + for (int j = 0; j < 1000; j++) { + if (instrumentsSongs[i][j]) + DebugPrintf("%d, ", j); + } + DebugPrintf("\n"); + } + } + + DebugPrintf("\n\n"); + } + + return true; +} + bool Console::cmdList(int argc, const char **argv) { if (argc < 2) { DebugPrintf("Lists all the resources of a given type\n"); @@ -870,19 +1016,19 @@ bool Console::cmdList(int argc, const char **argv) { } Common::List<ResourceId> *resources = _engine->getResMan()->listResources(res, number); - sort(resources->begin(), resources->end(), ResourceIdLess()); + Common::sort(resources->begin(), resources->end()); Common::List<ResourceId>::iterator itr = resources->begin(); int cnt = 0; while (itr != resources->end()) { if (number == -1) { - DebugPrintf("%8i", itr->number); + DebugPrintf("%8i", itr->getNumber()); if (++cnt % 10 == 0) DebugPrintf("\n"); - } - else if (number == (int)itr->number) { - DebugPrintf("(%3i, %3i, %3i, %3i) ", (itr->tuple >> 24) & 0xff, (itr->tuple >> 16) & 0xff, - (itr->tuple >> 8) & 0xff, itr->tuple & 0xff); + } else if (number == (int)itr->getNumber()) { + const uint32 tuple = itr->getTuple(); + DebugPrintf("(%3i, %3i, %3i, %3i) ", (tuple >> 24) & 0xff, (tuple >> 16) & 0xff, + (tuple >> 8) & 0xff, tuple & 0xff); if (++cnt % 4 == 0) DebugPrintf("\n"); } @@ -920,7 +1066,7 @@ bool Console::cmdSaveGame(int argc, const char **argv) { } // TODO: enable custom descriptions? force filename into a specific format? - if (gamestate_save(_engine->_gamestate, out, "debugging", version)) { + if (!gamestate_save(_engine->_gamestate, out, "debugging", version)) { DebugPrintf("Saving the game state to '%s' failed\n", argv[1]); } else { out->finalize(); @@ -957,8 +1103,7 @@ bool Console::cmdRestoreGame(int argc, const char **argv) { } bool Console::cmdRestartGame(int argc, const char **argv) { - _engine->_gamestate->restarting_flags |= SCI_GAME_IS_RESTARTING_NOW; - _engine->_gamestate->script_abort_flag = 1; + _engine->_gamestate->abortScriptProcessing = kAbortRestartGame;; return false; } @@ -1135,7 +1280,11 @@ bool Console::cmdDrawRobot(int argc, const char **argv) { uint16 resourceId = atoi(argv[1]); - _engine->_gui32->drawRobot(resourceId); + if (_engine->_gfxPaint32) { + _engine->_gfxPaint32->debugDrawRobot(resourceId); + } else { + DebugPrintf("command not available in non-sci32 games"); + } return true; } #endif @@ -1217,7 +1366,7 @@ bool Console::cmdPrintSegmentTable(int argc, const char **argv) { switch (mobj->getType()) { case SEG_TYPE_SCRIPT: - DebugPrintf("S script.%03d l:%d ", (*(Script *)mobj)._nr, (*(Script *)mobj).getLockers()); + DebugPrintf("S script.%03d l:%d ", (*(Script *)mobj).getScriptNumber(), (*(Script *)mobj).getLockers()); break; case SEG_TYPE_CLONES: @@ -1252,10 +1401,16 @@ bool Console::cmdPrintSegmentTable(int argc, const char **argv) { DebugPrintf("M dynmem: %d bytes", (*(DynMem *)mobj)._size); break; - case SEG_TYPE_STRING_FRAG: - DebugPrintf("F string fragments"); +#ifdef ENABLE_SCI32 + case SEG_TYPE_ARRAY: + DebugPrintf("A SCI32 arrays (%d)", (*(ArrayTable *)mobj).entries_used); break; + case SEG_TYPE_STRING: + DebugPrintf("T SCI32 strings (%d)", (*(StringTable *)mobj).entries_used); + break; +#endif + default: DebugPrintf("I Invalid (type = %x)", mobj->getType()); break; @@ -1281,9 +1436,9 @@ bool Console::segmentInfo(int nr) { case SEG_TYPE_SCRIPT: { Script *scr = (Script *)mobj; - DebugPrintf("script.%03d locked by %d, bufsize=%d (%x)\n", scr->_nr, scr->getLockers(), (uint)scr->getBufSize(), (uint)scr->getBufSize()); + DebugPrintf("script.%03d locked by %d, bufsize=%d (%x)\n", scr->getScriptNumber(), scr->getLockers(), (uint)scr->getBufSize(), (uint)scr->getBufSize()); if (scr->getExportTable()) - DebugPrintf(" Exports: %4d at %d\n", scr->getExportsNr(), (int)(((const byte *)scr->getExportTable()) - ((const byte *)scr->_buf))); + DebugPrintf(" Exports: %4d at %d\n", scr->getExportsNr(), (int)(((const byte *)scr->getExportTable()) - ((const byte *)scr->getBuf()))); else DebugPrintf(" Exports: none\n"); @@ -1394,10 +1549,14 @@ bool Console::segmentInfo(int nr) { } break; - case SEG_TYPE_STRING_FRAG: { - DebugPrintf("string frags\n"); +#ifdef ENABLE_SCI32 + case SEG_TYPE_STRING: + DebugPrintf("SCI32 strings\n"); break; - } + case SEG_TYPE_ARRAY: + DebugPrintf("SCI32 arrays\n"); + break; +#endif default : DebugPrintf("Invalid type %d\n", mobj->getType()); @@ -1413,7 +1572,7 @@ bool Console::cmdSegmentInfo(int argc, const char **argv) { DebugPrintf("Provides information on the specified segment(s)\n"); DebugPrintf("Usage: %s <segment number>\n", argv[0]); DebugPrintf("<segment number> can be a number, which shows the information of the segment with\n"); - DebugPrintf("the specified number, or \"all\" to show information on all active segments"); + DebugPrintf("the specified number, or \"all\" to show information on all active segments\n"); return true; } @@ -1421,9 +1580,11 @@ bool Console::cmdSegmentInfo(int argc, const char **argv) { for (uint i = 0; i < _engine->_gamestate->_segMan->_heap.size(); i++) segmentInfo(i); } else { - int nr = atoi(argv[1]); - if (!segmentInfo(nr)) - DebugPrintf("Segment %04x does not exist\n", nr); + int segmentNr; + if (!parseInteger(argv[1], segmentNr)) + return true; + if (!segmentInfo(segmentNr)) + DebugPrintf("Segment %04xh does not exist\n", segmentNr); } return true; @@ -1436,21 +1597,23 @@ bool Console::cmdKillSegment(int argc, const char **argv) { DebugPrintf("Usage: %s <segment number>\n", argv[0]); return true; } - - _engine->_gamestate->_segMan->getScript(atoi(argv[1]))->setLockers(0); + int segmentNumber; + if (!parseInteger(argv[1], segmentNumber)) + return true; + _engine->_gamestate->_segMan->getScript(segmentNumber)->setLockers(0); return true; } bool Console::cmdShowMap(int argc, const char **argv) { if (argc != 2) { - DebugPrintf("Shows one of the screen maps\n"); + DebugPrintf("Switches to one of the following screen maps\n"); DebugPrintf("Usage: %s <screen map>\n", argv[0]); DebugPrintf("Screen maps:\n"); - DebugPrintf("- 0: visual map (back buffer)\n"); - DebugPrintf("- 1: priority map (back buffer)\n"); - DebugPrintf("- 2: control map (static buffer)\n"); - DebugPrintf("- 3: display screen (newgui only)\n"); + DebugPrintf("- 0: visual map\n"); + DebugPrintf("- 1: priority map\n"); + DebugPrintf("- 2: control map\n"); + DebugPrintf("- 3: display screen\n"); return true; } @@ -1473,23 +1636,7 @@ bool Console::cmdShowMap(int argc, const char **argv) { bool Console::cmdSongLib(int argc, const char **argv) { DebugPrintf("Song library:\n"); - -#ifdef USE_OLD_MUSIC_FUNCTIONS - Song *seeker = _engine->_gamestate->_sound._songlib._lib; - - do { - DebugPrintf(" %p", (void *)seeker); - - if (seeker) { - DebugPrintf("[%04lx,p=%d,s=%d]->", seeker->_handle, seeker->_priority, seeker->_status); - seeker = seeker->_next; - } - DebugPrintf("\n"); - } while (seeker); - DebugPrintf("\n"); -#else - _engine->_gamestate->_soundCmd->printPlayList(this); -#endif + g_sci->_soundCmd->printPlayList(this); return true; } @@ -1509,7 +1656,7 @@ bool Console::cmdSongInfo(int argc, const char **argv) { return true; } - _engine->_gamestate->_soundCmd->printSongInfo(addr, this); + g_sci->_soundCmd->printSongInfo(addr, this); return true; } @@ -1528,7 +1675,7 @@ bool Console::cmdStartSound(int argc, const char **argv) { return true; } - _engine->_gamestate->_soundCmd->startNewSound(number); + g_sci->_soundCmd->startNewSound(number); return false; } @@ -1552,37 +1699,21 @@ bool Console::cmdToggleSound(int argc, const char **argv) { return true; } -#ifdef USE_OLD_MUSIC_FUNCTIONS - int handle = id.segment << 16 | id.offset; // frobnicate handle - - if (id.segment) { - SegManager *segMan = _engine->_gamestate->_segMan; // for writeSelectorValue - _engine->_gamestate->_sound.sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - _engine->_gamestate->_sound.sfx_remove_song(handle); - writeSelectorValue(segMan, id, SELECTOR(signal), SIGNAL_OFFSET); - writeSelectorValue(segMan, id, SELECTOR(nodePtr), 0); - writeSelectorValue(segMan, id, SELECTOR(handle), 0); - } -#else - Common::String newState = argv[2]; newState.toLowercase(); if (newState == "play") - _engine->_gamestate->_soundCmd->playSound(id); + g_sci->_soundCmd->processPlaySound(id); else if (newState == "stop") - _engine->_gamestate->_soundCmd->stopSound(id); + g_sci->_soundCmd->processStopSound(id, false); else DebugPrintf("New state can either be 'play' or 'stop'"); -#endif return true; } bool Console::cmdStopAllSounds(int argc, const char **argv) { -#ifndef USE_OLD_MUSIC_FUNCTIONS - _engine->_gamestate->_soundCmd->stopAllSounds(); -#endif + g_sci->_soundCmd->stopAllSounds(); DebugPrintf("All sounds have been stopped\n"); return true; @@ -1596,36 +1727,6 @@ bool Console::cmdIsSample(int argc, const char **argv) { return true; } -#ifdef USE_OLD_MUSIC_FUNCTIONS - Resource *song = _engine->getResMan()->findResource(ResourceId(kResourceTypeSound, atoi(argv[1])), 0); - SongIterator *songit; - Audio::AudioStream *data; - - if (!song) { - DebugPrintf("Not a sound resource!\n"); - return true; - } - - songit = songit_new(song->data, song->size, SCI_SONG_ITERATOR_TYPE_SCI0, 0xcaffe /* What do I care about the ID? */); - - if (!songit) { - DebugPrintf("Could not convert to song iterator!\n"); - return true; - } - - data = songit->getAudioStream(); - if (data) { - // TODO -/* - DebugPrintf("\nIs sample (encoding %dHz/%s/%04x)", data->conf.rate, (data->conf.stereo) ? - ((data->conf.stereo == SFX_PCM_STEREO_LR) ? "stereo-LR" : "stereo-RL") : "mono", data->conf.format); -*/ - delete data; - } else - DebugPrintf("Valid song, but not a sample.\n"); - - delete songit; -#else int16 number = atoi(argv[1]); if (!_engine->getResMan()->testResource(ResourceId(kResourceTypeSound, number))) { @@ -1649,7 +1750,6 @@ bool Console::cmdIsSample(int argc, const char **argv) { DebugPrintf("Sample size: %d, sample rate: %d, channels: %d, digital channel number: %d\n", track->digitalSampleSize, track->digitalSampleRate, track->channelCount, track->digitalChannelNr); -#endif return true; } @@ -1661,10 +1761,10 @@ bool Console::cmdGCInvoke(int argc, const char **argv) { } bool Console::cmdGCObjects(int argc, const char **argv) { - reg_t_hash_map *use_map = find_all_used_references(_engine->_gamestate); + AddrSet *use_map = findAllActiveReferences(_engine->_gamestate); DebugPrintf("Reachable object references (normalised):\n"); - for (reg_t_hash_map::iterator i = use_map->begin(); i != use_map->end(); ++i) { + for (AddrSet::iterator i = use_map->begin(); i != use_map->end(); ++i) { DebugPrintf(" - %04x:%04x\n", PRINT_REG(i->_key)); } @@ -1673,11 +1773,6 @@ bool Console::cmdGCObjects(int argc, const char **argv) { return true; } -void _print_address(void * _, reg_t addr) { - if (addr.segment) - g_sci->getSciDebugger()->DebugPrintf(" %04x:%04x\n", PRINT_REG(addr)); -} - bool Console::cmdGCShowReachable(int argc, const char **argv) { if (argc != 2) { DebugPrintf("Prints all addresses directly reachable from the memory object specified as parameter.\n"); @@ -1701,7 +1796,10 @@ bool Console::cmdGCShowReachable(int argc, const char **argv) { } DebugPrintf("Reachable from %04x:%04x:\n", PRINT_REG(addr)); - mobj->listAllOutgoingReferences(addr, NULL, _print_address); + 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) + g_sci->getSciDebugger()->DebugPrintf(" %04x:%04x\n", PRINT_REG(*it)); return true; } @@ -1730,7 +1828,10 @@ bool Console::cmdGCShowFreeable(int argc, const char **argv) { } DebugPrintf("Freeable in segment %04x:\n", addr.segment); - mobj->listAllDeallocatable(addr.segment, NULL, _print_address); + const Common::Array<reg_t> tmp = mobj->listAllDeallocatable(addr.segment); + for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) + if (it->segment) + g_sci->getSciDebugger()->DebugPrintf(" %04x:%04x\n", PRINT_REG(*it)); return true; } @@ -1772,9 +1873,9 @@ bool Console::cmdVMVarlist(int argc, const char **argv) { DebugPrintf("Addresses of variables in the VM:\n"); for (int i = 0; i < 4; i++) { - DebugPrintf("%s vars at %04x:%04x ", varnames[i], PRINT_REG(make_reg(s->variables_seg[i], s->variables[i] - s->variables_base[i]))); - if (s->variables_max) - DebugPrintf(" total %d", s->variables_max[i]); + DebugPrintf("%s vars at %04x:%04x ", varnames[i], PRINT_REG(make_reg(s->variablesSegment[i], s->variables[i] - s->variablesBase[i]))); + if (s->variablesMax) + DebugPrintf(" total %d", s->variablesMax[i]); DebugPrintf("\n"); } @@ -1782,76 +1883,95 @@ bool Console::cmdVMVarlist(int argc, const char **argv) { } bool Console::cmdVMVars(int argc, const char **argv) { - if (argc < 3) { + if (argc < 2) { DebugPrintf("Displays or changes variables in the VM\n"); DebugPrintf("Usage: %s <type> <varnum> [<value>]\n", argv[0]); - DebugPrintf("First parameter is either g(lobal), l(ocal), t(emp) or p(aram).\n"); - DebugPrintf("Second parameter is the var number\n"); + DebugPrintf("First parameter is either g(lobal), l(ocal), t(emp), p(aram) or a(cc).\n"); + DebugPrintf("Second parameter is the var number (not specified on acc)\n"); DebugPrintf("Third parameter (if specified) is the value to set the variable to, in address form\n"); DebugPrintf("Check the \"addresses\" command on how to use addresses\n"); return true; } EngineState *s = _engine->_gamestate; - const char *varnames[] = {"global", "local", "temp", "param"}; - const char *varabbrev = "gltp"; - const char *vartype_pre = strchr(varabbrev, *argv[1]); - int vartype; - int idx; - - if (!vartype_pre) { + const char *varNames[] = {"global", "local", "temp", "param", "acc"}; + const char *varAbbrev = "gltpa"; + const char *varType_pre = strchr(varAbbrev, *argv[1]); + int varType; + int varIndex = 0; + reg_t *curValue = NULL; + const char *setValue = NULL; + + if (!varType_pre) { DebugPrintf("Invalid variable type '%c'\n", *argv[1]); return true; } - vartype = vartype_pre - varabbrev; + varType = varType_pre - varAbbrev; - char *endPtr = 0; - int idxLen = strlen(argv[2]); - const char *lastChar = argv[2] + idxLen - (idxLen == 0 ? 0 : 1); + switch (varType) { + case 0: + case 1: + case 2: + case 3: { + // for global, local, temp and param, we need an index + if (argc < 3) { + DebugPrintf("Variable number must be specified for requested type\n"); + return true; + } + if (argc > 4) { + DebugPrintf("Too many arguments\n"); + return true; + } - if ((strncmp(argv[2], "0x", 2) == 0) || (*lastChar == 'h')) { - // hexadecimal number - idx = strtol(argv[2], &endPtr, 16); - if ((*endPtr != 0) && (*endPtr != 'h')) { - DebugPrintf("Invalid hexadecimal index '%s'\n", argv[2]); + if (!parseInteger(argv[2], varIndex)) + return true; + + if (varIndex < 0) { + DebugPrintf("Variable number may not be negative\n"); return true; } - } else { - // decimal number - idx = strtol(argv[2], &endPtr, 10); - if (*endPtr != 0) { - DebugPrintf("Invalid decimal index '%s'\n", argv[2]); + + if ((s->variablesMax) && (s->variablesMax[varType] <= varIndex)) { + DebugPrintf("Maximum variable number for this type is %d (0x%x)\n", s->variablesMax[varType], s->variablesMax[varType]); return true; } + curValue = &s->variables[varType][varIndex]; + if (argc == 4) + setValue = argv[3]; + break; } - if (idx < 0) { - DebugPrintf("Invalid: negative index\n"); - return true; - } + case 4: + // acc + if (argc > 3) { + DebugPrintf("Too many arguments\n"); + return true; + } + curValue = &s->r_acc; + if (argc == 3) + setValue = argv[2]; + break; - if ((s->variables_max) && (s->variables_max[vartype] <= idx)) { - DebugPrintf("Max. index is %d (0x%x)\n", s->variables_max[vartype], s->variables_max[vartype]); - return true; + default: + break; } - switch (argc) { - case 3: - DebugPrintf("%s var %d == %04x:%04x\n", varnames[vartype], idx, PRINT_REG(s->variables[vartype][idx])); - break; - case 4: - if (parse_reg_t(_engine->_gamestate, argv[3], &s->variables[vartype][idx], true)) { + if (!setValue) { + if (varType == 4) + DebugPrintf("%s == %04x:%04x", varNames[varType], PRINT_REG(*curValue)); + else + DebugPrintf("%s var %d == %04x:%04x", varNames[varType], varIndex, PRINT_REG(*curValue)); + printBasicVarInfo(*curValue); + DebugPrintf("\n"); + } else { + if (parse_reg_t(s, setValue, curValue, true)) { DebugPrintf("Invalid value/address passed.\n"); DebugPrintf("Check the \"addresses\" command on how to use addresses\n"); DebugPrintf("Or pass a decimal or hexadecimal value directly (e.g. 12, 1Ah)\n"); return true; } - break; - default: - DebugPrintf("Too many arguments\n"); } - return true; } @@ -1901,20 +2021,22 @@ bool Console::cmdValueType(int argc, const char **argv) { int t = g_sci->getKernel()->findRegType(val); switch (t) { - case KSIG_LIST: + case SIG_TYPE_LIST: DebugPrintf("List"); break; - case KSIG_OBJECT: + case SIG_TYPE_OBJECT: DebugPrintf("Object"); break; - case KSIG_REF: + case SIG_TYPE_REFERENCE: DebugPrintf("Reference"); break; - case KSIG_ARITHMETIC: - DebugPrintf("Arithmetic"); + case SIG_TYPE_INTEGER: + DebugPrintf("Integer"); + case SIG_TYPE_INTEGER | SIG_TYPE_NULL: + DebugPrintf("Null"); break; default: - DebugPrintf("Erroneous unknown type %02x(%d decimal)\n", t, t); + DebugPrintf("Erroneous unknown type 0x%02x (%d decimal)\n", t, t); } return true; @@ -1978,7 +2100,7 @@ bool Console::cmdViewReference(int argc, const char **argv) { return true; } - if (reg_end.segment != reg.segment) { + if (reg_end.segment != reg.segment && reg_end != NULL_REG) { DebugPrintf("Ending segment different from starting segment. Assuming no bound on dump.\n"); reg_end = NULL_REG; } @@ -1994,47 +2116,68 @@ bool Console::cmdViewReference(int argc, const char **argv) { switch (type) { case 0: break; - case KSIG_LIST: { - List *l = _engine->_gamestate->_segMan->lookupList(reg); + case SIG_TYPE_LIST: { + List *list = _engine->_gamestate->_segMan->lookupList(reg); DebugPrintf("list\n"); - if (l) - printList(l); + if (list) + printList(list); else DebugPrintf("Invalid list.\n"); } break; - case KSIG_NODE: + case SIG_TYPE_NODE: DebugPrintf("list node\n"); printNode(reg); break; - case KSIG_OBJECT: + case SIG_TYPE_OBJECT: DebugPrintf("object\n"); printObject(reg); break; - case KSIG_REF: { - int size; - const SegmentRef block = _engine->_gamestate->_segMan->dereference(reg); - size = block.maxSize; + case SIG_TYPE_REFERENCE: { + switch (_engine->_gamestate->_segMan->getSegmentType(reg.segment)) { +#ifdef ENABLE_SCI32 + case SEG_TYPE_STRING: { + DebugPrintf("SCI32 string\n"); + const SciString *str = _engine->_gamestate->_segMan->lookupString(reg); + Common::hexdump((const byte *) str->getRawData(), str->getSize(), 16, 0); + break; + } + case SEG_TYPE_ARRAY: { + DebugPrintf("SCI32 array:\n"); + const SciArray<reg_t> *array = _engine->_gamestate->_segMan->lookupArray(reg); + hexDumpReg(array->getRawData(), array->getSize(), 4, 0, true); + break; + } +#endif + default: { + int size; + const SegmentRef block = _engine->_gamestate->_segMan->dereference(reg); + size = block.maxSize; - DebugPrintf("raw data\n"); + DebugPrintf("raw data\n"); - if (reg_end.segment != 0 && size < reg_end.offset - reg.offset) { - 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) { + 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.segment != 0 && (size >= reg_end.offset - reg.offset)) + size = reg_end.offset - reg.offset; - if (reg_end.segment != 0) - DebugPrintf("Block size less than or equal to %d\n", size); + if (reg_end.segment != 0) + DebugPrintf("Block size less than or equal to %d\n", size); - Common::hexdump(block.raw, size, 16, 0); + if (block.isRaw) + Common::hexdump(block.raw, size, 16, 0); + else + hexDumpReg(block.reg, size / 2, 4, 0); + } } break; - case KSIG_ARITHMETIC: + } + case SIG_TYPE_INTEGER: DebugPrintf("arithmetic value\n %d (%04x)\n", (int16) reg.offset, reg.offset); break; default: @@ -2087,35 +2230,12 @@ bool Console::cmdViewAccumulatorObject(int argc, const char **argv) { } bool Console::cmdScriptSteps(int argc, const char **argv) { - DebugPrintf("Number of executed SCI operations: %d\n", _engine->_gamestate->script_step_counter); - return true; -} - -bool Console::cmdSetAccumulator(int argc, const char **argv) { - if (argc != 2) { - DebugPrintf("Sets the accumulator.\n"); - DebugPrintf("Usage: %s <address>\n", argv[0]); - DebugPrintf("Check the \"addresses\" command on how to use addresses\n"); - return true; - } - - reg_t val; - - if (parse_reg_t(_engine->_gamestate, argv[1], &val, false)) { - DebugPrintf("Invalid address passed.\n"); - DebugPrintf("Check the \"addresses\" command on how to use addresses\n"); - return true; - } - - _engine->_gamestate->r_acc = val; - + DebugPrintf("Number of executed SCI operations: %d\n", _engine->_gamestate->scriptStepCounter); return true; } bool Console::cmdBacktrace(int argc, const char **argv) { - DebugPrintf("Dumping the send/self/super/call/calle/callb stack:\n"); - - DebugPrintf("Call stack (current base: 0x%x):\n", _engine->_gamestate->execution_stack_base); + DebugPrintf("Call stack (current base: 0x%x):\n", _engine->_gamestate->executionStackBase); Common::List<ExecStack>::iterator iter; uint i = 0; @@ -2126,20 +2246,25 @@ bool Console::cmdBacktrace(int argc, const char **argv) { int paramc, totalparamc; switch (call.type) { - - case EXEC_STACK_TYPE_CALL: {// Normal function - DebugPrintf(" %x:[%x] %s::%s(", i, call.origin, objname, (call.selector == -1) ? "<call[be]?>" : - selector_name(_engine->_gamestate, call.selector)); - } - break; + 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()); + if (call.debugSelector != -1) { + DebugPrintf("%s::%s(", objname, _engine->getKernel()->getSelectorName(call.debugSelector).c_str()); + } else if (call.debugExportId != -1) { + DebugPrintf("export %d (", call.debugExportId); + } else if (call.debugLocalCallOffset != -1) { + DebugPrintf("call %x (", call.debugLocalCallOffset); + } + break; case EXEC_STACK_TYPE_KERNEL: // Kernel function - DebugPrintf(" %x:[%x] k%s(", i, call.origin, _engine->getKernel()->getKernelName(call.selector).c_str()); + DebugPrintf(" %x:[%x] k%s(", i, call.debugOrigin, _engine->getKernel()->getKernelName(call.debugSelector).c_str()); break; case EXEC_STACK_TYPE_VARSELECTOR: - DebugPrintf(" %x:[%x] vs%s %s::%s (", i, call.origin, (call.argc) ? "write" : "read", - objname, _engine->getKernel()->getSelectorName(call.selector).c_str()); + DebugPrintf(" %x:[%x] vs%s %s::%s (", i, call.debugOrigin, (call.argc) ? "write" : "read", + objname, _engine->getKernel()->getSelectorName(call.debugSelector).c_str()); break; } @@ -2158,7 +2283,10 @@ bool Console::cmdBacktrace(int argc, const char **argv) { if (call.argc > 16) DebugPrintf("..."); - DebugPrintf(")\n obj@%04x:%04x", PRINT_REG(call.objp)); + DebugPrintf(")\n "); + if (call.debugOrigin != -1) + DebugPrintf("by %x ", call.debugOrigin); + DebugPrintf("obj@%04x:%04x", PRINT_REG(call.objp)); if (call.type == EXEC_STACK_TYPE_CALL) { DebugPrintf(" pc=%04x:%04x", PRINT_REG(call.addr.pc)); if (call.sp == CALL_SP_CARRY) @@ -2171,33 +2299,39 @@ bool Console::cmdBacktrace(int argc, const char **argv) { DebugPrintf(" pc:none"); DebugPrintf(" argp:ST:%04x", (unsigned)(call.variables_argp - _engine->_gamestate->stack_base)); - if (call.type == EXEC_STACK_TYPE_CALL) - DebugPrintf(" script: %d", (*(Script *)_engine->_gamestate->_segMan->_heap[call.addr.pc.segment])._nr); DebugPrintf("\n"); } return true; } -bool Console::cmdStep(int argc, const char **argv) { +bool Console::cmdTrace(int argc, const char **argv) { if (argc == 2 && atoi(argv[1]) > 0) - g_debugState.runningStep = atoi(argv[1]) - 1; - g_debugState.debugging = true; + _debugState.runningStep = atoi(argv[1]) - 1; + _debugState.debugging = true; + + return false; +} + +bool Console::cmdStepOver(int argc, const char **argv) { + _debugState.seeking = kDebugSeekStepOver; + _debugState.seekLevel = _engine->_gamestate->_executionStack.size(); + _debugState.debugging = true; return false; } bool Console::cmdStepEvent(int argc, const char **argv) { - g_debugState.stopOnEvent = true; - g_debugState.debugging = true; + _debugState.stopOnEvent = true; + _debugState.debugging = true; return false; } bool Console::cmdStepRet(int argc, const char **argv) { - g_debugState.seeking = kDebugSeekLevelRet; - g_debugState.seekLevel = _engine->_gamestate->_executionStack.size() - 1; - g_debugState.debugging = true; + _debugState.seeking = kDebugSeekLevelRet; + _debugState.seekLevel = _engine->_gamestate->_executionStack.size() - 1; + _debugState.debugging = true; return false; } @@ -2209,9 +2343,9 @@ bool Console::cmdStepGlobal(int argc, const char **argv) { return true; } - g_debugState.seeking = kDebugSeekGlobal; - g_debugState.seekSpecial = atoi(argv[1]); - g_debugState.debugging = true; + _debugState.seeking = kDebugSeekGlobal; + _debugState.seekSpecial = atoi(argv[1]); + _debugState.debugging = true; return false; } @@ -2239,12 +2373,12 @@ bool Console::cmdStepCallk(int argc, const char **argv) { } } - g_debugState.seeking = kDebugSeekSpecialCallk; - g_debugState.seekSpecial = callk_index; + _debugState.seeking = kDebugSeekSpecialCallk; + _debugState.seekSpecial = callk_index; } else { - g_debugState.seeking = kDebugSeekCallk; + _debugState.seeking = kDebugSeekCallk; } - g_debugState.debugging = true; + _debugState.debugging = true; return false; } @@ -2357,11 +2491,11 @@ bool Console::cmdSend(int argc, const char **argv) { return true; } - const char *selector_name = argv[2]; - int selectorId = _engine->getKernel()->findSelector(selector_name); + const char *selectorName = argv[2]; + int selectorId = _engine->getKernel()->findSelector(selectorName); if (selectorId < 0) { - DebugPrintf("Unknown selector: \"%s\"\n", selector_name); + DebugPrintf("Unknown selector: \"%s\"\n", selectorName); return true; } @@ -2371,10 +2505,10 @@ bool Console::cmdSend(int argc, const char **argv) { return true; } - SelectorType selector_type = lookupSelector(_engine->_gamestate->_segMan, object, selectorId, 0, 0); + SelectorType selector_type = lookupSelector(_engine->_gamestate->_segMan, object, selectorId, NULL, NULL); if (selector_type == kSelectorNone) { - DebugPrintf("Object does not support selector: \"%s\"\n", selector_name); + DebugPrintf("Object does not support selector: \"%s\"\n", selectorName); return true; } @@ -2426,7 +2560,7 @@ bool Console::cmdSend(int argc, const char **argv) { bool Console::cmdGo(int argc, const char **argv) { // CHECKME: is this necessary? - g_debugState.seeking = kDebugSeekNothing; + _debugState.seeking = kDebugSeekNothing; return Cmd_Exit(argc, argv); } @@ -2437,8 +2571,8 @@ bool Console::cmdBreakpointList(int argc, const char **argv) { DebugPrintf("Breakpoint list:\n"); - Common::List<Breakpoint>::const_iterator bp = g_debugState._breakpoints.begin(); - Common::List<Breakpoint>::const_iterator end = g_debugState._breakpoints.end(); + Common::List<Breakpoint>::const_iterator bp = _debugState._breakpoints.begin(); + Common::List<Breakpoint>::const_iterator end = _debugState._breakpoints.end(); for (; bp != end; ++bp) { DebugPrintf(" #%i: ", i); switch (bp->type) { @@ -2454,6 +2588,9 @@ bool Console::cmdBreakpointList(int argc, const char **argv) { i++; } + if (!i) + DebugPrintf(" No breakpoints defined.\n"); + return true; } @@ -2461,14 +2598,21 @@ bool Console::cmdBreakpointDelete(int argc, const char **argv) { if (argc != 2) { DebugPrintf("Deletes a breakpoint with the specified index.\n"); DebugPrintf("Usage: %s <breakpoint index>\n", argv[0]); + DebugPrintf("<index> * will remove all breakpoints\n"); + return true; + } + + if (strcmp(argv[1], "*") == 0) { + _debugState._breakpoints.clear(); + _debugState._activeBreakpointTypes = 0; return true; } const int idx = atoi(argv[1]); // Find the breakpoint at index idx. - Common::List<Breakpoint>::iterator bp = g_debugState._breakpoints.begin(); - const Common::List<Breakpoint>::iterator end = g_debugState._breakpoints.end(); + Common::List<Breakpoint>::iterator bp = _debugState._breakpoints.begin(); + const Common::List<Breakpoint>::iterator end = _debugState._breakpoints.end(); for (int i = 0; bp != end && i < idx; ++bp, ++i) { // do nothing } @@ -2479,15 +2623,15 @@ bool Console::cmdBreakpointDelete(int argc, const char **argv) { } // Delete it - g_debugState._breakpoints.erase(bp); + _debugState._breakpoints.erase(bp); // Update EngineState::_activeBreakpointTypes. int type = 0; - for (bp = g_debugState._breakpoints.begin(); bp != end; ++bp) { + for (bp = _debugState._breakpoints.begin(); bp != end; ++bp) { type |= bp->type; } - g_debugState._activeBreakpointTypes = type; + _debugState._activeBreakpointTypes = type; return true; } @@ -2509,8 +2653,8 @@ bool Console::cmdBreakpointExecMethod(int argc, const char **argv) { bp.type = BREAK_SELECTOR; bp.name = argv[1]; - g_debugState._breakpoints.push_back(bp); - g_debugState._activeBreakpointTypes |= BREAK_SELECTOR; + _debugState._breakpoints.push_back(bp); + _debugState._activeBreakpointTypes |= BREAK_SELECTOR; return true; } @@ -2530,8 +2674,8 @@ bool Console::cmdBreakpointExecFunction(int argc, const char **argv) { bp.type = BREAK_EXPORT; bp.address = (atoi(argv[1]) << 16 | atoi(argv[2])); - g_debugState._breakpoints.push_back(bp); - g_debugState._activeBreakpointTypes |= BREAK_EXPORT; + _debugState._breakpoints.push_back(bp); + _debugState._activeBreakpointTypes |= BREAK_EXPORT; return true; } @@ -2731,9 +2875,9 @@ bool Console::cmdQuit(int argc, const char **argv) { if (!scumm_stricmp(argv[1], "game")) { // Quit gracefully - _engine->_gamestate->script_abort_flag = 1; // Terminate VM - g_debugState.seeking = kDebugSeekNothing; - g_debugState.runningStep = 0; + _engine->_gamestate->abortScriptProcessing = kAbortQuitGame; // Terminate VM + _debugState.seeking = kDebugSeekNothing; + _debugState.runningStep = 0; } else if (!scumm_stricmp(argv[1], "now")) { // Quit ungracefully @@ -2980,8 +3124,71 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV return 0; } -void Console::printList(List *l) { - reg_t pos = l->first; +bool Console::parseInteger(const char *argument, int &result) { + char *endPtr = 0; + int idxLen = strlen(argument); + const char *lastChar = argument + idxLen - (idxLen == 0 ? 0 : 1); + + if ((strncmp(argument, "0x", 2) == 0) || (*lastChar == 'h')) { + // hexadecimal number + result = strtol(argument, &endPtr, 16); + if ((*endPtr != 0) && (*endPtr != 'h')) { + DebugPrintf("Invalid hexadecimal number '%s'\n", argument); + return false; + } + } else { + // decimal number + result = strtol(argument, &endPtr, 10); + if (*endPtr != 0) { + DebugPrintf("Invalid decimal number '%s'\n", argument); + return false; + } + } + return true; +} + +void Console::printBasicVarInfo(reg_t variable) { + int regType = g_sci->getKernel()->findRegType(variable); + int segType = regType; + SegManager *segMan = g_sci->getEngineState()->_segMan; + + segType &= SIG_TYPE_INTEGER | SIG_TYPE_OBJECT | SIG_TYPE_REFERENCE | SIG_TYPE_NODE | SIG_TYPE_LIST | SIG_TYPE_UNINITIALIZED | SIG_TYPE_ERROR; + + switch (segType) { + case SIG_TYPE_INTEGER: { + uint16 content = variable.toUint16(); + if (content >= 10) + DebugPrintf(" (%dd)", content); + break; + } + case SIG_TYPE_OBJECT: + DebugPrintf(" (object '%s')", segMan->getObjectName(variable)); + break; + case SIG_TYPE_REFERENCE: + DebugPrintf(" (reference)"); + break; + case SIG_TYPE_NODE: + DebugPrintf(" (node)"); + break; + case SIG_TYPE_LIST: + DebugPrintf(" (list)"); + break; + case SIG_TYPE_UNINITIALIZED: + DebugPrintf(" (uninitialized)"); + break; + case SIG_TYPE_ERROR: + DebugPrintf(" (error)"); + break; + default: + DebugPrintf(" (??\?)"); + } + + if (regType & SIG_IS_INVALID) + DebugPrintf(" IS INVALID!"); +} + +void Console::printList(List *list) { + reg_t pos = list->first; reg_t my_prev = NULL_REG; DebugPrintf("\t<\n"); @@ -3008,9 +3215,9 @@ void Console::printList(List *l) { pos = node->succ; } - if (my_prev != l->last) + if (my_prev != list->last) DebugPrintf(" WARNING: Last node was expected to be %04x:%04x, was %04x:%04x!\n", - PRINT_REG(l->last), PRINT_REG(my_prev)); + PRINT_REG(list->last), PRINT_REG(my_prev)); DebugPrintf("\t>\n"); } @@ -3076,7 +3283,7 @@ int Console::printObject(reg_t pos) { DebugPrintf(" "); if (i < var_container->getVarCount()) { uint16 varSelector = var_container->getVarSelector(i); - DebugPrintf("[%03x] %s = ", varSelector, selector_name(s, varSelector)); + DebugPrintf("[%03x] %s = ", varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str()); } else DebugPrintf("p#%x = ", i); @@ -3095,12 +3302,66 @@ int Console::printObject(reg_t pos) { DebugPrintf(" -- methods:\n"); for (i = 0; i < obj->getMethodCount(); i++) { reg_t fptr = obj->getFunction(i); - DebugPrintf(" [%03x] %s = %04x:%04x\n", obj->getFuncSelector(i), selector_name(s, obj->getFuncSelector(i)), PRINT_REG(fptr)); + 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)->_nr); + DebugPrintf("\nOwner script: %d\n", s->_segMan->getScript(pos.segment)->getScriptNumber()); return 0; } +void Console::hexDumpReg(const reg_t *data, int len, int regsPerLine, int startOffset, bool isArray) { + // reg_t version of Common::hexdump + assert(1 <= regsPerLine && regsPerLine <= 8); + int i; + byte c; + int offset = startOffset; + while (len >= regsPerLine) { + printf("%06x: ", offset); + for (i = 0; i < regsPerLine; i++) { + printf("%04x:%04x ", PRINT_REG(data[i])); + } + printf(" |"); + for (i = 0; i < regsPerLine; i++) { + c = data[i].toUint16() >> 8; + if (c < 32 || c >= 127) + c = '.'; + printf("%c", c); + c = data[i].toUint16() & 0xff; + if (c < 32 || c >= 127) + c = '.'; + printf("%c", c); + } + printf("|\n"); + data += regsPerLine; + len -= regsPerLine; + offset += regsPerLine * (isArray ? 1 : 2); + } + + if (len <= 0) + return; + + printf("%06x: ", offset); + for (i = 0; i < regsPerLine; i++) { + if (i < len) + printf("%04x:%04x ", PRINT_REG(data[i])); + else + printf(" "); + } + printf(" |"); + for (i = 0; i < len; i++) { + c = data[i].toUint16() >> 8; + if (c < 32 || c >= 127) + c = '.'; + printf("%c", c); + c = data[i].toUint16() & 0xff; + if (c < 32 || c >= 127) + c = '.'; + printf("%c", c); + } + for (; i < regsPerLine; i++) + printf(" "); + printf("|\n"); +} + } // End of namespace Sci diff --git a/engines/sci/console.h b/engines/sci/console.h index 2b13e03ef6..51f02d7168 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -74,6 +74,7 @@ private: bool cmdList(int argc, const char **argv); bool cmdHexgrep(int argc, const char **argv); bool cmdVerifyScripts(int argc, const char **argv); + bool cmdShowInstruments(int argc, const char **argv); // Game bool cmdSaveGame(int argc, const char **argv); bool cmdRestoreGame(int argc, const char **argv); @@ -117,9 +118,9 @@ private: bool cmdAddresses(int argc, const char **argv); bool cmdRegisters(int argc, const char **argv); bool cmdDissectScript(int argc, const char **argv); - bool cmdSetAccumulator(int argc, const char **argv); bool cmdBacktrace(int argc, const char **argv); - bool cmdStep(int argc, const char **argv); + bool cmdTrace(int argc, const char **argv); + bool cmdStepOver(int argc, const char **argv); bool cmdStepEvent(int argc, const char **argv); bool cmdStepRet(int argc, const char **argv); bool cmdStepGlobal(int argc, const char **argv); @@ -145,12 +146,18 @@ private: bool cmdViewActiveObject(int argc, const char **argv); bool cmdViewAccumulatorObject(int argc, const char **argv); + bool parseInteger(const char *argument, int &result); + + void printBasicVarInfo(reg_t variable); + bool segmentInfo(int nr); - void printList(List *l); + void printList(List *list); int printNode(reg_t addr); + void hexDumpReg(const reg_t *data, int len, int regsPerLine = 4, int startOffset = 0, bool isArray = false); private: SciEngine *_engine; + DebugState &_debugState; bool _mouseVisible; Common::String _videoFile; int _videoFrameDelay; diff --git a/engines/sci/debug.h b/engines/sci/debug.h index 8383722956..5cf0e38fbc 100644 --- a/engines/sci/debug.h +++ b/engines/sci/debug.h @@ -26,8 +26,8 @@ #ifndef SCI_DEBUG_H #define SCI_DEBUG_H +#include "common/list.h" #include "sci/engine/vm_types.h" // for StackPtr -#include "sci/engine/vm.h" // for ExecStack namespace Sci { @@ -57,8 +57,8 @@ enum DebugSeeking { kDebugSeekCallk = 1, // Step forward until callk is found kDebugSeekLevelRet = 2, // Step forward until returned from this level kDebugSeekSpecialCallk = 3, // Step forward until a /special/ callk is found - kDebugSeekSO = 4, // Step forward until specified PC (after the send command) and stack depth - kDebugSeekGlobal = 5 // Step forward until one specified global variable is modified + kDebugSeekGlobal = 4, // Step forward until one specified global variable is modified + kDebugSeekStepOver = 5 // Step forward until we reach same stack-level again }; struct DebugState { @@ -79,7 +79,6 @@ struct DebugState { extern int g_debug_sleeptime_factor; extern int g_debug_simulated_key; extern bool g_debug_track_mouse_clicks; -extern DebugState g_debugState; } // End of namespace Sci diff --git a/engines/sci/decompressor.cpp b/engines/sci/decompressor.cpp index 84af50b596..96c7f24ef6 100644 --- a/engines/sci/decompressor.cpp +++ b/engines/sci/decompressor.cpp @@ -574,15 +574,17 @@ void DecompressorLZW::reorderView(byte *src, byte *dest) { for (c = 0; c < cel_total; c++) decodeRLE(&rle_ptr, &pix_ptr, cc_pos[c] + 8, cc_lengths[c]); - *writer++ = 'P'; - *writer++ = 'A'; - *writer++ = 'L'; + if (pal_offset) { + *writer++ = 'P'; + *writer++ = 'A'; + *writer++ = 'L'; - for (c = 0; c < 256; c++) - *writer++ = c; + for (c = 0; c < 256; c++) + *writer++ = c; - seeker -= 4; /* The missing four. Don't ask why. */ - memcpy(writer, seeker, 4*256 + 4); + seeker -= 4; /* The missing four. Don't ask why. */ + memcpy(writer, seeker, 4*256 + 4); + } free(cc_pos); free(cc_lengths); diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index 1ccfc6bf02..eb2c989e0d 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -36,12 +36,11 @@ #include "sci/engine/script.h" #include "sci/engine/seg_manager.h" #include "sci/engine/state.h" -#include "sci/engine/vm.h" // for convertSierraGameId namespace Sci { // Titles of the games -static const PlainGameDescriptor SciGameTitles[] = { +static const PlainGameDescriptor s_sciGameTitles[] = { {"sci", "Sierra SCI Game"}, {"sci-fanmade", "Fanmade SCI Game"}, // === SCI0 games ========================================================= @@ -106,6 +105,7 @@ static const PlainGameDescriptor SciGameTitles[] = { // TODO: Inside The Chest/Behind the Developer's Shield {"kq7", "King's Quest VII: The Princeless Bride"}, // TODO: King's Questions + {"lsl6hires", "Leisure Suit Larry 6: Shape Up or Slip Out!"}, {"phantasmagoria", "Phantasmagoria"}, {"pqswat", "Police Quest: SWAT"}, {"shivers", "Shivers"}, @@ -120,6 +120,81 @@ static const PlainGameDescriptor SciGameTitles[] = { {0, 0} }; +struct GameIdStrToEnum { + const char *gameidStr; + SciGameId gameidEnum; +}; + +static const GameIdStrToEnum s_gameIdStrToEnum[] = { + { "astrochicken", GID_ASTROCHICKEN }, + { "camelot", GID_CAMELOT }, + { "castlebrain", GID_CASTLEBRAIN }, + { "christmas1988", GID_CHRISTMAS1988 }, + { "christmas1990", GID_CHRISTMAS1990 }, + { "christmas1992", GID_CHRISTMAS1992 }, + { "cnick-kq", GID_CNICK_KQ }, + { "cnick-laurabow", GID_CNICK_LAURABOW }, + { "cnick-longbow", GID_CNICK_LONGBOW }, + { "cnick-lsl", GID_CNICK_LSL }, + { "cnick-sq", GID_CNICK_SQ }, + { "ecoquest", GID_ECOQUEST }, + { "ecoquest2", GID_ECOQUEST2 }, + { "fairytales", GID_FAIRYTALES }, + { "freddypharkas", GID_FREDDYPHARKAS }, + { "funseeker", GID_FUNSEEKER }, + { "gk1", GID_GK1 }, + { "gk2", GID_GK2 }, + { "hoyle1", GID_HOYLE1 }, + { "hoyle2", GID_HOYLE2 }, + { "hoyle3", GID_HOYLE3 }, + { "hoyle4", GID_HOYLE4 }, + { "iceman", GID_ICEMAN }, + { "islandbrain", GID_ISLANDBRAIN }, + { "jones", GID_JONES }, + { "kq1sci", GID_KQ1 }, + { "kq4sci", GID_KQ4 }, + { "kq5", GID_KQ5 }, + { "kq6", GID_KQ6 }, + { "kq7", GID_KQ7 }, + { "laurabow", GID_LAURABOW }, + { "laurabow2", GID_LAURABOW2 }, + { "lighthouse", GID_LIGHTHOUSE }, + { "longbow", GID_LONGBOW }, + { "lsl1sci", GID_LSL1 }, + { "lsl2", GID_LSL2 }, + { "lsl3", GID_LSL3 }, + { "lsl5", GID_LSL5 }, + { "lsl6", GID_LSL6 }, + { "lsl6hires", GID_LSL6HIRES }, + { "lsl7", GID_LSL7 }, + { "mothergoose", GID_MOTHERGOOSE }, + { "msastrochicken", GID_MSASTROCHICKEN }, + { "pepper", GID_PEPPER }, + { "phantasmagoria", GID_PHANTASMAGORIA }, + { "phantasmagoria2", GID_PHANTASMAGORIA2 }, + { "pq1sci", GID_PQ1 }, + { "pq2", GID_PQ2 }, + { "pq3", GID_PQ3 }, + { "pq4", GID_PQ4 }, + { "pqswat", GID_PQSWAT }, + { "qfg1", GID_QFG1 }, + { "qfg2", GID_QFG2 }, + { "qfg3", GID_QFG3 }, + { "qfg4", GID_QFG4 }, + { "rama", GID_RAMA }, + { "sci-fanmade", GID_FANMADE }, // FIXME: Do we really need/want this? + { "shivers", GID_SHIVERS }, + { "shivers2", GID_SHIVERS2 }, + { "slater", GID_SLATER }, + { "sq1sci", GID_SQ1 }, + { "sq3", GID_SQ3 }, + { "sq4", GID_SQ4 }, + { "sq5", GID_SQ5 }, + { "sq6", GID_SQ6 }, + { "torin", GID_TORIN }, + { NULL, (SciGameId)-1 } +}; + struct OldNewIdTableEntry { const char *oldId; const char *newId; @@ -198,6 +273,12 @@ static const OldNewIdTableEntry s_oldNewTable[] = { { "", "", SCI_VERSION_NONE } }; +/** + * Converts the builtin Sierra game IDs to the ones we use in ScummVM + * @param[in] gameId The internal game ID + * @param[in] gameFlags The game's flags, which are adjusted accordingly for demos + * @return The equivalent ScummVM game id + */ Common::String convertSierraGameId(Common::String sierraId, uint32 *gameFlags, ResourceManager *resMan) { // Convert the id to lower case, so that we match all upper/lower case variants. sierraId.toLowercase(); @@ -295,7 +376,7 @@ static const ADParams detectionParams = { // Number of bytes to compute MD5 sum for 5000, // List of all engine targets - SciGameTitles, + s_sciGameTitles, // Structure for autoupgrading obsolete targets 0, // Name of single gameid (optional) @@ -305,7 +386,11 @@ static const ADParams detectionParams = { // Flags 0, // Additional GUI options (for every game} - Common::GUIO_NONE + Common::GUIO_NONE, + // Maximum directory depth + 1, + // List of directory globs + 0 }; class SciMetaEngine : public AdvancedMetaEngine { @@ -419,7 +504,12 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const Common::FSList &fsl return 0; } - ResourceManager *resMan = new ResourceManager(fslist); + ResourceManager *resMan = new ResourceManager(); + assert(resMan); + resMan->addAppropriateSources(fslist); + resMan->init(); + // TODO: Add error handling. + ViewType gameViews = resMan->getViewType(); // Have we identified the game views? If not, stop here @@ -509,12 +599,16 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const Common::FSList &fsl return (const ADGameDescription *)&s_fallbackDesc; } -bool SciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { - const ADGameDescription *desc = (const ADGameDescription *)gd; - - *engine = new SciEngine(syst, desc); +bool SciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const GameIdStrToEnum *g = s_gameIdStrToEnum; + for (; g->gameidStr; ++g) { + if (0 == strcmp(desc->gameid, g->gameidStr)) { + *engine = new SciEngine(syst, desc, g->gameidEnum); + return true; + } + } - return true; + return false; } bool SciMetaEngine::hasFeature(MetaEngineFeature f) const { @@ -652,7 +746,7 @@ Common::Error SciEngine::saveGameState(int slot, const char *desc) { return Common::kWritingFailed; } - if (gamestate_save(_gamestate, out, desc, version)) { + if (!gamestate_save(_gamestate, out, desc, version)) { warning("Saving the game state to '%s' failed", fileName.c_str()); return Common::kWritingFailed; } else { @@ -668,11 +762,11 @@ Common::Error SciEngine::saveGameState(int slot, const char *desc) { } bool SciEngine::canLoadGameStateCurrently() { - return !_gamestate->execution_stack_base; + return !_gamestate->executionStackBase; } bool SciEngine::canSaveGameStateCurrently() { - return !_gamestate->execution_stack_base; + return !_gamestate->executionStackBase; } } // End of namespace Sci diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index dba4d879aa..aa1e26e0f4 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -753,14 +753,31 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, // Hoyle 4 - English DOS Demo + {"hoyle4", "Demo", { + {"resource.map", 0, "60f764020a6b788bbbe415dbc2ccb9f3", 931}, + {"resource.000", 0, "5fe3670e3ddcd4f85c10013b5453141a", 615522}, + {NULL, 0, NULL, 0}}, + Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO_NOSPEECH }, + + // Hoyle 4 - English DOS Demo // SCI interpreter version 1.001.200 (just a guess) + // Does anyone have this version? -clone2727 {"hoyle4", "Demo", { {"resource.map", 0, "662087cb383e52e3cc4ae7ecb10e20aa", 938}, {"resource.000", 0, "24c10844792c54d476d272213cbac300", 675252}, {NULL, 0, NULL, 0}}, Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO_NOSPEECH }, - // Jones in the Fast Lane - English DOS + // Jones in the Fast Lane EGA - English DOS + // SCI interpreter version 1.000.172 (not 100% sure FIXME) + {"jones", "", { + {"resource.map", 0, "be4cf9e8c1e253623ef35ae3b8a1d998", 1800}, + {"resource.001", 0, "bac3ec6cb3e3920984ab0f32becf5163", 202105}, + {"resource.002", 0, "b86daa3ba2784d1502da881eedb80d9b", 341771}, + {NULL, 0, NULL, 0}}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + + // Jones in the Fast Lane VGA - English DOS // SCI interpreter version 1.000.172 {"jones", "", { {"resource.map", 0, "65cbe19b36fffc71c8e7b2686bd49ad7", 1800}, @@ -1667,7 +1684,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 // Larry 6 - English/German DOS CD - HIRES // SCI interpreter version 2.100.002 - {"lsl6", "", { + {"lsl6hires", "", { {"resource.map", 0, "0c0804434ea62278dd15032b1947426c", 8872}, {"resource.000", 0, "9a9f4870504444cda863dd14d077a680", 18520872}, {NULL, 0, NULL, 0}}, @@ -1675,7 +1692,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { // Larry 6 - German DOS CD - HIRES (provided by richiefs in bug report #2670691) // SCI interpreter version 2.100.002 - {"lsl6", "", { + {"lsl6hires", "", { {"resource.map", 0, "badfdf446ffed569a310d2c63a249421", 8896}, {"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18534274}, {NULL, 0, NULL, 0}}, @@ -1683,7 +1700,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { // Larry 6 - French DOS CD - HIRES (provided by richiefs in bug report #2670691) // SCI interpreter version 2.100.002 - {"lsl6", "", { + {"lsl6hires", "", { {"resource.map", 0, "d184e9aa4f2d4b5670ddb3669db82cda", 8896}, {"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18538987}, {NULL, 0, NULL, 0}}, @@ -2390,7 +2407,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { }, // Quest for Glory 4 1.1 Floppy - German DOS (supplied by markcool in bug report #2723850) - // SCI interpreter version 2.000.000 (a guess?) + // Executable scanning reports "2.000.000", VERSION file reports "1.1" {"qfg4", "", { {"resource.map", 0, "9e0abba8746f40565bc7eb5720522ecd", 9301}, {"resource.000", 0, "57f22cdc54eeb35fce1f26b31b5c3ee1", 11076197}, @@ -2407,27 +2424,6 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, -#if 0 - // NOTE: This version looks to be exactly the same as the English one - // Perhaps it's the English one? - - // Quest for Glory 4 - German DOS/Windows (from PCJoker 2/98) - {"qfg4", "", { - {"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246}, - {"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394}, - {NULL, 0, NULL, 0}}, - Common::DE_DEU, Common::kPlatformPC, 0, GUIO_NOSPEECH - }, -#endif - - // Quest for Glory 4 - German DOS/Windows Disk V1.1 (from PCJoker 2/89) - // SCI interpreter version 2.000.000 (a guess?) - {"qfg4", "", { - {"resource.map", 0, "9e0abba8746f40565bc7eb5720522ecd", 9301}, - {"resource.000", 0, "57f22cdc54eeb35fce1f26b31b5c3ee1", 11076197}, - {NULL, 0, NULL, 0}}, - Common::DE_DEU, Common::kPlatformPC, 0, GUIO_NOSPEECH - }, #endif // Slater & Charlie go camping @@ -2572,6 +2568,18 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + // Space Quest 1 VGA Remake - English Mac (from Fingolfin) + {"sq1sci", "VGA Remake", { + {"resource.map", 0, "5c6ad20407261b544238e8dce87afead", 5895}, + {"resource.000", 0, "2c414644b23839069c8d1a93b721df16", 1017033}, + {"resource.001", 0, "8744ae2ea6b316e91e2a35ab1aa301d2", 1024622}, + {"resource.002", 0, "96860704f7a07ecc10bef223b4b2f153", 1273992}, + {"resource.003", 0, "ae46e195e66df5a131917f0aa80b5669", 1242794}, + {"resource.004", 0, "91d58a9eb2187c38424990afe4c12bc6", 1250949}, + {NULL, 0, NULL, 0}}, + Common::EN_ANY, Common::kPlatformMacintosh, 0, GUIO_NOSPEECH + }, + // Space Quest 1 VGA Remake - English Non-Interactive Demo (from FRG) // SCI interpreter version 1.000.181 {"sq1sci", "VGA Remake, Demo", { @@ -2660,6 +2668,15 @@ static const struct ADGameDescription SciGameDescriptions[] = { {NULL, 0, NULL, 0}}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + // Space Quest 3 - English Mac (from Fingolfin) + {"sq3", "", { + {"resource.map", 0, "5c931675c6e01c4b418faca85d76c92c", 5844}, + {"resource.001", 0, "0d8dfe42683b46f3131823233a91ce6a", 771917}, + {"resource.002", 0, "0d8dfe42683b46f3131823233a91ce6a", 794072}, + {"resource.003", 0, "0d8dfe42683b46f3131823233a91ce6a", 776536}, + {NULL, 0, NULL, 0}}, + Common::EN_ANY, Common::kPlatformMacintosh, 0, GUIO_NOSPEECH }, + // Space Quest 3 - German DOS (from Tobis87) // SCI interpreter version 0.000.453 (?) {"sq3", "", { diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index 48f7c2d64f..fee6e69da7 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -24,6 +24,7 @@ */ #include "sci/engine/features.h" +#include "sci/engine/kernel.h" #include "sci/engine/script.h" #include "sci/engine/selector.h" #include "sci/engine/vm.h" @@ -51,13 +52,13 @@ reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc reg_t addr; if (objAddr.isNull()) { - warning("getDetectionAddr: %s object couldn't be found", objName.c_str()); + error("getDetectionAddr: %s object couldn't be found", objName.c_str()); return NULL_REG; } if (methodNum == -1) { if (lookupSelector(_segMan, objAddr, slc, NULL, &addr) != kSelectorMethod) { - warning("getDetectionAddr: target selector is not a method of object %s", objName.c_str()); + error("getDetectionAddr: target selector is not a method of object %s", objName.c_str()); return NULL_REG; } } else { @@ -69,7 +70,7 @@ reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc bool GameFeatures::autoDetectSoundType() { // Look up the script address - reg_t addr = getDetectionAddr("Sound", _kernel->_selectorCache.play); + reg_t addr = getDetectionAddr("Sound", SELECTOR(play)); if (!addr.segment) return false; @@ -83,17 +84,17 @@ bool GameFeatures::autoDetectSoundType() { int16 opparams[4]; byte extOpcode; byte opcode; - offset += readPMachineInstruction(script->_buf + offset, extOpcode, opparams); + offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams); opcode = extOpcode >> 1; // Check for end of script if (opcode == op_ret || offset >= script->getBufSize()) break; - // The play method of the Sound object pushes the DoSound command - // that it'll use just before it calls DoSound. We intercept that here - // in order to check what sound semantics are used, cause the position - // of the sound commands has changed at some point during SCI1 middle + // The play method of the Sound object pushes the DoSound command that + // it will use just before it calls DoSound. We intercept that here in + // order to check what sound semantics are used, cause the position of + // the sound commands has changed at some point during SCI1 middle. if (opcode == op_pushi) { // Load the pushi parameter intParam = opparams[0]; @@ -104,8 +105,8 @@ bool GameFeatures::autoDetectSoundType() { if (kFuncNum == 6) { // kIsObject (SCI0-SCI11) foundTarget = true; } else if (kFuncNum == 45) { // kDoSound (SCI1) - // First, check which DoSound function is called by the play method of - // the Sound object + // First, check which DoSound function is called by the play + // method of the Sound object switch (intParam) { case 1: _doSoundType = SCI_VERSION_0_EARLY; @@ -118,8 +119,8 @@ bool GameFeatures::autoDetectSoundType() { break; default: // Unknown case... should never happen. We fall back to - // alternative detection here, which works in general, apart from - // some transitive games like Jones CD + // alternative detection here, which works in general, apart + // from some transitive games like Jones CD _doSoundType = foundTarget ? SCI_VERSION_1_LATE : SCI_VERSION_1_EARLY; break; } @@ -136,9 +137,10 @@ bool GameFeatures::autoDetectSoundType() { SciVersion GameFeatures::detectDoSoundType() { if (_doSoundType == SCI_VERSION_NONE) { if (getSciVersion() == SCI_VERSION_0_EARLY) { - // This game is using early SCI0 sound code (different headers than SCI0 late) + // This game is using early SCI0 sound code (different headers than + // SCI0 late) _doSoundType = SCI_VERSION_0_EARLY; - } else if (_kernel->_selectorCache.nodePtr == -1) { + } else if (SELECTOR(nodePtr) == -1) { // No nodePtr selector, so this game is definitely using newer // SCI0 sound code (i.e. SCI_VERSION_0_LATE) _doSoundType = SCI_VERSION_0_LATE; @@ -171,14 +173,16 @@ SciVersion GameFeatures::detectSetCursorType() { // SCI1.1 games always use cursor views _setCursorType = SCI_VERSION_1_1; } else { // SCI1 late game, detect cursor semantics - // If the Cursor object doesn't exist, we're using the SCI0 early kSetCursor semantics. + // If the Cursor object doesn't exist, we're using the SCI0 early + // kSetCursor semantics. if (_segMan->findObjectByName("Cursor") == NULL_REG) { _setCursorType = SCI_VERSION_0_EARLY; debugC(1, kDebugLevelGraphics, "Detected SetCursor type: %s", getSciVersionDesc(_setCursorType)); return _setCursorType; } - // Check for the existence of the handCursor object (first found). This is based on KQ5. + // Check for the existence of the handCursor object (first found). + // This is based on KQ5. reg_t objAddr = _segMan->findObjectByName("handCursor", 0); // If that doesn't exist, we assume it uses SCI1.1 kSetCursor semantics @@ -188,11 +192,13 @@ SciVersion GameFeatures::detectSetCursorType() { return _setCursorType; } - // Now we check what the number variable holds in the handCursor object. + // Now we check what the number variable holds in the handCursor + // object. uint16 number = readSelectorValue(_segMan, objAddr, SELECTOR(number)); - // If the number is 0, it uses views and therefore the SCI1.1 kSetCursor semantics, - // otherwise it uses the SCI0 early kSetCursor semantics. + // If the number is 0, it uses views and therefore the SCI1.1 + // kSetCursor semantics, otherwise it uses the SCI0 early kSetCursor + // semantics. if (number == 0) _setCursorType = SCI_VERSION_1_1; else @@ -219,7 +225,7 @@ bool GameFeatures::autoDetectLofsType(int methodNum) { int16 opparams[4]; byte extOpcode; byte opcode; - offset += readPMachineInstruction(script->_buf + offset, extOpcode, opparams); + offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams); opcode = extOpcode >> 1; // Check for end of script @@ -293,7 +299,7 @@ SciVersion GameFeatures::detectLofsType() { bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) { // Look up the script address - reg_t addr = getDetectionAddr("Rm", _kernel->_selectorCache.overlay, methodNum); + reg_t addr = getDetectionAddr("Rm", SELECTOR(overlay), methodNum); if (!addr.segment) return false; @@ -305,7 +311,7 @@ bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) { int16 opparams[4]; byte extOpcode; byte opcode; - offset += readPMachineInstruction(script->_buf + offset, extOpcode, opparams); + offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams); opcode = extOpcode >> 1; // Check for end of script @@ -317,10 +323,10 @@ bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) { uint16 argc = opparams[1]; if (kFuncNum == 8) { // kDrawPic (SCI0 - SCI11) - // If kDrawPic is called with 6 parameters from the - // overlay selector, the game is using old graphics functions. + // If kDrawPic is called with 6 parameters from the overlay + // selector, the game is using old graphics functions. // Otherwise, if it's called with 8 parameters, it's using new - // graphics functions + // graphics functions. _gfxFunctionsType = (argc == 8) ? SCI_VERSION_0_LATE : SCI_VERSION_0_EARLY; return true; } @@ -343,33 +349,36 @@ SciVersion GameFeatures::detectGfxFunctionsType() { bool searchRoomObj = false; reg_t rmObjAddr = _segMan->findObjectByName("Rm"); - if (_kernel->_selectorCache.overlay != -1) { - // The game has an overlay selector, check how it calls kDrawPicto determine - // the graphics functions type used - if (lookupSelector(_segMan, rmObjAddr, _kernel->_selectorCache.overlay, NULL, NULL) == kSelectorMethod) { + if (SELECTOR(overlay) != -1) { + // The game has an overlay selector, check how it calls kDrawPic + // to determine the graphics functions type used + if (lookupSelector(_segMan, rmObjAddr, SELECTOR(overlay), NULL, NULL) == kSelectorMethod) { if (!autoDetectGfxFunctionsType()) { warning("Graphics functions detection failed, taking an educated guess"); - // Try detecting the graphics function types from the existence of the motionCue - // selector (which is a bit of a hack) + // Try detecting the graphics function types from the + // existence of the motionCue selector (which is a bit + // of a hack) if (_kernel->findSelector("motionCue") != -1) _gfxFunctionsType = SCI_VERSION_0_LATE; else _gfxFunctionsType = SCI_VERSION_0_EARLY; } } else { - // The game has an overlay selector, but it's not a method of the Rm object - // (like in Hoyle 1 and 2), so search for other methods + // The game has an overlay selector, but it's not a method + // of the Rm object (like in Hoyle 1 and 2), so search for + // other methods searchRoomObj = true; } } else { - // The game doesn't have an overlay selector, so search for it manually + // The game doesn't have an overlay selector, so search for it + // manually searchRoomObj = true; } if (searchRoomObj) { - // If requested, check if any method of the Rm object is calling kDrawPic, - // as the overlay selector might be missing in demos + // If requested, check if any method of the Rm object is calling + // kDrawPic, as the overlay selector might be missing in demos bool found = false; const Object *obj = _segMan->getObject(rmObjAddr); @@ -380,8 +389,9 @@ SciVersion GameFeatures::detectGfxFunctionsType() { } if (!found) { - // No method of the Rm object is calling kDrawPic, thus the game - // doesn't have overlays and is using older graphics functions + // No method of the Rm object is calling kDrawPic, thus the + // game doesn't have overlays and is using older graphics + // functions _gfxFunctionsType = SCI_VERSION_0_EARLY; } } @@ -395,8 +405,20 @@ SciVersion GameFeatures::detectGfxFunctionsType() { #ifdef ENABLE_SCI32 bool GameFeatures::autoDetectSci21KernelType() { + // First, check if the Sound object is loaded + reg_t soundObjAddr = _segMan->findObjectByName("Sound"); + if (soundObjAddr.isNull()) { + // Usually, this means that the Sound object isn't loaded yet. + // This case doesn't occur in early SCI2.1 games, and we've only + // seen it happen in the RAMA demo, thus we can assume that the + // game is using a SCI2.1 table + warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table"); + _sci21KernelType = SCI_VERSION_2_1; + return true; + } + // Look up the script address - reg_t addr = getDetectionAddr("Sound", _kernel->_selectorCache.play); + reg_t addr = getDetectionAddr("Sound", SELECTOR(play)); if (!addr.segment) return false; @@ -408,7 +430,7 @@ bool GameFeatures::autoDetectSci21KernelType() { int16 opparams[4]; byte extOpcode; byte opcode; - offset += readPMachineInstruction(script->_buf + offset, extOpcode, opparams); + offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams); opcode = extOpcode >> 1; // Check for end of script @@ -419,9 +441,11 @@ bool GameFeatures::autoDetectSci21KernelType() { uint16 kFuncNum = opparams[0]; // Here we check for the kDoSound opcode that's used in SCI2.1. - // Finding 0x40 as kDoSound in the Sound::play() function means the game is using - // the modified SCI2 kernel table found in some older SCI2.1 games (GK2 demo, KQ7 v1.4). - // Finding 0x75 as kDoSound means the game is using the regular SCI2.1 kernel table. + // Finding 0x40 as kDoSound in the Sound::play() function means the + // game is using the modified SCI2 kernel table found in some older + // SCI2.1 games (GK2 demo, KQ7 v1.4). + // Finding 0x75 as kDoSound means the game is using the regular + // SCI2.1 kernel table. if (kFuncNum == 0x40) { _sci21KernelType = SCI_VERSION_2; return true; @@ -448,7 +472,7 @@ SciVersion GameFeatures::detectSci21KernelType() { bool GameFeatures::autoDetectMoveCountType() { // Look up the script address - reg_t addr = getDetectionAddr("Motion", _kernel->_selectorCache.doit); + reg_t addr = getDetectionAddr("Motion", SELECTOR(doit)); if (!addr.segment) return false; @@ -461,7 +485,7 @@ bool GameFeatures::autoDetectMoveCountType() { int16 opparams[4]; byte extOpcode; byte opcode; - offset += readPMachineInstruction(script->_buf + offset, extOpcode, opparams); + offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams); opcode = extOpcode >> 1; // Check for end of script @@ -491,7 +515,7 @@ MoveCountType GameFeatures::detectMoveCountType() { _moveCountType = kIncrementMoveCount; } else { if (!autoDetectMoveCountType()) { - warning("Move count autodetection failed"); + error("Move count autodetection failed"); _moveCountType = kIncrementMoveCount; // Most games do this, so best guess } } diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 77c2f0cff7..5b383746d8 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -31,6 +31,12 @@ namespace Sci { +enum MoveCountType { + kMoveCountUninitialized, + kIgnoreMoveCount, + kIncrementMoveCount +}; + class GameFeatures { public: GameFeatures(SegManager *segMan, Kernel *kernel); diff --git a/engines/sci/engine/game.cpp b/engines/sci/engine/game.cpp deleted file mode 100644 index bc10099e52..0000000000 --- a/engines/sci/engine/game.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#include "common/system.h" -#include "common/file.h" - -#include "engines/advancedDetector.h" // for ADGF_DEMO - -#include "sci/sci.h" -#include "sci/resource.h" -#include "sci/engine/features.h" -#include "sci/engine/state.h" -#include "sci/engine/kernel.h" -#include "sci/engine/message.h" -#include "sci/graphics/gui.h" -#include "sci/graphics/menu.h" -#include "sci/sound/audio.h" -#include "sci/sound/music.h" - -namespace Sci { - -#ifdef USE_OLD_MUSIC_FUNCTIONS -int game_init_sound(EngineState *s, int sound_flags, SciVersion soundVersion) { - if (getSciVersion() > SCI_VERSION_0_LATE) - sound_flags |= SFX_STATE_FLAG_MULTIPLAY; - - s->sfx_init_flags = sound_flags; - s->_sound.sfx_init(g_sci->getResMan(), sound_flags, soundVersion); - - return 0; -} -#endif - -// Architectural stuff: Init/Unintialize engine -int script_init_engine(EngineState *s) { - s->_msgState = new MessageState(s->_segMan); - s->gc_countdown = GC_INTERVAL - 1; - - SegmentId script_000_segment = s->_segMan->getScriptSegment(0, SCRIPT_GET_LOCK); - - if (script_000_segment <= 0) { - debug(2, "Failed to instantiate script.000"); - return 1; - } - - s->script_000 = s->_segMan->getScript(script_000_segment); - - s->_segMan->initSysStrings(); - - s->r_acc = s->r_prev = NULL_REG; - s->restAdjust = 0; - - s->_executionStack.clear(); // Start without any execution stack - s->execution_stack_base = -1; // No vm is running yet - - s->restarting_flags = SCI_GAME_IS_NOT_RESTARTING; - - debug(2, "Engine initialized"); - - return 0; -} - -/*************************************************************/ -/* Game instance stuff: Init/Unitialize state-dependant data */ -/*************************************************************/ - -int game_init(EngineState *s) { - // FIXME Use new VM instantiation code all over the place - DataStack *stack; - - stack = s->_segMan->allocateStack(VM_STACK_SIZE, NULL); - s->stack_base = stack->_entries; - s->stack_top = stack->_entries + stack->_capacity; - - if (!script_instantiate(g_sci->getResMan(), s->_segMan, 0)) { - warning("game_init(): Could not instantiate script 0"); - return 1; - } - - // Reset parser - Vocabulary *voc = g_sci->getVocabulary(); - if (voc) { - voc->parserIsValid = false; // Invalidate parser - voc->parser_event = NULL_REG; // Invalidate parser event - voc->parser_base = make_reg(s->_segMan->getSysStringsSegment(), SYS_STRING_PARSER_BASE); - } - - // Initialize menu TODO: Actually this should be another init() - if (g_sci->_gfxMenu) - g_sci->_gfxMenu->reset(); - - s->restoring = false; - - s->game_start_time = g_system->getMillis(); - s->last_wait_time = s->game_start_time; - - srand(g_system->getMillis()); // Initialize random number generator - - s->_gameObj = g_sci->getResMan()->findGameObject(); - -#ifdef USE_OLD_MUSIC_FUNCTIONS - if (s->sfx_init_flags & SFX_STATE_FLAG_NOSOUND) - game_init_sound(s, 0, g_sci->_features->detectDoSoundType()); -#endif - - // Load game language into printLang property of game object - // FIXME: It's evil to achieve this as a side effect of a getter. - // Much better to have an explicit init method for this. - g_sci->getSciLanguage(); - - return 0; -} - -int game_exit(EngineState *s) { - if (!s->restoring) { - s->_executionStack.clear(); -#ifdef USE_OLD_MUSIC_FUNCTIONS - s->_sound.sfx_exit(); - // Reinit because some other code depends on having a valid state - game_init_sound(s, SFX_STATE_FLAG_NOSOUND, g_sci->_features->detectDoSoundType()); -#else - g_sci->_audio->stopAllAudio(); - s->_soundCmd->clearPlayList(); -#endif - } - - // Note: It's a bad idea to delete the segment manager here - // when loading a game. - // This function is called right after a game is loaded, and - // the segment manager has already been initialized from the - // save game. Deleting or resetting it here will result in - // invalidating the loaded save state - if (s->restarting_flags & SCI_GAME_IS_RESTARTING_NOW) - s->_segMan->resetSegMan(); - - // TODO Free parser segment here - - // TODO Free scripts here - - // Close all opened file handles - s->_fileHandles.clear(); - s->_fileHandles.resize(5); - - return 0; -} - -} // End of namespace Sci diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp index c2f1c15776..936b83d760 100644 --- a/engines/sci/engine/gc.cpp +++ b/engines/sci/engine/gc.cpp @@ -28,11 +28,9 @@ namespace Sci { -//#define DEBUG_GC - struct WorklistManager { Common::Array<reg_t> _worklist; - reg_t_hash_map _map; + AddrSet _map; void push(reg_t reg) { if (!reg.segment) // No numbers @@ -46,14 +44,19 @@ struct WorklistManager { _map.setVal(reg, true); _worklist.push_back(reg); } + + void pushArray(const Common::Array<reg_t> &tmp) { + for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) + push(*it); + } }; -static reg_t_hash_map *normalise_hashmap_ptrs(SegManager *segMan, reg_t_hash_map &nonnormal_map) { - reg_t_hash_map *normal_map = new reg_t_hash_map(); +static AddrSet *normalizeAddresses(SegManager *segMan, const AddrSet &nonnormal_map) { + AddrSet *normal_map = new AddrSet(); - for (reg_t_hash_map::iterator i = nonnormal_map.begin(); i != nonnormal_map.end(); ++i) { + for (AddrSet::const_iterator i = nonnormal_map.begin(); i != nonnormal_map.end(); ++i) { reg_t reg = i->_key; - SegmentObj *mobj = (reg.segment < segMan->_heap.size()) ? segMan->_heap[reg.segment] : NULL; + SegmentObj *mobj = segMan->getSegmentObj(reg.segment); if (mobj) { reg = mobj->findCanonicAddress(segMan, reg); @@ -65,14 +68,9 @@ static reg_t_hash_map *normalise_hashmap_ptrs(SegManager *segMan, reg_t_hash_map } -void add_outgoing_refs(void *refcon, reg_t addr) { - WorklistManager *wm = (WorklistManager *)refcon; - wm->push(addr); -} - -reg_t_hash_map *find_all_used_references(EngineState *s) { +AddrSet *findAllActiveReferences(EngineState *s) { SegManager *segMan = s->_segMan; - reg_t_hash_map *normal_map = NULL; + AddrSet *normal_map = NULL; WorklistManager wm; uint i; @@ -84,22 +82,19 @@ reg_t_hash_map *find_all_used_references(EngineState *s) { wm.push(s->r_prev); // Init: Value Stack // We do this one by hand since the stack doesn't know the current execution stack - Common::List<ExecStack>::iterator iter; - { - iter = s->_executionStack.reverse_begin(); + Common::List<ExecStack>::iterator iter = s->_executionStack.reverse_begin(); - // Skip fake kernel stack frame if it's on top - if (((*iter).type == EXEC_STACK_TYPE_KERNEL)) - --iter; + // Skip fake kernel stack frame if it's on top + if (((*iter).type == EXEC_STACK_TYPE_KERNEL)) + --iter; - assert((iter != s->_executionStack.end()) && ((*iter).type != EXEC_STACK_TYPE_KERNEL)); + assert((iter != s->_executionStack.end()) && ((*iter).type != EXEC_STACK_TYPE_KERNEL)); - ExecStack &xs = *iter; - reg_t *pos; + ExecStack &xs = *iter; + reg_t *pos; - for (pos = s->stack_base; pos < xs.sp; pos++) - wm.push(*pos); - } + for (pos = s->stack_base; pos < xs.sp; pos++) + wm.push(*pos); debugC(2, kDebugLevelGC, "[GC] -- Finished adding value stack"); @@ -118,24 +113,18 @@ reg_t_hash_map *find_all_used_references(EngineState *s) { debugC(2, kDebugLevelGC, "[GC] -- Finished adding execution stack"); + const Common::Array<SegmentObj *> &heap = segMan->getSegments(); + // Init: Explicitly loaded scripts - for (i = 1; i < segMan->_heap.size(); i++) - if (segMan->_heap[i] - && segMan->_heap[i]->getType() == SEG_TYPE_SCRIPT) { - Script *script = (Script *)segMan->_heap[i]; + for (i = 1; i < heap.size(); i++) { + if (heap[i] && heap[i]->getType() == SEG_TYPE_SCRIPT) { + Script *script = (Script *)heap[i]; if (script->getLockers()) { // Explicitly loaded? - // Locals, if present - wm.push(make_reg(script->_localsSegment, 0)); - - // All objects (may be classes, may be indirectly reachable) - ObjMap::iterator it; - const ObjMap::iterator end = script->_objects.end(); - for (it = script->_objects.begin(); it != end; ++it) { - wm.push(it->_value.getPos()); - } + wm.pushArray(script->listObjectReferences()); } } + } debugC(2, kDebugLevelGC, "[GC] -- Finished explicitly loaded scripts, done with root set"); @@ -146,76 +135,64 @@ reg_t_hash_map *find_all_used_references(EngineState *s) { wm._worklist.pop_back(); if (reg.segment != stack_seg) { // No need to repeat this one debugC(2, kDebugLevelGC, "[GC] Checking %04x:%04x", PRINT_REG(reg)); - if (reg.segment < segMan->_heap.size() && segMan->_heap[reg.segment]) - segMan->_heap[reg.segment]->listAllOutgoingReferences(reg, &wm, add_outgoing_refs); + if (reg.segment < heap.size() && heap[reg.segment]) { + // Valid heap object? Find its outgoing references! + wm.pushArray(heap[reg.segment]->listAllOutgoingReferences(reg)); + } } } // Normalise - normal_map = normalise_hashmap_ptrs(segMan, wm._map); + normal_map = normalizeAddresses(segMan, wm._map); return normal_map; } -struct deallocator_t { - SegManager *segMan; - SegmentObj *mobj; -#ifdef DEBUG_GC - char *segnames[SEG_TYPE_MAX + 1]; - int segcount[SEG_TYPE_MAX + 1]; -#endif - reg_t_hash_map *use_map; -}; - -void free_unless_used(void *refcon, reg_t addr) { - deallocator_t *deallocator = (deallocator_t *)refcon; - reg_t_hash_map *use_map = deallocator->use_map; - - if (!use_map->contains(addr)) { - // Not found -> we can free it - deallocator->mobj->freeAtAddress(deallocator->segMan, addr); -#ifdef DEBUG_GC - debugC(2, kDebugLevelGC, "[GC] Deallocating %04x:%04x", PRINT_REG(addr)); - deallocator->segcount[deallocator->mobj->getType()]++; -#endif - } - -} - void run_gc(EngineState *s) { - uint seg_nr; - deallocator_t deallocator; SegManager *segMan = s->_segMan; -#ifdef DEBUG_GC + // Some debug stuff debugC(2, kDebugLevelGC, "[GC] Running..."); - memset(&(deallocator.segcount), 0, sizeof(int) * (SEG_TYPE_MAX + 1)); -#endif - - deallocator.segMan = segMan; - deallocator.use_map = find_all_used_references(s); - - for (seg_nr = 1; seg_nr < segMan->_heap.size(); seg_nr++) { - if (segMan->_heap[seg_nr] != NULL) { - deallocator.mobj = segMan->_heap[seg_nr]; -#ifdef DEBUG_GC - deallocator.segnames[deallocator.mobj->getType()] = deallocator.mobj->type; // FIXME: add a segment "name" -#endif - deallocator.mobj->listAllDeallocatable(seg_nr, &deallocator, free_unless_used); + const char *segnames[SEG_TYPE_MAX + 1]; + int segcount[SEG_TYPE_MAX + 1]; + memset(segnames, 0, sizeof(segnames)); + memset(segcount, 0, sizeof(segcount)); + + // Compute the set of all segments references currently in use. + AddrSet *activeRefs = findAllActiveReferences(s); + + // Iterate over all segments, and check for each whether it + // contains stuff that can be collected. + const Common::Array<SegmentObj *> &heap = segMan->getSegments(); + for (uint seg = 1; seg < heap.size(); seg++) { + SegmentObj *mobj = heap[seg]; + if (mobj != NULL) { + const SegmentType type = mobj->getType(); + segnames[type] = SegmentObj::getSegmentTypeName(type); + + // Get a list of all deallocatable objects in this segment, + // then free any which are not referenced from somewhere. + const Common::Array<reg_t> tmp = mobj->listAllDeallocatable(seg); + for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) { + const reg_t addr = *it; + if (!activeRefs->contains(addr)) { + // Not found -> we can free it + mobj->freeAtAddress(segMan, addr); + debugC(2, kDebugLevelGC, "[GC] Deallocating %04x:%04x", PRINT_REG(addr)); + segcount[type]++; + } + } + } } - delete deallocator.use_map; + delete activeRefs; -#ifdef DEBUG_GC - { - int i; - debugC(2, kDebugLevelGC, "[GC] Summary:"); - for (i = 0; i <= SEG_TYPE_MAX; i++) - if (deallocator.segcount[i]) - debugC(2, kDebugLevelGC, "\t%d\t* %s", deallocator.segcount[i], deallocator.segnames[i]); - } -#endif + // Output debug summary of garbage collection + debugC(2, kDebugLevelGC, "[GC] Summary:"); + for (int i = 0; i <= SEG_TYPE_MAX; i++) + if (segcount[i]) + debugC(2, kDebugLevelGC, "\t%d\t* %s", segcount[i], segnames[i]); } } // End of namespace Sci diff --git a/engines/sci/engine/gc.h b/engines/sci/engine/gc.h index 9f9347ca18..f4318a1453 100644 --- a/engines/sci/engine/gc.h +++ b/engines/sci/engine/gc.h @@ -33,29 +33,24 @@ namespace Sci { -struct reg_t_EqualTo { - bool operator()(const reg_t& x, const reg_t& y) const { - return (x.segment == y.segment) && (x.offset == y.offset); - } -}; - struct reg_t_Hash { uint operator()(const reg_t& x) const { - return (x.segment << 3) | x.offset; + return (x.segment << 3) ^ x.offset ^ (x.offset << 16); } }; /* - * The reg_t_hash_map is actually really a hashset + * The AddrSet is a "set" of reg_t values. + * We don't have a HashSet type, so we abuse a HashMap for this. */ -typedef Common::HashMap<reg_t, bool, reg_t_Hash, reg_t_EqualTo> reg_t_hash_map; +typedef Common::HashMap<reg_t, bool, reg_t_Hash> AddrSet; /** * Finds all used references and normalises them to their memory addresses * @param s The state to gather all information from * @return A hash map containing entries for all used references */ -reg_t_hash_map *find_all_used_references(EngineState *s); +AddrSet *findAllActiveReferences(EngineState *s); /** * Runs garbage collection on the current system state diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index 6ebee2dfbd..beb1d3ce35 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -33,10 +33,28 @@ namespace Sci { -// Default kernel name table -#define SCI_KNAMES_DEFAULT_ENTRIES_NR 0x89 - -static const char *sci_default_knames[SCI_KNAMES_DEFAULT_ENTRIES_NR] = { +// Uncompiled kernel signatures are formed from a string of letters. +// each corresponding to a type of a parameter (see below). +// Use small letters to indicate end of sum type. +// Use capital letters for sum types, e.g. +// "LNoLr" for a function which takes two arguments: +// (1) list, node or object +// (2) list or ref +#define KSIG_SPEC_LIST 'l' +#define KSIG_SPEC_NODE 'n' +#define KSIG_SPEC_OBJECT 'o' +#define KSIG_SPEC_REF 'r' // Said Specs and strings +#define KSIG_SPEC_ARITHMETIC 'i' +#define KSIG_SPEC_NULL 'z' +#define KSIG_SPEC_ANY '.' +#define KSIG_SPEC_ELLIPSIS '*' // Arbitrarily more TYPED arguments + +#define KSIG_SPEC_SUM_DONE ('a' - 'A') + + + +/** Default kernel name table. */ +static const char *s_defaultKernelNames[] = { /*0x00*/ "Load", /*0x01*/ "UnLoad", /*0x02*/ "ScriptID", @@ -152,18 +170,18 @@ static const char *sci_default_knames[SCI_KNAMES_DEFAULT_ENTRIES_NR] = { /*0x6c*/ "Graph", /*0x6d*/ "Joystick", // End of kernel function table for SCI0 - /*0x6e*/ "ShiftScreen", + /*0x6e*/ "Dummy", // ShiftScreen /*0x6f*/ "Palette", /*0x70*/ "MemorySegment", /*0x71*/ "Intersections", // MoveCursor (SCI1 late), PalVary (SCI1.1) /*0x72*/ "Memory", - /*0x73*/ "ListOps", + /*0x73*/ "Dummy", // ListOps /*0x74*/ "FileIO", /*0x75*/ "DoAudio", /*0x76*/ "DoSync", /*0x77*/ "AvoidPath", /*0x78*/ "Sort", // StrSplit (SCI01) - /*0x79*/ "ATan", + /*0x79*/ "Dummy", // ATan /*0x7a*/ "Lock", /*0x7b*/ "StrSplit", /*0x7c*/ "GetMessage", // Message (SCI1.1) @@ -181,227 +199,456 @@ static const char *sci_default_knames[SCI_KNAMES_DEFAULT_ENTRIES_NR] = { /*0x88*/ "Dummy" // DbugStr }; -struct SciKernelFunction { +reg_t kStub(EngineState *s, int argc, reg_t *argv) { + Kernel *kernel = g_sci->getKernel(); + int kernelCallNr = -1; + + Common::List<ExecStack>::iterator callIterator = s->_executionStack.end(); + if (callIterator != s->_executionStack.begin()) { + callIterator--; + ExecStack lastCall = *callIterator; + kernelCallNr = lastCall.debugSelector; + } + + Common::String warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) + + Common::String::printf("[%x]", kernelCallNr) + + " invoked. Params: " + + Common::String::printf("%d", argc) + " ("; + + for (int i = 0; i < argc; i++) { + warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); + warningMsg += (i == argc - 1 ? ")" : ", "); + } + + warning("%s", warningMsg.c_str()); + return s->r_acc; +} + +reg_t kStubNull(EngineState *s, int argc, reg_t *argv) { + kStub(s, argc, argv); + return NULL_REG; +} + +reg_t kDummy(EngineState *s, int argc, reg_t *argv) { + kStub(s, argc, argv); + error("Kernel function was called, which was considered to be unused - see log for details"); +} + +// [io] -> either integer or object +// (io) -> optionally integer AND an object +// (i) -> optional integer +// . -> any type +// i* -> optional multiple integers +// .* -> any parameters afterwards (or none) + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kAbs_workarounds[] = { + { GID_HOYLE1, 1, 0, "room1", "doit", -1, 0, { 2, 0x3e9 } }, // crazy eights - called with objects instead of integers + { GID_HOYLE1, 2, 0, "room2", "doit", -1, 0, { 2, 0x3e9 } }, // old maid - called with objects instead of integers + { GID_HOYLE1, 3, 0, "room3", "doit", -1, 0, { 2, 0x3e9 } }, // hearts - called with objects instead of integers + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kDisposeScript_workarounds[] = { + { GID_QFG1, 64, 0, "rm64", "dispose", -1, 0, { 1, 0 } }, // when leaving graveyard, parameter 0 is an object + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kDoSoundFade_workarounds[] = { + { GID_KQ6, 989, 0, "globalSound", "fade", -1, 0, { 0, 0 } }, // during intro, parameter 4 is an object + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = { + { GID_LSL6, 85, 0, "rScroller", "hide", -1, 0, { 0, 0 } }, // happens when restoring (sometimes), same as the one below + { GID_LSL6, 85, 0, "lScroller", "hide", -1, 0, { 0, 0 } }, // happens when restoring (sometimes), same as the one below + { GID_LSL6, 86, 0, "LL6Inv", "show", -1, 0, { 0, 0 } }, // happens when restoring, is called with hunk segment, but hunk is not allocated at that time + // ^^ TODO: check, if this is really a script error or an issue with our restore code + { GID_LSL6, 86, 0, "LL6Inv", "hide", -1, 0, { 0, 0 } }, // happens during the game, gets called with 1 extra parameter + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[] = { + { GID_LSL6, 0, 0, "LSL6", "hideControls", -1, 0, { 0, 0 } }, // happens when giving the bungee key to merrily - gets called with additional 5th parameter + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kGraphFillBoxAny_workarounds[] = { + { GID_SQ4, 818, 0, "iconTextSwitch", "show", -1, 0, { 0, 0 } }, // game menu "text/speech" display - parameter 5 is missing, but the right color number is on the stack + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry kUnLoad_workarounds[] = { + { GID_LSL6, 130, 0, "recruitLarryScr", "changeState", -1, 0, { 1, 0 } }, // during intro, a 3rd parameter is passed by accident + { GID_LSL6, 740, 0, "showCartoon", "changeState", -1, 0, { 1, 0 } }, // during ending, 4 additional parameters are passed by accident + { GID_SQ1, 303, 0, "slotGuy", "dispose", -1, 0, { 1, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +struct SciKernelMapSubEntry { + SciVersion fromVersion; + SciVersion toVersion; + + uint16 id; + const char *name; - KernelFunc *fun; /* The actual function */ - const char *signature; /* kfunct signature */ + KernelFunctionCall *function; + + const char *signature; + const SciWorkaroundEntry *workarounds; +}; + +#define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL } + + +#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE +#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 +#define SIG_SCI1 SCI_VERSION_1_EGA, SCI_VERSION_1_LATE +#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 +#define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1 +#define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE + +// SCI-Sound-Version +#define SIG_SOUNDSCI0 SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE +#define SIG_SOUNDSCI1EARLY SCI_VERSION_1_EARLY, SCI_VERSION_1_EARLY +#define SIG_SOUNDSCI1LATE SCI_VERSION_1_LATE, SCI_VERSION_1_LATE + +#define SIGFOR_ALL 0x3f +#define SIGFOR_DOS 1 << 0 +#define SIGFOR_PC98 1 << 1 +#define SIGFOR_WIN 1 << 2 +#define SIGFOR_MAC 1 << 3 +#define SIGFOR_AMIGA 1 << 4 +#define SIGFOR_ATARI 1 << 5 +#define SIGFOR_PC SIGFOR_DOS|SIGFOR_WIN + +#define SIG_EVERYWHERE SIG_SCIALL, SIGFOR_ALL + +#define MAP_CALL(_name_) #_name_, k##_name_ + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kDoSound_subops[] = { + { SIG_SOUNDSCI0, 0, MAP_CALL(DoSoundInit), "o", NULL }, + { SIG_SOUNDSCI0, 1, MAP_CALL(DoSoundPlay), "o", NULL }, + { SIG_SOUNDSCI0, 2, MAP_CALL(DoSoundDummy), "o", NULL }, + { SIG_SOUNDSCI0, 3, MAP_CALL(DoSoundDispose), "o", NULL }, + { SIG_SOUNDSCI0, 4, MAP_CALL(DoSoundMute), "(i)", NULL }, + { SIG_SOUNDSCI0, 5, MAP_CALL(DoSoundStop), "o", NULL }, + { SIG_SOUNDSCI0, 6, MAP_CALL(DoSoundPause), "i", NULL }, + { SIG_SOUNDSCI0, 7, MAP_CALL(DoSoundResume), "", NULL }, + { SIG_SOUNDSCI0, 8, MAP_CALL(DoSoundMasterVolume), "(i)", NULL }, + { SIG_SOUNDSCI0, 9, MAP_CALL(DoSoundUpdate), "o", NULL }, + { SIG_SOUNDSCI0, 10, MAP_CALL(DoSoundFade), "o", NULL }, + { SIG_SOUNDSCI0, 11, MAP_CALL(DoSoundGetPolyphony), "", NULL }, + { SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL }, + { SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 1, MAP_CALL(DoSoundMute), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 2, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 4, MAP_CALL(DoSoundUpdate), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 6, MAP_CALL(DoSoundDispose), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 7, MAP_CALL(DoSoundPlay), "oi", NULL }, + { SIG_SOUNDSCI1EARLY, 8, MAP_CALL(DoSoundStop), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 9, MAP_CALL(DoSoundPause), "[o0]i", NULL }, + { SIG_SOUNDSCI1EARLY, 10, MAP_CALL(DoSoundFade), "oiiii", NULL }, + { SIG_SOUNDSCI1EARLY, 11, MAP_CALL(DoSoundUpdateCues), "o", NULL }, + { SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi), "oiiii", NULL }, + { SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundReverb), "oi", NULL }, + { SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold), "oi", NULL }, + { SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy), "", NULL }, + // ^^ Longbow demo + { SIG_SOUNDSCI1LATE, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 1, MAP_CALL(DoSoundMute), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 2, MAP_CALL(DoSoundDummy), "", NULL }, + { SIG_SOUNDSCI1LATE, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL }, + { SIG_SOUNDSCI1LATE, 5, MAP_CALL(DoSoundSuspend), "i", NULL }, + { SIG_SOUNDSCI1LATE, 6, MAP_CALL(DoSoundInit), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 7, MAP_CALL(DoSoundDispose), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 8, MAP_CALL(DoSoundPlay), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 9, MAP_CALL(DoSoundStop), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 10, MAP_CALL(DoSoundPause), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 11, MAP_CALL(DoSoundFade), "oiiii(i)", kDoSoundFade_workarounds }, + { SIG_SOUNDSCI1LATE, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL }, + { SIG_SOUNDSCI1LATE, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL }, + { SIG_SOUNDSCI1LATE, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL }, + { SIG_SOUNDSCI1LATE, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 18, MAP_CALL(DoSoundSendMidi), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 19, MAP_CALL(DoSoundReverb), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 20, MAP_CALL(DoSoundUpdate), NULL, NULL }, + SCI_SUBOPENTRY_TERMINATOR }; -#define DEFUN(name, fun, sig) {name, fun, sig} - -SciKernelFunction kfunct_mappers[] = { - /*00*/ DEFUN("Load", kLoad, "iii*"), - /*01*/ DEFUN("UnLoad", kUnLoad, "i.*"), // Work around SQ1 bug, when exiting the Ulence flats bar - /*02*/ DEFUN("ScriptID", kScriptID, "Ioi*"), - /*03*/ DEFUN("DisposeScript", kDisposeScript, "Oii*"), // Work around QfG1 bug - /*04*/ DEFUN("Clone", kClone, "o"), - /*05*/ DEFUN("DisposeClone", kDisposeClone, "o"), - /*06*/ DEFUN("IsObject", kIsObject, "."), - /*07*/ DEFUN("RespondsTo", kRespondsTo, ".i"), - /*08*/ DEFUN("DrawPic", kDrawPic, "i*"), - - /*0a*/ DEFUN("PicNotValid", kPicNotValid, "i*"), - /*0b*/ DEFUN("Animate", kAnimate, "LI*"), // More like (li?)? - /*0c*/ DEFUN("SetNowSeen", kSetNowSeen, "oi*"), // The second parameter is ignored - /*0d*/ DEFUN("NumLoops", kNumLoops, "o"), - /*0e*/ DEFUN("NumCels", kNumCels, "o"), - /*0f*/ DEFUN("CelWide", kCelWide, "iOi*"), - /*10*/ DEFUN("CelHigh", kCelHigh, "iOi*"), - /*11*/ DEFUN("DrawCel", kDrawCel, "iiiiii*i*r*"), - /*12*/ DEFUN("AddToPic", kAddToPic, "Il*"), - // FIXME: signature check removed (set to .*) as kNewWindow is different in Mac versions - /*13*/ DEFUN("NewWindow", kNewWindow, "*."), - ///*13*/ DEFUN("NewWindow", kNewWindow, "iiiiZRi*"), - /*14*/ DEFUN("GetPort", kGetPort, ""), - /*15*/ DEFUN("SetPort", kSetPort, "ii*"), - /*16*/ DEFUN("DisposeWindow", kDisposeWindow, "ii*"), - /*17*/ DEFUN("DrawControl", kDrawControl, "o"), - /*18*/ DEFUN("HiliteControl", kHiliteControl, "o"), - /*19*/ DEFUN("EditControl", kEditControl, "ZoZo"), - /*1a*/ DEFUN("TextSize", kTextSize, "rZrii*r*"), - /*1b*/ DEFUN("Display", kDisplay, ".*"), - /*1c*/ DEFUN("GetEvent", kGetEvent, "ioi*"), // Mac versions pass an extra 3rd parameter (ignored - always 0?) - /*1d*/ DEFUN("GlobalToLocal", kGlobalToLocal, "oo*"), - /*1e*/ DEFUN("LocalToGlobal", kLocalToGlobal, "oo*"), - /*1f*/ DEFUN("MapKeyToDir", kMapKeyToDir, "o"), - /*20*/ DEFUN("DrawMenuBar", kDrawMenuBar, "i"), - /*21*/ DEFUN("MenuSelect", kMenuSelect, "oi*"), - /*22*/ DEFUN("AddMenu", kAddMenu, "rr"), - /*23*/ DEFUN("DrawStatus", kDrawStatus, "Zri*"), - /*24*/ DEFUN("Parse", kParse, "ro"), - /*25*/ DEFUN("Said", kSaid, "Zr"), - /*26*/ DEFUN("SetSynonyms", kSetSynonyms, "o"), - /*27*/ DEFUN("HaveMouse", kHaveMouse, ""), - /*28*/ DEFUN("SetCursor", kSetCursor, "i*"), - // FIXME: The number 0x28 occurs twice :-) - /*28*/ DEFUN("MoveCursor", kMoveCursor, "ii"), - /*29*/ DEFUN("FOpen", kFOpen, "ri"), - /*2a*/ DEFUN("FPuts", kFPuts, "ir"), - /*2b*/ DEFUN("FGets", kFGets, "rii"), - /*2c*/ DEFUN("FClose", kFClose, "i"), - /*2d*/ DEFUN("SaveGame", kSaveGame, "rirr*"), - /*2e*/ DEFUN("RestoreGame", kRestoreGame, "rir*"), - /*2f*/ DEFUN("RestartGame", kRestartGame, ""), - /*30*/ DEFUN("GameIsRestarting", kGameIsRestarting, "i*"), - /*31*/ DEFUN("DoSound", kDoSound, "iIo*"), - /*32*/ DEFUN("NewList", kNewList, ""), - /*33*/ DEFUN("DisposeList", kDisposeList, "l"), - /*34*/ DEFUN("NewNode", kNewNode, ".."), - /*35*/ DEFUN("FirstNode", kFirstNode, "Zl"), - /*36*/ DEFUN("LastNode", kLastNode, "l"), - /*37*/ DEFUN("EmptyList", kEmptyList, "l"), - /*38*/ DEFUN("NextNode", kNextNode, "n"), - /*39*/ DEFUN("PrevNode", kPrevNode, "n"), - /*3a*/ DEFUN("NodeValue", kNodeValue, "Zn"), - /*3b*/ DEFUN("AddAfter", kAddAfter, "lnn"), - /*3c*/ DEFUN("AddToFront", kAddToFront, "ln"), - /*3d*/ DEFUN("AddToEnd", kAddToEnd, "ln"), - /*3e*/ DEFUN("FindKey", kFindKey, "l."), - /*3f*/ DEFUN("DeleteKey", kDeleteKey, "l."), - /*40*/ DEFUN("Random", kRandom, "i*"), - /*41*/ DEFUN("Abs", kAbs, "Oi"), - /*42*/ DEFUN("Sqrt", kSqrt, "i"), - /*43*/ DEFUN("GetAngle", kGetAngle, "iiiii*"), // occasionally KQ6 passes a 5th argument by mistake - /*44*/ DEFUN("GetDistance", kGetDistance, "iiiii*"), - /*45*/ DEFUN("Wait", kWait, "i"), - /*46*/ DEFUN("GetTime", kGetTime, "i*"), - /*47*/ DEFUN("StrEnd", kStrEnd, "r"), - /*48*/ DEFUN("StrCat", kStrCat, "rr"), - /*49*/ DEFUN("StrCmp", kStrCmp, "rri*"), - /*4a*/ DEFUN("StrLen", kStrLen, "Zr"), - /*4b*/ DEFUN("StrCpy", kStrCpy, "rZri*"), - /*4c*/ DEFUN("Format", kFormat, "r.*"), - /*4d*/ DEFUN("GetFarText", kGetFarText, "iiZr"), - /*4e*/ DEFUN("ReadNumber", kReadNumber, "r"), - /*4f*/ DEFUN("BaseSetter", kBaseSetter, "o"), - /*50*/ DEFUN("DirLoop", kDirLoop, "oi"), - // Opcode 51 is defined twice for a reason: In older SCI versions - // it is CanBeHere, whereas in newer version it is CantBeHere - /*51*/ DEFUN("CanBeHere", kCanBeHere, "ol*"), - /*51*/ DEFUN("CantBeHere", kCantBeHere, "ol*"), - /*52*/ DEFUN("OnControl", kOnControl, "i*"), - /*53*/ DEFUN("InitBresen", kInitBresen, "oi*"), - /*54*/ DEFUN("DoBresen", kDoBresen, "o"), - /*55*/ DEFUN("DoAvoider", kDoAvoider, "o"), - /*56*/ DEFUN("SetJump", kSetJump, "oiii"), - /*57*/ DEFUN("SetDebug", kSetDebug, "i*"), - /*5c*/ DEFUN("MemoryInfo", kMemoryInfo, "i"), - /*5f*/ DEFUN("GetMenu", kGetMenu, "i."), - /*60*/ DEFUN("SetMenu", kSetMenu, "i.*"), - /*61*/ DEFUN("GetSaveFiles", kGetSaveFiles, "rrr"), - /*62*/ DEFUN("GetCWD", kGetCWD, "r"), - /*63*/ DEFUN("CheckFreeSpace", kCheckFreeSpace, "r.*"), - /*64*/ DEFUN("ValidPath", kValidPath, "r"), - /*65*/ DEFUN("CoordPri", kCoordPri, "ii*"), - /*66*/ DEFUN("StrAt", kStrAt, "rii*"), - /*67*/ DEFUN("DeviceInfo", kDeviceInfo, "i.*"), - /*68*/ DEFUN("GetSaveDir", kGetSaveDir, ".*"), // accepts a parameter in SCI2+ games - /*69*/ DEFUN("CheckSaveGame", kCheckSaveGame, ".*"), - /*6a*/ DEFUN("ShakeScreen", kShakeScreen, "ii*"), - /*6b*/ DEFUN("FlushResources", kFlushResources, "i"), - /*6c*/ DEFUN("TimesSin", kTimesSin, "ii"), - /*6d*/ DEFUN("TimesCos", kTimesCos, "ii"), - /*6e*/ DEFUN("6e", kTimesSin, "ii"), - /*6f*/ DEFUN("6f", kTimesCos, "ii"), - /*70*/ DEFUN("Graph", kGraph, ".*"), - /*71*/ DEFUN("Joystick", kJoystick, ".*"), - - // Experimental functions - /*74*/ DEFUN("FileIO", kFileIO, "i.*"), - /*(?)*/ DEFUN("Memory", kMemory, "i.*"), - /*(?)*/ DEFUN("Sort", kSort, "ooo"), - /*(?)*/ DEFUN("AvoidPath", kAvoidPath, "ii.*"), - /*(?)*/ DEFUN("Lock", kLock, "iii*"), - /*(?)*/ DEFUN("Palette", kPalette, "i.*"), - /*(?)*/ DEFUN("IsItSkip", kIsItSkip, "iiiii"), - /*7b*/ DEFUN("StrSplit", kStrSplit, "rrZr"), - - // Non-experimental functions without a fixed ID - DEFUN("CosMult", kTimesCos, "ii"), - DEFUN("SinMult", kTimesSin, "ii"), - - // Misc functions - /*(?)*/ DEFUN("CosDiv", kCosDiv, "ii"), - /*(?)*/ DEFUN("PriCoord", kPriCoord, "i"), - /*(?)*/ DEFUN("SinDiv", kSinDiv, "ii"), - /*(?)*/ DEFUN("TimesCot", kTimesCot, "ii"), - /*(?)*/ DEFUN("TimesTan", kTimesTan, "ii"), - DEFUN("Message", kMessage, ".*"), - DEFUN("GetMessage", kGetMessage, "iiir"), - DEFUN("DoAudio", kDoAudio, ".*"), - DEFUN("DoSync", kDoSync, ".*"), - DEFUN("MemorySegment", kMemorySegment, "iri*"), - DEFUN("Intersections", kIntersections, "iiiiriiiri"), - DEFUN("MergePoly", kMergePoly, "rli"), - DEFUN("ResCheck", kResCheck, "iii*"), - DEFUN("SetQuitStr", kSetQuitStr, "r"), - DEFUN("ShowMovie", kShowMovie, ".*"), - DEFUN("SetVideoMode", kSetVideoMode, "i"), - DEFUN("Platform", kPlatform, ".*"), - DEFUN("TextColors", kTextColors, ".*"), - DEFUN("TextFonts", kTextFonts, ".*"), - DEFUN("Portrait", kPortrait, ".*"), +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kGraph_subops[] = { + { SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start + { SIG_SCIALL, 2, MAP_CALL(GraphGetColorCount), "", NULL }, + // 3 - set palette via resource + { SIG_SCIALL, 4, MAP_CALL(GraphDrawLine), "iiiii(i)(i)", NULL }, + // 5 - nop + // 6 - draw pattern + { SIG_SCIALL, 7, MAP_CALL(GraphSaveBox), "iiiii", NULL }, + { SIG_SCIALL, 8, MAP_CALL(GraphRestoreBox), "[r0!]", kGraphRestoreBox_workarounds }, + // ^ this may get called with invalid references, we check them within restoreBits() and sierra sci behaves the same + { SIG_SCIALL, 9, MAP_CALL(GraphFillBoxBackground), "iiii", NULL }, + { SIG_SCIALL, 10, MAP_CALL(GraphFillBoxForeground), "iiii", kGraphFillBoxForeground_workarounds }, + { SIG_SCIALL, 11, MAP_CALL(GraphFillBoxAny), "iiiiii(i)(i)", kGraphFillBoxAny_workarounds }, + { SIG_SCI11, 12, MAP_CALL(GraphUpdateBox), "iiii(i)(r0)", NULL }, // kq6 hires + { SIG_SCIALL, 12, MAP_CALL(GraphUpdateBox), "iiii(i)", NULL }, + { SIG_SCIALL, 13, MAP_CALL(GraphRedrawBox), "iiii", NULL }, + { SIG_SCIALL, 14, MAP_CALL(GraphAdjustPriority), "ii", NULL }, + { SIG_SCI11, 15, MAP_CALL(GraphSaveUpscaledHiresBox), "iiii", NULL }, // kq6 hires + SCI_SUBOPENTRY_TERMINATOR +}; -#ifdef ENABLE_SCI32 - // SCI2 Kernel Functions - DEFUN("IsHiRes", kIsHiRes, ""), - DEFUN("Array", kArray, ".*"), - DEFUN("ListAt", kListAt, "li"), - DEFUN("String", kString, ".*"), - DEFUN("AddScreenItem", kAddScreenItem, "o"), - DEFUN("UpdateScreenItem", kUpdateScreenItem, "o"), - DEFUN("DeleteScreenItem", kDeleteScreenItem, "o"), - DEFUN("AddPlane", kAddPlane, "o"), - DEFUN("DeletePlane", kDeletePlane, "o"), - DEFUN("UpdatePlane", kUpdatePlane, "o"), - DEFUN("RepaintPlane", kRepaintPlane, "o"), - DEFUN("GetHighPlanePri", kGetHighPlanePri, ""), - DEFUN("FrameOut", kFrameOut, ""), - DEFUN("ListEachElementDo", kListEachElementDo, "li.*"), - DEFUN("ListFirstTrue", kListFirstTrue, "li.*"), - DEFUN("ListAllTrue", kListAllTrue, "li.*"), - DEFUN("ListIndexOf", kListIndexOf, "lZo"), - DEFUN("OnMe", kOnMe, "iio.*"), - DEFUN("InPolygon", kInPolygon, "iio"), - DEFUN("CreateTextBitmap", kCreateTextBitmap, "i.*"), - - // SCI2.1 Kernel Functions - DEFUN("Save", kSave, ".*"), - DEFUN("List", kList, ".*"), - DEFUN("Robot", kRobot, ".*"), - DEFUN("IsOnMe", kOnMe, "iio.*"), // TODO: this seems right, but verify... +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kPalVary_subops[] = { + { SIG_SCIALL, 0, MAP_CALL(PalVaryInit), "ii(i)(i)", NULL }, + { SIG_SCIALL, 1, MAP_CALL(PalVaryReverse), "(i)(i)(i)", NULL }, + { SIG_SCIALL, 2, MAP_CALL(PalVaryGetCurrentStep), "", NULL }, + { SIG_SCIALL, 3, MAP_CALL(PalVaryDeinit), "", NULL }, + { SIG_SCIALL, 4, MAP_CALL(PalVaryChangeTarget), "i", NULL }, + { SIG_SCIALL, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, + { SIG_SCIALL, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, + { SIG_SCI32, 8, MAP_CALL(PalVaryUnknown), "", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; -#endif +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kPalette_subops[] = { + { SIG_SCIALL, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, + { SIG_SCIALL, 2, MAP_CALL(PaletteSetFlag), "iii", NULL }, + { SIG_SCIALL, 3, MAP_CALL(PaletteUnsetFlag), "iii", NULL }, + { SIG_SCIALL, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL }, + { SIG_SCIALL, 5, MAP_CALL(PaletteFindColor), "iii", NULL }, + { SIG_SCIALL, 6, MAP_CALL(PaletteAnimate), "i*", NULL }, + { SIG_SCIALL, 7, MAP_CALL(PaletteSave), "", NULL }, + { SIG_SCIALL, 8, MAP_CALL(PaletteRestore), "i", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; - // its a stub, but its needed for Pharkas to work - DEFUN("PalVary", kPalVary, "ii*"), - DEFUN("AssertPalette", kAssertPalette, "i"), - -#if 0 - // Stub functions - /*09*/ DEFUN("Show", kShow, "i"), - DEFUN("ShiftScreen", kShiftScreen, ".*"), - DEFUN("ListOps", kListOps, ".*"), - DEFUN("ATan", kATan, ".*"), - DEFUN("Record", kRecord, ".*"), - DEFUN("PlayBack", kPlayBack, ".*"), - DEFUN("DbugStr", kDbugStr, ".*"), -#endif +struct SciKernelMapEntry { + const char *name; + KernelFunctionCall *function; + + SciVersion fromVersion; + SciVersion toVersion; + byte forPlatform; + + const char *signature; + const SciKernelMapSubEntry *subFunctions; + const SciWorkaroundEntry *workarounds; +}; + +// name, version/platform, signature, sub-signatures, workarounds +static SciKernelMapEntry s_kernelMap[] = { + { MAP_CALL(Abs), SIG_EVERYWHERE, "i", NULL, kAbs_workarounds }, + { MAP_CALL(AddAfter), SIG_EVERYWHERE, "lnn", NULL, NULL }, + { MAP_CALL(AddMenu), SIG_EVERYWHERE, "rr", NULL, NULL }, + { MAP_CALL(AddToEnd), SIG_EVERYWHERE, "ln", NULL, NULL }, + { MAP_CALL(AddToFront), SIG_EVERYWHERE, "ln", NULL, NULL }, + { MAP_CALL(AddToPic), SIG_EVERYWHERE, "[il](iiiiii)", NULL, NULL }, + { MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL }, + { MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, + { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, + { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, + { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, + { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, + { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL }, + { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(CheckSaveGame), SIG_EVERYWHERE, ".*", NULL, NULL }, + { MAP_CALL(Clone), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(CoordPri), SIG_EVERYWHERE, "i(i)", NULL, NULL }, + { MAP_CALL(CosDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(DeleteKey), SIG_EVERYWHERE, "l.", NULL, NULL }, + { MAP_CALL(DeviceInfo), SIG_EVERYWHERE, "i(r)(r)(i)", NULL, NULL }, // subop + { MAP_CALL(Display), SIG_EVERYWHERE, "[ir]([ir!]*)", NULL, NULL }, + // ^ we allow invalid references here, because kDisplay gets called with those in e.g. pq3 during intro + // restoreBits() checks and skips invalid handles, so that's fine. Sierra SCI behaved the same + { MAP_CALL(DirLoop), SIG_EVERYWHERE, "oi", NULL, NULL }, + { MAP_CALL(DisposeClone), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL }, + { MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds }, + { MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL }, + { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DoSound), SIG_EVERYWHERE, "i([io])(i)(ii[io])(i)", kDoSound_subops, NULL }, + { MAP_CALL(DoSync), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(DrawCel), SIG_SCI11, SIGFOR_PC, "iiiii(i)(i)(r0)", NULL, NULL }, // for kq6 hires + { MAP_CALL(DrawCel), SIG_EVERYWHERE, "iiiii(i)(i)", NULL, NULL }, + { MAP_CALL(DrawControl), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DrawMenuBar), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(DrawPic), SIG_EVERYWHERE, "i(i)(i)(i)", NULL, NULL }, + { MAP_CALL(DrawStatus), SIG_EVERYWHERE, "[r0](i)(i)", NULL, NULL }, + { 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 }, + { MAP_CALL(FileIO), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(FindKey), SIG_EVERYWHERE, "l.", NULL, NULL }, + { MAP_CALL(FirstNode), SIG_EVERYWHERE, "[l0]", NULL, NULL }, + { MAP_CALL(FlushResources), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(Format), SIG_EVERYWHERE, "r(.*)", NULL, NULL }, + { MAP_CALL(GameIsRestarting), SIG_EVERYWHERE, "(i)", NULL, NULL }, + { MAP_CALL(GetAngle), SIG_EVERYWHERE, "iiii", NULL, NULL }, + // ^^ FIXME - occasionally KQ6 passes a 5th argument by mistake + { MAP_CALL(GetCWD), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(GetDistance), SIG_EVERYWHERE, "ii(i)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(GetEvent), SIG_SCIALL, SIGFOR_MAC, "io(i*)", NULL, NULL }, + { MAP_CALL(GetEvent), SIG_EVERYWHERE, "io", NULL, NULL }, + { MAP_CALL(GetFarText), SIG_EVERYWHERE, "ii[r0]", NULL, NULL }, + { MAP_CALL(GetMenu), SIG_EVERYWHERE, "i.", NULL, NULL }, + { MAP_CALL(GetMessage), SIG_EVERYWHERE, "iiir", NULL, NULL }, + { MAP_CALL(GetPort), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(GetSaveDir), SIG_SCI32, SIGFOR_ALL, "(r*)", NULL, NULL }, + { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, + { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, + { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, + { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, + { MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(InitBresen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(Intersections), SIG_EVERYWHERE, "iiiiriiiri", NULL, NULL }, + { MAP_CALL(IsItSkip), SIG_EVERYWHERE, "iiiii", NULL, NULL }, + { MAP_CALL(IsObject), SIG_EVERYWHERE, ".", NULL, NULL }, + { MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL }, + { MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, + { MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(MemoryInfo), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(MemorySegment), SIG_EVERYWHERE, "ir(i)", NULL, NULL }, // subop + { MAP_CALL(MenuSelect), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(MergePoly), SIG_EVERYWHERE, "rli", NULL, NULL }, + { MAP_CALL(Message), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(MoveCursor), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(NewList), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(NewNode), SIG_EVERYWHERE, "..", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCIALL, SIGFOR_MAC, ".*", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCI0, SIGFOR_ALL, "iiii[r0]i(i)(i)(i)", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCI1, SIGFOR_ALL, "iiii[ir]i(i)(i)([ir])(i)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCI11, SIGFOR_ALL, "iiiiiiii[r0]i(i)(i)(i)", NULL, NULL }, + { MAP_CALL(NextNode), SIG_EVERYWHERE, "n", NULL, NULL }, + { MAP_CALL(NodeValue), SIG_EVERYWHERE, "[n0]", NULL, NULL }, + { MAP_CALL(NumCels), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(NumLoops), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(OnControl), SIG_EVERYWHERE, "ii(i)(i)(i)", NULL, NULL }, + { MAP_CALL(PalVary), SIG_EVERYWHERE, "i(i*)", kPalVary_subops, NULL }, + { MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL }, + { MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL }, + { MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL }, + { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL }, + { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(Random), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, + { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, + { MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(RestoreGame), SIG_EVERYWHERE, "rir", NULL, NULL }, + { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, + { MAP_CALL(SaveGame), SIG_EVERYWHERE, "rir(r)", NULL, NULL }, + { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, + { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i*)", NULL, NULL }, + { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, + { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, + { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, + { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iii)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, + { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL }, + { MAP_CALL(Sqrt), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(StrAt), SIG_EVERYWHERE, "ri(i)", NULL, NULL }, + { MAP_CALL(StrCat), SIG_EVERYWHERE, "rr", NULL, NULL }, + { MAP_CALL(StrCmp), SIG_EVERYWHERE, "rr(i)", NULL, NULL }, + { MAP_CALL(StrCpy), SIG_EVERYWHERE, "[r0]r(i)", NULL, NULL }, + { MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, NULL }, + { MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL }, + { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL }, + { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL }, + { MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL }, + { "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(TimesSin), SIG_EVERYWHERE, "ii", NULL, NULL }, + { "SinMult", kTimesSin, SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(TimesTan), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(UnLoad), SIG_EVERYWHERE, "i[ri]", NULL, kUnLoad_workarounds }, + { MAP_CALL(ValidPath), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(Wait), SIG_EVERYWHERE, "i", NULL, NULL }, - {NULL, NULL, NULL} // Terminator +#ifdef ENABLE_SCI32 + // SCI2 Kernel Functions + { MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, + { MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(FrameOut), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL }, + { MAP_CALL(IsHiRes), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(ListAllTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, + { MAP_CALL(ListAt), SIG_EVERYWHERE, "li", NULL, NULL }, + { MAP_CALL(ListEachElementDo), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, + { MAP_CALL(ListFirstTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, + { MAP_CALL(ListIndexOf), SIG_EVERYWHERE, "l[o0]", NULL, NULL }, + { MAP_CALL(OnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL }, + { MAP_CALL(RepaintPlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(String), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(UpdatePlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, + + // SCI2.1 Kernel Functions + { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL }, + { MAP_CALL(List), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { 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(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL } +#endif }; -Kernel::Kernel(ResourceManager *resMan, SegManager *segMan) : _resMan(resMan), _segMan(segMan) { +Kernel::Kernel(ResourceManager *resMan, SegManager *segMan) + : _resMan(resMan), _segMan(segMan), _invalid("<invalid>") { loadSelectorNames(); mapSelectors(); // Map a few special selectors for later use } Kernel::~Kernel() { - for (KernelFuncsContainer::iterator i = _kernelFuncs.begin(); i != _kernelFuncs.end(); ++i) - // TODO: Doing a const_cast is not that nice actually... But since KernelFuncWithSignature - // keeps the signature member as "const char *" there is no way around it. - // Think of a clever way to avoid this. - free(const_cast<char *>(i->signature)); + for (KernelFunctionArray::iterator i = _kernelFuncs.begin(); i != _kernelFuncs.end(); ++i) + free(i->signature); } uint Kernel::getSelectorNamesSize() const { @@ -409,6 +656,8 @@ uint Kernel::getSelectorNamesSize() const { } const Common::String &Kernel::getSelectorName(uint selector) const { + if (selector >= _selectorNames.size()) + return _invalid; return _selectorNames[selector]; } @@ -417,12 +666,10 @@ uint Kernel::getKernelNamesSize() const { } const Common::String &Kernel::getKernelName(uint number) const { - // FIXME: The following check is a temporary workaround for - // an issue leading to crashes when using the debugger's backtrace - // command. - static const Common::String invalid = "(invalid)"; + // FIXME: The following check is a temporary workaround for an issue + // leading to crashes when using the debugger's backtrace command. if (number >= _kernelNames.size()) - return invalid; + return _invalid; return _kernelNames[number]; } @@ -477,165 +724,241 @@ void Kernel::loadSelectorNames() { } } -static void kernel_compile_signature(const char **s) { - const char *src = *s; - char *result; - int ellipsis = 0; - char v; - int index = 0; - - if (!src) - return; // NULL signature: Nothing to do - - result = (char *)malloc(strlen(*s) + 1); - - while (*src) { - char c; - v = 0; - - if (ellipsis) { - error("Failed compiling kernel function signature '%s': non-terminal ellipsis '%c'", *s, *src); - } - - do { - char cc; - cc = c = *src++; - if (c >= 'A' || c <= 'Z') - cc = c | KSIG_SPEC_SUM_DONE; - - switch (cc) { - case KSIG_SPEC_LIST: - v |= KSIG_LIST; - break; - - case KSIG_SPEC_NODE: - v |= KSIG_NODE; - break; - - case KSIG_SPEC_REF: - v |= KSIG_REF; - break; - - case KSIG_SPEC_OBJECT: - v |= KSIG_OBJECT; - break; - - case KSIG_SPEC_ARITHMETIC: - v |= KSIG_ARITHMETIC; - break; - - case KSIG_SPEC_NULL: - v |= KSIG_NULL; - break; - - case KSIG_SPEC_ANY: - v |= KSIG_ANY; - break; - - case KSIG_SPEC_ELLIPSIS: - v |= KSIG_ELLIPSIS; - ellipsis = 1; - break; - - default: - error("INTERNAL ERROR when compiling kernel function signature '%s': (%02x) not understood (aka" - " '%c')\n", *s, c, c); +// this parses a written kernel signature into an internal memory format +// [io] -> either integer or object +// (io) -> optionally integer AND an object +// (i) -> optional integer +// . -> any type +// i* -> optional multiple integers +// .* -> any parameters afterwards (or none) +static uint16 *parseKernelSignature(const char *kernelName, const char *writtenSig) { + const char *curPos; + char curChar; + uint16 *result = NULL; + uint16 *writePos = NULL; + int size = 0; + bool validType = false; + bool optionalType = false; + bool eitherOr = false; + bool optional = false; + bool hadOptional = false; + + // No signature given? no signature out + if (!writtenSig) + return NULL; + + // First, we check how many bytes the result will be + // we also check, if the written signature makes any sense + curPos = writtenSig; + while (*curPos) { + curChar = *curPos; + switch (curChar) { + case '[': // either or + if (eitherOr) + error("signature for k%s: '[' used within '[]'", kernelName); + eitherOr = true; + validType = false; + break; + case ']': // either or end + if (!eitherOr) + error("signature for k%s: ']' used without leading '['", kernelName); + if (!validType) + error("signature for k%s: '[]' does not surround valid type(s)", kernelName); + eitherOr = false; + validType = false; + size++; + break; + case '(': // optional + if (optional) + error("signature for k%s: '(' used within '()' brackets", kernelName); + if (eitherOr) + error("signature for k%s: '(' used within '[]' brackets", kernelName); + optional = true; + validType = false; + optionalType = false; + break; + case ')': // optional end + if (!optional) + error("signature for k%s: ')' used without leading '('", kernelName); + if (!optionalType) + error("signature for k%s: '()' does not to surround valid type(s)", kernelName); + optional = false; + validType = false; + hadOptional = true; + break; + case '0': // allowed types + case 'i': + case 'o': + case 'r': + case 'l': + case 'n': + case '.': + case '!': + if ((hadOptional) & (!optional)) + error("signature for k%s: non-optional type may not follow optional type", kernelName); + validType = true; + if (optional) + optionalType = true; + if (!eitherOr) + size++; + break; + case '*': // accepts more of the same parameter (must be last char) + if (!validType) { + if ((writtenSig == curPos) || (*(curPos - 1) != ']')) + error("signature for k%s: a valid type must be in front of '*'", kernelName); } - } while (*src && (*src == KSIG_SPEC_ELLIPSIS || (c < 'a' && c != KSIG_SPEC_ANY))); - - // To handle sum types - result[index++] = v; + if (eitherOr) + error("signature for k%s: '*' may not be inside '[]'", kernelName); + if (optional) { + if ((*(curPos + 1) != ')') || (*(curPos + 2) != 0)) + error("signature for k%s: '*' may only be used for last type", kernelName); + } else { + if (*(curPos + 1) != 0) + error("signature for k%s: '*' may only be used for last type", kernelName); + } + break; + default: + error("signature for k%s: '%c' unknown", kernelName, *curPos); + } + curPos++; } - result[index] = 0; - *s = result; // Write back -} - -void Kernel::mapFunctions() { - int mapped = 0; - int ignored = 0; - uint functions_nr = _kernelNames.size(); - - _kernelFuncs.resize(functions_nr); - - for (uint functnr = 0; functnr < functions_nr; functnr++) { - int found = -1; - - // First, get the name, if known, of the kernel function with number functnr - Common::String sought_name = _kernelNames[functnr]; - - // Reset the table entry - _kernelFuncs[functnr].fun = NULL; - _kernelFuncs[functnr].signature = NULL; - _kernelFuncs[functnr].orig_name = sought_name; - - if (sought_name.empty()) { - // No name was given -> must be an unknown opcode - warning("Kernel function %x unknown", functnr); - _kernelFuncs[functnr].isDummy = true; - continue; + uint16 signature = 0; + + // Now we allocate buffer with required size and fill it + result = new uint16[size + 1]; + writePos = result; + curPos = writtenSig; + do { + curChar = *curPos; + if (!eitherOr) { + // not within either-or, check if next character forces output + switch (curChar) { + case 0: + case '[': + case '(': + case ')': + case 'i': + case 'o': + case 'r': + case 'l': + case 'n': + case '.': + case '!': + // and we also got some signature pending? + if (signature) { + if (!(signature & SIG_MAYBE_ANY)) + error("signature for k%s: invalid ('!') may only get used in combination with a real type", kernelName); + if ((signature & SIG_IS_INVALID) && ((signature & SIG_MAYBE_ANY) == (SIG_TYPE_NULL | SIG_TYPE_INTEGER))) + error("signature for k%s: invalid ('!') should not be used on exclusive null/integer type", kernelName); + if (optional) { + signature |= SIG_IS_OPTIONAL; + if (curChar != ')') + signature |= SIG_NEEDS_MORE; + } + *writePos = signature; + writePos++; + signature = 0; + } + } } - - // Don't map dummy functions - they will never be called - if (sought_name == "Dummy") { - _kernelFuncs[functnr].isDummy = true; - continue; + switch (curChar) { + case '[': // either or + eitherOr = true; + break; + case ']': // either or end + eitherOr = false; + break; + case '(': // optional + optional = true; + break; + case ')': // optional end + optional = false; + break; + case '0': + if (signature & SIG_TYPE_NULL) + error("signature for k%s: NULL ('0') specified more than once", kernelName); + signature |= SIG_TYPE_NULL; + break; + case 'i': + if (signature & SIG_TYPE_INTEGER) + error("signature for k%s: integer ('i') specified more than once", kernelName); + signature |= SIG_TYPE_INTEGER | SIG_TYPE_NULL; + break; + case 'o': + if (signature & SIG_TYPE_OBJECT) + error("signature for k%s: object ('o') specified more than once", kernelName); + signature |= SIG_TYPE_OBJECT; + break; + case 'r': + if (signature & SIG_TYPE_REFERENCE) + error("signature for k%s: reference ('r') specified more than once", kernelName); + signature |= SIG_TYPE_REFERENCE; + break; + case 'l': + if (signature & SIG_TYPE_LIST) + error("signature for k%s: list ('l') specified more than once", kernelName); + signature |= SIG_TYPE_LIST; + break; + case 'n': + if (signature & SIG_TYPE_NODE) + error("signature for k%s: node ('n') specified more than once", kernelName); + signature |= SIG_TYPE_NODE; + break; + case '.': + if (signature & SIG_MAYBE_ANY) + error("signature for k%s: maybe-any ('.') shouldn't get specified with other types in front of it", kernelName); + signature |= SIG_MAYBE_ANY; + break; + case '!': + if (signature & SIG_IS_INVALID) + error("signature for k%s: invalid ('!') specified more than once", kernelName); + signature |= SIG_IS_INVALID; + break; + case '*': // accepts more of the same parameter + signature |= SIG_MORE_MAY_FOLLOW; + break; + default: + break; } + curPos++; + } while (curChar); - // If the name is known, look it up in kfunct_mappers. This table - // maps kernel func names to actual function (pointers). - for (uint seeker = 0; (found == -1) && kfunct_mappers[seeker].name; seeker++) - if (sought_name == kfunct_mappers[seeker].name) - found = seeker; // Found a kernel function with the correct name! - - if (found == -1) { - // No match but a name was given -> stub - warning("Kernel function %s[%x] unmapped", sought_name.c_str(), functnr); - _kernelFuncs[functnr].isDummy = true; - } else { - // A match in kfunct_mappers was found - if (kfunct_mappers[found].fun) { - _kernelFuncs[functnr].fun = kfunct_mappers[found].fun; - _kernelFuncs[functnr].signature = kfunct_mappers[found].signature; - _kernelFuncs[functnr].isDummy = false; - kernel_compile_signature(&(_kernelFuncs[functnr].signature)); - ++mapped; - } else { - //warning("Ignoring function %s\n", kfunct_mappers[found].name); - ++ignored; - } - } - } // for all functions requesting to be mapped + // Write terminator + *writePos = 0; - debugC(2, kDebugLevelVM, "Handled %d/%d kernel functions, mapping %d and ignoring %d.", - mapped + ignored, _kernelNames.size(), mapped, ignored); - - return; + return result; } -int Kernel::findRegType(reg_t reg) { - // No segment? Must be arithmetic +uint16 Kernel::findRegType(reg_t reg) { + // No segment? Must be integer if (!reg.segment) - return reg.offset ? KSIG_ARITHMETIC : KSIG_ARITHMETIC | KSIG_NULL; + return SIG_TYPE_INTEGER | (reg.offset ? 0 : SIG_TYPE_NULL); + + if (reg.segment == 0xFFFF) + return SIG_TYPE_UNINITIALIZED; // Otherwise it's an object SegmentObj *mobj = _segMan->getSegmentObj(reg.segment); if (!mobj) - return 0; // Invalid + return SIG_TYPE_ERROR; + uint16 result = 0; if (!mobj->isValidOffset(reg.offset)) - warning("[KERN] ref %04x:%04x is invalid", PRINT_REG(reg)); + 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)._buf + reg.offset)) { - return ((Script *)mobj)->getObject(reg.offset) ? KSIG_OBJECT : KSIG_REF; + RAW_IS_OBJECT((*(Script *)mobj).getBuf(reg.offset)) ) { + result |= ((Script *)mobj)->getObject(reg.offset) ? SIG_TYPE_OBJECT : SIG_TYPE_REFERENCE; } else - return KSIG_REF; + result |= SIG_TYPE_REFERENCE; + break; case SEG_TYPE_CLONES: - return KSIG_OBJECT; + result |= SIG_TYPE_OBJECT; + break; case SEG_TYPE_LOCALS: case SEG_TYPE_STACK: case SEG_TYPE_SYS_STRINGS: @@ -645,54 +968,286 @@ int Kernel::findRegType(reg_t reg) { case SEG_TYPE_ARRAY: case SEG_TYPE_STRING: #endif - return KSIG_REF; + result |= SIG_TYPE_REFERENCE; + break; case SEG_TYPE_LISTS: - return KSIG_LIST; + result |= SIG_TYPE_LIST; + break; case SEG_TYPE_NODES: - return KSIG_NODE; + result |= SIG_TYPE_NODE; + break; default: - return 0; + return SIG_TYPE_ERROR; } + return result; } -bool Kernel::signatureMatch(const char *sig, int argc, const reg_t *argv) { - // Always "match" if no signature is given - if (!sig) - return true; +struct SignatureDebugType { + uint16 typeCheck; + const char *text; +}; - while (*sig && argc) { - if ((*sig & KSIG_ANY) != KSIG_ANY) { - int type = findRegType(*argv); +static const SignatureDebugType signatureDebugTypeList[] = { + { SIG_TYPE_NULL, "null" }, + { SIG_TYPE_INTEGER, "integer" }, + { SIG_TYPE_UNINITIALIZED, "uninitialized" }, + { SIG_TYPE_OBJECT, "object" }, + { SIG_TYPE_REFERENCE, "reference" }, + { SIG_TYPE_LIST, "list" }, + { SIG_TYPE_NODE, "node" }, + { SIG_TYPE_ERROR, "error" }, + { SIG_IS_INVALID, "invalid" }, + { 0, NULL } +}; - if (!type) { - warning("[KERN] Could not determine type of ref %04x:%04x; failing signature check", PRINT_REG(*argv)); - return false; - } +static void kernelSignatureDebugType(const uint16 type) { + bool firstPrint = true; - if (!(type & *sig)) { - warning("kernel_matches_signature: %d args left, is %d, should be %d", argc, type, *sig); - return false; - } + const SignatureDebugType *list = signatureDebugTypeList; + while (list->typeCheck) { + if (type & list->typeCheck) { + if (!firstPrint) + printf(", "); + printf("%s", list->text); + firstPrint = false; + } + list++; + } +} +// Shows kernel call signature and current arguments for debugging purposes +void Kernel::signatureDebug(const uint16 *sig, int argc, const reg_t *argv) { + int argnr = 0; + while (*sig || argc) { + printf("parameter %d: ", argnr++); + if (argc) { + reg_t parameter = *argv; + printf("%04x:%04x (", PRINT_REG(parameter)); + int regType = findRegType(parameter); + if (regType) + kernelSignatureDebugType(regType); + else + printf("unknown type of %04x:%04x", PRINT_REG(parameter)); + printf(")"); + argv++; + argc--; + } else { + printf("not passed"); } - if (!(*sig & KSIG_ELLIPSIS)) - ++sig; - ++argv; - --argc; + if (*sig) { + const uint16 signature = *sig; + if ((signature & SIG_MAYBE_ANY) == SIG_MAYBE_ANY) { + printf(", may be any"); + } else { + printf(", should be "); + kernelSignatureDebugType(signature); + } + if (signature & SIG_IS_OPTIONAL) + printf(" (optional)"); + if (signature & SIG_NEEDS_MORE) + printf(" (needs more)"); + if (signature & SIG_MORE_MAY_FOLLOW) + printf(" (more may follow)"); + sig++; + } + printf("\n"); } +} + +bool Kernel::signatureMatch(const uint16 *sig, int argc, const reg_t *argv) { + uint16 nextSig = *sig; + uint16 curSig = nextSig; + while (nextSig && argc) { + curSig = nextSig; + int type = findRegType(*argv); + + if ((type & SIG_IS_INVALID) && (!(curSig & SIG_IS_INVALID))) + return false; // pointer is invalid and signature doesn't allow that? + + if (!((type & ~SIG_IS_INVALID) & curSig)) + return false; // type mismatch - if (argc) { - warning("kernel_matches_signature: too many arguments"); - return false; // Too many arguments + if (!(curSig & SIG_MORE_MAY_FOLLOW)) { + sig++; + nextSig = *sig; + } else { + nextSig |= SIG_IS_OPTIONAL; // more may follow -> assumes followers are optional + } + argv++; + argc--; } - if (*sig == 0 || (*sig & KSIG_ELLIPSIS)) + + // Too many arguments? + if (argc) + return false; + // Signature end reached? + if (nextSig == 0) return true; - warning("kernel_matches_signature: too few arguments"); + // current parameter is optional? + if (curSig & SIG_IS_OPTIONAL) { + // yes, check if nothing more is required + if (!(curSig & SIG_NEEDS_MORE)) + return true; + } else { + // no, check if next parameter is optional + if (nextSig & SIG_IS_OPTIONAL) + return true; + } + // Too few arguments or more optional arguments required return false; } -void Kernel::setDefaultKernelNames(Common::String gameId) { - _kernelNames = Common::StringArray(sci_default_knames, SCI_KNAMES_DEFAULT_ENTRIES_NR); +void Kernel::mapFunctions() { + int mapped = 0; + int ignored = 0; + uint functionCount = _kernelNames.size(); + byte platformMask = 0; + SciVersion myVersion = getSciVersion(); + + switch (g_sci->getPlatform()) { + case Common::kPlatformPC: + platformMask = SIGFOR_DOS; + break; + case Common::kPlatformPC98: + platformMask = SIGFOR_PC98; + break; + case Common::kPlatformWindows: + platformMask = SIGFOR_WIN; + break; + case Common::kPlatformMacintosh: + platformMask = SIGFOR_MAC; + break; + case Common::kPlatformAmiga: + platformMask = SIGFOR_AMIGA; + break; + case Common::kPlatformAtariST: + platformMask = SIGFOR_ATARI; + break; + default: + break; + } + + _kernelFuncs.resize(functionCount); + + for (uint id = 0; id < functionCount; id++) { + // First, get the name, if known, of the kernel function with number functnr + Common::String kernelName = _kernelNames[id]; + + // Reset the table entry + _kernelFuncs[id].function = NULL; + _kernelFuncs[id].signature = NULL; + _kernelFuncs[id].name = NULL; + _kernelFuncs[id].workarounds = NULL; + _kernelFuncs[id].subFunctions = NULL; + _kernelFuncs[id].subFunctionCount = 0; + _kernelFuncs[id].debugCalls = false; + if (kernelName.empty()) { + // No name was given -> must be an unknown opcode + warning("Kernel function %x unknown", id); + continue; + } + + // Don't map dummy functions - they will never be called + if (kernelName == "Dummy") { + _kernelFuncs[id].function = kDummy; + continue; + } + + // If the name is known, look it up in s_kernelMap. This table + // maps kernel func names to actual function (pointers). + SciKernelMapEntry *kernelMap = s_kernelMap; + bool nameMatch = false; + while (kernelMap->name) { + if (kernelName == kernelMap->name) { + if ((kernelMap->fromVersion == SCI_VERSION_NONE) || (kernelMap->fromVersion <= myVersion)) + if ((kernelMap->toVersion == SCI_VERSION_NONE) || (kernelMap->toVersion >= myVersion)) + if (platformMask & kernelMap->forPlatform) + break; + nameMatch = true; + } + kernelMap++; + } + + if (kernelMap->name) { + // A match was found + _kernelFuncs[id].function = kernelMap->function; + _kernelFuncs[id].name = kernelMap->name; + _kernelFuncs[id].signature = parseKernelSignature(kernelMap->name, kernelMap->signature); + _kernelFuncs[id].workarounds = kernelMap->workarounds; + if (kernelMap->subFunctions) { + // Get version for subfunction identification + SciVersion mySubVersion = (SciVersion)kernelMap->function(NULL, 0, NULL).offset; + // Now check whats the highest subfunction-id for this version + const SciKernelMapSubEntry *kernelSubMap = kernelMap->subFunctions; + uint16 subFunctionCount = 0; + while (kernelSubMap->function) { + if ((kernelSubMap->fromVersion == SCI_VERSION_NONE) || (kernelSubMap->fromVersion <= mySubVersion)) + if ((kernelSubMap->toVersion == SCI_VERSION_NONE) || (kernelSubMap->toVersion >= mySubVersion)) + if (subFunctionCount <= kernelSubMap->id) + subFunctionCount = kernelSubMap->id + 1; + kernelSubMap++; + } + if (!subFunctionCount) + error("k%s[%x]: no subfunctions found for requested version", kernelName.c_str(), id); + // Now allocate required memory and go through it again + _kernelFuncs[id].subFunctionCount = subFunctionCount; + KernelSubFunction *subFunctions = new KernelSubFunction[subFunctionCount]; + _kernelFuncs[id].subFunctions = subFunctions; + memset(subFunctions, 0, sizeof(KernelSubFunction) * subFunctionCount); + // And fill this info out + kernelSubMap = kernelMap->subFunctions; + uint kernelSubNr = 0; + while (kernelSubMap->function) { + if ((kernelSubMap->fromVersion == SCI_VERSION_NONE) || (kernelSubMap->fromVersion <= mySubVersion)) + if ((kernelSubMap->toVersion == SCI_VERSION_NONE) || (kernelSubMap->toVersion >= mySubVersion)) { + uint subId = kernelSubMap->id; + if (!subFunctions[subId].function) { + subFunctions[subId].function = kernelSubMap->function; + subFunctions[subId].name = kernelSubMap->name; + subFunctions[subId].workarounds = kernelSubMap->workarounds; + if (kernelSubMap->signature) { + subFunctions[subId].signature = parseKernelSignature(kernelSubMap->name, kernelSubMap->signature); + } else { + // we go back the submap to find the previous signature for that kernel call + const SciKernelMapSubEntry *kernelSubMapBack = kernelSubMap; + uint kernelSubLeft = kernelSubNr; + while (kernelSubLeft) { + kernelSubLeft--; + kernelSubMapBack--; + if (kernelSubMapBack->name == kernelSubMap->name) { + if (kernelSubMapBack->signature) { + subFunctions[subId].signature = parseKernelSignature(kernelSubMap->name, kernelSubMapBack->signature); + break; + } + } + } + if (!subFunctions[subId].signature) + error("k%s: no previous signatures", kernelSubMap->name); + } + } + } + kernelSubMap++; + kernelSubNr++; + } + } + ++mapped; + } else { + if (nameMatch) + error("k%s[%x]: not found for this version/platform", kernelName.c_str(), id); + // No match but a name was given -> stub + warning("k%s[%x]: unmapped", kernelName.c_str(), id); + _kernelFuncs[id].function = kStub; + } + } // for all functions requesting to be mapped + + debugC(2, kDebugLevelVM, "Handled %d/%d kernel functions, mapping %d and ignoring %d.", + mapped + ignored, _kernelNames.size(), mapped, ignored); + + return; +} + +void Kernel::setDefaultKernelNames() { + _kernelNames = Common::StringArray(s_defaultKernelNames, ARRAYSIZE(s_defaultKernelNames)); // Some (later) SCI versions replaced CanBeHere by CantBeHere if (_selectorCache.cantBeHere != -1) @@ -727,14 +1282,19 @@ void Kernel::setDefaultKernelNames(Common::String gameId) { break; case SCI_VERSION_1_1: - // In KQ6 CD, the empty kSetSynonyms function has been replaced - // with kPortrait. In KQ6 Mac, kPlayBack has been replaced by - // kShowMovie. - if (gameId == "kq6") { - if (g_sci->getPlatform() == Common::kPlatformMacintosh) - _kernelNames[0x84] = "ShowMovie"; - else + // In SCI1.1, kSetSynonyms is an empty function + _kernelNames[0x26] = "Empty"; + + if (g_sci->getGameId() == GID_KQ6) { + // In the Windows version of KQ6 CD, the empty kSetSynonyms + // function has been replaced with kPortrait. In KQ6 Mac, + // kPlayBack has been replaced by kShowMovie. + if (g_sci->getPlatform() == Common::kPlatformWindows) _kernelNames[0x26] = "Portrait"; + else if (g_sci->getPlatform() == Common::kPlatformMacintosh) + _kernelNames[0x84] = "ShowMovie"; + } else if (g_sci->getGameId() == GID_QFG4 && g_sci->isDemo()) { + _kernelNames[0x7b] = "RemapColors"; // QFG4 Demo has this SCI2 function instead of StrSplit } _kernelNames[0x71] = "PalVary"; @@ -747,20 +1307,19 @@ void Kernel::setDefaultKernelNames(Common::String gameId) { } } -bool Kernel::loadKernelNames(Common::String gameId) { +void Kernel::loadKernelNames(GameFeatures *features) { _kernelNames.clear(); #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2_1) - setKernelNamesSci21(); + setKernelNamesSci21(features); else if (getSciVersion() == SCI_VERSION_2) setKernelNamesSci2(); else #endif - setDefaultKernelNames(gameId); + setDefaultKernelNames(); mapFunctions(); - return true; } Common::String Kernel::lookupText(reg_t address, int index) { @@ -769,31 +1328,28 @@ Common::String Kernel::lookupText(reg_t address, int index) { if (address.segment) return _segMan->getString(address); - else { - int textlen; - int _index = index; - textres = _resMan->findResource(ResourceId(kResourceTypeText, address.offset), 0); - - if (!textres) { - error("text.%03d not found", address.offset); - return NULL; /* Will probably segfault */ - } - textlen = textres->size; - seeker = (char *) textres->data; + int textlen; + int _index = index; + textres = _resMan->findResource(ResourceId(kResourceTypeText, address.offset), 0); - while (index--) - while ((textlen--) && (*seeker++)) - ; + if (!textres) { + error("text.%03d not found", address.offset); + return NULL; /* Will probably segfault */ + } - if (textlen) - return seeker; - else { - error("Index %d out of bounds in text.%03d", _index, address.offset); - return NULL; - } + textlen = textres->size; + seeker = (char *) textres->data; - } + while (index--) + while ((textlen--) && (*seeker++)) + ; + + if (textlen) + return seeker; + + error("Index %d out of bounds in text.%03d", _index, address.offset); + return NULL; } } // End of namespace Sci diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 8f8f34f74e..fa206e8053 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -31,7 +31,7 @@ #include "common/rect.h" #include "common/str-array.h" -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS +#include "sci/engine/selector.h" #include "sci/engine/vm_types.h" // for reg_t #include "sci/engine/vm.h" @@ -39,6 +39,7 @@ namespace Sci { struct Node; // from segment.h struct List; // from segment.h +struct SelectorCache; // from selector.h /** * @defgroup VocabularyResources Vocabulary resources in SCI @@ -92,59 +93,67 @@ struct List; // from segment.h //@{ //#define DEBUG_PARSER // enable for parser debugging -//#define DISABLE_VALIDATIONS // enable to stop validation checks // ---- Kernel signatures ----------------------------------------------------- -#define KSIG_TERMINATOR 0 - -// Uncompiled signatures -#define KSIG_SPEC_ARITMETIC 'i' -#define KSIG_SPEC_LIST 'l' -#define KSIG_SPEC_NODE 'n' -#define KSIG_SPEC_OBJECT 'o' -#define KSIG_SPEC_REF 'r' // Said Specs and strings -#define KSIG_SPEC_ARITHMETIC 'i' -#define KSIG_SPEC_NULL 'z' -#define KSIG_SPEC_ANY '.' -#define KSIG_SPEC_ELLIPSIS '*' // Arbitrarily more TYPED arguments - -#define KSIG_SPEC_SUM_DONE ('a' - 'A') // Use small letters to indicate end of sum type -/* Use capital letters for sum types, e.g. -** "LNoLr" for a function which takes two arguments: -** (1) list, node or object -** (2) list or ref -*/ - -// Compiled signatures -#define KSIG_LIST 0x01 -#define KSIG_NODE 0x02 -#define KSIG_OBJECT 0x04 -#define KSIG_REF 0x08 -#define KSIG_ARITHMETIC 0x10 - -#define KSIG_NULL 0x40 -#define KSIG_ANY 0x5f -#define KSIG_ELLIPSIS 0x80 + +// internal kernel signature data +enum { + SIG_TYPE_NULL = 0x01, // may be 0:0 [0] + SIG_TYPE_INTEGER = 0x02, // may be 0:* [i], automatically also allows null + SIG_TYPE_UNINITIALIZED = 0x04, // may be FFFF:* -> not allowable, only used for comparsion + SIG_TYPE_OBJECT = 0x08, // may be object [o] + SIG_TYPE_REFERENCE = 0x10, // may be reference [r] + SIG_TYPE_LIST = 0x20, // may be list [l] + SIG_TYPE_NODE = 0x40, // may be node [n] + SIG_TYPE_ERROR = 0x80, // happens, when there is a identification error - only used for comparsion + SIG_IS_INVALID = 0x100, // ptr is invalid [!] -> invalid offset + SIG_IS_OPTIONAL = 0x200, // is optional + SIG_NEEDS_MORE = 0x400, // needs at least one additional parameter following + SIG_MORE_MAY_FOLLOW = 0x800 // may have more parameters of the same type following +}; + +// this does not include SIG_TYPE_UNINITIALIZED, because we can not allow uninitialized values anywhere +#define SIG_MAYBE_ANY (SIG_TYPE_NULL | SIG_TYPE_INTEGER | SIG_TYPE_OBJECT | SIG_TYPE_REFERENCE | SIG_TYPE_LIST | SIG_TYPE_NODE) + // ---------------------------------------------------------------------------- /* Generic description: */ -typedef reg_t KernelFunc(EngineState *s, int argc, reg_t *argv); +typedef reg_t KernelFunctionCall(EngineState *s, int argc, reg_t *argv); + +struct SciWorkaroundEntry { + SciGameId gameId; + int scriptNr; + int16 inheritanceLevel; + const char *objectName; + const char *methodName; + int localCallOffset; + int index; + reg_t newValue; +}; + +#define SCI_WORKAROUNDENTRY_TERMINATOR { (SciGameId)0, -1, 0, NULL, NULL, -1, 0, { 0, 0 } } -struct KernelFuncWithSignature { - KernelFunc *fun; /**< The actual function */ - const char *signature; /**< KernelFunc signature */ - Common::String orig_name; /**< Original name, in case we couldn't map it */ - bool isDummy; +struct KernelSubFunction { + KernelFunctionCall *function; + const char *name; + uint16 *signature; + const SciWorkaroundEntry *workarounds; }; -enum AutoDetectedFeatures { - kFeatureOldScriptHeader = 1 << 0 +struct KernelFunction { + KernelFunctionCall *function; + const char *name; + uint16 *signature; + const SciWorkaroundEntry *workarounds; + const KernelSubFunction *subFunctions; + uint16 subFunctionCount; + bool debugCalls; }; class Kernel { public: /** - * Initializes the SCI kernel + * Initializes the SCI kernel. */ Kernel(ResourceManager *resMan, SegManager *segMan); ~Kernel(); @@ -156,18 +165,7 @@ public: const Common::String &getKernelName(uint number) const; /** - * Loads the kernel function names. - * - * This function reads the kernel function name table from resource_map, - * and fills the _kernelNames array with them. - * The resulting list has the same format regardless of the format of the - * name table of the resource (the format changed between version 0 and 1). - * @return true on success, false on failure - */ - bool loadKernelNames(Common::String gameId); - - /** - * Determines the selector ID of a selector by its name + * Determines the selector ID of a selector by its name. * @param selectorName Name of the selector to look up * @return The appropriate selector ID, or -1 on error */ @@ -178,22 +176,24 @@ public: void dumpScriptObject(char *data, int seeker, int objsize); void dumpScriptClass(char *data, int seeker, int objsize); - SelectorCache _selectorCache; /**< Shortcut list for important selectors */ - typedef Common::Array<KernelFuncWithSignature> KernelFuncsContainer; - KernelFuncsContainer _kernelFuncs; /**< Table of kernel functions */ + SelectorCache _selectorCache; /**< Shortcut list for important selectors. */ + typedef Common::Array<KernelFunction> KernelFunctionArray; + KernelFunctionArray _kernelFuncs; /**< Table of kernel functions. */ /** * Determines whether a list of registers matches a given signature. * If no signature is given (i.e., if sig is NULL), this is always * treated as a match. * - * @param segMan pointer to the segment manager * @param sig signature to test against * @param argc number of arguments to test * @param argv argument list * @return true if the signature was matched, false otherwise */ - bool signatureMatch(const char *sig, int argc, const reg_t *argv); + bool signatureMatch(const uint16 *sig, int argc, const reg_t *argv); + + // Prints out debug information in case a signature check fails + void signatureDebug(const uint16 *sig, int argc, const reg_t *argv); /** * Determines the type of the object indicated by reg. @@ -202,15 +202,15 @@ public: * KSIG_INVALID set if the type of reg can be determined, but is invalid. * 0 on error. */ - int findRegType(reg_t reg); + uint16 findRegType(reg_t reg); /******************** Text functionality ********************/ /** - * Looks up text referenced by scripts - * SCI uses two values to reference to text: An address, and an index. The address - * determines whether the text should be read from a resource file, or from the heap, - * while the index either refers to the number of the string in the specified source, - * or to a relative position inside the text. + * Looks up text referenced by scripts. + * SCI uses two values to reference to text: An address, and an index. The + * address determines whether the text should be read from a resource file, + * or from the heap, while the index either refers to the number of the + * string in the specified source, or to a relative position inside the text. * * @param address The address to look up * @param index The relative index @@ -218,22 +218,32 @@ public: */ Common::String lookupText(reg_t address, int index); + /** + * Loads the kernel function names. + * + * This function reads the kernel function name table from resource_map, + * and fills the _kernelNames array with them. + * The resulting list has the same format regardless of the format of the + * name table of the resource (the format changed between version 0 and 1). + */ + void loadKernelNames(GameFeatures *features); + private: /** - * Sets the default kernel function names, based on the SCI version used + * Sets the default kernel function names, based on the SCI version used. */ - void setDefaultKernelNames(Common::String gameId); + void setDefaultKernelNames(); #ifdef ENABLE_SCI32 /** - * Sets the default kernel function names to the SCI2 kernel functions + * 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 + * Sets the default kernel function names to the SCI2.1 kernel functions. */ - void setKernelNamesSci21(); + void setKernelNamesSci21(GameFeatures *features); #endif /** @@ -248,36 +258,26 @@ private: Common::StringArray checkStaticSelectorNames(); /** - * Maps special selectors + * Maps special selectors. */ void mapSelectors(); /** - * Maps kernel functions + * Maps kernel functions. */ void mapFunctions(); ResourceManager *_resMan; SegManager *_segMan; - uint32 features; // Kernel-related lists Common::StringArray _selectorNames; Common::StringArray _kernelNames; + + const Common::String _invalid; }; -#ifdef USE_OLD_MUSIC_FUNCTIONS -/******************** Misc functions ********************/ - -/** - * Get all sound events, apply their changes to the heap - */ -void process_sound_events(EngineState *s); - -/******************** Constants ********************/ -#endif - -/* Maximum length of a savegame name (including terminator character) */ +/* Maximum length of a savegame name (including terminator character). */ #define SCI_MAX_SAVENAME_LENGTH 0x24 /******************** Kernel functions ********************/ @@ -423,6 +423,7 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv); reg_t kPlatform(EngineState *s, int argc, reg_t *argv); reg_t kTextColors(EngineState *s, int argc, reg_t *argv); reg_t kTextFonts(EngineState *s, int argc, reg_t *argv); +reg_t kEmpty(EngineState *s, int argc, reg_t *argv); #ifdef ENABLE_SCI32 // SCI2 Kernel Functions @@ -430,6 +431,7 @@ reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv); reg_t kArray(EngineState *s, int argc, reg_t *argv); 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); // "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); @@ -451,12 +453,69 @@ reg_t kOnMe(EngineState *s, int argc, reg_t *argv); reg_t kInPolygon(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 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); +reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); +reg_t kCD(EngineState *s, int argc, reg_t *argv); #endif +reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundStopAll(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundPause(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundResume(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundUpdate(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundFade(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundReverb(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv); +reg_t kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv); + +reg_t kGraphGetColorCount(EngineState *s, int argc, reg_t *argv); +reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv); +reg_t kGraphSaveBox(EngineState *s, int argc, reg_t *argv); +reg_t kGraphRestoreBox(EngineState *s, int argc, reg_t *argv); +reg_t kGraphFillBoxBackground(EngineState *s, int argc, reg_t *argv); +reg_t kGraphFillBoxForeground(EngineState *s, int argc, reg_t *argv); +reg_t kGraphFillBoxAny(EngineState *s, int argc, reg_t *argv); +reg_t kGraphUpdateBox(EngineState *s, int argc, reg_t *argv); +reg_t kGraphRedrawBox(EngineState *s, int argc, reg_t *argv); +reg_t kGraphAdjustPriority(EngineState *s, int argc, reg_t *argv); +reg_t kGraphSaveUpscaledHiresBox(EngineState *s, int argc, reg_t *argv); + +reg_t kPalVaryInit(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryReverse(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryGetCurrentStep(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryDeinit(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryChangeTarget(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryChangeTicks(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryPauseResume(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv); + +reg_t kPaletteSetFromResource(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteSetFlag(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteUnsetFlag(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteSetIntensity(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteFindColor(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteSave(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteRestore(EngineState *s, int argc, reg_t *argv); + //@} } // End of namespace Sci diff --git a/engines/sci/engine/kernel32.cpp b/engines/sci/engine/kernel32.cpp index 0afdc3f2eb..eab1b90139 100644 --- a/engines/sci/engine/kernel32.cpp +++ b/engines/sci/engine/kernel32.cpp @@ -30,9 +30,8 @@ #include "sci/engine/segment.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" -#include "sci/graphics/gui.h" -#include "sci/graphics/gui32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/screen.h" #include "common/system.h" @@ -337,7 +336,7 @@ static const char *sci21_default_knames[] = { /*0x80*/ "Dummy", /*0x81*/ "Dummy", /*0x82*/ "Dummy", - /*0x83*/ "Dummy", + /*0x83*/ "PrintDebug", // used by the Shivers 2 demo /*0x84*/ "Dummy", /*0x85*/ "Dummy", /*0x86*/ "Dummy", @@ -378,15 +377,17 @@ void Kernel::setKernelNamesSci2() { _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2); } -void Kernel::setKernelNamesSci21() { - // Some SCI games use a modified SCI2 kernel table instead of the SCI2.1/SCI3 kernel table. - // The GK2 demo does this as well as at least one version of KQ7. We detect which version - // to use based on where kDoSound is called from Sound::play(). +void Kernel::setKernelNamesSci21(GameFeatures *features) { + // Some SCI games use a modified SCI2 kernel table instead of the + // SCI2.1/SCI3 kernel table. The GK2 demo does this as well as at + // least one version of KQ7. We detect which version to use based on + // where kDoSound is called from Sound::play(). - // 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. + // 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 (g_sci->_features->detectSci21KernelType() == SCI_VERSION_2) { + if (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 @@ -400,7 +401,8 @@ void Kernel::setKernelNamesSci21() { // SCI2 Kernel Functions 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. + // 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); @@ -461,6 +463,13 @@ reg_t kArray(EngineState *s, int argc, reg_t *argv) { return argv[1]; } case 6: { // Cpy + if (s->_segMan->getSegmentObj(argv[1].segment)->getType() != SEG_TYPE_ARRAY || + s->_segMan->getSegmentObj(argv[3].segment)->getType() != SEG_TYPE_ARRAY) { + // Happens in the RAMA demo + warning("kArray(Cpy): Request to copy a segment which isn't an array, ignoring"); + return NULL_REG; + } + SciArray<reg_t> *array1 = s->_segMan->lookupArray(argv[1]); SciArray<reg_t> *array2 = s->_segMan->lookupArray(argv[3]); uint32 index1 = argv[2].toUint16(); @@ -509,6 +518,19 @@ reg_t kArray(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +reg_t kText(EngineState *s, int argc, reg_t *argv) { + switch (argv[0].toUint16()) { + case 0: + return kTextSize(s, argc - 1, argv + 1); + default: + // TODO: Other subops here too, perhaps kTextColors and kTextFonts + warning("kText(%d)", argv[0].toUint16()); + break; + } + + return s->r_acc; +} + reg_t kString(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: { // New @@ -594,21 +616,24 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) { if (argv[1].segment == s->_segMan->getSysStringsSegment()) { // Resize if necessary const uint16 sysStringId = argv[1].toUint16(); - if ((uint32)s->_segMan->sysStrings->_strings[sysStringId]._maxSize < index1 + count) { - free(s->_segMan->sysStrings->_strings[sysStringId]._value); - s->_segMan->sysStrings->_strings[sysStringId]._maxSize = index1 + count; - s->_segMan->sysStrings->_strings[sysStringId]._value = (char *)calloc(index1 + count, sizeof(char)); + SystemString *sysString = s->_segMan->getSystemString(sysStringId); + assert(sysString); + if ((uint32)sysString->_maxSize < index1 + count) { + free(sysString->_value); + sysString->_maxSize = index1 + count; + sysString->_value = (char *)calloc(index1 + count, sizeof(char)); } - strncpy(s->_segMan->sysStrings->_strings[sysStringId]._value + index1, string2 + index2, count); + strncpy(sysString->_value + index1, string2 + index2, count); } else { SciString *string1 = s->_segMan->lookupString(argv[1]); if (string1->getSize() < index1 + count) string1->setSize(index1 + count); - // Note: We're accessing from c_str() here because the string's size ignores - // the trailing 0 and therefore triggers an assert when doing string2[i + index2]. + // Note: We're accessing from c_str() here because the + // string's size ignores the trailing 0 and therefore + // triggers an assert when doing string2[i + index2]. for (uint16 i = 0; i < count; i++) string1->setValue(i + index1, string2[i + index2]); } @@ -791,8 +816,76 @@ reg_t kOnMe(EngineState *s, int argc, reg_t *argv) { nsRect.top = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsTop)); nsRect.right = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsRight)); nsRect.bottom = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsBottom)); + uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); + uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); + + // If top and left are negative, we need to adjust coordinates by + // the item's x and y (e.g. happens in GK1, day 1, with detective + // Mosely's hotspot in his office) - //warning("kOnMe: (%d, %d) on object %04x:%04x, parameter %d", argv[0].toUint16(), argv[1].toUint16(), PRINT_REG(argv[2]), argv[3].toUint16()); + if (nsRect.left < 0) + nsRect.translate(itemX, 0); + + if (nsRect.top < 0) + nsRect.translate(0, itemY); + + // HACK: nsLeft and nsTop can be invalid, so try and fix them here + // using x and y (e.g. with the inventory screen in GK1) + if (nsRect.left == itemY && nsRect.top == itemX) { + // Swap the values, as they're inversed (eh???) + nsRect.left = itemX; + nsRect.top = itemY; + } + + /* + warning("kOnMe: (%d, %d) on object %04x:%04x (%s), rect (%d, %d, %d, %d), parameter %d", + argv[0].toUint16(), argv[1].toUint16(), PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2]), + nsRect.left, nsRect.top, nsRect.right, nsRect.bottom, + argv[3].toUint16()); + */ + + return make_reg(0, nsRect.contains(x, y)); +} + +reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { + // Tests if the cursor is on the passed object, after adjusting the + // coordinates of the object according to the object's plane + + uint16 x = argv[0].toUint16(); + uint16 y = argv[1].toUint16(); + reg_t targetObject = argv[2]; + // TODO: argv[3] - it's usually 0 + Common::Rect nsRect; + + // Get the bounding rectangle of the object + nsRect.left = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsLeft)); + nsRect.top = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsTop)); + nsRect.right = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsRight)); + nsRect.bottom = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsBottom)); + + // Get the object's plane + reg_t planeObject = readSelector(s->_segMan, targetObject, SELECTOR(plane)); + if (!planeObject.isNull()) { + uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); + uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); + uint16 planeResY = readSelectorValue(s->_segMan, planeObject, SELECTOR(resY)); + uint16 planeResX = readSelectorValue(s->_segMan, planeObject, SELECTOR(resX)); + uint16 planeTop = readSelectorValue(s->_segMan, planeObject, SELECTOR(top)); + uint16 planeLeft = readSelectorValue(s->_segMan, planeObject, SELECTOR(left)); + planeTop = (planeTop * g_sci->_gfxScreen->getHeight()) / planeResY; + planeLeft = (planeLeft * g_sci->_gfxScreen->getWidth()) / planeResX; + + // Adjust the bounding rectangle of the object by the object's + // actual X, Y coordinates + itemY = ((itemY * g_sci->_gfxScreen->getHeight()) / planeResY); + itemX = ((itemX * g_sci->_gfxScreen->getWidth()) / planeResX); + itemY += planeTop; + itemX += planeLeft; + + nsRect.translate(itemX, itemY); + } + + //warning("kIsOnMe: (%d, %d) on object %04x:%04x, parameter %d", argv[0].toUint16(), argv[1].toUint16(), PRINT_REG(argv[2]), argv[3].toUint16()); return make_reg(0, nsRect.contains(x, y)); } @@ -805,7 +898,7 @@ reg_t kInPolygon(EngineState *s, int argc, reg_t *argv) { reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { // TODO: argument 0 is usually 0, and arguments 1 and 2 are usually 1 switch (argv[0].toUint16()) { - case 0: + case 0: { if (argc != 4) { warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); return NULL_REG; @@ -814,6 +907,22 @@ reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); debug("kCreateTextBitmap: %s", text.c_str()); } + default: + warning("CreateTextBitmap(%d)", argv[0].toUint16()); + } + + return NULL_REG; +} + +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()); + } return NULL_REG; } diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index fd7711f196..7547ad5ab6 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -31,8 +31,6 @@ #include "sci/console.h" #include "sci/debug.h" // for g_debug_simulated_key #include "sci/event.h" -#include "sci/graphics/gui.h" -#include "sci/graphics/gui32.h" #include "sci/graphics/coordadjuster.h" #include "sci/graphics/cursor.h" @@ -43,7 +41,7 @@ namespace Sci { reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { int mask = argv[0].toUint16(); reg_t obj = argv[1]; - sciEvent curEvent; + SciEvent curEvent; int oldx, oldy; int modifier_mask = getSciVersion() <= SCI_VERSION_01 ? SCI_KEYMOD_ALL : SCI_KEYMOD_NO_FOOLOCK; SegManager *segMan = s->_segMan; @@ -67,7 +65,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { oldx = mousePos.x; oldy = mousePos.y; - curEvent = s->_event->get(mask); + curEvent = g_sci->getEventManager()->getSciEvent(mask); if (g_sci->getVocabulary()) g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event @@ -79,7 +77,9 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { switch (curEvent.type) { case SCI_EVENT_QUIT: - quit_vm(s); + s->abortScriptProcessing = kAbortQuitGame; // Terminate VM + g_sci->_debugState.seeking = kDebugSeekNothing; + g_sci->_debugState.runningStep = 0; break; case SCI_EVENT_KEYBOARD: @@ -124,12 +124,12 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { s->r_acc = NULL_REG; // Unknown or no event } - if ((s->r_acc.offset) && (g_debugState.stopOnEvent)) { - g_debugState.stopOnEvent = false; + if ((s->r_acc.offset) && (g_sci->_debugState.stopOnEvent)) { + g_sci->_debugState.stopOnEvent = false; - // A SCI event occured, and we have been asked to stop, so open the debug console + // A SCI event occurred, and we have been asked to stop, so open the debug console Console *con = g_sci->getSciDebugger(); - con->DebugPrintf("SCI event occured: "); + con->DebugPrintf("SCI event occurred: "); switch (curEvent.type) { case SCI_EVENT_QUIT: con->DebugPrintf("quit event\n"); @@ -149,16 +149,14 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { con->onFrame(); } -#ifndef USE_OLD_MUSIC_FUNCTIONS if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) { // If we're running a SCI0 game, update the sound cues, to compensate // for the fact that SCI0 does not poll to update the sound cues itself, // like SCI01 and later do with cmdUpdateSoundCues. kGetEvent is called // quite often, so emulate the SCI01 behavior of cmdUpdateSoundCues with // this call - s->_soundCmd->updateSci0Cues(); + g_sci->_soundCmd->updateSci0Cues(); } -#endif return s->r_acc; } @@ -203,7 +201,10 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { } if (mover >= 0) { - writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_JOYSTICK); + if (g_sci->getEventManager()->getUsesNewKeyboardDirectionType()) + writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_KEYBOARD | SCI_EVENT_DIRECTION); + else + writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_DIRECTION); writeSelectorValue(segMan, obj, SELECTOR(message), mover); return make_reg(0, 1); } else diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 3e0ecd1a28..807edc63a5 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -36,12 +36,9 @@ namespace Sci { -enum { - MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ -}; - struct SavegameDesc { - int id; + uint id; + int virtualId; // straight numbered, according to id but w/o gaps int date; int time; int version; @@ -115,15 +112,17 @@ void file_open(EngineState *s, const char *filename, int mode) { 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 no matching savestate exists: fall back to reading from a regular + // file if (!inFile) inFile = SearchMan.createReadStreamForMember(englishName); - // Special case for LSL3: It tries to create a new dummy file, LARRY3.DRV - // Apparently, if the file doesn't exist here, it should be created. The game - // scripts then go ahead and fill its contents with data. It seems to be a similar - // case as the dummy MEMORY.DRV file in LSL5, but LSL5 creates the file if it can't - // find it with a separate call to file_open() + // Special case for LSL3: It tries to create a new dummy file, + // LARRY3.DRV. Apparently, if the file doesn't exist here, it should be + // created. The game scripts then go ahead and fill its contents with + // data. It seems to be a similar case as the dummy MEMORY.DRV file in + // LSL5, but LSL5 creates the file if it can't find it with a separate + // call to file_open(). if (!inFile && englishName == "LARRY3.DRV") { outFile = saveFileMan->openForSaving(wrappedName); outFile->finalize(); @@ -133,27 +132,28 @@ void file_open(EngineState *s, const char *filename, int mode) { } if (!inFile) - warning("file_open(_K_FILE_MODE_OPEN_OR_FAIL) failed to open file '%s'", englishName.c_str()); + warning(" -> 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) - warning("file_open(_K_FILE_MODE_CREATE) failed to create file '%s'", englishName.c_str()); + warning(" -> 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) - warning("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 + warning(" -> 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 - debug(3, "file_open() failed"); + debugC(2, kDebugLevelFile, " -> file_open() failed"); s->r_acc = SIGNAL_REG; return; } @@ -163,7 +163,8 @@ void file_open(EngineState *s, const char *filename, int mode) { while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen()) handle++; - if (handle == s->_fileHandles.size()) { // Hit size limit => Allocate more space + if (handle == s->_fileHandles.size()) { + // Hit size limit => Allocate more space s->_fileHandles.resize(s->_fileHandles.size() + 1); } @@ -173,14 +174,14 @@ void file_open(EngineState *s, const char *filename, int mode) { s->r_acc = make_reg(0, handle); - debug(3, " -> opened file '%s' with handle %d", englishName.c_str(), handle); + debugC(2, kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle); } reg_t kFOpen(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); int mode = argv[1].toUint16(); - debug(3, "kFOpen(%s,0x%x)", name.c_str(), mode); + debugC(2, kDebugLevelFile, "kFOpen(%s,0x%x)", name.c_str(), mode); file_open(s, name.c_str(), mode); return s->r_acc; } @@ -200,7 +201,7 @@ static FileHandle *getFileFromHandle(EngineState *s, uint handle) { } reg_t kFClose(EngineState *s, int argc, reg_t *argv) { - debug(3, "kFClose(%d)", argv[0].toUint16()); + debugC(2, kDebugLevelFile, "kFClose(%d)", argv[0].toUint16()); if (argv[0] != SIGNAL_REG) { FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); if (f) @@ -221,8 +222,6 @@ reg_t kFPuts(EngineState *s, int argc, reg_t *argv) { } static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) { - debugC(2, kDebugLevelFile, "FGets'ing %d bytes from handle %d", maxsize, handle); - FileHandle *f = getFileFromHandle(s, handle); if (!f) return; @@ -244,66 +243,7 @@ static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) { *dest = f->_in->readByte(); } - debugC(2, kDebugLevelFile, "FGets'ed \"%s\"", dest); -} - -static bool _savegame_index_struct_compare(const SavegameDesc &l, const SavegameDesc &r) { - if (l.date != r.date) - return (l.date > r.date); - return (l.time > r.time); -} - -void listSavegames(Common::Array<SavegameDesc> &saves) { - Common::SaveFileManager *saveFileMan = g_engine->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.savegame_name.empty()) { - // invalid - delete in; - continue; - } - delete in; - - SavegameDesc desc; - desc.id = strtol(filename.end() - 3, NULL, 10); - desc.date = meta.savegame_date; - // We need to fix date in here, because we save DDMMYYYY instead of YYYYMMDD, so sorting wouldnt work - desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24); - desc.time = meta.savegame_time; - desc.version = meta.savegame_version; - - if (meta.savegame_name.lastChar() == '\n') - meta.savegame_name.deleteLastChar(); - - Common::strlcpy(desc.name, meta.savegame_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_index_struct_compare); -} - -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; + debugC(2, kDebugLevelFile, " -> FGets'ed \"%s\"", dest); } reg_t kFGets(EngineState *s, int argc, reg_t *argv) { @@ -311,7 +251,7 @@ reg_t kFGets(EngineState *s, int argc, reg_t *argv) { char *buf = new char[maxsize]; int handle = argv[2].toUint16(); - debug(3, "kFGets(%d,%d)", handle, maxsize); + debugC(2, kDebugLevelFile, "kFGets(%d, %d)", handle, maxsize); fgets_wrapper(s, buf, maxsize, handle); s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); return argv[0]; @@ -326,11 +266,14 @@ reg_t kGetCWD(EngineState *s, int argc, reg_t *argv) { // TODO/FIXME: Is "/" a good value? Maybe "" or "." or "C:\" are better? s->_segMan->strcpy(argv[0], "/"); - debug(3, "kGetCWD() -> %s", "/"); + debugC(2, kDebugLevelFile, "kGetCWD() -> %s", "/"); return argv[0]; } +static void listSavegames(Common::Array<SavegameDesc> &saves); +static int findSavegame(Common::Array<SavegameDesc> &saves, uint saveId); + enum { K_DEVICE_INFO_GET_DEVICE = 0, K_DEVICE_INFO_GET_CURRENT_DEVICE = 1, @@ -389,21 +332,25 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) { break; case K_DEVICE_INFO_GET_SAVEFILE_NAME: { Common::String game_prefix = s->_segMan->getString(argv[2]); - int savegame_id = argv[3].toUint16(); + uint virtualId = argv[3].toUint16(); s->_segMan->strcpy(argv[1], "__throwaway"); - debug(3, "K_DEVICE_INFO_GET_SAVEFILE_NAME(%s,%d) -> %s", game_prefix.c_str(), savegame_id, "__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"); + uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; Common::Array<SavegameDesc> saves; listSavegames(saves); - int savedir_nr = saves[savegame_id].id; - Common::String filename = g_sci->getSavegameName(savedir_nr); - Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); - saveFileMan->removeSavefile(filename); + if (findSavegame(saves, savegameId) != -1) { + // Confirmed that this id still lives... + Common::String filename = g_sci->getSavegameName(savegameId); + Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); + saveFileMan->removeSavefile(filename); } break; + } default: - // TODO: Not all sub-commands are handled. E.g. KQ5CD calls sub-command 5 - warning("Unknown DeviceInfo() sub-command: %d", mode); + error("Unknown DeviceInfo() sub-command: %d", mode); break; } @@ -430,27 +377,108 @@ reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { Common::String path = s->_segMan->getString(argv[0]); debug(3, "kCheckFreeSpace(%s)", path.c_str()); - // We simply always pretend that there is enough space. - // The alternative would be to write a big test file, which is not nice - // on systems where doing so is very slow. + // We simply always pretend that there is enough space. The alternative + // would be to write a big test file, which is not nice on systems where + // doing so is very slow. 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_engine->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.savegame_name.empty()) { + // invalid + delete in; + continue; + } + delete in; + + SavegameDesc desc; + desc.id = strtol(filename.end() - 3, NULL, 10); + desc.date = meta.savegame_date; + // 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.savegame_time; + desc.version = meta.savegame_version; + + if (meta.savegame_name.lastChar() == '\n') + meta.savegame_name.deleteLastChar(); + + Common::strlcpy(desc.name, meta.savegame_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, uint savegameId) { + for (uint saveNr = 0; saveNr < saves.size(); saveNr++) { + if (saves[saveNr].id == savegameId) + return saveNr; + } + return -1; +} + +// The scripts get IDs ranging from 1000->1999, 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 behaviour 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 savedir_nr = argv[1].toUint16(); + uint16 virtualId = argv[1].toUint16(); - debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), savedir_nr); + debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), virtualId); Common::Array<SavegameDesc> saves; listSavegames(saves); - // Check for savegame slot being out of range - if (savedir_nr >= saves.size()) + // 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[savedir_nr].version; + int ver = saves[savegameNr].version; if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION) return NULL_REG; @@ -463,9 +491,12 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { 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); @@ -480,7 +511,7 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { char *saveNamePtr = saveNames; for (uint i = 0; i < totalSaves; i++) { - *slot++ = make_reg(0, i); // Store slot + *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; } @@ -495,46 +526,51 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { Common::String game_id = s->_segMan->getString(argv[0]); - int savedir_nr = argv[1].toUint16(); - int savedir_id; // Savegame ID, derived from savedir_nr and the savegame ID list + uint virtualId = argv[1].toUint16(); Common::String game_description = s->_segMan->getString(argv[2]); Common::String version; if (argc > 3) version = s->_segMan->getString(argv[3]); - debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), savedir_nr, game_description.c_str(), version.c_str()); + 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 (savedir_nr >= 0 && (uint)savedir_nr < saves.size()) { - // Overwrite - savedir_id = saves[savedir_nr].id; - } else if (savedir_nr >= 0 && savedir_nr < MAX_SAVEGAME_NR) { - uint i = 0; - - savedir_id = 0; - - // First, look for holes - while (i < saves.size()) { - if (saves[i].id == savedir_id) { - ++savedir_id; - i = 0; - } else - ++i; - } - if (savedir_id >= MAX_SAVEGAME_NR) { - warning("Internal error: Free savegame ID is %d, shouldn't happen", savedir_id); + uint savegameId; + 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"); } - - // This loop terminates when savedir_id is not in [x | ex. n. saves [n].id = x] } else { - warning("Savegame ID %d is not allowed", savedir_nr); - return NULL_REG; + error("kSaveGame: invalid savegameId used"); } - Common::String filename = g_sci->getSavegameName(savedir_id); + // Save in case caller wants to overwrite last newly created save + s->_lastSaveVirtualId = virtualId; + s->_lastSaveNewId = savegameId; + + Common::String filename = g_sci->getSavegameName(savegameId); Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); Common::OutSaveFile *out; if (!(out = saveFileMan->openForSaving(filename))) { @@ -543,7 +579,7 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } - if (gamestate_save(s, out, game_description.c_str(), version.c_str())) { + if (!gamestate_save(s, out, game_description.c_str(), version.c_str())) { warning("Saving the game failed."); s->r_acc = NULL_REG; } else { @@ -563,35 +599,37 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : ""; - int savedir_nr = argv[1].toUint16(); + uint savegameId = argv[1].toUint16(); - debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savedir_nr); + debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); - if (!argv[0].isNull()) { - Common::Array<SavegameDesc> saves; - listSavegames(saves); + if ((savegameId < 1000) || (savegameId > 1999)) { + warning("Savegame ID %d is not allowed", savegameId); + return TRUE_REG; + } + savegameId -= 1000; - savedir_nr = saves[savedir_nr].id; - } else { - // Loading from launcher, no change necessary + Common::Array<SavegameDesc> saves; + listSavegames(saves); + if (findSavegame(saves, savegameId) == -1) { + warning("Savegame ID %d not found", savegameId); + return TRUE_REG; } - if (savedir_nr > -1) { - Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); - Common::String filename = g_sci->getSavegameName(savedir_nr); - Common::SeekableReadStream *in; - if ((in = saveFileMan->openForLoading(filename))) { - // found a savegame file + Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); + Common::String filename = g_sci->getSavegameName(savegameId); + Common::SeekableReadStream *in; + if ((in = saveFileMan->openForLoading(filename))) { + // found a savegame file - gamestate_restore(s, in); - delete in; + gamestate_restore(s, in); + delete in; - return s->r_acc; - } + return s->r_acc; } - s->r_acc = make_reg(0, 1); - warning("Savegame #%d not found", savedir_nr); + s->r_acc = TRUE_REG; + warning("Savegame #%d not found", savegameId); return s->r_acc; } @@ -642,7 +680,8 @@ reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); _savefiles = saveFileMan->listSavefiles(wrappedMask); - // Reset the list iterator and write the first match to the output buffer, if any. + // Reset the list iterator and write the first match to the output buffer, + // if any. _iter = _savefiles.begin(); return nextFile(segMan); } @@ -672,7 +711,8 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { case K_FILEIO_OPEN : { Common::String name = s->_segMan->getString(argv[1]); - // SCI32 can call K_FILEIO_OPEN with only two arguments. It seems to just be checking if it exists. + // SCI32 can call K_FILEIO_OPEN with only two arguments. It seems to + // just be checking if it exists. int mode = (argc < 3) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[2].toUint16(); // SQ4 floppy prepends /\ to the filenames @@ -681,10 +721,10 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { name.deleteChar(0); } - // SQ4 floppy attempts to update the savegame index file sq4sg.dir - // when deleting saved games. We don't use an index file for saving - // or loading, so just stop the game from modifying the file here - // in order to avoid having it saved in the ScummVM save directory + // SQ4 floppy attempts to update the savegame index file sq4sg.dir when + // deleting saved games. We don't use an index file for saving or + // loading, so just stop the game from modifying the file here in order + // to avoid having it saved in the ScummVM save directory. if (name == "sq4sg.dir") { debugC(2, kDebugLevelFile, "Not opening unused file sq4sg.dir"); return SIGNAL_REG; @@ -694,12 +734,12 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { warning("Attempted to open a file with an empty filename"); return SIGNAL_REG; } + debugC(2, kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); file_open(s, name.c_str(), mode); - debug(3, "K_FILEIO_OPEN(%s,0x%x)", name.c_str(), mode); break; } case K_FILEIO_CLOSE : { - debug(3, "K_FILEIO_CLOSE(%d)", argv[1].toUint16()); + debugC(2, kDebugLevelFile, "kFileIO(close): %d", argv[1].toUint16()); FileHandle *f = getFileFromHandle(s, argv[1].toUint16()); if (f) @@ -710,8 +750,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { int handle = argv[1].toUint16(); int size = argv[3].toUint16(); char *buf = new char[size]; - debug(3, "K_FILEIO_READ_RAW(%d,%d)", handle, size); - + debugC(2, kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size); FileHandle *f = getFileFromHandle(s, handle); if (f) { @@ -727,7 +766,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { int size = argv[3].toUint16(); char *buf = new char[size]; s->_segMan->memcpy((byte*)buf, argv[2], size); - debug(3, "K_FILEIO_WRITE_RAW(%d,%d)", handle, size); + debugC(2, kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size); FileHandle *f = getFileFromHandle(s, handle); if (f) @@ -745,11 +784,13 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { name.deleteChar(0); } - // Special case for SQ4 floppy: This game has hardcoded names for all of its - // savegames, and they are all named "sq4sg.xxx", where xxx is the slot. We just - // take the slot number here, and delete the appropriate save game + // Special case for SQ4 floppy: This game has hardcoded names for all of + // its savegames, and they are all named "sq4sg.xxx", where xxx is the + // slot. We just take the slot number here, and delete the appropriate + // save game. if (name.hasPrefix("sq4sg.")) { - // Special handling for SQ4... get the slot number and construct the save game name + // Special handling for SQ4... get the slot number and construct the + // save game name. int slotNum = atoi(name.c_str() + name.size() - 3); Common::Array<SavegameDesc> saves; listSavegames(saves); @@ -761,7 +802,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { saveFileMan->removeSavefile(wrappedName); } - debug(3, "K_FILEIO_UNLINK(%s)", name.c_str()); + debugC(2, kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); // TODO/FIXME: Should we return something (like, a bool indicating // whether deleting the save succeeded or failed)? @@ -771,7 +812,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { int size = argv[2].toUint16(); char *buf = new char[size]; int handle = argv[3].toUint16(); - debug(3, "K_FILEIO_READ_STRING(%d,%d)", handle, size); + debugC(2, kDebugLevelFile, "kFileIO(readString): %d, %d", handle, size); fgets_wrapper(s, buf, size, handle); s->_segMan->memcpy(argv[1], (const byte*)buf, size); @@ -782,7 +823,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { int handle = argv[1].toUint16(); int size = argv[3].toUint16(); Common::String str = s->_segMan->getString(argv[2]); - debug(3, "K_FILEIO_WRITE_STRING(%d,%d)", handle, size); + debugC(2, kDebugLevelFile, "kFileIO(writeString): %d, %d", handle, size); // CHECKME: Is the size parameter used at all? // In the LSL5 password protection it is zero, and we should @@ -798,7 +839,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { int handle = argv[1].toUint16(); int offset = argv[2].toUint16(); int whence = argv[3].toUint16(); - debug(3, "K_FILEIO_SEEK(%d,%d,%d)", handle, offset, whence); + debugC(2, kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); FileHandle *f = getFileFromHandle(s, handle); if (f) @@ -809,7 +850,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { Common::String mask = s->_segMan->getString(argv[1]); reg_t buf = argv[2]; int attr = argv[3].toUint16(); // We won't use this, Win32 might, though... - debug(3, "K_FILEIO_FIND_FIRST(%s,0x%x)", mask.c_str(), attr); + debugC(2, kDebugLevelFile, "kFileIO(findFirst): %s, 0x%x", mask.c_str(), attr); // We remove ".*". mask will get prefixed, so we will return all additional files for that gameid if (mask == "*.*") @@ -827,7 +868,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { break; } case K_FILEIO_FIND_NEXT : { - debug(3, "K_FILEIO_FIND_NEXT()"); + debugC(2, kDebugLevelFile, "kFileIO(findNext)"); s->r_acc = s->_dirseeker.nextFile(s->_segMan); break; } @@ -849,14 +890,17 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { delete inFile; } - // Special case for non-English versions of LSL5: The English version of LSL5 calls - // kFileIO(), case K_FILEIO_OPEN for reading to check if memory.drv exists (which is - // where the game's password is stored). If it's not found, it calls kFileIO() again, - // case K_FILEIO_OPEN for writing and creates a new file. Non-English versions call - // kFileIO(), case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be found. - // We create a default memory.drv file with no password, so that the game can continue + // Special case for non-English versions of LSL5: The English version of + // LSL5 calls kFileIO(), case K_FILEIO_OPEN for reading to check if + // memory.drv exists (which is where the game's password is stored). If + // it's not found, it calls kFileIO() again, case K_FILEIO_OPEN for + // writing and creates a new file. Non-English versions call kFileIO(), + // case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be + // found. We create a default memory.drv file with no password, so that + // the game can continue. if (!exists && name == "memory.drv") { - // Create a new file, and write the bytes for the empty password string inside + // Create a new file, and write the bytes for the empty password + // string inside byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 }; Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName); for (int i = 0; i < 10; i++) @@ -866,15 +910,15 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { exists = true; } - debug(3, "K_FILEIO_FILE_EXISTS(%s) -> %d", name.c_str(), exists); + debugC(2, kDebugLevelFile, "kFileIO(fileExists) %s -> %d", name.c_str(), exists); return make_reg(0, exists); } case K_FILEIO_RENAME: { Common::String oldName = s->_segMan->getString(argv[1]); Common::String newName = s->_segMan->getString(argv[2]); - // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 returns -1 on fail. - // We just return -1 for all versions. + // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 + // returns -1 on fail. We just return -1 for all versions. if (g_engine->getSaveFileManager()->renameSavefile(oldName, newName)) return NULL_REG; else @@ -914,7 +958,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { break; #endif default: - error("Unknown FileIO() sub-command: %d", func_nr); + error("kFileIO(): unknown sub-command: %d", func_nr); } return s->r_acc; diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index abe55455de..29f7565ef9 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -37,8 +37,6 @@ #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/engine/kernel.h" -#include "sci/graphics/gui.h" -#include "sci/graphics/gui32.h" #include "sci/graphics/animate.h" #include "sci/graphics/cache.h" #include "sci/graphics/compare.h" @@ -48,7 +46,11 @@ #include "sci/graphics/paint16.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/video/vmd_decoder.h" +#endif namespace Sci { @@ -171,7 +173,7 @@ static reg_t kSetCursorSci11(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxCursor->kernelSetView(argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), hotspot); break; default : - warning("kSetCursor: Unhandled case: %d arguments given", argc); + error("kSetCursor: Unhandled case: %d arguments given", argc); break; } return s->r_acc; @@ -184,7 +186,7 @@ reg_t kSetCursor(EngineState *s, int argc, reg_t *argv) { case SCI_VERSION_1_1: return kSetCursorSci11(s, argc, argv); default: - warning("Unknown SetCursor type"); + error("Unknown SetCursor type"); return NULL_REG; } } @@ -205,121 +207,108 @@ reg_t kPicNotValid(EngineState *s, int argc, reg_t *argv) { return make_reg(0, g_sci->_gfxScreen->kernelPicNotValid(newPicNotValid)); } -Common::Rect kGraphCreateRect(int16 x, int16 y, int16 x1, int16 y1) { +static Common::Rect getGraphRect(reg_t *argv) { + int16 x = argv[1].toSint16(); + int16 y = argv[0].toSint16(); + int16 x1 = argv[3].toSint16(); + int16 y1 = argv[2].toSint16(); if (x > x1) SWAP(x, x1); if (y > y1) SWAP(y, y1); return Common::Rect(x, y, x1, y1); } -// Graph subfunctions -enum { - K_GRAPH_GET_COLORS_NR = 2, - // 3 - SET PALETTE VIA RESOURCE - K_GRAPH_DRAW_LINE = 4, - // 5 - NOP - // 6 - DRAW PATTERN - K_GRAPH_SAVE_BOX = 7, - K_GRAPH_RESTORE_BOX = 8, - K_GRAPH_FILL_BOX_BACKGROUND = 9, - K_GRAPH_FILL_BOX_FOREGROUND = 10, - K_GRAPH_FILL_BOX_ANY = 11, - K_GRAPH_UPDATE_BOX = 12, - K_GRAPH_REDRAW_BOX = 13, - K_GRAPH_ADJUST_PRIORITY = 14, - K_GRAPH_SAVE_UPSCALEDHIRES_BOX = 15 // KQ6CD Windows version -}; +static Common::Point getGraphPoint(reg_t *argv) { + int16 x = argv[1].toSint16(); + int16 y = argv[0].toSint16(); + return Common::Point(x, y); +} reg_t kGraph(EngineState *s, int argc, reg_t *argv) { - int16 x = 0, y = 0, x1 = 0, y1 = 0; - uint16 screenMask; - int16 priority, control, color, colorMask; - Common::Rect rect; - - if (argc >= 5) { - x = argv[2].toSint16(); - y = argv[1].toSint16(); - x1 = argv[4].toSint16(); - y1 = argv[3].toSint16(); - } - - switch (argv[0].toSint16()) { - case K_GRAPH_GET_COLORS_NR: - if (g_sci->getResMan()->isAmiga32color()) - return make_reg(0, 32); - return make_reg(0, !g_sci->getResMan()->isVGA() ? 16 : 256); - - case K_GRAPH_DRAW_LINE: - priority = (argc > 6) ? argv[6].toSint16() : -1; - control = (argc > 7) ? argv[7].toSint16() : -1; - color = argv[5].toSint16(); - - // TODO: Find out why we get >15 for color in EGA - if (!g_sci->getResMan()->isVGA() && !g_sci->getResMan()->isAmiga32color()) - color &= 0x0F; + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - g_sci->_gfxPaint16->kernelGraphDrawLine(Common::Point(x, y), Common::Point(x1, y1), color, priority, control); - break; +reg_t kGraphGetColorCount(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isAmiga32color()) + return make_reg(0, 32); + return make_reg(0, !g_sci->getResMan()->isVGA() ? 16 : 256); +} - case K_GRAPH_SAVE_BOX: - rect = kGraphCreateRect(x, y, x1, y1); - screenMask = (argc > 5) ? argv[5].toUint16() : 0; - return g_sci->_gfxPaint16->kernelGraphSaveBox(rect, screenMask); +reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv) { + int16 color = argv[4].toSint16(); + int16 priority = (argc > 5) ? argv[5].toSint16() : -1; + int16 control = (argc > 6) ? argv[6].toSint16() : -1; - case K_GRAPH_RESTORE_BOX: - // This may be called with a memoryhandle from SAVE_BOX or SAVE_UPSCALEDHIRES_BOX - g_sci->_gfxPaint16->kernelGraphRestoreBox(argv[1]); - break; + // TODO: Find out why we get >15 for color in EGA + if (!g_sci->getResMan()->isVGA() && !g_sci->getResMan()->isAmiga32color()) + color &= 0x0F; - case K_GRAPH_FILL_BOX_BACKGROUND: - rect = kGraphCreateRect(x, y, x1, y1); - g_sci->_gfxPaint16->kernelGraphFillBoxBackground(rect); - break; + g_sci->_gfxPaint16->kernelGraphDrawLine(getGraphPoint(argv), getGraphPoint(argv + 2), color, priority, control); + return s->r_acc; +} - case K_GRAPH_FILL_BOX_FOREGROUND: - rect = kGraphCreateRect(x, y, x1, y1); - g_sci->_gfxPaint16->kernelGraphFillBoxForeground(rect); - break; +reg_t kGraphSaveBox(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + uint16 screenMask = (argc > 4) ? argv[4].toUint16() : 0; + return g_sci->_gfxPaint16->kernelGraphSaveBox(rect, screenMask); +} - case K_GRAPH_FILL_BOX_ANY: - priority = (argc > 7) ? argv[7].toSint16() : -1; - control = (argc > 8) ? argv[8].toSint16() : -1; - color = argv[6].toSint16(); - colorMask = argv[5].toUint16(); +reg_t kGraphRestoreBox(EngineState *s, int argc, reg_t *argv) { + // This may be called with a memoryhandle from SAVE_BOX or SAVE_UPSCALEDHIRES_BOX + g_sci->_gfxPaint16->kernelGraphRestoreBox(argv[0]); + return s->r_acc; +} - rect = kGraphCreateRect(x, y, x1, y1); - g_sci->_gfxPaint16->kernelGraphFillBox(rect, colorMask, color, priority, control); - break; +reg_t kGraphFillBoxBackground(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + g_sci->_gfxPaint16->kernelGraphFillBoxBackground(rect); + return s->r_acc; +} - case K_GRAPH_UPDATE_BOX: { - rect = kGraphCreateRect(x, y, x1, y1); - bool hiresMode = (argc > 6) ? true : false; - // argc == 7 on upscaled hires - g_sci->_gfxPaint16->kernelGraphUpdateBox(rect, hiresMode); - break; - } +reg_t kGraphFillBoxForeground(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + g_sci->_gfxPaint16->kernelGraphFillBoxForeground(rect); + return s->r_acc; +} - case K_GRAPH_REDRAW_BOX: - rect = kGraphCreateRect(x, y, x1, y1); - g_sci->_gfxPaint16->kernelGraphRedrawBox(rect); - break; +reg_t kGraphFillBoxAny(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + int16 colorMask = argv[4].toUint16(); + int16 color = argv[5].toSint16(); + int16 priority = (argc > 6) ? argv[6].toSint16() : -1; + int16 control = (argc > 7) ? argv[7].toSint16() : -1; - case K_GRAPH_ADJUST_PRIORITY: - // Seems to be only implemented for SCI0/SCI01 games - debugC(2, kDebugLevelGraphics, "adjust_priority(%d, %d)", argv[1].toUint16(), argv[2].toUint16()); - g_sci->_gfxPorts->kernelGraphAdjustPriority(argv[1].toUint16(), argv[2].toUint16()); - break; + g_sci->_gfxPaint16->kernelGraphFillBox(rect, colorMask, color, priority, control); + return s->r_acc; +} - case K_GRAPH_SAVE_UPSCALEDHIRES_BOX: - rect = kGraphCreateRect(x, y, x1, y1); - return g_sci->_gfxPaint16->kernelGraphSaveUpscaledHiresBox(rect); +reg_t kGraphUpdateBox(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + // argv[4] is the map (1 for visual, etc.) + // argc == 6 on upscaled hires + bool hiresMode = (argc > 5) ? true : false; + g_sci->_gfxPaint16->kernelGraphUpdateBox(rect, hiresMode); + return s->r_acc; +} - default: - warning("Unsupported kGraph() operation %04x", argv[0].toSint16()); - } +reg_t kGraphRedrawBox(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + g_sci->_gfxPaint16->kernelGraphRedrawBox(rect); + return s->r_acc; +} +// Seems to be only implemented for SCI0/SCI01 games +reg_t kGraphAdjustPriority(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxPorts->kernelGraphAdjustPriority(argv[0].toUint16(), argv[1].toUint16()); return s->r_acc; } +reg_t kGraphSaveUpscaledHiresBox(EngineState *s, int argc, reg_t *argv) { + Common::Rect rect = getGraphRect(argv); + return g_sci->_gfxPaint16->kernelGraphSaveUpscaledHiresBox(rect); +} + reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { int16 textWidth, textHeight; Common::String text = s->_segMan->getString(argv[1]); @@ -343,13 +332,14 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { } textWidth = dest[3].toUint16(); textHeight = dest[2].toUint16(); - + #ifdef ENABLE_SCI32 - if (g_sci->_gui32) - g_sci->_gui32->textSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); - else + if (!g_sci->_gfxText16) { + // TODO: Implement this + textWidth = 0; textHeight = 0; + } else #endif - g_sci->_gui->textSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText16->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); debugC(2, kDebugLevelStrings, "GetTextSize '%s' -> %dx%d", text.c_str(), textWidth, textHeight); dest[2] = make_reg(0, textHeight); @@ -531,7 +521,7 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) { // WORKAROUND for a problem in LSL1VGA. This allows the casino door to be opened, // till the actual problem is found - if (!strcmp(g_sci->getGameID(), "lsl1sci") && s->currentRoomNumber() == 300) { + if (s->currentRoomNumber() == 300 && g_sci->getGameId() == GID_LSL1) { int top = readSelectorValue(s->_segMan, object, SELECTOR(brTop)); writeSelectorValue(s->_segMan, object, SELECTOR(brTop), top + 2); } @@ -545,64 +535,72 @@ reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +// we are called on EGA/amiga games as well, this doesnt make sense. +// doing this would actually break the system EGA/amiga palette reg_t kPalette(EngineState *s, int argc, reg_t *argv) { - // we are called on EGA/amiga games as well, this doesnt make sense. - // doing this would actually break the system EGA/amiga palette - if (!g_sci->getResMan()->isVGA()) - return s->r_acc; + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - switch (argv[0].toUint16()) { - case 1: // Set resource palette - if (argc==3) { - GuiResourceId resourceId = argv[1].toUint16(); - bool force = argv[2].toUint16() == 2 ? true : false; - g_sci->_gfxPalette->kernelSetFromResource(resourceId, force); - } else { - warning("kPalette(1) called with %d parameters", argc); - } - break; - case 2: { // Set palette-flag(s) - uint16 fromColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); - uint16 toColor = CLIP<uint16>(argv[2].toUint16(), 1, 255); - uint16 flags = argv[3].toUint16(); +reg_t kPaletteSetFromResource(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { + GuiResourceId resourceId = argv[0].toUint16(); + bool force = false; + if (argc == 2) + force = argv[1].toUint16() == 2 ? true : false; + g_sci->_gfxPalette->kernelSetFromResource(resourceId, force); + } + return s->r_acc; +} + +reg_t kPaletteSetFlag(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { + uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); + uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); + uint16 flags = argv[2].toUint16(); g_sci->_gfxPalette->kernelSetFlag(fromColor, toColor, flags); - break; } - case 3: { // Remove palette-flag(s) - uint16 fromColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); - uint16 toColor = CLIP<uint16>(argv[2].toUint16(), 1, 255); - uint16 flags = argv[3].toUint16(); + return s->r_acc; +} + +reg_t kPaletteUnsetFlag(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { + uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); + uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); + uint16 flags = argv[2].toUint16(); g_sci->_gfxPalette->kernelUnsetFlag(fromColor, toColor, flags); - break; } - case 4: { // Set palette intensity - switch (argc) { - case 4: - case 5: { - uint16 fromColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); - uint16 toColor = CLIP<uint16>(argv[2].toUint16(), 1, 255); - uint16 intensity = argv[3].toUint16(); - bool setPalette = (argc < 5) ? true : (argv[4].isNull()) ? true : false; - - g_sci->_gfxPalette->kernelSetIntensity(fromColor, toColor, intensity, setPalette); - break; - } - default: - warning("kPalette(4) called with %d parameters", argc); - } - break; + return s->r_acc; +} + +reg_t kPaletteSetIntensity(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { + uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); + uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); + uint16 intensity = argv[2].toUint16(); + bool setPalette = (argc < 4) ? true : (argv[3].isNull()) ? true : false; + + g_sci->_gfxPalette->kernelSetIntensity(fromColor, toColor, intensity, setPalette); } - case 5: { // Find closest color - uint16 r = argv[1].toUint16(); - uint16 g = argv[2].toUint16(); - uint16 b = argv[3].toUint16(); + return s->r_acc; +} +reg_t kPaletteFindColor(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { + uint16 r = argv[0].toUint16(); + uint16 g = argv[1].toUint16(); + uint16 b = argv[2].toUint16(); return make_reg(0, g_sci->_gfxPalette->kernelFindColor(r, g, b)); } - case 6: { // Animate + return NULL_REG; +} + +reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { int16 argNr; bool paletteChanged = false; - for (argNr = 1; argNr < argc; argNr += 3) { + for (argNr = 0; argNr < argc; argNr += 3) { uint16 fromColor = argv[argNr].toUint16(); uint16 toColor = argv[argNr + 1].toUint16(); int16 speed = argv[argNr + 2].toSint16(); @@ -611,80 +609,82 @@ reg_t kPalette(EngineState *s, int argc, reg_t *argv) { } if (paletteChanged) g_sci->_gfxPalette->kernelAnimateSet(); - break; } - case 7: { // Save palette to heap + return s->r_acc; +} + +reg_t kPaletteSave(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { warning("kPalette(7), save palette to heap STUB"); - break; - } - case 8: { // Restore palette from heap - warning("kPalette(8), set stored palette STUB"); - break; - } - default: - warning("kPalette(%d), not implemented", argv[0].toUint16()); } + return NULL_REG; +} +reg_t kPaletteRestore(EngineState *s, int argc, reg_t *argv) { + if (g_sci->getResMan()->isVGA()) { + warning("kPalette(8), restore palette from heap STUB"); + } return s->r_acc; } -// This here is needed to make Pharkas work reg_t kPalVary(EngineState *s, int argc, reg_t *argv) { - uint16 operation = argv[0].toUint16(); + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - if (!g_sci->_gui) - return s->r_acc; +reg_t kPalVaryInit(EngineState *s, int argc, reg_t *argv) { + GuiResourceId paletteId = argv[0].toUint16(); + uint16 ticks = argv[1].toUint16(); + uint16 stepStop = argc >= 3 ? argv[2].toUint16() : 64; + uint16 direction = argc >= 4 ? argv[3].toUint16() : 1; + if (g_sci->_gfxPalette->kernelPalVaryInit(paletteId, ticks, stepStop, direction)) + return SIGNAL_REG; + return NULL_REG; +} - switch (operation) { - case 0: { // Init - GuiResourceId paletteId; - uint16 time; - if (argc == 3) { - paletteId = argv[1].toUint16(); - time = argv[2].toUint16(); - g_sci->_gfxPalette->startPalVary(paletteId, time); - warning("kPalVary(init) called with paletteId = %d, time = %d", paletteId, time); - } else { - warning("kPalVary(init) called with unsupported argc %d", argc); - } - break; - } - case 1: { // Unknown - warning("kPalVary(1) called with parameter %d (argc %d)", argv[1].toUint16(), argc); - break; - } - case 3: { // DeInit - if (argc == 1) { - g_sci->_gfxPalette->stopPalVary(); - warning("kPalVary(deinit)"); - } else { - warning("kPalVary(deinit) called with unsupported argc %d", argc); - } - break; - } - case 4: { // Unknown - warning("kPalVary(4) called with parameter %d (argc %d)", argv[1].toUint16(), argc); - break; - } - case 6: { // Pause - bool pauseState; - if (argc == 2) { - pauseState = argv[1].isNull() ? false : true; - g_sci->_gfxPalette->togglePalVary(pauseState); - warning("kPalVary(pause) called with state = %d", pauseState); - } else { - warning("kPalVary(pause) called with unsupported argc %d", argc); - } - break; - } - default: - warning("kPalVary(%d), not implemented (argc = %d)", operation, argc); - } +reg_t kPalVaryReverse(EngineState *s, int argc, reg_t *argv) { + int16 ticks = argc >= 1 ? argv[0].toUint16() : -1; + int16 stepStop = argc >= 2 ? argv[1].toUint16() : 0; + int16 direction = argc >= 3 ? argv[2].toSint16() : -1; + + return make_reg(0, g_sci->_gfxPalette->kernelPalVaryReverse(ticks, stepStop, direction)); +} + +reg_t kPalVaryGetCurrentStep(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_gfxPalette->kernelPalVaryGetCurrentStep()); +} + +reg_t kPalVaryDeinit(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxPalette->kernelPalVaryDeinit(); + return NULL_REG; +} + +reg_t kPalVaryChangeTarget(EngineState *s, int argc, reg_t *argv) { + GuiResourceId paletteId = argv[0].toUint16(); + int16 currentStep = g_sci->_gfxPalette->kernelPalVaryChangeTarget(paletteId); + return make_reg(0, currentStep); +} + +reg_t kPalVaryChangeTicks(EngineState *s, int argc, reg_t *argv) { + uint16 ticks = argv[0].toUint16(); + g_sci->_gfxPalette->kernelPalVaryChangeTicks(ticks); + return NULL_REG; +} + +reg_t kPalVaryPauseResume(EngineState *s, int argc, reg_t *argv) { + bool pauseState = !argv[0].isNull(); + g_sci->_gfxPalette->kernelPalVaryPause(pauseState); + return NULL_REG; +} + +reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv) { + // Unknown (seems to be SCI32 exclusive) return NULL_REG; } reg_t kAssertPalette(EngineState *s, int argc, reg_t *argv) { - GuiResourceId paletteId = argv[1].toUint16(); + GuiResourceId paletteId = argv[0].toUint16(); g_sci->_gfxPalette->kernelAssertPalette(paletteId); return s->r_acc; @@ -698,9 +698,9 @@ reg_t kPortrait(EngineState *s, int argc, reg_t *argv) { case 0: { // load if (argc == 2) { Common::String resourceName = s->_segMan->getString(argv[1]); - s->r_acc = g_sci->_gui->portraitLoad(resourceName); + s->r_acc = g_sci->_gfxPaint16->kernelPortraitLoad(resourceName); } else { - warning("kPortrait(loadResource) called with unsupported argc %d", argc); + error("kPortrait(loadResource) called with unsupported argc %d", argc); } break; } @@ -715,24 +715,24 @@ reg_t kPortrait(EngineState *s, int argc, reg_t *argv) { uint seq = argv[8].toUint16() & 0xff; // argv[9] is usually 0??!! - g_sci->_gui->portraitShow(resourceName, position, resourceNum, noun, verb, cond, seq); + g_sci->_gfxPaint16->kernelPortraitShow(resourceName, position, resourceNum, noun, verb, cond, seq); return SIGNAL_REG; } else { - warning("kPortrait(show) called with unsupported argc %d", argc); + error("kPortrait(show) called with unsupported argc %d", argc); } break; } case 2: { // unload if (argc == 2) { uint16 portraitId = argv[1].toUint16(); - g_sci->_gui->portraitUnload(portraitId); + g_sci->_gfxPaint16->kernelPortraitUnload(portraitId); } else { - warning("kPortrait(unload) called with unsupported argc %d", argc); + error("kPortrait(unload) called with unsupported argc %d", argc); } break; } default: - warning("kPortrait(%d), not implemented (argc = %d)", operation, argc); + error("kPortrait(%d), not implemented (argc = %d)", operation, argc); } return s->r_acc; @@ -790,6 +790,11 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { mode = readSelectorValue(s->_segMan, controlObject, SELECTOR(mode)); maxChars = readSelectorValue(s->_segMan, controlObject, SELECTOR(max)); cursorPos = readSelectorValue(s->_segMan, controlObject, SELECTOR(cursor)); + if (cursorPos > (int)text.size()) { + // if cursor is outside of text, adjust accordingly + cursorPos = text.size(); + writeSelectorValue(s->_segMan, controlObject, SELECTOR(cursor), cursorPos); + } debugC(2, kDebugLevelGraphics, "drawing edit control %04x:%04x (text %04x:%04x, '%s') to %d,%d", PRINT_REG(controlObject), PRINT_REG(textReference), text.c_str(), x, y); g_sci->_gfxControls->kernelDrawTextEdit(rect, controlObject, g_sci->strSplit(text.c_str(), NULL).c_str(), fontId, mode, style, cursorPos, maxChars, hilite); return; @@ -801,11 +806,8 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { loopNo = (l & 0x80) ? l - 256 : l; int c = readSelectorValue(s->_segMan, controlObject, SELECTOR(cel)); celNo = (c & 0x80) ? c - 256 : c; - // Game-specific: *ONLY* the jones EGA/VGA sierra interpreter contain code using priority selector - // ALL other games use a hardcoded -1 (madness!) - // We are detecting jones/talkie as "jones" as well, but the sierra interpreter of talkie doesnt have this - // "hack". Hopefully it wont cause regressions (the code causes regressions if used against kq5/floppy) - if (!strcmp(g_sci->getGameID(), "jones")) + // Check if the control object specifies a priority selector (like in Jones) + if (lookupSelector(s->_segMan, controlObject, SELECTOR(priority), NULL, NULL) == kSelectorVariable) priority = readSelectorValue(s->_segMan, controlObject, SELECTOR(priority)); else priority = -1; @@ -821,12 +823,12 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { maxChars = readSelectorValue(s->_segMan, controlObject, SELECTOR(x)); // max chars per entry cursorOffset = readSelectorValue(s->_segMan, controlObject, SELECTOR(cursor)); - if (g_sci->getKernel()->_selectorCache.topString != -1) { + if (SELECTOR(topString) != -1) { // Games from early SCI1 onwards use topString upperOffset = readSelectorValue(s->_segMan, controlObject, SELECTOR(topString)); } else { // Earlier games use lsTop or brTop - if (lookupSelector(s->_segMan, controlObject, g_sci->getKernel()->_selectorCache.brTop, NULL, NULL) == kSelectorVariable) + if (lookupSelector(s->_segMan, controlObject, SELECTOR(brTop), NULL, NULL) == kSelectorVariable) upperOffset = readSelectorValue(s->_segMan, controlObject, SELECTOR(brTop)); else upperOffset = readSelectorValue(s->_segMan, controlObject, SELECTOR(lsTop)); @@ -883,6 +885,18 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) { int state = readSelectorValue(s->_segMan, controlObject, SELECTOR(state)); writeSelectorValue(s->_segMan, controlObject, SELECTOR(state), (state | SCI_CONTROLS_STYLE_DISABLED) & ~SCI_CONTROLS_STYLE_ENABLED); } + if (objName == "DEdit") { + reg_t textReference = readSelector(s->_segMan, controlObject, SELECTOR(text)); + if (!textReference.isNull()) { + Common::String text = s->_segMan->getString(textReference); + if (text == "a:hq1_hero.sav") { + // Remove "a:" from hero quest export default filename + text.deleteChar(0); + text.deleteChar(0); + s->_segMan->strcpy(textReference, text.c_str()); + } + } + } _k_GenericDrawControl(s, controlObject, false); return NULL_REG; @@ -989,24 +1003,6 @@ reg_t kDrawCel(EngineState *s, int argc, reg_t *argv) { bool hiresMode = (argc > 7) ? true : false; reg_t upscaledHiresHandle = (argc > 7) ? argv[7] : NULL_REG; - if (!strcmp(g_sci->getGameID(), "freddypharkas") || !strcmp(g_sci->getGameID(), "freddypharkas-demo")) { - // WORKAROUND - // Script 24 contains code that draws the game menu on screen. It uses a temp variable for setting priority that - // is not set. in Sierra sci this happens to be 8250h. In our sci temporary variables are initialized thus we would - // get 0 here resulting in broken menus. - if ((viewId == 995) && (loopNo == 0) && (celNo == 0) && (priority == 0)) // game menu - priority = 15; - if ((viewId == 992) && (loopNo == 0) && (celNo == 0) && (priority == 0)) // quit game - priority = 15; - } - - if (!strcmp(g_sci->getGameID(), "laurabow2")) { - // WORKAROUND - // see the one above - if ((viewId == 995) && (priority == 0)) - priority = 15; - } - g_sci->_gfxPaint16->kernelDrawCel(viewId, loopNo, celNo, x, y, priority, paletteNo, hiresMode, upscaledHiresHandle); return s->r_acc; @@ -1049,10 +1045,6 @@ reg_t kAnimate(EngineState *s, int argc, reg_t *argv) { reg_t castListReference = (argc > 0) ? argv[0] : NULL_REG; bool cycle = (argc > 1) ? ((argv[1].toUint16()) ? true : false) : false; -#ifdef USE_OLD_MUSIC_FUNCTIONS - // Take care of incoming events (kAnimate is called semi-regularly) - process_sound_events(s); -#endif g_sci->_gfxAnimate->kernelAnimate(castListReference, cycle, argc, argv); return s->r_acc; @@ -1083,6 +1075,58 @@ reg_t kDisplay(EngineState *s, int argc, reg_t *argv) { return g_sci->_gfxPaint16->kernelDisplay(g_sci->strSplit(text.c_str()).c_str(), argc, argv); } +void playVideo(Graphics::VideoDecoder *videoDecoder) { + if (!videoDecoder) + return; + + byte *scaleBuffer = 0; + uint16 width = videoDecoder->getWidth(); + uint16 height = videoDecoder->getHeight(); + uint16 screenWidth = g_system->getWidth(); + uint16 screenHeight = g_system->getHeight(); + + if (screenWidth == 640 && width <= 320 && height <= 240) { + assert(videoDecoder->getPixelFormat().bytesPerPixel == 1); + width *= 2; + height *= 2; + scaleBuffer = new byte[width * height]; + } + + uint16 x = (screenWidth - width) / 2; + uint16 y = (screenHeight - height) / 2; + bool skipVideo = false; + + while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { + if (videoDecoder->needsUpdate()) { + Graphics::Surface *frame = videoDecoder->decodeNextFrame(); + if (frame) { + if (scaleBuffer) { + // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows + g_sci->_gfxScreen->scale2x((byte *)frame->pixels, scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight()); + g_system->copyRectToScreen(scaleBuffer, width, x, y, width, height); + } else + g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, width, height); + + if (videoDecoder->hasDirtyPalette()) + videoDecoder->setSystemPalette(); + + g_system->updateScreen(); + } + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) { + if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) + skipVideo = true; + } + + g_system->delayMillis(10); + } + + delete[] scaleBuffer; + delete videoDecoder; +} + reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { // Hide the cursor if it's showing and then show it again if it was // previously visible. @@ -1106,7 +1150,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { initGraphics(screenWidth, screenHeight, screenWidth > 320, NULL); if (g_system->getScreenFormat().bytesPerPixel == 1) { - warning("This video requires >8bpp color to be displayed, but could not switch to RGB color mode."); + error("This video requires >8bpp color to be displayed, but could not switch to RGB color mode."); return NULL_REG; } @@ -1159,37 +1203,16 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { } if (videoDecoder) { - uint16 x = (screenWidth - videoDecoder->getWidth()) / 2; - uint16 y = (screenHeight - videoDecoder->getHeight()) / 2; - - while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo()) { - if (videoDecoder->needsUpdate()) { - Graphics::Surface *frame = videoDecoder->decodeNextFrame(); - if (frame) { - g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); - - if (videoDecoder->hasDirtyPalette()) - videoDecoder->setSystemPalette(); - - g_system->updateScreen(); - } - } - - Common::Event event; - while (g_system->getEventManager()->pollEvent(event)) - ; - - g_system->delayMillis(10); - } + playVideo(videoDecoder); // HACK: Switch back to 8bpp if we played a QuickTime video. // We also won't be copying the screen to the SCI screen... if (g_system->getScreenFormat().bytesPerPixel != 1) initGraphics(screenWidth, screenHeight, screenWidth > 320); - else + else { g_sci->_gfxScreen->kernelSyncWithFramebuffer(); - - delete videoDecoder; + g_sci->_gfxPalette->kernelSyncScreenPalette(); + } } if (reshowCursor) @@ -1213,6 +1236,9 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y); } break; + case 1: // LSL6 hires (startup) + // TODO + return NULL_REG; // an integer is expected case 4: { // start int id = argv[1].toUint16(); warning("kRobot(start), id %d", id); @@ -1228,30 +1254,140 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } + +reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { + uint16 operation = argv[0].toUint16(); + Graphics::VideoDecoder *videoDecoder = 0; + bool reshowCursor = g_sci->_gfxCursor->isVisible(); + Common::String fileName, warningMsg; + + switch (operation) { + case 0: // init + // This is actually meant to init the video file, but we play it instead + fileName = s->_segMan->derefString(argv[1]); + // TODO: argv[2] (usually null). When it exists, it points to an "Event" object, + // that holds no data initially (e.g. in the intro of Phantasmagoria 1 demo). + // Perhaps it's meant for syncing + if (argv[2] != NULL_REG) + warning("kPlayVMD: third parameter isn't 0 (it's %04x:%04x - %s)", PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2])); + + videoDecoder = new VMDDecoder(g_system->getMixer()); + + if (reshowCursor) + g_sci->_gfxCursor->kernelHide(); + + if (videoDecoder && videoDecoder->loadFile(fileName)) + playVideo(videoDecoder); + + if (reshowCursor) + g_sci->_gfxCursor->kernelShow(); + break; + case 1: + { + // Set VMD parameters. Called with a maximum of 6 parameters: + // + // x, y, flags, gammaBoost, gammaFirst, gammaLast + // + // Flags are as follows: + // bit 0 doubled + // bit 1 "drop frames"? + // bit 2 insert black lines + // bit 3 unknown + // bit 4 gamma correction + // bit 5 hold black frame + // bit 6 hold last frame + // bit 7 unknown + // bit 8 stretch + + // gammaBoost boosts palette colors in the range gammaFirst to + // gammaLast, but only if bit 4 in flags is set. Percent value such that + // 0% = no amplification These three parameters are optional if bit 4 is + // clear. Also note that the x, y parameters play subtle games if used + // with subfx 21. The subtleness has to do with creation of temporary + // planes and positioning relative to such planes. + + int flags = argv[3].offset; + Common::String flagspec; + + if (argc > 3) { + if (flags & 1) + flagspec += "doubled "; + if (flags & 2) + flagspec += "dropframes "; + if (flags & 4) + flagspec += "blacklines "; + if (flags & 8) + flagspec += "bit3 "; + if (flags & 16) + flagspec += "gammaboost "; + if (flags & 32) + flagspec += "holdblack "; + if (flags & 64) + flagspec += "holdlast "; + if (flags & 128) + flagspec += "bit7 "; + if (flags & 256) + flagspec += "stretch"; + + warning("VMDFlags: %s", flagspec.c_str()); + } + + warning("x, y: %d, %d", argv[1].offset, argv[2].offset); + + if (argc > 4 && flags & 16) + warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].offset, argv[5].offset, argv[6].offset); + break; + } + case 6: + // Play, perhaps? Or stop? This is the last call made, and takes no extra parameters + case 14: + // Takes an additional integer parameter (e.g. 3) + case 16: + // Takes an additional parameter, usually 0 + case 21: + // Looks to be setting the video size and position. Called with 4 extra integer + // parameters (e.g. 86, 41, 235, 106) + default: + warningMsg = "PlayVMD - unsupported subop. Params: " + + Common::String::printf("%d", argc) + " ("; + + for (int i = 0; i < argc; i++) { + warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); + warningMsg += (i == argc - 1 ? ")" : ", "); + } + + warning("%s", warningMsg.c_str()); + break; + } + + return s->r_acc; +} + #endif reg_t kSetVideoMode(EngineState *s, int argc, reg_t *argv) { - // This call is used for KQ6's intro. It has one parameter, which is - // 1 when the intro begins, and 0 when it ends. It is suspected that - // this is actually a flag to enable video planar memory access, as - // the video decoder in KQ6 is specifically written for the planar - // memory model. Planar memory mode access was used for VGA "Mode X" - // (320x240 resolution, although the intro in KQ6 is 320x200). + // This call is used for KQ6's intro. It has one parameter, which is 1 when + // the intro begins, and 0 when it ends. It is suspected that this is + // actually a flag to enable video planar memory access, as the video + // decoder in KQ6 is specifically written for the planar memory model. + // Planar memory mode access was used for VGA "Mode X" (320x240 resolution, + // although the intro in KQ6 is 320x200). // Refer to http://en.wikipedia.org/wiki/Mode_X //warning("STUB: SetVideoMode %d", argv[0].toUint16()); return s->r_acc; } -// New calls for SCI11. Using those is only needed when using text-codes so that one is able to change -// font and/or color multiple times during kDisplay and kDrawControl +// New calls for SCI11. Using those is only needed when using text-codes so that +// one is able to change font and/or color multiple times during kDisplay and +// kDrawControl reg_t kTextFonts(EngineState *s, int argc, reg_t *argv) { - g_sci->_gui->textFonts(argc, argv); + g_sci->_gfxText16->kernelTextFonts(argc, argv); return s->r_acc; } reg_t kTextColors(EngineState *s, int argc, reg_t *argv) { - g_sci->_gui->textColors(argc, argv); + g_sci->_gfxText16->kernelTextColors(argc, argv); return s->r_acc; } diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp index f06f3eec77..0701883a9b 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -37,12 +37,12 @@ static bool isSaneNodePointer(SegManager *segMan, reg_t addr) { Node *node = segMan->lookupNode(addr); if (!node) { - warning("isSaneNodePointer: Node at %04x:%04x wasn't found", PRINT_REG(addr)); + error("isSaneNodePointer: Node at %04x:%04x wasn't found", PRINT_REG(addr)); return false; } if (havePrev && node->pred != prev) { - warning("isSaneNodePointer: Node at %04x:%04x points to invalid predecessor %04x:%04x (should be %04x:%04x)", + error("isSaneNodePointer: Node at %04x:%04x points to invalid predecessor %04x:%04x (should be %04x:%04x)", PRINT_REG(addr), PRINT_REG(node->pred), PRINT_REG(prev)); //node->pred = prev; // fix the problem in the node @@ -58,33 +58,33 @@ static bool isSaneNodePointer(SegManager *segMan, reg_t addr) { } static void checkListPointer(SegManager *segMan, reg_t addr) { - List *l = segMan->lookupList(addr); + List *list = segMan->lookupList(addr); - if (!l) { - warning("isSaneListPointer (list %04x:%04x): The requested list wasn't found", + if (!list) { + error("checkListPointer (list %04x:%04x): The requested list wasn't found", PRINT_REG(addr)); return; } - if (l->first.isNull() && l->last.isNull()) { + if (list->first.isNull() && list->last.isNull()) { // Empty list is fine - } else if (!l->first.isNull() && !l->last.isNull()) { + } else if (!list->first.isNull() && !list->last.isNull()) { // Normal list - Node *node_a = segMan->lookupNode(l->first); - Node *node_z = segMan->lookupNode(l->last); + Node *node_a = segMan->lookupNode(list->first); + Node *node_z = segMan->lookupNode(list->last); if (!node_a) { - warning("isSaneListPointer (list %04x:%04x): missing first node", PRINT_REG(addr)); + error("checkListPointer (list %04x:%04x): missing first node", PRINT_REG(addr)); return; } if (!node_z) { - warning("isSaneListPointer (list %04x:%04x): missing last node", PRINT_REG(addr)); + error("checkListPointer (list %04x:%04x): missing last node", PRINT_REG(addr)); return; } if (!node_a->pred.isNull()) { - warning("isSaneListPointer (list %04x:%04x): First node of the list points to a predecessor node", + error("checkListPointer (list %04x:%04x): First node of the list points to a predecessor node", PRINT_REG(addr)); //node_a->pred = NULL_REG; // fix the problem in the node @@ -93,7 +93,7 @@ static void checkListPointer(SegManager *segMan, reg_t addr) { } if (!node_z->succ.isNull()) { - warning("isSaneListPointer (list %04x:%04x): Last node of the list points to a successor node", + error("checkListPointer (list %04x:%04x): Last node of the list points to a successor node", PRINT_REG(addr)); //node_z->succ = NULL_REG; // fix the problem in the node @@ -101,66 +101,42 @@ static void checkListPointer(SegManager *segMan, reg_t addr) { return; } - isSaneNodePointer(segMan, l->first); + isSaneNodePointer(segMan, list->first); } else { // Not sane list... it's missing pointers to the first or last element - if (l->first.isNull()) - warning("isSaneListPointer (list %04x:%04x): missing pointer to first element", + if (list->first.isNull()) + error("checkListPointer (list %04x:%04x): missing pointer to first element", PRINT_REG(addr)); - if (l->last.isNull()) - warning("isSaneListPointer (list %04x:%04x): missing pointer to last element", + if (list->last.isNull()) + error("checkListPointer (list %04x:%04x): missing pointer to last element", PRINT_REG(addr)); } } reg_t kNewList(EngineState *s, int argc, reg_t *argv) { - reg_t listbase; - List *l; - l = s->_segMan->allocateList(&listbase); - l->first = l->last = NULL_REG; - debugC(2, kDebugLevelNodes, "New listbase at %04x:%04x", PRINT_REG(listbase)); + reg_t listRef; + List *list = s->_segMan->allocateList(&listRef); + list->first = list->last = NULL_REG; + debugC(2, kDebugLevelNodes, "New listRef at %04x:%04x", PRINT_REG(listRef)); - return listbase; // Return list base address + return listRef; // Return list base address } reg_t kDisposeList(EngineState *s, int argc, reg_t *argv) { // This function is not needed in ScummVM. The garbage collector // cleans up unused objects automatically -#if 0 - List *l = s->_segMan->lookupList(argv[0]); - - if (!l) { - // FIXME: This should be an error, but it's turned to a warning for now - warning("Attempt to dispose non-list at %04x:%04x", PRINT_REG(argv[0])); - return NULL_REG; - } - - checkListPointer(s->_segMan, argv[0]); - - if (!l->first.isNull()) { - reg_t n_addr = l->first; - - while (!n_addr.isNull()) { // Free all nodes - Node *n = s->_segMan->lookupNode(n_addr); - n_addr = n->succ; - - //s->_segMan->free_Node(n_addr); // TODO - } - } - - //s->_segMan->free_list(argv[0]); // TODO -#endif - return s->r_acc; } reg_t kNewNode(EngineState *s, int argc, reg_t *argv) { reg_t nodeValue = argv[0]; - reg_t nodeKey = (argc == 2) ? argv[1] : NULL_REG; + // Some SCI32 games call this with 1 parameter (e.g. the demo of Phantasmagoria). + // Set the key to be the same as the value in this case + reg_t nodeKey = (argc == 2) ? argv[1] : argv[0]; s->r_acc = s->_segMan->newNode(nodeValue, nodeKey); - debugC(2, kDebugLevelNodes, "New nodebase at %04x:%04x", PRINT_REG(s->r_acc)); + debugC(2, kDebugLevelNodes, "New nodeRef at %04x:%04x", PRINT_REG(s->r_acc)); return s->r_acc; } @@ -169,11 +145,11 @@ reg_t kFirstNode(EngineState *s, int argc, reg_t *argv) { if (argv[0].isNull()) return NULL_REG; - List *l = s->_segMan->lookupList(argv[0]); + List *list = s->_segMan->lookupList(argv[0]); - if (l) { + if (list) { checkListPointer(s->_segMan, argv[0]); - return l->first; + return list->first; } else { return NULL_REG; } @@ -183,11 +159,11 @@ reg_t kLastNode(EngineState *s, int argc, reg_t *argv) { if (argv[0].isNull()) return NULL_REG; - List *l = s->_segMan->lookupList(argv[0]); + List *list = s->_segMan->lookupList(argv[0]); - if (l) { + if (list) { checkListPointer(s->_segMan, argv[0]); - return l->last; + return list->last; } else { return NULL_REG; } @@ -197,56 +173,56 @@ reg_t kEmptyList(EngineState *s, int argc, reg_t *argv) { if (argv[0].isNull()) return NULL_REG; - List *l = s->_segMan->lookupList(argv[0]); + List *list = s->_segMan->lookupList(argv[0]); checkListPointer(s->_segMan, argv[0]); - return make_reg(0, ((l) ? l->first.isNull() : 0)); + return make_reg(0, ((list) ? list->first.isNull() : 0)); } -static void _k_add_to_front(EngineState *s, reg_t listbase, reg_t nodebase) { - List *l = s->_segMan->lookupList(listbase); - Node *new_n = s->_segMan->lookupNode(nodebase); +static void addToFront(EngineState *s, reg_t listRef, reg_t nodeRef) { + List *list = s->_segMan->lookupList(listRef); + Node *newNode = s->_segMan->lookupNode(nodeRef); + + debugC(2, kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef)); - debugC(2, kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x", PRINT_REG(nodebase), PRINT_REG(listbase)); + if (!newNode) + error("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef)); + checkListPointer(s->_segMan, listRef); - // FIXME: This should be an error, but it's turned to a warning for now - if (!new_n) - warning("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodebase), PRINT_REG(listbase)); - checkListPointer(s->_segMan, listbase); + newNode->pred = NULL_REG; + newNode->succ = list->first; - new_n->succ = l->first; - new_n->pred = NULL_REG; // Set node to be the first and last node if it's the only node of the list - if (l->first.isNull()) - l->last = nodebase; + if (list->first.isNull()) + list->last = nodeRef; else { - Node *old_n = s->_segMan->lookupNode(l->first); - old_n->pred = nodebase; + Node *oldNode = s->_segMan->lookupNode(list->first); + oldNode->pred = nodeRef; } - l->first = nodebase; + list->first = nodeRef; } -static void _k_add_to_end(EngineState *s, reg_t listbase, reg_t nodebase) { - List *l = s->_segMan->lookupList(listbase); - Node *new_n = s->_segMan->lookupNode(nodebase); +static void addToEnd(EngineState *s, reg_t listRef, reg_t nodeRef) { + List *list = s->_segMan->lookupList(listRef); + Node *newNode = s->_segMan->lookupNode(nodeRef); - debugC(2, kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x", PRINT_REG(nodebase), PRINT_REG(listbase)); + debugC(2, kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef)); - // FIXME: This should be an error, but it's turned to a warning for now - if (!new_n) - warning("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodebase), PRINT_REG(listbase)); - checkListPointer(s->_segMan, listbase); + if (!newNode) + error("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef)); + checkListPointer(s->_segMan, listRef); + + newNode->pred = list->last; + newNode->succ = NULL_REG; - new_n->succ = NULL_REG; - new_n->pred = l->last; // Set node to be the first and last node if it's the only node of the list - if (l->last.isNull()) - l->first = nodebase; + if (list->last.isNull()) + list->first = nodeRef; else { - Node *old_n = s->_segMan->lookupNode(l->last); - old_n->succ = nodebase; + Node *old_n = s->_segMan->lookupNode(list->last); + old_n->succ = nodeRef; } - l->last = nodebase; + list->last = nodeRef; } reg_t kNextNode(EngineState *s, int argc, reg_t *argv) { @@ -274,28 +250,30 @@ reg_t kNodeValue(EngineState *s, int argc, reg_t *argv) { } reg_t kAddToFront(EngineState *s, int argc, reg_t *argv) { - _k_add_to_front(s, argv[0], argv[1]); + addToFront(s, argv[0], argv[1]); return s->r_acc; } reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) { - List *l = s->_segMan->lookupList(argv[0]); + List *list = s->_segMan->lookupList(argv[0]); Node *firstnode = argv[1].isNull() ? NULL : s->_segMan->lookupNode(argv[1]); Node *newnode = s->_segMan->lookupNode(argv[2]); checkListPointer(s->_segMan, argv[0]); - // FIXME: This should be an error, but it's turned to a warning for now if (!newnode) { - warning("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2])); + error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2])); return NULL_REG; } - if (argc != 3) { - warning("kAddAfter: Haven't got 3 arguments, aborting"); + if (argc != 3 && argc != 4) { + error("kAddAfter: Haven't got 3 or 4 arguments, aborting"); return NULL_REG; } + if (argc == 4) + newnode->key = argv[3]; + if (firstnode) { // We're really appending after reg_t oldnext = firstnode->succ; @@ -305,19 +283,19 @@ reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) { if (oldnext.isNull()) // Appended after last node? // Set new node as last list node - l->last = argv[2]; + list->last = argv[2]; else s->_segMan->lookupNode(oldnext)->pred = argv[2]; } else { // !firstnode - _k_add_to_front(s, argv[0], argv[2]); // Set as initial list node + addToFront(s, argv[0], argv[2]); // Set as initial list node } return s->r_acc; } reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv) { - _k_add_to_end(s, argv[0], argv[1]); + addToEnd(s, argv[0], argv[1]); return s->r_acc; } @@ -352,23 +330,27 @@ reg_t kFindKey(EngineState *s, int argc, reg_t *argv) { reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) { reg_t node_pos = kFindKey(s, 2, argv); Node *n; - List *l = s->_segMan->lookupList(argv[0]); + List *list = s->_segMan->lookupList(argv[0]); if (node_pos.isNull()) return NULL_REG; // Signal failure n = s->_segMan->lookupNode(node_pos); - if (l->first == node_pos) - l->first = n->succ; - if (l->last == node_pos) - l->last = n->pred; + if (list->first == node_pos) + list->first = n->succ; + if (list->last == node_pos) + list->last = n->pred; if (!n->pred.isNull()) s->_segMan->lookupNode(n->pred)->succ = n->succ; if (!n->succ.isNull()) s->_segMan->lookupNode(n->succ)->pred = n->pred; - //s->_segMan->free_Node(node_pos); // TODO + // Erase references to the predecessor and successor nodes, as the game + // scripts could reference the node itself again. + // Happens in the intro of QFG1 and in Longbow, when exiting the cave. + n->pred = NULL_REG; + n->succ = NULL_REG; return make_reg(0, 1); // Signal success } @@ -398,8 +380,6 @@ reg_t kSort(EngineState *s, int argc, reg_t *argv) { reg_t order_func = argv[2]; int input_size = (int16)readSelectorValue(segMan, source, SELECTOR(size)); - int i; - reg_t input_data = readSelector(segMan, source, SELECTOR(elements)); reg_t output_data = readSelector(segMan, dest, SELECTOR(elements)); @@ -422,9 +402,10 @@ reg_t kSort(EngineState *s, int argc, reg_t *argv) { sort_temp_t *temp_array = (sort_temp_t *)malloc(sizeof(sort_temp_t) * input_size); - i = 0; + int i = 0; while (node) { - invokeSelector(INV_SEL(s, order_func, doit, kStopOnInvalidSelector), 1, node->value); + reg_t params[1] = { node->value }; + invokeSelector(s, order_func, SELECTOR(doit), argc, argv, 1, params); temp_array[i].key = node->key; temp_array[i].value = node->value; temp_array[i].order = s->r_acc; @@ -436,7 +417,7 @@ reg_t kSort(EngineState *s, int argc, reg_t *argv) { for (i = 0;i < input_size;i++) { reg_t lNode = s->_segMan->newNode(temp_array[i].value, temp_array[i].key); - _k_add_to_end(s, output_data, lNode); + addToEnd(s, output_data, lNode); } free(temp_array); @@ -449,14 +430,14 @@ reg_t kSort(EngineState *s, int argc, reg_t *argv) { reg_t kListAt(EngineState *s, int argc, reg_t *argv) { if (argc != 2) { - warning("kListAt called with %d parameters", argc); + error("kListAt called with %d parameters", argc); return NULL_REG; } List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; if (list->first.isNull()) { - warning("kListAt tried to reference empty list (%04x:%04x)", PRINT_REG(argv[0])); + error("kListAt tried to reference empty list (%04x:%04x)", PRINT_REG(argv[0])); return NULL_REG; } Node *curNode = s->_segMan->lookupNode(curAddress); @@ -518,12 +499,12 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { // This can only happen with 3 params (list, target selector, variable) if (argc != 3) { - warning("kListEachElementDo: Attempted to modify a variable selector with %d params", argc); + error("kListEachElementDo: Attempted to modify a variable selector with %d params", argc); } else { writeSelector(s->_segMan, curObject, slc, argv[2]); } } else { - invokeSelectorArgv(s, curObject, slc, kContinueOnInvalidSelector, argc, argv, argc - 2, argv + 2); + invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); } curNode = s->_segMan->lookupNode(nextNode); @@ -550,9 +531,9 @@ 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? - warning("kListFirstTrue: Attempted to access a variable selector"); + error("kListFirstTrue: Attempted to access a variable selector"); } else { - invokeSelectorArgv(s, curObject, slc, kContinueOnInvalidSelector, argc, argv, argc - 2, argv + 2); + invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); // Check if the result is true if (!s->r_acc.isNull()) @@ -584,9 +565,9 @@ 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? - warning("kListAllTrue: Attempted to access a variable selector"); + error("kListAllTrue: Attempted to access a variable selector"); } else { - invokeSelectorArgv(s, curObject, slc, kContinueOnInvalidSelector, argc, argv, argc - 2, argv + 2); + invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); // Check if the result isn't true if (s->r_acc.isNull()) @@ -627,15 +608,15 @@ reg_t kList(EngineState *s, int argc, reg_t *argv) { case 11: return kAddToEnd(s, argc - 1, argv + 1); case 12: - warning("kList: unimplemented subfunction kAddBefore"); + error("kList: unimplemented subfunction kAddBefore"); //return kAddBefore(s, argc - 1, argv + 1); return NULL_REG; case 13: - warning("kList: unimplemented subfunction kMoveToFront"); + error("kList: unimplemented subfunction kMoveToFront"); //return kMoveToFront(s, argc - 1, argv + 1); return NULL_REG; case 14: - warning("kList: unimplemented subfunction kMoveToEnd"); + error("kList: unimplemented subfunction kMoveToEnd"); //return kMoveToEnd(s, argc - 1, argv + 1); return NULL_REG; case 15: @@ -655,7 +636,7 @@ reg_t kList(EngineState *s, int argc, reg_t *argv) { case 22: return kSort(s, argc - 1, argv + 1); default: - warning("kList: Unhandled case %d", argv[0].toUint16()); + error("kList: Unhandled case %d", argv[0].toUint16()); return NULL_REG; } } diff --git a/engines/sci/engine/kmath.cpp b/engines/sci/engine/kmath.cpp index dbf317860f..eab964d624 100644 --- a/engines/sci/engine/kmath.cpp +++ b/engines/sci/engine/kmath.cpp @@ -36,9 +36,6 @@ reg_t kRandom(EngineState *s, int argc, reg_t *argv) { } reg_t kAbs(EngineState *s, int argc, reg_t *argv) { - // This is a hack, but so is the code in Hoyle1 that needs it. - if (argv[0].segment) - return make_reg(0, 0x3e9); // Yes people, this is an object return make_reg(0, abs(argv[0].toSint16())); } @@ -112,7 +109,7 @@ reg_t kCosDiv(EngineState *s, int argc, reg_t *argv) { double cosval = cos(angle * PI / 180.0); if ((cosval < 0.0001) && (cosval > -0.0001)) { - warning("kCosDiv: Attempted division by zero"); + error("kCosDiv: Attempted division by zero"); return SIGNAL_REG; } else return make_reg(0, (int16)(value / cosval)); @@ -124,7 +121,7 @@ reg_t kSinDiv(EngineState *s, int argc, reg_t *argv) { double sinval = sin(angle * PI / 180.0); if ((sinval < 0.0001) && (sinval > -0.0001)) { - warning("kSinDiv: Attempted division by zero"); + error("kSinDiv: Attempted division by zero"); return SIGNAL_REG; } else return make_reg(0, (int16)(value / sinval)); @@ -136,7 +133,7 @@ reg_t kTimesTan(EngineState *s, int argc, reg_t *argv) { param -= 90; if ((param % 90) == 0) { - warning("kTimesTan: Attempted tan(pi/2)"); + error("kTimesTan: Attempted tan(pi/2)"); return SIGNAL_REG; } else return make_reg(0, (int16) - (tan(param * PI / 180.0) * scale)); @@ -147,10 +144,28 @@ reg_t kTimesCot(EngineState *s, int argc, reg_t *argv) { int scale = (argc > 1) ? argv[1].toSint16() : 1; if ((param % 90) == 0) { - warning("kTimesCot: Attempted tan(pi/2)"); + error("kTimesCot: Attempted tan(pi/2)"); return SIGNAL_REG; } else return make_reg(0, (int16)(tan(param * PI / 180.0) * scale)); } +#ifdef ENABLE_SCI32 + +reg_t kMulDiv(EngineState *s, int argc, reg_t *argv) { + int16 multiplicant = argv[0].toSint16(); + int16 multiplier = argv[1].toSint16(); + int16 denominator = argv[2].toSint16(); + + // Sanity check... + if (!denominator) { + error("kMulDiv: attempt to divide by zero (%d * %d / %d", multiplicant, multiplier, denominator); + return NULL_REG; + } + + return make_reg(0, multiplicant * multiplier / denominator); +} + +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kmenu.cpp b/engines/sci/engine/kmenu.cpp index a5f2f01297..c8a6e03556 100644 --- a/engines/sci/engine/kmenu.cpp +++ b/engines/sci/engine/kmenu.cpp @@ -27,7 +27,6 @@ #include "sci/resource.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" -#include "sci/graphics/gui.h" #include "sci/graphics/cursor.h" #include "sci/graphics/menu.h" @@ -74,7 +73,7 @@ reg_t kDrawStatus(EngineState *s, int argc, reg_t *argv) { int16 colorBack = (argc > 2) ? argv[2].toSint16() : g_sci->getResMan()->isVGA() ? 255 : 15; if (!textReference.isNull()) { - // Sometimes this is called without giving text, if thats the case dont process it + // Sometimes this is called without giving text, if thats the case dont process it. text = s->_segMan->getString(textReference); g_sci->_gfxMenu->kernelDrawStatus(g_sci->strSplit(text.c_str(), NULL).c_str(), colorPen, colorBack); @@ -91,10 +90,9 @@ reg_t kDrawMenuBar(EngineState *s, int argc, reg_t *argv) { reg_t kMenuSelect(EngineState *s, int argc, reg_t *argv) { reg_t eventObject = argv[0]; - //bool pauseSound = argc > 1 ? (argv[1].isNull() ? false : true) : false; + bool pauseSound = argc > 1 ? (argv[1].isNull() ? false : true) : true; - // TODO: pauseSound implementation - return g_sci->_gfxMenu->kernelSelect(eventObject); + return g_sci->_gfxMenu->kernelSelect(eventObject, pauseSound); } } // End of namespace Sci diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index f91ba0fd82..b8c62210f9 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -31,17 +31,14 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/gc.h" -#include "sci/graphics/gui.h" #include "sci/graphics/maciconbar.h" namespace Sci { reg_t kRestartGame(EngineState *s, int argc, reg_t *argv) { - s->restarting_flags |= SCI_GAME_IS_RESTARTING_NOW; - s->shrinkStackToBase(); - s->script_abort_flag = 1; // Force vm to abort ASAP + s->abortScriptProcessing = kAbortRestartGame; // Force vm to abort ASAP return NULL_REG; } @@ -49,47 +46,28 @@ reg_t kRestartGame(EngineState *s, int argc, reg_t *argv) { ** Returns the restarting_flag in acc */ reg_t kGameIsRestarting(EngineState *s, int argc, reg_t *argv) { - s->r_acc = make_reg(0, (s->restarting_flags & SCI_GAME_WAS_RESTARTED)); + s->r_acc = make_reg(0, s->gameWasRestarted); if (argc) { // Only happens during replay if (!argv[0].toUint16()) // Set restarting flag - s->restarting_flags &= ~SCI_GAME_WAS_RESTARTED; + s->gameWasRestarted = false; } uint32 neededSleep = 30; - // WORKAROUND: - // LSL3 calculates a machinespeed variable during game startup (right after the filthy questions) - // This one would go through w/o throttling resulting in having to do 1000 pushups or something - // Another way of handling this would be delaying incrementing of "machineSpeed" selector - if (!strcmp(g_sci->getGameID(), "lsl3") && s->currentRoomNumber() == 290) + // WORKAROUND: LSL3 calculates a machinespeed variable during game startup + // (right after the filthy questions). This one would go through w/o + // throttling resulting in having to do 1000 pushups or something. Another + // way of handling this would be delaying incrementing of "machineSpeed" + // selector. + if (g_sci->getGameId() == GID_LSL3 && s->currentRoomNumber() == 290) s->_throttleTrigger = true; - if (!strcmp(g_sci->getGameID(), "iceman") && s->currentRoomNumber() == 27) { + else if (g_sci->getGameId() == GID_ICEMAN && s->currentRoomNumber() == 27) { s->_throttleTrigger = true; neededSleep = 60; } - if (s->_throttleTrigger) { - // Some games seem to get the duration of main loop initially and then switch of animations for the whole game - // based on that (qfg2, iceman). We are now running full speed initially to avoid that. - // It seems like we dont need to do that anymore - //if (s->_throttleCounter < 50) { - // s->_throttleCounter++; - // return s->r_acc; - //} - - uint32 curTime = g_system->getMillis(); - uint32 duration = curTime - s->_throttleLastTime; - - if (duration < neededSleep) { - s->_event->sleep(neededSleep - duration); - s->_throttleLastTime = g_system->getMillis(); - } else { - s->_throttleLastTime = curTime; - } - s->_throttleTrigger = false; - } - + s->speedThrottler(neededSleep); return s->r_acc; } @@ -106,7 +84,11 @@ enum kMemoryInfoFunc { }; reg_t kMemoryInfo(EngineState *s, int argc, reg_t *argv) { - const uint16 size = 0x7fff; // Must not be 0xffff, or some memory calculations will overflow + // The free heap size returned must not be 0xffff, or some memory + // calculations will overflow. Crazy Nick's games handle up to 32746 + // bytes (0x7fea), otherwise they throw a warning that the memory is + // fragmented + const uint16 size = 0x7fea; switch (argv[0].offset) { case K_MEMORYINFO_LARGEST_HEAP_BLOCK: @@ -120,7 +102,7 @@ reg_t kMemoryInfo(EngineState *s, int argc, reg_t *argv) { return make_reg(0, size); default: - warning("Unknown MemoryInfo operation: %04x", argv[0].offset); + error("Unknown MemoryInfo operation: %04x", argv[0].offset); } return NULL_REG; @@ -172,8 +154,8 @@ reg_t kFlushResources(EngineState *s, int argc, reg_t *argv) { reg_t kSetDebug(EngineState *s, int argc, reg_t *argv) { printf("Debug mode activated\n"); - g_debugState.seeking = kDebugSeekNothing; - g_debugState.runningStep = 0; + g_sci->_debugState.seeking = kDebugSeekNothing; + g_sci->_debugState.runningStep = 0; return s->r_acc; } @@ -190,12 +172,12 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) { int retval = 0; // Avoid spurious warning g_system->getTimeAndDate(loc_time); - elapsedTime = g_system->getMillis() - s->game_start_time; + elapsedTime = g_system->getMillis() - s->gameStartTime; int mode = (argc > 0) ? argv[0].toUint16() : 0; if (getSciVersion() <= SCI_VERSION_0_LATE && mode > 1) - warning("kGetTime called in SCI0 with mode %d (expected 0 or 1)", mode); + error("kGetTime called in SCI0 with mode %d (expected 0 or 1)", mode); switch (mode) { case K_NEW_GETTIME_TICKS : @@ -215,7 +197,7 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) { debugC(2, kDebugLevelTime, "GetTime(date) returns %d", retval); break; default: - warning("Attempt to use unknown GetTime mode %d", mode); + error("Attempt to use unknown GetTime mode %d", mode); break; } @@ -261,7 +243,7 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { SegmentRef ref = s->_segMan->dereference(argv[1]); if (!ref.isValid() || ref.maxSize < 2) { - warning("Attempt to peek invalid memory at %04x:%04x", PRINT_REG(argv[1])); + error("Attempt to peek invalid memory at %04x:%04x", PRINT_REG(argv[1])); return s->r_acc; } if (ref.isRaw) @@ -277,7 +259,7 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { SegmentRef ref = s->_segMan->dereference(argv[1]); if (!ref.isValid() || ref.maxSize < 2) { - warning("Attempt to poke invalid memory at %04x:%04x", PRINT_REG(argv[1])); + error("Attempt to poke invalid memory at %04x:%04x", PRINT_REG(argv[1])); return s->r_acc; } @@ -335,11 +317,12 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { bool isWindows = g_sci->getPlatform() == Common::kPlatformWindows; if (argc == 0 && getSciVersion() < SCI_VERSION_2) { - // This is called in KQ5CD with no parameters, where it seems to do some graphics - // driver check. This kernel function didn't have subfunctions then. If 0 is - // returned, the game functions normally, otherwise all the animations show up - // like a slideshow (e.g. in the intro). So we return 0. However, the behavior - // changed for kPlatform with no parameters in SCI32. + // This is called in KQ5CD with no parameters, where it seems to do some + // graphics driver check. This kernel function didn't have subfunctions + // then. If 0 is returned, the game functions normally, otherwise all + // the animations show up like a slideshow (e.g. in the intro). So we + // return 0. However, the behavior changed for kPlatform with no + // parameters in SCI32. return NULL_REG; } @@ -371,10 +354,18 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { case kPlatformIsItWindows: return make_reg(0, isWindows); default: - warning("Unsupported kPlatform operation %d", operation); + error("Unsupported kPlatform operation %d", operation); } return NULL_REG; } +reg_t kEmpty(EngineState *s, int argc, reg_t *argv) { + // Placeholder for empty kernel functions which are still called from the + // engine scripts (like the empty kSetSynonyms function in SCI1.1). This + // differs from dummy functions because it does nothing and never throws a + // warning when it is called. + return s->r_acc; +} + } // End of namespace Sci diff --git a/engines/sci/engine/kmovement.cpp b/engines/sci/engine/kmovement.cpp index 499aeabcc6..ccef3d862a 100644 --- a/engines/sci/engine/kmovement.cpp +++ b/engines/sci/engine/kmovement.cpp @@ -33,42 +33,43 @@ namespace Sci { -/* -Compute "velocity" vector (xStep,yStep)=(vx,vy) for a jump from (0,0) to (dx,dy), with gravity gy. -The gravity is assumed to be non-negative. - -If this was ordinary continuous physics, we would compute the desired (floating point!) -velocity vector (vx,vy) as follows, under the assumption that vx and vy are linearly correlated -by some constant factor c, i.e. vy = c * vx: - dx = t * vx - dy = t * vy + gy * t^2 / 2 -=> dy = c * dx + gy * (dx/vx)^2 / 2 -=> |vx| = sqrt( gy * dx^2 / (2 * (dy - c * dx)) ) -Here, the sign of vx must be chosen equal to the sign of dx, obviously. - -Clearly, this square root only makes sense in our context if the denominator is positive, -or equivalently, (dy - c * dx) must be positive. For simplicity and by symmetry -along the x-axis, we assume dx to be positive for all computations, and only adjust for -its sign in the end. Switching the sign of c appropriately, we set tmp := (dy + c * dx) -and compute c so that this term becomes positive. - -Remark #1: If the jump is straight up, i.e. dx == 0, then we should not assume the above -linear correlation vy = c * vx of the velocities (as vx will be 0, but vy shouldn't be, -unless we drop). - - -Remark #2: We are actually in a discrete setup. The motion is computed iteratively: each iteration, -we add vx and vy to the position, then add gy to vy. So the real formula is the following -(where t is ideally close to an int): - - dx = t * vx - dy = t * vy + gy * t*(t-1) / 2 - -But the solution resulting from that is a lot more complicated, so we use the above approximation instead. - -Still, what we compute in the end is of course not a real velocity anymore, but an integer approximation, -used in an iterative stepping algorithm -*/ +/** + * Compute "velocity" vector (xStep,yStep)=(vx,vy) for a jump from (0,0) to + * (dx,dy), with gravity constant gy. The gravity is assumed to be non-negative. + * + * If this was ordinary continuous physics, we would compute the desired + * (floating point!) velocity vector (vx,vy) as follows, under the assumption + * that vx and vy are linearly correlated by a constant c, i.e., vy = c * vx: + * dx = t * vx + * dy = t * vy + gy * t^2 / 2 + * => dy = c * dx + gy * (dx/vx)^2 / 2 + * => |vx| = sqrt( gy * dx^2 / (2 * (dy - c * dx)) ) + * Here, the sign of vx must be chosen equal to the sign of dx, obviously. + * + * This square root only makes sense in our context if the denominator is + * positive, or equivalently, (dy - c * dx) must be positive. For simplicity + * and by symmetry along the x-axis, we assume dx to be positive for all + * computations, and only adjust for its sign in the end. Switching the sign of + * c appropriately, we set tmp := (dy + c * dx) and compute c so that this term + * becomes positive. + * + * Remark #1: If the jump is straight up, i.e. dx == 0, then we should not + * assume the above linear correlation vy = c * vx of the velocities (as vx + * will be 0, but vy shouldn't be, unless we drop down). + * + * Remark #2: We are actually in a discrete setup. The motion is computed + * iteratively: each iteration, we add vx and vy to the position, then add gy + * to vy. So the real formula is the following (where t ideally is close to an int): + * + * dx = t * vx + * dy = t * vy + gy * t*(t-1) / 2 + * + * But the solution resulting from that is a lot more complicated, so we use + * the above approximation instead. + * + * Still, what we compute in the end is of course not a real velocity anymore, + * but an integer approximation, used in an iterative stepping algorithm. + */ reg_t kSetJump(EngineState *s, int argc, reg_t *argv) { SegManager *segMan = s->_segMan; // Input data @@ -115,7 +116,7 @@ reg_t kSetJump(EngineState *s, int argc, reg_t *argv) { //tmp = dx * 3 / 2; // ALMOST the resulting value, except for obvious rounding issues // FIXME: Where is the 3 coming from? Maybe they hard/coded, by "accident", that usually gy=3 ? - // Then this choice of will make t equal to roughly sqrt(dx) + // Then this choice of scalar will make t equal to roughly sqrt(dx) } } // POST: c >= 1 @@ -315,11 +316,11 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { debugC(2, kDebugLevelBresen, "New data: (x,y)=(%d,%d), di=%d", x, y, bdi); - if (g_sci->getKernel()->_selectorCache.cantBeHere != -1) { - invokeSelector(INV_SEL(s, client, cantBeHere, kStopOnInvalidSelector), 0); + if (SELECTOR(cantBeHere) != -1) { + invokeSelector(s, client, SELECTOR(cantBeHere), argc, argv); s->r_acc = make_reg(0, !s->r_acc.offset); } else { - invokeSelector(INV_SEL(s, client, canBeHere, kStopOnInvalidSelector), 0); + invokeSelector(s, client, SELECTOR(canBeHere), argc, argv); } if (!s->r_acc.offset) { // Contains the return value @@ -335,7 +336,7 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { if ((getSciVersion() >= SCI_VERSION_1_EGA)) if (completed) - invokeSelector(INV_SEL(s, mover, moveDone, kStopOnInvalidSelector), 0); + invokeSelector(s, mover, SELECTOR(moveDone), argc, argv); return make_reg(0, completed); } @@ -373,14 +374,14 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) { s->r_acc = SIGNAL_REG; if (!s->_segMan->isHeapObject(avoider)) { - warning("DoAvoider() where avoider %04x:%04x is not an object", PRINT_REG(avoider)); + error("DoAvoider() where avoider %04x:%04x is not an object", PRINT_REG(avoider)); return NULL_REG; } client = readSelector(segMan, avoider, SELECTOR(client)); if (!s->_segMan->isHeapObject(client)) { - warning("DoAvoider() where client %04x:%04x is not an object", PRINT_REG(client)); + error("DoAvoider() where client %04x:%04x is not an object", PRINT_REG(client)); return NULL_REG; } @@ -389,7 +390,7 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) { if (!s->_segMan->isHeapObject(mover)) { if (mover.segment) { - warning("DoAvoider() where mover %04x:%04x is not an object", PRINT_REG(mover)); + error("DoAvoider() where mover %04x:%04x is not an object", PRINT_REG(mover)); } return s->r_acc; } @@ -399,20 +400,13 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) { debugC(2, kDebugLevelBresen, "Doing avoider %04x:%04x (dest=%d,%d)", PRINT_REG(avoider), destx, desty); - if (invokeSelector(INV_SEL(s, mover, doit, kContinueOnInvalidSelector) , 0)) { - error("Mover %04x:%04x of avoider %04x:%04x doesn't have a doit() funcselector", PRINT_REG(mover), PRINT_REG(avoider)); - return NULL_REG; - } + invokeSelector(s, mover, SELECTOR(doit), argc, argv); mover = readSelector(segMan, client, SELECTOR(mover)); if (!mover.segment) // Mover has been disposed? return s->r_acc; // Return gracefully. - if (invokeSelector(INV_SEL(s, client, isBlocked, kContinueOnInvalidSelector) , 0)) { - error("Client %04x:%04x of avoider %04x:%04x doesn't" - " have an isBlocked() funcselector", PRINT_REG(client), PRINT_REG(avoider)); - return NULL_REG; - } + invokeSelector(s, client, SELECTOR(isBlocked), argc, argv); dx = destx - readSelectorValue(segMan, client, SELECTOR(x)); dy = desty - readSelectorValue(segMan, client, SELECTOR(y)); @@ -439,11 +433,7 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) { debugC(2, kDebugLevelBresen, "Pos (%d,%d): Trying angle %d; delta=(%d,%d)", oldx, oldy, angle, move_x, move_y); - if (invokeSelector(INV_SEL(s, client, canBeHere, kContinueOnInvalidSelector) , 0)) { - error("Client %04x:%04x of avoider %04x:%04x doesn't" - " have a canBeHere() funcselector", PRINT_REG(client), PRINT_REG(avoider)); - return NULL_REG; - } + invokeSelector(s, client, SELECTOR(canBeHere), argc, argv); writeSelectorValue(segMan, client, SELECTOR(x), oldx); writeSelectorValue(segMan, client, SELECTOR(y), oldy); @@ -461,7 +451,7 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) { angle -= 360; } - warning("DoAvoider failed for avoider %04x:%04x", PRINT_REG(avoider)); + error("DoAvoider failed for avoider %04x:%04x", PRINT_REG(avoider)); } else { int heading = readSelectorValue(segMan, client, SELECTOR(heading)); @@ -472,12 +462,11 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) { s->r_acc = make_reg(0, angle); + reg_t params[2] = { make_reg(0, angle), client }; + if (looper.segment) { - if (invokeSelector(INV_SEL(s, looper, doit, kContinueOnInvalidSelector), 2, angle, client)) { - error("Looper %04x:%04x of avoider %04x:%04x doesn't" - " have a doit() funcselector", PRINT_REG(looper), PRINT_REG(avoider)); - } else - return s->r_acc; + invokeSelector(s, looper, SELECTOR(doit), argc, argv, 2, params); + return s->r_acc; } else { // No looper? Fall back to DirLoop _k_dirloop(client, (uint16)angle, s, argc, argv); diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp index 785ff39d22..45493a95d2 100644 --- a/engines/sci/engine/kparse.cpp +++ b/engines/sci/engine/kparse.cpp @@ -94,8 +94,8 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { ResultWordList words; reg_t event = argv[1]; Vocabulary *voc = g_sci->getVocabulary(); - voc->parser_event = event; + reg_t params[2] = { voc->parser_base, stringpos }; bool res = voc->tokenizeString(words, string.c_str(), &error); voc->parserIsValid = false; /* not valid */ @@ -118,7 +118,7 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { s->r_acc = make_reg(0, 1); writeSelectorValue(segMan, event, SELECTOR(claimed), 1); - invokeSelector(INV_SEL(s, s->_gameObj, syntaxFail, kStopOnInvalidSelector), 2, voc->parser_base, stringpos); + invokeSelector(s, g_sci->getGameObject(), SELECTOR(syntaxFail), argc, argv, 2, params); /* Issue warning */ debugC(2, kDebugLevelParser, "Tree building failed"); @@ -141,7 +141,7 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { debugC(2, kDebugLevelParser, "Word unknown: %s", error); /* Issue warning: */ - invokeSelector(INV_SEL(s, s->_gameObj, wordFail, kStopOnInvalidSelector), 2, voc->parser_base, stringpos); + invokeSelector(s, g_sci->getGameObject(), SELECTOR(wordFail), argc, argv, 2, params); free(error); return make_reg(0, 1); /* Tell them that it didn't work */ } diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 857ccc2a08..fdaae3e121 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -436,33 +436,41 @@ static void print_input(EngineState *s, reg_t poly_list, Common::Point start, Co } } +/** + * Computes the area of a triangle + * Parameters: (const Common::Point &) a, b, c: The points of the triangle + * Returns : (int) The area multiplied by two + */ static int area(const Common::Point &a, const Common::Point &b, const Common::Point &c) { - // Computes the area of a triangle - // Parameters: (const Common::Point &) a, b, c: The points of the triangle - // Returns : (int) The area multiplied by two return (b.x - a.x) * (a.y - c.y) - (c.x - a.x) * (a.y - b.y); } +/** + * Determines whether or not a point is to the left of a directed line + * Parameters: (const Common::Point &) a, b: The directed line (a, b) + * (const Common::Point &) c: The query point + * Returns : (int) true if c is to the left of (a, b), false otherwise + */ static bool left(const Common::Point &a, const Common::Point &b, const Common::Point &c) { - // Determines whether or not a point is to the left of a directed line - // Parameters: (const Common::Point &) a, b: The directed line (a, b) - // (const Common::Point &) c: The query point - // Returns : (int) true if c is to the left of (a, b), false otherwise return area(a, b, c) > 0; } +/** + * Determines whether or not three points are collinear + * Parameters: (const Common::Point &) a, b, c: The three points + * Returns : (int) true if a, b, and c are collinear, false otherwise + */ static bool collinear(const Common::Point &a, const Common::Point &b, const Common::Point &c) { - // Determines whether or not three points are collinear - // Parameters: (const Common::Point &) a, b, c: The three points - // Returns : (int) true if a, b, and c are collinear, false otherwise return area(a, b, c) == 0; } +/** + * Determines whether or not a point lies on a line segment + * Parameters: (const Common::Point &) a, b: The line segment (a, b) + * (const Common::Point &) c: The query point + * Returns : (int) true if c lies on (a, b), false otherwise + */ static bool between(const Common::Point &a, const Common::Point &b, const Common::Point &c) { - // Determines whether or not a point lies on a line segment - // Parameters: (const Common::Point &) a, b: The line segment (a, b) - // (const Common::Point &) c: The query point - // Returns : (int) true if c lies on (a, b), false otherwise if (!collinear(a, b, c)) return false; @@ -473,25 +481,29 @@ static bool between(const Common::Point &a, const Common::Point &b, const Common return ((a.y <= c.y) && (c.y <= b.y)) || ((a.y >= c.y) && (c.y >= b.y)); } +/** + * Determines whether or not two line segments properly intersect + * Parameters: (const Common::Point &) a, b: The line segment (a, b) + * (const Common::Point &) c, d: The line segment (c, d) + * Returns : (int) true if (a, b) properly intersects (c, d), false otherwise + */ static bool intersect_proper(const Common::Point &a, const Common::Point &b, const Common::Point &c, const Common::Point &d) { - // Determines whether or not two line segments properly intersect - // Parameters: (const Common::Point &) a, b: The line segment (a, b) - // (const Common::Point &) c, d: The line segment (c, d) - // Returns : (int) true if (a, b) properly intersects (c, d), false otherwise int ab = (left(a, b, c) && left(b, a, d)) || (left(a, b, d) && left(b, a, c)); int cd = (left(c, d, a) && left(d, c, b)) || (left(c, d, b) && left(d, c, a)); return ab && cd; } +/** + * Polygon containment test + * Parameters: (const Common::Point &) p: The point + * (Polygon *) polygon: The polygon + * Returns : (int) CONT_INSIDE if p is strictly contained in polygon, + * CONT_ON_EDGE if p lies on an edge of polygon, + * CONT_OUTSIDE otherwise + * Number of ray crossing left and right + */ static int contained(const Common::Point &p, Polygon *polygon) { - // Polygon containment test - // Parameters: (const Common::Point &) p: The point - // (Polygon *) polygon: The polygon - // Returns : (int) CONT_INSIDE if p is strictly contained in polygon, - // CONT_ON_EDGE if p lies on an edge of polygon, - // CONT_OUTSIDE otherwise - // Number of ray crossing left and right int lcross = 0, rcross = 0; Vertex *vertex; @@ -549,10 +561,12 @@ static int contained(const Common::Point &p, Polygon *polygon) { return CONT_OUTSIDE; } +/** + * Computes polygon area + * Parameters: (Polygon *) polygon: The polygon + * Returns : (int) The area multiplied by two + */ static int polygon_area(Polygon *polygon) { - // Computes polygon area - // Parameters: (Polygon *) polygon: The polygon - // Returns : (int) The area multiplied by two Vertex *first = polygon->vertices.first(); Vertex *v; int size = 0; @@ -567,11 +581,13 @@ static int polygon_area(Polygon *polygon) { return size; } +/** + * Fixes the vertex order of a polygon if incorrect. Contained access + * polygons should have their vertices ordered clockwise, all other types + * anti-clockwise + * Parameters: (Polygon *) polygon: The polygon + */ static void fix_vertex_order(Polygon *polygon) { - // Fixes the vertex order of a polygon if incorrect. Contained access - // polygons should have their vertices ordered clockwise, all other types - // anti-clockwise - // Parameters: (Polygon *) polygon: The polygon int area = polygon_area(polygon); // When the polygon area is positive the vertices are ordered @@ -584,13 +600,15 @@ static void fix_vertex_order(Polygon *polygon) { } } +/** + * Determines whether or not a line from a point to a vertex intersects the + * interior of the polygon, locally at that vertex + * Parameters: (Common::Point) p: The point + * (Vertex *) vertex: The vertex + * Returns : (int) 1 if the line (p, vertex->v) intersects the interior of + * the polygon, locally at the vertex. 0 otherwise + */ static int inside(const Common::Point &p, Vertex *vertex) { - // Determines whether or not a line from a point to a vertex intersects the - // interior of the polygon, locally at that vertex - // Parameters: (Common::Point) p: The point - // (Vertex *) vertex: The vertex - // Returns : (int) 1 if the line (p, vertex->v) intersects the interior of - // the polygon, locally at the vertex. 0 otherwise // Check that it's not a single-vertex polygon if (VERTEX_HAS_EDGES(vertex)) { const Common::Point &prev = CLIST_PREV(vertex)->v; @@ -655,28 +673,34 @@ static VertexList *visible_vertices(PathfindingState *s, Vertex *vertex_cur) { return visVerts; } +/** + * Determines if a point lies on the screen border + * Parameters: (const Common::Point &) p: The point + * Returns : (int) true if p lies on the screen border, false otherwise + */ bool PathfindingState::pointOnScreenBorder(const Common::Point &p) { - // Determines if a point lies on the screen border - // Parameters: (const Common::Point &) p: The point - // Returns : (int) true if p lies on the screen border, false otherwise return (p.x == 0) || (p.x == _width - 1) || (p.y == 0) || (p.y == _height - 1); } +/** + * Determines if an edge lies on the screen border + * Parameters: (const Common::Point &) p, q: The edge (p, q) + * Returns : (int) true if (p, q) lies on the screen border, false otherwise + */ bool PathfindingState::edgeOnScreenBorder(const Common::Point &p, const Common::Point &q) { - // Determines if an edge lies on the screen border - // Parameters: (const Common::Point &) p, q: The edge (p, q) - // Returns : (int) true if (p, q) lies on the screen border, false otherwise return ((p.x == 0 && q.x == 0) || (p.y == 0 && q.y == 0) || ((p.x == _width - 1) && (q.x == _width - 1)) || ((p.y == _height - 1) && (q.y == _height - 1))); } +/** + * Searches for a nearby point that is not contained in a polygon + * Parameters: (FloatPoint) f: The pointf to search nearby + * (Polygon *) polygon: The polygon + * Returns : (int) PF_OK on success, PF_FATAL otherwise + * (Common::Point) *ret: The non-contained point on success + */ static int find_free_point(FloatPoint f, Polygon *polygon, Common::Point *ret) { - // Searches for a nearby point that is not contained in a polygon - // Parameters: (FloatPoint) f: The pointf to search nearby - // (Polygon *) polygon: The polygon - // Returns : (int) PF_OK on success, PF_FATAL otherwise - // (Common::Point) *ret: The non-contained point on success Common::Point p; // Try nearest point first @@ -706,12 +730,14 @@ static int find_free_point(FloatPoint f, Polygon *polygon, Common::Point *ret) { return PF_OK; } +/** + * Computes the near point of a point contained in a polygon + * Parameters: (const Common::Point &) p: The point + * (Polygon *) polygon: The polygon + * Returns : (int) PF_OK on success, PF_FATAL otherwise + * (Common::Point) *ret: The near point of p in polygon on success + */ int PathfindingState::findNearPoint(const Common::Point &p, Polygon *polygon, Common::Point *ret) { - // Computes the near point of a point contained in a polygon - // Parameters: (const Common::Point &) p: The point - // (Polygon *) polygon: The polygon - // Returns : (int) PF_OK on success, PF_FATAL otherwise - // (Common::Point) *ret: The near point of p in polygon on success Vertex *vertex; FloatPoint near_p; uint32 dist = HUGE_DISTANCE; @@ -751,13 +777,15 @@ int PathfindingState::findNearPoint(const Common::Point &p, Polygon *polygon, Co return find_free_point(near_p, polygon, ret); } +/** + * Computes the intersection point of a line segment and an edge (not + * 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 + * (FloatPoint) *ret: The intersection point + */ static int intersection(const Common::Point &a, const Common::Point &b, Vertex *vertex, FloatPoint *ret) { - // Computes the intersection point of a line segment and an edge (not - // 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 - // (FloatPoint) *ret: The intersection point // Parameters of parametric equations float s, t; // Numerator and denominator of equations @@ -790,16 +818,18 @@ static int intersection(const Common::Point &a, const Common::Point &b, Vertex * return PF_ERROR; } +/** + * Computes the nearest intersection point of a line segment and the polygon + * set. Intersection points that are reached from the inside of a polygon + * are ignored as are improper intersections which do not obstruct + * visibility + * Parameters: (PathfindingState *) s: The pathfinding state + * (const Common::Point &) p, q: The line segment (p, q) + * Returns : (int) PF_OK on success, PF_ERROR when no intersections were + * found, PF_FATAL otherwise + * (Common::Point) *ret: On success, the closest intersection point + */ static int nearest_intersection(PathfindingState *s, const Common::Point &p, const Common::Point &q, Common::Point *ret) { - // Computes the nearest intersection point of a line segment and the polygon - // set. Intersection points that are reached from the inside of a polygon - // are ignored as are improper intersections which do not obstruct - // visibility - // Parameters: (PathfindingState *) s: The pathfinding state - // (const Common::Point &) p, q: The line segment (p, q) - // Returns : (int) PF_OK on success, PF_ERROR when no intersections were - // found, PF_FATAL otherwise - // (Common::Point) *ret: On success, the closest intersection point Polygon *polygon = 0; FloatPoint isec; Polygon *ipolygon = 0; @@ -981,14 +1011,16 @@ static Common::Point *fixup_end_point(PathfindingState *s, const Common::Point & return new_end; } +/** + * Merges a point into the polygon set. A new vertex is allocated for this + * point, unless a matching vertex already exists. If the point is on an + * already existing edge that edge is split up into two edges connected by + * the new vertex + * Parameters: (PathfindingState *) s: The pathfinding state + * (const Common::Point &) v: The point to merge + * Returns : (Vertex *) The vertex corresponding to v + */ static Vertex *merge_point(PathfindingState *s, const Common::Point &v) { - // Merges a point into the polygon set. A new vertex is allocated for this - // point, unless a matching vertex already exists. If the point is on an - // already existing edge that edge is split up into two edges connected by - // the new vertex - // Parameters: (PathfindingState *) s: The pathfinding state - // (const Common::Point &) v: The point to merge - // Returns : (Vertex *) The vertex corresponding to v Vertex *vertex; Vertex *v_new; Polygon *polygon; @@ -1029,11 +1061,13 @@ static Vertex *merge_point(PathfindingState *s, const Common::Point &v) { return v_new; } +/** + * Converts an SCI polygon into a Polygon + * Parameters: (EngineState *) s: The game state + * (reg_t) polygon: The SCI polygon to convert + * Returns : (Polygon *) The converted polygon, or NULL on error + */ static Polygon *convert_polygon(EngineState *s, reg_t polygon) { - // Converts an SCI polygon into a Polygon - // Parameters: (EngineState *) s: The game state - // (reg_t) polygon: The SCI polygon to convert - // Returns : (Polygon *) The converted polygon, or NULL on error SegManager *segMan = s->_segMan; int i; reg_t points = readSelector(segMan, polygon, SELECTOR(points)); @@ -1056,7 +1090,7 @@ static Polygon *convert_polygon(EngineState *s, reg_t polygon) { // WORKAROUND: broken polygon in lsl1sci, room 350, after opening elevator // Polygon has 17 points but size is set to 19 - if ((size == 19) && !strcmp(g_sci->getGameID(), "lsl1sci")) { + if ((size == 19) && g_sci->getGameId() == GID_LSL1) { if ((s->currentRoomNumber() == 350) && (read_point(segMan, points, 18) == Common::Point(108, 137))) { debug(1, "Applying fix for broken polygon in lsl1sci, room 350"); @@ -1074,11 +1108,13 @@ static Polygon *convert_polygon(EngineState *s, reg_t polygon) { return poly; } +/** + * Changes the polygon list for optimization level 0 (used for keyboard + * support). Totally accessible polygons are removed and near-point + * accessible polygons are changed into totally accessible polygons. + * Parameters: (PathfindingState *) s: The pathfinding state + */ static void change_polygons_opt_0(PathfindingState *s) { - // Changes the polygon list for optimization level 0 (used for keyboard - // support). Totally accessible polygons are removed and near-point - // accessible polygons are changed into totally accessible polygons. - // Parameters: (PathfindingState *) s: The pathfinding state PolygonList::iterator it = s->polygons.begin(); while (it != s->polygons.end()) { @@ -1096,15 +1132,17 @@ static void change_polygons_opt_0(PathfindingState *s) { } } +/** + * Converts the SCI input data for pathfinding + * Parameters: (EngineState *) s: The game state + * (reg_t) poly_list: Polygon list + * (Common::Point) start: The start point + * (Common::Point) end: The end point + * (int) opt: Optimization level (0, 1 or 2) + * Returns : (PathfindingState *) On success a newly allocated pathfinding state, + * NULL otherwise + */ static PathfindingState *convert_polygon_set(EngineState *s, reg_t poly_list, Common::Point start, Common::Point end, int width, int height, int opt) { - // Converts the SCI input data for pathfinding - // Parameters: (EngineState *) s: The game state - // (reg_t) poly_list: Polygon list - // (Common::Point) start: The start point - // (Common::Point) end: The end point - // (int) opt: Optimization level (0, 1 or 2) - // Returns : (PathfindingState *) On success a newly allocated pathfinding state, - // NULL otherwise SegManager *segMan = s->_segMan; Polygon *polygon; int err; @@ -1174,7 +1212,7 @@ static PathfindingState *convert_polygon_set(EngineState *s, reg_t poly_list, Co // WORKAROUND LSL5 room 660. Priority glitch due to us choosing a different path // than SSCI. Happens when Patti walks to the control room. - if (!strcmp(g_sci->getGameID(), "lsl5") && (s->currentRoomNumber() == 660) && (Common::Point(67, 131) == *new_start) && (Common::Point(229, 101) == *new_end)) { + if (g_sci->getGameId() == GID_LSL5 && (s->currentRoomNumber() == 660) && (Common::Point(67, 131) == *new_start) && (Common::Point(229, 101) == *new_end)) { debug(1, "[avoidpath] Applying fix for priority problem in LSL5, room 660"); pf_s->_prependPoint = new_start; new_start = new Common::Point(77, 107); @@ -1297,11 +1335,13 @@ static reg_t allocateOutputArray(SegManager *segMan, int size) { return addr; } +/** + * Stores the final path in newly allocated dynmem + * Parameters: (PathfindingState *) p: The pathfinding state + * (EngineState *) s: The game state + * Returns : (reg_t) Pointer to dynmem containing path + */ static reg_t output_path(PathfindingState *p, EngineState *s) { - // Stores the final path in newly allocated dynmem - // Parameters: (PathfindingState *) p: The pathfinding state - // (EngineState *) s: The game state - // Returns : (reg_t) Pointer to dynmem containing path int path_len = 0; reg_t output; Vertex *vertex = p->vertex_end; @@ -1694,17 +1734,14 @@ reg_t kIntersections(EngineState *s, int argc, reg_t *argv) { } } -// This is a quite rare kernel function. An example of when it's called -// is in QFG1VGA, after killing any monster. +/** + * This is a quite rare kernel function. An example of when it's called + * is in QFG1VGA, after killing any monster. + */ 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]; - - // TODO: actually merge the polygon - // In QFG1VGA, there are no immediately visible side-effects - // of this being a stub. - -#if 0 List *list = s->_segMan->lookupList(argv[1]); Node *node = s->_segMan->lookupNode(list->first); // List size is not needed @@ -1723,9 +1760,14 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) { } #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); + SegmentRef arrayRef = s->_segMan->dereference(output); + writePoint(arrayRef, 0, Common::Point(POLY_LAST_POINT, POLY_LAST_POINT)); warning("Stub: kMergePoly"); - - return polygonData; + return output; } } // End of namespace Sci diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index 722d0175d1..029943b070 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -25,16 +25,20 @@ #include "sci/sci.h" #include "sci/resource.h" +#include "sci/engine/seg_manager.h" +#include "sci/engine/script.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/engine/kernel.h" +#include "common/file.h" + namespace Sci { // Loads arbitrary resources of type 'restype' with resource numbers 'resnrs' // This implementation ignores all resource numbers except the first one. reg_t kLoad(EngineState *s, int argc, reg_t *argv) { - int restype = argv[0].toUint16(); + ResourceType restype = (ResourceType)(argv[0].toUint16() & 0x7f); int resnr = argv[1].toUint16(); // Request to dynamically allocate hunk memory for later use @@ -44,6 +48,29 @@ reg_t kLoad(EngineState *s, int argc, reg_t *argv) { return make_reg(0, ((restype << 11) | resnr)); // Return the resource identifier as handle } +// Unloads an arbitrary resource of type 'restype' with resource numbber 'resnr' +// behaviour of this call didn't change between sci0->sci1.1 parameter wise, which means getting called with +// 1 or 3+ parameters is not right according to sierra sci +reg_t kUnLoad(EngineState *s, int argc, reg_t *argv) { + if (argc >= 2) { + ResourceType restype = (ResourceType)(argv[0].toUint16() & 0x7f); + reg_t resnr = argv[1]; + + // WORKAROUND for a broken script in room 320 in Castle of Dr. Brain. + // Script 377 tries to free the hunk memory allocated for the saved area + // (underbits) beneath the pop up window, which results in having the + // window stay on screen even when it's closed. Ignore this request here. + if (restype == kResourceTypeMemory && g_sci->getGameId() == GID_CASTLEBRAIN && + s->currentRoomNumber() == 320) + return s->r_acc; + + if (restype == kResourceTypeMemory) + s->_segMan->freeHunkEntry(resnr); + } + + return s->r_acc; +} + reg_t kLock(EngineState *s, int argc, reg_t *argv) { int state = argc > 2 ? argv[2].toUint16() : 1; ResourceType type = (ResourceType)(argv[0].toUint16() & 0x7f); @@ -56,43 +83,46 @@ reg_t kLock(EngineState *s, int argc, reg_t *argv) { g_sci->getResMan()->findResource(id, 1); break; case 0 : - which = g_sci->getResMan()->findResource(id, 0); - - if (which) - g_sci->getResMan()->unlockResource(which); - else { - if (id.type == kResourceTypeInvalid) - warning("[resMan] Attempt to unlock resource %i of invalid type %i", id.number, type); - else - warning("[resMan] Attempt to unlock non-existant resource %s", id.toString().c_str()); + if (id.getNumber() == 0xFFFF) { + // Unlock all resources of the requested type + Common::List<ResourceId> *resources = g_sci->getResMan()->listResources(type); + Common::List<ResourceId>::iterator itr = resources->begin(); + + while (itr != resources->end()) { + Resource *res = g_sci->getResMan()->testResource(*itr); + if (res->isLocked()) + g_sci->getResMan()->unlockResource(res); + ++itr; + } + + } else { + which = g_sci->getResMan()->findResource(id, 0); + + if (which) + g_sci->getResMan()->unlockResource(which); + else { + if (id.getType() == kResourceTypeInvalid) + warning("[resMan] Attempt to unlock resource %i of invalid type %i", id.getNumber(), type); + else + // Happens in CD games (e.g. LSL6CD) with the message resource + warning("[resMan] Attempt to unlock non-existant resource %s", id.toString().c_str()); + } } break; } return s->r_acc; } -// Unloads an arbitrary resource of type 'restype' with resource numbber 'resnr' -reg_t kUnLoad(EngineState *s, int argc, reg_t *argv) { - if (argc >= 2) { - int restype = argv[0].toUint16(); - reg_t resnr = argv[1]; - - if (restype == kResourceTypeMemory) - s->_segMan->freeHunkEntry(resnr); - - if (argc > 2) - warning("kUnload called with more than 2 parameters (%d)", argc); - } else { - warning("kUnload called with less than 2 parameters (%d) - ignoring", argc); - } - - return s->r_acc; -} - reg_t kResCheck(EngineState *s, int argc, reg_t *argv) { Resource *res = NULL; ResourceType restype = (ResourceType)(argv[0].toUint16() & 0x7f); + if (restype == kResourceTypeVMD) { + char fileName[10]; + sprintf(fileName, "%d.vmd", argv[1].toUint16()); + return make_reg(0, Common::File::exists(fileName)); + } + if ((restype == kResourceTypeAudio36) || (restype == kResourceTypeSync36)) { if (argc >= 6) { uint noun = argv[2].toUint16() & 0xff; @@ -155,24 +185,10 @@ reg_t kDisposeClone(EngineState *s, int argc, reg_t *argv) { } if (!victim_obj->isClone()) { - //warning("Attempt to dispose something other than a clone at %04x", offset); // SCI silently ignores this behaviour; some games actually depend on it return s->r_acc; } - // QFG3 clears clones with underbits set - //if (readSelectorValue(victim_addr, underBits)) - // warning("Clone %04x:%04x was cleared with underBits set", PRINT_REG(victim_addr)); - -#if 0 - if (s->dyn_views) { // Free any widget associated with the clone - GfxWidget *widget = gfxw_set_id(gfxw_remove_ID(s->dyn_views, offset), GFXW_NO_ID); - - if (widget && s->bg_widgets) - s->bg_widgets->add(GFXWC(s->bg_widgets), widget); - } -#endif - victim_obj->markAsFreed(); return s->r_acc; @@ -198,9 +214,10 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { // and this call is probably used to load them in memory, ignoring // the return value. If only one argument is passed, this call is done // only to load the script in memory. Thus, don't show any warning, - // as no return value is expected + // as no return value is expected. If an export is requested, then + // it will most certainly fail with OOB access. if (argc == 2) - warning("Script 0x%x does not have a dispatch table and export %d " + error("Script 0x%x does not have a dispatch table and export %d " "was requested from it", script, index); return NULL_REG; } @@ -222,10 +239,6 @@ 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; - // Work around QfG1 graveyard bug - if (argv[0].segment) - return s->r_acc; - SegmentId id = s->_segMan->getScriptSegment(script); Script *scr = s->_segMan->getScriptIfLoaded(id); if (scr) { @@ -233,13 +246,13 @@ reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv) { scr->setLockers(1); } - script_uninstantiate(s->_segMan, script); + s->_segMan->uninstantiateScript(script); if (argc != 2) { return s->r_acc; } else { - // This exists in the KQ5CD and GK1 interpreter. We know it is used when GK1 starts - // up, before the Sierra logo. + // This exists in the KQ5CD and GK1 interpreter. We know it is used + // when GK1 starts up, before the Sierra logo. warning("kDisposeScript called with 2 parameters, still untested"); return argv[1]; } diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 367a89005c..69ae68674b 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -39,12 +39,38 @@ namespace Sci { * Used for synthesized music playback */ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) { - return s->_soundCmd->parseCommand(argc, argv, s->r_acc); + if (!s) + return make_reg(0, g_sci->_features->detectDoSoundType()); + error("not supposed to call this"); } +#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(argc, argv, s->r_acc); } + +CREATE_DOSOUND_FORWARD(DoSoundInit) +CREATE_DOSOUND_FORWARD(DoSoundPlay) +CREATE_DOSOUND_FORWARD(DoSoundDummy) +CREATE_DOSOUND_FORWARD(DoSoundDispose) +CREATE_DOSOUND_FORWARD(DoSoundMute) +CREATE_DOSOUND_FORWARD(DoSoundStop) +CREATE_DOSOUND_FORWARD(DoSoundStopAll) +CREATE_DOSOUND_FORWARD(DoSoundPause) +CREATE_DOSOUND_FORWARD(DoSoundResume) +CREATE_DOSOUND_FORWARD(DoSoundMasterVolume) +CREATE_DOSOUND_FORWARD(DoSoundUpdate) +CREATE_DOSOUND_FORWARD(DoSoundFade) +CREATE_DOSOUND_FORWARD(DoSoundGetPolyphony) +CREATE_DOSOUND_FORWARD(DoSoundUpdateCues) +CREATE_DOSOUND_FORWARD(DoSoundSendMidi) +CREATE_DOSOUND_FORWARD(DoSoundReverb) +CREATE_DOSOUND_FORWARD(DoSoundSetHold) +CREATE_DOSOUND_FORWARD(DoSoundGetAudioCapability) +CREATE_DOSOUND_FORWARD(DoSoundSuspend) +CREATE_DOSOUND_FORWARD(DoSoundSetVolume) +CREATE_DOSOUND_FORWARD(DoSoundSetPriority) +CREATE_DOSOUND_FORWARD(DoSoundSetLoop) + reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { - case kSciAudioWPlay: case kSciAudioPlay: { if (argc < 2) return NULL_REG; @@ -72,6 +98,7 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) { break; case kSciAudioPosition: return make_reg(0, g_sci->_audio->audioCdPosition()); + case kSciAudioWPlay: // CD Audio can't be preloaded case kSciAudioRate: // No need to set the audio rate case kSciAudioVolume: // The speech setting isn't used by CD Audio case kSciAudioLanguage: // No need to set the language @@ -80,7 +107,7 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) { // Init return make_reg(0, 1); default: - warning("kCdDoAudio: Unhandled case %d", argv[0].toUint16()); + error("kCdDoAudio: Unhandled case %d", argv[0].toUint16()); } return s->r_acc; @@ -110,8 +137,10 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { number = argv[1].toUint16(); } else if (argc == 6 || argc == 8) { module = argv[1].toUint16(); - number = ((argv[2].toUint16() & 0xff) << 24) | ((argv[3].toUint16() & 0xff) << 16) | - ((argv[4].toUint16() & 0xff) << 8) | (argv[5].toUint16() & 0xff); + number = ((argv[2].toUint16() & 0xff) << 24) | + ((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"); } else { @@ -119,46 +148,91 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } - return make_reg(0, g_sci->_audio->startAudio(module, number)); // return sample length in ticks + debugC(2, kDebugLevelSound, "kDoAudio: play sample %d, module %d", number, module); + + // return sample length in ticks + if (argv[0].toUint16() == kSciAudioWPlay) + return make_reg(0, g_sci->_audio->wPlayAudio(module, number)); + else + return make_reg(0, g_sci->_audio->startAudio(module, number)); } case kSciAudioStop: + debugC(2, kDebugLevelSound, "kDoAudio: stop"); g_sci->_audio->stopAudio(); break; case kSciAudioPause: + debugC(2, kDebugLevelSound, "kDoAudio: pause"); g_sci->_audio->pauseAudio(); break; case kSciAudioResume: + debugC(2, kDebugLevelSound, "kDoAudio: resume"); g_sci->_audio->resumeAudio(); break; case kSciAudioPosition: + //debugC(2, kDebugLevelSound, "kDoAudio: get position"); // too verbose return make_reg(0, g_sci->_audio->getAudioPosition()); case kSciAudioRate: + debugC(2, kDebugLevelSound, "kDoAudio: set audio rate to %d", argv[1].toUint16()); g_sci->_audio->setAudioRate(argv[1].toUint16()); break; case kSciAudioVolume: { int16 volume = argv[1].toUint16(); volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX); + debugC(2, kDebugLevelSound, "kDoAudio: set volume to %d", volume); +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2_1) { + int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2; + volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX); + mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2); + return make_reg(0, volumePrev); + } else +#endif mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2); - break; } case kSciAudioLanguage: // In SCI1.1: tests for digital audio support - if (getSciVersion() == SCI_VERSION_1_1) + if (getSciVersion() == SCI_VERSION_1_1) { + debugC(2, kDebugLevelSound, "kDoAudio: audio capability test"); return make_reg(0, 1); - else { + } else { int16 language = argv[1].toSint16(); + debugC(2, kDebugLevelSound, "kDoAudio: set language to %d", language); if (language != -1) g_sci->getResMan()->setAudioLanguage(language); - return make_reg(0, g_sci->getSciLanguage()); + kLanguage kLang = g_sci->getSciLanguage(); + g_sci->setSciLanguage(kLang); + + return make_reg(0, kLang); } break; case kSciAudioCD: - return kDoCdAudio(s, argc - 1, argv + 1); - // TODO: There are 3 more functions used in Freddy Pharkas (11, 12 and 13) and new within sierra sci - // Details currently unknown - // kDoAudio sits at seg026:038C + + if (getSciVersion() <= SCI_VERSION_1_1) { + debugC(2, kDebugLevelSound, "kDoAudio: CD audio subop"); + return kDoCdAudio(s, argc - 1, argv + 1); +#ifdef ENABLE_SCI32 + } else { + // TODO: This isn't CD Audio in SCI32 anymore + warning("kDoAudio: Unhandled case 10, %d extra arguments passed", argc - 1); + break; +#endif + } + + // 3 new subops in Pharkas. kDoAudio in Pharkas sits at seg026:038C + case 11: + warning("kDoAudio: Unhandled case 11, %d extra arguments passed", argc - 1); + break; + case 12: + // Seems to be audio sync, used in Pharkas. Silenced the warning due to + // the high level of spam it produces. + //warning("kDoAudio: Unhandled case 12, %d extra arguments passed", argc - 1); + break; + case 13: + // Used in Pharkas whenever a speech sample starts + warning("kDoAudio: Unhandled case 13, %d extra arguments passed", argc - 1); + break; default: warning("kDoAudio: Unhandled case %d, %d extra arguments passed", argv[0].toUint16(), argc - 1); } @@ -195,7 +269,7 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) { g_sci->_audio->stopSoundSync(); break; default: - warning("DoSync: Unhandled subfunction %d", argv[0].toUint16()); + error("DoSync: Unhandled subfunction %d", argv[0].toUint16()); } return s->r_acc; diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 2681b612e9..f2f9543ad2 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -115,12 +115,14 @@ reg_t kStrAt(EngineState *s, int argc, reg_t *argv) { if (argc > 2) { /* Request to modify this char */ tmp.offset &= 0xff00; tmp.offset |= newvalue; + tmp.segment = 0; } } else { value = tmp.offset >> 8; if (argc > 2) { /* Request to modify this char */ tmp.offset &= 0x00ff; tmp.offset |= newvalue << 8; + tmp.segment = 0; } } } @@ -141,19 +143,22 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { int16 result = 0; if (*source == '$') { - // hexadecimal input + // Hexadecimal input result = (int16)strtol(source + 1, NULL, 16); } else { - // decimal input, we can not use strtol/atoi in here, because sierra used atoi BUT it was a non standard compliant - // atoi, that didnt do clipping. In SQ4 we get the door code in here and that's even larger than uint32! + // Decimal input. We can not use strtol/atoi in here, because while + // Sierra used atoi, it was a non standard compliant atoi, that didn't + // do clipping. In SQ4 we get the door code in here and that's even + // larger than uint32! if (*source == '-') { result = -1; source++; } while (*source) { if ((*source < '0') || (*source > '9')) { - // Sierras atoi stopped processing at anything different than number - // Sometimes the input has a trailing space, that's fine (example: lsl3) + // Sierra's atoi stopped processing at anything which is not + // a digit. Sometimes the input has a trailing space, that's + // fine (example: lsl3) if (*source != ' ') { // TODO: this happens in lsl5 right in the intro -> we get '1' '3' 0xCD 0xCD 0xCD 0xCD 0xCD // find out why this happens and fix it @@ -423,15 +428,16 @@ reg_t kGetFarText(EngineState *s, int argc, reg_t *argv) { seeker = (char *)textres->data; - // The second parameter (counter) determines the number of the string inside the text - // resource. + // The second parameter (counter) determines the number of the string + // inside the text resource. while (counter--) { while (*seeker++) ; } - // If the third argument is NULL, allocate memory for the destination. This occurs in - // SCI1 Mac games. The memory will later be freed by the game's scripts. + // If the third argument is NULL, allocate memory for the destination. This + // occurs in SCI1 Mac games. The memory will later be freed by the game's + // scripts. if (argv[2] == NULL_REG) s->_segMan->allocDynmem(strlen(seeker) + 1, "Mac FarText", &argv[2]); @@ -561,7 +567,7 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) { reg_t kSetQuitStr(EngineState *s, int argc, reg_t *argv) { Common::String quitStr = s->_segMan->getString(argv[0]); - debug("Setting quit string to '%s'", quitStr.c_str()); + //debug("Setting quit string to '%s'", quitStr.c_str()); return s->r_acc; } @@ -578,7 +584,8 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { // Make sure target buffer is large enough SegmentRef buf_r = s->_segMan->dereference(argv[0]); if (!buf_r.isValid() || buf_r.maxSize < (int)str.size() + 1) { - warning("StrSplit: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'", PRINT_REG(argv[0]), str.size() + 1, str.c_str()); + warning("StrSplit: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'", + PRINT_REG(argv[0]), str.size() + 1, str.c_str()); return NULL_REG; } s->_segMan->strcpy(argv[0], str.c_str()); diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index 07f8792471..18e60eb521 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -161,7 +161,7 @@ bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &re reader = new MessageReaderV4(res->data, res->size); break; default: - warning("Message: unsupported resource version %d", version); + error("Message: unsupported resource version %d", version); return false; } @@ -259,7 +259,7 @@ void MessageState::popCursorStack() { if (!_cursorStackStack.empty()) _cursorStack = _cursorStackStack.pop(); else - warning("Message: attempt to pop from empty stack"); + error("Message: attempt to pop from empty stack"); } int MessageState::hexDigitToInt(char h) { @@ -330,7 +330,8 @@ bool MessageState::stringStage(Common::String &outstr, const Common::String &inS } // If we find a lowercase character or a digit, it's not a stage direction - if (((inStr[i] >= 'a') && (inStr[i] <= 'z')) || ((inStr[i] >= '0') && (inStr[i] <= '9'))) + // SCI32 seems to support having digits in stage directions + if (((inStr[i] >= 'a') && (inStr[i] <= 'z')) || ((inStr[i] >= '0') && (inStr[i] <= '9') && (getSciVersion() < SCI_VERSION_2))) return false; } diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 9bf23dedf5..0fe5f2088a 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -33,24 +33,18 @@ #include "sci/event.h" #include "sci/engine/features.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/message.h" #include "sci/engine/savegame.h" +#include "sci/engine/selector.h" #include "sci/engine/vm_types.h" #include "sci/engine/script.h" // for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS -#include "sci/graphics/gui.h" #include "sci/graphics/ports.h" #include "sci/sound/audio.h" -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/core.h" -#include "sci/sound/iterator/iterator.h" -#else #include "sci/sound/music.h" -#endif -#ifdef ENABLE_SCI32 -#include "sci/graphics/gui32.h" -#endif +#include "gui/message.h" namespace Sci { @@ -62,47 +56,11 @@ namespace Sci { const uint32 INTMAPPER_MAGIC_KEY = 0xDEADBEEF; -#ifdef USE_OLD_MUSIC_FUNCTIONS -// from ksound.cpp: -SongIterator *build_iterator(ResourceManager *resMan, int song_nr, SongIteratorType type, songit_id_t id); -#endif - - #pragma mark - // TODO: Many of the following sync_*() methods should be turned into member funcs // of the classes they are syncing. -#ifdef USE_OLD_MUSIC_FUNCTIONS -static void sync_songlib(Common::Serializer &s, SongLibrary &obj); -#endif - -static void sync_reg_t(Common::Serializer &s, reg_t &obj) { - s.syncAsUint16LE(obj.segment); - s.syncAsUint16LE(obj.offset); -} - -#ifdef USE_OLD_MUSIC_FUNCTIONS -static void syncSong(Common::Serializer &s, Song &obj) { - s.syncAsSint32LE(obj._handle); - s.syncAsSint32LE(obj._resourceNum); - s.syncAsSint32LE(obj._priority); - s.syncAsSint32LE(obj._status); - s.syncAsSint32LE(obj._restoreBehavior); - s.syncAsSint32LE(obj._restoreTime); - s.syncAsSint32LE(obj._loops); - s.syncAsSint32LE(obj._hold); - - if (s.isLoading()) { - obj._it = 0; - obj._delay = 0; - obj._next = 0; - obj._nextPlaying = 0; - obj._nextStopping = 0; - } -} -#else - #define DEFROBNICATE_HANDLE(handle) (make_reg((handle >> 16) & 0xffff, handle & 0xffff)) void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) { @@ -132,7 +90,7 @@ void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) { fadeTickerStep = 0; } else { // A bit more optimized saving - sync_reg_t(s, soundObj); + soundObj.saveLoadWithSerializer(s); s.syncAsSint16LE(resourceId); s.syncAsSint16LE(dataInc); s.syncAsSint16LE(ticker); @@ -156,7 +114,6 @@ void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) { pStreamAud = 0; } } -#endif // Experimental hack: Use syncWithSerializer to sync. By default, this assume // the object to be synced is a subclass of Serializable and thus tries to invoke @@ -217,31 +174,18 @@ void syncArray(Common::Serializer &s, Common::Array<T> &arr) { template <> void syncWithSerializer(Common::Serializer &s, reg_t &obj) { - sync_reg_t(s, obj); + obj.saveLoadWithSerializer(s); } void SegManager::saveLoadWithSerializer(Common::Serializer &s) { - s.skip(4, VER(9), VER(9)); // OBSOLETE: Used to be reserved_id - s.skip(4, VER(9), VER(18)); // OBSOLETE: Used to be _exportsAreWide - s.skip(4, VER(9), VER(9)); // OBSOLETE: Used to be gc_mark_bits + if (s.isLoading()) + resetSegMan(); + + s.skip(4, VER(12), VER(18)); // OBSOLETE: Used to be _exportsAreWide if (s.isLoading()) { // Reset _scriptSegMap, to be restored below _scriptSegMap.clear(); - - if (s.getVersion() <= 9) { - // OBSOLETE: Skip over the old id_seg_map when loading (we now - // regenerate the equivalent data, in _scriptSegMap, from scratch). - - s.skip(4); // base_value - while (true) { - uint32 key = 0; - s.syncAsSint32LE(key); - if (key == INTMAPPER_MAGIC_KEY) - break; - s.skip(4); // idx - } - } } @@ -257,57 +201,38 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { // If we were saving and mobj == 0, or if we are loading and this is an // entry marked as empty -> skip to next - if (type == SEG_TYPE_INVALID) { + if (type == SEG_TYPE_INVALID) continue; - } - - s.skip(4, VER(9), VER(9)); // OBSOLETE: Used to be _segManagerId // Don't save or load HunkTable segments - if (type == SEG_TYPE_HUNK) { + if (type == SEG_TYPE_HUNK) continue; - } - - // Handle the OBSOLETE type SEG_TYPE_STRING_FRAG -- just ignore it - if (s.isLoading() && type == SEG_TYPE_STRING_FRAG) { - continue; - } - - if (s.isLoading()) { + if (s.isLoading()) mobj = SegmentObj::createSegmentObj(type); - } + assert(mobj); // Let the object sync custom data mobj->saveLoadWithSerializer(s); // If we are loading a script, hook it up in the script->segment map. - if (s.isLoading() && type == SEG_TYPE_SCRIPT) { - _scriptSegMap[((Script *)mobj)->_nr] = i; - } + if (s.isLoading() && type == SEG_TYPE_SCRIPT) + _scriptSegMap[((Script *)mobj)->getScriptNumber()] = i; } - s.syncAsSint32LE(Clones_seg_id); - s.syncAsSint32LE(Lists_seg_id); - s.syncAsSint32LE(Nodes_seg_id); -} - -static void sync_SegManagerPtr(Common::Serializer &s, SegManager *&obj) { - s.skip(1, VER(9), VER(9)); // obsolete: used to be a flag indicating if we got sci11 or not - - if (s.isLoading()) - obj->resetSegMan(); + s.syncAsSint32LE(_clonesSegId); + s.syncAsSint32LE(_listsSegId); + s.syncAsSint32LE(_nodesSegId); - obj->saveLoadWithSerializer(s); + syncArray<Class>(s, _classTable); } - template <> void syncWithSerializer(Common::Serializer &s, Class &obj) { s.syncAsSint32LE(obj.script); - sync_reg_t(s, obj.reg); + obj.reg.saveLoadWithSerializer(s); } static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) { @@ -318,17 +243,20 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) s.syncVersion(CURRENT_SAVEGAME_VERSION); obj.savegame_version = s.getVersion(); s.syncString(obj.game_version); - s.skip(4, VER(9), VER(9)); // obsolete: used to be game version s.syncAsSint32LE(obj.savegame_date); s.syncAsSint32LE(obj.savegame_time); + if (s.getVersion() < 22) { + obj.game_object_offset = 0; + obj.script0_size = 0; + } else { + s.syncAsUint16LE(obj.game_object_offset); + s.syncAsUint16LE(obj.script0_size); + } } void EngineState::saveLoadWithSerializer(Common::Serializer &s) { - s.skip(4, VER(9), VER(9)); // OBSOLETE: Used to be savegame_version - Common::String tmp; - s.syncString(tmp); // OBSOLETE: Used to be game_version - s.skip(4, VER(9), VER(9)); // OBSOLETE: Used to be version + s.syncString(tmp, VER(12), VER(23)); // OBSOLETE: Used to be game_version // OBSOLETE: Saved menus. Skip all of the saved data if (s.getVersion() < 14) { @@ -347,7 +275,6 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { for (int j = 0; j < menuLength; j++) { s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _type s.syncString(tmp); // OBSOLETE: Used to be _keytext - s.skip(4, VER(9), VER(9)); // OBSOLETE: Used to be keytext_size s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _flags s.skip(64, VER(12), VER(12)); // OBSOLETE: Used to be MENU_SAID_SPEC_SIZE @@ -362,16 +289,14 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { s.skip(4, VER(12), VER(12)); // obsolete: used to be status_bar_foreground s.skip(4, VER(12), VER(12)); // obsolete: used to be status_bar_background - if (s.getVersion() >= 13 && g_sci->_gui) { - // Save/Load picPort as well (cause sierra sci also does this) + if (s.getVersion() >= 13 && getSciVersion() <= SCI_VERSION_1_1) { + // Save/Load picPort as well for SCI0-SCI1.1. Necessary for Castle of Dr. Brain, + // as the picPort has been changed when loading during the intro int16 picPortTop, picPortLeft; Common::Rect picPortRect; - if (s.isSaving()) { - // FIXME: _gfxPorts is 0 when using SCI32 code - assert(g_sci->_gfxPorts); + if (s.isSaving()) picPortRect = g_sci->_gfxPorts->kernelGetPicWindow(picPortTop, picPortLeft); - } s.syncAsSint16LE(picPortRect.top); s.syncAsSint16LE(picPortRect.left); @@ -379,17 +304,14 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint16LE(picPortRect.right); s.syncAsSint16LE(picPortTop); s.syncAsSint16LE(picPortLeft); - } - sync_SegManagerPtr(s, _segMan); + if (s.isLoading()) + g_sci->_gfxPorts->kernelSetPicWindow(picPortRect, picPortTop, picPortLeft, false); + } - syncArray<Class>(s, _segMan->_classTable); + _segMan->saveLoadWithSerializer(s); -#ifdef USE_OLD_MUSIC_FUNCTIONS - sync_songlib(s, _sound._songlib); -#else - _soundCmd->syncPlayList(s); -#endif + g_sci->_soundCmd->syncPlayList(s); } void LocalVariables::saveLoadWithSerializer(Common::Serializer &s) { @@ -400,8 +322,8 @@ void LocalVariables::saveLoadWithSerializer(Common::Serializer &s) { void Object::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(_flags); - sync_reg_t(s, _pos); - s.skip(4, VER(9), VER(12)); // OBSOLETE: Used to be variable_names_nr + _pos.saveLoadWithSerializer(s); + s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be variable_names_nr s.syncAsSint32LE(_methodCount); // that's actually a uint16 syncArray<reg_t>(s, _variables); @@ -418,18 +340,18 @@ template <> void syncWithSerializer(Common::Serializer &s, Table<List>::Entry &obj) { s.syncAsSint32LE(obj.next_free); - sync_reg_t(s, obj.first); - sync_reg_t(s, obj.last); + obj.first.saveLoadWithSerializer(s); + obj.last.saveLoadWithSerializer(s); } template <> void syncWithSerializer(Common::Serializer &s, Table<Node>::Entry &obj) { s.syncAsSint32LE(obj.next_free); - sync_reg_t(s, obj.pred); - sync_reg_t(s, obj.succ); - sync_reg_t(s, obj.key); - sync_reg_t(s, obj.value); + obj.pred.saveLoadWithSerializer(s); + obj.succ.saveLoadWithSerializer(s); + obj.key.saveLoadWithSerializer(s); + obj.value.saveLoadWithSerializer(s); } #ifdef ENABLE_SCI32 @@ -463,7 +385,7 @@ void syncWithSerializer(Common::Serializer &s, Table<SciArray<reg_t> >::Entry &o if (s.isSaving()) value = obj.getValue(i); - sync_reg_t(s, value); + value.saveLoadWithSerializer(s); if (s.isLoading()) obj.setValue(i, value); @@ -524,25 +446,15 @@ void HunkTable::saveLoadWithSerializer(Common::Serializer &s) { void Script::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(_nr); - s.syncAsUint32LE(_bufSize); - s.syncAsUint32LE(_scriptSize); - s.syncAsUint32LE(_heapSize); - - if (s.getVersion() <= 10) { - assert((s.isLoading())); - // OBSOLETE: Skip over the old _objIndices data when loading - s.skip(4); // base_value - while (true) { - uint32 key = 0; - s.syncAsSint32LE(key); - if (key == INTMAPPER_MAGIC_KEY) - break; - s.skip(4); // idx - } - } - s.skip(4, VER(9), VER(19)); // OBSOLETE: Used to be _numExports - s.skip(4, VER(9), VER(19)); // OBSOLETE: Used to be _numSynonyms + if (s.isLoading()) + init(_nr, g_sci->getResMan()); + s.skip(4, VER(12), VER(22)); // OBSOLETE: Used to be _bufSize + s.skip(4, VER(12), VER(22)); // OBSOLETE: Used to be _scriptSize + s.skip(4, VER(12), VER(22)); // OBSOLETE: Used to be _heapSize + + s.skip(4, VER(12), VER(19)); // OBSOLETE: Used to be _numExports + s.skip(4, VER(12), VER(19)); // OBSOLETE: Used to be _numSynonyms s.syncAsSint32LE(_lockers); // Sync _objects. This is a hashmap, and we use the following on disk format: @@ -570,7 +482,7 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) { } } - s.syncAsSint32LE(_localsOffset); + s.skip(4, VER(12), VER(20)); // OBSOLETE: Used to be _localsOffset s.syncAsSint32LE(_localsSegment); s.syncAsSint32LE(_markedAsDeleted); @@ -622,30 +534,6 @@ void DataStack::saveLoadWithSerializer(Common::Serializer &s) { #pragma mark - -#ifdef USE_OLD_MUSIC_FUNCTIONS -static void sync_songlib(Common::Serializer &s, SongLibrary &obj) { - int songcount = 0; - if (s.isSaving()) - songcount = obj.countSongs(); - s.syncAsUint32LE(songcount); - - if (s.isLoading()) { - obj._lib = 0; - while (songcount--) { - Song *newsong = new Song; - syncSong(s, *newsong); - obj.addSong(newsong); - } - } else { - Song *seeker = obj._lib; - while (seeker) { - seeker->_restoreTime = seeker->_it->getTimepos(); - syncSong(s, *seeker); - seeker = seeker->_next; - } - } -} -#else void SciMusic::saveLoadWithSerializer(Common::Serializer &s) { // Sync song lib data. When loading, the actual song lib will be initialized // afterwards in gamestate_restore() @@ -694,7 +582,35 @@ void SciMusic::saveLoadWithSerializer(Common::Serializer &s) { } } } -#endif + +void SoundCommandParser::syncPlayList(Common::Serializer &s) { + _music->saveLoadWithSerializer(s); +} + +void SoundCommandParser::reconstructPlayList(int savegame_version) { + Common::StackLock lock(_music->_mutex); + + const MusicList::iterator end = _music->getPlayListEnd(); + for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) { + if ((*i)->resourceId && _resMan->testResource(ResourceId(kResourceTypeSound, (*i)->resourceId))) { + (*i)->soundRes = new SoundResource((*i)->resourceId, _resMan, _soundVersion); + _music->soundInitSnd(*i); + } else { + (*i)->soundRes = 0; + } + if ((*i)->status == kSoundPlaying) { + if (savegame_version < 14) { + (*i)->dataInc = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(dataInc)); + (*i)->signal = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(signal)); + + if (_soundVersion >= SCI_VERSION_1_LATE) + (*i)->volume = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol)); + } + + processPlaySound((*i)->soundObj); + } + } +} #ifdef ENABLE_SCI32 void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) { @@ -712,43 +628,13 @@ void StringTable::saveLoadWithSerializer(Common::Serializer &ser) { } #endif -#pragma mark - - - -int gamestate_save(EngineState *s, Common::WriteStream *fh, const char* savename, const char *version) { - TimeDate curTime; - g_system->getTimeAndDate(curTime); - - SavegameMetadata meta; - meta.savegame_version = CURRENT_SAVEGAME_VERSION; - meta.savegame_name = savename; - meta.game_version = version; - meta.savegame_date = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF); - meta.savegame_time = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min) & 0xFF) << 8) | ((curTime.tm_sec) & 0xFF); - - if (s->execution_stack_base) { - warning("Cannot save from below kernel function"); - return 1; - } - - Common::Serializer ser(0, fh); - sync_SavegameMetadata(ser, meta); - Graphics::saveThumbnail(*fh); - s->saveLoadWithSerializer(ser); // FIXME: Error handling? - - return 0; +void SegManager::reconstructStack(EngineState *s) { + DataStack *stack = (DataStack *)(_heap[findSegmentByType(SEG_TYPE_STACK)]); + s->stack_base = stack->_entries; + s->stack_top = s->stack_base + stack->_capacity; } -// TODO: This should probably be turned into an EngineState or DataStack method. -static void reconstruct_stack(EngineState *retval) { - SegmentId stack_seg = retval->_segMan->findSegmentByType(SEG_TYPE_STACK); - DataStack *stack = (DataStack *)(retval->_segMan->_heap[stack_seg]); - - retval->stack_base = stack->_entries; - retval->stack_top = stack->_entries + stack->_capacity; -} - -// TODO: Move thie function to a more appropriate place, such as vm.cpp or script.cpp +// TODO: Move this function to a more appropriate place, such as vm.cpp or script.cpp void SegManager::reconstructScripts(EngineState *s) { uint i; @@ -761,7 +647,7 @@ void SegManager::reconstructScripts(EngineState *s) { scr->_localsBlock = (scr->_localsSegment == 0) ? NULL : (LocalVariables *)(_heap[scr->_localsSegment]); for (ObjMap::iterator it = scr->_objects.begin(); it != scr->_objects.end(); ++it) - it->_value._baseObj = scr->_buf + it->_value.getPos().offset; + it->_value._baseObj = scr->getBuf(it->_value.getPos().offset); } for (i = 0; i < _heap.size(); i++) { @@ -776,6 +662,8 @@ void SegManager::reconstructScripts(EngineState *s) { if (getSciVersion() < SCI_VERSION_1_1) { if (!obj->initBaseObject(this, addr, false)) { + // TODO/FIXME: This should not be happening at all. It might indicate a possible issue + // with the garbage collector. It happens for example in LSL5 (German, perhaps English too). warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); scr->scriptObjRemove(addr); } @@ -784,47 +672,70 @@ void SegManager::reconstructScripts(EngineState *s) { } } -#ifdef USE_OLD_MUSIC_FUNCTIONS -static void reconstruct_sounds(EngineState *s) { - Song *seeker; - SongIteratorType it_type; +void SegManager::reconstructClones() { + for (uint i = 0; i < _heap.size(); i++) { + SegmentObj *mobj = _heap[i]; + if (mobj && mobj->getType() == SEG_TYPE_CLONES) { + CloneTable *ct = (CloneTable *)mobj; + + for (uint j = 0; j < ct->_table.size(); j++) { + // Check if the clone entry is used + uint entryNum = (uint)ct->first_free; + bool isUsed = true; + while (entryNum != ((uint) CloneTable::HEAPENTRY_INVALID)) { + if (entryNum == j) { + isUsed = false; + break; + } + entryNum = ct->_table[entryNum].next_free; + } + + if (!isUsed) + continue; - if (getSciVersion() > SCI_VERSION_01) - it_type = SCI_SONG_ITERATOR_TYPE_SCI1; - else - it_type = SCI_SONG_ITERATOR_TYPE_SCI0; + CloneTable::Entry &seeker = ct->_table[j]; + const Object *baseObj = getObject(seeker.getSpeciesSelector()); + seeker.cloneFromObject(baseObj); + if (!baseObj) + error("Clone entry without a base class: %d", j); + } // end for + } // end if + } // end for +} - seeker = s->_sound._songlib._lib; - while (seeker) { - SongIterator *base, *ff = 0; - int oldstatus; - SongIterator::Message msg; +#pragma mark - - base = ff = build_iterator(g_sci->getResMan(), seeker->_resourceNum, it_type, seeker->_handle); - if (seeker->_restoreBehavior == RESTORE_BEHAVIOR_CONTINUE) - ff = new_fast_forward_iterator(base, seeker->_restoreTime); - ff->init(); - msg = SongIterator::Message(seeker->_handle, SIMSG_SET_LOOPS(seeker->_loops)); - songit_handle_message(&ff, msg); - msg = SongIterator::Message(seeker->_handle, SIMSG_SET_HOLD(seeker->_hold)); - songit_handle_message(&ff, msg); +bool gamestate_save(EngineState *s, Common::WriteStream *fh, const char* savename, const char *version) { + TimeDate curTime; + g_system->getTimeAndDate(curTime); - oldstatus = seeker->_status; - seeker->_status = SOUND_STATUS_STOPPED; - seeker->_it = ff; - s->_sound.sfx_song_set_status(seeker->_handle, oldstatus); - seeker = seeker->_next; + SavegameMetadata meta; + meta.savegame_version = CURRENT_SAVEGAME_VERSION; + meta.savegame_name = savename; + meta.game_version = version; + meta.savegame_date = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF); + meta.savegame_time = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min) & 0xFF) << 8) | ((curTime.tm_sec) & 0xFF); + + Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false); + meta.script0_size = script0->size; + meta.game_object_offset = g_sci->getGameObject().offset; + + if (s->executionStackBase) { + warning("Cannot save from below kernel function"); + return false; } + + Common::Serializer ser(0, fh); + sync_SavegameMetadata(ser, meta); + Graphics::saveThumbnail(*fh); + s->saveLoadWithSerializer(ser); // FIXME: Error handling? + + return true; } -#endif void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - SongLibrary temp; -#endif - SavegameMetadata meta; Common::Serializer ser(fh, 0); @@ -837,85 +748,62 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if ((meta.savegame_version < MINIMUM_SAVEGAME_VERSION) || (meta.savegame_version > CURRENT_SAVEGAME_VERSION)) { + /* if (meta.savegame_version < MINIMUM_SAVEGAME_VERSION) - warning("Old savegame version detected- can't load"); + warning("Old savegame version detected, unable to load it"); else - warning("Savegame version is %d- maximum supported is %0d", meta.savegame_version, CURRENT_SAVEGAME_VERSION); + warning("Savegame version is %d, maximum supported is %0d", meta.savegame_version, CURRENT_SAVEGAME_VERSION); + */ + + GUI::MessageDialog dialog("The format of this saved game is obsolete, unable to load it", "OK"); + dialog.runModal(); s->r_acc = make_reg(0, 1); // signal failure return; } - if (meta.savegame_version >= 12) { - // We don't need the thumbnail here, so just read it and discard it - Graphics::Surface *thumbnail = new Graphics::Surface(); - assert(thumbnail); - Graphics::loadThumbnail(*fh, *thumbnail); - delete thumbnail; - thumbnail = 0; + if (meta.game_object_offset > 0 && meta.script0_size > 0) { + Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false); + if (script0->size != meta.script0_size || g_sci->getGameObject().offset != meta.game_object_offset) { + //warning("This saved game was created with a different version of the game, unable to load it"); + + GUI::MessageDialog dialog("This saved game was created with a different version of the game, unable to load it", "OK"); + dialog.runModal(); + + s->r_acc = make_reg(0, 1); // signal failure + return; + } } + // We don't need the thumbnail here, so just read it and discard it + Graphics::Surface *thumbnail = new Graphics::Surface(); + assert(thumbnail); + Graphics::loadThumbnail(*fh, *thumbnail); + delete thumbnail; + thumbnail = 0; + s->reset(true); s->saveLoadWithSerializer(ser); // FIXME: Error handling? -#ifdef USE_OLD_MUSIC_FUNCTIONS - s->_sound.sfx_exit(); -#endif - - // Set exec stack base to zero - s->execution_stack_base = 0; - // Now copy all current state information -#ifdef USE_OLD_MUSIC_FUNCTIONS - temp = s->_sound._songlib; - s->_sound.sfx_init(g_sci->getResMan(), s->sfx_init_flags, g_sci->_features->detectDoSoundType()); - s->sfx_init_flags = s->sfx_init_flags; - s->_sound._songlib.freeSounds(); - s->_sound._songlib = temp; - s->_soundCmd->updateSfxState(&retval->_sound); -#endif - - reconstruct_stack(s); + s->_segMan->reconstructStack(s); s->_segMan->reconstructScripts(s); s->_segMan->reconstructClones(); - s->_gameObj = s->_gameObj; - s->script_000 = s->_segMan->getScript(s->_segMan->getScriptSegment(0, SCRIPT_GET_DONT_LOAD)); - s->gc_countdown = GC_INTERVAL - 1; + s->initGlobals(); + s->gcCountDown = GC_INTERVAL - 1; // Time state: - s->last_wait_time = g_system->getMillis(); - s->game_start_time = g_system->getMillis(); - - s->restoring = false; - -#ifdef USE_OLD_MUSIC_FUNCTIONS - s->_sound._it = NULL; - s->_sound._flags = s->_sound._flags; - s->_sound._song = NULL; - s->_sound._suspended = s->_sound._suspended; - reconstruct_sounds(s); -#else - s->_soundCmd->reconstructPlayList(meta.savegame_version); -#endif + s->lastWaitTime = g_system->getMillis(); + s->gameStartTime = g_system->getMillis(); + s->_screenUpdateTime = g_system->getMillis(); + + g_sci->_soundCmd->reconstructPlayList(meta.savegame_version); // Message state: s->_msgState = new MessageState(s->_segMan); -#ifdef ENABLE_SCI32 - if (g_sci->_gui32) { - g_sci->_gui32->init(); - } else { -#endif - g_sci->_gui->resetEngineState(s); - g_sci->_gui->init(g_sci->_features->usesOldGfxFunctions()); -#ifdef ENABLE_SCI32 - } -#endif - - - s->restoring = true; - s->script_abort_flag = 2; // Abort current game with replay + s->abortScriptProcessing = kAbortLoadGame; s->shrinkStackToBase(); } diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 7be05381da..9a882f2bf7 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -36,8 +36,8 @@ namespace Sci { struct EngineState; enum { - CURRENT_SAVEGAME_VERSION = 20, - MINIMUM_SAVEGAME_VERSION = 9 + CURRENT_SAVEGAME_VERSION = 23, + MINIMUM_SAVEGAME_VERSION = 12 }; // Savegame metadata @@ -47,6 +47,8 @@ struct SavegameMetadata { Common::String game_version; int savegame_date; int savegame_time; + uint16 game_object_offset; + uint16 script0_size; }; @@ -57,7 +59,7 @@ struct SavegameMetadata { * @param savename The description of the savegame * @return 0 on success, 1 otherwise */ -int gamestate_save(EngineState *s, Common::WriteStream *save, const char *savename, const char *version); +bool gamestate_save(EngineState *s, Common::WriteStream *save, const char *savename, const char *version); /** * Restores a game state from a directory. diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 1f32e50b67..a293f81d2f 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -35,431 +35,553 @@ namespace Sci { -#define END Script_None - -opcode_format g_opcode_formats[128][4] = { - /*00*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*04*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*08*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*0C*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*10*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*14*/ - {Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END}, - /*18*/ - {Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None}, - /*1C*/ - {Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END}, - /*20*/ - {Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END}, - /*24 (24=ret)*/ - {Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid}, - /*28*/ - {Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END}, - /*2C*/ - {Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid}, - /*30*/ - {Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, - /*34*/ - {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, - /*38*/ - {Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None}, - /*3C*/ - {Script_None}, {Script_None}, {Script_None}, {Script_Word}, - /*40-4F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*50-5F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*60-6F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*70-7F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END} -}; -#undef END - -// TODO: script_adjust_opcode_formats should probably be part of the -// constructor (?) of a VirtualMachine or a ScriptManager class. -void script_adjust_opcode_formats(EngineState *s) { - // TODO: Check that this is correct - if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) { - g_opcode_formats[op_lofsa][0] = Script_Offset; - g_opcode_formats[op_lofss][0] = Script_Offset; - } +Script::Script() : SegmentObj(SEG_TYPE_SCRIPT) { + _nr = 0; + _buf = NULL; + _bufSize = 0; + _scriptSize = 0; + _heapSize = 0; + + _synonyms = NULL; + _heapStart = NULL; + _exportTable = NULL; + + _localsOffset = 0; + _localsSegment = 0; + _localsBlock = NULL; + _localsCount = 0; + + _markedAsDeleted = false; +} + +Script::~Script() { + freeScript(); +} + +void Script::freeScript() { + free(_buf); + _buf = NULL; + _bufSize = 0; -#ifdef ENABLE_SCI32 - // In SCI32, some arguments are now words instead of bytes - if (getSciVersion() >= SCI_VERSION_2) { - g_opcode_formats[op_calle][2] = Script_Word; - g_opcode_formats[op_callk][1] = Script_Word; - g_opcode_formats[op_super][1] = Script_Word; - g_opcode_formats[op_send][0] = Script_Word; - g_opcode_formats[op_self][0] = Script_Word; - g_opcode_formats[op_call][1] = Script_Word; - g_opcode_formats[op_callb][1] = Script_Word; + _objects.clear(); +} + +void Script::init(int script_nr, ResourceManager *resMan) { + Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0); + + _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; + + if (getSciVersion() == SCI_VERSION_0_EARLY) { + _bufSize += READ_LE_UINT16(script->data) * 2; + } else if (getSciVersion() >= SCI_VERSION_1_1) { + // In SCI11, the heap was in a separate space from the script. We append + // it to the end of the script, and adjust addressing accordingly. + // However, since we address the heap with a 16-bit pointer, the + // combined size of the stack and the heap must be 64KB. So far this has + // worked for SCI11, SCI2 and SCI21 games. SCI3 games use a different + // script format, and theoretically they can exceed the 64KB boundary + // using relocation. + Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, script_nr), 0); + _bufSize += heap->size; + _heapSize = heap->size; + + // Ensure that the start of the heap resource can be word-aligned. + if (script->size & 2) { + _bufSize++; + _scriptSize++; + } + + // As mentioned above, the script and the heap together should not exceed 64KB + if (script->size + heap->size > 65535) + error("Script and heap sizes combined exceed 64K. This means a fundamental " + "design bug was made regarding SCI1.1 and newer games.\n" + "Please report this error to the ScummVM team"); } -#endif } -void SegManager::createClassTable() { - Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1); +void Script::load(ResourceManager *resMan) { + Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0); + assert(script != 0); - if (!vocab996) - error("SegManager: failed to open vocab 996"); + _buf = (byte *)malloc(_bufSize); + assert(_buf); - int totalClasses = vocab996->size >> 2; - _classTable.resize(totalClasses); + assert(_bufSize >= script->size); + memcpy(_buf, script->data, script->size); + + if (getSciVersion() >= SCI_VERSION_1_1) { + Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0); + assert(heap != 0); - for (uint16 classNr = 0; classNr < totalClasses; classNr++) { - uint16 scriptNr = READ_SCI11ENDIAN_UINT16(vocab996->data + classNr * 4 + 2); + _heapStart = _buf + _scriptSize; - _classTable[classNr].reg = NULL_REG; - _classTable[classNr].script = scriptNr; + assert(_bufSize - _scriptSize <= heap->size); + memcpy(_heapStart, heap->data, heap->size); } - _resMan->unlockResource(vocab996); -} + _exportTable = 0; + _numExports = 0; + _synonyms = 0; + _numSynonyms = 0; + + if (getSciVersion() >= SCI_VERSION_1_1) { + if (READ_LE_UINT16(_buf + 1 + 5) > 0) { // does the script have an export table? + _exportTable = (const uint16 *)(_buf + 1 + 5 + 2); + _numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1); + } + _localsOffset = _scriptSize + 4; + _localsCount = READ_SCI11ENDIAN_UINT16(_buf + _localsOffset - 2); + } else { + _exportTable = (const uint16 *)findBlock(SCI_OBJ_EXPORTS); + if (_exportTable) { + _numExports = READ_SCI11ENDIAN_UINT16(_exportTable + 1); + _exportTable += 3; // skip header plus 2 bytes (_exportTable is a uint16 pointer) + } + _synonyms = findBlock(SCI_OBJ_SYNONYMS); + if (_synonyms) { + _numSynonyms = READ_SCI11ENDIAN_UINT16(_synonyms + 2) / 4; + _synonyms += 4; // skip header + } + const byte* localsBlock = findBlock(SCI_OBJ_LOCALVARS); + if (localsBlock) { + _localsOffset = localsBlock - _buf + 4; + _localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1; // half block size + } + } -reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) { - if (classnr == 0xffff) - return NULL_REG; + if (getSciVersion() > SCI_VERSION_0_EARLY) { + // Does the script actually have locals? If not, set the locals offset to 0 + if (!_localsCount) + _localsOffset = 0; - if (classnr < 0 || (int)_classTable.size() <= classnr || _classTable[classnr].script < 0) { - error("[VM] Attempt to dereference class %x, which doesn't exist (max %x)", classnr, _classTable.size()); - return NULL_REG; + if (_localsOffset + _localsCount * 2 + 1 >= (int)_bufSize) { + error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, _bufSize); + _localsCount = (_bufSize - _localsOffset) >> 1; + } } else { - Class *the_class = &_classTable[classnr]; - if (!the_class->reg.segment) { - getScriptSegment(the_class->script, lock); + // Old script block. There won't be a localvar block in this case. + // Instead, the script starts with a 16 bit int specifying the + // number of locals we need; these are then allocated and zeroed. + _localsCount = READ_LE_UINT16(_buf); + _localsOffset = -_localsCount * 2; // Make sure it's invalid + } +} - if (!the_class->reg.segment) { - 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(); +Object *Script::getObject(uint16 offset) { + if (_objects.contains(offset)) + return &_objects[offset]; + else + return 0; +} - return the_class->reg; - } +const Object *Script::getObject(uint16 offset) const { + if (_objects.contains(offset)) + return &_objects[offset]; + else + return 0; } -void SegManager::scriptInitialiseLocalsZero(SegmentId seg, int count) { - Script *scr = getScript(seg); +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) - scr->_localsOffset = -count * 2; // Make sure it's invalid + VERIFY(obj_pos.offset < _bufSize, "Attempt to initialize object beyond end of script\n"); - LocalVariables *locals = allocLocalsSegment(scr, count); - if (locals) { - for (int i = 0; i < count; i++) - locals->_locals[i] = NULL_REG; - } + VERIFY(obj_pos.offset + kOffsetFunctionArea < (int)_bufSize, "Function area pointer stored beyond end of script\n"); + + // 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]; + obj->init(_buf, obj_pos, fullObjectInit); + + return obj; } -void SegManager::scriptInitialiseLocals(reg_t location) { - Script *scr = getScript(location.segment); - unsigned int count; +void Script::scriptObjRemove(reg_t obj_pos) { + if (getSciVersion() < SCI_VERSION_1_1) + obj_pos.offset += 8; - VERIFY(location.offset + 1 < (uint16)scr->getBufSize(), "Locals beyond end of script\n"); + _objects.erase(obj_pos.toUint16()); +} - if (getSciVersion() >= SCI_VERSION_1_1) - count = READ_SCI11ENDIAN_UINT16(scr->_buf + location.offset - 2); - else - count = (READ_LE_UINT16(scr->_buf + location.offset - 2) - 4) >> 1; - // half block size +// This helper function is used by Script::relocateLocal and Object::relocate +// Duplicate in segment.cpp and script.cpp +static bool relocateBlock(Common::Array<reg_t> &block, int block_location, SegmentId segment, int location, size_t scriptSize) { + int rel = location - block_location; - scr->_localsOffset = location.offset; + if (rel < 0) + return false; - if (!(location.offset + count * 2 + 1 < scr->getBufSize())) { - warning("Locals extend beyond end of script: offset %04x, count %x vs size %x", location.offset, count, (uint)scr->getBufSize()); - count = (scr->getBufSize() - location.offset) >> 1; - } + uint idx = rel >> 1; - LocalVariables *locals = allocLocalsSegment(scr, count); - if (locals) { - uint i; - const byte *base = (const byte *)(scr->_buf + location.offset); + if (idx >= block.size()) + return false; - for (i = 0; i < count; i++) - locals->_locals[i] = make_reg(0, READ_SCI11ENDIAN_UINT16(base + i * 2)); + if (rel & 1) { + error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); + return false; } + block[idx].segment = segment; // Perform relocation + if (getSciVersion() >= SCI_VERSION_1_1) + block[idx].offset += scriptSize; + + return true; } -void SegManager::scriptInitialiseObjectsSci11(SegmentId seg) { - Script *scr = getScript(seg); - const byte *seeker = scr->_heapStart + 4 + READ_SCI11ENDIAN_UINT16(scr->_heapStart + 2) * 2; +bool Script::relocateLocal(SegmentId segment, int location) { + if (_localsBlock) + return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, _scriptSize); + else + return false; +} - while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { - if (READ_SCI11ENDIAN_UINT16(seeker + 14) & kInfoFlagClass) { // -info- selector - int classpos = seeker - scr->_buf; - int species = READ_SCI11ENDIAN_UINT16(seeker + 10); - - if (species < 0 || species >= (int)_classTable.size()) { - error("Invalid species %d(0x%x) not in interval [0,%d) while instantiating script %d", - species, species, _classTable.size(), scr->_nr); - return; - } +void Script::relocate(reg_t block) { + const byte *heap = _buf; + uint16 heapSize = (uint16)_bufSize; + uint16 heapOffset = 0; - _classTable[species].reg.segment = seg; - _classTable[species].reg.offset = classpos; - } - seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * 2; + if (getSciVersion() >= SCI_VERSION_1_1) { + heap = _heapStart; + heapSize = (uint16)_heapSize; + heapOffset = _scriptSize; } - seeker = scr->_heapStart + 4 + READ_SCI11ENDIAN_UINT16(scr->_heapStart + 2) * 2; - while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { - reg_t reg = make_reg(seg, seeker - scr->_buf); - Object *obj = scr->scriptObjInit(reg); - - // Copy base from species class, as we need its selector IDs - obj->setSuperClassSelector( - getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); - - // If object is instance, get -propDict- from class and set it for this object - // This is needed for ::isMemberOf() to work. - // Example testcase - room 381 of sq4cd - if isMemberOf() doesn't work, talk-clicks on the robot will act like - // clicking on ego - if (!obj->isClass()) { - reg_t classObject = obj->getSuperClassSelector(); - Object *classObj = getObject(classObject); - obj->setPropDictSelector(classObj->getPropDictSelector()); + VERIFY(block.offset < (uint16)heapSize && READ_SCI11ENDIAN_UINT16(heap + block.offset) * 2 + block.offset < (uint16)heapSize, + "Relocation block outside of script\n"); + + int count = READ_SCI11ENDIAN_UINT16(heap + block.offset); + int exportIndex = 0; + int pos = 0; + + for (int i = 0; i < count; i++) { + pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 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; + if (!pos) + error("Script::relocate(): Consecutive zero exports found"); } - // Set the -classScript- selector to the script number. - // FIXME: As this selector is filled in at run-time, it is likely - // that it is supposed to hold a pointer. The Obj::isKindOf method - // uses this selector together with -propDict- to compare classes. - // For the purpose of Obj::isKindOf, using the script number appears - // to be sufficient. - obj->setClassScriptSelector(make_reg(0, scr->_nr)); + // In SCI0-SCI1, script local variables, objects and code are relocated. + // 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)) { + // 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.relocate(block.segment, pos, _scriptSize)) + break; + } - seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * 2; + exportIndex++; } } -void script_instantiate_sci0(Script *scr, int segmentId, SegManager *segMan) { - int objType; - uint32 objLength = 0; - bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - uint16 curOffset = oldScriptHeader ? 2 : 0; +void Script::incrementLockers() { + _lockers++; +} - if (oldScriptHeader) { - // Old script block - // There won't be a localvar block in this case - // Instead, the script starts with a 16 bit int specifying the - // number of locals we need; these are then allocated and zeroed. - int localsCount = READ_LE_UINT16(scr->_buf); - if (localsCount) - segMan->scriptInitialiseLocalsZero(segmentId, localsCount); - } +void Script::decrementLockers() { + if (_lockers > 0) + _lockers--; +} - // Now do a first pass through the script objects to find the - // local variable blocks +int Script::getLockers() const { + return _lockers; +} - do { - objType = scr->getHeap(curOffset); - if (!objType) - break; +void Script::setLockers(int lockers) { + _lockers = lockers; +} - objLength = scr->getHeap(curOffset + 2); - curOffset += 4; // skip header +uint16 Script::validateExportFunc(int pubfunct) { + bool exportsAreWide = (g_sci->_features->detectLofsType() == SCI_VERSION_1_MIDDLE); - switch (objType) { - case SCI_OBJ_LOCALVARS: - segMan->scriptInitialiseLocals(make_reg(segmentId, curOffset)); - break; - case SCI_OBJ_CLASS: { - int classpos = curOffset - SCRIPT_OBJECT_MAGIC_OFFSET; - int species = scr->getHeap(curOffset - SCRIPT_OBJECT_MAGIC_OFFSET + SCRIPT_SPECIES_OFFSET); - if (species < 0 || species >= (int)segMan->classTableSize()) { - if (species == (int)segMan->classTableSize()) { - // Happens in the LSL2 demo - warning("Applying workaround for an off-by-one invalid species access"); - segMan->resizeClassTable(segMan->classTableSize() + 1); - } else { - error("Invalid species %d(0x%x) not in interval " - "[0,%d) while instantiating script at segment %d\n", - species, species, segMan->classTableSize(), - segmentId); - return; - } - } + if (_numExports <= pubfunct) { + error("validateExportFunc(): pubfunct is invalid"); + return 0; + } - segMan->setClassOffset(species, make_reg(segmentId, classpos)); - // Set technical class position-- into the block allocated for it - } - break; + if (exportsAreWide) + pubfunct *= 2; + uint16 offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct); + VERIFY(offset < _bufSize, "invalid export function pointer"); - default: - break; - } + return offset; +} - curOffset += objLength - 4; - } while (objType != 0 && curOffset < scr->getScriptSize() - 2); +byte *Script::findBlock(int type) { + byte *buf = _buf; + bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - // And now a second pass to adjust objects and class pointers, and the general pointers - objLength = 0; - curOffset = oldScriptHeader ? 2 : 0; + if (oldScriptHeader) + buf += 2; do { - objType = scr->getHeap(curOffset); - if (!objType) + int seekerType = READ_LE_UINT16(buf); + + if (seekerType == 0) break; + if (seekerType == type) + return buf; - objLength = scr->getHeap(curOffset + 2); - curOffset += 4; // skip header + int seekerSize = READ_LE_UINT16(buf + 2); + assert(seekerSize > 0); + buf += seekerSize; + } while (1); - reg_t addr = make_reg(segmentId, curOffset); + return NULL; +} - switch (objType) { - case SCI_OBJ_CODE: - scr->scriptAddCodeBlock(addr); - break; - case SCI_OBJ_OBJECT: - case SCI_OBJ_CLASS: { // object or class? - Object *obj = scr->scriptObjInit(addr); - obj->initSpecies(segMan, addr); +// memory operations - if (!obj->initBaseObject(segMan, addr)) { - warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); - scr->scriptObjRemove(addr); - } - } // if object or class - break; - default: - break; - } +void Script::mcpyInOut(int dst, const void *src, size_t n) { + if (_buf) { + assert(dst + n <= _bufSize); + memcpy(_buf + dst, src, n); + } +} + +bool Script::isValidOffset(uint16 offset) const { + return offset < _bufSize; +} + +SegmentRef Script::dereference(reg_t pointer) { + if (pointer.offset > _bufSize) { + error("Script::dereference(): Attempt to dereference invalid pointer %04x:%04x into script segment (script size=%d)", + PRINT_REG(pointer), (uint)_bufSize); + return SegmentRef(); + } - curOffset += objLength - 4; - } while (objType != 0 && curOffset < scr->getScriptSize() - 2); + SegmentRef ret; + ret.isRaw = true; + ret.maxSize = _bufSize - pointer.offset; + ret.raw = _buf + pointer.offset; + return ret; } -int script_instantiate(ResourceManager *resMan, SegManager *segMan, int scriptNum) { - SegmentId segmentId = segMan->getScriptSegment(scriptNum); - Script *scr = segMan->getScriptIfLoaded(segmentId); - if (scr) { - if (!scr->isMarkedAsDeleted()) { - scr->incrementLockers(); - return segmentId; +void Script::initialiseLocals(SegManager *segMan) { + LocalVariables *locals = segMan->allocLocalsSegment(this); + if (locals) { + if (getSciVersion() > SCI_VERSION_0_EARLY) { + const byte *base = (const byte *)(_buf + getLocalsOffset()); + + for (uint16 i = 0; i < getLocalsCount(); i++) + locals->_locals[i] = make_reg(0, READ_SCI11ENDIAN_UINT16(base + i * 2)); } else { - scr->freeScript(); + // In SCI0 early, locals are set at run time, thus zero them all here + for (uint16 i = 0; i < getLocalsCount(); i++) + locals->_locals[i] = NULL_REG; } - } else { - scr = segMan->allocateScript(scriptNum, &segmentId); } +} - scr->init(scriptNum, resMan); - scr->load(resMan); - +void Script::initialiseClasses(SegManager *segMan) { + const byte *seeker = 0; + uint16 mult = 0; + if (getSciVersion() >= SCI_VERSION_1_1) { - int heapStart = scr->getScriptSize(); - segMan->scriptInitialiseLocals(make_reg(segmentId, heapStart + 4)); - segMan->scriptInitialiseObjectsSci11(segmentId); - scr->relocate(make_reg(segmentId, READ_SCI11ENDIAN_UINT16(scr->_heapStart))); + seeker = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; + mult = 2; } else { - script_instantiate_sci0(scr, segmentId, segMan); - byte *relocationBlock = scr->findBlock(SCI_OBJ_POINTERS); - if (relocationBlock) - scr->relocate(make_reg(segmentId, relocationBlock - scr->_buf + 4)); + seeker = findBlock(SCI_OBJ_CLASS); + mult = 1; } - return segmentId; -} - -void script_uninstantiate_sci0(SegManager *segMan, int script_nr, SegmentId seg) { - bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - reg_t reg = make_reg(seg, oldScriptHeader ? 2 : 0); - int objType, objLength = 0; - Script *scr = segMan->getScript(seg); + if (!seeker) + return; - // Make a pass over the object in order uninstantiate all superclasses + uint16 marker; + bool isClass; + uint16 classpos; + int16 species = 0; - do { - reg.offset += objLength; // Step over the last checked object + while (true) { + // In SCI0-SCI1, this is the segment type. In SCI11, it's a marker (0x1234) + marker = READ_SCI11ENDIAN_UINT16(seeker); + classpos = seeker - _buf; - objType = scr->getHeap(reg.offset); - if (!objType) + if (!marker) break; - objLength = scr->getHeap(reg.offset + 2); // use SEG_UGET_HEAP ?? - reg.offset += 4; // Step over header + if (getSciVersion() >= SCI_VERSION_1_1) { + isClass = (READ_SCI11ENDIAN_UINT16(seeker + 14) & kInfoFlagClass); // -info- selector + species = READ_SCI11ENDIAN_UINT16(seeker + 10); + } else { + isClass = (marker == SCI_OBJ_CLASS); + if (isClass) + species = READ_SCI11ENDIAN_UINT16(seeker + 12); + classpos += 12; + } - if ((objType == SCI_OBJ_OBJECT) || (objType == SCI_OBJ_CLASS)) { // object or class? - int superclass; + if (isClass) { + // WORKAROUND for an invalid species access in the demo of LSL2 + if (g_sci->getGameId() == GID_LSL2 && g_sci->isDemo() && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); - reg.offset -= SCRIPT_OBJECT_MAGIC_OFFSET; + if (species < 0 || species >= (int)segMan->classTableSize()) + error("Invalid species %d(0x%x) not in interval [0,%d) while instantiating script %d\n", + species, species, segMan->classTableSize(), _nr); - superclass = scr->getHeap(reg.offset + SCRIPT_SUPERCLASS_OFFSET); // Get superclass... + SegmentId segmentId = segMan->getScriptSegment(_nr); + segMan->setClassOffset(species, make_reg(segmentId, classpos)); + } + + seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * mult; + } +} - if (superclass >= 0) { - int superclass_script = segMan->getClass(superclass).script; +void Script::initialiseObjectsSci0(SegManager *segMan, SegmentId segmentId) { + bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); + const byte *seeker = _buf + (oldScriptHeader ? 2 : 0); + + do { + uint16 objType = READ_SCI11ENDIAN_UINT16(seeker); + if (!objType) + break; - if (superclass_script == script_nr) { - if (scr->getLockers()) - scr->decrementLockers(); // Decrease lockers if this is us ourselves - } else - script_uninstantiate(segMan, superclass_script); - // Recurse to assure that the superclass lockers number gets decreased + switch (objType) { + case SCI_OBJ_OBJECT: + case SCI_OBJ_CLASS: + { + reg_t addr = make_reg(segmentId, seeker - _buf + 4); + Object *obj = scriptObjInit(addr); + obj->initSpecies(segMan, addr); + + if (!obj->initBaseObject(segMan, addr)) { + if (_nr == 202 && g_sci->getGameId() == GID_KQ5 && g_sci->getSciLanguage() == K_LANG_FRENCH) { + // Script 202 of KQ5 French has an invalid object. This is non-fatal. + } else { + error("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); + } + scriptObjRemove(addr); + } } + break; - reg.offset += SCRIPT_OBJECT_MAGIC_OFFSET; - } // if object or class + default: + break; + } - reg.offset -= 4; // Step back on header + seeker += READ_SCI11ENDIAN_UINT16(seeker + 2); + } while ((uint32)(seeker - _buf) < getScriptSize() - 2); - } while (objType != 0); + byte *relocationBlock = findBlock(SCI_OBJ_POINTERS); + if (relocationBlock) + relocate(make_reg(segmentId, relocationBlock - getBuf() + 4)); } -void script_uninstantiate(SegManager *segMan, int script_nr) { - SegmentId segment = segMan->getScriptSegment(script_nr); - Script *scr = segMan->getScriptIfLoaded(segment); +void Script::initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId) { + const byte *seeker = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; - if (!scr) { // Is it already loaded? - //warning("unloading script 0x%x requested although not loaded", script_nr); - // This is perfectly valid SCI behaviour - return; - } + while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { + reg_t reg = make_reg(segmentId, seeker - _buf); + Object *obj = scriptObjInit(reg); - scr->decrementLockers(); // One less locker + // Copy base from species class, as we need its selector IDs + obj->setSuperClassSelector( + segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); - if (scr->getLockers() > 0) - return; + // If object is instance, get -propDict- from class and set it for this + // object. This is needed for ::isMemberOf() to work. + // Example testcase - room 381 of sq4cd - if isMemberOf() doesn't work, + // talk-clicks on the robot will act like clicking on ego + if (!obj->isClass()) { + reg_t classObject = obj->getSuperClassSelector(); + const Object *classObj = segMan->getObject(classObject); + obj->setPropDictSelector(classObj->getPropDictSelector()); + } - // Free all classtable references to this script - for (uint i = 0; i < segMan->classTableSize(); i++) - if (segMan->getClass(i).reg.segment == segment) - segMan->setClassOffset(i, NULL_REG); + // Set the -classScript- selector to the script number. + // FIXME: As this selector is filled in at run-time, it is likely + // that it is supposed to hold a pointer. The Obj::isKindOf method + // uses this selector together with -propDict- to compare classes. + // For the purpose of Obj::isKindOf, using the script number appears + // to be sufficient. + obj->setClassScriptSelector(make_reg(0, _nr)); - if (getSciVersion() < SCI_VERSION_1_1) - script_uninstantiate_sci0(segMan, script_nr, segment); - // FIXME: Add proper script uninstantiation for SCI 1.1 + seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * 2; + } - if (scr->getLockers()) - return; // if xxx.lockers > 0 + relocate(make_reg(segmentId, READ_SCI11ENDIAN_UINT16(_heapStart))); +} + +reg_t Script::findCanonicAddress(SegManager *segMan, reg_t addr) const { + addr.offset = 0; + return addr; +} - // Otherwise unload it completely - // Explanation: I'm starting to believe that this work is done by SCI itself. - scr->markDeleted(); +void Script::freeAtAddress(SegManager *segMan, reg_t addr) { + /* + debugC(2, kDebugLevelGC, "[GC] Freeing script %04x:%04x", PRINT_REG(addr)); + if (_localsSegment) + debugC(2, kDebugLevelGC, "[GC] Freeing locals %04x:0000", _localsSegment); + */ + + if (_markedAsDeleted) + segMan->deallocateScript(_nr); +} - debugC(kDebugLevelScripts, "Unloaded script 0x%x.", script_nr); +Common::Array<reg_t> Script::listAllDeallocatable(SegmentId segId) const { + const reg_t r = make_reg(segId, 0); + return Common::Array<reg_t>(&r, 1); +} - return; +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 (obj) { + // Note all local variables, if we have a local variable environment + if (_localsSegment) + tmp.push_back(make_reg(_localsSegment, 0)); + + for (uint i = 0; i < obj->getVarCount(); i++) + tmp.push_back(obj->getVariable(i)); + } else { + error("Request for outgoing script-object reference at %04x:%04x failed", PRINT_REG(addr)); + } + } else { + /* warning("Unexpected request for outgoing script-object references at %04x:%04x", PRINT_REG(addr));*/ + /* Happens e.g. when we're looking into strings */ + } + return tmp; } +Common::Array<reg_t> Script::listObjectReferences() const { + Common::Array<reg_t> tmp; + + // Locals, if present + if (_localsSegment) + tmp.push_back(make_reg(_localsSegment, 0)); + + // All objects (may be classes, may be indirectly reachable) + ObjMap::iterator it; + const ObjMap::iterator end = _objects.end(); + for (it = _objects.begin(); it != end; ++it) { + tmp.push_back(it->_value.getPos()); + } + + return tmp; +} } // End of namespace Sci diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h index e94e9f64e6..3817f8aae1 100644 --- a/engines/sci/engine/script.h +++ b/engines/sci/engine/script.h @@ -27,14 +27,13 @@ #define SCI_ENGINE_SCRIPT_H #include "common/str.h" +#include "sci/engine/segment.h" namespace Sci { struct EngineState; class ResourceManager; -#define SCI_SCRIPTS_NR 1000 - enum ScriptObjectTypes { SCI_OBJ_TERMINATOR, SCI_OBJ_OBJECT, @@ -49,160 +48,214 @@ enum ScriptObjectTypes { SCI_OBJ_LOCALVARS }; -// Opcode formats -enum opcode_format { - Script_Invalid = -1, - Script_None = 0, - Script_Byte, - Script_SByte, - Script_Word, - Script_SWord, - Script_Variable, - Script_SVariable, - Script_SRelative, - Script_Property, - Script_Global, - Script_Local, - Script_Temp, - Script_Param, - Script_Offset, - Script_End -}; +typedef Common::HashMap<uint16, Object> ObjMap; -enum sci_opcodes { - op_bnot = 0x00, // 000 - op_add = 0x01, // 001 - op_sub = 0x02, // 002 - op_mul = 0x03, // 003 - op_div = 0x04, // 004 - op_mod = 0x05, // 005 - op_shr = 0x06, // 006 - op_shl = 0x07, // 007 - op_xor = 0x08, // 008 - op_and = 0x09, // 009 - op_or = 0x0a, // 010 - op_neg = 0x0b, // 011 - op_not = 0x0c, // 012 - op_eq_ = 0x0d, // 013 - op_ne_ = 0x0e, // 014 - op_gt_ = 0x0f, // 015 - op_ge_ = 0x10, // 016 - op_lt_ = 0x11, // 017 - op_le_ = 0x12, // 018 - op_ugt_ = 0x13, // 019 - op_uge_ = 0x14, // 020 - op_ult_ = 0x15, // 021 - op_ule_ = 0x16, // 022 - op_bt = 0x17, // 023 - op_bnt = 0x18, // 024 - op_jmp = 0x19, // 025 - op_ldi = 0x1a, // 026 - op_push = 0x1b, // 027 - op_pushi = 0x1c, // 028 - op_toss = 0x1d, // 029 - op_dup = 0x1e, // 030 - op_link = 0x1f, // 031 - op_call = 0x20, // 032 - op_callk = 0x21, // 033 - op_callb = 0x22, // 034 - op_calle = 0x23, // 035 - op_ret = 0x24, // 036 - op_send = 0x25, // 037 - // dummy 0x26, // 038 - // dummy 0x27, // 039 - op_class = 0x28, // 040 - // dummy 0x29, // 041 - op_self = 0x2a, // 042 - op_super = 0x2b, // 043 - op_rest = 0x2c, // 044 - op_lea = 0x2d, // 045 - op_selfID = 0x2e, // 046 - // dummy 0x2f // 047 - op_pprev = 0x30, // 048 - op_pToa = 0x31, // 049 - op_aTop = 0x32, // 050 - op_pTos = 0x33, // 051 - op_sTop = 0x34, // 052 - op_ipToa = 0x35, // 053 - op_dpToa = 0x36, // 054 - op_ipTos = 0x37, // 055 - op_dpTos = 0x38, // 056 - op_lofsa = 0x39, // 057 - op_lofss = 0x3a, // 058 - op_push0 = 0x3b, // 059 - op_push1 = 0x3c, // 060 - op_push2 = 0x3d, // 061 - op_pushSelf = 0x3e, // 062 - op_line = 0x3f, // 063 - op_lag = 0x40, // 064 - op_lal = 0x41, // 065 - op_lat = 0x42, // 066 - op_lap = 0x43, // 067 - op_lsg = 0x44, // 068 - op_lsl = 0x45, // 069 - op_lst = 0x46, // 070 - op_lsp = 0x47, // 071 - op_lagi = 0x48, // 072 - op_lali = 0x49, // 073 - op_lati = 0x4a, // 074 - op_lapi = 0x4b, // 075 - op_lsgi = 0x4c, // 076 - op_lsli = 0x4d, // 077 - op_lsti = 0x4e, // 078 - op_lspi = 0x4f, // 079 - op_sag = 0x50, // 080 - op_sal = 0x51, // 081 - op_sat = 0x52, // 082 - op_sap = 0x53, // 083 - op_ssg = 0x54, // 084 - op_ssl = 0x55, // 085 - op_sst = 0x56, // 086 - op_ssp = 0x57, // 087 - op_sagi = 0x58, // 088 - op_sali = 0x59, // 089 - op_sati = 0x5a, // 090 - op_sapi = 0x5b, // 091 - op_ssgi = 0x5c, // 092 - op_ssli = 0x5d, // 093 - op_ssti = 0x5e, // 094 - op_sspi = 0x5f, // 095 - op_plusag = 0x60, // 096 - op_plusal = 0x61, // 097 - op_plusat = 0x62, // 098 - op_plusap = 0x63, // 099 - op_plussg = 0x64, // 100 - op_plussl = 0x65, // 101 - op_plusst = 0x66, // 102 - op_plussp = 0x67, // 103 - op_plusagi = 0x68, // 104 - op_plusali = 0x69, // 105 - op_plusati = 0x6a, // 106 - op_plusapi = 0x6b, // 107 - op_plussgi = 0x6c, // 108 - op_plussli = 0x6d, // 109 - op_plussti = 0x6e, // 110 - op_plusspi = 0x6f, // 111 - op_minusag = 0x70, // 112 - op_minusal = 0x71, // 113 - op_minusat = 0x72, // 114 - op_minusap = 0x73, // 115 - op_minussg = 0x74, // 116 - op_minussl = 0x75, // 117 - op_minusst = 0x76, // 118 - op_minussp = 0x77, // 119 - op_minusagi = 0x78, // 120 - op_minusali = 0x79, // 121 - op_minusati = 0x7a, // 122 - op_minusapi = 0x7b, // 123 - op_minussgi = 0x7c, // 124 - op_minussli = 0x7d, // 125 - op_minussti = 0x7e, // 126 - op_minusspi = 0x7f // 127 -}; +class Script : public SegmentObj { +private: + int _nr; /**< Script number */ + byte *_buf; /**< Static data buffer, or NULL if not used */ + byte *_heapStart; /**< Start of heap if SCI1.1, NULL otherwise */ + + int _lockers; /**< Number of classes and objects that require this script */ + size_t _scriptSize; + size_t _heapSize; + uint16 _bufSize; + + const uint16 *_exportTable; /**< Abs. offset of the export table or 0 if not present */ + uint16 _numExports; /**< Number of entries in the exports table */ + + const byte *_synonyms; /**< Synonyms block or 0 if not present*/ + uint16 _numSynonyms; /**< Number of entries in the synonyms block */ + + int _localsOffset; + uint16 _localsCount; + + bool _markedAsDeleted; + +public: + /** + * Table for objects, contains property variables. + * Indexed by the TODO offset. + */ + ObjMap _objects; + SegmentId _localsSegment; /**< The local variable segment */ + LocalVariables *_localsBlock; + +public: + int getLocalsOffset() const { return _localsOffset; } + uint16 getLocalsCount() const { return _localsCount; } + + uint32 getScriptSize() const { return _scriptSize; } + uint32 getHeapSize() const { return _heapSize; } + uint32 getBufSize() const { return _bufSize; } + const byte *getBuf(uint offset = 0) const { return _buf + offset; } + + int getScriptNumber() const { return _nr; } + +public: + Script(); + ~Script(); + + void freeScript(); + void init(int script_nr, ResourceManager *resMan); + void load(ResourceManager *resMan); + + virtual bool isValidOffset(uint16 offset) const; + virtual SegmentRef dereference(reg_t pointer); + virtual reg_t findCanonicAddress(SegManager *segMan, reg_t sub_addr) const; + virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); + virtual Common::Array<reg_t> listAllDeallocatable(SegmentId segId) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; + + /** + * Return a list of all references to objects in this script + * (and also to the locals segment, if any). + * Used by the garbage collector. + * @return a list of outgoing references within the object + */ + Common::Array<reg_t> listObjectReferences() const; + + virtual void saveLoadWithSerializer(Common::Serializer &ser); + + Object *getObject(uint16 offset); + const Object *getObject(uint16 offset) const; + + /** + * Initializes an object within the segment manager + * @param obj_pos Location (segment, offset) of the object. It must + * point to the beginning of the script/class block + * (as opposed to what the VM considers to be the + * object location) + * @returns A newly created Object describing the object, + * stored within the relevant script + */ + Object *scriptObjInit(reg_t obj_pos, bool fullObjectInit = true); + + /** + * Removes a script object + * @param obj_pos Location (segment, offset) of the object. + */ + void scriptObjRemove(reg_t obj_pos); -extern opcode_format g_opcode_formats[128][4]; + /** + * Initializes the script's local variables + * @param segMan A reference to the segment manager + */ + void initialiseLocals(SegManager *segMan); -void script_adjust_opcode_formats(EngineState *s); + /** + * Adds the script's classes to the segment manager's class table + * @param segMan A reference to the segment manager + */ + void initialiseClasses(SegManager *segMan); + + /** + * Initializes the script's objects (SCI0) + * @param segMan A reference to the segment manager + * @param segmentId The script's segment id + */ + void initialiseObjectsSci0(SegManager *segMan, SegmentId segmentId); + + /** + * Initializes the script's objects (SCI1.1+) + * @param segMan A reference to the segment manager + * @param segmentId The script's segment id + */ + void initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId); + + // script lock operations + + /** Increments the number of lockers of this script by one. */ + void incrementLockers(); + + /** Decrements the number of lockers of this script by one. */ + void decrementLockers(); + + /** + * Retrieves the number of locks held on this script. + * @return the number of locks held on the previously identified script + */ + int getLockers() const; + + /** Sets the number of locks held on this script. */ + void setLockers(int lockers); + + /** + * Retrieves a pointer to the exports of this script + * @return pointer to the exports. + */ + const uint16 *getExportTable() const { return _exportTable; } + + /** + * Retrieves the number of exports of script. + * @return the number of exports of this script + */ + uint16 getExportsNr() const { return _numExports; } + + /** + * Retrieves a pointer to the synonyms associated with this script + * @return pointer to the synonyms, in non-parsed format. + */ + const byte *getSynonyms() const { return _synonyms; } + + /** + * Retrieves the number of synonyms associated with this script. + * @return the number of synonyms associated with this script + */ + uint16 getSynonymsNr() const { return _numSynonyms; } + + /** + * Validate whether the specified public function is exported by + * the script in the specified segment. + * @param pubfunct Index of the function to validate + * @return NULL if the public function is invalid, its + * offset into the script's segment otherwise + */ + uint16 validateExportFunc(int pubfunct); + + /** + * Marks the script as deleted. + * This will not actually delete the script. If references remain present on the + * heap or the stack, the script will stay in memory in a quasi-deleted state until + * either unreachable (resulting in its eventual deletion) or reloaded (resulting + * in its data being updated). + */ + void markDeleted() { + _markedAsDeleted = true; + } + + /** + * Determines whether the script is marked as being deleted. + */ + bool isMarkedAsDeleted() const { + return _markedAsDeleted; + } + + /** + * Copies a byte string into a script's heap representation. + * @param dst script-relative offset of the destination area + * @param src pointer to the data source location + * @param n number of bytes to copy + */ + void mcpyInOut(int dst, const void *src, size_t n); + + /** + * Finds the pointer where a block of a specific type starts from + */ + byte *findBlock(int type); + +private: + /** + * Processes a relocation block witin a script + * This function is idempotent, but it must only be called after all + * objects have been instantiated, or a run-time error will occur. + * @param obj_pos Location (segment, offset) of the block + * @return Location of the relocation block + */ + void relocate(reg_t block); + + bool relocateLocal(SegmentId segment, int location); +}; } // End of namespace Sci diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index b465ab3d4e..915a6fa994 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -63,15 +63,11 @@ const char *opcodeNames[] = { "-sli", "-sti", "-spi" }; -extern const char *selector_name(EngineState *s, int selector); - -DebugState g_debugState; - // 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, int print_bw_tag, int print_bytecode) { SegmentObj *mobj = s->_segMan->getSegment(pos.segment, SEG_TYPE_SCRIPT); Script *script_entity = NULL; - byte *scr; + const byte *scr; int scr_size; reg_t retval = make_reg(pos.segment, pos.offset + 1); uint16 param_value; @@ -84,7 +80,7 @@ reg_t disassemble(EngineState *s, reg_t pos, int print_bw_tag, int print_bytecod } else script_entity = (Script *)mobj; - scr = script_entity->_buf; + scr = script_entity->getBuf(); scr_size = script_entity->getBufSize(); if (pos.offset >= scr_size) { @@ -197,7 +193,7 @@ reg_t disassemble(EngineState *s, reg_t pos, int print_bw_tag, int print_bytecod if (!obj) warning("Attempted to reference on non-object at %04x:%04x", PRINT_REG(s->xs->objp)); else - printf(" (%s)", selector_name(s, obj->propertyOffsetToId(s->_segMan, scr[pos.offset + 1]))); + printf(" (%s)", g_sci->getKernel()->getSelectorName(obj->propertyOffsetToId(s->_segMan, scr[pos.offset + 1])).c_str()); } } @@ -205,12 +201,12 @@ reg_t disassemble(EngineState *s, reg_t pos, int print_bw_tag, int print_bytecod 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->restAdjustCur); + int stackframe = (scr[pos.offset + 2] >> 1) + (s->restAdjust); int argc = ((s->xs->sp)[- stackframe - 1]).offset; bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); if (!oldScriptHeader) - argc += (s->restAdjustCur); + argc += (s->restAdjust); printf(" Kernel params: ("); @@ -221,7 +217,7 @@ reg_t disassemble(EngineState *s, reg_t pos, int print_bw_tag, int print_bytecod } printf(")\n"); } else if ((opcode == op_send) || (opcode == op_self)) { - int restmod = s->restAdjustCur; + int restmod = s->restAdjust; int stackframe = (scr[pos.offset + 1] >> 1) + restmod; reg_t *sb = s->xs->sp; uint16 selector; @@ -244,7 +240,7 @@ reg_t disassemble(EngineState *s, reg_t pos, int print_bw_tag, int print_bytecod if (!name) name = "<invalid>"; - printf(" %s::%s[", name, (selector > kernel->getSelectorNamesSize()) ? "<invalid>" : selector_name(s, selector)); + printf(" %s::%s[", name, g_sci->getKernel()->getSelectorName(selector).c_str()); switch (lookupSelector(s->_segMan, called_obj_addr, selector, 0, &fun_ref)) { case kSelectorMethod: @@ -279,90 +275,76 @@ reg_t disassemble(EngineState *s, reg_t pos, int print_bw_tag, int print_bytecod } -void script_debug(EngineState *s) { - // Do we support a separate console? +void SciEngine::scriptDebug() { + EngineState *s = _gamestate; + if (_debugState.seeking && !_debugState.breakpointWasHit) { // Are we looking for something special? + if (_debugState.seeking == kDebugSeekStepOver) { + // are we above seek-level? resume then + if (_debugState.seekLevel < (int)s->_executionStack.size()) + return; + _debugState.seeking = kDebugSeekNothing; + } -#if 0 - if (sci_debug_flags & _DEBUG_FLAG_LOGGING) { - printf("%d: acc=%04x:%04x ", script_step_counter, PRINT_REG(s->r_acc)); - disassemble(s, s->xs->addr.pc, 0, 1); - if (s->seeking == kDebugSeekGlobal) - printf("Global %d (0x%x) = %04x:%04x\n", s->seekSpecial, - s->seekSpecial, PRINT_REG(s->script_000->_localsBlock->_locals[s->seekSpecial])); - } -#endif + if (_debugState.seeking != kDebugSeekNothing) { + const reg_t pc = s->xs->addr.pc; + SegmentObj *mobj = s->_segMan->getSegment(pc.segment, 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]; + 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)); + + switch (_debugState.seeking) { + case kDebugSeekSpecialCallk: + if (paramb1 != _debugState.seekSpecial) + return; + + case kDebugSeekCallk: + if (op != op_callk) + return; + break; -#if 0 - if (!g_debugState.debugging) - return; -#endif - - if (g_debugState.seeking && !g_debugState.breakpointWasHit) { // Are we looking for something special? - SegmentObj *mobj = s->_segMan->getSegment(s->xs->addr.pc.segment, SEG_TYPE_SCRIPT); - - if (mobj) { - Script *scr = (Script *)mobj; - byte *code_buf = scr->_buf; - int code_buf_size = scr->getBufSize(); - int opcode = s->xs->addr.pc.offset >= code_buf_size ? 0 : code_buf[s->xs->addr.pc.offset]; - int op = opcode >> 1; - int paramb1 = s->xs->addr.pc.offset + 1 >= code_buf_size ? 0 : code_buf[s->xs->addr.pc.offset + 1]; - int paramf1 = (opcode & 1) ? paramb1 : (s->xs->addr.pc.offset + 2 >= code_buf_size ? 0 : (int16)READ_SCI11ENDIAN_UINT16(code_buf + s->xs->addr.pc.offset + 1)); - - switch (g_debugState.seeking) { - case kDebugSeekSpecialCallk: - if (paramb1 != g_debugState.seekSpecial) - return; - - case kDebugSeekCallk: { - if (op != op_callk) - return; - break; - } + case kDebugSeekLevelRet: + if ((op != op_ret) || (_debugState.seekLevel < (int)s->_executionStack.size()-1)) + return; + break; - case kDebugSeekLevelRet: { - if ((op != op_ret) || (g_debugState.seekLevel < (int)s->_executionStack.size()-1)) - return; - break; - } + case kDebugSeekGlobal: + if (op < op_sag) + return; + if ((op & 0x3) > 1) + return; // param or temp + if ((op & 0x3) && s->_executionStack.back().local_segment > 0) + return; // locals and not running in script.000 + if (paramf1 != _debugState.seekSpecial) + return; // CORRECT global? + break; - case kDebugSeekGlobal: - if (op < op_sag) - return; - if ((op & 0x3) > 1) - return; // param or temp - if ((op & 0x3) && s->_executionStack.back().local_segment > 0) - return; // locals and not running in script.000 - if (paramf1 != g_debugState.seekSpecial) - return; // CORRECT global? - break; - - case kDebugSeekSO: - // FIXME: Unhandled? - break; - - case kDebugSeekNothing: - // We seek nothing, so just continue - break; - } + default: + break; + } - g_debugState.seeking = kDebugSeekNothing; - // OK, found whatever we were looking for + _debugState.seeking = kDebugSeekNothing; + } } + // OK, found whatever we were looking for } - printf("Step #%d\n", s->script_step_counter); + printf("Step #%d\n", s->scriptStepCounter); disassemble(s, s->xs->addr.pc, 0, 1); - if (g_debugState.runningStep) { - g_debugState.runningStep--; + if (_debugState.runningStep) { + _debugState.runningStep--; return; } - g_debugState.debugging = false; + _debugState.debugging = false; - Console *con = ((Sci::SciEngine*)g_engine)->getSciDebugger(); - con->attach(); + _console->attach(); } void Kernel::dumpScriptObject(char *data, int seeker, int objsize) { @@ -491,19 +473,17 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { dumpScriptObject((char *)script->data, seeker, objsize); break; - case SCI_OBJ_CODE: { + case SCI_OBJ_CODE: printf("Code\n"); Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); - }; - break; + break; - case 3: { + case 3: printf("<unknown>\n"); Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); - }; - break; + break; - case SCI_OBJ_SAID: { + case SCI_OBJ_SAID: printf("Said\n"); Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); @@ -551,46 +531,40 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { } } printf("\n"); - } - break; + break; - case SCI_OBJ_STRINGS: { + case SCI_OBJ_STRINGS: printf("Strings\n"); while (script->data [seeker]) { printf("%04x: %s\n", seeker, script->data + seeker); seeker += strlen((char *)script->data + seeker) + 1; } seeker++; // the ending zero byte - }; - break; + break; case SCI_OBJ_CLASS: dumpScriptClass((char *)script->data, seeker, objsize); break; - case SCI_OBJ_EXPORTS: { + case SCI_OBJ_EXPORTS: printf("Exports\n"); Common::hexdump((unsigned char *)script->data + seeker, objsize - 4, 16, seeker); - }; - break; + break; - case SCI_OBJ_POINTERS: { + case SCI_OBJ_POINTERS: printf("Pointers\n"); Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); - }; - break; + break; - case 9: { + case 9: printf("<unknown>\n"); Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); - }; - break; + break; - case SCI_OBJ_LOCALVARS: { + case SCI_OBJ_LOCALVARS: printf("Local vars\n"); Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); - }; - break; + break; default: printf("Unsupported!\n"); diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 4d3e6f754e..ef2279e492 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -26,6 +26,7 @@ #include "sci/sci.h" #include "sci/engine/seg_manager.h" #include "sci/engine/state.h" +#include "sci/engine/script.h" namespace Sci { @@ -39,14 +40,14 @@ enum { SegManager::SegManager(ResourceManager *resMan) { _heap.push_back(0); - Clones_seg_id = 0; - Lists_seg_id = 0; - Nodes_seg_id = 0; - Hunks_seg_id = 0; + _clonesSegId = 0; + _listsSegId = 0; + _nodesSegId = 0; + _hunksSegId = 0; #ifdef ENABLE_SCI32 - Arrays_seg_id = 0; - String_seg_id = 0; + _arraysSegId = 0; + _stringSegId = 0; #endif _resMan = resMan; @@ -70,10 +71,10 @@ void SegManager::resetSegMan() { // And reinitialize _heap.push_back(0); - Clones_seg_id = 0; - Lists_seg_id = 0; - Nodes_seg_id = 0; - Hunks_seg_id = 0; + _clonesSegId = 0; + _listsSegId = 0; + _nodesSegId = 0; + _hunksSegId = 0; // Reinitialize class table _classTable.clear(); @@ -81,10 +82,10 @@ void SegManager::resetSegMan() { } void SegManager::initSysStrings() { - sysStrings = (SystemStrings *)allocSegment(new SystemStrings(), &sysStringsSegment); + _sysStrings = (SystemStrings *)allocSegment(new SystemStrings(), &_sysStringsSegId); // Allocate static buffer for savegame and CWD directories - SystemString *strSaveDir = &sysStrings->_strings[SYS_STRING_SAVEDIR]; + SystemString *strSaveDir = getSystemString(SYS_STRING_SAVEDIR); strSaveDir->_name = "savedir"; strSaveDir->_maxSize = MAX_SAVE_DIR_SIZE; strSaveDir->_value = (char *)calloc(MAX_SAVE_DIR_SIZE, sizeof(char)); @@ -93,23 +94,17 @@ void SegManager::initSysStrings() { ::strcpy(strSaveDir->_value, ""); // Allocate static buffer for the parser base - SystemString *strParserBase = &sysStrings->_strings[SYS_STRING_PARSER_BASE]; + SystemString *strParserBase = getSystemString(SYS_STRING_PARSER_BASE); strParserBase->_name = "parser-base"; strParserBase->_maxSize = MAX_PARSER_BASE; strParserBase->_value = (char *)calloc(MAX_PARSER_BASE, sizeof(char)); } SegmentId SegManager::findFreeSegment() const { - // FIXME: This is a very crude approach: We find a free segment id by scanning - // from the start. This can be slow if the number of segments becomes large. - // Optimizations are possible and easy, but I refrain from doing any until this - // refactoring is complete. - // One very simple yet probably effective approach would be to add - // "int firstFreeSegment", which is updated when allocating/freeing segments. - // There are some scenarios where it performs badly, but we should first check - // whether those occur at all. If so, there are easy refinements that deal - // with this. Finally, we could switch to a more elaborate scheme, - // such as keeping track of all free segments. But I doubt this is necessary. + // The following is a very crude approach: We find a free segment id by + // scanning from the start. This can be slow if the number of segments + // becomes large. Optimizations are possible and easy, but I'll refrain + // from attempting any until we determine we actually need it. uint seg = 1; while (seg < _heap.size() && _heap[seg]) { ++seg; @@ -162,7 +157,7 @@ int SegManager::deallocate(SegmentId seg, bool recursive) { if (mobj->getType() == SEG_TYPE_SCRIPT) { Script *scr = (Script *)mobj; - _scriptSegMap.erase(scr->_nr); + _scriptSegMap.erase(scr->getScriptNumber()); if (recursive && scr->_localsSegment) deallocate(scr->_localsSegment, recursive); } @@ -173,12 +168,12 @@ int SegManager::deallocate(SegmentId seg, bool recursive) { return 1; } -bool SegManager::isHeapObject(reg_t pos) { +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); - return !(scr && scr->_markedAsDeleted); + return !(scr && scr->isMarkedAsDeleted()); } void SegManager::deallocateScript(int script_nr) { @@ -199,36 +194,36 @@ Script *SegManager::getScript(const SegmentId seg) { return (Script *)_heap[seg]; } -Script *SegManager::getScriptIfLoaded(const SegmentId seg) { +Script *SegManager::getScriptIfLoaded(const SegmentId seg) const { if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg] || _heap[seg]->getType() != SEG_TYPE_SCRIPT) return 0; return (Script *)_heap[seg]; } -SegmentId SegManager::findSegmentByType(int type) { +SegmentId SegManager::findSegmentByType(int type) const { for (uint i = 0; i < _heap.size(); i++) if (_heap[i] && _heap[i]->getType() == type) return i; return 0; } -SegmentObj *SegManager::getSegmentObj(SegmentId seg) { +SegmentObj *SegManager::getSegmentObj(SegmentId seg) const { if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg]) return 0; return _heap[seg]; } -SegmentType SegManager::getSegmentType(SegmentId seg) { +SegmentType SegManager::getSegmentType(SegmentId seg) const { if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg]) return SEG_TYPE_INVALID; return _heap[seg]->getType(); } -SegmentObj *SegManager::getSegment(SegmentId seg, SegmentType type) { +SegmentObj *SegManager::getSegment(SegmentId seg, SegmentType type) const { return getSegmentType(seg) == type ? _heap[seg] : NULL; } -Object *SegManager::getObject(reg_t pos) { +Object *SegManager::getObject(reg_t pos) const { SegmentObj *mobj = getSegmentObj(pos.segment); Object *obj = NULL; @@ -242,7 +237,7 @@ Object *SegManager::getObject(reg_t pos) { } 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->_buf + pos.offset)) { + && RAW_IS_OBJECT(scr->getBuf(pos.offset))) { obj = scr->getObject(pos.offset); } } @@ -268,78 +263,55 @@ const char *SegManager::getObjectName(reg_t pos) { } reg_t SegManager::findObjectByName(const Common::String &name, int index) { - reg_t retVal = NULL_REG; + Common::Array<reg_t> result; + uint i; // Now all values are available; iterate over all objects. - int timesFound = 0; - for (uint i = 0; i < _heap.size(); i++) { - SegmentObj *mobj = _heap[i]; - int idx = 0; - int max_index = 0; - ObjMap::iterator it; - Script *scr = 0; - CloneTable *ct = 0; - - if (mobj) { - if (mobj->getType() == SEG_TYPE_SCRIPT) { - scr = (Script *)mobj; - max_index = scr->_objects.size(); - it = scr->_objects.begin(); - } else if (mobj->getType() == SEG_TYPE_CLONES) { - ct = (CloneTable *)mobj; - max_index = ct->_table.size(); - } - } + for (i = 0; i < _heap.size(); i++) { + const SegmentObj *mobj = _heap[i]; + + if (!mobj) + continue; + + reg_t objpos = make_reg(i, 0); - // It's a script or a clone table, scan all objects in it - for (; idx < max_index; ++idx) { - const Object *obj = NULL; - reg_t objpos; - objpos.offset = 0; - objpos.segment = i; - - if (mobj->getType() == SEG_TYPE_SCRIPT) { - obj = &(it->_value); - objpos.offset = obj->getPos().offset; - ++it; - } else if (mobj->getType() == SEG_TYPE_CLONES) { + if (mobj->getType() == SEG_TYPE_SCRIPT) { + // It's a script, scan all objects in it + const Script *scr = (const Script *)mobj; + for (ObjMap::const_iterator it = scr->_objects.begin(); it != scr->_objects.end(); ++it) { + objpos.offset = it->_value.getPos().offset; + if (name == getObjectName(objpos)) + result.push_back(objpos); + } + } else if (mobj->getType() == SEG_TYPE_CLONES) { + // It's clone table, scan all objects in it + const CloneTable *ct = (const CloneTable *)mobj; + for (uint idx = 0; idx < ct->_table.size(); ++idx) { if (!ct->isValidEntry(idx)) continue; - obj = &(ct->_table[idx]); - objpos.offset = idx; - } - const char *objname = getObjectName(objpos); - if (name == objname) { - // Found a match! - if ((index < 0) && (timesFound > 0)) { - if (timesFound == 1) { - // First time we realized the ambiguity - printf("Ambiguous:\n"); - printf(" %3x: [%04x:%04x] %s\n", 0, PRINT_REG(retVal), name.c_str()); - } - printf(" %3x: [%04x:%04x] %s\n", timesFound, PRINT_REG(objpos), name.c_str()); - } - if (index < 0 || timesFound == index) - retVal = objpos; - ++timesFound; + objpos.offset = idx; + if (name == getObjectName(objpos)) + result.push_back(objpos); } } - } - if (!timesFound) + if (result.empty()) return NULL_REG; - if (timesFound > 1 && index < 0) { - printf("Ambiguous: Aborting.\n"); + if (result.size() > 1 && index < 0) { + printf("Ambiguous:\n"); + for (i = 0; i < result.size(); i++) + printf(" %3x: [%04x:%04x] %s\n", i, PRINT_REG(result[i]), name.c_str()); return NULL_REG; // Ambiguous } - if (timesFound <= index) + if (index < 0) + return result[0]; + else if (result.size() <= (uint)index) return NULL_REG; // Not found - - return retVal; + return result[index]; } // validate the seg @@ -366,7 +338,7 @@ SegmentId SegManager::getScriptSegment(int script_nr, ScriptLoadType load) { SegmentId segment; if ((load & SCRIPT_GET_LOAD) == SCRIPT_GET_LOAD) - script_instantiate(_resMan, this, script_nr); + instantiateScript(script_nr); segment = getScriptSegment(script_nr); @@ -377,8 +349,8 @@ SegmentId SegManager::getScriptSegment(int script_nr, ScriptLoadType load) { return segment; } -LocalVariables *SegManager::allocLocalsSegment(Script *scr, int count) { - if (!count) { // No locals +LocalVariables *SegManager::allocLocalsSegment(Script *scr) { + if (!scr->getLocalsCount()) { // No locals scr->_localsSegment = 0; scr->_localsBlock = NULL; return NULL; @@ -389,13 +361,13 @@ LocalVariables *SegManager::allocLocalsSegment(Script *scr, int count) { locals = (LocalVariables *)_heap[scr->_localsSegment]; VERIFY(locals != NULL, "Re-used locals segment was NULL'd out"); VERIFY(locals->getType() == SEG_TYPE_LOCALS, "Re-used locals segment did not consist of local variables"); - VERIFY(locals->script_id == scr->_nr, "Re-used locals segment belonged to other script"); + VERIFY(locals->script_id == scr->getScriptNumber(), "Re-used locals segment belonged to other script"); } else locals = (LocalVariables *)allocSegment(new LocalVariables(), &scr->_localsSegment); scr->_localsBlock = locals; - locals->script_id = scr->_nr; - locals->_locals.resize(count); + locals->script_id = scr->getScriptNumber(); + locals->_locals.resize(scr->getLocalsCount()); return locals; } @@ -408,6 +380,12 @@ DataStack *SegManager::allocateStack(int size, SegmentId *segid) { retval->_entries = (reg_t *)calloc(size, sizeof(reg_t)); retval->_capacity = size; + // SSCI initializes the stack with "S" characters (uppercase S in SCI0-SCI1, + // lowercase s in SCI0 and SCI11) - probably stands for "stack" + byte filler = (getSciVersion() >= SCI_VERSION_01 && getSciVersion() <= SCI_VERSION_1_LATE) ? 'S' : 's'; + for (int i = 0; i < size; i++) + retval->_entries[i] = make_reg(0, filler); + return retval; } @@ -431,13 +409,13 @@ reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) { HunkTable *table; int offset; - if (!Hunks_seg_id) - allocSegment(new HunkTable(), &(Hunks_seg_id)); - table = (HunkTable *)_heap[Hunks_seg_id]; + if (!_hunksSegId) + allocSegment(new HunkTable(), &(_hunksSegId)); + table = (HunkTable *)_heap[_hunksSegId]; offset = table->allocEntry(); - reg_t addr = make_reg(Hunks_seg_id, offset); + reg_t addr = make_reg(_hunksSegId, offset); Hunk *h = &(table->_table[offset]); if (!h) @@ -454,7 +432,7 @@ byte *SegManager::getHunkPointer(reg_t addr) { HunkTable *ht = (HunkTable *)getSegment(addr.segment, SEG_TYPE_HUNK); if (!ht || !ht->isValidEntry(addr.offset)) { - warning("getHunkPointer() with invalid handle"); + // Valid SCI behavior, e.g. when loading/quitting return NULL; } @@ -465,61 +443,28 @@ Clone *SegManager::allocateClone(reg_t *addr) { CloneTable *table; int offset; - if (!Clones_seg_id) - table = (CloneTable *)allocSegment(new CloneTable(), &(Clones_seg_id)); + if (!_clonesSegId) + table = (CloneTable *)allocSegment(new CloneTable(), &(_clonesSegId)); else - table = (CloneTable *)_heap[Clones_seg_id]; + table = (CloneTable *)_heap[_clonesSegId]; offset = table->allocEntry(); - *addr = make_reg(Clones_seg_id, offset); + *addr = make_reg(_clonesSegId, offset); return &(table->_table[offset]); } -void SegManager::reconstructClones() { - for (uint i = 0; i < _heap.size(); i++) { - if (_heap[i]) { - SegmentObj *mobj = _heap[i]; - if (mobj->getType() == SEG_TYPE_CLONES) { - CloneTable *ct = (CloneTable *)mobj; - - for (uint j = 0; j < ct->_table.size(); j++) { - // Check if the clone entry is used - uint entryNum = (uint)ct->first_free; - bool isUsed = true; - while (entryNum != ((uint) CloneTable::HEAPENTRY_INVALID)) { - if (entryNum == j) { - isUsed = false; - break; - } - entryNum = ct->_table[entryNum].next_free; - } - - if (!isUsed) - continue; - - CloneTable::Entry &seeker = ct->_table[j]; - const Object *baseObj = getObject(seeker.getSpeciesSelector()); - seeker.cloneFromObject(baseObj); - if (!baseObj) - warning("Clone entry without a base class: %d", j); - } // end for - } // end if - } // end if - } // end for -} - List *SegManager::allocateList(reg_t *addr) { ListTable *table; int offset; - if (!Lists_seg_id) - allocSegment(new ListTable(), &(Lists_seg_id)); - table = (ListTable *)_heap[Lists_seg_id]; + if (!_listsSegId) + allocSegment(new ListTable(), &(_listsSegId)); + table = (ListTable *)_heap[_listsSegId]; offset = table->allocEntry(); - *addr = make_reg(Lists_seg_id, offset); + *addr = make_reg(_listsSegId, offset); return &(table->_table[offset]); } @@ -527,36 +472,36 @@ Node *SegManager::allocateNode(reg_t *addr) { NodeTable *table; int offset; - if (!Nodes_seg_id) - allocSegment(new NodeTable(), &(Nodes_seg_id)); - table = (NodeTable *)_heap[Nodes_seg_id]; + if (!_nodesSegId) + allocSegment(new NodeTable(), &(_nodesSegId)); + table = (NodeTable *)_heap[_nodesSegId]; offset = table->allocEntry(); - *addr = make_reg(Nodes_seg_id, offset); + *addr = make_reg(_nodesSegId, offset); return &(table->_table[offset]); } reg_t SegManager::newNode(reg_t value, reg_t key) { - reg_t nodebase; - Node *n = allocateNode(&nodebase); + reg_t nodeRef; + Node *n = allocateNode(&nodeRef); n->pred = n->succ = NULL_REG; n->key = key; n->value = value; - return nodebase; + return nodeRef; } List *SegManager::lookupList(reg_t addr) { if (getSegmentType(addr.segment) != SEG_TYPE_LISTS) { - warning("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); + error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } ListTable *lt = (ListTable *)_heap[addr.segment]; if (!lt->isValidEntry(addr.offset)) { - warning("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); + error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } @@ -567,18 +512,17 @@ Node *SegManager::lookupNode(reg_t addr) { if (addr.isNull()) return NULL; // Non-error null - if (getSegmentType(addr.segment) != SEG_TYPE_NODES) { - // FIXME: This occurs right at the beginning of SQ4, when walking north from the first screen. It doesn't - // seem to have any apparent ill-effects, though, so it's been changed to non-fatal, for now - //error("%s, L%d: Attempt to use non-node %04x:%04x as list node", __FILE__, __LINE__, PRINT_REG(addr)); - warning("Attempt to use non-node %04x:%04x as list node", PRINT_REG(addr)); + SegmentType type = getSegmentType(addr.segment); + + 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]; if (!nt->isValidEntry(addr.offset)) { - warning("Attempt to use non-node %04x:%04x as list node", PRINT_REG(addr)); + error("Attempt to use invalid or discarded reference %04x:%04x as list node", PRINT_REG(addr)); return NULL; } @@ -606,7 +550,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->_heap[pointer.segment]->getType(), + segMan->getSegmentType(pointer.segment), ret.isRaw ? "raw" : "not raw", wantRaw ? "raw" : "not raw"); } @@ -646,8 +590,12 @@ static inline char getChar(const SegmentRef &ref, uint offset) { reg_t val = ref.reg[offset / 2]; + // 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) - warning("Attempt to read character from non-raw data"); + if (!((val.segment == 0xFFFF) && (offset > 1))) + warning("Attempt to read character from non-raw data"); return (offset & 1 ? val.offset >> 8 : val.offset & 0xff); } @@ -695,6 +643,14 @@ void SegManager::strncpy(reg_t dest, const char* src, size_t n) { } void SegManager::strncpy(reg_t dest, reg_t src, size_t n) { + if (src.isNull()) { + // Clear target string instead. + if (n > 0) + strcpy(dest, ""); + + return; // empty text + } + SegmentRef dest_r = dereference(dest); const SegmentRef src_r = dereference(src); if (!src_r.isValid()) { @@ -822,6 +778,9 @@ void SegManager::memcpy(byte *dest, reg_t src, size_t n) { } size_t SegManager::strlen(reg_t str) { + if (str.isNull()) + return 0; // empty text + SegmentRef str_r = dereference(str); if (!str_r.isValid()) { warning("Attempt to call strlen on invalid pointer %04x:%04x", PRINT_REG(str)); @@ -841,6 +800,9 @@ size_t SegManager::strlen(reg_t str) { Common::String SegManager::getString(reg_t pointer, int entries) { Common::String ret; + if (pointer.isNull()) + return ret; // empty text + SegmentRef src_r = dereference(pointer); if (!src_r.isValid()) { warning("SegManager::getString(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); @@ -867,7 +829,6 @@ Common::String SegManager::getString(reg_t pointer, int entries) { return ret; } - byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) { SegmentId seg; SegmentObj *mobj = allocSegment(new DynMem(), &seg); @@ -901,14 +862,14 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { ArrayTable *table; int offset; - if (!Arrays_seg_id) { - table = (ArrayTable *)allocSegment(new ArrayTable(), &(Arrays_seg_id)); + if (!_arraysSegId) { + table = (ArrayTable *)allocSegment(new ArrayTable(), &(_arraysSegId)); } else - table = (ArrayTable *)_heap[Arrays_seg_id]; + table = (ArrayTable *)_heap[_arraysSegId]; offset = table->allocEntry(); - *addr = make_reg(Arrays_seg_id, offset); + *addr = make_reg(_arraysSegId, offset); return &(table->_table[offset]); } @@ -941,14 +902,14 @@ SciString *SegManager::allocateString(reg_t *addr) { StringTable *table; int offset; - if (!String_seg_id) { - table = (StringTable *)allocSegment(new StringTable(), &(String_seg_id)); + if (!_stringSegId) { + table = (StringTable *)allocSegment(new StringTable(), &(_stringSegId)); } else - table = (StringTable *)_heap[String_seg_id]; + table = (StringTable *)_heap[_stringSegId]; offset = table->allocEntry(); - *addr = make_reg(String_seg_id, offset); + *addr = make_reg(_stringSegId, offset); return &(table->_table[offset]); } @@ -979,4 +940,148 @@ void SegManager::freeString(reg_t addr) { #endif +void SegManager::createClassTable() { + Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1); + + if (!vocab996) + error("SegManager: failed to open vocab 996"); + + int totalClasses = vocab996->size >> 2; + _classTable.resize(totalClasses); + + for (uint16 classNr = 0; classNr < totalClasses; classNr++) { + uint16 scriptNr = READ_SCI11ENDIAN_UINT16(vocab996->data + classNr * 4 + 2); + + _classTable[classNr].reg = NULL_REG; + _classTable[classNr].script = scriptNr; + } + + _resMan->unlockResource(vocab996); +} + +reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) { + if (classnr == 0xffff) + return NULL_REG; + + if (classnr < 0 || (int)_classTable.size() <= classnr || _classTable[classnr].script < 0) { + error("[VM] Attempt to dereference class %x, which doesn't exist (max %x)", classnr, _classTable.size()); + return NULL_REG; + } else { + Class *the_class = &_classTable[classnr]; + if (!the_class->reg.segment) { + getScriptSegment(the_class->script, lock); + + if (!the_class->reg.segment) { + 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(); + + return the_class->reg; + } +} + +int SegManager::instantiateScript(int scriptNum) { + SegmentId segmentId = getScriptSegment(scriptNum); + Script *scr = getScriptIfLoaded(segmentId); + if (scr) { + if (!scr->isMarkedAsDeleted()) { + scr->incrementLockers(); + return segmentId; + } else { + scr->freeScript(); + } + } else { + scr = allocateScript(scriptNum, &segmentId); + } + + scr->init(scriptNum, _resMan); + scr->load(_resMan); + scr->initialiseLocals(this); + scr->initialiseClasses(this); + + if (getSciVersion() >= SCI_VERSION_1_1) { + scr->initialiseObjectsSci11(this, segmentId); + } else { + scr->initialiseObjectsSci0(this, segmentId); + } + + return segmentId; +} + +void SegManager::uninstantiateScript(int script_nr) { + SegmentId segmentId = getScriptSegment(script_nr); + Script *scr = getScriptIfLoaded(segmentId); + + if (!scr) { // Is it already unloaded? + //warning("unloading script 0x%x requested although not loaded", script_nr); + // This is perfectly valid SCI behaviour + return; + } + + scr->decrementLockers(); // One less locker + + if (scr->getLockers() > 0) + return; + + // Free all classtable references to this script + for (uint i = 0; i < classTableSize(); i++) + if (getClass(i).reg.segment == segmentId) + setClassOffset(i, NULL_REG); + + if (getSciVersion() < SCI_VERSION_1_1) + uninstantiateScriptSci0(script_nr); + // FIXME: Add proper script uninstantiation for SCI 1.1 + + if (!scr->getLockers()) { + // The actual script deletion seems to be done by SCI scripts themselves + scr->markDeleted(); + debugC(kDebugLevelScripts, "Unloaded script 0x%x.", script_nr); + } +} + +void SegManager::uninstantiateScriptSci0(int script_nr) { + bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); + SegmentId segmentId = getScriptSegment(script_nr); + Script *scr = getScript(segmentId); + reg_t reg = make_reg(segmentId, oldScriptHeader ? 2 : 0); + int objType, objLength = 0; + + // Make a pass over the object in order uninstantiate all superclasses + + do { + reg.offset += objLength; // Step over the last checked object + + objType = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.offset)); + if (!objType) + break; + objLength = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.offset + 2)); + + reg.offset += 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)); + + if (superclass >= 0) { + int superclass_script = getClass(superclass).script; + + if (superclass_script == script_nr) { + if (scr->getLockers()) + scr->decrementLockers(); // Decrease lockers if this is us ourselves + } else + uninstantiateScript(superclass_script); + // Recurse to assure that the superclass lockers number gets decreased + } + + reg.offset += SCRIPT_OBJECT_MAGIC_OFFSET; + } // if object or class + + reg.offset -= 4; // Step back on header + + } while (objType != 0); +} + } // End of namespace Sci diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index 9312f51f9d..a7f5f8517f 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -28,7 +28,9 @@ #include "common/scummsys.h" #include "common/serializer.h" +#include "sci/engine/script.h" #include "sci/engine/vm.h" +#include "sci/engine/vm_types.h" #include "sci/engine/segment.h" namespace Sci { @@ -42,8 +44,6 @@ namespace Sci { error("%s, line, %d, %s", __FILE__, __LINE__, msg); \ } - - /** * Parameters for getScriptSegment(). */ @@ -53,6 +53,7 @@ enum ScriptLoadType { SCRIPT_GET_LOCK = 3 /**< Load, if neccessary, and lock */ }; +class Script; class SegManager : public Common::Serializable { friend class Console; @@ -96,6 +97,11 @@ public: void reconstructScripts(EngineState *s); /** + * Reconstructs the stack. Used when restoring saved games + */ + void reconstructStack(EngineState *s); + + /** * Determines the segment occupied by a certain script, if any. * @param script_nr Number of the script to look up * @return The script's segment ID, or 0 on failure @@ -111,6 +117,30 @@ public: */ SegmentId getScriptSegment(int script_nr, ScriptLoadType load); + /** + * Makes sure that a script and its superclasses get loaded to the heap. + * If the script already has been loaded, only the number of lockers is + * increased. All scripts containing superclasses of this script are loaded + * recursively as well, unless 'recursive' is set to zero. The + * complementary function is "uninstantiateScript()" below. + * @param[in] script_nr The script number to load + * @return The script's segment ID or 0 if out of heap + */ + int instantiateScript(int script_nr); + + /** + * Decreases the numer of lockers of a script and unloads it if that number + * reaches zero. + * This function will recursively unload scripts containing its + * superclasses, if those aren't locked by other scripts as well. + * @param[in] script_nr The script number that is requestet to be unloaded + */ + void uninstantiateScript(int script_nr); + +private: + void uninstantiateScriptSci0(int script_nr); + +public: // TODO: document this reg_t getClassAddress(int classnr, ScriptLoadType lock, reg_t caller); @@ -131,28 +161,7 @@ public: * @param seg ID of the script segment to check for * @return A pointer to the Script object, or NULL */ - Script *getScriptIfLoaded(SegmentId seg); - - - // 1b. Script Initialisation - - // The set of functions below are intended - // to be used during script instantiation, - // i.e. loading and linking. - - /** - * Initializes a script's local variable block - * All variables are initialized to zero. - * @param seg Segment containing the script to initialize - * @param nr Number of local variables to allocate - */ - void scriptInitialiseLocalsZero(SegmentId seg, int nr); - - /** - * Initializes a script's local variable block according to a prototype - * @param location Location to initialize from - */ - void scriptInitialiseLocals(reg_t location); + Script *getScriptIfLoaded(SegmentId seg) const; // 2. Clones @@ -378,39 +387,39 @@ public: * @param type The type of the segment to find * @return The segment number, or -1 if the segment wasn't found */ - SegmentId findSegmentByType(int type); + SegmentId findSegmentByType(int type) const; // TODO: document this - SegmentObj *getSegmentObj(SegmentId seg); + SegmentObj *getSegmentObj(SegmentId seg) const; // TODO: document this - SegmentType getSegmentType(SegmentId seg); + SegmentType getSegmentType(SegmentId seg) const; // TODO: document this - SegmentObj *getSegment(SegmentId seg, SegmentType type); + SegmentObj *getSegment(SegmentId seg, SegmentType type) const; /** * Retrieves an object from the specified location * @param[in] offset Location (segment, offset) of the object * @return The object in question, or NULL if there is none */ - Object *getObject(reg_t pos); + Object *getObject(reg_t pos) const; /** * Checks whether a heap address contains an object * @parm obj The address to check * @return True if it is an object, false otherwise */ - bool isObject(reg_t obj) { return getObject(obj) != NULL; } + bool isObject(reg_t obj) const { return getObject(obj) != NULL; } // TODO: document this - bool isHeapObject(reg_t pos); + bool isHeapObject(reg_t pos) const; /** * Determines the name of an object * @param[in] pos Location (segment, offset) of the object * @return A name for that object, or a string describing an error - * that occured while looking it up. The string is stored + * that occurred while looking it up. The string is stored * in a static buffer and need not be freed (neither may * it be modified). */ @@ -429,22 +438,27 @@ public: */ reg_t findObjectByName(const Common::String &name, int index = -1); - void scriptInitialiseObjectsSci11(SegmentId seg); - - uint32 classTableSize() { return _classTable.size(); } - Class getClass(int index) { return _classTable[index]; } + uint32 classTableSize() const { return _classTable.size(); } + Class getClass(int index) const { return _classTable[index]; } void setClassOffset(int index, reg_t offset) { _classTable[index].reg = offset; } void resizeClassTable(uint32 size) { _classTable.resize(size); } /** * Obtains the system strings segment ID */ - SegmentId getSysStringsSegment() { return sysStringsSegment; } + SegmentId getSysStringsSegment() { return _sysStringsSegId; } -public: // TODO: make private - Common::Array<SegmentObj *> _heap; - // Only accessible from saveLoadWithSerializer() - Common::Array<Class> _classTable; /**< Table of all classes */ + /** + * Get a pointer to the system string with the specified index, + * or NULL if that index is invalid. + * + * This method is currently only used by kString(). + */ + SystemString *getSystemString(uint idx) const { + if (idx >= SYS_STRINGS_MAX) + return NULL; + return &_sysStrings->_strings[idx]; + } #ifdef ENABLE_SCI32 SciArray<reg_t> *allocateArray(reg_t *addr); @@ -453,35 +467,35 @@ public: // TODO: make private SciString *allocateString(reg_t *addr); SciString *lookupString(reg_t addr); void freeString(reg_t addr); - SegmentId getStringSegmentId() { return String_seg_id; } + SegmentId getStringSegmentId() { return _stringSegId; } #endif + const Common::Array<SegmentObj *> &getSegments() const { return _heap; } + private: + Common::Array<SegmentObj *> _heap; + Common::Array<Class> _classTable; /**< Table of all classes */ /** Map script ids to segment ids. */ Common::HashMap<int, SegmentId> _scriptSegMap; ResourceManager *_resMan; - SegmentId Clones_seg_id; ///< ID of the (a) clones segment - SegmentId Lists_seg_id; ///< ID of the (a) list segment - SegmentId Nodes_seg_id; ///< ID of the (a) node segment - SegmentId Hunks_seg_id; ///< ID of the (a) hunk segment + SegmentId _clonesSegId; ///< ID of the (a) clones segment + SegmentId _listsSegId; ///< ID of the (a) list segment + SegmentId _nodesSegId; ///< ID of the (a) node segment + SegmentId _hunksSegId; ///< ID of the (a) hunk segment /* System strings */ - SegmentId sysStringsSegment; -public: // TODO: make private. Only kString() needs direct access - SystemStrings *sysStrings; - -private: + SegmentId _sysStringsSegId; + SystemStrings *_sysStrings; #ifdef ENABLE_SCI32 - SegmentId Arrays_seg_id; - SegmentId String_seg_id; + SegmentId _arraysSegId; + SegmentId _stringSegId; #endif private: SegmentObj *allocSegment(SegmentObj *mem, SegmentId *segid); - LocalVariables *allocLocalsSegment(Script *scr, int count); int deallocate(SegmentId seg, bool recursive); void createClassTable(); @@ -494,6 +508,9 @@ private: * 'seg' is a valid segment */ bool check(SegmentId seg); + +public: + LocalVariables *allocLocalsSegment(Script *scr); }; } // End of namespace Sci diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index 0e0a759d4b..cb908979a3 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -86,175 +86,52 @@ SegmentObj *SegmentObj::createSegmentObj(SegmentType type) { return mem; } -Script::Script() : SegmentObj(SEG_TYPE_SCRIPT) { - _nr = 0; - _buf = NULL; - _bufSize = 0; - _scriptSize = 0; - _heapSize = 0; - - _synonyms = NULL; - _heapStart = NULL; - _exportTable = NULL; - - _localsOffset = 0; - _localsSegment = 0; - _localsBlock = NULL; - - _markedAsDeleted = false; -} - -Script::~Script() { - freeScript(); -} - -void Script::freeScript() { - free(_buf); - _buf = NULL; - _bufSize = 0; - - _objects.clear(); - _codeBlocks.clear(); -} - -void Script::init(int script_nr, ResourceManager *resMan) { - Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0); - - _localsOffset = 0; - _localsBlock = NULL; - - _codeBlocks.clear(); - - _markedAsDeleted = false; - - _nr = script_nr; - _buf = 0; - _heapStart = 0; - - _scriptSize = script->size; - _bufSize = script->size; - _heapSize = 0; - - _lockers = 1; - - if (getSciVersion() == SCI_VERSION_0_EARLY) { - _bufSize += READ_LE_UINT16(script->data) * 2; - } else if (getSciVersion() >= SCI_VERSION_1_1) { - /** - * In SCI11, the heap was in a separate space from the script. - * We append it to the end of the script, and adjust addressing accordingly. - * However, since we address the heap with a 16-bit pointer, the combined - * size of the stack and the heap must be 64KB. So far this has worked - * for SCI11, SCI2 and SCI21 games. SCI3 games use a different script format, - * and theoretically they can exceed the 64KB boundary using relocation. - */ - Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, script_nr), 0); - _bufSize += heap->size; - _heapSize = heap->size; - - // Ensure that the start of the heap resource can be word-aligned. - if (script->size & 2) { - _bufSize++; - _scriptSize++; - } - - // As mentioned above, the script and the heap together should not exceed 64KB - if (_bufSize > 65535) - error("Script and heap sizes combined exceed 64K. This means a fundamental " - "design bug was made regarding SCI1.1 and newer games.\nPlease " - "report this error to the ScummVM team"); - } -} - -void Script::load(ResourceManager *resMan) { - Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0); - assert(script != 0); - - _buf = (byte *)malloc(_bufSize); - assert(_buf); - - assert(_bufSize >= script->size); - memcpy(_buf, script->data, script->size); - - if (getSciVersion() >= SCI_VERSION_1_1) { - Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0); - assert(heap != 0); - - _heapStart = _buf + _scriptSize; - - assert(_bufSize - _scriptSize <= heap->size); - memcpy(_heapStart, heap->data, heap->size); - } - - _codeBlocks.clear(); - - _exportTable = 0; - _numExports = 0; - _synonyms = 0; - _numSynonyms = 0; - - if (getSciVersion() >= SCI_VERSION_1_1) { - if (READ_LE_UINT16(_buf + 1 + 5) > 0) { - _exportTable = (const uint16 *)(_buf + 1 + 5 + 2); - _numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1); - } - } else { - _exportTable = (const uint16 *)findBlock(SCI_OBJ_EXPORTS); - if (_exportTable) { - _numExports = READ_SCI11ENDIAN_UINT16(_exportTable + 1); - _exportTable += 3; // skip header plus 2 bytes (_exportTable is a uint16 pointer) - } - _synonyms = findBlock(SCI_OBJ_SYNONYMS); - if (_synonyms) { - _numSynonyms = READ_SCI11ENDIAN_UINT16(_synonyms + 2) / 4; - _synonyms += 4; // skip header - } +const char *SegmentObj::getSegmentTypeName(SegmentType type) { + switch (type) { + case SEG_TYPE_SCRIPT: + return "script"; + break; + case SEG_TYPE_CLONES: + return "clones"; + break; + case SEG_TYPE_LOCALS: + return "locals"; + break; + case SEG_TYPE_SYS_STRINGS: + return "strings"; + break; + case SEG_TYPE_STACK: + return "stack"; + break; + case SEG_TYPE_HUNK: + return "hunk"; + break; + case SEG_TYPE_LISTS: + return "lists"; + break; + case SEG_TYPE_NODES: + return "nodes"; + break; + case SEG_TYPE_DYNMEM: + return "dynmem"; + break; +#ifdef ENABLE_SCI32 + case SEG_TYPE_ARRAY: + return "array"; + break; + case SEG_TYPE_STRING: + return "string"; + break; +#endif + default: + error("Unknown SegmentObj type %d", type); + break; } -} - -Object *Script::allocateObject(uint16 offset) { - return &_objects[offset]; -} - -Object *Script::getObject(uint16 offset) { - if (_objects.contains(offset)) - return &_objects[offset]; - else - return 0; -} - -const Object *Script::getObject(uint16 offset) const { - if (_objects.contains(offset)) - return &_objects[offset]; - else - return 0; -} - -Object *Script::scriptObjInit(reg_t obj_pos, bool fullObjectInit) { - Object *obj; - - if (getSciVersion() < SCI_VERSION_1_1 && fullObjectInit) - obj_pos.offset += 8; // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) - - VERIFY(obj_pos.offset < _bufSize, "Attempt to initialize object beyond end of script\n"); - - obj = allocateObject(obj_pos.offset); - - VERIFY(obj_pos.offset + kOffsetFunctionArea < (int)_bufSize, "Function area pointer stored beyond end of script\n"); - - obj->init(_buf, obj_pos, fullObjectInit); - - return obj; -} - -void Script::scriptObjRemove(reg_t obj_pos) { - if (getSciVersion() < SCI_VERSION_1_1) - obj_pos.offset += 8; - - _objects.erase(obj_pos.toUint16()); + return NULL; } // This helper function is used by Script::relocateLocal and Object::relocate +// Duplicate in segment.cpp and script.cpp static bool relocateBlock(Common::Array<reg_t> &block, int block_location, SegmentId segment, int location, size_t scriptSize) { int rel = location - block_location; @@ -267,7 +144,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme return false; if (rel & 1) { - warning("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); + error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); return false; } block[idx].segment = segment; // Perform relocation @@ -277,182 +154,12 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme return true; } -bool Script::relocateLocal(SegmentId segment, int location) { - if (_localsBlock) - return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, _scriptSize); - else - return false; -} - -void Script::scriptAddCodeBlock(reg_t location) { - CodeBlock cb; - cb.pos = location; - cb.size = READ_SCI11ENDIAN_UINT16(_buf + location.offset - 2); - _codeBlocks.push_back(cb); -} - -void Script::relocate(reg_t block) { - byte *heap = _buf; - uint16 heapSize = (uint16)_bufSize; - uint16 heapOffset = 0; - - if (getSciVersion() >= SCI_VERSION_1_1) { - heap = _heapStart; - heapSize = (uint16)_heapSize; - heapOffset = _scriptSize; - } - - VERIFY(block.offset < (uint16)heapSize && READ_SCI11ENDIAN_UINT16(heap + block.offset) * 2 + block.offset < (uint16)heapSize, - "Relocation block outside of script\n"); - - int count = READ_SCI11ENDIAN_UINT16(heap + block.offset); - int exportIndex = 0; - - for (int i = 0; i < count; i++) { - int pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset; - // This occurs in SCI01/SCI1 games where every 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; - if (!pos) - error("Script::relocate(): Consecutive zero exports found"); - } - - if (!relocateLocal(block.segment, pos)) { - bool done = false; - uint k; - - ObjMap::iterator it; - const ObjMap::iterator end = _objects.end(); - for (it = _objects.begin(); !done && it != end; ++it) { - if (it->_value.relocate(block.segment, pos, _scriptSize)) - done = true; - } - - // Sanity check for SCI0-SCI1 - if (getSciVersion() < SCI_VERSION_1_1) { - for (k = 0; !done && k < _codeBlocks.size(); k++) { - if (pos >= _codeBlocks[k].pos.offset && - pos < _codeBlocks[k].pos.offset + _codeBlocks[k].size) - done = true; - } - } - - if (!done) { - debug("While processing relocation block %04x:%04x:\n", PRINT_REG(block)); - debug("Relocation failed for index %04x (%d/%d)\n", pos, exportIndex + 1, count); - if (_localsBlock) - debug("- locals: %d at %04x\n", _localsBlock->_locals.size(), _localsOffset); - else - debug("- No locals\n"); - for (it = _objects.begin(), k = 0; it != end; ++it, ++k) - debug("- obj#%d at %04x w/ %d vars\n", k, it->_value.getPos().offset, it->_value.getVarCount()); - debug("Trying to continue anyway...\n"); - } - } - - exportIndex++; - } -} - -void Script::incrementLockers() { - _lockers++; -} - -void Script::decrementLockers() { - if (_lockers > 0) - _lockers--; -} - -int Script::getLockers() const { - return _lockers; -} - -void Script::setLockers(int lockers) { - _lockers = lockers; -} - -uint16 Script::validateExportFunc(int pubfunct) { - bool exportsAreWide = (g_sci->_features->detectLofsType() == SCI_VERSION_1_MIDDLE); - - if (_numExports <= pubfunct) { - warning("validateExportFunc(): pubfunct is invalid"); - return 0; - } - - if (exportsAreWide) - pubfunct *= 2; - uint16 offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct); - VERIFY(offset < _bufSize, "invalid export function pointer"); - - return offset; -} - -byte *Script::findBlock(int type) { - byte *buf = _buf; - bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - - if (oldScriptHeader) - buf += 2; - - do { - int seekerType = READ_LE_UINT16(buf); - - if (seekerType == 0) - break; - if (seekerType == type) - return buf; - - int seekerSize = READ_LE_UINT16(buf + 2); - assert(seekerSize > 0); - buf += seekerSize; - } while (1); - - return NULL; -} - - -// memory operations - -void Script::mcpyInOut(int dst, const void *src, size_t n) { - if (_buf) { - assert(dst + n <= _bufSize); - memcpy(_buf + dst, src, n); - } -} - -int16 Script::getHeap(uint16 offset) const { - assert(offset + 1 < (int)_bufSize); - return READ_SCI11ENDIAN_UINT16(_buf + offset); -// return (_buf[offset] | (_buf[offset+1]) << 8); -} - SegmentRef SegmentObj::dereference(reg_t pointer) { error("Error: Trying to dereference pointer %04x:%04x to inappropriate segment", PRINT_REG(pointer)); return SegmentRef(); } -bool Script::isValidOffset(uint16 offset) const { - return offset < _bufSize; -} - -SegmentRef Script::dereference(reg_t pointer) { - if (pointer.offset > _bufSize) { - warning("Script::dereference(): Attempt to dereference invalid pointer %04x:%04x into script segment (script size=%d)", - PRINT_REG(pointer), (uint)_bufSize); - return SegmentRef(); - } - - SegmentRef ret; - ret.isRaw = true; - ret.maxSize = _bufSize - pointer.offset; - ret.raw = _buf + pointer.offset; - return ret; -} bool LocalVariables::isValidOffset(uint16 offset) const { return offset < _locals.size() * 2; @@ -471,7 +178,16 @@ SegmentRef LocalVariables::dereference(reg_t pointer) { if (ret.maxSize > 0) { ret.reg = &_locals[pointer.offset / 2]; } else { - warning("LocalVariables::dereference: Offset at end or out of bounds %04x:%04x", PRINT_REG(pointer)); + if ((g_sci->getEngineState()->currentRoomNumber() == 660 || g_sci->getEngineState()->currentRoomNumber() == 660) + && g_sci->getGameId() == GID_LAURABOW2) { + // Happens in two places during the intro of LB2CD, both from kMemory(peek): + // - room 160: Heap 160 has 83 local variables (0-82), and the game + // asks for variables at indices 83 - 90 too. + // - room 220: Heap 220 has 114 local variables (0-113), and the + // game asks for variables at indices 114-120 too. + } else { + error("LocalVariables::dereference: Offset at end or out of bounds %04x:%04x", PRINT_REG(pointer)); + } ret.reg = 0; } return ret; @@ -518,58 +234,21 @@ SegmentRef SystemStrings::dereference(reg_t pointer) { if (isValidOffset(pointer.offset)) ret.raw = (byte *)(_strings[pointer.offset]._value); else { - // This occurs in KQ5CD when interacting with certain objects - warning("SystemStrings::dereference(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); - } - - return ret; -} - - -//-------------------- script -------------------- -reg_t Script::findCanonicAddress(SegManager *segMan, reg_t addr) const { - addr.offset = 0; - return addr; -} - -void Script::freeAtAddress(SegManager *segMan, reg_t addr) { - /* - debugC(2, kDebugLevelGC, "[GC] Freeing script %04x:%04x", PRINT_REG(addr)); - if (_localsSegment) - debugC(2, kDebugLevelGC, "[GC] Freeing locals %04x:0000", _localsSegment); - */ - - if (_markedAsDeleted) - segMan->deallocateScript(_nr); -} - -void Script::listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) const { - (*note)(param, make_reg(segId, 0)); -} - -void Script::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { - if (addr.offset <= _bufSize && addr.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && RAW_IS_OBJECT(_buf + addr.offset)) { - const Object *obj = getObject(addr.offset); - if (obj) { - // Note all local variables, if we have a local variable environment - if (_localsSegment) - (*note)(param, make_reg(_localsSegment, 0)); - - for (uint i = 0; i < obj->getVarCount(); i++) - (*note)(param, obj->getVariable(i)); + if (g_sci->getGameId() == GID_KQ5) { + // This occurs in KQ5CD when interacting with certain objects } else { - warning("Request for outgoing script-object reference at %04x:%04x failed", PRINT_REG(addr)); + error("SystemStrings::dereference(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); } - } else { - /* warning("Unexpected request for outgoing script-object references at %04x:%04x", PRINT_REG(addr));*/ - /* Happens e.g. when we're looking into strings */ } + + return ret; } //-------------------- clones -------------------- -void CloneTable::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { +Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { + Common::Array<reg_t> tmp; // assert(addr.segment == _segId); if (!isValidEntry(addr.offset)) { @@ -580,20 +259,20 @@ void CloneTable::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback // Emit all member variables (including references to the 'super' delegate) for (uint i = 0; i < clone->getVarCount(); i++) - (*note)(param, clone->getVariable(i)); + tmp.push_back(clone->getVariable(i)); // Note that this also includes the 'base' object, which is part of the script and therefore also emits the locals. - (*note)(param, clone->getPos()); + tmp.push_back(clone->getPos()); //debugC(2, kDebugLevelGC, "[GC] Reporting clone-pos %04x:%04x", PRINT_REG(clone->pos)); + + return tmp; } void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) { #ifdef GC_DEBUG - Object *victim_obj; - -// assert(addr.segment == _segId); + // assert(addr.segment == _segId); - victim_obj = &(_table[addr.offset]); + Object *victim_obj = &(_table[addr.offset]); if (!(victim_obj->_flags & OBJECT_FLAG_FREED)) warning("[GC] Clone %04x:%04x not reachable and not freed (freeing now)", PRINT_REG(addr)); @@ -620,11 +299,14 @@ reg_t LocalVariables::findCanonicAddress(SegManager *segMan, reg_t addr) const { return make_reg(owner_seg, 0); } -void LocalVariables::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { +Common::Array<reg_t> LocalVariables::listAllOutgoingReferences(reg_t addr) const { + Common::Array<reg_t> tmp; // assert(addr.segment == _segId); for (uint i = 0; i < _locals.size(); i++) - (*note)(param, _locals[i]); + tmp.push_back(_locals[i]); + + return tmp; } @@ -634,11 +316,14 @@ reg_t DataStack::findCanonicAddress(SegManager *segMan, reg_t addr) const { return addr; } -void DataStack::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { +Common::Array<reg_t> DataStack::listAllOutgoingReferences(reg_t object) const { + Common::Array<reg_t> tmp; fprintf(stderr, "Emitting %d stack entries\n", _capacity); for (int i = 0; i < _capacity; i++) - (*note)(param, _entries[i]); + tmp.push_back(_entries[i]); fprintf(stderr, "DONE"); + + return tmp; } @@ -647,18 +332,20 @@ void ListTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { freeEntry(sub_addr.offset); } -void ListTable::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { +Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const { + Common::Array<reg_t> tmp; if (!isValidEntry(addr.offset)) { - warning("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); - return; + error("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } const List *list = &(_table[addr.offset]); - note(param, list->first); - note(param, list->last); + tmp.push_back(list->first); + tmp.push_back(list->last); // We could probably get away with just one of them, but // let's be conservative here. + + return tmp; } @@ -667,19 +354,21 @@ void NodeTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { freeEntry(sub_addr.offset); } -void NodeTable::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { +Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const { + Common::Array<reg_t> tmp; if (!isValidEntry(addr.offset)) { - warning("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); - return; + error("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } const Node *node = &(_table[addr.offset]); // 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 - note(param, node->pred); - note(param, node->succ); - note(param, node->key); - note(param, node->value); + tmp.push_back(node->pred); + tmp.push_back(node->succ); + tmp.push_back(node->key); + tmp.push_back(node->value); + + return tmp; } @@ -743,7 +432,7 @@ int Object::propertyOffsetToId(SegManager *segMan, int propertyOffset) const { int selectors = getVarCount(); if (propertyOffset < 0 || (propertyOffset >> 1) >= selectors) { - warning("Applied propertyOffsetToId to invalid property offset %x (property #%d not in [0..%d])", + error("Applied propertyOffsetToId to invalid property offset %x (property #%d not in [0..%d])", propertyOffset, propertyOffset >> 1, selectors - 1); return -1; } @@ -800,8 +489,9 @@ reg_t DynMem::findCanonicAddress(SegManager *segMan, reg_t addr) const { return addr; } -void DynMem::listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) const { - (*note)(param, make_reg(segId, 0)); +Common::Array<reg_t> DynMem::listAllDeallocatable(SegmentId segId) const { + const reg_t r = make_reg(segId, 0); + return Common::Array<reg_t>(&r, 1); } #ifdef ENABLE_SCI32 @@ -819,10 +509,10 @@ void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { freeEntry(sub_addr.offset); } -void ArrayTable::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback note) const { +Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const { + Common::Array<reg_t> tmp; if (!isValidEntry(addr.offset)) { - warning("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); - return; + error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } const SciArray<reg_t> *array = &(_table[addr.offset]); @@ -830,8 +520,10 @@ void ArrayTable::listAllOutgoingReferences(reg_t addr, void *param, NoteCallback for (uint32 i = 0; i < array->getSize(); i++) { reg_t value = array->getValue(i); if (value.segment != 0) - note(param, value); + tmp.push_back(value); } + + return tmp; } Common::String SciString::toString() const { diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index f1b6dccaa2..2465576302 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -67,7 +67,7 @@ enum SegmentType { SEG_TYPE_NODES = 7, SEG_TYPE_HUNK = 8, SEG_TYPE_DYNMEM = 9, - SEG_TYPE_STRING_FRAG = 10, // obsolete, we keep it to be able to load old saves + // 10 used to be string fragments, now obsolete #ifdef ENABLE_SCI32 SEG_TYPE_ARRAY = 11, @@ -80,10 +80,9 @@ enum SegmentType { struct SegmentObj : public Common::Serializable { SegmentType _type; - typedef void (*NoteCallback)(void *param, reg_t addr); // FIXME: Bad choice of name - public: static SegmentObj *createSegmentObj(SegmentType type); + static const char *getSegmentTypeName(SegmentType type); public: SegmentObj(SegmentType type) : _type(type) {} @@ -106,6 +105,7 @@ public: /** * Finds the canonic address associated with sub_reg. + * Used by the garbage collector. * * For each valid address a, there exists a canonic address c(a) such that c(a) = c(c(a)). * This address "governs" a in the sense that deallocating c(a) will deallocate a. @@ -116,25 +116,31 @@ public: /** * Deallocates all memory associated with the specified address. + * Used by the garbage collector. * @param sub_addr address (within the given segment) to deallocate */ virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) {} /** - * Iterates over and reports all addresses within the current segment. - * @param note Invoked for each address on which free_at_address() makes sense - * @param param parameter passed to 'note' + * Iterates over and reports all addresses within the segment. + * Used by the garbage collector. + * @return a list of addresses within the segment */ - virtual void listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) const {} + virtual Common::Array<reg_t> listAllDeallocatable(SegmentId segId) const { + return Common::Array<reg_t>(); + } /** * Iterates over all references reachable from the specified object. - * @param object object (within the current segment) to analyse - * @param param parameter passed to 'note' - * @param note Invoked for each outgoing reference within the object - * Note: This function may also choose to report numbers (segment 0) as adresses + * Used by the garbage collector. + * @param object object (within the current segment) to analyse + * @return a list of outgoing references within the object + * + * @note This function may also choose to report numbers (segment 0) as adresses */ - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const {} + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const { + return Common::Array<reg_t>(); + } }; @@ -195,7 +201,7 @@ public: virtual bool isValidOffset(uint16 offset) const; virtual SegmentRef dereference(reg_t pointer); virtual reg_t findCanonicAddress(SegManager *segMan, reg_t sub_addr) const; - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; virtual void saveLoadWithSerializer(Common::Serializer &ser); }; @@ -331,199 +337,6 @@ private: reg_t _pos; /**< Object offset within its script; for clones, this is their base */ }; -struct CodeBlock { - reg_t pos; - int size; -}; - -typedef Common::HashMap<uint16, Object> ObjMap; - -class Script : public SegmentObj { -public: - int _nr; /**< Script number */ - byte *_buf; /**< Static data buffer, or NULL if not used */ - byte *_heapStart; /**< Start of heap if SCI1.1, NULL otherwise */ - - uint32 getScriptSize() { return _scriptSize; } - uint32 getHeapSize() { return _heapSize; } - uint32 getBufSize() { return _bufSize; } - -protected: - int _lockers; /**< Number of classes and objects that require this script */ - -private: - size_t _scriptSize; - size_t _heapSize; - 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 */ - - const byte *_synonyms; /**< Synonyms block or 0 if not present*/ - uint16 _numSynonyms; /**< Number of entries in the synonyms block */ - - Common::Array<CodeBlock> _codeBlocks; - -public: - /** - * Table for objects, contains property variables. - * Indexed by the TODO offset. - */ - ObjMap _objects; - - int _localsOffset; - SegmentId _localsSegment; /**< The local variable segment */ - LocalVariables *_localsBlock; - - bool _markedAsDeleted; - -public: - Script(); - ~Script(); - - void freeScript(); - void init(int script_nr, ResourceManager *resMan); - void load(ResourceManager *resMan); - - virtual bool isValidOffset(uint16 offset) const; - virtual SegmentRef dereference(reg_t pointer); - virtual reg_t findCanonicAddress(SegManager *segMan, reg_t sub_addr) const; - virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); - virtual void listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) const; - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; - - virtual void saveLoadWithSerializer(Common::Serializer &ser); - - Object *allocateObject(uint16 offset); - Object *getObject(uint16 offset); - const Object *getObject(uint16 offset) const; - - /** - * Informs the segment manager that a code block must be relocated - * @param location Start of block to relocate - */ - void scriptAddCodeBlock(reg_t location); - - /** - * Initializes an object within the segment manager - * @param obj_pos Location (segment, offset) of the object. It must - * point to the beginning of the script/class block - * (as opposed to what the VM considers to be the - * object location) - * @returns A newly created Object describing the object, - * stored within the relevant script - */ - Object *scriptObjInit(reg_t obj_pos, bool fullObjectInit = true); - - /** - * Removes a script object - * @param obj_pos Location (segment, offset) of the object. - */ - void scriptObjRemove(reg_t obj_pos); - - /** - * Processes a relocation block witin a script - * This function is idempotent, but it must only be called after all - * objects have been instantiated, or a run-time error will occur. - * @param obj_pos Location (segment, offset) of the block - * @return Location of the relocation block - */ - void relocate(reg_t block); - -private: - bool relocateLocal(SegmentId segment, int location); - -public: - // script lock operations - - /** Increments the number of lockers of this script by one. */ - void incrementLockers(); - - /** Decrements the number of lockers of this script by one. */ - void decrementLockers(); - - /** - * Retrieves the number of locks held on this script. - * @return the number of locks held on the previously identified script - */ - int getLockers() const; - - /** Sets the number of locks held on this script. */ - void setLockers(int lockers); - - /** - * Retrieves a pointer to the exports of this script - * @return pointer to the exports. - */ - const uint16 *getExportTable() const { return _exportTable; } - - /** - * Retrieves the number of exports of script. - * @return the number of exports of this script - */ - uint16 getExportsNr() const { return _numExports; } - - /** - * Retrieves a pointer to the synonyms associated with this script - * @return pointer to the synonyms, in non-parsed format. - */ - const byte *getSynonyms() const { return _synonyms; } - - /** - * Retrieves the number of synonyms associated with this script. - * @return the number of synonyms associated with this script - */ - uint16 getSynonymsNr() const { return _numSynonyms; } - - /** - * Validate whether the specified public function is exported by - * the script in the specified segment. - * @param pubfunct Index of the function to validate - * @return NULL if the public function is invalid, its - * offset into the script's segment otherwise - */ - uint16 validateExportFunc(int pubfunct); - - /** - * Marks the script as deleted. - * This will not actually delete the script. If references remain present on the - * heap or the stack, the script will stay in memory in a quasi-deleted state until - * either unreachable (resulting in its eventual deletion) or reloaded (resulting - * in its data being updated). - */ - void markDeleted() { - _markedAsDeleted = true; - } - - /** - * Determines whether the script is marked as being deleted. - */ - bool isMarkedAsDeleted() const { - return _markedAsDeleted; - } - - /** - * Copies a byte string into a script's heap representation. - * @param dst script-relative offset of the destination area - * @param src pointer to the data source location - * @param n number of bytes to copy - */ - void mcpyInOut(int dst, const void *src, size_t n); - - - /** - * Retrieves a 16 bit value from within a script's heap representation. - * @param offset offset to read from - * @return the value read from the specified location - */ - int16 getHeap(uint16 offset) const; - - /** - * Finds the pointer where a block of a specific type starts from - */ - byte *findBlock(int type); -}; - /** Data stack */ struct DataStack : SegmentObj { int _capacity; /**< Number of stack entries */ @@ -542,7 +355,7 @@ public: virtual bool isValidOffset(uint16 offset) const; virtual SegmentRef dereference(reg_t pointer); virtual reg_t findCanonicAddress(SegManager *segMan, reg_t sub_addr) const; - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; virtual void saveLoadWithSerializer(Common::Serializer &ser); }; @@ -630,10 +443,12 @@ public: entries_used--; } - virtual void listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) const { + virtual Common::Array<reg_t> listAllDeallocatable(SegmentId segId) const { + Common::Array<reg_t> tmp; for (uint i = 0; i < _table.size(); i++) if (isValidEntry(i)) - (*note)(param, make_reg(segId, i)); + tmp.push_back(make_reg(segId, i)); + return tmp; } }; @@ -643,7 +458,7 @@ struct CloneTable : public Table<Clone> { CloneTable() : Table<Clone>(SEG_TYPE_CLONES) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; virtual void saveLoadWithSerializer(Common::Serializer &ser); }; @@ -654,7 +469,7 @@ struct NodeTable : public Table<Node> { NodeTable() : Table<Node>(SEG_TYPE_NODES) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; virtual void saveLoadWithSerializer(Common::Serializer &ser); }; @@ -665,7 +480,7 @@ struct ListTable : public Table<List> { ListTable() : Table<List>(SEG_TYPE_LISTS) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; virtual void saveLoadWithSerializer(Common::Serializer &ser); }; @@ -678,7 +493,10 @@ struct HunkTable : public Table<Hunk> { virtual void freeEntry(int idx) { Table<Hunk>::freeEntry(idx); + if (!_table[idx].mem) + warning("Attempt to free an already freed hunk"); free(_table[idx].mem); + _table[idx].mem = 0; } virtual void saveLoadWithSerializer(Common::Serializer &ser); @@ -701,7 +519,7 @@ public: virtual bool isValidOffset(uint16 offset) const; virtual SegmentRef dereference(reg_t pointer); virtual reg_t findCanonicAddress(SegManager *segMan, reg_t sub_addr) const; - virtual void listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllDeallocatable(SegmentId segId) const; virtual void saveLoadWithSerializer(Common::Serializer &ser); }; @@ -811,6 +629,7 @@ public: byte getType() const { return _type; } uint32 getSize() const { return _size; } T *getRawData() { return _data; } + const T *getRawData() const { return _data; } protected: int8 _type; @@ -834,7 +653,7 @@ struct ArrayTable : public Table<SciArray<reg_t> > { ArrayTable() : Table<SciArray<reg_t> >(SEG_TYPE_ARRAY) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); - virtual void listAllOutgoingReferences(reg_t object, void *param, NoteCallback note) const; + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; void saveLoadWithSerializer(Common::Serializer &ser); SegmentRef dereference(reg_t pointer); diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 04a1b8fbba..00480743cc 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -24,6 +24,7 @@ */ #include "sci/sci.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" @@ -157,6 +158,9 @@ void Kernel::mapSelectors() { FIND_SELECTOR(scaleSignal); FIND_SELECTOR(scaleX); FIND_SELECTOR(scaleY); + FIND_SELECTOR(maxScale); + FIND_SELECTOR(vanishingX); + FIND_SELECTOR(vanishingY); FIND_SELECTOR(iconIndex); #ifdef ENABLE_SCI32 @@ -188,42 +192,37 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t ObjVarRef address; if ((selectorId < 0) || (selectorId > (int)g_sci->getKernel()->getSelectorNamesSize())) { - warning("Attempt to write to invalid selector %d of" + error("Attempt to write to invalid selector %d of" " object at %04x:%04x.", selectorId, PRINT_REG(object)); return; } if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) - warning("Selector '%s' of object at %04x:%04x could not be" + error("Selector '%s' of object at %04x:%04x could not be" " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); else *address.getPointer(segMan) = value; } -int invokeSelectorArgv(EngineState *s, reg_t object, int selectorId, SelectorInvocation noinvalid, +void invokeSelector(EngineState *s, reg_t object, int selectorId, int k_argc, StackPtr k_argp, int argc, const reg_t *argv) { int i; int framesize = 2 + 1 * argc; - reg_t address; int slc_type; StackPtr stackframe = k_argp + k_argc; stackframe[0] = make_reg(0, selectorId); // The selector we want to call stackframe[1] = make_reg(0, argc); // Argument count - slc_type = lookupSelector(s->_segMan, object, selectorId, NULL, &address); + slc_type = lookupSelector(s->_segMan, object, selectorId, NULL, NULL); if (slc_type == kSelectorNone) { - warning("Selector '%s' of object at %04x:%04x could not be invoked", + error("Selector '%s' of object at %04x:%04x could not be invoked", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); - if (noinvalid == kStopOnInvalidSelector) - error("[Kernel] Not recoverable: VM was halted"); - return 1; } if (slc_type == kSelectorVariable) { - warning("Attempting to invoke variable selector %s of object %04x:%04x", + error("Attempting to invoke variable selector %s of object %04x:%04x", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); - return 0; } for (i = 0; i < argc; i++) @@ -238,24 +237,6 @@ int invokeSelectorArgv(EngineState *s, reg_t object, int selectorId, SelectorInv xstack->fp += argc + 2; run_vm(s, false); // Start a new vm - - return 0; -} - -int invokeSelector(EngineState *s, reg_t object, int selectorId, SelectorInvocation noinvalid, - int k_argc, StackPtr k_argp, int argc, ...) { - va_list argp; - reg_t *args = new reg_t[argc]; - - va_start(argp, argc); - for (int i = 0; i < argc; i++) - args[i] = va_arg(argp, reg_t); - va_end(argp); - - int retval = invokeSelectorArgv(s, object, selectorId, noinvalid, k_argc, k_argp, argc, args); - - delete[] args; - return retval; } SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector selectorId, ObjVarRef *varp, reg_t *fptr) { diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index f50b9ab1b3..acb7912d8d 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -30,13 +30,118 @@ #include "sci/engine/vm_types.h" // for reg_t #include "sci/engine/vm.h" -#include "sci/engine/kernel.h" // for Kernel::_selectorCache namespace Sci { -enum SelectorInvocation { - kStopOnInvalidSelector = 0, - kContinueOnInvalidSelector = 1 +/** Contains selector IDs for a few selected selectors */ +struct SelectorCache { + SelectorCache() { + memset(this, 0, sizeof(*this)); + } + + // Statically defined selectors, (almost the) same in all SCI versions + Selector y; + Selector x; + Selector view, loop, cel; ///< Description of a specific image + Selector underBits; ///< Used by the graphics subroutines to store backupped BG pic data + Selector nsTop, nsLeft, nsBottom, nsRight; ///< View boundaries ('now seen') + Selector lsTop, lsLeft, lsBottom, lsRight; ///< Used by Animate() subfunctions and scroll list controls + Selector signal; ///< Used by Animate() to control a view's behaviour + Selector illegalBits; ///< Used by CanBeHere + Selector brTop, brLeft, brBottom, brRight; ///< Bounding Rectangle + // name, key, time + Selector text; ///< Used by controls + Selector elements; ///< Used by SetSynonyms() + // color, back + Selector mode; ///< Used by text controls (-> DrawControl()) + // style + Selector state, font, type;///< Used by controls + // window + Selector cursor, max; ///< Used by EditControl + // mark, who + Selector message; ///< Used by GetEvent + // edit + Selector play; ///< Play function (first function to be called) + Selector number; + Selector handle; ///< Replaced by nodePtr in SCI1+ + Selector nodePtr; ///< Replaces handle in SCI1+ + Selector client; ///< The object that wants to be moved + Selector dx, dy; ///< Deltas + Selector b_movCnt, b_i1, b_i2, b_di, b_xAxis, b_incr; ///< Various Bresenham vars + Selector xStep, yStep; ///< BR adjustments + Selector moveSpeed; ///< Used for DoBresen + Selector canBeHere; ///< Funcselector: Checks for movement validity in SCI0 + Selector heading, mover; ///< Used in DoAvoider + Selector doit; ///< Called (!) by the Animate() system call + Selector isBlocked, looper; ///< Used in DoAvoider + Selector priority; + Selector modifiers; ///< Used by GetEvent + Selector replay; ///< Replay function + // setPri, at, next, done, width + Selector wordFail, syntaxFail; ///< Used by Parse() + // semanticFail, pragmaFail + // said + Selector claimed; ///< Used generally by the event mechanism + // value, save, restore, title, button, icon, draw + Selector delete_; ///< Called by Animate() to dispose a view object + Selector z; + + // SCI1+ static selectors + Selector parseLang; + Selector printLang; ///< Used for i18n + Selector subtitleLang; + Selector size; + Selector points; ///< Used by AvoidPath() + Selector palette; + Selector dataInc; + // handle (in SCI1) + Selector min; ///< SMPTE time format + Selector sec; + Selector frame; + Selector vol; + Selector pri; + // perform + Selector moveDone; ///< used for DoBresen + + // SCI1 selectors which have been moved a bit in SCI1.1, but otherwise static + Selector cantBeHere; ///< Checks for movement avoidance in SCI1+. Replaces canBeHere + Selector topString; ///< SCI1 scroll lists use this instead of lsTop + Selector flags; + + // SCI1+ audio sync related selectors, not static. They're used for lip syncing in + // CD talkie games + Selector syncCue; ///< Used by DoSync() + Selector syncTime; + + // SCI1.1 specific selectors + Selector scaleSignal; //< Used by kAnimate() for cel scaling (SCI1.1+) + Selector scaleX, scaleY; ///< SCI1.1 view scaling + Selector maxScale; ///< SCI1.1 view scaling, limit for cel, when using global scaling + Selector vanishingX; ///< SCI1.1 view scaling, used by global scaling + Selector vanishingY; ///< SCI1.1 view scaling, used by global scaling + + // Used for auto detection purposes + Selector overlay; ///< Used to determine if a game is using old gfx functions or not + + // SCI1.1 Mac icon bar selectors + Selector iconIndex; ///< Used to index icon bar objects + +#ifdef ENABLE_SCI32 + Selector data; // Used by Array()/String() + Selector picture; // Used to hold the picture ID for SCI32 pictures + + Selector plane; + Selector top; + Selector left; + Selector bottom; + Selector right; + Selector resX; + Selector resY; + + Selector fore; + Selector back; + Selector dimmed; +#endif }; /** @@ -71,18 +176,8 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t /** * Invokes a selector from an object. */ -int invokeSelector(EngineState *s, reg_t object, int selectorId, SelectorInvocation noinvalid, - int k_argc, StackPtr k_argp, int argc, ...); -int invokeSelectorArgv(EngineState *s, reg_t object, int selectorId, SelectorInvocation noinvalid, - int k_argc, StackPtr k_argp, int argc, const reg_t *argv); - -/** - * Kludge for use with invokeSelector(). Used for compatibility with compilers - * that cannot handle vararg macros. - */ -#define INV_SEL(s, _object_, _selector_, _noinvalid_) \ - s, _object_, g_sci->getKernel()->_selectorCache._selector_, _noinvalid_, argc, argv - +void invokeSelector(EngineState *s, reg_t object, int selectorId, + int k_argc, StackPtr k_argp, int argc = 0, const reg_t *argv = 0); } // End of namespace Sci diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index b4a04f8826..36b03c0ad9 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -29,6 +29,7 @@ #include "sci/debug.h" // for g_debug_sleeptime_factor #include "sci/event.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/engine/vm.h" @@ -80,73 +81,91 @@ EngineState::~EngineState() { } void EngineState::reset(bool isRestoring) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - sfx_init_flags = 0; -#endif - if (!isRestoring) { - script_000 = 0; - _gameObj = NULL_REG; - _memorySegmentSize = 0; - _soundCmd = 0; - restarting_flags = 0; + _fileHandles.resize(5); - execution_stack_base = 0; - _executionStackPosChanged = false; + abortScriptProcessing = kAbortNone; + } - _fileHandles.resize(5); + executionStackBase = 0; + _executionStackPosChanged = false; + stack_base = 0; + stack_top = 0; - r_acc = NULL_REG; - restAdjust = 0; - r_prev = NULL_REG; + restAdjust = 0; - stack_base = 0; - stack_top = 0; - } + r_acc = NULL_REG; + r_prev = NULL_REG; - last_wait_time = 0; + lastWaitTime = 0; - gc_countdown = 0; + gcCountDown = 0; _throttleCounter = 0; _throttleLastTime = 0; _throttleTrigger = false; - script_abort_flag = 0; - script_step_counter = 0; - script_gc_interval = GC_INTERVAL; + _lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START; + _lastSaveNewId = 0; - restoring = false; + scriptStepCounter = 0; + scriptGCInterval = GC_INTERVAL; } -void EngineState::wait(int16 ticks) { - uint32 time; +void EngineState::speedThrottler(uint32 neededSleep) { + if (_throttleTrigger) { + uint32 curTime = g_system->getMillis(); + uint32 duration = curTime - _throttleLastTime; + + if (duration < neededSleep) { + g_sci->sleep(neededSleep - duration); + _throttleLastTime = g_system->getMillis(); + } else { + _throttleLastTime = curTime; + } + _throttleTrigger = false; + } +} - time = g_system->getMillis(); - r_acc = make_reg(0, ((long)time - (long)last_wait_time) * 60 / 1000); - last_wait_time = time; +void EngineState::wait(int16 ticks) { + uint32 time = g_system->getMillis(); + r_acc = make_reg(0, ((long)time - (long)lastWaitTime) * 60 / 1000); + lastWaitTime = time; ticks *= g_debug_sleeptime_factor; - _event->sleep(ticks * 1000 / 60); + g_sci->sleep(ticks * 1000 / 60); +} + +void EngineState::initGlobals() { + Script *script_000 = _segMan->getScript(1); + + if (!script_000->_localsBlock) + error("Script 0 has no locals block"); + + variablesSegment[VAR_GLOBAL] = script_000->_localsSegment; + variablesBase[VAR_GLOBAL] = variables[VAR_GLOBAL] = script_000->_localsBlock->_locals.begin(); + variablesMax[VAR_GLOBAL] = script_000->_localsBlock->_locals.size(); } uint16 EngineState::currentRoomNumber() const { - return script_000->_localsBlock->_locals[13].toUint16(); + return variables[VAR_GLOBAL][13].toUint16(); } void EngineState::setRoomNumber(uint16 roomNumber) { - script_000->_localsBlock->_locals[13] = make_reg(0, roomNumber); + variables[VAR_GLOBAL][13] = make_reg(0, roomNumber); } void EngineState::shrinkStackToBase() { - uint size = execution_stack_base + 1; - assert(_executionStack.size() >= size); - Common::List<ExecStack>::iterator iter = _executionStack.begin(); - for (uint i = 0; i < size; ++i) - ++iter; - _executionStack.erase(iter, _executionStack.end()); + if (_executionStack.size() > 0) { + uint size = executionStackBase + 1; + assert(_executionStack.size() >= size); + Common::List<ExecStack>::iterator iter = _executionStack.begin(); + for (uint i = 0; i < size; ++i) + ++iter; + _executionStack.erase(iter, _executionStack.end()); + } } static kLanguage charToLanguage(const char c) { @@ -204,7 +223,7 @@ Common::String SciEngine::getSciLanguageString(const char *str, kLanguage lang, // Copy double-byte character char c2 = *(++seeker); if (!c2) { - warning("SJIS character %02X is missing second byte", c); + error("SJIS character %02X is missing second byte", c); break; } fullWidth += c; @@ -231,8 +250,8 @@ kLanguage SciEngine::getSciLanguage() { lang = K_LANG_ENGLISH; - if (_kernel->_selectorCache.printLang != -1) { - lang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gamestate->_gameObj, SELECTOR(printLang)); + if (SELECTOR(printLang) != -1) { + lang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(printLang)); if ((getSciVersion() >= SCI_VERSION_1_1) || (lang == K_LANG_NONE)) { // If language is set to none, we use the language from the game detector. @@ -265,21 +284,27 @@ kLanguage SciEngine::getSciLanguage() { default: lang = K_LANG_ENGLISH; } - - // Store language in printLang selector - writeSelectorValue(_gamestate->_segMan, _gamestate->_gameObj, SELECTOR(printLang), lang); } } return lang; } +void SciEngine::setSciLanguage(kLanguage lang) { + if (SELECTOR(printLang) != -1) + writeSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(printLang), lang); +} + +void SciEngine::setSciLanguage() { + setSciLanguage(getSciLanguage()); +} + Common::String SciEngine::strSplit(const char *str, const char *sep) { kLanguage lang = getSciLanguage(); kLanguage subLang = K_LANG_NONE; - if (_kernel->_selectorCache.subtitleLang != -1) { - subLang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gamestate->_gameObj, SELECTOR(subtitleLang)); + if (SELECTOR(subtitleLang) != -1) { + subLang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(subtitleLang)); } kLanguage secondLang; diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index bcdf66d6ef..243a460645 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -41,17 +41,21 @@ namespace Common { #include "sci/parser/vocabulary.h" -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/core.h" -#endif #include "sci/sound/soundcmd.h" namespace Sci { -class SciEvent; +class EventManager; class MessageState; class SoundCommandParser; +enum AbortGameState { + kAbortNone = 0, + kAbortLoadGame = 1, + kAbortRestartGame = 2, + kAbortQuitGame = 3 +}; + class DirSeeker { protected: reg_t _outbuffer; @@ -72,11 +76,15 @@ enum { MAX_SAVE_DIR_SIZE = MAXPATHLEN }; -/** values for EngineState.restarting_flag */ enum { - SCI_GAME_IS_NOT_RESTARTING = 0, - SCI_GAME_WAS_RESTARTED = 1, - SCI_GAME_IS_RESTARTING_NOW = 2 + MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ +}; + +// We assume that scripts give us savegameId 0->999 for creating a new save slot +// and savegameId 1000->1999 for existing save slots ffs. kfile.cpp +enum { + SAVEGAMEID_OFFICIALRANGE_START = 1000, + SAVEGAMEID_OFFICIALRANGE_END = 1999 }; class FileHandle { @@ -105,19 +113,11 @@ public: /* Non-VM information */ - SciEvent *_event; // Event handling - -#ifdef USE_OLD_MUSIC_FUNCTIONS - SfxState _sound; /**< sound subsystem */ - int sfx_init_flags; /**< flags the sfx subsystem was initialised with */ -#endif - SoundCommandParser *_soundCmd; - - byte restarting_flags; /**< Flags used for restarting */ - - uint32 game_start_time; /**< The time at which the interpreter was started */ - uint32 last_wait_time; /**< The last time the game invoked Wait() */ + uint32 gameStartTime; /**< The time at which the interpreter was started */ + uint32 lastWaitTime; /**< The last time the game invoked Wait() */ + uint32 _screenUpdateTime; /**< The last time the game updated the screen */ + void speedThrottler(uint32 neededSleep); void wait(int16 ticks); uint32 _throttleCounter; /**< total times kAnimate was invoked */ @@ -130,6 +130,9 @@ public: DirSeeker _dirseeker; + uint _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween + uint _lastSaveNewId; // last newly created filename-id by kSaveGame + public: /* VM Information */ @@ -138,12 +141,11 @@ public: * When called from kernel functions, the vm is re-started recursively on * the same stack. This variable contains the stack base for the current vm. */ - int execution_stack_base; + int executionStackBase; bool _executionStackPosChanged; /**< Set to true if the execution stack position should be re-evaluated by the vm */ reg_t r_acc; /**< Accumulator */ - int16 restAdjust; /**< &rest register (only used for save games) */ - int16 restAdjustCur; /**< current &rest register (only used for save games) */ + int16 restAdjust; /**< current &rest register */ reg_t r_prev; /**< previous comparison result */ StackPtr stack_base; /**< Pointer to the least stack element */ @@ -152,35 +154,31 @@ public: // Script state ExecStack *xs; reg_t *variables[4]; ///< global, local, temp, param, as immediate pointers - reg_t *variables_base[4]; ///< Used for referencing VM ops - SegmentId variables_seg[4]; ///< Same as above, contains segment IDs - int variables_max[4]; ///< Max. values for all variables + reg_t *variablesBase[4]; ///< Used for referencing VM ops + SegmentId variablesSegment[4]; ///< Same as above, contains segment IDs + int variablesMax[4]; ///< Max. values for all variables - Script *script_000; /**< script 000, e.g. for globals */ + AbortGameState abortScriptProcessing; + bool gameWasRestarted; - int loadFromLauncher; - - /** - * Set this to 1 to abort script execution immediately. Aborting will - * leave the debug exec stack intact. - * Set it to 2 to force a replay afterwards. - */ - int script_abort_flag; // Set to 1 to abort execution. Set to 2 to force a replay afterwards - int script_step_counter; // Counts the number of steps executed - int script_gc_interval; // Number of steps in between gcs + int scriptStepCounter; // Counts the number of steps executed + int scriptGCInterval; // Number of steps in between gcs uint16 currentRoomNumber() const; void setRoomNumber(uint16 roomNumber); /** + * Sets global variables from script 0 + */ + void initGlobals(); + + /** * Shrink execution stack to size. - * Contains an assert it is not already smaller. + * Contains an assert if it is not already smaller. */ void shrinkStackToBase(); - reg_t _gameObj; /**< Pointer to the game object */ - - int gc_countdown; /**< Number of kernel calls until next gc */ + int gcCountDown; /**< Number of kernel calls until next gc */ public: MessageState *_msgState; @@ -197,8 +195,6 @@ public: * Resets the engine state. */ void reset(bool isRestoring); - - bool restoring; /**< A flag to indicate if a game is being restored */ }; } // End of namespace Sci diff --git a/engines/sci/engine/static_selectors.cpp b/engines/sci/engine/static_selectors.cpp index 0f558f2dc8..85089e74c8 100644 --- a/engines/sci/engine/static_selectors.cpp +++ b/engines/sci/engine/static_selectors.cpp @@ -100,13 +100,24 @@ static const SelectorRemap sciSelectorRemap[] = { { SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE, "moveDone", 170 }, { SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE, "points", 316 }, { SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE, "flags", 368 }, - { SCI_VERSION_1_EARLY, SCI_VERSION_1_1, "nodePtr", 44 }, - { SCI_VERSION_1_LATE, SCI_VERSION_1_1, "cantBeHere", 57 }, - { SCI_VERSION_1_EARLY, SCI_VERSION_1_1, "topString", 101 }, - { SCI_VERSION_1_EARLY, SCI_VERSION_1_1, "flags", 102 }, + { SCI_VERSION_1_EARLY, SCI_VERSION_1_LATE, "nodePtr", 44 }, + { SCI_VERSION_1_LATE, SCI_VERSION_1_LATE, "cantBeHere", 57 }, + { SCI_VERSION_1_EARLY, SCI_VERSION_1_LATE, "topString", 101 }, + { SCI_VERSION_1_EARLY, SCI_VERSION_1_LATE, "flags", 102 }, + // SCI1.1 + { SCI_VERSION_1_1, SCI_VERSION_1_1, "nodePtr", 41 }, + { SCI_VERSION_1_1, SCI_VERSION_1_1, "cantBeHere", 54 }, + { SCI_VERSION_1_1, SCI_VERSION_1_1, "topString", 98 }, + { SCI_VERSION_1_1, SCI_VERSION_1_1, "flags", 99 }, + // quitGame + // restart + // hide { SCI_VERSION_1_1, SCI_VERSION_1_1,"scaleSignal", 103 }, { SCI_VERSION_1_1, SCI_VERSION_1_1, "scaleX", 104 }, { SCI_VERSION_1_1, SCI_VERSION_1_1, "scaleY", 105 }, + { SCI_VERSION_1_1, SCI_VERSION_1_1, "maxScale", 106 }, + { SCI_VERSION_1_1, SCI_VERSION_1_1, "vanishingX", 107 }, + { SCI_VERSION_1_1, SCI_VERSION_1_1, "vanishingY", 108 }, { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, 0 } }; @@ -139,16 +150,26 @@ Common::StringArray Kernel::checkStaticSelectorNames() { } for (const SelectorRemap *selectorRemap = sciSelectorRemap; selectorRemap->slot; ++selectorRemap) { - uint32 slot = selectorRemap->slot; - if (selectorRemap->slot >= names.size()) - names.resize(selectorRemap->slot + 1); if (getSciVersion() >= selectorRemap->minVersion && getSciVersion() <= selectorRemap->maxVersion) { - // The SCI1 selectors we use exist in SCI1.1 too, offset by 3 - if (selectorRemap->minVersion >= SCI_VERSION_1_EARLY && getSciVersion() == SCI_VERSION_1_1) - slot -= 3; + const uint32 slot = selectorRemap->slot; + if (slot >= names.size()) + names.resize(slot + 1); names[slot] = selectorRemap->name; } } + + if (g_sci->getGameId() == GID_HOYLE4) { + // The demo of Hoyle 4 is one of the few demos with lip syncing and no selector vocabulary. + // This needs two selectors, "syncTime" and "syncCue", which keep changing positions in each + // game. Usually, games with speech and lip sync have a selector vocabulary, so we don't need + // to set these two selectors, but we need for Hoyle... + if (names.size() < 276) + names.resize(276); + + names[274] = "syncTime"; + names[275] = "syncCue"; + } + #ifdef ENABLE_SCI32 } else { // SCI2+ diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 1dcdf450ba..8108440102 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -30,24 +30,104 @@ #include "sci/sci.h" #include "sci/console.h" -#include "sci/debug.h" // for g_debugState #include "sci/resource.h" #include "sci/engine/features.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" -#include "sci/engine/seg_manager.h" #include "sci/engine/script.h" +#include "sci/engine/seg_manager.h" +#include "sci/engine/selector.h" // for SELECTOR #include "sci/engine/gc.h" namespace Sci { const reg_t NULL_REG = {0, 0}; const reg_t SIGNAL_REG = {0, SIGNAL_OFFSET}; - +const reg_t TRUE_REG = {0, 1}; //#define VM_DEBUG_SEND #define SCI_XS_CALLEE_LOCALS ((SegmentId)-1) +#define END Script_None + +opcode_format g_opcode_formats[128][4] = { + /*00*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*04*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*08*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*0C*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*10*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*14*/ + {Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END}, + /*18*/ + {Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None}, + /*1C*/ + {Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END}, + /*20*/ + {Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END}, + /*24 (24=ret)*/ + {Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid}, + /*28*/ + {Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END}, + /*2C*/ + {Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid}, + /*30*/ + {Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, + /*34*/ + {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, + /*38*/ + {Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None}, + /*3C*/ + {Script_None}, {Script_None}, {Script_None}, {Script_Word}, + /*40-4F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + /*50-5F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + /*60-6F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + /*70-7F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END} +}; +#undef END + +// TODO: script_adjust_opcode_formats should probably be part of the +// constructor (?) of a VirtualMachine or a ScriptManager class. +void script_adjust_opcode_formats() { + if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) { + g_opcode_formats[op_lofsa][0] = Script_Offset; + g_opcode_formats[op_lofss][0] = Script_Offset; + } + +#ifdef ENABLE_SCI32 + // In SCI32, some arguments are now words instead of bytes + if (getSciVersion() >= SCI_VERSION_2) { + g_opcode_formats[op_calle][2] = Script_Word; + g_opcode_formats[op_callk][1] = Script_Word; + g_opcode_formats[op_super][1] = Script_Word; + g_opcode_formats[op_send][0] = Script_Word; + g_opcode_formats[op_self][0] = Script_Word; + g_opcode_formats[op_call][1] = Script_Word; + g_opcode_formats[op_callb][1] = Script_Word; + } +#endif +} + /** * Adds an entry to the top of the execution stack. * @@ -59,6 +139,8 @@ const reg_t SIGNAL_REG = {0, SIGNAL_OFFSET}; * @param[in] argp Heap pointer to the first parameter * @param[in] selector The selector by which it was called or * NULL_SELECTOR if n.a. For debugging. + * @param[in] exportId The exportId by which it was called or + * -1 if n.a. For debugging. * @param[in] sendp Pointer to the object which the message was * sent to. Equal to objp for anything but super. * @param[in] origin Number of the execution stack element this @@ -69,7 +151,7 @@ const reg_t SIGNAL_REG = {0, SIGNAL_OFFSET}; * @return A pointer to the new exec stack TOS entry */ static ExecStack *add_exec_stack_entry(Common::List<ExecStack> &execStack, reg_t pc, StackPtr sp, - reg_t objp, int argc, StackPtr argp, Selector selector, + reg_t objp, int argc, StackPtr argp, Selector selector, int exportId, int localCallOffset, reg_t sendp, int origin, SegmentId local_segment); @@ -94,20 +176,24 @@ static ExecStack *add_exec_stack_varselector(Common::List<ExecStack> &execStack, // validation functionality -#ifndef DISABLE_VALIDATIONS - static reg_t &validate_property(Object *obj, int index) { // A static dummy reg_t, which we return if obj or index turn out to be // invalid. Note that we cannot just return NULL_REG, because client code // may modify the value of the returned reg_t. static reg_t dummyReg = NULL_REG; + // FIXME/TODO: Where does this occur? Returning a dummy reg here could lead + // to all sorts of issues! Turned it into an error for now... + // If this occurs, it means there's probably something wrong with the garbage + // collector, so don't hide it with fake return values if (!obj) { - debugC(2, kDebugLevelVM, "[VM] Sending to disposed object!"); - return dummyReg; + error("validate_property: Sending to disposed object"); + //return dummyReg; } if (index < 0 || (uint)index >= obj->getVarCount()) { + // This is same way sierra does it and there are some games, that contain such scripts like + // iceman script 998 (fred::canBeHere, executed right at the start) debugC(2, kDebugLevelVM, "[VM] Invalid property #%d (out of [0..%d]) requested!", index, obj->getVarCount()); return dummyReg; @@ -127,7 +213,10 @@ static StackPtr validate_stack_addr(EngineState *s, StackPtr sp) { static int validate_arithmetic(reg_t reg) { if (reg.segment) { - warning("[VM] Attempt to read arithmetic value from non-zero segment [%04x]", reg.segment); + // The results of this are likely unpredictable... It most likely means that a kernel function is returning something wrong. + // If such an error occurs, we usually need to find the last kernel function called and check its return value. Check + // callKernelFunc() below + error("[VM] Attempt to read arithmetic value from non-zero segment [%04x]. Address: %04x:%04x", reg.segment, PRINT_REG(reg)); return 0; } @@ -135,15 +224,10 @@ static int validate_arithmetic(reg_t reg) { } static int signed_validate_arithmetic(reg_t reg) { - if (reg.segment) { - warning("[VM] Attempt to read arithmetic value from non-zero segment [%04x]", reg.segment); - return 0; - } - - return (int16)reg.offset; + return (int16)validate_arithmetic(reg); } -static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, int index, int line) { +static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, int index) { const char *names[4] = {"global", "local", "temp", "param"}; if (index < 0 || index >= max) { @@ -158,10 +242,15 @@ static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, in if (type == VAR_PARAM || type == VAR_TEMP) { int total_offset = r - stack_base; if (total_offset < 0 || total_offset >= VM_STACK_SIZE) { - warning("%s", txt.c_str()); - warning("[VM] Access would be outside even of the stack (%d); access denied", total_offset); + // Fatal, as the game is trying to do an OOB access + error("%s. [VM] Access would be outside even of the stack (%d); access denied", txt.c_str(), total_offset); return false; } else { + // WORKAROUND: Mixed-Up Mother Goose tries to use an invalid parameter in Event::new(). + // Just skip around it here so we don't error out in validate_arithmetic. + if (g_sci->getGameId() == GID_MOTHERGOOSE && getSciVersion() <= SCI_VERSION_1_1 && type == VAR_PARAM && index == 1) + return false; + debugC(2, kDebugLevelVM, "%s", txt.c_str()); debugC(2, kDebugLevelVM, "[VM] Access within stack boundaries; access granted."); return true; @@ -173,15 +262,139 @@ static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, in return true; } -static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, int index, int line, reg_t default_value) { - if (validate_variable(r, stack_base, type, max, index, line)) +struct SciTrackOriginReply { + int scriptNr; + Common::String objectName; + Common::String methodName; + int localCallOffset; +}; + +static reg_t trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) { + EngineState *state = g_sci->getEngineState(); + ExecStack *lastCall = state->xs; + Script *local_script = state->_segMan->getScriptIfLoaded(lastCall->local_segment); + int curScriptNr = local_script->getScriptNumber(); + + if (lastCall->debugLocalCallOffset != -1) { + // if lastcall was actually a local call search back for a real call + Common::List<ExecStack>::iterator callIterator = state->_executionStack.end(); + while (callIterator != state->_executionStack.begin()) { + callIterator--; + ExecStack loopCall = *callIterator; + if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) { + lastCall->debugSelector = loopCall.debugSelector; + lastCall->debugExportId = loopCall.debugExportId; + break; + } + } + } + + Common::String curObjectName = state->_segMan->getObjectName(lastCall->sendp); + Common::String curMethodName; + const SciGameId gameId = g_sci->getGameId(); + + if (lastCall->type == EXEC_STACK_TYPE_CALL) { + if (lastCall->debugSelector != -1) { + curMethodName = g_sci->getKernel()->getSelectorName(lastCall->debugSelector); + } else if (lastCall->debugExportId != -1) { + curObjectName = ""; + curMethodName = curMethodName.printf("export %d", lastCall->debugExportId); + } + } + + if (workaroundList) { + // Search if there is a workaround for this one + const SciWorkaroundEntry *workaround; + int16 inheritanceLevel = 0; + Common::String searchObjectName = curObjectName; + reg_t searchObject = lastCall->sendp; + do { + workaround = workaroundList; + while (workaround->objectName) { + if (workaround->gameId == gameId && workaround->scriptNr == curScriptNr + && ((workaround->inheritanceLevel == -1) || (workaround->inheritanceLevel == inheritanceLevel)) + && (workaround->objectName == searchObjectName) + && workaround->methodName == curMethodName && workaround->localCallOffset == lastCall->debugLocalCallOffset && workaround->index == index) { + // Workaround found + return workaround->newValue; + } + workaround++; + } + + // Go back to the parent + inheritanceLevel++; + searchObject = state->_segMan->getObject(searchObject)->getSuperClassSelector(); + if (!searchObject.isNull()) + searchObjectName = state->_segMan->getObjectName(searchObject); + } while (!searchObject.isNull()); // no parent left? + } + + // give caller origin data + trackOrigin->objectName = curObjectName; + trackOrigin->methodName = curMethodName; + trackOrigin->scriptNr = curScriptNr; + trackOrigin->localCallOffset = lastCall->debugLocalCallOffset; + return make_reg(0xFFFF, 0xFFFF); +} + +// gameID, scriptNr,lvl, object-name, method-name, call, index, replace +static const SciWorkaroundEntry uninitializedReadWorkarounds[] = { + { GID_LAURABOW2, 24, 0, "gcWin", "open", -1, 5, { 0, 0xf } }, // is used as priority for game menu + { GID_FREDDYPHARKAS, 24, 0, "gcWin", "open", -1, 5, { 0, 0xf } }, // is used as priority for game menu + { GID_FREDDYPHARKAS, 31, 0, "quitWin", "open", -1, 5, { 0, 0xf } }, // is used as priority for game menu + { GID_GK2, 11, 0, "", "export 10", -1, 3, { 0, 0 } }, // called when the game starts + { GID_JONES, 232, 0, "weekendText", "draw", 0x3d3, 0, { 0, 0 } }, // jones/cd only - gets called during the game + { GID_JONES, 255, 0, "", "export 0", -1, 13, { 0, 0 } }, // jones/ega&vga only - called when the game starts + { GID_JONES, 255, 0, "", "export 0", -1, 14, { 0, 0 } }, // jones/ega&vga only - called when the game starts + { GID_LSL1, 720, 0, "rm720", "init", -1, 0, { 0, 0 } }, // age check room + { GID_LSL3, 997, 0, "TheMenuBar", "handleEvent", -1, 1, { 0, 0xf } }, // when setting volume the first time, this temp is used to set volume on entry (normally it would have been initialized to 's') + { GID_LSL6, 928, -1, "Narrator", "startText", -1, 0, { 0, 0 } }, // used by various objects that are even translated in foreign versions, that's why we use the base-class + { GID_ISLANDBRAIN, 140, 0, "piece", "init", -1, 3, { 0, 1 } }, // first puzzle right at the start, some initialization variable. bnt is done on it, and it should be non-0 + { GID_ISLANDBRAIN, 268, 0, "anElement", "select", -1, 0, { 0, 0 } }, // elements puzzle, gets used before super TextIcon + { GID_KQ5, 0, 0, "", "export 29", -1, 3, { 0, 0 } }, // called when playing harp for the harpies, is used for kDoAudio + { GID_KQ5, 25, 0, "rm025", "doit", -1, 0, { 0, 0 } }, // inside witch forest, where the walking rock is + { GID_KQ6, 903, 0, "controlWin", "open", -1, 4, { 0, 0 } }, // when opening the controls window (save, load etc) + { GID_KQ6, 500, 0, "rm500", "init", -1, 0, { 0, 0 } }, // going to island of the beast + { GID_KQ6, 520, 0, "rm520", "init", -1, 0, { 0, 0 } }, // going to boiling water trap on beast isle + { GID_KQ6, 30, 0, "rats", "changeState", -1, 0, { 0, 0 } }, // rats in the catacombs + { GID_LSL6, 85, 0, "washcloth", "doVerb", -1, 0, { 0, 0 } }, // washcloth in inventory + { GID_SQ1, 703, 0, "", "export 1", -1, 0, { 0, 0 } }, // sub that's called from several objects while on sarien battle cruiser + { GID_SQ1, 703, 0, "firePulsar", "changeState", 0x18a, 0, { 0, 0 } }, // export 1, but called locally (when shooting at aliens) + { GID_SQ4, 928, 0, "Narrator", "startText", -1, 1000, { 0, 1 } }, // sq4cd: method returns this to the caller + { GID_SQ6, 0, 0, "SQ6", "init", -1, 2, { 0, 0 } }, // called when the game starts + { GID_SQ6, 64950, 0, "View", "handleEvent", -1, 0, { 0, 0 } }, // called when pressing "Start game" in the main menu + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, int index, reg_t default_value) { + if (validate_variable(r, stack_base, type, max, index)) { + if (r[index].segment == 0xffff) { + switch (type) { + case VAR_TEMP: { + // Uninitialized read on a temp + // We need to find correct replacements for each situation manually + SciTrackOriginReply originReply; + r[index] = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply); + if ((r[index].segment == 0xFFFF) && (r[index].offset == 0xFFFF)) + error("Uninitialized read for temp %d from method %s::%s (script %d, localCall %x)", index, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); + break; + } + case VAR_PARAM: + // Out-of-bounds read for a parameter that goes onto stack and hits an uninitialized temp + // We return 0 currently in that case + warning("Read for a parameter goes out-of-bounds, onto the stack and gets uninitialized temp"); + return NULL_REG; + default: + break; + } + } return r[index]; - else + } else return default_value; } -static void validate_write_var(reg_t *r, reg_t *stack_base, int type, int max, int index, int line, reg_t value, SegManager *segMan, Kernel *kernel) { - if (validate_variable(r, stack_base, type, max, index, line)) { +static void validate_write_var(reg_t *r, reg_t *stack_base, int type, int max, int index, reg_t value, SegManager *segMan, Kernel *kernel) { + if (validate_variable(r, stack_base, type, max, index)) { // WORKAROUND: This code is needed to work around a probable script bug, or a // limitation of the original SCI engine, which can be observed in LSL5. @@ -200,44 +413,34 @@ static void validate_write_var(reg_t *r, reg_t *stack_base, int type, int max, i // stopGroop object, which points to ego, to the new ego object. If this is not // done, ego's movement will not be updated properly, so the result is // unpredictable (for example in LSL5, Patti spins around instead of walking). - if (index == 0 && type == VAR_GLOBAL) { // global 0 is ego + if (index == 0 && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) { // global 0 is ego reg_t stopGroopPos = segMan->findObjectByName("stopGroop"); if (!stopGroopPos.isNull()) { // does the game have a stopGroop object? // Find the "client" member variable of the stopGroop object, and update it ObjVarRef varp; - if (lookupSelector(segMan, stopGroopPos, kernel->_selectorCache.client, &varp, NULL) == kSelectorVariable) { + if (lookupSelector(segMan, stopGroopPos, SELECTOR(client), &varp, NULL) == kSelectorVariable) { reg_t *clientVar = varp.getPointer(segMan); *clientVar = value; } } } + // If we are writing an uninitialized value into a temp, we remove the uninitialized segment + // 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; + r[index] = value; } } -#else -// Non-validating alternatives - -# define validate_stack_addr(s, sp) sp -# define validate_arithmetic(r) ((r).offset) -# define signed_validate_arithmetic(r) ((int16)(r).offset) -# define validate_variable(r, sb, t, m, i, l) -# define validate_read_var(r, sb, t, m, i, l, dv) ((r)[i]) -# define validate_write_var(r, sb, t, m, i, l, v, sm, k) ((r)[i] = (v)) -# define validate_property(o, p) ((o)->_variables[p]) - -#endif - -#define READ_VAR(type, index, def) validate_read_var(s->variables[type], s->stack_base, type, s->variables_max[type], index, __LINE__, def) -#define WRITE_VAR(type, index, value) validate_write_var(s->variables[type], s->stack_base, type, s->variables_max[type], index, __LINE__, value, s->_segMan, g_sci->getKernel()) +#define READ_VAR(type, index) validate_read_var(s->variables[type], s->stack_base, type, s->variablesMax[type], index, s->r_acc) +#define WRITE_VAR(type, index, value) validate_write_var(s->variables[type], s->stack_base, type, s->variablesMax[type], index, value, s->_segMan, g_sci->getKernel()) #define WRITE_VAR16(type, index, value) WRITE_VAR(type, index, make_reg(0, value)); #define ACC_ARITHMETIC_L(op) make_reg(0, (op validate_arithmetic(s->r_acc))) -#define ACC_AUX_LOAD() aux_acc = signed_validate_arithmetic(s->r_acc) -#define ACC_AUX_STORE() s->r_acc = make_reg(0, aux_acc) - -#define OBJ_PROPERTY(o, p) (validate_property(o, p)) // Operating on the stack // 16 bit: @@ -247,12 +450,32 @@ static void validate_write_var(reg_t *r, reg_t *stack_base, int type, int max, i #define PUSH32(a) (*(validate_stack_addr(s, (s->xs->sp)++)) = (a)) #define POP32() (*(validate_stack_addr(s, --(s->xs->sp)))) +bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) { + if (_debugState._activeBreakpointTypes & BREAK_EXPORT) { + uint32 bpaddress; + + bpaddress = (script << 16 | pubfunct); + + Common::List<Breakpoint>::const_iterator bp; + for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) { + if (bp->type == BREAK_EXPORT && bp->address == bpaddress) { + _console->DebugPrintf("Break on script %d, export %d\n", script, pubfunct); + _debugState.debugging = true; + _debugState.breakpointWasHit = true; + return true;; + } + } + } + + return false; +} + ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackPtr sp, reg_t calling_obj, uint16 argc, StackPtr argp) { int seg = s->_segMan->getScriptSegment(script); Script *scr = s->_segMan->getScriptIfLoaded(seg); if (!scr || scr->isMarkedAsDeleted()) { // Script not present yet? - seg = script_instantiate(g_sci->getResMan(), s->_segMan, script); + seg = s->_segMan->instantiateScript(script); scr = s->_segMan->getScript(seg); } @@ -270,24 +493,9 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP } // Check if a breakpoint is set on this method - if (g_debugState._activeBreakpointTypes & BREAK_EXPORT) { - uint32 bpaddress; - - bpaddress = (script << 16 | pubfunct); - - Common::List<Breakpoint>::const_iterator bp; - for (bp = g_debugState._breakpoints.begin(); bp != g_debugState._breakpoints.end(); ++bp) { - if (bp->type == BREAK_EXPORT && bp->address == bpaddress) { - Console *con = g_sci->getSciDebugger(); - con->DebugPrintf("Break on script %d, export %d\n", script, pubfunct); - g_debugState.debugging = true; - g_debugState.breakpointWasHit = true; - break; - } - } - } + g_sci->checkExportBreakpoint(script, pubfunct); - return add_exec_stack_entry(s->_executionStack, make_reg(seg, temp), sp, calling_obj, argc, argp, -1, calling_obj, s->_executionStack.size()-1, seg); + return add_exec_stack_entry(s->_executionStack, make_reg(seg, temp), sp, calling_obj, argc, argp, -1, pubfunct, -1, calling_obj, s->_executionStack.size()-1, seg); } @@ -297,7 +505,7 @@ static void _exec_varselectors(EngineState *s) { ExecStack &xs = s->_executionStack.back(); reg_t *var = xs.getVarPointer(s->_segMan); if (!var) { - warning("Invalid varselector exec stack entry"); + error("Invalid varselector exec stack entry"); } else { // varselector access? if (xs.argc) { // write? @@ -325,6 +533,30 @@ struct CallsStruct { int type; /**< Same as ExecStack.type */ }; +bool SciEngine::checkSelectorBreakpoint(reg_t send_obj, int selector) { + if (_debugState._activeBreakpointTypes & BREAK_SELECTOR) { + char method_name[256]; + + sprintf(method_name, "%s::%s", _gamestate->_segMan->getObjectName(send_obj), getKernel()->getSelectorName(selector).c_str()); + + Common::List<Breakpoint>::const_iterator bp; + for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) { + int cmplen = bp->name.size(); + if (bp->name.lastChar() != ':') + cmplen = 256; + + if (bp->type == BREAK_SELECTOR && !strncmp(bp->name.c_str(), method_name, cmplen)) { + _console->DebugPrintf("Break on %s (in [%04x:%04x])\n", method_name, PRINT_REG(send_obj)); + _debugState.debugging = true; + _debugState.breakpointWasHit = true; + return true; + } + } + } + + return false; +} + ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPtr sp, int framesize, StackPtr argp) { // send_obj and work_obj are equal for anything but 'super' // Returns a pointer to the TOS exec_stack element @@ -349,27 +581,7 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt } // Check if a breakpoint is set on this method - if (g_debugState._activeBreakpointTypes & BREAK_SELECTOR) { - char method_name[256]; - - sprintf(method_name, "%s::%s", s->_segMan->getObjectName(send_obj), g_sci->getKernel()->getSelectorName(selector).c_str()); - - Common::List<Breakpoint>::const_iterator bp; - for (bp = g_debugState._breakpoints.begin(); bp != g_debugState._breakpoints.end(); ++bp) { - int cmplen = bp->name.size(); - if (bp->name.lastChar() != ':') - cmplen = 256; - - if (bp->type == BREAK_SELECTOR && !strncmp(bp->name.c_str(), method_name, cmplen)) { - Console *con = g_sci->getSciDebugger(); - con->DebugPrintf("Break on %s (in [%04x:%04x])\n", method_name, PRINT_REG(send_obj)); - printSendActions = true; - g_debugState.debugging = true; - g_debugState.breakpointWasHit = true; - break; - } - } - } + printSendActions = g_sci->checkSelectorBreakpoint(send_obj, selector); #ifdef VM_DEBUG_SEND printf("Send to %04x:%04x, selector %04x (%s):", PRINT_REG(send_obj), selector, g_sci->getKernel()->getSelectorName(selector).c_str()); @@ -405,19 +617,30 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt } if (argc > 1) { - // argc can indeed be bigger than 1 in some cases, and it seems correct - // (i.e. we should skip that many bytes later on)... question is, why - // does this occur? Could such calls be used to point to data after X - // bytes in the heap? What are the skipped bytes in this case? - // In SQ4CD, this occurs with the returnVal selector of object - // Sq4GlobalNarrator when the game starts, and right after the narrator - // is heard (e.g. after he talks when examining something) - reg_t oldReg = *varp.getPointer(s->_segMan); - reg_t newReg = argp[1]; - warning("send_selector(): argc = %d while modifying variable selector " - "%x (%s) of object %04x:%04x (%s) from %04x:%04x to %04x:%04x", - argc, selector, g_sci->getKernel()->getSelectorName(selector).c_str(), PRINT_REG(send_obj), - s->_segMan->getObjectName(send_obj), PRINT_REG(oldReg), PRINT_REG(newReg)); + // argc can indeed be bigger than 1 in some cases, and it's usually the + // result of a script bug + + const char *objectName = s->_segMan->getObjectName(send_obj); + + if (!strcmp(objectName, "Sq4GlobalNarrator") && selector == 606) { + // SQ4 has a script bug in the Sq4GlobalNarrator object when invoking the + // returnVal selector, which doesn't affect gameplay, thus don't diplay it + } else if (!strcmp(objectName, "longSong") && selector == 3 && g_sci->getGameId() == GID_QFG1) { + // QFG1VGA has a script bug in the longSong object when invoking the + // loop selector, which doesn't affect gameplay, thus don't diplay it + } else if (!strcmp(objectName, "PuzPiece") && selector == 77 && g_sci->getGameId() == GID_CASTLEBRAIN) { + // Castle of Dr. Brain has a script bug in the PuzPiece object when invoking + // the value selector, which doesn't affect gameplay, thus don't display it + } else { + // Unknown script bug, show it. Usually these aren't fatal. + reg_t oldReg = *varp.getPointer(s->_segMan); + reg_t newReg = argp[1]; + const char *selectorName = g_sci->getKernel()->getSelectorName(selector).c_str(); + warning("send_selector(): argc = %d while modifying variable selector " + "%x (%s) of object %04x:%04x (%s) from %04x:%04x to %04x:%04x", + argc, selector, selectorName, PRINT_REG(send_obj), + objectName, PRINT_REG(oldReg), PRINT_REG(newReg)); + } } { @@ -444,7 +667,28 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt printf(") at %04x:%04x\n", PRINT_REG(funcp)); #endif // VM_DEBUG_SEND if (printSendActions) { - debug("[invoke selector]\n"); + printf("[invoke selector]"); +#ifndef VM_DEBUG_SEND + int displaySize = 0; + for (int argNr = 1; argNr <= argc; argNr++) { + if (argNr == 1) + printf(" - "); + reg_t curParam = argp[argNr]; + if (curParam.segment) { + printf("[%04x:%04x] ", PRINT_REG(curParam)); + displaySize += 12; + } else { + printf("[%04x] ", curParam.offset); + displaySize += 7; + } + if (displaySize > 50) { + if (argNr < argc) + printf("..."); + break; + } + } +#endif + printf("\n"); printSendActions = false; } @@ -477,7 +721,7 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt else add_exec_stack_entry(s->_executionStack, call.address.func, call.sp, work_obj, call.argc, call.argp, - call.selector, send_obj, origin, SCI_XS_CALLEE_LOCALS); + call.selector, -1, -1, send_obj, origin, SCI_XS_CALLEE_LOCALS); } _exec_varselectors(s); @@ -486,7 +730,7 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt } static ExecStack *add_exec_stack_varselector(Common::List<ExecStack> &execStack, reg_t objp, int argc, StackPtr argp, Selector selector, const ObjVarRef& address, int origin) { - ExecStack *xstack = add_exec_stack_entry(execStack, NULL_REG, 0, objp, argc, argp, selector, objp, origin, SCI_XS_CALLEE_LOCALS); + ExecStack *xstack = add_exec_stack_entry(execStack, NULL_REG, 0, objp, argc, argp, selector, -1, -1, objp, origin, SCI_XS_CALLEE_LOCALS); // Store selector address in sp xstack->addr.varp = address; @@ -496,7 +740,7 @@ static ExecStack *add_exec_stack_varselector(Common::List<ExecStack> &execStack, } static ExecStack *add_exec_stack_entry(Common::List<ExecStack> &execStack, reg_t pc, StackPtr sp, reg_t objp, int argc, - StackPtr argp, Selector selector, reg_t sendp, int origin, SegmentId _localsSegment) { + StackPtr argp, Selector selector, int exportId, int localCallOffset, reg_t sendp, int origin, SegmentId _localsSegment) { // Returns new TOS element for the execution stack // _localsSegment may be -1 if derived from the called object @@ -520,8 +764,10 @@ static ExecStack *add_exec_stack_entry(Common::List<ExecStack> &execStack, reg_t *argp = make_reg(0, argc); // SCI code relies on the zeroeth argument to equal argc // Additional debug information - xstack.selector = selector; - xstack.origin = origin; + xstack.debugSelector = selector; + xstack.debugExportId = exportId; + xstack.debugLocalCallOffset = localCallOffset; + xstack.debugOrigin = origin; xstack.type = EXEC_STACK_TYPE_CALL; // Normal call @@ -529,10 +775,6 @@ static ExecStack *add_exec_stack_entry(Common::List<ExecStack> &execStack, reg_t return &(execStack.back()); } -#ifdef DISABLE_VALIDATIONS -# define kernel_matches_signature(a, b, c, d) 1 -#endif - static reg_t pointer_add(EngineState *s, reg_t base, int offset) { SegmentObj *mobj = s->_segMan->getSegmentObj(base.segment); @@ -560,84 +802,114 @@ static reg_t pointer_add(EngineState *s, reg_t base, int offset) { } } -static void callKernelFunc(EngineState *s, int kernelFuncNum, int argc) { - - if (kernelFuncNum >= (int)g_sci->getKernel()->_kernelFuncs.size()) - error("Invalid kernel function 0x%x requested", kernelFuncNum); +static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, reg_t *argv) { + // 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; + xstack = add_exec_stack_entry(s->_executionStack, NULL_REG, NULL, NULL_REG, argc, argv - 1, 0, -1, -1, NULL_REG, + s->_executionStack.size()-1, SCI_XS_CALLEE_LOCALS); + xstack->debugSelector = kernelCallNr; + xstack->type = EXEC_STACK_TYPE_KERNEL; +} - const KernelFuncWithSignature &kernelFunc = g_sci->getKernel()->_kernelFuncs[kernelFuncNum]; +static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { + Kernel *kernel = g_sci->getKernel(); - if (kernelFunc.signature - && !g_sci->getKernel()->signatureMatch(kernelFunc.signature, argc, s->xs->sp + 1)) { - error("[VM] Invalid arguments to kernel call %x", kernelFuncNum); - } + if (kernelCallNr >= (int)kernel->_kernelFuncs.size()) + error("Invalid kernel function 0x%x requested", kernelCallNr); + const KernelFunction &kernelCall = kernel->_kernelFuncs[kernelCallNr]; reg_t *argv = s->xs->sp + 1; - if (!kernelFunc.isDummy) { - // 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; - xstack = add_exec_stack_entry(s->_executionStack, NULL_REG, NULL, NULL_REG, argc, argv - 1, 0, NULL_REG, - s->_executionStack.size()-1, SCI_XS_CALLEE_LOCALS); - xstack->selector = kernelFuncNum; - xstack->type = EXEC_STACK_TYPE_KERNEL; - - //warning("callk %s", kernelFunc.orig_name.c_str()); - - // TODO: SCI2.1 equivalent - if (s->loadFromLauncher >= 0 && ( - (kernelFuncNum == 0x8 && getSciVersion() <= SCI_VERSION_1_1) || // DrawPic - (kernelFuncNum == 0x3d && getSciVersion() == SCI_VERSION_2) // GetSaveDir - //(kernelFuncNum == 0x28 && getSciVersion() == SCI_VERSION_2_1) // AddPlane - )) { - - // A game is being loaded from the launcher, and the game is about to draw something on - // screen, hence all initialization has taken place (i.e. menus have been constructed etc). - // Therefore, inject a kRestoreGame call here, instead of the requested function. - // The restore call is injected here mainly for games which have a menu, as the menu is - // constructed when the game starts and is not reconstructed when a saved game is loaded. - int saveSlot = s->loadFromLauncher; - s->loadFromLauncher = -1; // invalidate slot, so that we don't load again - - if (saveSlot < 0) - error("Requested to load invalid save slot"); // should never happen, really - - reg_t restoreArgv[2] = { NULL_REG, make_reg(0, saveSlot) }; // special call (argv[0] is NULL) - kRestoreGame(s, 2, restoreArgv); - } else { - // Call kernel function - s->r_acc = kernelFunc.fun(s, argc, argv); + if (kernelCall.signature + && !kernel->signatureMatch(kernelCall.signature, argc, argv)) { + // signature mismatch, check if a workaround is available + SciTrackOriginReply originReply; + reg_t workaround; + workaround = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply); + if ((workaround.segment == 0xFFFF) && (workaround.offset == 0xFFFF)) { + kernel->signatureDebug(kernelCall.signature, argc, argv); + error("[VM] k%s[%x]: signature mismatch via method %s::%s (script %d, localCall %x)", kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); } + // FIXME: implement some real workaround type logic - ignore call, still do call etc. + if (workaround.segment == 2) + s->r_acc = make_reg(0, workaround.offset); + if (workaround.segment) + return; + } - // Remove callk stack frame again - s->_executionStack.pop_back(); + + // Call kernel function + if (!kernelCall.subFunctionCount) { + addKernelCallToExecStack(s, kernelCallNr, argc, argv); + s->r_acc = kernelCall.function(s, argc, argv); } else { - Common::String warningMsg = "Dummy function " + kernelFunc.orig_name + - Common::String::printf("[0x%x]", kernelFuncNum) + - " invoked - ignoring. Params: " + - Common::String::printf("%d", argc) + " ("; + // Sub-functions available, check signature and call that one directly + if (argc < 1) + error("[VM] k%s[%x]: no subfunction-id parameter given", kernelCall.name, kernelCallNr); + if (argv[0].segment) + error("[VM] k%s[%x]: given subfunction-id is actually a pointer", kernelCall.name, kernelCallNr); + const uint16 subId = argv[0].toUint16(); + // Skip over subfunction-id + argc--; + argv++; + if (subId >= kernelCall.subFunctionCount) + error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); + const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId]; + if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) { + // Signature mismatch + SciTrackOriginReply originReply; + reg_t workaround; + workaround = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply); + if ((workaround.segment == 0xFFFF) && (workaround.offset == 0xFFFF)) { + kernel->signatureDebug(kernelSubCall.signature, argc, argv); + int callNameLen = strlen(kernelCall.name); + if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) { + const char *subCallName = kernelSubCall.name + callNameLen; + error("[VM] k%s(%s): signature mismatch via method %s::%s (script %d, localCall %x)", kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); + } + error("[VM] k%s: signature mismatch via method %s::%s (script %d, localCall %x)", kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); + } + // FIXME: implement some real workaround type logic - ignore call, still do call etc. + if (workaround.segment == 2) + s->r_acc = make_reg(0, workaround.offset); + if (workaround.segment) + return; + } + if (!kernelSubCall.function) + error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); + addKernelCallToExecStack(s, kernelCallNr, argc, argv); + s->r_acc = kernelSubCall.function(s, argc, argv); + } + +#if 0 + // Used for debugging + Common::String debugMsg = Common::String::printf("%s [0x%x]", kernelCall.name, kernelCallNr) + + Common::String::printf(", %d params: ", argc) + + " ("; for (int i = 0; i < argc; i++) { - warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); - warningMsg += (i == argc - 1 ? ")" : ", "); + debugMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); + debugMsg += (i == argc - 1 ? ")" : ", "); } - warning("%s", warningMsg.c_str()); - } + debugMsg += ", result: " + Common::String::printf("%04x:%04x", PRINT_REG(s->r_acc)); + debug("%s", debugMsg.c_str()); +#endif + + // Remove callk stack frame again, if there's still an execution stack + if (s->_executionStack.begin() != s->_executionStack.end()) + s->_executionStack.pop_back(); } -static void gc_countdown(EngineState *s) { - if (s->gc_countdown-- <= 0) { - s->gc_countdown = s->script_gc_interval; +static void gcCountDown(EngineState *s) { + if (s->gcCountDown-- <= 0) { + s->gcCountDown = s->scriptGCInterval; run_gc(s); } } -static const byte _fake_return_buffer[2] = {op_ret << 1, op_ret << 1}; - - int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]) { uint offset = 0; extOpcode = src[offset++]; // Get "extended" opcode (lower bit has special meaning) @@ -709,50 +981,29 @@ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]) void run_vm(EngineState *s, bool restoring) { assert(s); -#ifndef DISABLE_VALIDATIONS - unsigned int code_buf_size = 0 ; // (Avoid spurious warning) -#endif int temp; - int16 aux_acc; // Auxiliary 16 bit accumulator reg_t r_temp; // Temporary register StackPtr s_temp; // Temporary stack pointer int16 opparams[4]; // opcode parameters - s->restAdjustCur = s->restAdjust; - // &rest adjusts the parameter count by this value + s->restAdjust = 0; // &rest adjusts the parameter count by this value // Current execution data: s->xs = &(s->_executionStack.back()); ExecStack *xs_new = NULL; Object *obj = s->_segMan->getObject(s->xs->objp); + Script *scr = 0; Script *local_script = s->_segMan->getScriptIfLoaded(s->xs->local_segment); - int old_execution_stack_base = s->execution_stack_base; + int old_executionStackBase = s->executionStackBase; // Used to detect the stack bottom, for "physical" returns - const byte *code_buf = NULL; // (Avoid spurious warning) - if (!local_script) { + if (!local_script) error("run_vm(): program counter gone astray (local_script pointer is null)"); - } if (!restoring) - s->execution_stack_base = s->_executionStack.size() - 1; - -#ifndef DISABLE_VALIDATIONS - // Initialize maximum variable count - if (s->script_000->_localsBlock) - s->variables_max[VAR_GLOBAL] = s->script_000->_localsBlock->_locals.size(); - else - s->variables_max[VAR_GLOBAL] = 0; -#endif - - s->variables_seg[VAR_GLOBAL] = s->script_000->_localsSegment; - s->variables_seg[VAR_TEMP] = s->variables_seg[VAR_PARAM] = s->_segMan->findSegmentByType(SEG_TYPE_STACK); - s->variables_base[VAR_TEMP] = s->variables_base[VAR_PARAM] = s->stack_base; + s->executionStackBase = s->_executionStack.size() - 1; - // SCI code reads the zeroth argument to determine argc - if (s->script_000->_localsBlock) - s->variables_base[VAR_GLOBAL] = s->variables[VAR_GLOBAL] = s->script_000->_localsBlock->_locals.begin(); - else - s->variables_base[VAR_GLOBAL] = s->variables[VAR_GLOBAL] = NULL; + s->variablesSegment[VAR_TEMP] = s->variablesSegment[VAR_PARAM] = s->_segMan->findSegmentByType(SEG_TYPE_STACK); + s->variablesBase[VAR_TEMP] = s->variablesBase[VAR_PARAM] = s->stack_base; s->_executionStackPosChanged = true; // Force initialization @@ -760,101 +1011,90 @@ void run_vm(EngineState *s, bool restoring) { int var_type; // See description below int var_number; - g_debugState.old_pc_offset = s->xs->addr.pc.offset; - g_debugState.old_sp = s->xs->sp; + g_sci->_debugState.old_pc_offset = s->xs->addr.pc.offset; + g_sci->_debugState.old_sp = s->xs->sp; + + if (s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit()) + return; // Stop processing if (s->_executionStackPosChanged) { - Script *scr; + scr = s->_segMan->getScriptIfLoaded(s->xs->addr.pc.segment); + if (!scr) + error("No script in segment %d", s->xs->addr.pc.segment); s->xs = &(s->_executionStack.back()); s->_executionStackPosChanged = false; - scr = s->_segMan->getScriptIfLoaded(s->xs->addr.pc.segment); - if (!scr) { - // No script? Implicit return via fake instruction buffer - warning("Running on non-existant script in segment %x", s->xs->addr.pc.segment); - code_buf = _fake_return_buffer; -#ifndef DISABLE_VALIDATIONS - code_buf_size = 2; -#endif - s->xs->addr.pc.offset = 1; - - scr = NULL; - obj = NULL; + obj = s->_segMan->getObject(s->xs->objp); + local_script = s->_segMan->getScriptIfLoaded(s->xs->local_segment); + if (!local_script) { + // FIXME: Why does this happen? Is the script not loaded yet at this point? + warning("Could not find local script from segment %x", s->xs->local_segment); + local_script = NULL; + s->variablesBase[VAR_LOCAL] = s->variables[VAR_LOCAL] = NULL; + s->variablesMax[VAR_LOCAL] = 0; } else { - obj = s->_segMan->getObject(s->xs->objp); - code_buf = scr->_buf; -#ifndef DISABLE_VALIDATIONS - code_buf_size = scr->getBufSize(); -#endif - local_script = s->_segMan->getScriptIfLoaded(s->xs->local_segment); - if (!local_script) { - warning("Could not find local script from segment %x", s->xs->local_segment); - local_script = NULL; - s->variables_base[VAR_LOCAL] = s->variables[VAR_LOCAL] = NULL; -#ifndef DISABLE_VALIDATIONS - s->variables_max[VAR_LOCAL] = 0; -#endif - } else { - - s->variables_seg[VAR_LOCAL] = local_script->_localsSegment; - if (local_script->_localsBlock) - s->variables_base[VAR_LOCAL] = s->variables[VAR_LOCAL] = local_script->_localsBlock->_locals.begin(); - else - s->variables_base[VAR_LOCAL] = s->variables[VAR_LOCAL] = NULL; -#ifndef DISABLE_VALIDATIONS - if (local_script->_localsBlock) - s->variables_max[VAR_LOCAL] = local_script->_localsBlock->_locals.size(); - else - s->variables_max[VAR_LOCAL] = 0; - s->variables_max[VAR_TEMP] = s->xs->sp - s->xs->fp; - s->variables_max[VAR_PARAM] = s->xs->argc + 1; -#endif - } - s->variables[VAR_TEMP] = s->xs->fp; - s->variables[VAR_PARAM] = s->xs->variables_argp; + s->variablesSegment[VAR_LOCAL] = local_script->_localsSegment; + if (local_script->_localsBlock) + s->variablesBase[VAR_LOCAL] = s->variables[VAR_LOCAL] = local_script->_localsBlock->_locals.begin(); + else + s->variablesBase[VAR_LOCAL] = s->variables[VAR_LOCAL] = NULL; + if (local_script->_localsBlock) + s->variablesMax[VAR_LOCAL] = local_script->_localsBlock->_locals.size(); + else + s->variablesMax[VAR_LOCAL] = 0; + s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp; + s->variablesMax[VAR_PARAM] = s->xs->argc + 1; } - + s->variables[VAR_TEMP] = s->xs->fp; + s->variables[VAR_PARAM] = s->xs->variables_argp; } - if (s->script_abort_flag || g_engine->shouldQuit()) - return; // Emergency + if (s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit()) + return; // Stop processing // Debug if this has been requested: // TODO: re-implement sci_debug_flags - if (g_debugState.debugging /* sci_debug_flags*/) { - script_debug(s); - g_debugState.breakpointWasHit = false; + if (g_sci->_debugState.debugging /* sci_debug_flags*/) { + g_sci->scriptDebug(); + g_sci->_debugState.breakpointWasHit = false; } Console *con = g_sci->getSciDebugger(); if (con->isAttached()) { con->onFrame(); } -#ifndef DISABLE_VALIDATIONS if (s->xs->sp < s->xs->fp) - error("run_vm(): stack underflow, sp: %04x:%04x, fp: %04x:%04x", + error("run_vm(): stack underflow, sp: %04x:%04x, fp: %04x:%04x", PRINT_REG(*s->xs->sp), PRINT_REG(*s->xs->fp)); - s->variables_max[VAR_TEMP] = s->xs->sp - s->xs->fp; + s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp; - if (s->xs->addr.pc.offset >= code_buf_size) - error("run_vm(): program counter gone astray, addr: %d, code buffer size: %d", - s->xs->addr.pc.offset, code_buf_size); -#endif + if (s->xs->addr.pc.offset >= scr->getBufSize()) + error("run_vm(): program counter gone astray, addr: %d, code buffer size: %d", + s->xs->addr.pc.offset, scr->getBufSize()); // Get opcode byte extOpcode; - s->xs->addr.pc.offset += readPMachineInstruction(code_buf + s->xs->addr.pc.offset, extOpcode, opparams); + s->xs->addr.pc.offset += readPMachineInstruction(scr->getBuf() + s->xs->addr.pc.offset, extOpcode, opparams); const byte opcode = extOpcode >> 1; switch (opcode) { case op_bnot: // 0x00 (00) + // Binary not s->r_acc = ACC_ARITHMETIC_L(0xffff ^ /*acc*/); break; case op_add: // 0x01 (01) r_temp = POP32(); + + // Happens in SQ1, room 28, when throwing the water at Orat + if (s->r_acc.segment == 0xFFFF) { + // WORKAROUND: init uninitialized variable to 0 + warning("op_add: attempt to write to uninitialized variable"); + s->r_acc = NULL_REG; + } + if (r_temp.segment || s->r_acc.segment) { reg_t r_ptr = NULL_REG; int offset; @@ -912,23 +1152,23 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc = ACC_ARITHMETIC_L(((int16)POP()) * (int16)/*acc*/); break; - case op_div: // 0x04 (04) - ACC_AUX_LOAD(); - aux_acc = aux_acc != 0 ? ((int16)POP()) / aux_acc : 0; - ACC_AUX_STORE(); + case op_div: { // 0x04 (04) + int16 divisor = signed_validate_arithmetic(s->r_acc); + s->r_acc = make_reg(0, (divisor != 0 ? ((int16)POP()) / divisor : 0)); break; - - case op_mod: // 0x05 (05) - ACC_AUX_LOAD(); - aux_acc = aux_acc != 0 ? ((int16)POP()) % aux_acc : 0; - ACC_AUX_STORE(); + } + case op_mod: { // 0x05 (05) + int16 modulo = signed_validate_arithmetic(s->r_acc); + s->r_acc = make_reg(0, (modulo != 0 ? ((int16)POP()) % modulo : 0)); break; - + } case op_shr: // 0x06 (06) + // Shift right logical s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) >> /*acc*/); break; case op_shl: // 0x07 (07) + // Shift left logical s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) << /*acc*/); break; @@ -954,6 +1194,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_eq_: // 0x0d (13) + // == s->r_prev = s->r_acc; r_temp = POP32(); s->r_acc = make_reg(0, r_temp == s->r_acc); @@ -961,6 +1202,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_ne_: // 0x0e (14) + // != s->r_prev = s->r_acc; r_temp = POP32(); s->r_acc = make_reg(0, r_temp != s->r_acc); @@ -968,6 +1210,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_gt_: // 0x0f (15) + // > s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { @@ -975,11 +1218,18 @@ void run_vm(EngineState *s, bool restoring) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); + } else if (r_temp.segment && !s->r_acc.segment) { + if (s->r_acc.offset >= 1000) + error("[VM] op_gt: comparsion between a pointer and number"); + // Pseudo-WORKAROUND: sierra allows any pointer <-> value comparsion + // Happens in SQ1, room 28, when throwing the water at Orat + s->r_acc = make_reg(0, 1); } else s->r_acc = ACC_ARITHMETIC_L(signed_validate_arithmetic(r_temp) > (int16)/*acc*/); break; case op_ge_: // 0x10 (16) + // >= s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { @@ -991,17 +1241,25 @@ void run_vm(EngineState *s, bool restoring) { break; case op_lt_: // 0x11 (17) + // < s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); + } else if (r_temp.segment && !s->r_acc.segment) { + if (s->r_acc.offset >= 1000) + error("[VM] op_lt: comparsion between a pointer and number"); + // Pseudo-WORKAROUND: sierra allows any pointer <-> value comparsion + // Happens in SQ1, room 58, when giving id-card to robot + s->r_acc = make_reg(0, 1); } else s->r_acc = ACC_ARITHMETIC_L(signed_validate_arithmetic(r_temp) < (int16)/*acc*/); break; case op_le_: // 0x12 (18) + // <= s->r_prev = s->r_acc; r_temp = POP32(); if (r_temp.segment && s->r_acc.segment) { @@ -1013,6 +1271,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_ugt_: // 0x13 (19) + // > (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); @@ -1024,8 +1283,8 @@ void run_vm(EngineState *s, bool restoring) { // (Print "foo") // Pointer to a string // (Print 420 5) // Reference to the fifth message in text resource 420 - // It works because in those games, the maximum resource number is 999, - // so any parameter value above that threshold must be a pointer. + // It works because in those games, the maximum resource number is 999, + // so any parameter value above that threshold must be a pointer. if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) s->r_acc = make_reg(0, 1); else if (r_temp.segment && s->r_acc.segment) @@ -1035,6 +1294,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_uge_: // 0x14 (20) + // >= (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); @@ -1048,6 +1308,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_ult_: // 0x15 (21) + // < (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); @@ -1061,6 +1322,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_ule_: // 0x16 (22) + // <= (unsigned) s->r_prev = s->r_acc; r_temp = POP32(); @@ -1074,11 +1336,13 @@ void run_vm(EngineState *s, bool restoring) { break; 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]; 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]; break; @@ -1088,45 +1352,56 @@ void run_vm(EngineState *s, bool restoring) { break; case op_ldi: // 0x1a (26) + // Load data immediate s->r_acc = make_reg(0, opparams[0]); break; case op_push: // 0x1b (27) + // Push to stack PUSH32(s->r_acc); break; case op_pushi: // 0x1c (28) + // Push immediate PUSH(opparams[0]); break; case op_toss: // 0x1d (29) + // TOS (Top Of Stack) subtract s->xs->sp--; break; case op_dup: // 0x1e (30) + // Duplicate TOD (Top Of Stack) element r_temp = s->xs->sp[-1]; PUSH32(r_temp); break; case op_link: // 0x1f (31) + // We shouldn't initialize temp variables at all + // We put special segment 0xFFFF in there, so that uninitialized reads can get detected for (int i = 0; i < opparams[0]; i++) - s->xs->sp[i] = NULL_REG; + s->xs->sp[i] = make_reg(0xffff, 0); + s->xs->sp += opparams[0]; break; case op_call: { // 0x20 (32) + // Call a script subroutine int argc = (opparams[1] >> 1) // Given as offset, but we need count - + 1 + s->restAdjustCur; + + 1 + s->restAdjust; StackPtr call_base = s->xs->sp - argc; - s->xs->sp[1].offset += s->restAdjustCur; + s->xs->sp[1].offset += s->restAdjust; + + uint16 localCallOffset = s->xs->addr.pc.offset + opparams[0]; xs_new = add_exec_stack_entry(s->_executionStack, make_reg(s->xs->addr.pc.segment, - s->xs->addr.pc.offset + opparams[0]), + localCallOffset), s->xs->sp, s->xs->objp, - (validate_arithmetic(*call_base)) + s->restAdjustCur, - call_base, NULL_SELECTOR, s->xs->objp, + (validate_arithmetic(*call_base)) + s->restAdjust, + call_base, NULL_SELECTOR, -1, localCallOffset, s->xs->objp, s->_executionStack.size()-1, s->xs->local_segment); - s->restAdjustCur = 0; // Used up the &rest adjustment + s->restAdjust = 0; // Used up the &rest adjustment s->xs->sp = call_base; s->_executionStackPosChanged = true; @@ -1134,74 +1409,80 @@ void run_vm(EngineState *s, bool restoring) { } case op_callk: { // 0x21 (33) - gc_countdown(s); + // Call kernel function + gcCountDown(s); s->xs->sp -= (opparams[1] >> 1) + 1; bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - if (!oldScriptHeader) { - s->xs->sp -= s->restAdjustCur; - s->restAdjust = 0; // We just used up the s->restAdjustCur, remember? - } + if (!oldScriptHeader) + s->xs->sp -= s->restAdjust; int argc = validate_arithmetic(s->xs->sp[0]); if (!oldScriptHeader) - argc += s->restAdjustCur; + argc += s->restAdjust; callKernelFunc(s, opparams[0], argc); if (!oldScriptHeader) - s->restAdjustCur = s->restAdjust; + s->restAdjust = 0; // Calculate xs again: The kernel function might // have spawned a new VM xs_new = &(s->_executionStack.back()); s->_executionStackPosChanged = true; + + // If a game is being loaded, stop processing + if (s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit()) + return; // Stop processing + break; } case op_callb: // 0x22 (34) - temp = ((opparams[1] >> 1) + s->restAdjustCur + 1); + // Call base script + temp = ((opparams[1] >> 1) + s->restAdjust + 1); s_temp = s->xs->sp; s->xs->sp -= temp; - s->xs->sp[0].offset += s->restAdjustCur; + s->xs->sp[0].offset += s->restAdjust; xs_new = execute_method(s, 0, opparams[0], s_temp, s->xs->objp, s->xs->sp[0].offset, s->xs->sp); - s->restAdjustCur = 0; // Used up the &rest adjustment + s->restAdjust = 0; // Used up the &rest adjustment if (xs_new) // in case of error, keep old stack s->_executionStackPosChanged = true; break; case op_calle: // 0x23 (35) - temp = ((opparams[2] >> 1) + s->restAdjustCur + 1); + // Call external script + temp = ((opparams[2] >> 1) + s->restAdjust + 1); s_temp = s->xs->sp; s->xs->sp -= temp; - s->xs->sp[0].offset += s->restAdjustCur; + s->xs->sp[0].offset += s->restAdjust; xs_new = execute_method(s, opparams[0], opparams[1], s_temp, s->xs->objp, s->xs->sp[0].offset, s->xs->sp); - s->restAdjustCur = 0; // Used up the &rest adjustment + s->restAdjust = 0; // Used up the &rest adjustment if (xs_new) // in case of error, keep old stack s->_executionStackPosChanged = true; break; case op_ret: // 0x24 (36) + // Return from an execution loop started by call, calle, callb, send, self or super do { StackPtr old_sp2 = s->xs->sp; StackPtr old_fp = s->xs->fp; ExecStack *old_xs = &(s->_executionStack.back()); - if ((int)s->_executionStack.size() - 1 == s->execution_stack_base) { // Have we reached the base? - s->execution_stack_base = old_execution_stack_base; // Restore stack base + if ((int)s->_executionStack.size() - 1 == s->executionStackBase) { // Have we reached the base? + s->executionStackBase = old_executionStackBase; // Restore stack base s->_executionStack.pop_back(); s->_executionStackPosChanged = true; - s->restAdjust = s->restAdjustCur; // Update &rest return; // "Hard" return } @@ -1233,17 +1514,18 @@ void run_vm(EngineState *s, bool restoring) { break; case op_send: // 0x25 (37) + // Send for one or more selectors s_temp = s->xs->sp; - s->xs->sp -= ((opparams[0] >> 1) + s->restAdjustCur); // Adjust stack + s->xs->sp -= ((opparams[0] >> 1) + s->restAdjust); // Adjust stack - s->xs->sp[1].offset += s->restAdjustCur; + s->xs->sp[1].offset += s->restAdjust; xs_new = send_selector(s, s->r_acc, s->r_acc, s_temp, - (int)(opparams[0] >> 1) + (uint16)s->restAdjustCur, s->xs->sp); + (int)(opparams[0] >> 1) + (uint16)s->restAdjust, s->xs->sp); if (xs_new && xs_new != s->xs) s->_executionStackPosChanged = true; - s->restAdjustCur = 0; + s->restAdjust = 0; break; @@ -1253,6 +1535,7 @@ void run_vm(EngineState *s, bool restoring) { break; case op_class: // 0x28 (40) + // Get class address s->r_acc = s->_segMan->getClassAddress((unsigned)opparams[0], SCRIPT_GET_LOCK, s->xs->addr.pc); break; @@ -1262,45 +1545,48 @@ void run_vm(EngineState *s, bool restoring) { break; case op_self: // 0x2a (42) + // Send to self s_temp = s->xs->sp; - s->xs->sp -= ((opparams[0] >> 1) + s->restAdjustCur); // Adjust stack + s->xs->sp -= ((opparams[0] >> 1) + s->restAdjust); // Adjust stack - s->xs->sp[1].offset += s->restAdjustCur; + s->xs->sp[1].offset += s->restAdjust; xs_new = send_selector(s, s->xs->objp, s->xs->objp, - s_temp, (int)(opparams[0] >> 1) + (uint16)s->restAdjustCur, + s_temp, (int)(opparams[0] >> 1) + (uint16)s->restAdjust, s->xs->sp); if (xs_new && xs_new != s->xs) s->_executionStackPosChanged = true; - s->restAdjustCur = 0; + s->restAdjust = 0; break; case op_super: // 0x2b (43) + // Send to any class r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc); if (!r_temp.segment) error("[VM]: Invalid superclass in object"); else { s_temp = s->xs->sp; - s->xs->sp -= ((opparams[1] >> 1) + s->restAdjustCur); // Adjust stack + s->xs->sp -= ((opparams[1] >> 1) + s->restAdjust); // Adjust stack - s->xs->sp[1].offset += s->restAdjustCur; + s->xs->sp[1].offset += s->restAdjust; xs_new = send_selector(s, r_temp, s->xs->objp, s_temp, - (int)(opparams[1] >> 1) + (uint16)s->restAdjustCur, + (int)(opparams[1] >> 1) + (uint16)s->restAdjust, s->xs->sp); if (xs_new && xs_new != s->xs) s->_executionStackPosChanged = true; - s->restAdjustCur = 0; + s->restAdjust = 0; } break; case op_rest: // 0x2c (44) + // Pushes all or part of the parameter variable list on the stack temp = (uint16) opparams[0]; // First argument - s->restAdjustCur = MAX<int16>(s->xs->argc - temp + 1, 0); // +1 because temp counts the paramcount while argc doesn't + s->restAdjust = MAX<int16>(s->xs->argc - temp + 1, 0); // +1 because temp counts the paramcount while argc doesn't for (; temp <= s->xs->argc; temp++) PUSH32(s->xs->variables_argp[temp]); @@ -1308,12 +1594,13 @@ void run_vm(EngineState *s, bool restoring) { break; case op_lea: // 0x2d (45) + // Load Effective Address temp = (uint16) opparams[0] >> 1; var_number = temp & 0x03; // Get variable type // Get variable block offset - r_temp.segment = s->variables_seg[var_number]; - r_temp.offset = s->variables[var_number] - s->variables_base[var_number]; + r_temp.segment = s->variablesSegment[var_number]; + r_temp.offset = s->variables[var_number] - s->variablesBase[var_number]; if (temp & 0x08) // Add accumulator offset if requested r_temp.offset += signed_validate_arithmetic(s->r_acc); @@ -1326,6 +1613,7 @@ void run_vm(EngineState *s, bool restoring) { case op_selfID: // 0x2e (46) + // Get 'self' identity s->r_acc = s->xs->objp; break; @@ -1334,66 +1622,60 @@ void run_vm(EngineState *s, bool restoring) { break; case op_pprev: // 0x30 (48) + // Pushes the value of the prev register, set by the last comparison + // bytecode (eq?, lt?, etc.), on the stack PUSH32(s->r_prev); break; case op_pToa: // 0x31 (49) - s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)); + // Property To Accumulator + s->r_acc = validate_property(obj, (opparams[0] >> 1)); break; case op_aTop: // 0x32 (50) - OBJ_PROPERTY(obj, (opparams[0] >> 1)) = s->r_acc; + // Accumulator To Property + validate_property(obj, (opparams[0] >> 1)) = s->r_acc; break; case op_pTos: // 0x33 (51) - PUSH32(OBJ_PROPERTY(obj, opparams[0] >> 1)); + // Property To Stack + PUSH32(validate_property(obj, opparams[0] >> 1)); break; case op_sTop: // 0x34 (52) - OBJ_PROPERTY(obj, (opparams[0] >> 1)) = POP32(); + // Stack To Property + validate_property(obj, (opparams[0] >> 1)) = POP32(); break; case op_ipToa: // 0x35 (53) - s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)); - s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(1 + /*acc*/); + // Incement Property and copy To Accumulator + s->r_acc = validate_property(obj, (opparams[0] >> 1)); + s->r_acc = validate_property(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(1 + /*acc*/); break; case op_dpToa: { // 0x36 (54) - s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)); -#if 0 - // Speed throttling is possible here as well - // although this opens other issues like mud wrestling in lsl5 uses another local variable for delays - Object *var_container = obj; - if (!(obj->getInfoSelector().offset & SCRIPT_INFO_CLASS)) - var_container = s->_segMan->getObject(obj->getSuperClassSelector()); - uint16 varSelector = var_container->getVarSelector(opparams[0] >> 1); -// printf("%X\n", varSelector); -// printf("%s\n", g_sci->getKernel()->getSelectorName(varSelector).c_str()); - if ((varSelector == 0x84) || (varSelector == 0x92))) { - // selectors cycles, cycleCnt from lsl5 hardcoded - uint32 curTime = g_system->getMillis(); - if (s->_lastAnimateTime + 30 > curTime) - break; - s->_lastAnimateTime = curTime; - } -#endif - s->r_acc = OBJ_PROPERTY(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(-1 + /*acc*/); + // Decrement Property and copy To Accumulator + s->r_acc = validate_property(obj, (opparams[0] >> 1)); + s->r_acc = validate_property(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(-1 + /*acc*/); break; } case op_ipTos: // 0x37 (55) - validate_arithmetic(OBJ_PROPERTY(obj, (opparams[0] >> 1))); - temp = ++OBJ_PROPERTY(obj, (opparams[0] >> 1)).offset; + // Increment Property and push to Stack + validate_arithmetic(validate_property(obj, (opparams[0] >> 1))); + temp = ++validate_property(obj, (opparams[0] >> 1)).offset; PUSH(temp); break; case op_dpTos: // 0x38 (56) - validate_arithmetic(OBJ_PROPERTY(obj, (opparams[0] >> 1))); - temp = --OBJ_PROPERTY(obj, (opparams[0] >> 1)).offset; + // Decrement Property and push to Stack + validate_arithmetic(validate_property(obj, (opparams[0] >> 1))); + temp = --validate_property(obj, (opparams[0] >> 1)).offset; PUSH(temp); break; case op_lofsa: // 0x39 (57) + // Load Offset to Accumulator s->r_acc.segment = s->xs->addr.pc.segment; switch (g_sci->_features->detectLofsType()) { @@ -1407,15 +1689,14 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc.offset = s->xs->addr.pc.offset + opparams[0]; } -#ifndef DISABLE_VALIDATIONS - if (s->r_acc.offset >= code_buf_size) { + if (s->r_acc.offset >= scr->getBufSize()) { error("VM: lofsa operation overflowed: %04x:%04x beyond end" - " of script (at %04x)\n", PRINT_REG(s->r_acc), code_buf_size); + " of script (at %04x)\n", PRINT_REG(s->r_acc), scr->getBufSize()); } -#endif break; case op_lofss: // 0x3a (58) + // Load Offset to Stack r_temp.segment = s->xs->addr.pc.segment; switch (g_sci->_features->detectLofsType()) { @@ -1429,12 +1710,10 @@ void run_vm(EngineState *s, bool restoring) { r_temp.offset = s->xs->addr.pc.offset + opparams[0]; } -#ifndef DISABLE_VALIDATIONS - if (r_temp.offset >= code_buf_size) { + if (r_temp.offset >= scr->getBufSize()) { error("VM: lofss operation overflowed: %04x:%04x beyond end" - " of script (at %04x)", PRINT_REG(r_temp), code_buf_size); + " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize()); } -#endif PUSH32(r_temp); break; @@ -1455,6 +1734,7 @@ void run_vm(EngineState *s, bool restoring) { PUSH32(s->xs->objp); } else { // Debug opcode op_file, skip null-terminated string (file name) + const byte *code_buf = scr->getBuf(); while (code_buf[s->xs->addr.pc.offset++]) ; } break; @@ -1467,42 +1747,49 @@ void run_vm(EngineState *s, bool restoring) { case op_lal: // 0x41 (65) case op_lat: // 0x42 (66) case op_lap: // 0x43 (67) + // Load global, local, temp or param variable into the accumulator var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; - s->r_acc = READ_VAR(var_type, var_number, s->r_acc); + s->r_acc = READ_VAR(var_type, var_number); break; case op_lsg: // 0x44 (68) case op_lsl: // 0x45 (69) case op_lst: // 0x46 (70) case op_lsp: // 0x47 (71) + // Load global, local, temp or param variable into the stack var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; - PUSH32(READ_VAR(var_type, var_number, s->r_acc)); + PUSH32(READ_VAR(var_type, var_number)); break; case op_lagi: // 0x48 (72) case op_lali: // 0x49 (73) case op_lati: // 0x4a (74) case op_lapi: // 0x4b (75) + // Load global, local, temp or param variable into the accumulator, + // using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); - s->r_acc = READ_VAR(var_type, var_number, s->r_acc); + s->r_acc = READ_VAR(var_type, var_number); break; case op_lsgi: // 0x4c (76) case op_lsli: // 0x4d (77) case op_lsti: // 0x4e (78) case op_lspi: // 0x4f (79) + // Load global, local, temp or param variable into the stack, + // using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); - PUSH32(READ_VAR(var_type, var_number, s->r_acc)); + PUSH32(READ_VAR(var_type, var_number)); break; case op_sag: // 0x50 (80) case op_sal: // 0x51 (81) case op_sat: // 0x52 (82) case op_sap: // 0x53 (83) + // Save the accumulator into the global, local, temp or param variable var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; WRITE_VAR(var_type, var_number, s->r_acc); @@ -1512,6 +1799,7 @@ void run_vm(EngineState *s, bool restoring) { case op_ssl: // 0x55 (85) case op_sst: // 0x56 (86) case op_ssp: // 0x57 (87) + // Save the stack into the global, local, temp or param variable var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; WRITE_VAR(var_type, var_number, POP32()); @@ -1521,6 +1809,9 @@ void run_vm(EngineState *s, bool restoring) { case op_sali: // 0x59 (89) case op_sati: // 0x5a (90) case op_sapi: // 0x5b (91) + // Save the accumulator into the global, local, temp or param variable, + // using the accumulator as an additional index + // Special semantics because it wouldn't really make a whole lot // of sense otherwise, with acc being used for two things // simultaneously... @@ -1534,6 +1825,8 @@ void run_vm(EngineState *s, bool restoring) { case op_ssli: // 0x5d (93) case op_ssti: // 0x5e (94) case op_sspi: // 0x5f (95) + // Save the stack into the global, local, temp or param variable, + // using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); WRITE_VAR(var_type, var_number, POP32()); @@ -1543,9 +1836,11 @@ void run_vm(EngineState *s, bool restoring) { case op_plusal: // 0x61 (97) case op_plusat: // 0x62 (98) case op_plusap: // 0x63 (99) + // Increment the global, local, temp or param variable and save it + // to the accumulator var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, 1); @@ -1558,9 +1853,11 @@ void run_vm(EngineState *s, bool restoring) { case op_plussl: // 0x65 (101) case op_plusst: // 0x66 (102) case op_plussp: // 0x67 (103) + // Increment the global, local, temp or param variable and save it + // to the stack var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, 1); @@ -1574,9 +1871,11 @@ void run_vm(EngineState *s, bool restoring) { case op_plusali: // 0x69 (105) case op_plusati: // 0x6a (106) case op_plusapi: // 0x6b (107) + // Increment the global, local, temp or param variable and save it + // to the accumulator, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, 1); @@ -1589,9 +1888,11 @@ void run_vm(EngineState *s, bool restoring) { case op_plussli: // 0x6d (109) case op_plussti: // 0x6e (110) case op_plusspi: // 0x6f (111) + // Increment the global, local, temp or param variable and save it + // to the stack, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, 1); @@ -1605,9 +1906,11 @@ void run_vm(EngineState *s, bool restoring) { case op_minusal: // 0x71 (113) case op_minusat: // 0x72 (114) case op_minusap: // 0x73 (115) + // Decrement the global, local, temp or param variable and save it + // to the accumulator var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, -1); @@ -1620,9 +1923,11 @@ void run_vm(EngineState *s, bool restoring) { case op_minussl: // 0x75 (117) case op_minusst: // 0x76 (118) case op_minussp: // 0x77 (119) + // Decrement the global, local, temp or param variable and save it + // to the stack var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, -1); @@ -1636,9 +1941,11 @@ void run_vm(EngineState *s, bool restoring) { case op_minusali: // 0x79 (121) case op_minusati: // 0x7a (122) case op_minusapi: // 0x7b (123) + // Decrement the global, local, temp or param variable and save it + // to the accumulator, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! s->r_acc = pointer_add(s, r_temp, -1); @@ -1651,9 +1958,11 @@ void run_vm(EngineState *s, bool restoring) { case op_minussli: // 0x7d (125) case op_minussti: // 0x7e (126) case op_minusspi: // 0x7f (127) + // Decrement the global, local, temp or param variable and save it + // to the stack, using the accumulator as an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p var_number = opparams[0] + signed_validate_arithmetic(s->r_acc); - r_temp = READ_VAR(var_type, var_number, s->r_acc); + r_temp = READ_VAR(var_type, var_number); if (r_temp.segment) { // Pointer arithmetics! r_temp = pointer_add(s, r_temp, -1); @@ -1671,100 +1980,15 @@ void run_vm(EngineState *s, bool restoring) { if (s->_executionStackPosChanged) // Force initialization s->xs = xs_new; -//#ifndef DISABLE_VALIDATIONS if (s->xs != &(s->_executionStack.back())) { - warning("xs is stale (%p vs %p); last command was %02x", + error("xs is stale (%p vs %p); last command was %02x", (void *)s->xs, (void *)&(s->_executionStack.back()), opcode); } -//#endif - ++s->script_step_counter; + ++s->scriptStepCounter; } } -static void _init_stack_base_with_selector(EngineState *s, Selector selector) { - s->stack_base[0] = make_reg(0, (uint16)selector); - s->stack_base[1] = NULL_REG; -} - -static EngineState *_game_run(EngineState *&s) { - bool restoring = false; - - if (DebugMan.isDebugChannelEnabled(kDebugLevelOnStartup)) - g_sci->getSciDebugger()->attach(); - - do { - s->_executionStackPosChanged = false; - run_vm(s, restoring); - if (s->restarting_flags & SCI_GAME_IS_RESTARTING_NOW) { // Restart was requested? - restoring = false; - s->_executionStack.clear(); - s->_executionStackPosChanged = false; - - game_exit(s); - script_init_engine(s); - game_init(s); -#ifdef USE_OLD_MUSIC_FUNCTIONS - s->_sound.sfx_reset_player(); -#endif - _init_stack_base_with_selector(s, g_sci->getKernel()->_selectorCache.play); - - send_selector(s, s->_gameObj, s->_gameObj, s->stack_base, 2, s->stack_base); - - s->script_abort_flag = 0; - s->restarting_flags = SCI_GAME_WAS_RESTARTED; - - } else { - restoring = s->restoring; - if (restoring) { - game_exit(s); - s->restoring = false; - if (s->script_abort_flag == 2) { - debugC(2, kDebugLevelVM, "Restarting with replay()"); - s->_executionStack.clear(); // Restart with replay - - _init_stack_base_with_selector(s, g_sci->getKernel()->_selectorCache.replay); - - send_selector(s, s->_gameObj, s->_gameObj, s->stack_base, 2, s->stack_base); - } - - s->script_abort_flag = 0; - - } else - break; // exit loop - } - } while (true); - - return s; -} - -int game_run(EngineState **_s) { - EngineState *s = *_s; - - debugC(2, kDebugLevelVM, "Calling %s::play()", g_sci->getGameID()); - _init_stack_base_with_selector(s, g_sci->getKernel()->_selectorCache.play); // Call the play selector - - // Now: Register the first element on the execution stack- - if (!send_selector(s, s->_gameObj, s->_gameObj, s->stack_base, 2, s->stack_base)) { - Console *con = g_sci->getSciDebugger(); - con->printObject(s->_gameObj); - warning("Failed to run the game! Aborting..."); - return 1; - } - // and ENGAGE! - _game_run(*_s); - - debugC(2, kDebugLevelVM, "Game::play() finished."); - - return 0; -} - -void quit_vm(EngineState *s) { - s->script_abort_flag = 1; // Terminate VM - g_debugState.seeking = kDebugSeekNothing; - g_debugState.runningStep = 0; -} - reg_t *ObjVarRef::getPointer(SegManager *segMan) const { Object *o = segMan->getObject(obj); return o ? &o->getVariableRef(varindex) : 0; diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 67a6bd0dc3..81ec4f1c61 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -49,11 +49,6 @@ class ResourceManager; /** Offset of this identifier */ #define SCRIPT_OBJECT_MAGIC_OFFSET (getSciVersion() < SCI_VERSION_1_1 ? -8 : 0) -/** Script-relative offset of the species ID */ -#define SCRIPT_SPECIES_OFFSET 8 -8 - -#define SCRIPT_SUPERCLASS_OFFSET (getSciVersion() < SCI_VERSION_1_1 ? 10 -8 : 12) - /** Stack pointer value: Use predecessor's value */ #define CALL_SP_CARRY NULL @@ -69,115 +64,7 @@ struct Class { reg_t reg; ///< offset; script-relative offset, segment: 0 if not instantiated }; -#define RAW_IS_OBJECT(datablock) (READ_SCI11ENDIAN_UINT16(((byte *) datablock) + SCRIPT_OBJECT_MAGIC_OFFSET) == SCRIPT_OBJECT_MAGIC_NUMBER) - -/** Contains selector IDs for a few selected selectors */ -struct SelectorCache { - SelectorCache() { - memset(this, 0, sizeof(*this)); - } - - // Statically defined selectors, (almost the) same in all SCI versions - Selector y; - Selector x; - Selector view, loop, cel; ///< Description of a specific image - Selector underBits; ///< Used by the graphics subroutines to store backupped BG pic data - Selector nsTop, nsLeft, nsBottom, nsRight; ///< View boundaries ('now seen') - Selector lsTop, lsLeft, lsBottom, lsRight; ///< Used by Animate() subfunctions and scroll list controls - Selector signal; ///< Used by Animate() to control a view's behaviour - Selector illegalBits; ///< Used by CanBeHere - Selector brTop, brLeft, brBottom, brRight; ///< Bounding Rectangle - // name, key, time - Selector text; ///< Used by controls - Selector elements; ///< Used by SetSynonyms() - // color, back - Selector mode; ///< Used by text controls (-> DrawControl()) - // style - Selector state, font, type;///< Used by controls - // window - Selector cursor, max; ///< Used by EditControl - // mark, who - Selector message; ///< Used by GetEvent - // edit - Selector play; ///< Play function (first function to be called) - Selector number; - Selector handle; ///< Replaced by nodePtr in SCI1+ - Selector nodePtr; ///< Replaces handle in SCI1+ - Selector client; ///< The object that wants to be moved - Selector dx, dy; ///< Deltas - Selector b_movCnt, b_i1, b_i2, b_di, b_xAxis, b_incr; ///< Various Bresenham vars - Selector xStep, yStep; ///< BR adjustments - Selector moveSpeed; ///< Used for DoBresen - Selector canBeHere; ///< Funcselector: Checks for movement validity in SCI0 - Selector heading, mover; ///< Used in DoAvoider - Selector doit; ///< Called (!) by the Animate() system call - Selector isBlocked, looper; ///< Used in DoAvoider - Selector priority; - Selector modifiers; ///< Used by GetEvent - Selector replay; ///< Replay function - // setPri, at, next, done, width - Selector wordFail, syntaxFail; ///< Used by Parse() - // semanticFail, pragmaFail - // said - Selector claimed; ///< Used generally by the event mechanism - // value, save, restore, title, button, icon, draw - Selector delete_; ///< Called by Animate() to dispose a view object - Selector z; - - // SCI1+ static selectors - Selector parseLang; - Selector printLang; ///< Used for i18n - Selector subtitleLang; - Selector size; - Selector points; ///< Used by AvoidPath() - Selector palette; - Selector dataInc; - // handle (in SCI1) - Selector min; ///< SMPTE time format - Selector sec; - Selector frame; - Selector vol; - Selector pri; - // perform - Selector moveDone; ///< used for DoBresen - - // SCI1 selectors which have been moved a bit in SCI1.1, but otherwise static - Selector cantBeHere; ///< Checks for movement avoidance in SCI1+. Replaces canBeHere - Selector topString; ///< SCI1 scroll lists use this instead of lsTop - Selector flags; - - // SCI1+ audio sync related selectors, not static. They're used for lip syncing in - // CD talkie games - Selector syncCue; ///< Used by DoSync() - Selector syncTime; - - // SCI1.1 specific selectors - Selector scaleSignal; // < Used by Animate() for cel scaling (SCI1.1+) - Selector scaleX, scaleY; ///< SCI1.1 view scaling - - // Used for auto detection purposes - Selector overlay; ///< Used to determine if a game is using old gfx functions or not - - // SCI1.1 Mac icon bar selectors - Selector iconIndex; ///< Used to index icon bar objects - -#ifdef ENABLE_SCI32 - Selector data; // Used by Array()/String() - Selector picture; // Used to hold the picture ID for SCI32 pictures - - Selector plane; - Selector top; - Selector left; - Selector bottom; - Selector right; - Selector resX; - Selector resY; - - Selector fore; - Selector back; - Selector dimmed; -#endif -}; +#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 @@ -210,8 +97,10 @@ struct ExecStack { StackPtr variables_argp; // Argument pointer SegmentId local_segment; // local variables etc - Selector selector; // The selector which was used to call or -1 if not applicable - int origin; // The stack frame position the call was made from, or -1 if it was the initial call + Selector debugSelector; // The selector which was used to call or -1 if not applicable + int debugExportId; // The exportId which was called or -1 if not applicable + int debugLocalCallOffset; // Local call offset or -1 if not applicable + int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call ExecStackType type; reg_t* getVarPointer(SegManager *segMan) const; @@ -229,6 +118,160 @@ enum { GC_INTERVAL = 32768 }; +// Opcode formats +enum opcode_format { + Script_Invalid = -1, + Script_None = 0, + Script_Byte, + Script_SByte, + Script_Word, + Script_SWord, + Script_Variable, + Script_SVariable, + Script_SRelative, + Script_Property, + Script_Global, + Script_Local, + Script_Temp, + Script_Param, + Script_Offset, + Script_End +}; + +enum sci_opcodes { + op_bnot = 0x00, // 000 + op_add = 0x01, // 001 + op_sub = 0x02, // 002 + op_mul = 0x03, // 003 + op_div = 0x04, // 004 + op_mod = 0x05, // 005 + op_shr = 0x06, // 006 + op_shl = 0x07, // 007 + op_xor = 0x08, // 008 + op_and = 0x09, // 009 + op_or = 0x0a, // 010 + op_neg = 0x0b, // 011 + op_not = 0x0c, // 012 + op_eq_ = 0x0d, // 013 + op_ne_ = 0x0e, // 014 + op_gt_ = 0x0f, // 015 + op_ge_ = 0x10, // 016 + op_lt_ = 0x11, // 017 + op_le_ = 0x12, // 018 + op_ugt_ = 0x13, // 019 + op_uge_ = 0x14, // 020 + op_ult_ = 0x15, // 021 + op_ule_ = 0x16, // 022 + op_bt = 0x17, // 023 + op_bnt = 0x18, // 024 + op_jmp = 0x19, // 025 + op_ldi = 0x1a, // 026 + op_push = 0x1b, // 027 + op_pushi = 0x1c, // 028 + op_toss = 0x1d, // 029 + op_dup = 0x1e, // 030 + op_link = 0x1f, // 031 + op_call = 0x20, // 032 + op_callk = 0x21, // 033 + op_callb = 0x22, // 034 + op_calle = 0x23, // 035 + op_ret = 0x24, // 036 + op_send = 0x25, // 037 + // dummy 0x26, // 038 + // dummy 0x27, // 039 + op_class = 0x28, // 040 + // dummy 0x29, // 041 + op_self = 0x2a, // 042 + op_super = 0x2b, // 043 + op_rest = 0x2c, // 044 + op_lea = 0x2d, // 045 + op_selfID = 0x2e, // 046 + // dummy 0x2f // 047 + op_pprev = 0x30, // 048 + op_pToa = 0x31, // 049 + op_aTop = 0x32, // 050 + op_pTos = 0x33, // 051 + op_sTop = 0x34, // 052 + op_ipToa = 0x35, // 053 + op_dpToa = 0x36, // 054 + op_ipTos = 0x37, // 055 + op_dpTos = 0x38, // 056 + op_lofsa = 0x39, // 057 + op_lofss = 0x3a, // 058 + op_push0 = 0x3b, // 059 + op_push1 = 0x3c, // 060 + op_push2 = 0x3d, // 061 + op_pushSelf = 0x3e, // 062 + op_line = 0x3f, // 063 + op_lag = 0x40, // 064 + op_lal = 0x41, // 065 + op_lat = 0x42, // 066 + op_lap = 0x43, // 067 + op_lsg = 0x44, // 068 + op_lsl = 0x45, // 069 + op_lst = 0x46, // 070 + op_lsp = 0x47, // 071 + op_lagi = 0x48, // 072 + op_lali = 0x49, // 073 + op_lati = 0x4a, // 074 + op_lapi = 0x4b, // 075 + op_lsgi = 0x4c, // 076 + op_lsli = 0x4d, // 077 + op_lsti = 0x4e, // 078 + op_lspi = 0x4f, // 079 + op_sag = 0x50, // 080 + op_sal = 0x51, // 081 + op_sat = 0x52, // 082 + op_sap = 0x53, // 083 + op_ssg = 0x54, // 084 + op_ssl = 0x55, // 085 + op_sst = 0x56, // 086 + op_ssp = 0x57, // 087 + op_sagi = 0x58, // 088 + op_sali = 0x59, // 089 + op_sati = 0x5a, // 090 + op_sapi = 0x5b, // 091 + op_ssgi = 0x5c, // 092 + op_ssli = 0x5d, // 093 + op_ssti = 0x5e, // 094 + op_sspi = 0x5f, // 095 + op_plusag = 0x60, // 096 + op_plusal = 0x61, // 097 + op_plusat = 0x62, // 098 + op_plusap = 0x63, // 099 + op_plussg = 0x64, // 100 + op_plussl = 0x65, // 101 + op_plusst = 0x66, // 102 + op_plussp = 0x67, // 103 + op_plusagi = 0x68, // 104 + op_plusali = 0x69, // 105 + op_plusati = 0x6a, // 106 + op_plusapi = 0x6b, // 107 + op_plussgi = 0x6c, // 108 + op_plussli = 0x6d, // 109 + op_plussti = 0x6e, // 110 + op_plusspi = 0x6f, // 111 + op_minusag = 0x70, // 112 + op_minusal = 0x71, // 113 + op_minusat = 0x72, // 114 + op_minusap = 0x73, // 115 + op_minussg = 0x74, // 116 + op_minussl = 0x75, // 117 + op_minusst = 0x76, // 118 + op_minussp = 0x77, // 119 + op_minusagi = 0x78, // 120 + op_minusali = 0x79, // 121 + op_minusati = 0x7a, // 122 + op_minusapi = 0x7b, // 123 + op_minussgi = 0x7c, // 124 + op_minussli = 0x7d, // 125 + op_minussti = 0x7e, // 126 + op_minusspi = 0x7f // 127 +}; + +extern opcode_format g_opcode_formats[128][4]; + +void script_adjust_opcode_formats(); /** * Executes function pubfunct of the specified script. @@ -283,14 +326,6 @@ void run_vm(EngineState *s, bool restoring); void script_debug(EngineState *s); /** - * Initializes a EngineState block - * @param[in] s The state to initialize - * @return 0 on success, 1 if vocab.996 (the class table) is missing - * or corrupted - */ -int script_init_engine(EngineState *); - -/** * Looks up a selector and returns its type and value * varindex is written to iff it is non-NULL and the selector indicates a property of the object. * @param[in] segMan The Segment Manager @@ -314,95 +349,6 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj, Selector selectorid, ObjVarRef *varp, reg_t *fptr); /** - * Makes sure that a script and its superclasses get loaded to the heap. - * If the script already has been loaded, only the number of lockers is - * increased. All scripts containing superclasses of this script are loaded - * recursively as well, unless 'recursive' is set to zero. The - * complementary function is "script_uninstantiate()" below. - * @param[in] resMan The resource manager - * @param[in] segMan The segment manager - * @param[in] script_nr The script number to load - * @return The script's segment ID or 0 if out of heap - */ -int script_instantiate(ResourceManager *resMan, SegManager *segMan, int script_nr); - -/** - * Decreases the numer of lockers of a script and unloads it if that number - * reaches zero. - * This function will recursively unload scripts containing its - * superclasses, if those aren't locked by other scripts as well. - * @param[in] segMan The segment manager - * @param[in] version The SCI version to use - * @param[in] script_nr The script number that is requestet to be unloaded - */ -void script_uninstantiate(SegManager *segMan, int script_nr); - -/** - * Converts the builtin Sierra game IDs to the ones we use in ScummVM - * @param[in] gameId The internal game ID - * @param[in] gameFlags The game's flags, which are adjusted accordingly for demos - * @return The equivalent ScummVM game id - */ -Common::String convertSierraGameId(const char *gameId, uint32 *gameFlags, ResourceManager *resMan); - -/** - * Initializes an SCI game - * This function must be run before script_run() is executed. Graphics data - * is initialized iff s->gfx_state != NULL. - * @param[in] s The state to operate on - * @return 0 on success, 1 if an error occured. - */ -int game_init(EngineState *s); - -#ifdef USE_OLD_MUSIC_FUNCTIONS -/** - * Initializes the sound part of an SCI game - * This function may only be called if game_init() did not initialize - * the sound data. - * @param[in] s The state to initialize the sound in - * @param[in] sound_flags Flags to pass to the sound subsystem - * @param[in] soundVersion sound-version that got detected during game init - * @return 0 on success, 1 if an error occured - */ -int game_init_sound(EngineState *s, int sound_flags, SciVersion soundVersion); -#endif - -/** - * Runs an SCI game - * This is the main function for SCI games. It takes a valid state, loads - * script 0 to it, finds the game object, allocates a stack, and runs the - * init method of the game object. In layman's terms, this runs an SCI game. - * Note that, EngineState *s may be changed during the game, e.g. if a game - * state is restored. - * @param[in] s Pointer to the pointer of the state to operate on - * @return 0 on success, 1 if an error occured. - */ -int game_run(EngineState **s); - -/** - * Restores an SCI game state and runs the game - * This restores a savegame; otherwise, it behaves just like game_run(). - * @param[in] s Pointer to the pointer of the state to - * operate on - * @param[in] savegame_name Name of the savegame to restore - * @return 0 on success, 1 if an error occured. - */ -int game_restore(EngineState **s, char *savegame_name); - -/** - * Uninitializes an initialized SCI game - * This function should be run after each script_run() call. - * @param[in] s The state to operate on - * @return 0 on success, 1 if an error occured. - */ -int game_exit(EngineState *s); - -/** - * Instructs the virtual machine to abort - */ -void quit_vm(EngineState *s); - -/** * Read a PMachine instruction from a memory buffer and return its length. * * @param[in] src address from which to start parsing diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index 29dc798c35..828fba3d7d 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -27,6 +27,7 @@ #define SCI_ENGINE_VM_TYPES_H #include "common/scummsys.h" +#include "common/serializer.h" namespace Sci { @@ -56,6 +57,11 @@ struct reg_t { int16 toSint16() const { return (int16) offset; } + + void saveLoadWithSerializer(Common::Serializer &s) { + s.syncAsUint16LE(segment); + s.syncAsUint16LE(offset); + } }; static inline reg_t make_reg(SegmentId segment, uint16 offset) { @@ -81,6 +87,7 @@ enum { extern const reg_t NULL_REG; extern const reg_t SIGNAL_REG; +extern const reg_t TRUE_REG; // Selector ID typedef int Selector; diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index c3be22b143..a100dda27c 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -25,6 +25,7 @@ #include "common/system.h" #include "common/events.h" +#include "common/file.h" #include "sci/sci.h" #include "sci/event.h" @@ -34,33 +35,51 @@ namespace Sci { -#define SCANCODE_ROWS_NR 3 +EventManager::EventManager(bool fontIsExtended) : _fontIsExtended(fontIsExtended), _modifierStates(0) { -SciEvent::SciEvent(ResourceManager *resMan) { - // Check, if font of current game includes extended chars - _fontIsExtended = resMan->detectFontExtended(); + if (getSciVersion() >= SCI_VERSION_1_MIDDLE) { + _usesNewKeyboardDirectionType = true; + } else if (getSciVersion() <= SCI_VERSION_01) { + _usesNewKeyboardDirectionType = false; + } else { + // they changed this somewhere inbetween SCI1EGA/EARLY + _usesNewKeyboardDirectionType = false; + + // We are looking if script 933 exists, that one has the PseudoMouse class in it that handles it + // The good thing is that PseudoMouse seems to only exists in games that use the new method + if (g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 933))) + _usesNewKeyboardDirectionType = true; + // Checking the keyboard driver size in here would also be a valid method, but the driver is only available + // in PC versions of the game + } +} + +EventManager::~EventManager() { } -SciEvent::~SciEvent() { +bool EventManager::getUsesNewKeyboardDirectionType() { + return _usesNewKeyboardDirectionType; } -struct scancode_row { +struct ScancodeRow { int offset; const char *keys; -} scancode_rows[SCANCODE_ROWS_NR] = { +}; + +static const ScancodeRow s_scancodeRows[] = { {0x10, "QWERTYUIOP[]"}, {0x1e, "ASDFGHJKL;'\\"}, {0x2c, "ZXCVBNM,./"} }; -int SciEvent::altify (int ch) { +static int altify(int ch) { // Calculates a PC keyboard scancode from a character */ int row; int c = toupper((char)ch); - for (row = 0; row < SCANCODE_ROWS_NR; row++) { - const char *keys = scancode_rows[row].keys; - int offset = scancode_rows[row].offset; + for (row = 0; row < ARRAYSIZE(s_scancodeRows); row++) { + const char *keys = s_scancodeRows[row].keys; + int offset = s_scancodeRows[row].offset; while (*keys) { if (*keys == c) @@ -74,7 +93,8 @@ int SciEvent::altify (int ch) { return ch; } -int SciEvent::numlockify (int c) { +/* +static int numlockify(int c) { switch (c) { case SCI_KEY_DELETE: return '.'; @@ -102,6 +122,7 @@ int SciEvent::numlockify (int c) { return c; // Unchanged } } +*/ static const byte codepagemap_88591toDOS[0x80] = { '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x8x @@ -114,9 +135,8 @@ static const byte codepagemap_88591toDOS[0x80] = { '?', 0xa4, 0x95, 0xa2, 0x93, '?', 0x94, '?', '?', 0x97, 0xa3, 0x96, 0x81, '?', '?', 0x98 // 0xFx }; -sciEvent SciEvent::getFromScummVM() { - static int _modifierStates = 0; // FIXME: Avoid non-const global vars - sciEvent input = { SCI_EVENT_NONE, 0, 0, 0 }; +SciEvent EventManager::getScummVMEvent() { + SciEvent input = { SCI_EVENT_NONE, 0, 0, 0 }; Common::EventManager *em = g_system->getEventManager(); Common::Event ev; @@ -315,22 +335,26 @@ sciEvent SciEvent::getFromScummVM() { return input; } -sciEvent SciEvent::get(unsigned int mask) { +SciEvent EventManager::getSciEvent(unsigned int mask) { //sci_event_t error_event = { SCI_EVT_ERROR, 0, 0, 0 }; - sciEvent event = { 0, 0, 0, 0 }; + SciEvent event = { 0, 0, 0, 0 }; - // Update the screen here, since it's called very often - g_system->updateScreen(); + // Update the screen here, since it's called very often. + // Throttle the screen update rate to 60fps. + if (g_system->getMillis() - g_sci->getEngineState()->_screenUpdateTime >= 1000 / 60) { + g_system->updateScreen(); + g_sci->getEngineState()->_screenUpdateTime = g_system->getMillis(); + } // Get all queued events from graphics driver do { - event = getFromScummVM(); + event = getScummVMEvent(); if (event.type != SCI_EVENT_NONE) _events.push_back(event); } while (event.type != SCI_EVENT_NONE); // Search for matching event in queue - Common::List<sciEvent>::iterator iter = _events.begin(); + Common::List<SciEvent>::iterator iter = _events.begin(); while (iter != _events.end() && !((*iter).type & mask)) ++iter; @@ -380,13 +404,13 @@ sciEvent SciEvent::get(unsigned int mask) { return event; } -void SciEvent::sleep(uint32 msecs) { +void SciEngine::sleep(uint32 msecs) { uint32 time; const uint32 wakeup_time = g_system->getMillis() + msecs; while (true) { // let backend process events and update the screen - get(SCI_EVENT_PEEK); + _eventMan->getSciEvent(SCI_EVENT_PEEK); time = g_system->getMillis(); if (time + 10 < wakeup_time) { g_system->delayMillis(10); diff --git a/engines/sci/event.h b/engines/sci/event.h index 9301b1ca09..30098b0f6e 100644 --- a/engines/sci/event.h +++ b/engines/sci/event.h @@ -23,31 +23,29 @@ * */ -#ifndef SCI_ENGINE_EVENT_H -#define SCI_ENGINE_EVENT_H +#ifndef SCI_EVENT_H +#define SCI_EVENT_H #include "common/list.h" namespace Sci { -#define SCI_INPUT_DEFAULT_CLOCKTIME 100000 -#define SCI_INPUT_DEFAULT_REDRAWTIME 30000 - - -struct sciEvent { +struct SciEvent { short type; short data; short modifiers; - short character; /* for keyboard events: 'data' after applying - ** the effects of 'modifiers', e.g. if - ** type == SCI_EVT_KEYBOARD - ** data == 'a' - ** buckybits == SCI_EVM_LSHIFT - ** then - ** character == 'A' - ** For 'Alt', characters are interpreted by their - ** PC keyboard scancodes. - */ + /** + * For keyboard events: 'data' after applying + * the effects of 'modifiers', e.g. if + * type == SCI_EVT_KEYBOARD + * data == 'a' + * buckybits == SCI_EVM_LSHIFT + * then + * character == 'A' + * For 'Alt', characters are interpreted by their + * PC keyboard scancodes. + */ + short character; }; /*Values for type*/ @@ -55,7 +53,7 @@ struct sciEvent { #define SCI_EVENT_MOUSE_PRESS (1<<0) #define SCI_EVENT_MOUSE_RELEASE (1<<1) #define SCI_EVENT_KEYBOARD (1<<2) -#define SCI_EVENT_JOYSTICK (1<<6) +#define SCI_EVENT_DIRECTION (1<<6) #define SCI_EVENT_SAID (1<<7) /*Fake values for other events*/ #define SCI_EVENT_ERROR (1<<10) @@ -111,25 +109,22 @@ struct sciEvent { #define SCI_KEYMOD_NO_FOOLOCK (~(SCI_KEYMOD_SCRLOCK | SCI_KEYMOD_NUMLOCK | SCI_KEYMOD_CAPSLOCK | SCI_KEYMOD_INSERT)) #define SCI_KEYMOD_ALL 0xFF -class SciEvent { +class EventManager { public: - SciEvent(ResourceManager *resMgr); - ~SciEvent(); - - sciEvent get(unsigned int mask); + EventManager(bool fontIsExtended); + ~EventManager(); - void sleep(uint32 msecs); + SciEvent getSciEvent(unsigned int mask); + bool getUsesNewKeyboardDirectionType(); private: - int altify (int ch); - int shiftify (int c); - int numlockify (int c); - sciEvent getFromScummVM(); + SciEvent getScummVMEvent(); - ResourceManager *_resMan; + const bool _fontIsExtended; + int _modifierStates; + Common::List<SciEvent> _events; - bool _fontIsExtended; - Common::List<sciEvent> _events; + bool _usesNewKeyboardDirectionType; }; } // End of namespace Sci diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp index c201b2cfb7..6fb427500d 100644 --- a/engines/sci/graphics/animate.cpp +++ b/engines/sci/graphics/animate.cpp @@ -28,6 +28,7 @@ #include "graphics/primitives.h" #include "sci/sci.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/engine/vm.h" @@ -35,6 +36,7 @@ #include "sci/graphics/cursor.h" #include "sci/graphics/ports.h" #include "sci/graphics/paint16.h" +#include "sci/graphics/palette.h" #include "sci/graphics/view.h" #include "sci/graphics/screen.h" #include "sci/graphics/transitions.h" @@ -48,28 +50,25 @@ GfxAnimate::GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, Gfx } GfxAnimate::~GfxAnimate() { - free(_listData); - free(_lastCastData); } void GfxAnimate::init() { - _listData = NULL; - _listCount = 0; - _lastCastData = NULL; - _lastCastCount = 0; + _lastCastData.clear(); _ignoreFastCast = false; // fastCast object is not found in any SCI games prior SCI1 if (getSciVersion() <= SCI_VERSION_01) _ignoreFastCast = true; // Also if fastCast object exists at gamestartup, we can assume that the interpreter doesnt do kAnimate aborts - // (found in larry 1) - if (!_s->_segMan->findObjectByName("fastCast").isNull()) - _ignoreFastCast = true; + // (found in Larry 1) + if (getSciVersion() > SCI_VERSION_0_EARLY) { + if (!_s->_segMan->findObjectByName("fastCast").isNull()) + _ignoreFastCast = true; + } } void GfxAnimate::disposeLastCast() { - _lastCastCount = 0; + _lastCastData.clear(); } bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) { @@ -84,10 +83,8 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) { if (!_ignoreFastCast) { // Check if the game has a fastCast object set // if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes. - reg_t global84 = _s->script_000->_localsBlock->_locals[84]; - - if (!global84.isNull()) { - if (!strcmp(_s->_segMan->getObjectName(global84), "fastCast")) + if (!_s->variables[VAR_GLOBAL][84].isNull()) { + if (!strcmp(_s->_segMan->getObjectName(_s->variables[VAR_GLOBAL][84]), "fastCast")) return false; } } @@ -95,7 +92,12 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) { signal = readSelectorValue(_s->_segMan, curObject, SELECTOR(signal)); if (!(signal & kSignalFrozen)) { // Call .doit method of that object - invokeSelector(_s, curObject, g_sci->getKernel()->_selectorCache.doit, kContinueOnInvalidSelector, argc, argv, 0); + invokeSelector(_s, curObject, SELECTOR(doit), argc, argv, 0); + + // If a game is being loaded, stop processing + if (_s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit()) + return true; // Stop processing + // Lookup node again, since the nodetable it was in may have been reallocated curNode = _s->_segMan->lookupNode(curAddress); } @@ -108,99 +110,64 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) { return true; } -bool sortHelper(const AnimateEntry* entry1, const AnimateEntry* entry2) { - if (entry1->y == entry2->y) { +bool sortHelper(const AnimateEntry &entry1, const AnimateEntry &entry2) { + if (entry1.y == entry2.y) { // if both y and z are the same, use the order we were given originally // this is needed for special cases like iceman room 35 - if (entry1->z == entry2->z) - return entry1->givenOrderNo < entry2->givenOrderNo; + if (entry1.z == entry2.z) + return entry1.givenOrderNo < entry2.givenOrderNo; else - return entry1->z < entry2->z; + return entry1.z < entry2.z; } - return entry1->y < entry2->y; + return entry1.y < entry2.y; } void GfxAnimate::makeSortedList(List *list) { reg_t curAddress = list->first; Node *curNode = _s->_segMan->lookupNode(curAddress); - reg_t curObject; - AnimateEntry *listEntry; - int16 listNr, listCount = 0; - - // Count the list entries - while (curNode) { - listCount++; - curAddress = curNode->succ; - curNode = _s->_segMan->lookupNode(curAddress); - } + int16 listNr; + // Clear lists _list.clear(); - - // No entries -> exit immediately - if (listCount == 0) - return; - - // Adjust list size, if needed - if ((_listData == NULL) || (_listCount < listCount)) { - free(_listData); - _listData = (AnimateEntry *)malloc(listCount * sizeof(AnimateEntry)); - if (!_listData) - error("Could not allocate memory for _listData"); - _listCount = listCount; - - free(_lastCastData); - _lastCastData = (AnimateEntry *)malloc(listCount * sizeof(AnimateEntry)); - if (!_lastCastData) - error("Could not allocate memory for _lastCastData"); - _lastCastCount = 0; - } + _lastCastData.clear(); // Fill the list - curAddress = list->first; - curNode = _s->_segMan->lookupNode(curAddress); - listEntry = _listData; - for (listNr = 0; listNr < listCount; listNr++) { - curObject = curNode->value; - listEntry->object = curObject; + for (listNr = 0; curNode != 0; listNr++) { + AnimateEntry listEntry; + const reg_t curObject = curNode->value; + listEntry.object = curObject; // Get data from current object - listEntry->givenOrderNo = listNr; - listEntry->viewId = readSelectorValue(_s->_segMan, curObject, SELECTOR(view)); - listEntry->loopNo = readSelectorValue(_s->_segMan, curObject, SELECTOR(loop)); - listEntry->celNo = readSelectorValue(_s->_segMan, curObject, SELECTOR(cel)); - listEntry->paletteNo = readSelectorValue(_s->_segMan, curObject, SELECTOR(palette)); - listEntry->x = readSelectorValue(_s->_segMan, curObject, SELECTOR(x)); - listEntry->y = readSelectorValue(_s->_segMan, curObject, SELECTOR(y)); - listEntry->z = readSelectorValue(_s->_segMan, curObject, SELECTOR(z)); - listEntry->priority = readSelectorValue(_s->_segMan, curObject, SELECTOR(priority)); - listEntry->signal = readSelectorValue(_s->_segMan, curObject, SELECTOR(signal)); + listEntry.givenOrderNo = listNr; + listEntry.viewId = readSelectorValue(_s->_segMan, curObject, SELECTOR(view)); + listEntry.loopNo = readSelectorValue(_s->_segMan, curObject, SELECTOR(loop)); + listEntry.celNo = readSelectorValue(_s->_segMan, curObject, SELECTOR(cel)); + listEntry.paletteNo = readSelectorValue(_s->_segMan, curObject, SELECTOR(palette)); + listEntry.x = readSelectorValue(_s->_segMan, curObject, SELECTOR(x)); + listEntry.y = readSelectorValue(_s->_segMan, curObject, SELECTOR(y)); + listEntry.z = readSelectorValue(_s->_segMan, curObject, SELECTOR(z)); + listEntry.priority = readSelectorValue(_s->_segMan, curObject, SELECTOR(priority)); + listEntry.signal = readSelectorValue(_s->_segMan, curObject, SELECTOR(signal)); if (getSciVersion() >= SCI_VERSION_1_1) { // Cel scaling - listEntry->scaleSignal = readSelectorValue(_s->_segMan, curObject, SELECTOR(scaleSignal)); - if (listEntry->scaleSignal & kScaleSignalDoScaling) { - listEntry->scaleX = readSelectorValue(_s->_segMan, curObject, SELECTOR(scaleX)); - listEntry->scaleY = readSelectorValue(_s->_segMan, curObject, SELECTOR(scaleY)); + listEntry.scaleSignal = readSelectorValue(_s->_segMan, curObject, SELECTOR(scaleSignal)); + if (listEntry.scaleSignal & kScaleSignalDoScaling) { + listEntry.scaleX = readSelectorValue(_s->_segMan, curObject, SELECTOR(scaleX)); + listEntry.scaleY = readSelectorValue(_s->_segMan, curObject, SELECTOR(scaleY)); } else { - listEntry->scaleX = 128; - listEntry->scaleY = 128; + listEntry.scaleX = 128; + listEntry.scaleY = 128; } - // TODO - // On scaleSignal bit 1 sierra sci does some stuff with global var 2, current Port - // and some other stuff and sets scaleX/Y accordingly. It seems this functionality is needed in at - // least sq5 right when starting the game before wilco exists the room. Currently we dont get scaling - // but sierra sci does scaling there. I dont fully understand the code yet, that's why i didnt implement - // anything. } else { - listEntry->scaleSignal = 0; - listEntry->scaleX = 128; - listEntry->scaleY = 128; + listEntry.scaleSignal = 0; + listEntry.scaleX = 128; + listEntry.scaleY = 128; } - // listEntry->celRect is filled in AnimateFill() - listEntry->showBitsFlag = false; + // listEntry.celRect is filled in AnimateFill() + listEntry.showBitsFlag = false; _list.push_back(listEntry); - listEntry++; curAddress = curNode->succ; curNode = _s->_segMan->lookupNode(curAddress); } @@ -211,47 +178,73 @@ void GfxAnimate::makeSortedList(List *list) { void GfxAnimate::fill(byte &old_picNotValid) { reg_t curObject; - AnimateEntry *listEntry; uint16 signal; GfxView *view = NULL; - AnimateList::iterator listIterator; - AnimateList::iterator listEnd = _list.end(); + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); - listIterator = _list.begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; // Get the corresponding view - view = _cache->getView(listEntry->viewId); + view = _cache->getView(it->viewId); // adjust loop and cel, if any of those is invalid - if (listEntry->loopNo >= view->getLoopCount()) { - listEntry->loopNo = 0; - writeSelectorValue(_s->_segMan, curObject, SELECTOR(loop), listEntry->loopNo); + if (it->loopNo >= view->getLoopCount()) { + it->loopNo = 0; + writeSelectorValue(_s->_segMan, curObject, SELECTOR(loop), it->loopNo); } - if (listEntry->celNo >= view->getCelCount(listEntry->loopNo)) { - listEntry->celNo = 0; - writeSelectorValue(_s->_segMan, curObject, SELECTOR(cel), listEntry->celNo); + if (it->celNo >= view->getCelCount(it->loopNo)) { + it->celNo = 0; + writeSelectorValue(_s->_segMan, curObject, SELECTOR(cel), it->celNo); + } + + // Process global scaling, if needed + if (it->scaleSignal & kScaleSignalDoScaling) { + if (it->scaleSignal & kScaleSignalGlobalScaling) { + // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY + int16 maxScale = readSelectorValue(_s->_segMan, curObject, SELECTOR(maxScale)); + int16 celHeight = view->getHeight(it->loopNo, it->celNo); + int16 maxCelHeight = (maxScale * celHeight) >> 7; + reg_t globalVar2 = _s->variables[VAR_GLOBAL][2]; // current room object + int16 vanishingY = readSelectorValue(_s->_segMan, globalVar2, SELECTOR(vanishingY)); + + int16 fixedPortY = _ports->getPort()->rect.bottom - vanishingY; + int16 fixedEntryY = it->y - vanishingY; + if (!fixedEntryY) + fixedEntryY = 1; + + if ((celHeight == 0) || (fixedPortY == 0)) + error("global scaling panic"); + + it->scaleY = ( maxCelHeight * fixedEntryY ) / fixedPortY; + it->scaleY = (it->scaleY * 128) / celHeight; + + it->scaleX = it->scaleY; + + // and set objects scale selectors + writeSelectorValue(_s->_segMan, curObject, SELECTOR(scaleX), it->scaleX); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(scaleY), it->scaleY); + } } // Create rect according to coordinates and given cel - if (listEntry->scaleSignal & kScaleSignalDoScaling) { - view->getCelScaledRect(listEntry->loopNo, listEntry->celNo, listEntry->x, listEntry->y, listEntry->z, listEntry->scaleX, listEntry->scaleY, &listEntry->celRect); + if (it->scaleSignal & kScaleSignalDoScaling) { + view->getCelScaledRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->scaleX, it->scaleY, it->celRect); } else { - view->getCelRect(listEntry->loopNo, listEntry->celNo, listEntry->x, listEntry->y, listEntry->z, &listEntry->celRect); + view->getCelRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->celRect); } - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsLeft), listEntry->celRect.left); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsTop), listEntry->celRect.top); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsRight), listEntry->celRect.right); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsBottom), listEntry->celRect.bottom); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsLeft), it->celRect.left); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsTop), it->celRect.top); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsRight), it->celRect.right); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsBottom), it->celRect.bottom); - signal = listEntry->signal; + signal = it->signal; // Calculate current priority according to y-coordinate if (!(signal & kSignalFixedPriority)) { - listEntry->priority = _ports->kernelCoordinateToPriority(listEntry->y); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(priority), listEntry->priority); + it->priority = _ports->kernelCoordinateToPriority(it->y); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(priority), it->priority); } if (signal & kSignalNoUpdate) { @@ -260,177 +253,154 @@ void GfxAnimate::fill(byte &old_picNotValid) { || (!(signal & kSignalHidden) && signal & kSignalRemoveView) || (signal & kSignalAlwaysUpdate)) old_picNotValid++; - signal &= 0xFFFF ^ kSignalStopUpdate; + signal &= ~kSignalStopUpdate; } else { if (signal & kSignalStopUpdate || signal & kSignalAlwaysUpdate) old_picNotValid++; - signal &= 0xFFFF ^ kSignalForceUpdate; + signal &= ~kSignalForceUpdate; } - listEntry->signal = signal; - - listIterator++; + it->signal = signal; } } void GfxAnimate::update() { reg_t curObject; - AnimateEntry *listEntry; uint16 signal; reg_t bitsHandle; Common::Rect rect; - AnimateList::iterator listIterator; - AnimateList::iterator listBegin = _list.begin(); - AnimateList::iterator listEnd = _list.end(); + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); // Remove all no-update cels, if requested - listIterator = _list.reverse_begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + for (it = _list.reverse_begin(); it != end; --it) { + curObject = it->object; + signal = it->signal; if (signal & kSignalNoUpdate) { if (!(signal & kSignalRemoveView)) { bitsHandle = readSelector(_s->_segMan, curObject, SELECTOR(underBits)); if (_screen->_picNotValid != 1) { _paint16->bitsRestore(bitsHandle); - listEntry->showBitsFlag = true; + it->showBitsFlag = true; } else { _paint16->bitsFree(bitsHandle); } writeSelectorValue(_s->_segMan, curObject, SELECTOR(underBits), 0); } - signal &= 0xFFFF ^ kSignalForceUpdate; - signal &= signal & kSignalViewUpdated ? 0xFFFF ^ (kSignalViewUpdated | kSignalNoUpdate) : 0xFFFF; + signal &= ~kSignalForceUpdate; + if (signal & kSignalViewUpdated) + signal &= ~(kSignalViewUpdated | kSignalNoUpdate); } else if (signal & kSignalStopUpdate) { - signal = (signal & (0xFFFF ^ kSignalStopUpdate)) | kSignalNoUpdate; + signal &= ~kSignalStopUpdate; + signal |= kSignalNoUpdate; } - listEntry->signal = signal; - listIterator--; + it->signal = signal; } // Draw always-update cels - listIterator = listBegin; - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; + signal = it->signal; if (signal & kSignalAlwaysUpdate) { // draw corresponding cel - _paint16->drawCel(listEntry->viewId, listEntry->loopNo, listEntry->celNo, listEntry->celRect, listEntry->priority, listEntry->paletteNo, listEntry->scaleX, listEntry->scaleY); - listEntry->showBitsFlag = true; + _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); + it->showBitsFlag = true; - signal &= 0xFFFF ^ (kSignalStopUpdate | kSignalViewUpdated | kSignalNoUpdate | kSignalForceUpdate); + signal &= ~(kSignalStopUpdate | kSignalViewUpdated | kSignalNoUpdate | kSignalForceUpdate); if ((signal & kSignalIgnoreActor) == 0) { - rect = listEntry->celRect; - rect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(listEntry->priority) - 1, rect.top, rect.bottom - 1); + rect = it->celRect; + rect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(it->priority) - 1, rect.top, rect.bottom - 1); _paint16->fillRect(rect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); } - listEntry->signal = signal; + it->signal = signal; } - listIterator++; } // Saving background for all NoUpdate-cels - listIterator = listBegin; - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; + signal = it->signal; if (signal & kSignalNoUpdate) { if (signal & kSignalHidden) { signal |= kSignalRemoveView; } else { - signal &= 0xFFFF ^ kSignalRemoveView; + signal &= ~kSignalRemoveView; if (signal & kSignalIgnoreActor) - bitsHandle = _paint16->bitsSave(listEntry->celRect, GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY); + bitsHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY); else - bitsHandle = _paint16->bitsSave(listEntry->celRect, GFX_SCREEN_MASK_ALL); + bitsHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_ALL); writeSelector(_s->_segMan, curObject, SELECTOR(underBits), bitsHandle); } - listEntry->signal = signal; + it->signal = signal; } - listIterator++; } // Draw NoUpdate cels - listIterator = listBegin; - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; + signal = it->signal; if (signal & kSignalNoUpdate && !(signal & kSignalHidden)) { // draw corresponding cel - _paint16->drawCel(listEntry->viewId, listEntry->loopNo, listEntry->celNo, listEntry->celRect, listEntry->priority, listEntry->paletteNo, listEntry->scaleX, listEntry->scaleY); - listEntry->showBitsFlag = true; + _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); + it->showBitsFlag = true; - if ((signal & kSignalIgnoreActor) == 0) { - rect = listEntry->celRect; - rect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(listEntry->priority) - 1, rect.top, rect.bottom - 1); + if (!(signal & kSignalIgnoreActor)) { + rect = it->celRect; + rect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(it->priority) - 1, rect.top, rect.bottom - 1); _paint16->fillRect(rect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); } } - listIterator++; } } void GfxAnimate::drawCels() { reg_t curObject; - AnimateEntry *listEntry; - AnimateEntry *lastCastEntry = _lastCastData; uint16 signal; reg_t bitsHandle; - AnimateList::iterator listIterator; - AnimateList::iterator listEnd = _list.end(); - _lastCastCount = 0; + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); + _lastCastData.clear(); - listIterator = _list.begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; + signal = it->signal; if (!(signal & (kSignalNoUpdate | kSignalHidden | kSignalAlwaysUpdate))) { // Save background - bitsHandle = _paint16->bitsSave(listEntry->celRect, GFX_SCREEN_MASK_ALL); + bitsHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_ALL); writeSelector(_s->_segMan, curObject, SELECTOR(underBits), bitsHandle); // draw corresponding cel - _paint16->drawCel(listEntry->viewId, listEntry->loopNo, listEntry->celNo, listEntry->celRect, listEntry->priority, listEntry->paletteNo, listEntry->scaleX, listEntry->scaleY); - listEntry->showBitsFlag = true; + _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); + it->showBitsFlag = true; if (signal & kSignalRemoveView) { - signal &= 0xFFFF ^ kSignalRemoveView; + signal &= ~kSignalRemoveView; } - listEntry->signal = signal; + it->signal = signal; // Remember that entry in lastCast - memcpy(lastCastEntry, listEntry, sizeof(AnimateEntry)); - lastCastEntry++; _lastCastCount++; + _lastCastData.push_back(*it); } - listIterator++; } } void GfxAnimate::updateScreen(byte oldPicNotValid) { reg_t curObject; - AnimateEntry *listEntry; uint16 signal; - AnimateList::iterator listIterator; - AnimateList::iterator listEnd = _list.end(); + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); Common::Rect lsRect; Common::Rect workerRect; - listIterator = _list.begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; + signal = it->signal; - if (listEntry->showBitsFlag || !(signal & (kSignalRemoveView | kSignalNoUpdate) || + if (it->showBitsFlag || !(signal & (kSignalRemoveView | kSignalNoUpdate) || (!(signal & kSignalRemoveView) && (signal & kSignalNoUpdate) && oldPicNotValid))) { lsRect.left = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsLeft)); lsRect.top = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsTop)); @@ -438,27 +408,27 @@ void GfxAnimate::updateScreen(byte oldPicNotValid) { lsRect.bottom = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsBottom)); workerRect = lsRect; - workerRect.clip(listEntry->celRect); + workerRect.clip(it->celRect); if (!workerRect.isEmpty()) { workerRect = lsRect; - workerRect.extend(listEntry->celRect); + workerRect.extend(it->celRect); } else { _paint16->bitsShow(lsRect); - workerRect = listEntry->celRect; + workerRect = it->celRect; } - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsLeft), workerRect.left); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsTop), workerRect.top); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsRight), workerRect.right); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsBottom), workerRect.bottom); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsLeft), it->celRect.left); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsTop), it->celRect.top); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsRight), it->celRect.right); + writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsBottom), it->celRect.bottom); + // may get used for debugging + //_paint16->frameRect(workerRect); _paint16->bitsShow(workerRect); if (signal & kSignalHidden) { - listEntry->signal |= kSignalRemoveView; + it->signal |= kSignalRemoveView; } } - - listIterator++; } // use this for debug purposes // _screen->copyToScreen(); @@ -466,30 +436,26 @@ void GfxAnimate::updateScreen(byte oldPicNotValid) { void GfxAnimate::restoreAndDelete(int argc, reg_t *argv) { reg_t curObject; - AnimateEntry *listEntry; uint16 signal; - AnimateList::iterator listIterator; - AnimateList::iterator listEnd = _list.end(); + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); - // This has to be done in a separate loop. At least in sq1 some .dispose modifies FIXEDLOOP flag in signal for - // another object. In that case we would overwrite the new signal with our version of the old signal - listIterator = _list.begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - signal = listEntry->signal; + // This has to be done in a separate loop. At least in sq1 some .dispose + // modifies FIXEDLOOP flag in signal for another object. In that case we + // would overwrite the new signal with our version of the old signal. + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; + signal = it->signal; // Finally update signal writeSelectorValue(_s->_segMan, curObject, SELECTOR(signal), signal); - listIterator++; } - listIterator = _list.reverse_begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; - // We read out signal here again, this is not by accident but to ensure that we got an up-to-date signal + for (it = _list.reverse_begin(); it != end; --it) { + curObject = it->object; + // We read out signal here again, this is not by accident but to ensure + // that we got an up-to-date signal signal = readSelectorValue(_s->_segMan, curObject, SELECTOR(signal)); if ((signal & (kSignalNoUpdate | kSignalRemoveView)) == 0) { @@ -499,31 +465,24 @@ void GfxAnimate::restoreAndDelete(int argc, reg_t *argv) { if (signal & kSignalDisposeMe) { // Call .delete_ method of that object - invokeSelector(_s, curObject, g_sci->getKernel()->_selectorCache.delete_, kContinueOnInvalidSelector, argc, argv, 0); + invokeSelector(_s, curObject, SELECTOR(delete_), argc, argv, 0); } - listIterator--; } } void GfxAnimate::reAnimate(Common::Rect rect) { - AnimateEntry *lastCastEntry; - uint16 lastCastCount; - - if (_lastCastCount > 0) { - lastCastEntry = _lastCastData; - lastCastCount = _lastCastCount; - while (lastCastCount > 0) { - lastCastEntry->castHandle = _paint16->bitsSave(lastCastEntry->celRect, GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY); - _paint16->drawCel(lastCastEntry->viewId, lastCastEntry->loopNo, lastCastEntry->celNo, lastCastEntry->celRect, lastCastEntry->priority, lastCastEntry->paletteNo, lastCastEntry->scaleX, lastCastEntry->scaleY); - lastCastEntry++; lastCastCount--; + if (!_lastCastData.empty()) { + AnimateArray::iterator it; + AnimateArray::iterator end = _lastCastData.end(); + for (it = _lastCastData.begin(); it != end; ++it) { + it->castHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY); + _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); } _paint16->bitsShow(rect); // restoring - lastCastCount = _lastCastCount; - while (lastCastCount > 0) { - lastCastEntry--; - _paint16->bitsRestore(lastCastEntry->castHandle); - lastCastCount--; + while (it != _lastCastData.begin()) { // FIXME: HACK, this iterator use is not very safe + it--; + _paint16->bitsRestore(it->castHandle); } } else { _paint16->bitsShow(rect); @@ -532,33 +491,28 @@ void GfxAnimate::reAnimate(Common::Rect rect) { void GfxAnimate::addToPicDrawCels() { reg_t curObject; - AnimateEntry *listEntry; GfxView *view = NULL; - AnimateList::iterator listIterator; - AnimateList::iterator listEnd = _list.end(); + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); - listIterator = _list.begin(); - while (listIterator != listEnd) { - listEntry = *listIterator; - curObject = listEntry->object; + for (it = _list.begin(); it != end; ++it) { + curObject = it->object; - if (listEntry->priority == -1) - listEntry->priority = _ports->kernelCoordinateToPriority(listEntry->y); + if (it->priority == -1) + it->priority = _ports->kernelCoordinateToPriority(it->y); // Get the corresponding view - view = _cache->getView(listEntry->viewId); + view = _cache->getView(it->viewId); // Create rect according to coordinates and given cel - view->getCelRect(listEntry->loopNo, listEntry->celNo, listEntry->x, listEntry->y, listEntry->z, &listEntry->celRect); + view->getCelRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->celRect); // draw corresponding cel - _paint16->drawCel(listEntry->viewId, listEntry->loopNo, listEntry->celNo, listEntry->celRect, listEntry->priority, listEntry->paletteNo); - if ((listEntry->signal & kSignalIgnoreActor) == 0) { - listEntry->celRect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(listEntry->priority) - 1, listEntry->celRect.top, listEntry->celRect.bottom - 1); - _paint16->fillRect(listEntry->celRect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); + _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo); + if ((it->signal & kSignalIgnoreActor) == 0) { + it->celRect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(it->priority) - 1, it->celRect.top, it->celRect.bottom - 1); + _paint16->fillRect(it->celRect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); } - - listIterator++; } } @@ -567,7 +521,7 @@ void GfxAnimate::addToPicDrawView(GuiResourceId viewId, int16 loopNo, int16 celN Common::Rect celRect; // Create rect according to coordinates and given cel - view->getCelRect(loopNo, celNo, leftPos, topPos, priority, &celRect); + view->getCelRect(loopNo, celNo, leftPos, topPos, priority, celRect); _paint16->drawCel(view, loopNo, celNo, celRect, priority, 0); } @@ -592,6 +546,9 @@ void GfxAnimate::animateShowPic() { void GfxAnimate::kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t *argv) { byte old_picNotValid = _screen->_picNotValid; + if (getSciVersion() >= SCI_VERSION_1_1) + _palette->palVaryUpdate(); + if (listReference.isNull()) { disposeLastCast(); if (_screen->_picNotValid) @@ -606,6 +563,9 @@ void GfxAnimate::kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t if (cycle) { if (!invoke(list, argc, argv)) return; + + // Look up the list again, as it may have been modified + list = _s->_segMan->lookupList(listReference); } Port *oldPort = _ports->setPort((Port *)_ports->_picWind); @@ -615,9 +575,9 @@ void GfxAnimate::kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t fill(old_picNotValid); if (old_picNotValid) { - // beginUpdate()/endUpdate() were introduced SCI1 - // calling those for SCI0 will work most of the time but breaks minor stuff like percentage bar of qfg1ega - // at the character skill screen + // beginUpdate()/endUpdate() were introduced SCI1. + // Calling those for SCI0 will work most of the time but breaks minor + // stuff like percentage bar of qfg1ega at the character skill screen. if (getSciVersion() >= SCI_VERSION_1_EGA) _ports->beginUpdate(_ports->_picWind); update(); @@ -633,7 +593,7 @@ void GfxAnimate::kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t updateScreen(old_picNotValid); restoreAndDelete(argc, argv); - if (getLastCastCount() > 1) + if (_lastCastData.size() > 1) _s->_throttleTrigger = true; _ports->setPort(oldPort); diff --git a/engines/sci/graphics/animate.h b/engines/sci/graphics/animate.h index 706b7182cf..7e82187eed 100644 --- a/engines/sci/graphics/animate.h +++ b/engines/sci/graphics/animate.h @@ -51,9 +51,9 @@ enum ViewSignals { }; enum ViewScaleSignals { - kScaleSignalDoScaling = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY) - kScaleSignalUnknown1 = 0x0002, // seems to do something with globalvar 2, sets scaleX/scaleY - kScaleSignalUnknown2 = 0x0004 // really unknown + kScaleSignalDoScaling = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY) + kScaleSignalGlobalScaling = 0x0002, // means that global scaling shall get applied on that cel (sets scaleX/scaleY) + kScaleSignalUnknown2 = 0x0004 // really unknown }; struct AnimateEntry { @@ -73,7 +73,8 @@ struct AnimateEntry { bool showBitsFlag; reg_t castHandle; }; -typedef Common::List<AnimateEntry *> AnimateList; +typedef Common::List<AnimateEntry> AnimateList; +typedef Common::Array<AnimateEntry> AnimateArray; class GfxCache; class GfxCursor; @@ -90,9 +91,6 @@ public: GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions); virtual ~GfxAnimate(); - // FIXME: Don't store EngineState - void resetEngineState(EngineState *newState) { _s = newState; } - void disposeLastCast(); bool invoke(List *list, int argc, reg_t *argv); void makeSortedList(List *list); @@ -105,8 +103,6 @@ public: void addToPicDrawCels(); void addToPicDrawView(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 leftPos, int16 topPos, int16 priority, int16 control); - uint16 getLastCastCount() { return _lastCastCount; } - virtual void kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t *argv); virtual void kernelAddToPicList(reg_t listReference, int argc, reg_t *argv); virtual void kernelAddToPicView(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 leftPos, int16 topPos, int16 priority, int16 control); @@ -126,12 +122,8 @@ private: GfxCursor *_cursor; GfxTransitions *_transitions; - uint16 _listCount; - AnimateEntry *_listData; AnimateList _list; - - uint16 _lastCastCount; - AnimateEntry *_lastCastData; + AnimateArray _lastCastData; bool _ignoreFastCast; }; diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp index 81bdab80ea..8caa28b3a1 100644 --- a/engines/sci/graphics/cache.cpp +++ b/engines/sci/graphics/cache.cpp @@ -102,7 +102,7 @@ int16 GfxCache::kernelViewGetLoopCount(GuiResourceId viewId) { } int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) { - return getView(viewId)->getLoopInfo(loopNo)->celCount; + return getView(viewId)->getCelCount(loopNo); } } // End of namespace Sci diff --git a/engines/sci/graphics/cache.h b/engines/sci/graphics/cache.h index 16ab1916d4..2e9a345230 100644 --- a/engines/sci/graphics/cache.h +++ b/engines/sci/graphics/cache.h @@ -26,8 +26,6 @@ #ifndef SCI_GRAPHICS_CACHE_H #define SCI_GRAPHICS_CACHE_H -#include "sci/graphics/gui.h" - #include "common/hashmap.h" namespace Sci { diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp index 3102edc2fa..31c2b210ce 100644 --- a/engines/sci/graphics/compare.cpp +++ b/engines/sci/graphics/compare.cpp @@ -28,6 +28,7 @@ #include "graphics/primitives.h" #include "sci/sci.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/graphics/compare.h" @@ -126,14 +127,22 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) { int16 x = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(x)); int16 y = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(y)); int16 z = 0; - if (_kernel->_selectorCache.z > -1) + if (SELECTOR(z) > -1) z = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(z)); - // now get cel rectangle view = _cache->getView(viewId); - view->getCelRect(loopNo, celNo, x, y, z, &celRect); - if (lookupSelector(_segMan, objectReference, _kernel->_selectorCache.nsTop, NULL, NULL) == kSelectorVariable) { + if (view->isSci2Hires()) + _screen->adjustToUpscaledCoordinates(y, x); + + view->getCelRect(loopNo, celNo, x, y, z, celRect); + + if (view->isSci2Hires()) { + _screen->adjustBackUpscaledCoordinates(celRect.top, celRect.left); + _screen->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right); + } + + if (lookupSelector(_segMan, objectReference, SELECTOR(nsTop), NULL, NULL) == kSelectorVariable) { writeSelectorValue(_segMan, objectReference, SELECTOR(nsLeft), celRect.left); writeSelectorValue(_segMan, objectReference, SELECTOR(nsRight), celRect.right); writeSelectorValue(_segMan, objectReference, SELECTOR(nsTop), celRect.top); @@ -174,19 +183,19 @@ bool GfxCompare::kernelCanBeHere(reg_t curObject, reg_t listReference) { bool GfxCompare::kernelIsItSkip(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Point position) { GfxView *tmpView = _cache->getView(viewId); - CelInfo *celInfo = tmpView->getCelInfo(loopNo, celNo); + const CelInfo *celInfo = tmpView->getCelInfo(loopNo, celNo); position.x = CLIP<int>(position.x, 0, celInfo->width - 1); position.y = CLIP<int>(position.y, 0, celInfo->height - 1); - byte *celData = tmpView->getBitmap(loopNo, celNo); + const byte *celData = tmpView->getBitmap(loopNo, celNo); bool result = (celData[position.y * celInfo->width + position.x] == celInfo->clearKey); return result; } void GfxCompare::kernelBaseSetter(reg_t object) { - if (lookupSelector(_segMan, object, _kernel->_selectorCache.brLeft, NULL, NULL) == kSelectorVariable) { + if (lookupSelector(_segMan, object, SELECTOR(brLeft), NULL, NULL) == kSelectorVariable) { int16 x = readSelectorValue(_segMan, object, SELECTOR(x)); int16 y = readSelectorValue(_segMan, object, SELECTOR(y)); - int16 z = (_kernel->_selectorCache.z > -1) ? readSelectorValue(_segMan, object, SELECTOR(z)) : 0; + int16 z = (SELECTOR(z) > -1) ? readSelectorValue(_segMan, object, SELECTOR(z)) : 0; int16 yStep = readSelectorValue(_segMan, object, SELECTOR(yStep)); GuiResourceId viewId = readSelectorValue(_segMan, object, SELECTOR(view)); int16 loopNo = readSelectorValue(_segMan, object, SELECTOR(loop)); @@ -199,7 +208,16 @@ void GfxCompare::kernelBaseSetter(reg_t object) { GfxView *tmpView = _cache->getView(viewId); Common::Rect celRect; - tmpView->getCelRect(loopNo, celNo, x, y, z, &celRect); + if (tmpView->isSci2Hires()) + _screen->adjustToUpscaledCoordinates(y, x); + + tmpView->getCelRect(loopNo, celNo, x, y, z, celRect); + + if (tmpView->isSci2Hires()) { + _screen->adjustBackUpscaledCoordinates(celRect.top, celRect.left); + _screen->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right); + } + celRect.bottom = y + 1; celRect.top = celRect.bottom - yStep; diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h index 348d5ef723..be461cdc5b 100644 --- a/engines/sci/graphics/compare.h +++ b/engines/sci/graphics/compare.h @@ -26,8 +26,6 @@ #ifndef SCI_GRAPHICS_GFX_H #define SCI_GRAPHICS_GFX_H -#include "sci/graphics/gui.h" - #include "common/hashmap.h" namespace Sci { diff --git a/engines/sci/graphics/controls.cpp b/engines/sci/graphics/controls.cpp index 26af9741c2..ff5a91eed4 100644 --- a/engines/sci/graphics/controls.cpp +++ b/engines/sci/graphics/controls.cpp @@ -30,6 +30,7 @@ #include "sci/sci.h" #include "sci/event.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/graphics/ports.h" diff --git a/engines/sci/graphics/coordadjuster.cpp b/engines/sci/graphics/coordadjuster.cpp index 422df52f27..9481a68f13 100644 --- a/engines/sci/graphics/coordadjuster.cpp +++ b/engines/sci/graphics/coordadjuster.cpp @@ -26,6 +26,7 @@ #include "common/util.h" #include "sci/sci.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/graphics/coordadjuster.h" diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index 2f8393f9ac..e1c05c97da 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -43,18 +43,18 @@ GfxCursor::GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *sc : _resMan(resMan), _palette(palette), _screen(screen) { _upscaledHires = _screen->getUpscaledHires(); + _isVisible = true; + // center mouse cursor setPosition(Common::Point(_screen->getWidth() / 2, _screen->getHeight() / 2)); kernelSetMoveZone(Common::Rect(0, 0, _screen->getDisplayWidth(), _screen->getDisplayHeight())); - - _isVisible = true; } GfxCursor::~GfxCursor() { purgeCache(); } -void GfxCursor::init(GfxCoordAdjuster *coordAdjuster, SciEvent *event) { +void GfxCursor::init(GfxCoordAdjuster *coordAdjuster, EventManager *event) { _coordAdjuster = coordAdjuster; _event = event; } @@ -120,7 +120,7 @@ void GfxCursor::kernelSetShape(GuiResourceId resourceId) { colorMapping[0] = 0; // Black is hardcoded colorMapping[1] = _screen->getColorWhite(); // White is also hardcoded colorMapping[2] = SCI_CURSOR_SCI0_TRANSPARENCYCOLOR; - colorMapping[3] = _palette->matchColor(&_palette->_sysPalette, 170, 170, 170); // Grey + colorMapping[3] = _palette->matchColor(170, 170, 170); // Grey // Seek to actual data resourceData += 4; @@ -164,42 +164,39 @@ void GfxCursor::kernelSetView(GuiResourceId viewNum, int loopNum, int celNum, Co GfxView *cursorView = _cachedCursors[viewNum]; - CelInfo *celInfo = cursorView->getCelInfo(loopNum, celNum); + const CelInfo *celInfo = cursorView->getCelInfo(loopNum, celNum); int16 width = celInfo->width; int16 height = celInfo->height; byte clearKey = celInfo->clearKey; Common::Point *cursorHotspot = hotspot; - byte *cursorBitmap; if (!cursorHotspot) // Compute hotspot from xoffset/yoffset cursorHotspot = new Common::Point((celInfo->width >> 1) - celInfo->displaceX, celInfo->height - celInfo->displaceY - 1); - // Eco Quest 1 uses a 1x1 transparent cursor to hide the cursor from the user. Some scalers don't seem to support this + // Eco Quest 1 uses a 1x1 transparent cursor to hide the cursor from the + // user. Some scalers don't seem to support this if (width < 2 || height < 2) { kernelHide(); delete cursorHotspot; return; } - celInfo->rawBitmap = cursorView->getBitmap(loopNum, celNum); + const byte *rawBitmap = cursorView->getBitmap(loopNum, celNum); if (_upscaledHires) { // Scale cursor by 2x - note: sierra didn't do this, but it looks much better width *= 2; height *= 2; cursorHotspot->x *= 2; cursorHotspot->y *= 2; - cursorBitmap = new byte[width * height]; - _screen->scale2x(celInfo->rawBitmap, cursorBitmap, celInfo->width, celInfo->height); + byte *cursorBitmap = new byte[width * height]; + _screen->scale2x(rawBitmap, cursorBitmap, celInfo->width, celInfo->height); + CursorMan.replaceCursor(cursorBitmap, width, height, cursorHotspot->x, cursorHotspot->y, clearKey); + delete[] cursorBitmap; } else { - cursorBitmap = celInfo->rawBitmap; + CursorMan.replaceCursor(rawBitmap, width, height, cursorHotspot->x, cursorHotspot->y, clearKey); } - CursorMan.replaceCursor(cursorBitmap, width, height, cursorHotspot->x, cursorHotspot->y, clearKey); - - if (_upscaledHires) - delete[] cursorBitmap; - kernelShow(); delete cursorHotspot; @@ -209,7 +206,7 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu // See http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-402.html // for more information. - // View 998 seems to be a fake resource used to call for for the Mac CURS resources + // View 998 seems to be a fake resource used to call for the Mac CURS resources. // For other resources, they're still in the views, so use them. if (viewNum != 998) { kernelSetView(viewNum, loopNum, celNum, hotspot); @@ -219,6 +216,7 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu // TODO: What about the 2000 resources? Inventory items? How to handle? // TODO: What games does this work for? At least it does for KQ6. // TODO: Stop asking rhetorical questions. + // TODO: It was fred all along! Resource *resource = _resMan->findResource(ResourceId(kResourceTypeCursor, 1000 + celNum), false); @@ -258,6 +256,15 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu } void GfxCursor::setPosition(Common::Point pos) { + // Don't set position, when cursor is not visible. + // This fixes eco quest 1 (floppy) right at the start, which is setting + // mouse cursor to (0,0) all the time during the intro. It's escapeable + // (now) by moving to the left or top, but it's getting on your nerves. This + // could theoretically break some things, although sierra normally sets + // position only when showing the cursor. + if (!_isVisible) + return; + if (!_upscaledHires) { g_system->warpMouse(pos.x, pos.y); } else { @@ -269,21 +276,8 @@ void GfxCursor::setPosition(Common::Point pos) { Common::Point GfxCursor::getPosition() { Common::Point mousePos = g_system->getEventManager()->getMousePos(); - switch (_upscaledHires) { - case GFX_SCREEN_UPSCALED_640x400: - mousePos.x /= 2; - mousePos.y /= 2; - break; - case GFX_SCREEN_UPSCALED_640x440: - mousePos.x /= 2; - mousePos.y = (mousePos.y * 5) / 11; - break; - case GFX_SCREEN_UPSCALED_640x480: - mousePos.x /= 2; - mousePos.y = (mousePos.y * 5) / 12; - default: - break; - } + if (_upscaledHires) + _screen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); return mousePos; } @@ -333,7 +327,7 @@ void GfxCursor::kernelMoveCursor(Common::Point pos) { // Trigger event reading to make sure the mouse coordinates will // actually have changed the next time we read them. - _event->get(SCI_EVENT_PEEK); + _event->getSciEvent(SCI_EVENT_PEEK); } } // End of namespace Sci diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h index 6d92b3cf5f..7acd14acd9 100644 --- a/engines/sci/graphics/cursor.h +++ b/engines/sci/graphics/cursor.h @@ -45,7 +45,7 @@ public: GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *screen); ~GfxCursor(); - void init(GfxCoordAdjuster *coordAdjuster, SciEvent *event); + void init(GfxCoordAdjuster *coordAdjuster, EventManager *event); void kernelShow(); void kernelHide(); @@ -74,7 +74,7 @@ private: GfxScreen *_screen; GfxPalette *_palette; GfxCoordAdjuster *_coordAdjuster; - SciEvent *_event; + EventManager *_event; int _upscaledHires; diff --git a/engines/sci/graphics/font.cpp b/engines/sci/graphics/font.cpp index 91cf01c912..852771d081 100644 --- a/engines/sci/graphics/font.cpp +++ b/engines/sci/graphics/font.cpp @@ -82,17 +82,18 @@ void GfxFontFromResource::draw(uint16 chr, int16 top, int16 left, byte color, bo int charWidth = MIN<int>(getCharWidth(chr), _screen->getWidth() - left); int charHeight = MIN<int>(getCharHeight(chr), _screen->getHeight() - top); byte b = 0, mask = 0xFF; - int y = top; + int y = 0; + int16 greyedTop = top; byte *pIn = getCharData(chr); for (int i = 0; i < charHeight; i++, y++) { if (greyedOutput) - mask = top++ % 2 ? 0xAA : 0x55; + mask = greyedTop++ % 2 ? 0xAA : 0x55; for (int done = 0; done < charWidth; done++) { if ((done & 7) == 0) // fetching next data byte b = *(pIn++) & mask; if (b & 0x80) // if MSB is set - paint it - _screen->putPixel(left + done, y, 1, color, 0, 0); + _screen->putFontPixel(top, left + done, y, color); b = b << 1; } } diff --git a/engines/sci/graphics/fontsjis.h b/engines/sci/graphics/fontsjis.h index 24c8423ddb..684e6cac5e 100644 --- a/engines/sci/graphics/fontsjis.h +++ b/engines/sci/graphics/fontsjis.h @@ -26,10 +26,12 @@ #ifndef SCI_GRAPHICS_FONTSJIS_H #define SCI_GRAPHICS_FONTSJIS_H -#include "graphics/sjis.h" - #include "sci/graphics/helpers.h" +namespace Graphics { + class FontSJIS; +} + namespace Sci { /** diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 3cc5ca5447..9ce4474ee3 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -28,6 +28,7 @@ #include "graphics/primitives.h" #include "sci/sci.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/engine/vm.h" @@ -37,6 +38,7 @@ #include "sci/graphics/view.h" #include "sci/graphics/screen.h" #include "sci/graphics/paint32.h" +#include "sci/graphics/palette.h" #include "sci/graphics/picture.h" #include "sci/graphics/frameout.h" @@ -46,7 +48,6 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32) { _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; - _highPlanePri = 0; } GfxFrameout::~GfxFrameout() { @@ -54,30 +55,20 @@ GfxFrameout::~GfxFrameout() { void GfxFrameout::kernelAddPlane(reg_t object) { _planes.push_back(object); - int16 planePri = readSelectorValue(_segMan, object, SELECTOR(priority)) & 0xFFFF; - if (planePri > _highPlanePri) - _highPlanePri = planePri; + sortPlanes(); } void GfxFrameout::kernelUpdatePlane(reg_t object) { + sortPlanes(); } void GfxFrameout::kernelDeletePlane(reg_t object) { - for (uint32 planeNr = 0; planeNr < _planes.size(); planeNr++) { - if (_planes[planeNr] == object) { - _planes.remove_at(planeNr); - break; + for (Common::List<reg_t>::iterator it = _planes.begin(); it != _planes.end(); it++) { + if (object == *it) { + _planes.erase(it); + return; } } - - // Recalculate highPlanePri - _highPlanePri = 0; - - for (uint32 planeNr = 0; planeNr < _planes.size(); planeNr++) { - int16 planePri = readSelectorValue(_segMan, _planes[planeNr], SELECTOR(priority)) & 0xFFFF; - if (planePri > _highPlanePri) - _highPlanePri = planePri; - } } void GfxFrameout::kernelAddScreenItem(reg_t object) { @@ -95,73 +86,102 @@ void GfxFrameout::kernelDeleteScreenItem(reg_t object) { } int16 GfxFrameout::kernelGetHighPlanePri() { - return _highPlanePri; + sortPlanes(); + return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back(), SELECTOR(priority)); } bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) { return (entry1->priority == entry2->priority) ? (entry1->y < entry2->y) : (entry1->priority < entry2->priority); } +bool planeSortHelper(const reg_t entry1, const reg_t entry2) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + + uint16 plane1Priority = readSelectorValue(segMan, entry1, SELECTOR(priority)); + uint16 plane2Priority = readSelectorValue(segMan, entry2, SELECTOR(priority)); + + if (plane1Priority == 0xffff) + return true; + + if (plane2Priority == 0xffff) + return false; + + return plane1Priority < plane2Priority; +} + +void GfxFrameout::sortPlanes() { + // First, remove any invalid planes + for (Common::List<reg_t>::iterator it = _planes.begin(); it != _planes.end();) { + if (!_segMan->isObject(*it)) + it = _planes.erase(it); + else + it++; + } + + // Sort the rest of them + Common::sort(_planes.begin(), _planes.end(), planeSortHelper); +} + void GfxFrameout::kernelFrameout() { - int16 itemCount = 0; - reg_t planeObject; - GuiResourceId planePictureNr; - GfxPicture *planePicture = 0; - int16 planePictureCels = 0; - int16 planePictureCel; - int16 planePriority; - Common::Rect planeRect; - int16 planeResY, planeResX; - byte planeBack; - - reg_t itemObject; - reg_t itemPlane; - - FrameoutEntry *itemData; - FrameoutList itemList; - FrameoutEntry *itemEntry; + _palette->palVaryUpdate(); // Allocate enough space for all screen items - itemData = (FrameoutEntry *)malloc(_screenItems.size() * sizeof(FrameoutEntry)); + FrameoutEntry *itemData = (FrameoutEntry *)malloc(_screenItems.size() * sizeof(FrameoutEntry)); + + const SciGameId gameId = g_sci->getGameId(); - for (uint32 planeNr = 0; planeNr < _planes.size(); planeNr++) { - planeObject = _planes[planeNr]; - planePriority = readSelectorValue(_segMan, planeObject, SELECTOR(priority)); + for (Common::List<reg_t>::iterator it = _planes.begin(); it != _planes.end(); it++) { + reg_t planeObject = *it; + uint16 planePriority = readSelectorValue(_segMan, planeObject, SELECTOR(priority)); - if (planePriority == -1) // Plane currently not meant to be shown + if (planePriority == 0xffff) // Plane currently not meant to be shown continue; + Common::Rect planeRect; planeRect.top = readSelectorValue(_segMan, planeObject, SELECTOR(top)); planeRect.left = readSelectorValue(_segMan, planeObject, SELECTOR(left)); planeRect.bottom = readSelectorValue(_segMan, planeObject, SELECTOR(bottom)); planeRect.right = readSelectorValue(_segMan, planeObject, SELECTOR(right)); - planeResY = readSelectorValue(_segMan, planeObject, SELECTOR(resY)); - planeResX = readSelectorValue(_segMan, planeObject, SELECTOR(resX)); + int16 planeResY = readSelectorValue(_segMan, planeObject, SELECTOR(resY)); + int16 planeResX = readSelectorValue(_segMan, planeObject, SELECTOR(resX)); planeRect.top = (planeRect.top * _screen->getHeight()) / planeResY; planeRect.left = (planeRect.left * _screen->getWidth()) / planeResX; planeRect.bottom = (planeRect.bottom * _screen->getHeight()) / planeResY; planeRect.right = (planeRect.right * _screen->getWidth()) / planeResX; - planeBack = readSelectorValue(_segMan, planeObject, SELECTOR(back)); - if (planeBack) { + byte planeBack = readSelectorValue(_segMan, planeObject, SELECTOR(back)); + if (planeBack) _paint32->fillRect(planeRect, planeBack); - } - planePictureNr = readSelectorValue(_segMan, planeObject, SELECTOR(picture)); + GuiResourceId planePictureNr = readSelectorValue(_segMan, planeObject, SELECTOR(picture)); + GfxPicture *planePicture = 0; + int16 planePictureCels = 0; + if ((planePictureNr != 0xFFFF) && (planePictureNr != 0xFFFE)) { planePicture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, planePictureNr, false); planePictureCels = planePicture->getSci32celCount(); _coordAdjuster->pictureSetDisplayArea(planeRect); + _palette->drewPicture(planePictureNr); } // Fill our itemlist for this plane - itemCount = 0; - itemEntry = itemData; + int16 itemCount = 0; + FrameoutEntry *itemEntry = itemData; + FrameoutList itemList; + for (uint32 itemNr = 0; itemNr < _screenItems.size(); itemNr++) { - itemObject = _screenItems[itemNr]; - itemPlane = readSelector(_segMan, itemObject, SELECTOR(plane)); + reg_t itemObject = _screenItems[itemNr]; + + // Remove any invalid items + if (!_segMan->isObject(itemObject)) { + _screenItems.remove_at(itemNr); + itemNr--; + continue; + } + + reg_t itemPlane = readSelector(_segMan, itemObject, SELECTOR(plane)); if (planeObject == itemPlane) { // Found an item on current plane itemEntry->viewId = readSelectorValue(_segMan, itemObject, SELECTOR(view)); @@ -171,6 +191,14 @@ void GfxFrameout::kernelFrameout() { itemEntry->y = readSelectorValue(_segMan, itemObject, SELECTOR(y)); itemEntry->z = readSelectorValue(_segMan, itemObject, SELECTOR(z)); itemEntry->priority = readSelectorValue(_segMan, itemObject, SELECTOR(priority)); + if (gameId == GID_GK1) { + if ((itemEntry->viewId == 11000) && (itemEntry->loopNo == 0) && (itemEntry->celNo == 0) && (itemEntry->priority == 1)) { + itemEntry->priority = 0; // HACK for gk1 hires main menu + } + if ((itemEntry->viewId == 10100) && (itemEntry->priority == 0)) { + itemEntry->priority = 1; // HACK for gk1 hires main menu + } + } itemEntry->signal = readSelectorValue(_segMan, itemObject, SELECTOR(signal)); itemEntry->scaleX = readSelectorValue(_segMan, itemObject, SELECTOR(scaleX)); itemEntry->scaleY = readSelectorValue(_segMan, itemObject, SELECTOR(scaleY)); @@ -181,8 +209,9 @@ void GfxFrameout::kernelFrameout() { itemEntry->y += planeRect.top; itemEntry->x += planeRect.left; - if (itemEntry->priority == 0) - itemEntry->priority = itemEntry->y; + if (!(itemEntry->signal & 0x0010)) { // kSignalFixedPriority + // TODO: Change priority of this item + } itemList.push_back(itemEntry); itemEntry++; @@ -194,12 +223,10 @@ void GfxFrameout::kernelFrameout() { Common::sort(itemList.begin(), itemList.end(), sortHelper); // Now display itemlist - planePictureCel = 0; - + int16 planePictureCel = 0; itemEntry = itemData; - FrameoutList::iterator listIterator = itemList.begin(); - FrameoutList::iterator listEnd = itemList.end(); - while (listIterator != listEnd) { + + for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) { itemEntry = *listIterator; if (planePicture) { while ((planePictureCel <= itemEntry->priority) && (planePictureCel < planePictureCels)) { @@ -207,63 +234,72 @@ void GfxFrameout::kernelFrameout() { planePictureCel++; } } + if (itemEntry->viewId != 0xFFFF) { GfxView *view = _cache->getView(itemEntry->viewId); - 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 (view->isSci2Hires()) + _screen->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x); - if (itemEntry->celRect.top < 0 || itemEntry->celRect.top >= _screen->getHeight()) { - listIterator++; - continue; + 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); + + int16 screenHeight = _screen->getHeight(); + int16 screenWidth = _screen->getWidth(); + if (view->isSci2Hires()) { + screenHeight = _screen->getDisplayHeight(); + screenWidth = _screen->getDisplayWidth(); } - if (itemEntry->celRect.left < 0 || itemEntry->celRect.left >= _screen->getWidth()) { - listIterator++; + if (itemEntry->celRect.top < 0 || itemEntry->celRect.top >= screenHeight) + continue; + + if (itemEntry->celRect.left < 0 || itemEntry->celRect.left >= screenWidth) continue; - } Common::Rect clipRect; clipRect = itemEntry->celRect; - clipRect.clip(planeRect); + if (view->isSci2Hires()) { + Common::Rect upscaledPlaneRect = planeRect; + _screen->adjustToUpscaledCoordinates(upscaledPlaneRect.top, upscaledPlaneRect.left); + _screen->adjustToUpscaledCoordinates(upscaledPlaneRect.bottom, upscaledPlaneRect.right); + clipRect.clip(upscaledPlaneRect); + } else { + clipRect.clip(planeRect); + } if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->draw(itemEntry->celRect, clipRect, clipRect, itemEntry->loopNo, itemEntry->celNo, 255, 0, false); + view->draw(itemEntry->celRect, clipRect, clipRect, itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires()); else view->drawScaled(itemEntry->celRect, clipRect, clipRect, itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY); } else { // Most likely a text entry // This draws text the "SCI0-SCI11" way. In SCI2, text is prerendered in kCreateTextBitmap // TODO: rewrite this the "SCI2" way (i.e. implement the text buffer to draw inside kCreateTextBitmap) - // This doesn't work for SCI2.1 games... - if (getSciVersion() == SCI_VERSION_2) { - Kernel *kernel = g_sci->getKernel(); - if (lookupSelector(_segMan, itemEntry->object, kernel->_selectorCache.text, NULL, NULL) == kSelectorVariable) { - Common::String text = _segMan->getString(readSelector(_segMan, itemEntry->object, SELECTOR(text))); - int16 fontRes = readSelectorValue(_segMan, itemEntry->object, SELECTOR(font)); - GfxFont *font = new GfxFontFromResource(_resMan, _screen, fontRes); - bool dimmed = readSelectorValue(_segMan, itemEntry->object, SELECTOR(dimmed)); - uint16 foreColor = readSelectorValue(_segMan, itemEntry->object, SELECTOR(fore)); - uint16 curX = itemEntry->x; - uint16 curY = itemEntry->y; - for (uint32 i = 0; i < text.size(); i++) { - // TODO: proper text splitting... this is a hack - if ((text[i] == ' ' && i > 0 && text[i - i] == ' ') || text[i] == '\n' || - (curX + font->getCharWidth(text[i]) > _screen->getWidth())) { - curY += font->getHeight(); - curX = itemEntry->x; - } - font->draw(text[i], curY, curX, foreColor, dimmed); - curX += font->getCharWidth(text[i]); + if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) { + Common::String text = _segMan->getString(readSelector(_segMan, itemEntry->object, SELECTOR(text))); + GfxFont *font = _cache->getFont(readSelectorValue(_segMan, itemEntry->object, SELECTOR(font))); + bool dimmed = readSelectorValue(_segMan, itemEntry->object, SELECTOR(dimmed)); + uint16 foreColor = readSelectorValue(_segMan, itemEntry->object, SELECTOR(fore)); + uint16 curX = itemEntry->x; + uint16 curY = itemEntry->y; + for (uint32 i = 0; i < text.size(); i++) { + unsigned char curChar = text[i]; + // TODO: proper text splitting... this is a hack + if ((curChar == ' ' && i > 0 && text[i - i] == ' ') || curChar == '\n' || + (curX + font->getCharWidth(curChar) > _screen->getWidth())) { + curY += font->getHeight(); + curX = itemEntry->x; } - delete font; + font->draw(curChar, curY, curX, foreColor, dimmed); + curX += font->getCharWidth(curChar); } } } - listIterator++; } + if (planePicture) { while (planePictureCel < planePictureCels) { planePicture->drawSci32Vga(planePictureCel); @@ -273,6 +309,7 @@ void GfxFrameout::kernelFrameout() { planePicture = 0; } } + free(itemData); _screen->copyToScreen(); } diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 36c02af278..e4568ad602 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -41,10 +41,14 @@ struct FrameoutEntry { int16 scaleY; Common::Rect celRect; }; + typedef Common::List<FrameoutEntry *> FrameoutList; class GfxCache; +class GfxCoordAdjuster32; class GfxPaint32; +class GfxPalette; +class GfxScreen; /** * Frameout class, kFrameout and relevant functions for SCI32 games */ @@ -71,8 +75,9 @@ private: GfxPaint32 *_paint32; Common::Array<reg_t> _screenItems; - Common::Array<reg_t> _planes; - int16 _highPlanePri; + Common::List<reg_t> _planes; + + void sortPlanes(); }; } // End of namespace Sci diff --git a/engines/sci/graphics/gui.cpp b/engines/sci/graphics/gui.cpp deleted file mode 100644 index e427edd732..0000000000 --- a/engines/sci/graphics/gui.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#include "common/timer.h" -#include "common/util.h" - -#include "sci/sci.h" -#include "sci/debug.h" // for g_debug_sleeptime_factor -#include "sci/event.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/gui.h" -#include "sci/graphics/screen.h" -#include "sci/graphics/palette.h" -#include "sci/graphics/cursor.h" -#include "sci/graphics/ports.h" -#include "sci/graphics/paint16.h" -#include "sci/graphics/cache.h" -#include "sci/graphics/compare.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/animate.h" -#include "sci/graphics/controls.h" -#include "sci/graphics/menu.h" -#include "sci/graphics/portrait.h" -#include "sci/graphics/text16.h" -#include "sci/graphics/transitions.h" -#include "sci/graphics/view.h" -#include "sci/sound/audio.h" - -namespace Sci { - -SciGui::SciGui(EngineState *state, GfxScreen *screen, GfxPalette *palette, GfxCache *cache, GfxCursor *cursor, GfxPorts *ports, AudioPlayer *audio) - : _s(state), _screen(screen), _palette(palette), _cache(cache), _cursor(cursor), _ports(ports), _audio(audio) { - - // FIXME/TODO: If SciGui inits all the stuff below, then it should *own* it, - // not SciEngine. Conversely, if we want SciEngine to own this stuff, - // then it should init it! - _coordAdjuster = new GfxCoordAdjuster16(_ports); - g_sci->_gfxCoordAdjuster = _coordAdjuster; - _cursor->init(_coordAdjuster, _s->_event); - _compare = new GfxCompare(_s->_segMan, g_sci->getKernel(), _cache, _screen, _coordAdjuster); - g_sci->_gfxCompare = _compare; - _transitions = new GfxTransitions(this, _screen, _palette, g_sci->getResMan()->isVGA()); - _paint16 = new GfxPaint16(g_sci->getResMan(), _s->_segMan, g_sci->getKernel(), this, _cache, _ports, _coordAdjuster, _screen, _palette, _transitions); - g_sci->_gfxPaint = _paint16; - g_sci->_gfxPaint16 = _paint16; - _animate = new GfxAnimate(_s, _cache, _ports, _paint16, _screen, _palette, _cursor, _transitions); - g_sci->_gfxAnimate = _animate; - _text16 = new GfxText16(g_sci->getResMan(), _cache, _ports, _paint16, _screen); - _controls = new GfxControls(_s->_segMan, _ports, _paint16, _text16, _screen); - g_sci->_gfxControls = _controls; - _menu = new GfxMenu(_s->_event, _s->_segMan, this, _ports, _paint16, _text16, _screen, _cursor); - g_sci->_gfxMenu = _menu; -} - -SciGui::~SciGui() { - delete _menu; - delete _controls; - delete _text16; - delete _animate; - delete _paint16; - delete _transitions; - delete _compare; - delete _coordAdjuster; -} - -void SciGui::resetEngineState(EngineState *s) { - _s = s; - _animate->resetEngineState(s); -} - -void SciGui::init(bool usesOldGfxFunctions) { - _ports->init(usesOldGfxFunctions, this, _paint16, _text16); - _paint16->init(_animate, _text16); -} - -void SciGui::wait(int16 ticks) { - _s->wait(ticks); -} - -void SciGui::textSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { - Common::Rect rect(0, 0, *textWidth, *textHeight); - _text16->Size(rect, text, font, maxWidth); - *textWidth = rect.width(); - *textHeight = rect.height(); -} - -// Used SCI1+ for text codes -void SciGui::textFonts(int argc, reg_t *argv) { - _text16->CodeSetFonts(argc, argv); -} - -// Used SCI1+ for text codes -void SciGui::textColors(int argc, reg_t *argv) { - _text16->CodeSetColors(argc, argv); -} - -reg_t SciGui::portraitLoad(Common::String resourceName) { - //Portrait *myPortrait = new Portrait(g_sci->getResMan(), _screen, _palette, resourceName); - return NULL_REG; -} - -void SciGui::portraitShow(Common::String resourceName, Common::Point position, uint16 resourceId, uint16 noun, uint16 verb, uint16 cond, uint16 seq) { - Portrait *myPortrait = new Portrait(g_sci->getResMan(), _s->_event, this, _screen, _palette, _audio, resourceName); - // TODO: cache portraits - // adjust given coordinates to curPort (but dont adjust coordinates on upscaledHires_Save_Box and give us hires coordinates - // on kDrawCel, yeah this whole stuff makes sense) - position.x += _ports->getPort()->left; position.y += _ports->getPort()->top; - _screen->adjustToUpscaledCoordinates(position.y, position.x); - myPortrait->doit(position, resourceId, noun, verb, cond, seq); - delete myPortrait; -} - -void SciGui::portraitUnload(uint16 portraitId) { -} - -} // End of namespace Sci diff --git a/engines/sci/graphics/gui.h b/engines/sci/graphics/gui.h deleted file mode 100644 index 7663036117..0000000000 --- a/engines/sci/graphics/gui.h +++ /dev/null @@ -1,91 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SCI_GRAPHICS_GUI_H -#define SCI_GRAPHICS_GUI_H - -#include "sci/graphics/helpers.h" - -namespace Sci { - -class GfxScreen; -class GfxPalette; -class GfxCursor; -class GfxCache; -class GfxCompare; -class GfxCoordAdjuster16; -class GfxPorts; -class GfxPaint16; -class GfxAnimate; -class GfxControls; -class GfxMenu; -class GfxText16; -class GfxTransitions; - -class SciGui { -public: - SciGui(EngineState *s, GfxScreen *screen, GfxPalette *palette, GfxCache *cache, GfxCursor *cursor, GfxPorts *ports, AudioPlayer *audio); - virtual ~SciGui(); - - virtual void init(bool usesOldGfxFunctions); - - virtual void wait(int16 ticks); - - virtual void textSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); - virtual void textFonts(int argc, reg_t *argv); - virtual void textColors(int argc, reg_t *argv); - - virtual reg_t portraitLoad(Common::String resourceName); - virtual void portraitShow(Common::String resourceName, Common::Point position, uint16 resourceNum, uint16 noun, uint16 verb, uint16 cond, uint16 seq); - virtual void portraitUnload(uint16 portraitId); - - // FIXME: Don't store EngineState - virtual void resetEngineState(EngineState *s); - -protected: - GfxCursor *_cursor; - EngineState *_s; - GfxScreen *_screen; - GfxPalette *_palette; - GfxCache *_cache; - GfxCoordAdjuster16 *_coordAdjuster; - GfxCompare *_compare; - GfxPorts *_ports; - GfxPaint16 *_paint16; - -private: - AudioPlayer *_audio; - GfxAnimate *_animate; - GfxControls *_controls; - GfxMenu *_menu; - GfxText16 *_text16; - GfxTransitions *_transitions; - - bool _usesOldGfxFunctions; -}; - -} // End of namespace Sci - -#endif diff --git a/engines/sci/graphics/gui32.cpp b/engines/sci/graphics/gui32.cpp deleted file mode 100644 index 4b72050d0b..0000000000 --- a/engines/sci/graphics/gui32.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#include "common/timer.h" -#include "common/util.h" - -#include "sci/sci.h" -#include "sci/event.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/gui32.h" -#include "sci/graphics/screen.h" -#include "sci/graphics/palette.h" -#include "sci/graphics/cursor.h" -#include "sci/graphics/cache.h" -#include "sci/graphics/compare.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/frameout.h" -#include "sci/graphics/paint32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/robot.h" -#include "sci/graphics/view.h" - -namespace Sci { - -SciGui32::SciGui32(SegManager *segMan, SciEvent *event, GfxScreen *screen, GfxPalette *palette, GfxCache *cache, GfxCursor *cursor) - : _screen(screen), _palette(palette), _cache(cache), _cursor(cursor) { - - _coordAdjuster = new GfxCoordAdjuster32(segMan); - g_sci->_gfxCoordAdjuster = _coordAdjuster; - _cursor->init(_coordAdjuster, event); - _compare = new GfxCompare(segMan, g_sci->getKernel(), _cache, _screen, _coordAdjuster); - g_sci->_gfxCompare = _compare; - _paint32 = new GfxPaint32(g_sci->getResMan(), segMan, g_sci->getKernel(), _coordAdjuster, _cache, _screen, _palette); - g_sci->_gfxPaint = _paint32; - _frameout = new GfxFrameout(segMan, g_sci->getResMan(), _coordAdjuster, _cache, _screen, _palette, _paint32); - g_sci->_gfxFrameout = _frameout; -} - -SciGui32::~SciGui32() { - delete _frameout; - delete _paint32; - delete _compare; - delete _coordAdjuster; -} - -void SciGui32::init() { -} - -void SciGui32::textSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { - *textWidth = 0; - *textHeight = 0; -} - -void SciGui32::drawRobot(GuiResourceId robotId) { - Robot *test = new Robot(g_sci->getResMan(), _screen, robotId); - test->draw(); - delete test; -} - -} // End of namespace Sci diff --git a/engines/sci/graphics/gui32.h b/engines/sci/graphics/gui32.h deleted file mode 100644 index 99eb03b321..0000000000 --- a/engines/sci/graphics/gui32.h +++ /dev/null @@ -1,68 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SCI_GRAPHICS_GUI32_H -#define SCI_GRAPHICS_GUI32_H - -#include "sci/graphics/helpers.h" - -namespace Sci { - -class GfxCursor; -class GfxScreen; -class GfxPalette; -class GfxCache; -class GfxCoordAdjuster32; -class GfxCompare; -class GfxFrameout; -class GfxPaint32; - -class SciGui32 { -public: - SciGui32(SegManager *segMan, SciEvent *event, GfxScreen *screen, GfxPalette *palette, GfxCache *cache, GfxCursor *cursor); - ~SciGui32(); - - void init(); - - void textSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); - - void drawRobot(GuiResourceId robotId); - -protected: - GfxCursor *_cursor; - GfxScreen *_screen; - GfxPalette *_palette; - GfxCache *_cache; - GfxCoordAdjuster32 *_coordAdjuster; - GfxCompare *_compare; - GfxFrameout *_frameout; - GfxPaint32 *_paint32; - -private: -}; - -} // End of namespace Sci - -#endif diff --git a/engines/sci/graphics/maciconbar.cpp b/engines/sci/graphics/maciconbar.cpp index a06e98ccbf..6f2c9596db 100644 --- a/engines/sci/graphics/maciconbar.cpp +++ b/engines/sci/graphics/maciconbar.cpp @@ -24,6 +24,7 @@ */ #include "sci/sci.h" +#include "sci/engine/kernel.h" #include "sci/engine/selector.h" #include "sci/engine/state.h" #include "sci/graphics/maciconbar.h" diff --git a/engines/sci/graphics/menu.cpp b/engines/sci/graphics/menu.cpp index 880e1aba12..bfe58e03d5 100644 --- a/engines/sci/graphics/menu.cpp +++ b/engines/sci/graphics/menu.cpp @@ -29,9 +29,9 @@ #include "sci/sci.h" #include "sci/event.h" +#include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" -#include "sci/graphics/gui.h" #include "sci/graphics/ports.h" #include "sci/graphics/paint16.h" #include "sci/graphics/animate.h" @@ -43,8 +43,8 @@ namespace Sci { -GfxMenu::GfxMenu(SciEvent *event, SegManager *segMan, SciGui *gui, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen, GfxCursor *cursor) - : _event(event), _segMan(segMan), _gui(gui), _ports(ports), _paint16(paint16), _text16(text16), _screen(screen), _cursor(cursor) { +GfxMenu::GfxMenu(EventManager *event, SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen, GfxCursor *cursor) + : _event(event), _segMan(segMan), _ports(ports), _paint16(paint16), _text16(text16), _screen(screen), _cursor(cursor) { _menuSaveHandle = NULL_REG; _barSaveHandle = NULL_REG; @@ -63,8 +63,9 @@ void GfxMenu::reset() { _itemList.clear(); _listCount = 0; - // We actually set active item in here and remember last selection of the user - // sierra sci always defaulted to first item every time menu was called via ESC, we dont follow that logic + // We actually set active item in here and remember last selection of the + // user. Sierra SCI always defaulted to first item every time menu was + // called via ESC, we don't follow that logic. _curMenuId = 1; _curItemId = 1; } @@ -92,15 +93,16 @@ void GfxMenu::kernelAddEntry(Common::String title, Common::String content, reg_t beginPos = curPos; - // Now go through the content till we find end-marker and collect data about it - // ':' is an end-marker for each item + // Now go through the content till we find end-marker and collect data about it. + // ':' is an end-marker for each item. tagPos = 0; rightAlignedPos = 0; controlPos = 0; altPos = 0; functionPos = 0; while ((curPos < contentSize) && (content[curPos] != ':')) { switch (content[curPos]) { case '=': // Set tag - // Special case for normal animation speed - they use right aligned "=" for that one, so we ignore it - // as being recognized as tag marker + // Special case for normal animation speed - they use right + // aligned "=" for that one, so we ignore it as being recognized + // as tag marker. if (rightAlignedPos == curPos - 1) break; if (tagPos) @@ -199,8 +201,9 @@ void GfxMenu::kernelAddEntry(Common::String title, Common::String content, reg_t if (separatorCount == tempPos - beginPos) { itemEntry->separatorLine = true; } else { - // we don't strSplit here, because multilingual SCI01 support language switching on the fly, so we have to do - // this everytime the menu is called + // We don't strSplit here, because multilingual SCI01 support + // language switching on the fly, so we have to do this everytime + // the menu is called. itemEntry->text = Common::String(content.c_str() + beginPos, tempPos - beginPos); // LSL6 uses "Ctrl-" prefix string instead of ^ like all the other games do @@ -222,10 +225,12 @@ void GfxMenu::kernelAddEntry(Common::String title, Common::String content, reg_t if (tagPos && tagPos >= rightAlignedPos) tempPos = tagPos; itemEntry->textRightAligned = Common::String(content.c_str() + rightAlignedPos, tempPos - rightAlignedPos); - // Remove ending space, if there is one. Strangely sometimes there are lone spaces at the end in some games + // Remove ending space, if there is one. Strangely sometimes there + // are lone spaces at the end in some games if (itemEntry->textRightAligned.hasSuffix(" ")) itemEntry->textRightAligned.deleteLastChar(); - // - and + are used sometimes for volume control/animation speed, = sometimes for animation speed + // - and + are used sometimes for volume control/animation speed, + // = sometimes for animation speed if (itemEntry->textRightAligned == "-") { itemEntry->keyPress = '-'; } else if (itemEntry->textRightAligned == "+") { @@ -377,7 +382,7 @@ void GfxMenu::calculateMenuAndItemWidth() { } } -reg_t GfxMenu::kernelSelect(reg_t eventObject) { +reg_t GfxMenu::kernelSelect(reg_t eventObject, bool pauseSound) { int16 eventType = readSelectorValue(_segMan, eventObject, SELECTOR(type)); int16 keyPress, keyModifier; Common::Point mousePosition; @@ -392,7 +397,8 @@ reg_t GfxMenu::kernelSelect(reg_t eventObject) { case SCI_EVENT_KEYBOARD: keyPress = readSelectorValue(_segMan, eventObject, SELECTOR(message)); keyModifier = readSelectorValue(_segMan, eventObject, SELECTOR(modifiers)); - // If tab got pressed, handle it here as if it was Ctrl-I - at least sci0 also did it that way + // If tab got pressed, handle it here as if it was Ctrl-I - at least + // sci0 also did it that way if (keyPress == SCI_KEY_TAB) { keyModifier = SCI_KEYMOD_CTRL; keyPress = 'i'; @@ -401,9 +407,9 @@ reg_t GfxMenu::kernelSelect(reg_t eventObject) { case 0: break; case SCI_KEY_ESC: - interactiveShowMouse(); + interactiveStart(pauseSound); itemEntry = interactiveWithKeyboard(); - interactiveRestoreMouse(); + interactiveEnd(pauseSound); forceClaimed = true; break; default: @@ -439,9 +445,9 @@ reg_t GfxMenu::kernelSelect(reg_t eventObject) { case SCI_EVENT_MOUSE_PRESS: mousePosition = _cursor->getPosition(); if (mousePosition.y < 10) { - interactiveShowMouse(); + interactiveStart(pauseSound); itemEntry = interactiveWithMouse(); - interactiveRestoreMouse(); + interactiveEnd(pauseSound); forceClaimed = true; } break; @@ -562,7 +568,8 @@ void GfxMenu::drawMenu(uint16 oldMenuId, uint16 newMenuId) { if (!maxTextRightAlignedWidth) _menuRect.right -= 5; - // if part of menu window is outside the screen, move it into the screen (this happens in multilingual sq3 and lsl3) + // If part of menu window is outside the screen, move it into the screen + // (this happens in multilingual sq3 and lsl3). if (_menuRect.right > _screen->getWidth()) { _menuRect.translate(-(_menuRect.right - _screen->getWidth()), 0); } @@ -625,12 +632,16 @@ void GfxMenu::invertMenuSelection(uint16 itemId) { _paint16->bitsShow(itemRect); } -void GfxMenu::interactiveShowMouse() { +void GfxMenu::interactiveStart(bool pauseSound) { _mouseOldState = _cursor->isVisible(); _cursor->kernelShow(); + if (pauseSound) + g_sci->_soundCmd->pauseAll(true); } -void GfxMenu::interactiveRestoreMouse() { +void GfxMenu::interactiveEnd(bool pauseSound) { + if (pauseSound) + g_sci->_soundCmd->pauseAll(false); if (!_mouseOldState) _cursor->kernelHide(); } @@ -682,18 +693,19 @@ uint16 GfxMenu::mouseFindMenuItemSelection(Common::Point mousePosition, uint16 m } GuiMenuItemEntry *GfxMenu::interactiveWithKeyboard() { - sciEvent curEvent; + SciEvent curEvent; uint16 newMenuId = _curMenuId; uint16 newItemId = _curItemId; GuiMenuItemEntry *curItemEntry = findItem(_curMenuId, _curItemId); GuiMenuItemEntry *newItemEntry = curItemEntry; Common::Point mousePosition; - // We don't 100% follow sierra here: we select last item instead of selecting first item of first menu everytime - // Also sierra sci didnt allow mouse interaction, when menu was activated via keyboard + // We don't 100% follow Sierra here: we select last item instead of + // selecting first item of first menu every time. Also sierra sci didn't + // allow mouse interaction, when menu was activated via keyboard. - calculateMenuAndItemWidth(); _oldPort = _ports->setPort(_ports->_menuPort); + calculateMenuAndItemWidth(); _barSaveHandle = _paint16->bitsSave(_ports->_menuRect, GFX_SCREEN_MASK_VISUAL); _ports->penColor(0); @@ -706,12 +718,13 @@ GuiMenuItemEntry *GfxMenu::interactiveWithKeyboard() { _paint16->bitsShow(_menuRect); while (true) { - curEvent = _event->get(SCI_EVENT_ANY); + curEvent = _event->getSciEvent(SCI_EVENT_ANY); switch (curEvent.type) { case SCI_EVENT_KEYBOARD: - // We don't 100% follow sierra here: - sierra didn't wrap around when changing item id - // - sierra allowed item id to be 0, which didnt make any sense + // We don't 100% follow sierra here: + // - sierra didn't wrap around when changing item id + // - sierra allowed item id to be 0, which didn't make any sense do { switch (curEvent.data) { case SCI_KEY_ESC: @@ -796,25 +809,26 @@ GuiMenuItemEntry *GfxMenu::interactiveWithKeyboard() { } break; case SCI_EVENT_NONE: - _event->sleep(2500 / 1000); + g_sci->sleep(2500 / 1000); break; } } } -// Mouse button is currently pressed - we are now interpreting mouse coordinates till mouse button is released -// The menu item that is selected at that time is chosen. If no menu item is selected we cancel -// No keyboard interaction is allowed, cause that wouldnt make any sense at all +// Mouse button is currently pressed - we are now interpreting mouse coordinates +// till mouse button is released. The menu item that is selected at that time is +// chosen. If no menu item is selected we cancel. No keyboard interaction is +// allowed, cause that wouldnt make any sense at all. GuiMenuItemEntry *GfxMenu::interactiveWithMouse() { - sciEvent curEvent; + SciEvent curEvent; uint16 newMenuId = 0, newItemId = 0; uint16 curMenuId = 0, curItemId = 0; Common::Point mousePosition = _cursor->getPosition(); bool firstMenuChange = true; GuiMenuItemEntry *curItemEntry = NULL; - calculateMenuAndItemWidth(); _oldPort = _ports->setPort(_ports->_menuPort); + calculateMenuAndItemWidth(); _barSaveHandle = _paint16->bitsSave(_ports->_menuRect, GFX_SCREEN_MASK_VISUAL); _ports->penColor(0); @@ -824,7 +838,7 @@ GuiMenuItemEntry *GfxMenu::interactiveWithMouse() { _paint16->bitsShow(_ports->_menuRect); while (true) { - curEvent = _event->get(SCI_EVENT_ANY); + curEvent = _event->getSciEvent(SCI_EVENT_ANY); switch (curEvent.type) { case SCI_EVENT_MOUSE_RELEASE: @@ -835,7 +849,7 @@ GuiMenuItemEntry *GfxMenu::interactiveWithMouse() { return curItemEntry; case SCI_EVENT_NONE: - _event->sleep(2500 / 1000); + g_sci->sleep(2500 / 1000); break; } diff --git a/engines/sci/graphics/menu.h b/engines/sci/graphics/menu.h index 8f23b46ff8..9a14d4c64a 100644 --- a/engines/sci/graphics/menu.h +++ b/engines/sci/graphics/menu.h @@ -83,7 +83,7 @@ typedef Common::List<GuiMenuItemEntry *> GuiMenuItemList; */ class GfxMenu { public: - GfxMenu(SciEvent *event, SegManager *segMan, SciGui *gui, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen, GfxCursor *cursor); + GfxMenu(EventManager *event, SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen, GfxCursor *cursor); ~GfxMenu(); void reset(); @@ -92,7 +92,7 @@ public: reg_t kernelGetAttribute(uint16 menuId, uint16 itemId, uint16 attributeId); void drawBar(); - reg_t kernelSelect(reg_t eventObject); + reg_t kernelSelect(reg_t eventObject, bool pauseSound); void kernelDrawStatus(const char *text, int16 colorPen, int16 colorBack); void kernelDrawMenuBar(bool clear); @@ -103,17 +103,16 @@ private: void calculateMenuAndItemWidth(); void drawMenu(uint16 oldMenuId, uint16 newMenuId); void invertMenuSelection(uint16 itemId); - void interactiveShowMouse(); - void interactiveRestoreMouse(); + void interactiveStart(bool pauseSound); + void interactiveEnd(bool pauseSound); GuiMenuItemEntry *interactiveWithKeyboard(); GuiMenuItemEntry *interactiveWithMouse(); uint16 mouseFindMenuSelection(Common::Point mousePosition); uint16 mouseFindMenuItemSelection(Common::Point mousePosition, uint16 menuId); GuiMenuItemEntry *interactiveGetItem(uint16 menuId, uint16 itemId, bool menuChanged); - SciEvent *_event; + EventManager *_event; SegManager *_segMan; - SciGui *_gui; GfxPorts *_ports; GfxPaint16 *_paint16; GfxText16 *_text16; diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/paint.h index f1342d55e5..75a17461d4 100644 --- a/engines/sci/graphics/paint.h +++ b/engines/sci/graphics/paint.h @@ -26,8 +26,6 @@ #ifndef SCI_GRAPHICS_PAINT_H #define SCI_GRAPHICS_PAINT_H -#include "sci/graphics/gui.h" - #include "common/hashmap.h" namespace Sci { diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index ff4f3bec52..90b05c68a6 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -42,13 +42,14 @@ #include "sci/graphics/view.h" #include "sci/graphics/screen.h" #include "sci/graphics/palette.h" +#include "sci/graphics/portrait.h" #include "sci/graphics/text16.h" #include "sci/graphics/transitions.h" namespace Sci { -GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, SciGui *gui, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions) - : _resMan(resMan), _segMan(segMan), _kernel(kernel), _gui(gui), _cache(cache), _ports(ports), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette), _transitions(transitions) { +GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio) + : _resMan(resMan), _segMan(segMan), _kernel(kernel), _cache(cache), _ports(ports), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette), _transitions(transitions), _audio(audio) { } GfxPaint16::~GfxPaint16() { @@ -74,6 +75,11 @@ void GfxPaint16::drawPicture(GuiResourceId pictureId, int16 animationNr, bool mi picture->draw(animationNr, mirroredFlag, addToFlag, paletteId); delete picture; + + // We make a call to SciPalette here, for increasing sys timestamp and also loading targetpalette, if palvary active + // (SCI1.1 only) + if (getSciVersion() == SCI_VERSION_1_1) + _palette->drewPicture(pictureId); } // This one is the only one that updates screen! @@ -101,12 +107,12 @@ void GfxPaint16::drawCelAndShow(GuiResourceId viewId, int16 loopNo, int16 celNo, } // This version of drawCel is not supposed to call BitsShow()! -void GfxPaint16::drawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Rect celRect, byte priority, uint16 paletteNo, uint16 scaleX, uint16 scaleY) { +void GfxPaint16::drawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, const Common::Rect &celRect, byte priority, uint16 paletteNo, uint16 scaleX, uint16 scaleY) { drawCel(_cache->getView(viewId), loopNo, celNo, celRect, priority, paletteNo, scaleX, scaleY); } // This version of drawCel is not supposed to call BitsShow()! -void GfxPaint16::drawCel(GfxView *view, int16 loopNo, int16 celNo, Common::Rect celRect, byte priority, uint16 paletteNo, uint16 scaleX, uint16 scaleY) { +void GfxPaint16::drawCel(GfxView *view, int16 loopNo, int16 celNo, const Common::Rect &celRect, byte priority, uint16 paletteNo, uint16 scaleX, uint16 scaleY) { Common::Rect clipRect = celRect; clipRect.clip(_ports->_curPort->rect); if (clipRect.isEmpty()) // nothing to draw @@ -121,8 +127,8 @@ void GfxPaint16::drawCel(GfxView *view, int16 loopNo, int16 celNo, Common::Rect } } -// This is used as replacement for drawCelAndShow() when hires-cels are drawn to screen -// Hires-cels are available only SCI 1.1+ +// This is used as replacement for drawCelAndShow() when hires-cels are drawn to +// screen. Hires-cels are available only SCI 1.1+. void GfxPaint16::drawHiresCelAndShow(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, byte priority, uint16 paletteNo, reg_t upscaledHiresHandle, uint16 scaleX, uint16 scaleY) { GfxView *view = _cache->getView(viewId); Common::Rect celRect, curPortRect, clipRect, clipRectTranslated; @@ -131,9 +137,10 @@ void GfxPaint16::drawHiresCelAndShow(GuiResourceId viewId, int16 loopNo, int16 c if (view) { if ((leftPos == 0) && (topPos == 0)) { - // HACK: in kq6, we get leftPos&topPos == 0 SOMETIMES, that's why we need to get coordinates from upscaledHiresHandle - // I'm not sure if this is what we are supposed to do or if there is some other bug that actually makes - // coordinates to be 0 in the first place + // HACK: in kq6, we get leftPos&topPos == 0 SOMETIMES, that's why we + // need to get coordinates from upscaledHiresHandle. I'm not sure if + // this is what we are supposed to do or if there is some other bug + // that actually makes coordinates to be 0 in the first place. byte *memoryPtr = NULL; memoryPtr = _segMan->getHunkPointer(upscaledHiresHandle); if (memoryPtr) { @@ -310,11 +317,12 @@ reg_t GfxPaint16::bitsSave(const Common::Rect &rect, byte screenMask) { return NULL_REG; if (screenMask == GFX_SCREEN_MASK_DISPLAY) { + // The coordinates we are given are actually up-to-including right/bottom - we extend accordingly + workerRect.bottom++; + workerRect.right++; // Adjust rect to upscaled hires, but dont adjust according to port _screen->adjustToUpscaledCoordinates(workerRect.top, workerRect.left); _screen->adjustToUpscaledCoordinates(workerRect.bottom, workerRect.right); - workerRect.bottom++; - workerRect.right++; } else { _ports->offsetRect(workerRect); } @@ -324,7 +332,8 @@ reg_t GfxPaint16::bitsSave(const Common::Rect &rect, byte screenMask) { memoryId = _segMan->allocateHunkEntry("SaveBits()", size); memoryPtr = _segMan->getHunkPointer(memoryId); - _screen->bitsSave(workerRect, screenMask, memoryPtr); + if (memoryPtr) + _screen->bitsSave(workerRect, screenMask, memoryPtr); return memoryId; } @@ -374,7 +383,8 @@ void GfxPaint16::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, b } void GfxPaint16::kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle) { - // some calls are hiresMode even under kq6 DOS, that's why we check for upscaled hires here + // some calls are hiresMode even under kq6 DOS, that's why we check for + // upscaled hires here if ((!hiresMode) || (!_screen->getUpscaledHires())) { drawCelAndShow(viewId, loopNo, celNo, leftPos, topPos, priority, paletteNo); } else { @@ -382,19 +392,19 @@ void GfxPaint16::kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, } } -void GfxPaint16::kernelGraphFillBoxForeground(Common::Rect rect) { +void GfxPaint16::kernelGraphFillBoxForeground(const Common::Rect &rect) { paintRect(rect); } -void GfxPaint16::kernelGraphFillBoxBackground(Common::Rect rect) { +void GfxPaint16::kernelGraphFillBoxBackground(const Common::Rect &rect) { eraseRect(rect); } -void GfxPaint16::kernelGraphFillBox(Common::Rect rect, uint16 colorMask, int16 color, int16 priority, int16 control) { +void GfxPaint16::kernelGraphFillBox(const Common::Rect &rect, uint16 colorMask, int16 color, int16 priority, int16 control) { fillRect(rect, colorMask, color, priority, control); } -void GfxPaint16::kernelGraphFrameBox(Common::Rect rect, int16 color) { +void GfxPaint16::kernelGraphFrameBox(const Common::Rect &rect, int16 color) { int16 oldColor = _ports->getPort()->penClr; _ports->penColor(color); frameRect(rect); @@ -406,11 +416,11 @@ void GfxPaint16::kernelGraphDrawLine(Common::Point startPoint, Common::Point end _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control); } -reg_t GfxPaint16::kernelGraphSaveBox(Common::Rect rect, uint16 screenMask) { +reg_t GfxPaint16::kernelGraphSaveBox(const Common::Rect &rect, uint16 screenMask) { return bitsSave(rect, screenMask); } -reg_t GfxPaint16::kernelGraphSaveUpscaledHiresBox(Common::Rect rect) { +reg_t GfxPaint16::kernelGraphSaveUpscaledHiresBox(const Common::Rect &rect) { return bitsSave(rect, GFX_SCREEN_MASK_DISPLAY); } @@ -418,8 +428,9 @@ void GfxPaint16::kernelGraphRestoreBox(reg_t handle) { bitsRestore(handle); } -void GfxPaint16::kernelGraphUpdateBox(Common::Rect rect, bool hiresMode) { - // some calls are hiresMode even under kq6 DOS, that's why we check for upscaled hires here +void GfxPaint16::kernelGraphUpdateBox(const Common::Rect &rect, bool hiresMode) { + // some calls are hiresMode even under kq6 DOS, that's why we check for + // upscaled hires here if ((!hiresMode) || (!_screen->getUpscaledHires())) bitsShow(rect); else @@ -447,6 +458,8 @@ void GfxPaint16::kernelGraphRedrawBox(Common::Rect rect) { #define SCI_DISPLAY_WIDTH 106 #define SCI_DISPLAY_SAVEUNDER 107 #define SCI_DISPLAY_RESTOREUNDER 108 +#define SCI_DISPLAY_DUMMY1 114 // used in longbow-demo, not supported in sierra sci - no parameters +#define SCI_DISPLAY_DUMMY2 115 // used in longbow-demo, not supported in sierra sci - has 1 parameter #define SCI_DISPLAY_DONTSHOWBITS 121 reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { @@ -457,7 +470,8 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { Common::Rect rect; reg_t result = NULL_REG; - // Make a "backup" of the port settings (required for some SCI0LATE and SCI01+ only) + // Make a "backup" of the port settings (required for some SCI0LATE and + // SCI01+ only) Port oldPort = *_ports->getPort(); // setting defaults @@ -512,8 +526,24 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { case SCI_DISPLAY_DONTSHOWBITS: bRedraw = 0; break; + + // 2 Dummy functions, longbow-demo is using those several times but sierra sci doesn't support them at all + case SCI_DISPLAY_DUMMY1: + case SCI_DISPLAY_DUMMY2: + if (!((g_sci->getGameId() == GID_LONGBOW) && (g_sci->isDemo()))) + error("Unknown kDisplay argument %X", displayArg); + if (displayArg == SCI_DISPLAY_DUMMY2) { + if (argc) { + argc--; argv++; + } else { + error("No parameter left for kDisplay(0x73)"); + } + } + break; default: - warning("Unknown kDisplay argument %X", displayArg); + if ((g_sci->getGameId() == GID_ISLANDBRAIN) && (g_sci->getEngineState()->currentRoomNumber() == 300)) + break; // WORKAROUND: we are called there with an forwarded 0 as additional parameter (script bug) + error("Unknown kDisplay argument %X", displayArg); break; } } @@ -540,14 +570,20 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { uint16 tTop = currport->curTop; uint16 tLeft = currport->curLeft; if (!g_sci->_features->usesOldGfxFunctions()) { - // Restore port settings for some SCI0LATE and SCI01+ only - // the change actually happened inbetween .530 (hoyle1) and .566 (heros quest). We don't have any detection for - // that currently, so we are using oldGfxFunctions (.502). The only games that could get regressions because of - // this are hoyle1, kq4 and funseeker. If there are regressions, we should use interpreter version (which would - // require exe version detection) - // If we restore the port for whole SCI0LATE, at least sq3old will get an issue - font 0 will get used when - // scanning for planets instead of font 600 - a setfont parameter is missing in one of the kDisplay calls in - // script 19. I assume this is a script bug, because it was added in sq3new. + // Restore port settings for some SCI0LATE and SCI01+ only. + // + // The change actually happened inbetween .530 (hoyle1) and .566 (heros + // quest). We don't have any detection for that currently, so we are + // using oldGfxFunctions (.502). The only games that could get + // regressions because of this are hoyle1, kq4 and funseeker. If there + // are regressions, we should use interpreter version (which would + // require exe version detection). + // + // If we restore the port for whole SCI0LATE, at least sq3old will get + // an issue - font 0 will get used when scanning for planets instead of + // font 600 - a setfont parameter is missing in one of the kDisplay + // calls in script 19. I assume this is a script bug, because it was + // added in sq3new. *currport = oldPort; } currport->curTop = tTop; @@ -562,12 +598,33 @@ void GfxPaint16::kernelShakeScreen(uint16 shakeCount, uint16 directions) { _screen->setVerticalShakePos(10); // TODO: horizontal shakes g_system->updateScreen(); - _gui->wait(3); + g_sci->getEngineState()->wait(3); + if (directions & SCI_SHAKE_DIRECTION_VERTICAL) _screen->setVerticalShakePos(0); + g_system->updateScreen(); - _gui->wait(3); + g_sci->getEngineState()->wait(3); } } +reg_t GfxPaint16::kernelPortraitLoad(const Common::String &resourceName) { + //Portrait *myPortrait = new Portrait(g_sci->getResMan(), _screen, _palette, resourceName); + return NULL_REG; +} + +void GfxPaint16::kernelPortraitShow(const Common::String &resourceName, Common::Point position, uint16 resourceId, uint16 noun, uint16 verb, uint16 cond, uint16 seq) { + Portrait *myPortrait = new Portrait(g_sci->getResMan(), g_sci->getEventManager(), _screen, _palette, _audio, resourceName); + // TODO: cache portraits + // adjust given coordinates to curPort (but dont adjust coordinates on upscaledHires_Save_Box and give us hires coordinates + // on kDrawCel, yeah this whole stuff makes sense) + position.x += _ports->getPort()->left; position.y += _ports->getPort()->top; + _screen->adjustToUpscaledCoordinates(position.y, position.x); + myPortrait->doit(position, resourceId, noun, verb, cond, seq); + delete myPortrait; +} + +void GfxPaint16::kernelPortraitUnload(uint16 portraitId) { +} + } // End of namespace Sci diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 65f9dd0d9c..4c3ac255c4 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -26,7 +26,6 @@ #ifndef SCI_GRAPHICS_PAINT16_H #define SCI_GRAPHICS_PAINT16_H -#include "sci/graphics/gui.h" #include "sci/graphics/paint.h" #include "common/hashmap.h" @@ -45,7 +44,7 @@ class GfxView; */ class GfxPaint16 : public GfxPaint { public: - GfxPaint16(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, SciGui *gui, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions); + GfxPaint16(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); ~GfxPaint16(); void init(GfxAnimate *animate, GfxText16 *text16); @@ -54,8 +53,8 @@ public: void drawPicture(GuiResourceId pictureId, int16 animationNr, bool mirroredFlag, bool addToFlag, GuiResourceId paletteId); void drawCelAndShow(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, byte priority, uint16 paletteNo, uint16 scaleX = 128, uint16 scaleY = 128); - void drawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Rect celRect, byte priority, uint16 paletteNo, uint16 scaleX = 128, uint16 scaleY = 128); - void drawCel(GfxView *view, int16 loopNo, int16 celNo, Common::Rect celRect, byte priority, uint16 paletteNo, uint16 scaleX = 128, uint16 scaleY = 128); + void drawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, const Common::Rect &celRect, byte priority, uint16 paletteNo, uint16 scaleX = 128, uint16 scaleY = 128); + void drawCel(GfxView *view, int16 loopNo, int16 celNo, const Common::Rect &celRect, byte priority, uint16 paletteNo, uint16 scaleX = 128, uint16 scaleY = 128); void drawHiresCelAndShow(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, byte priority, uint16 paletteNo, reg_t upscaledHiresHandle, uint16 scaleX = 128, uint16 scaleY = 128); void clearScreen(byte color = 255); @@ -76,26 +75,30 @@ public: void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); void kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle); - void kernelGraphFillBoxForeground(Common::Rect rect); - void kernelGraphFillBoxBackground(Common::Rect rect); - void kernelGraphFillBox(Common::Rect rect, uint16 colorMask, int16 color, int16 priority, int16 control); - void kernelGraphFrameBox(Common::Rect rect, int16 color); + void kernelGraphFillBoxForeground(const Common::Rect &rect); + void kernelGraphFillBoxBackground(const Common::Rect &rect); + void kernelGraphFillBox(const Common::Rect &rect, uint16 colorMask, int16 color, int16 priority, int16 control); + void kernelGraphFrameBox(const Common::Rect &rect, int16 color); void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); - reg_t kernelGraphSaveBox(Common::Rect rect, uint16 flags); - reg_t kernelGraphSaveUpscaledHiresBox(Common::Rect rect); + reg_t kernelGraphSaveBox(const Common::Rect &rect, uint16 flags); + reg_t kernelGraphSaveUpscaledHiresBox(const Common::Rect &rect); void kernelGraphRestoreBox(reg_t handle); - void kernelGraphUpdateBox(Common::Rect rect, bool hiresMode); + void kernelGraphUpdateBox(const Common::Rect &rect, bool hiresMode); void kernelGraphRedrawBox(Common::Rect rect); reg_t kernelDisplay(const char *text, int argc, reg_t *argv); void kernelShakeScreen(uint16 shakeCount, uint16 directions); + reg_t kernelPortraitLoad(const Common::String &resourceName); + void kernelPortraitShow(const Common::String &resourceName, Common::Point position, uint16 resourceNum, uint16 noun, uint16 verb, uint16 cond, uint16 seq); + void kernelPortraitUnload(uint16 portraitId); + private: ResourceManager *_resMan; SegManager *_segMan; Kernel *_kernel; - SciGui *_gui; + AudioPlayer *_audio; GfxAnimate *_animate; GfxCache *_cache; GfxPorts *_ports; diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp index 711efc9816..9b24da413b 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -38,6 +38,7 @@ #include "sci/graphics/view.h" #include "sci/graphics/screen.h" #include "sci/graphics/palette.h" +#include "sci/graphics/robot.h" namespace Sci { @@ -79,4 +80,10 @@ void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point end _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control); } +void GfxPaint32::debugDrawRobot(GuiResourceId robotId) { + GfxRobot *test = new GfxRobot(g_sci->getResMan(), _screen, robotId); + test->draw(); + delete test; +} + } // End of namespace Sci diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h index f4d6340361..a048d7f307 100644 --- a/engines/sci/graphics/paint32.h +++ b/engines/sci/graphics/paint32.h @@ -26,7 +26,6 @@ #ifndef SCI_GRAPHICS_PAINT32_H #define SCI_GRAPHICS_PAINT32_H -#include "sci/graphics/gui.h" #include "sci/graphics/paint.h" #include "common/hashmap.h" @@ -49,6 +48,8 @@ public: void kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle); void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); + void debugDrawRobot(GuiResourceId robotId); + private: ResourceManager *_resMan; SegManager *_segMan; diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp index 3c4cf7e964..957199f8b9 100644 --- a/engines/sci/graphics/palette.cpp +++ b/engines/sci/graphics/palette.cpp @@ -30,13 +30,15 @@ #include "sci/sci.h" #include "sci/engine/state.h" +#include "sci/graphics/cache.h" #include "sci/graphics/maciconbar.h" #include "sci/graphics/palette.h" #include "sci/graphics/screen.h" +#include "sci/graphics/view.h" namespace Sci { -GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool autoSetPalette) +GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool useMerging) : _resMan(resMan), _screen(screen) { int16 color; @@ -57,23 +59,39 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool autoSetP _sysPalette.colors[255].b = 255; _sysPaletteChanged = false; - if (autoSetPalette) { - if (_resMan->getViewType() == kViewEga) - setEGA(); - else if (_resMan->isAmiga32color()) - setAmiga(); - else - kernelSetFromResource(999, true); - } + + // Pseudo-WORKAROUND + // Quest for Glory 3 demo, Eco Quest 1 demo, Laura Bow 2 demo, Police Quest 1 vga and all Nick's Picks + // all use an inbetween interpreter, some parts are SCI1.1, some parts are SCI1 + // It's not using the SCI1.1 palette merging (copying over all the colors) but the real merging + // If we use the copying over, we will get issues because some views have marked all colors as being used + // and those will overwrite the current palette in that case + _useMerging = useMerging; + + palVaryInit(); } GfxPalette::~GfxPalette() { } +bool GfxPalette::isMerging() { + return _useMerging; +} + +// meant to get called only once during init of engine +void GfxPalette::setDefault() { + if (_resMan->getViewType() == kViewEga) + setEGA(); + else if (_resMan->isAmiga32color()) + setAmiga(); + else + kernelSetFromResource(999, true); +} + #define SCI_PAL_FORMAT_CONSTANT 1 #define SCI_PAL_FORMAT_VARIABLE 0 -void GfxPalette::createFromData(byte *data, Palette *paletteOut) { +void GfxPalette::createFromData(byte *data, int bytesLeft, Palette *paletteOut) { int palFormat = 0; int palOffset = 0; int palColorStart = 0; @@ -81,10 +99,16 @@ void GfxPalette::createFromData(byte *data, Palette *paletteOut) { int colorNo = 0; memset(paletteOut, 0, sizeof(Palette)); - // Setup default mapping + // Setup 1:1 mapping 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 + // palette + warning("GfxPalette::createFromData() - not enough bytes in resource, expected palette header"); + return; + } if ((data[0] == 0 && data[1] == 1) || (data[0] == 0 && data[1] == 0 && READ_LE_UINT16(data + 29) == 0)) { // SCI0/SCI1 palette palFormat = SCI_PAL_FORMAT_VARIABLE; // CONSTANT; @@ -99,6 +123,11 @@ void GfxPalette::createFromData(byte *data, Palette *paletteOut) { } switch (palFormat) { case SCI_PAL_FORMAT_CONSTANT: + // Check, if enough bytes left + if (bytesLeft < palOffset + (3 * palColorCount)) { + warning("GfxPalette::createFromData() - not enough bytes in resource, expected palette colors"); + return; + } for (colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) { paletteOut->colors[colorNo].used = 1; paletteOut->colors[colorNo].r = data[palOffset++]; @@ -107,6 +136,10 @@ void GfxPalette::createFromData(byte *data, Palette *paletteOut) { } break; case SCI_PAL_FORMAT_VARIABLE: + if (bytesLeft < palOffset + (4 * palColorCount)) { + warning("GfxPalette::createFromData() - not enough bytes in resource, expected palette colors"); + return; + } for (colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) { paletteOut->colors[colorNo].used = data[palOffset++]; paletteOut->colors[colorNo].r = data[palOffset++]; @@ -180,112 +213,144 @@ void GfxPalette::setEGA() { // Now setting colors 16-254 to the correct mix colors that occur when not doing a dithering run on // finished pictures for (curColor = 0x10; curColor <= 0xFE; curColor++) { - _sysPalette.colors[curColor].used = curColor; + _sysPalette.colors[curColor].used = 1; color1 = curColor & 0x0F; color2 = curColor >> 4; _sysPalette.colors[curColor].r = (_sysPalette.colors[color1].r >> 1) + (_sysPalette.colors[color2].r >> 1); _sysPalette.colors[curColor].g = (_sysPalette.colors[color1].g >> 1) + (_sysPalette.colors[color2].g >> 1); _sysPalette.colors[curColor].b = (_sysPalette.colors[color1].b >> 1) + (_sysPalette.colors[color2].b >> 1); } + _sysPalette.timestamp = 1; setOnScreen(); } -void GfxPalette::set(Palette *sciPal, bool force, bool forceRealMerge) { +void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) { uint32 systime = _sysPalette.timestamp; - if (force || sciPal->timestamp != systime) { - _sysPaletteChanged |= merge(sciPal, &_sysPalette, force, forceRealMerge); - sciPal->timestamp = _sysPalette.timestamp; - if (_sysPaletteChanged && _screen->_picNotValid == 0) { // && systime != _sysPalette.timestamp) { - // Removed timestamp checking, because this shouldnt be needed anymore. I'm leaving it commented just in - // case this causes regressions + if (force || newPalette->timestamp != systime) { + // SCI1.1+ doesnt do real merging anymore, but simply copying over the used colors from other palettes + // There are some games with inbetween SCI1.1 interpreters, use real merging for them (e.g. laura bow 2 demo) + if ((forceRealMerge) || (_useMerging)) + _sysPaletteChanged |= merge(newPalette, force, forceRealMerge); + else + _sysPaletteChanged |= insert(newPalette, &_sysPalette); + + // Adjust timestamp on newPalette, so it wont get merged/inserted w/o need + newPalette->timestamp = _sysPalette.timestamp; + + bool updatePalette = _sysPaletteChanged && _screen->_picNotValid == 0; + + if (_palVaryResourceId != -1) { + // Pal-vary currently active, we don't set at any time, but also insert into origin palette + insert(newPalette, &_palVaryOriginPalette); + palVaryProcess(0, updatePalette); + return; + } + + if (updatePalette) { setOnScreen(); _sysPaletteChanged = false; } } } -bool GfxPalette::merge(Palette *pFrom, Palette *pTo, bool force, bool forceRealMerge) { +bool GfxPalette::insert(Palette *newPalette, Palette *destPalette) { + bool paletteChanged = false; + + for (int i = 1; i < 255; i++) { + if (newPalette->colors[i].used) { + if ((newPalette->colors[i].r != destPalette->colors[i].r) || (newPalette->colors[i].g != destPalette->colors[i].g) || (newPalette->colors[i].b != destPalette->colors[i].b)) { + destPalette->colors[i].r = newPalette->colors[i].r; + destPalette->colors[i].g = newPalette->colors[i].g; + destPalette->colors[i].b = newPalette->colors[i].b; + paletteChanged = true; + } + destPalette->colors[i].used = newPalette->colors[i].used; + newPalette->mapping[i] = i; + } + } + // We don't update the timestamp for SCI1.1, it's only updated on kDrawPic calls + return paletteChanged; +} + +bool GfxPalette::merge(Palette *newPalette, bool force, bool forceRealMerge) { uint16 res; int i,j; bool paletteChanged = false; - if ((!forceRealMerge) && (getSciVersion() >= SCI_VERSION_1_1)) { - // SCI1.1+ doesnt do real merging anymore, but simply copying over the used colors from other palettes - for (i = 1; i < 255; i++) { - if (pFrom->colors[i].used) { - if ((pFrom->colors[i].r != pTo->colors[i].r) || (pFrom->colors[i].g != pTo->colors[i].g) || (pFrom->colors[i].b != pTo->colors[i].b)) { - pTo->colors[i].r = pFrom->colors[i].r; - pTo->colors[i].g = pFrom->colors[i].g; - pTo->colors[i].b = pFrom->colors[i].b; - paletteChanged = true; - } - pTo->colors[i].used = pFrom->colors[i].used; - pFrom->mapping[i] = i; + // colors 0 (black) and 255 (white) are not affected by merging + for (i = 1 ; i < 255; i++) { + if (!newPalette->colors[i].used)// color is not used - so skip it + continue; + // forced palette merging or dest color is not used yet + if (force || (!_sysPalette.colors[i].used)) { + _sysPalette.colors[i].used = newPalette->colors[i].used; + if ((newPalette->colors[i].r != _sysPalette.colors[i].r) || (newPalette->colors[i].g != _sysPalette.colors[i].g) || (newPalette->colors[i].b != _sysPalette.colors[i].b)) { + _sysPalette.colors[i].r = newPalette->colors[i].r; + _sysPalette.colors[i].g = newPalette->colors[i].g; + _sysPalette.colors[i].b = newPalette->colors[i].b; + paletteChanged = true; } + newPalette->mapping[i] = i; + continue; } - } else { - // colors 0 (black) and 255 (white) are not affected by merging - for (i = 1 ; i < 255; i++) { - if (!pFrom->colors[i].used)// color is not used - so skip it - continue; - // forced palette merging or dest color is not used yet - if (force || (!pTo->colors[i].used)) { - pTo->colors[i].used = pFrom->colors[i].used; - if ((pFrom->colors[i].r != pTo->colors[i].r) || (pFrom->colors[i].g != pTo->colors[i].g) || (pFrom->colors[i].b != pTo->colors[i].b)) { - pTo->colors[i].r = pFrom->colors[i].r; - pTo->colors[i].g = pFrom->colors[i].g; - pTo->colors[i].b = pFrom->colors[i].b; - paletteChanged = true; - } - pFrom->mapping[i] = i; - continue; - } - // is the same color already at the same position? -> match it directly w/o lookup - // this fixes games like lsl1demo/sq5 where the same rgb color exists multiple times and where we would - // otherwise match the wrong one (which would result into the pixels affected (or not) by palette changes) - if ((pTo->colors[i].r == pFrom->colors[i].r) && (pTo->colors[i].g == pFrom->colors[i].g) && (pTo->colors[i].b == pFrom->colors[i].b)) { - pFrom->mapping[i] = i; - continue; - } - // check if exact color could be matched - res = matchColor(pTo, pFrom->colors[i].r, pFrom->colors[i].g, pFrom->colors[i].b); - if (res & 0x8000) { // exact match was found - pFrom->mapping[i] = res & 0xFF; - continue; - } - // no exact match - see if there is an unused color - for (j = 1; j < 256; j++) - if (!pTo->colors[j].used) { - pTo->colors[j].used = pFrom->colors[i].used; - pTo->colors[j].r = pFrom->colors[i].r; - pTo->colors[j].g = pFrom->colors[i].g; - pTo->colors[j].b = pFrom->colors[i].b; - pFrom->mapping[i] = j; - paletteChanged = true; - break; - } - // if still no luck - set an approximate color - if (j == 256) { - pFrom->mapping[i] = res & 0xFF; - pTo->colors[res & 0xFF].used |= 0x10; + // is the same color already at the same position? -> match it directly w/o lookup + // this fixes games like lsl1demo/sq5 where the same rgb color exists multiple times and where we would + // otherwise match the wrong one (which would result into the pixels affected (or not) by palette changes) + if ((_sysPalette.colors[i].r == newPalette->colors[i].r) && (_sysPalette.colors[i].g == newPalette->colors[i].g) && (_sysPalette.colors[i].b == newPalette->colors[i].b)) { + newPalette->mapping[i] = i; + continue; + } + // check if exact color could be matched + res = matchColor(newPalette->colors[i].r, newPalette->colors[i].g, newPalette->colors[i].b); + if (res & 0x8000) { // exact match was found + newPalette->mapping[i] = res & 0xFF; + continue; + } + // no exact match - see if there is an unused color + for (j = 1; j < 256; j++) + if (!_sysPalette.colors[j].used) { + _sysPalette.colors[j].used = newPalette->colors[i].used; + _sysPalette.colors[j].r = newPalette->colors[i].r; + _sysPalette.colors[j].g = newPalette->colors[i].g; + _sysPalette.colors[j].b = newPalette->colors[i].b; + newPalette->mapping[i] = j; + paletteChanged = true; + break; } + // if still no luck - set an approximate color + if (j == 256) { + newPalette->mapping[i] = res & 0xFF; + _sysPalette.colors[res & 0xFF].used |= 0x10; } } - pTo->timestamp = g_system->getMillis() * 60 / 1000; + + if (!forceRealMerge) + _sysPalette.timestamp = g_system->getMillis() * 60 / 1000; return paletteChanged; } -uint16 GfxPalette::matchColor(Palette *pPal, byte r, byte g, byte b) { +// This is called for SCI1.1, when kDrawPic got done. We update sysPalette timestamp this way for SCI1.1 and also load +// target-palette, if palvary is active +void GfxPalette::drewPicture(GuiResourceId pictureId) { + if (!_useMerging) // Don't do this on inbetween SCI1.1 games + _sysPalette.timestamp++; + + if (_palVaryResourceId != -1) { + palVaryLoadTargetPalette(pictureId); + } +} + +uint16 GfxPalette::matchColor(byte r, byte g, byte b) { byte found = 0xFF; int diff = 0x2FFFF, cdiff; int16 dr,dg,db; for (int i = 1; i < 255; i++) { - if ((!pPal->colors[i].used)) + if ((!_sysPalette.colors[i].used)) continue; - dr = pPal->colors[i].r - r; - dg = pPal->colors[i].g - g; - db = pPal->colors[i].b - b; + dr = _sysPalette.colors[i].r - r; + dg = _sysPalette.colors[i].g - g; + db = _sysPalette.colors[i].b - b; // minimum squares match cdiff = (dr*dr) + (dg*dg) + (db*db); // minimum sum match (Sierra's) @@ -306,8 +371,6 @@ void GfxPalette::getSys(Palette *pal) { } void GfxPalette::setOnScreen() { -// if (pal != &_sysPalette) -// memcpy(&_sysPalette,pal,sizeof(Palette)); // We dont change palette at all times for amiga if (_resMan->isAmiga32color()) return; @@ -319,11 +382,11 @@ void GfxPalette::setOnScreen() { } bool GfxPalette::kernelSetFromResource(GuiResourceId resourceId, bool force) { - Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), 0); + Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); Palette palette; if (palResource) { - createFromData(palResource->data, &palette); + createFromData(palResource->data, palResource->size, &palette); set(&palette, force); return true; } @@ -346,12 +409,18 @@ void GfxPalette::kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag) void GfxPalette::kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 intensity, bool setPalette) { memset(&_sysPalette.intensity[0] + fromColor, intensity, toColor - fromColor); - if (setPalette) + if (setPalette) { setOnScreen(); + EngineState *state = g_sci->getEngineState(); + // Call speed throttler from here as well just in case we need it + // At least in kq6 intro the scripts call us in a tight loop for fadein/fadeout + state->speedThrottler(30); + state->_throttleTrigger = true; + } } int16 GfxPalette::kernelFindColor(uint16 r, uint16 g, uint16 b) { - return matchColor(&_sysPalette, r, g, b) & 0xFF; + return matchColor(r, g, b) & 0xFF; } // Returns true, if palette got changed @@ -378,6 +447,8 @@ bool GfxPalette::kernelAnimate(byte fromColor, byte toColor, int speed) { scheduleCount++; } + g_sci->getEngineState()->_throttleTrigger = true; + for (scheduleNr = 0; scheduleNr < scheduleCount; scheduleNr++) { if (_schedules[scheduleNr].from == fromColor) { if (_schedules[scheduleNr].schedule <= now) { @@ -414,7 +485,21 @@ void GfxPalette::kernelAnimateSet() { } void GfxPalette::kernelAssertPalette(GuiResourceId resourceId) { - warning("kAssertPalette %d", resourceId); + // Sometimes invalid viewIds are asked for, ignore those (e.g. qfg1vga) + //if (!_resMan->testResource(ResourceId(kResourceTypeView, resourceId))) + // return; + // maybe we took the wrong parameter before, if this causes invalid view again, enable to commented out code again + + GfxView *view = g_sci->_gfxCache->getView(resourceId); + Palette *viewPalette = view->getPalette(); + if (viewPalette) { + // merge/insert this palette + set(viewPalette, true); + } +} + +void GfxPalette::kernelSyncScreenPalette() { + _screen->getPalette(&_sysPalette); } // palVary @@ -443,40 +528,191 @@ void GfxPalette::kernelAssertPalette(GuiResourceId resourceId) { // Saving/restoring // need to save start and target-palette, when palVaryOn = true -void GfxPalette::startPalVary(uint16 paletteId, uint16 ticks) { - kernelSetFromResource(paletteId, true); - return; +void GfxPalette::palVaryInit() { + _palVaryResourceId = -1; + _palVaryPaused = 0; + _palVarySignal = 0; + _palVaryStep = 0; + _palVaryStepStop = 0; + _palVaryDirection = 0; + _palVaryTicks = 0; +} - if (_palVaryId >= 0) // another palvary is taking place, return - return; +bool GfxPalette::palVaryLoadTargetPalette(GuiResourceId resourceId) { + _palVaryResourceId = resourceId; + Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); + if (palResource) { + // Load and initialize destination palette + createFromData(palResource->data, palResource->size, &_palVaryTargetPalette); + return true; + } + return false; +} + +void GfxPalette::palVaryInstallTimer() { + int16 ticks = _palVaryTicks > 0 ? _palVaryTicks : 1; + // Call signal increase every [ticks] + g_sci->getTimerManager()->installTimerProc(&palVaryCallback, 1000000 / 60 * ticks, this); +} + +void GfxPalette::palVaryRemoveTimer() { + g_sci->getTimerManager()->removeTimerProc(&palVaryCallback); +} + +bool GfxPalette::kernelPalVaryInit(GuiResourceId resourceId, uint16 ticks, uint16 stepStop, uint16 direction) { + if (_palVaryResourceId != -1) // another palvary is taking place, return + return false; + + if (palVaryLoadTargetPalette(resourceId)) { + // Save current palette + memcpy(&_palVaryOriginPalette, &_sysPalette, sizeof(Palette)); + + _palVarySignal = 0; + _palVaryTicks = ticks; + _palVaryStep = 1; + _palVaryStepStop = stepStop; + _palVaryDirection = direction; + // if no ticks are given, jump directly to destination + if (!_palVaryTicks) + _palVaryDirection = stepStop; + palVaryInstallTimer(); + return true; + } + return false; +} + +int16 GfxPalette::kernelPalVaryReverse(int16 ticks, uint16 stepStop, int16 direction) { + if (_palVaryResourceId == -1) + return 0; + + if (_palVaryStep > 64) + _palVaryStep = 64; + if (ticks != -1) + _palVaryTicks = ticks; + _palVaryStepStop = stepStop; + _palVaryDirection = direction != -1 ? -direction : -_palVaryDirection; + + if (!_palVaryTicks) + _palVaryDirection = _palVaryStepStop - _palVaryStep; + palVaryInstallTimer(); + return kernelPalVaryGetCurrentStep(); +} + +int16 GfxPalette::kernelPalVaryGetCurrentStep() { + if (_palVaryDirection >= 0) + return _palVaryStep; + return -_palVaryStep; +} + +int16 GfxPalette::kernelPalVaryChangeTarget(GuiResourceId resourceId) { + if (_palVaryResourceId != -1) { + Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); + if (palResource) { + Palette insertPalette; + createFromData(palResource->data, palResource->size, &insertPalette); + // insert new palette into target + insert(&insertPalette, &_palVaryTargetPalette); + // update palette and set on screen + palVaryProcess(0, true); + } + } + return kernelPalVaryGetCurrentStep(); +} - _palVaryId = paletteId; - _palVaryStart = g_system->getMillis(); - _palVaryEnd = _palVaryStart + ticks * 1000 / 60; - g_sci->getTimerManager()->installTimerProc(&palVaryCallback, 1000 / 60, this); +void GfxPalette::kernelPalVaryChangeTicks(uint16 ticks) { + _palVaryTicks = ticks; + if (_palVaryStep - _palVaryStepStop) { + palVaryRemoveTimer(); + palVaryInstallTimer(); + } } -void GfxPalette::togglePalVary(bool pause) { +void GfxPalette::kernelPalVaryPause(bool pause) { + if (_palVaryResourceId == -1) + return; // this call is actually counting states, so calling this 3 times with true will require calling it later // 3 times with false to actually remove pause - - // TODO + if (pause) { + _palVaryPaused++; + } else { + if (_palVaryPaused) + _palVaryPaused--; + } } -void GfxPalette::stopPalVary() { - g_sci->getTimerManager()->removeTimerProc(&palVaryCallback); - _palVaryId = -1; // invalidate the target palette +void GfxPalette::kernelPalVaryDeinit() { + palVaryRemoveTimer(); - // HACK: just set the target palette - kernelSetFromResource(_palVaryId, true); + _palVaryResourceId = -1; // invalidate the target palette } void GfxPalette::palVaryCallback(void *refCon) { - ((GfxPalette *)refCon)->doPalVary(); + ((GfxPalette *)refCon)->palVaryIncreaseSignal(); +} + +void GfxPalette::palVaryIncreaseSignal() { + if (!_palVaryPaused) + _palVarySignal++; +} + +// Actually do the pal vary processing +void GfxPalette::palVaryUpdate() { + if (_palVarySignal) { + palVaryProcess(_palVarySignal, true); + _palVarySignal = 0; + } +} + +void GfxPalette::palVaryPrepareForTransition() { + if (_palVaryResourceId != -1) { + // Before doing transitions, we have to prepare palette + palVaryProcess(0, false); + } } -void GfxPalette::doPalVary() { - // TODO: do palette transition here... +// Processes pal vary updates +void GfxPalette::palVaryProcess(int signal, bool setPalette) { + int16 stepChange = signal * _palVaryDirection; + + _palVaryStep += stepChange; + if (stepChange > 0) { + if (_palVaryStep > _palVaryStepStop) + _palVaryStep = _palVaryStepStop; + } else { + if (_palVaryStep < _palVaryStepStop) { + if (signal) + _palVaryStep = _palVaryStepStop; + } + } + + // We don't need updates anymore, if we reached end-position + if (_palVaryStep == _palVaryStepStop) + palVaryRemoveTimer(); + if (_palVaryStep == 0) + _palVaryResourceId = -1; + + // Calculate inbetween palette + Sci::Color inbetween; + int16 color; + for (int colorNr = 1; colorNr < 255; colorNr++) { + inbetween.used = _sysPalette.colors[colorNr].used; + color = _palVaryTargetPalette.colors[colorNr].r - _palVaryOriginPalette.colors[colorNr].r; + inbetween.r = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].r; + color = _palVaryTargetPalette.colors[colorNr].g - _palVaryOriginPalette.colors[colorNr].g; + inbetween.g = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].g; + color = _palVaryTargetPalette.colors[colorNr].b - _palVaryOriginPalette.colors[colorNr].b; + inbetween.b = ((color * _palVaryStep) / 64) + _palVaryOriginPalette.colors[colorNr].b; + + if (memcmp(&inbetween, &_sysPalette.colors[colorNr], sizeof(Sci::Color))) { + _sysPalette.colors[colorNr] = inbetween; + _sysPaletteChanged = true; + } + } + + if ((_sysPaletteChanged) && (setPalette) && (_screen->_picNotValid == 0)) { + setOnScreen(); + _sysPaletteChanged = false; + } } } // End of namespace Sci diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h index 46fec48739..6af1d5a490 100644 --- a/engines/sci/graphics/palette.h +++ b/engines/sci/graphics/palette.h @@ -36,20 +36,26 @@ class Screen; */ class GfxPalette { public: - GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool autoSetPalette = true); + GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool useMerging); ~GfxPalette(); - void createFromData(byte *data, Palette *paletteOut); + bool isMerging(); + + void setDefault(); + void createFromData(byte *data, int bytesLeft, Palette *paletteOut); bool setAmiga(); void modifyAmigaPalette(byte *data); void setEGA(); void set(Palette *sciPal, bool force, bool forceRealMerge = false); - bool merge(Palette *pFrom, Palette *pTo, bool force, bool forceRealMerge); - uint16 matchColor(Palette *pPal, byte r, byte g, byte b); + bool insert(Palette *newPalette, Palette *destPalette); + bool merge(Palette *pFrom, bool force, bool forceRealMerge); + uint16 matchColor(byte r, byte g, byte b); void getSys(Palette *pal); void setOnScreen(); + void drewPicture(GuiResourceId pictureId); + bool kernelSetFromResource(GuiResourceId resourceId, bool force); void kernelSetFlag(uint16 fromColor, uint16 toColor, uint16 flag); void kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag); @@ -59,25 +65,46 @@ public: void kernelAnimateSet(); void kernelAssertPalette(GuiResourceId resourceId); - void startPalVary(uint16 paletteId, uint16 ticks); - void togglePalVary(bool pause); - void stopPalVary(); + void kernelSyncScreenPalette(); + + bool kernelPalVaryInit(GuiResourceId resourceId, uint16 ticks, uint16 stepStop, uint16 direction); + int16 kernelPalVaryReverse(int16 ticks, uint16 stepStop, int16 direction); + int16 kernelPalVaryGetCurrentStep(); + int16 kernelPalVaryChangeTarget(GuiResourceId resourceId); + void kernelPalVaryChangeTicks(uint16 ticks); + void kernelPalVaryPause(bool pause); + void kernelPalVaryDeinit(); + void palVaryUpdate(); + void palVaryPrepareForTransition(); + void palVaryProcess(int signal, bool setPalette); Palette _sysPalette; private: + void palVaryInit(); + void palVaryInstallTimer(); + void palVaryRemoveTimer(); + bool palVaryLoadTargetPalette(GuiResourceId resourceId); static void palVaryCallback(void *refCon); - void doPalVary(); + void palVaryIncreaseSignal(); GfxScreen *_screen; ResourceManager *_resMan; - int16 _palVaryId; - uint32 _palVaryStart; - uint32 _palVaryEnd; bool _sysPaletteChanged; + bool _useMerging; Common::Array<PalSchedule> _schedules; + + GuiResourceId _palVaryResourceId; + Palette _palVaryOriginPalette; + Palette _palVaryTargetPalette; + int16 _palVaryStep; + int16 _palVaryStepStop; + int16 _palVaryDirection; + uint16 _palVaryTicks; + int _palVaryPaused; + int _palVarySignal; }; } // End of namespace Sci diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index a59153f116..7cd37b6f46 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -109,7 +109,7 @@ void GfxPicture::drawSci11Vga() { Palette palette; // Create palette and set it - _palette->createFromData(inbuffer + palette_data_ptr, &palette); + _palette->createFromData(inbuffer + palette_data_ptr, size - palette_data_ptr, &palette); _palette->set(&palette, true); // display Cel-data @@ -147,7 +147,7 @@ void GfxPicture::drawSci32Vga(int16 celNo) { if ((celNo == -1) || (celNo == 0)) { // Create palette and set it - _palette->createFromData(inbuffer + palette_data_ptr, &palette); + _palette->createFromData(inbuffer + palette_data_ptr, size - palette_data_ptr, &palette); _palette->set(&palette, true); } if (celNo != -1) { @@ -437,7 +437,7 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { memcpy(&EGApalettes[i], &vector_defaultEGApalette, sizeof(vector_defaultEGApalette)); memcpy(&EGApriority, &vector_defaultEGApriority, sizeof(vector_defaultEGApriority)); - if (strcmp(g_sci->getGameID(), "iceman") == 0) { + if (g_sci->getGameId() == GID_ICEMAN) { // WORKAROUND: we remove certain visual&priority lines in underwater rooms of iceman, when not dithering the // picture. Normally those lines aren't shown, because they share the same color as the dithered // fill color combination. When not dithering, those lines would appear and get distracting. @@ -532,7 +532,7 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { // inside picture data for such games case PIC_OP_SET_PATTERN: if (_resourceType >= SCI_PICTURE_TYPE_SCI11) { - if (strcmp(g_sci->getGameID(), "sq4") == 0) { + if (g_sci->getGameId() == GID_SQ4) { // WORKAROUND: For SQ4 / for some pictures handle this like a terminator // This picture includes garbage data, first a set pattern w/o parameter and then short pattern // I guess that garbage is a left over from the sq4-floppy (sci1) to sq4-cd (sci1.1) conversion diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp index 1208f8ed65..8f4fe094a8 100644 --- a/engines/sci/graphics/portrait.cpp +++ b/engines/sci/graphics/portrait.cpp @@ -23,6 +23,7 @@ * */ +#include "common/archive.h" #include "common/util.h" #include "common/stack.h" #include "graphics/primitives.h" @@ -30,7 +31,6 @@ #include "sci/sci.h" #include "sci/event.h" #include "sci/engine/state.h" -#include "sci/graphics/gui.h" #include "sci/graphics/screen.h" #include "sci/graphics/palette.h" #include "sci/graphics/portrait.h" @@ -38,8 +38,8 @@ namespace Sci { -Portrait::Portrait(ResourceManager *resMan, SciEvent *event, SciGui *gui, GfxScreen *screen, GfxPalette *palette, AudioPlayer *audio, Common::String resourceName) - : _resMan(resMan), _event(event), _gui(gui), _screen(screen), _palette(palette), _audio(audio), _resourceName(resourceName) { +Portrait::Portrait(ResourceManager *resMan, EventManager *event, GfxScreen *screen, GfxPalette *palette, AudioPlayer *audio, Common::String resourceName) + : _resMan(resMan), _event(event), _screen(screen), _palette(palette), _audio(audio), _resourceName(resourceName) { init(); } @@ -166,7 +166,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint // Do animation depending on sync resource till audio is done playing uint16 syncCue; int timerPosition, curPosition; - sciEvent curEvent; + SciEvent curEvent; bool userAbort = false; while ((syncOffset < syncResource->size - 2) && (!userAbort)) { @@ -181,8 +181,8 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint // Wait till syncTime passed, then show specific animation bitmap do { - _gui->wait(1); - curEvent = _event->get(SCI_EVENT_ANY); + g_sci->getEngineState()->wait(1); + curEvent = _event->getSciEvent(SCI_EVENT_ANY); if (curEvent.type == SCI_EVENT_MOUSE_PRESS || (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) || g_engine->shouldQuit()) diff --git a/engines/sci/graphics/portrait.h b/engines/sci/graphics/portrait.h index 4b22e209a3..7da9425c9d 100644 --- a/engines/sci/graphics/portrait.h +++ b/engines/sci/graphics/portrait.h @@ -42,7 +42,7 @@ struct PortraitBitmap { */ class Portrait { public: - Portrait(ResourceManager *resMan, SciEvent *event, SciGui *gui, GfxScreen *screen, GfxPalette *palette, AudioPlayer *audio, Common::String resourceName); + Portrait(ResourceManager *resMan, EventManager *event, GfxScreen *screen, GfxPalette *palette, AudioPlayer *audio, Common::String resourceName); ~Portrait(); void setupAudio(uint16 resourceId, uint16 noun, uint16 verb, uint16 cond, uint16 seq); @@ -56,8 +56,7 @@ private: void bitsShow(); ResourceManager *_resMan; - SciEvent *_event; - SciGui *_gui; + EventManager *_event; GfxPalette *_palette; GfxScreen *_screen; AudioPlayer *_audio; diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp index cdb6fe4ae1..611db1061a 100644 --- a/engines/sci/graphics/ports.cpp +++ b/engines/sci/graphics/ports.cpp @@ -54,11 +54,10 @@ GfxPorts::~GfxPorts() { delete _menuPort; } -void GfxPorts::init(bool usesOldGfxFunctions, SciGui *gui, GfxPaint16 *paint16, GfxText16 *text16) { +void GfxPorts::init(bool usesOldGfxFunctions, GfxPaint16 *paint16, GfxText16 *text16) { int16 offTop = 10; _usesOldGfxFunctions = usesOldGfxFunctions; - _gui = gui; _paint16 = paint16; _text16 = text16; @@ -85,20 +84,44 @@ void GfxPorts::init(bool usesOldGfxFunctions, SciGui *gui, GfxPaint16 *paint16, else _styleUser = SCI_WINDOWMGR_STYLE_USER | SCI_WINDOWMGR_STYLE_TRANSPARENT; - // Jones, Slater and Hoyle 3 were called with parameter -Nw 0 0 200 320. - // Mother Goose (SCI1) uses -Nw 0 0 159 262. The game will later use SetPort so we don't need to set the other fields. + // Jones, Slater, Hoyle 3&4 and Crazy Nicks Laura Bow/Kings Quest were + // called with parameter -Nw 0 0 200 320. + // Mother Goose (SCI1) uses -Nw 0 0 159 262. The game will later use + // SetPort so we don't need to set the other fields. // This actually meant not skipping the first 10 pixellines in windowMgrPort - Common::String gameId = g_sci->getGameID(); - if (gameId == "jones" || gameId == "slater" || gameId == "hoyle3" || (gameId == "mothergoose" && getSciVersion() == SCI_VERSION_1_EARLY)) + switch (g_sci->getGameId()) { + case GID_JONES: + case GID_SLATER: + case GID_HOYLE3: + case GID_HOYLE4: + case GID_CNICK_LAURABOW: + case GID_CNICK_KQ: offTop = 0; + break; + case GID_MOTHERGOOSE: + if (getSciVersion() == SCI_VERSION_1_EARLY) + offTop = 0; + break; + case GID_FAIRYTALES: + // Mixed-Up Fairy Tales (& its demo) uses -w 26 0 200 320. If we don't + // also do this we will get not-fully-removed windows everywhere. + offTop = 26; + break; + default: + offTop = 10; + break; + } openPort(_wmgrPort); setPort(_wmgrPort); // SCI0 games till kq4 (.502 - not including) did not adjust against _wmgrPort in kNewWindow // We leave _wmgrPort top at 0, so the adjustment wont get done - if (!g_sci->_features->usesOldGfxFunctions()) + if (!g_sci->_features->usesOldGfxFunctions()) { setOrigin(0, offTop); - _wmgrPort->rect.bottom = _screen->getHeight() - offTop; + _wmgrPort->rect.bottom = _screen->getHeight() - offTop; + } else { + _wmgrPort->rect.bottom = _screen->getHeight(); + } _wmgrPort->rect.right = _screen->getWidth(); _wmgrPort->rect.moveTo(0, 0); _wmgrPort->curTop = 0; @@ -214,7 +237,7 @@ Window *GfxPorts::newWindow(const Common::Rect &dims, const Common::Rect *restor Common::Rect r; if (!pwnd) { - warning("Can't open window!"); + error("Can't open window!"); return 0; } @@ -228,6 +251,7 @@ Window *GfxPorts::newWindow(const Common::Rect &dims, const Common::Rect *restor r = dims; if (r.width() > _screen->getWidth()) { // We get invalid dimensions at least at the end of sq3 (script bug!) + // same happens very often in lsl5, sierra sci didnt fix it but it looked awful warning("fixing too large window, given left&right was %d, %d", dims.left, dims.right); r.left = 0; r.right = _screen->getWidth() - 1; @@ -496,6 +520,11 @@ void GfxPorts::priorityBandsInit(int16 bandCount, int16 top, int16 bottom) { // We fill space that is left over with the highest band (hardcoded 200 limit, because this algo isnt meant to be used on hires) for (y = _priorityBottom; y < 200; y++) _priorityBands[y] = _priorityBandCount; + + // adjust, if bottom is 200 (one over the actual screen range) - we could otherwise go possible out of bounds + // sierra sci also adjust accordingly + if (_priorityBottom == 200) + _priorityBottom--; } void GfxPorts::priorityBandsInit(byte *data) { diff --git a/engines/sci/graphics/ports.h b/engines/sci/graphics/ports.h index c8ce6b3470..818f92f44f 100644 --- a/engines/sci/graphics/ports.h +++ b/engines/sci/graphics/ports.h @@ -45,7 +45,7 @@ public: GfxPorts(SegManager *segMan, GfxScreen *screen); ~GfxPorts(); - void init(bool usesOldGfxFunctions, SciGui *gui, GfxPaint16 *paint16, GfxText16 *text16); + void init(bool usesOldGfxFunctions, GfxPaint16 *paint16, GfxText16 *text16); void kernelSetActive(uint16 portId); Common::Rect kernelGetPicWindow(int16 &picTop, int16 &picLeft); @@ -102,7 +102,6 @@ private: typedef Common::List<Port *> PortList; SegManager *_segMan; - SciGui *_gui; GfxPaint16 *_paint16; GfxScreen *_screen; GfxText16 *_text16; diff --git a/engines/sci/graphics/robot.cpp b/engines/sci/graphics/robot.cpp index 2f711eb58a..d926e037f4 100644 --- a/engines/sci/graphics/robot.cpp +++ b/engines/sci/graphics/robot.cpp @@ -31,17 +31,17 @@ namespace Sci { #ifdef ENABLE_SCI32 -Robot::Robot(ResourceManager *resMan, GfxScreen *screen, GuiResourceId resourceId) +GfxRobot::GfxRobot(ResourceManager *resMan, GfxScreen *screen, GuiResourceId resourceId) : _resMan(resMan), _screen(screen), _resourceId(resourceId) { assert(resourceId != -1); initData(resourceId); } -Robot::~Robot() { +GfxRobot::~GfxRobot() { _resMan->unlockResource(_resource); } -void Robot::initData(GuiResourceId resourceId) { +void GfxRobot::initData(GuiResourceId resourceId) { _resource = _resMan->findResource(ResourceId(kResourceTypeRobot, resourceId), true); if (!_resource) { error("robot resource %d not found", resourceId); @@ -158,7 +158,7 @@ void Robot::initData(GuiResourceId resourceId) { // TODO: just trying around in here... -void Robot::draw() { +void GfxRobot::draw() { byte *bitmapData = _resourceData + ROBOT_FILE_STARTOFDATA; int x, y; //int frame; diff --git a/engines/sci/graphics/robot.h b/engines/sci/graphics/robot.h index 009b76a91c..80dae95f95 100644 --- a/engines/sci/graphics/robot.h +++ b/engines/sci/graphics/robot.h @@ -31,10 +31,10 @@ namespace Sci { #define ROBOT_FILE_STARTOFDATA 58 #ifdef ENABLE_SCI32 -class Robot { +class GfxRobot { public: - Robot(ResourceManager *resMan, GfxScreen *screen, GuiResourceId resourceId); - ~Robot(); + GfxRobot(ResourceManager *resMan, GfxScreen *screen, GuiResourceId resourceId); + ~GfxRobot(); void draw(); diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index 0e054d5a76..488bb83ab3 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -35,8 +35,35 @@ namespace Sci { -GfxScreen::GfxScreen(ResourceManager *resMan, int16 width, int16 height, int upscaledHires) : - _resMan(resMan), _width(width), _height(height), _upscaledHires(upscaledHires) { +GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { + + // Scale the screen, if needed + _upscaledHires = GFX_SCREEN_UPSCALED_DISABLED; + + // King's Quest 6 and Gabriel Knight 1 have hires content, gk1/cd was able + // to provide that under DOS as well, but as gk1/floppy does support + // upscaled hires scriptswise, but doesn't actually have the hires content + // we need to limit it to platform windows. + if (g_sci->getPlatform() == Common::kPlatformWindows) { + if (g_sci->getGameId() == GID_KQ6) + _upscaledHires = GFX_SCREEN_UPSCALED_640x440; +#ifdef ENABLE_SCI32 + if (g_sci->getGameId() == GID_GK1) + _upscaledHires = GFX_SCREEN_UPSCALED_640x480; +#endif + } + + if (_resMan->detectHires()) { + _width = 640; + _height = 480; + } else { + _width = 320; + _height = 200; + } + + // 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; @@ -80,8 +107,9 @@ GfxScreen::GfxScreen(ResourceManager *resMan, int16 width, int16 height, int ups _unditherState = true; if (_resMan->isVGA() || (_resMan->isAmiga32color())) { - // It's not 100% accurate to set white to be 255 for amiga 32-color games - // 255 is defined as white in our sci at all times, so it doesnt matter + // It is not 100% accurate to set white to be 255 for Amiga 32-color + // games. But 255 is defined as white in our SCI at all times, so it + // doesn't matter. _colorWhite = 255; if (getSciVersion() >= SCI_VERSION_1_1) _colorDefaultVectorData = 255; @@ -97,9 +125,9 @@ GfxScreen::GfxScreen(ResourceManager *resMan, int16 width, int16 height, int ups if (_resMan->isSci11Mac() && getSciVersion() == SCI_VERSION_1_1) { // For SCI1.1 Mac, we need to expand the screen to accommodate for // the icon bar. Of course, both KQ6 and QFG1 VGA differ in size. - if (!scumm_stricmp(g_sci->getGameID(), "kq6")) + if (g_sci->getGameId() == GID_KQ6) initGraphics(_displayWidth, _displayHeight + 26, _displayWidth > 320); - else if (!scumm_stricmp(g_sci->getGameID(), "qfg1")) + else if (g_sci->getGameId() == GID_QFG1) initGraphics(_displayWidth, _displayHeight + 20, _displayWidth > 320); else error("Unknown SCI1.1 Mac game"); @@ -141,7 +169,10 @@ void GfxScreen::copyRectToScreen(const Common::Rect &rect) { } } -// This copies a rect to screen w/o scaling adjustment and is only meant to be used on hires graphics used in upscaled hires mode +/** + * This copies a rect to screen w/o scaling adjustment and is only meant to be + * used on hires graphics used in upscaled hires mode. + */ void GfxScreen::copyDisplayRectToScreen(const Common::Rect &rect) { if (!_upscaledHires) error("copyDisplayRectToScreen: not in upscaled hires mode"); @@ -192,15 +223,42 @@ void GfxScreen::putPixel(int x, int y, byte drawMask, byte color, byte priority, _controlScreen[offset] = control; } -// This will just change a pixel directly on displayscreen. Its supposed to get only used on upscaled-Hires games where -// hires content needs to get drawn ONTO the upscaled display screen (like japanese fonts, hires portraits, etc.) +/** + * This is used to put font pixels onto the screen - we adjust differently, so that we won't + * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted + * Sierra SCI didn't do this + */ +void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) { + int offset = (startingY + y) * _width + x; + + _visualScreen[offset] = color; + if (!_upscaledHires) { + _displayScreen[offset] = color; + } else { + int displayOffset = (_upscaledMapping[startingY] + y * 2) * _displayWidth + x * 2; + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + displayOffset += _displayWidth; + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + } +} + +/** + * This will just change a pixel directly on displayscreen. It is supposed to be + * only used on upscaled-Hires games where hires content needs to get drawn ONTO + * the upscaled display screen (like japanese fonts, hires portraits, etc.). + */ void GfxScreen::putPixelOnDisplay(int x, int y, byte color) { int offset = y * _displayWidth + x; _displayScreen[offset] = color; } -// Sierra's Bresenham line drawing -// WARNING: Do not just blindly replace this with Graphics::drawLine(), as it seems to create issues with flood fill +/** + * Sierra's Bresenham line drawing. + * WARNING: Do not replace this with Graphics::drawLine(), as this causes issues + * with flood fill, due to small difference in the Bresenham logic. + */ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { int16 left = startPoint.x; int16 top = startPoint.y; @@ -263,7 +321,8 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte } } -// We put hires kanji chars onto upscaled background, so we need to adjust coordinates. Caller gives use low-res ones +// We put hires kanji chars onto upscaled background, so we need to adjust +// coordinates. Caller gives use low-res ones. void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, uint16 chr, byte color) { byte *displayPtr = _displayScreen + y * _displayWidth * 2 + x * 2; // we don't use outline, so color 0 is actually not used @@ -400,6 +459,11 @@ void GfxScreen::bitsRestore(byte *memoryPtr) { if (!_upscaledHires) error("bitsRestore() called w/o being in upscaled hires mode"); bitsRestoreScreen(rect, memoryPtr, _displayScreen, _displayWidth); + // WORKAROUND - we are not sure what sierra is doing. If we don't do this here, portraits won't get fully removed + // from screen. Some lowres showBits() call is used for that and it's not covering the whole area + // We would need to find out inside the kq6 windows interpreter, but this here works already and seems not to have + // any side-effects. The whole hires is hacked into the interpreter, so maybe this is even right. + copyDisplayRectToScreen(rect); } } @@ -435,7 +499,19 @@ void GfxScreen::bitsRestoreDisplayScreen(Common::Rect rect, byte *&memoryPtr) { } } -void GfxScreen::setPalette(Palette*pal) { +void GfxScreen::getPalette(Palette *pal) { + // just copy palette to system + byte bpal[4 * 256]; + // Get current palette, update it and put back + g_system->grabPalette(bpal, 0, 256); + for (int16 i = 1; i < 255; i++) { + pal->colors[i].r = bpal[i * 4]; + pal->colors[i].g = bpal[i * 4 + 1]; + pal->colors[i].b = bpal[i * 4 + 2]; + } +} + +void GfxScreen::setPalette(Palette *pal) { // just copy palette to system byte bpal[4 * 256]; // Get current palette, update it and put back @@ -557,19 +633,20 @@ void GfxScreen::debugShowMap(int mapNo) { copyToScreen(); } -void GfxScreen::scale2x(byte *src, byte *dst, int16 srcWidth, int16 srcHeight) { - int newWidth = srcWidth * 2; - byte *srcPtr = src; +void GfxScreen::scale2x(const byte *src, byte *dst, int16 srcWidth, int16 srcHeight) { + const int newWidth = srcWidth * 2; + const byte *srcPtr = src; for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { - int destOffset = y * 2 * newWidth + x * 2; - dst[destOffset] = *srcPtr; - dst[destOffset + 1] = *srcPtr; - dst[destOffset + newWidth] = *srcPtr; - dst[destOffset + newWidth + 1] = *srcPtr; - srcPtr++; + const byte color = *srcPtr++; + dst[0] = color; + dst[1] = color; + dst[newWidth] = color; + dst[newWidth + 1] = color; + dst += 2; } + dst += newWidth; } } @@ -578,6 +655,24 @@ void GfxScreen::adjustToUpscaledCoordinates(int16 &y, int16 &x) { y = _upscaledMapping[y]; } +void GfxScreen::adjustBackUpscaledCoordinates(int16 &y, int16 &x) { + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_640x400: + x /= 2; + y /= 2; + break; + case GFX_SCREEN_UPSCALED_640x440: + x /= 2; + y = (y * 5) / 11; + break; + case GFX_SCREEN_UPSCALED_640x480: + x /= 2; + y = (y * 5) / 12; + default: + break; + } +} + int16 GfxScreen::kernelPicNotValid(int16 newPicNotValid) { int16 oldPicNotValid; diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index b2479e9735..f1e3d028a8 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -50,16 +50,21 @@ enum GfxScreenMasks { GFX_SCREEN_MASK_ALL = GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY|GFX_SCREEN_MASK_CONTROL }; -#define SCI_SCREEN_UNDITHERMEMORIAL_SIZE 256 +enum { + SCI_SCREEN_UNDITHERMEMORIAL_SIZE = 256 +}; /** - * Screen class, actually creates 3 (4) screens internally - which is visual/display (for the user), - * priority (contains priority information) and control (contains control information). Handles all operations to it - * and copies parts of visual/display screen to the actual screen, so the user can really see it. + * Screen class, actually creates 3 (4) screens internally: + * - visual/display (for the user), + * - priority (contains priority information) and + * - control (contains control information). + * Handles all operations to it and copies parts of visual/display screen to + * the actual screen, so the user can really see it. */ class GfxScreen { public: - GfxScreen(ResourceManager *resMan, int16 width = 320, int16 height = 200, int upscaledHires = GFX_SCREEN_UPSCALED_DISABLED); + GfxScreen(ResourceManager *resMan); ~GfxScreen(); uint16 getWidth() { return _width; } @@ -78,15 +83,16 @@ public: byte getDrawingMask(byte color, byte prio, byte control); void putPixel(int x, int y, byte drawMask, byte color, byte prio, byte control); + void putFontPixel(int startingY, int x, int y, byte color); void putPixelOnDisplay(int x, int y, byte color); void drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte prio, byte control); void drawLine(int16 left, int16 top, int16 right, int16 bottom, byte color, byte prio, byte control) { drawLine(Common::Point(left, top), Common::Point(right, bottom), color, prio, control); } - int getUpscaledHires() { + int getUpscaledHires() const { return _upscaledHires; } - bool getUnditherState() { + bool getUnditherState() const { return _unditherState; } void putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, uint16 chr, byte color); @@ -100,13 +106,15 @@ public: void bitsGetRect(byte *memoryPtr, Common::Rect *destRect); void bitsRestore(byte *memoryPtr); - void setPalette(Palette*pal); + void getPalette(Palette *pal); + void setPalette(Palette *pal); void setVerticalShakePos(uint16 shakePos); - void scale2x(byte *src, byte *dst, int16 srcWidth, int16 srcHeight); + void scale2x(const byte *src, byte *dst, int16 srcWidth, int16 srcHeight); void adjustToUpscaledCoordinates(int16 &y, int16 &x); + void adjustBackUpscaledCoordinates(int16 &y, int16 &x); void dither(bool addToFlag); void debugUnditherSetState(bool flag); @@ -138,27 +146,34 @@ private: bool _unditherState; int16 _unditherMemorial[SCI_SCREEN_UNDITHERMEMORIAL_SIZE]; - // these screens have the real resolution of the game engine (320x200 for SCI0/SCI1/SCI11 games, 640x480 for SCI2 games) - // SCI0 games will be dithered in here at any time + // These screens have the real resolution of the game engine (320x200 for + // SCI0/SCI1/SCI11 games, 640x480 for SCI2 games). SCI0 games will be + // dithered in here at any time. byte *_visualScreen; byte *_priorityScreen; byte *_controlScreen; - // this screen is the one that is actually displayed to the user. It may be 640x400 for japanese SCI1 games - // SCI0 games may be undithered in here. Only read from this buffer for Save/ShowBits usage. + // This screen is the one that is actually displayed to the user. It may be + // 640x400 for japanese SCI1 games. SCI0 games may be undithered in here. + // Only read from this buffer for Save/ShowBits usage. byte *_displayScreen; Common::Rect getScaledRect(Common::Rect rect); ResourceManager *_resMan; - // this is a pointer to the currently active screen (changing it only required for debug purposes) + /** + * Pointer to the currently active screen (changing it only required for + * debug purposes). + */ byte *_activeScreen; - // this variable defines, if upscaled hires is active and what upscaled mode is used + // This variable defines, if upscaled hires is active and what upscaled mode + // is used. int _upscaledHires; - // this here holds a translation for vertical coordinates between native (visual) and actual (display) screen + // This here holds a translation for vertical coordinates between native + // (visual) and actual (display) screen. int _upscaledMapping[SCI_SCREEN_UPSCALEDMAXHEIGHT + 1]; }; diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp index 952d13fbbd..4aa2346aed 100644 --- a/engines/sci/graphics/text16.cpp +++ b/engines/sci/graphics/text16.cpp @@ -73,28 +73,6 @@ void GfxText16::SetFont(GuiResourceId fontId) { _ports->_curPort->fontHeight = _font->getHeight(); } -void GfxText16::CodeSetFonts(int argc, reg_t *argv) { - int i; - - delete _codeFonts; - _codeFontsCount = argc; - _codeFonts = new GuiResourceId[argc]; - for (i = 0; i < argc; i++) { - _codeFonts[i] = (GuiResourceId)argv[i].toUint16(); - } -} - -void GfxText16::CodeSetColors(int argc, reg_t *argv) { - int i; - - delete _codeColors; - _codeColorsCount = argc; - _codeColors = new uint16[argc]; - for (i = 0; i < argc; i++) { - _codeColors[i] = argv[i].toUint16(); - } -} - void GfxText16::ClearChar(int16 chr) { if (_ports->_curPort->penMode != 1) return; @@ -106,10 +84,10 @@ void GfxText16::ClearChar(int16 chr) { _paint16->eraseRect(rect); } -// This internal function gets called as soon as a '|' is found in a text -// It will process the encountered code and set new font/set color -// We only support one-digit codes currently, don't know if multi-digit codes are possible -// Returns textcode character count +// This internal function gets called as soon as a '|' is found in a text. It +// will process the encountered code and set new font/set color. We only support +// one-digit codes currently, don't know if multi-digit codes are possible. +// Returns textcode character count. int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int16 orgPenColor) { const char *textCode = text; int16 textCodeSize = 0; @@ -155,9 +133,11 @@ int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int1 static const uint16 text16_punctuationSjis[] = { 0x9F82, 0xA182, 0xA382, 0xA582, 0xA782, 0xC182, 0xA782, 0xC182, 0xE182, 0xE382, 0xE582, 0xEC82, 0x4083, 0x4283, 0x4483, 0x4683, 0x4883, 0x6283, 0x8383, 0x8583, 0x8783, 0x8E83, 0x9583, 0x9683, - 0x5B81, 0x4181, 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0 }; + 0x5B81, 0x4181, 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0 +}; -// return max # of chars to fit maxwidth with full words, does not include breaking space +// return max # of chars to fit maxwidth with full words, does not include +// breaking space int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgFontId) { uint16 curChar = 0; int16 maxChars = 0, curCharCount = 0; @@ -217,9 +197,10 @@ int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgF uint16 nextChar; - // we remove the last char only, if maxWidth was actually equal width before adding the last char - // otherwise we won't get the same cutting as in sierra pc98 sci - // note: changing the while() instead will NOT WORK. it would break all sorts of regular sci games + // We remove the last char only, if maxWidth was actually equal width + // before adding the last char. Otherwise we won't get the same cutting + // as in sierra pc98 sci. Note: changing the while() instead will NOT + // WORK. it would break all sorts of regular sci games. if (maxWidth == (width - _font->getCharWidth(curChar))) { maxChars--; if (curChar > 0xFF) @@ -453,11 +434,14 @@ void GfxText16::Box(const char *text, int16 bshow, const Common::Rect &rect, Tex _ports->penColor(orgPenColor); if (doubleByteMode) { - // kanji is written by pc98 rom to screen directly. Because of GetLongest() behaviour (not cutting off the last - // char, that causes a new line), results in the script thinking that the text would need less space. The coordinate - // adjustment in fontsjis.cpp handles the incorrect centering because of that and this code actually shows all of - // the chars - if we don't do this, the scripts will only show most of the chars, but the last few pixels won't get - // shown most of the time. + // Kanji is written by pc98 rom to screen directly. Because of + // GetLongest() behaviour (not cutting off the last char, that causes a + // new line), results in the script thinking that the text would need + // less space. The coordinate adjustment in fontsjis.cpp handles the + // incorrect centering because of that and this code actually shows all + // of the chars - if we don't do this, the scripts will only show most + // of the chars, but the last few pixels won't get shown most of the + // time. Common::Rect kanjiRect = rect; _ports->offsetRect(kanjiRect); kanjiRect.left &= 0xFFC; @@ -478,7 +462,8 @@ void GfxText16::Draw_String(const char *text) { _ports->penColor(orgPenColor); } -// Sierra did this in their PC98 interpreter only, they identify a text as being sjis and then switch to font 900 +// Sierra did this in their PC98 interpreter only, they identify a text as being +// sjis and then switch to font 900 bool GfxText16::SwitchToFont900OnSjis(const char *text) { byte firstChar = (*(const byte *)text++); if (((firstChar >= 0x81) && (firstChar <= 0x9F)) || ((firstChar >= 0xE0) && (firstChar <= 0xEF))) { @@ -488,4 +473,35 @@ bool GfxText16::SwitchToFont900OnSjis(const char *text) { return false; } +void GfxText16::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { + Common::Rect rect(0, 0, 0, 0); + Size(rect, text, font, maxWidth); + *textWidth = rect.width(); + *textHeight = rect.height(); +} + +// Used SCI1+ for text codes +void GfxText16::kernelTextFonts(int argc, reg_t *argv) { + int i; + + delete _codeFonts; + _codeFontsCount = argc; + _codeFonts = new GuiResourceId[argc]; + for (i = 0; i < argc; i++) { + _codeFonts[i] = (GuiResourceId)argv[i].toUint16(); + } +} + +// Used SCI1+ for text codes +void GfxText16::kernelTextColors(int argc, reg_t *argv) { + int i; + + delete _codeColors; + _codeColorsCount = argc; + _codeColors = new uint16[argc]; + for (i = 0; i < argc; i++) { + _codeColors[i] = argv[i].toUint16(); + } +} + } // End of namespace Sci diff --git a/engines/sci/graphics/text16.h b/engines/sci/graphics/text16.h index 2885fc928b..71b602d116 100644 --- a/engines/sci/graphics/text16.h +++ b/engines/sci/graphics/text16.h @@ -48,8 +48,6 @@ public: GfxFont *GetFont(); void SetFont(GuiResourceId fontId); - void CodeSetFonts(int argc, reg_t *argv); - void CodeSetColors(int argc, reg_t *argv); int16 CodeProcessing(const char *&text, GuiResourceId orgFontId, int16 orgPenColor); void ClearChar(int16 chr); @@ -67,6 +65,10 @@ public: GfxFont *_font; + void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); + void kernelTextFonts(int argc, reg_t *argv); + void kernelTextColors(int argc, reg_t *argv); + private: void init(); bool SwitchToFont900OnSjis(const char *text); diff --git a/engines/sci/graphics/transitions.cpp b/engines/sci/graphics/transitions.cpp index 1976326aa9..c1f626449f 100644 --- a/engines/sci/graphics/transitions.cpp +++ b/engines/sci/graphics/transitions.cpp @@ -31,15 +31,14 @@ #include "sci/sci.h" #include "sci/engine/state.h" -#include "sci/graphics/gui.h" #include "sci/graphics/screen.h" #include "sci/graphics/palette.h" #include "sci/graphics/transitions.h" namespace Sci { -GfxTransitions::GfxTransitions(SciGui *gui, GfxScreen *screen, GfxPalette *palette, bool isVGA) - : _gui(gui), _screen(screen), _palette(palette), _isVGA(isVGA) { +GfxTransitions::GfxTransitions(GfxScreen *screen, GfxPalette *palette, bool isVGA) + : _screen(screen), _palette(palette), _isVGA(isVGA) { init(); } @@ -159,7 +158,8 @@ void GfxTransitions::doit(Common::Rect picRect) { } if (_blackoutFlag) { - // We need to find out what transition we are supposed to use for blackout + // We need to find out what transition we are supposed to use for + // blackout translationEntry = translateNumber(_number, blackoutTransitionIDs); if (translationEntry) { doTransition(translationEntry->newId, true); @@ -168,12 +168,15 @@ void GfxTransitions::doit(Common::Rect picRect) { } } + _palette->palVaryPrepareForTransition(); + // Now we do the actual transition to the new screen doTransition(_number, false); if (picRect.bottom != _screen->getHeight()) { // TODO: this is a workaround for lsl6 not showing menubar when playing - // There is some new code in the sierra sci in ShowPic that seems to do something similar to this + // There is some new code in the sierra sci in ShowPic that seems to do + // something similar to this _screen->copyToScreen(); g_system->updateScreen(); } @@ -181,8 +184,8 @@ void GfxTransitions::doit(Common::Rect picRect) { _screen->_picNotValid = 0; } -// This may get called twice, if blackoutFlag is set. It will get once called with blackoutFlag set and another time -// with no blackoutFlag. +// This may get called twice, if blackoutFlag is set. It will get once called +// with blackoutFlag set and another time with no blackoutFlag. void GfxTransitions::doTransition(int16 number, bool blackoutFlag) { if (number != SCI_TRANSITIONS_FADEPALETTE) { setNewPalette(blackoutFlag); @@ -193,7 +196,7 @@ void GfxTransitions::doTransition(int16 number, bool blackoutFlag) { verticalRollFromCenter(blackoutFlag); break; case SCI_TRANSITIONS_VERTICALROLL_TOCENTER: - verticalRollFromCenter(blackoutFlag); + verticalRollToCenter(blackoutFlag); break; case SCI_TRANSITIONS_HORIZONTALROLL_FROMCENTER: horizontalRollFromCenter(blackoutFlag); @@ -277,7 +280,8 @@ void GfxTransitions::copyRectToScreen(const Common::Rect rect, bool blackoutFlag } } -// Note: dont do too many steps in here, otherwise cpu will crap out because of the load +// Note: don't do too many steps in here, otherwise cpu will crap out because of +// the load void GfxTransitions::fadeOut() { byte oldPalette[4 * 256], workPalette[4 * 256]; int16 stepNr, colorNr; @@ -291,23 +295,24 @@ void GfxTransitions::fadeOut() { workPalette[colorNr * 4 + 2] = oldPalette[colorNr * 4 + 2] * stepNr / 100; } g_system->setPalette(workPalette + 4, 1, 254); - _gui->wait(2); + g_sci->getEngineState()->wait(2); } } -// Note: dont do too many steps in here, otherwise cpu will crap out because of the load +// Note: don't do too many steps in here, otherwise cpu will crap out because of +// the load void GfxTransitions::fadeIn() { int16 stepNr; for (stepNr = 0; stepNr <= 100; stepNr += 10) { _palette->kernelSetIntensity(1, 255, stepNr, true); - _gui->wait(2); + g_sci->getEngineState()->wait(2); } } -// pixelates the new picture over the old one - works against the whole screen +// Pixelates the new picture over the old one - works against the whole screen. // TODO: it seems this needs to get applied on _picRect only if possible -void GfxTransitions::pixelation (bool blackoutFlag) { +void GfxTransitions::pixelation(bool blackoutFlag) { uint16 mask = 0x40, stepNr = 0; Common::Rect pixelRect; @@ -327,7 +332,7 @@ void GfxTransitions::pixelation (bool blackoutFlag) { } while (mask != 0x40); } -// like pixelation but uses 8x8 blocks - works against the whole screen +// Like pixelation but uses 8x8 blocks - works against the whole screen. // TODO: it seems this needs to get applied on _picRect only if possible void GfxTransitions::blocks(bool blackoutFlag) { uint16 mask = 0x40, stepNr = 0; @@ -349,7 +354,8 @@ void GfxTransitions::blocks(bool blackoutFlag) { } while (mask != 0x40); } -// directly shows new screen starting up/down/left/right and going to the opposite direction - works on _picRect area only +// Directly shows new screen starting up/down/left/right and going to the +// opposite direction - works on _picRect area only void GfxTransitions::straight(int16 number, bool blackoutFlag) { int16 stepNr = 0; Common::Rect newScreenRect = _picRect; @@ -401,7 +407,8 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) { } } -// scroll old screen (up/down/left/right) and insert new screen that way - works on _picRect area only +// Scroll old screen (up/down/left/right) and insert new screen that way - works +// on _picRect area only. void GfxTransitions::scroll(int16 number) { int16 screenWidth, screenHeight; byte *oldScreenPtr; @@ -440,8 +447,12 @@ void GfxTransitions::scroll(int16 number) { } stepNr++; } - if ((stepNr & 1) == 0) - g_system->updateScreen(); + if ((stepNr & 1) == 0) { + if (g_system->getMillis() - g_sci->getEngineState()->_screenUpdateTime >= 1000 / 60) { + g_system->updateScreen(); + g_sci->getEngineState()->_screenUpdateTime = g_system->getMillis(); + } + } break; case SCI_TRANSITIONS_SCROLL_RIGHT: @@ -461,8 +472,12 @@ void GfxTransitions::scroll(int16 number) { } stepNr++; } - if ((stepNr & 1) == 0) - g_system->updateScreen(); + if ((stepNr & 1) == 0) { + if (g_system->getMillis() - g_sci->getEngineState()->_screenUpdateTime >= 1000 / 60) { + g_system->updateScreen(); + g_sci->getEngineState()->_screenUpdateTime = g_system->getMillis(); + } + } break; case SCI_TRANSITIONS_SCROLL_UP: @@ -503,7 +518,8 @@ void GfxTransitions::scroll(int16 number) { } } -// vertically displays new screen starting from center - works on _picRect area only +// Vertically displays new screen starting from center - works on _picRect area +// only void GfxTransitions::verticalRollFromCenter(bool blackoutFlag) { Common::Rect leftRect = Common::Rect(_picRect.left + (_picRect.width() / 2) -1, _picRect.top, _picRect.left + (_picRect.width() / 2), _picRect.bottom); Common::Rect rightRect = Common::Rect(leftRect.right, _picRect.top, leftRect.right + 1, _picRect.bottom); @@ -519,7 +535,8 @@ void GfxTransitions::verticalRollFromCenter(bool blackoutFlag) { } } -// vertically displays new screen starting from edges - works on _picRect area only +// Vertically displays new screen starting from edges - works on _picRect area +// only void GfxTransitions::verticalRollToCenter(bool blackoutFlag) { Common::Rect leftRect = Common::Rect(_picRect.left, _picRect.top, _picRect.left + 1, _picRect.bottom); Common::Rect rightRect = Common::Rect(leftRect.right - 1, _picRect.top, leftRect.right, _picRect.bottom); @@ -531,7 +548,8 @@ void GfxTransitions::verticalRollToCenter(bool blackoutFlag) { } } -// horizontally displays new screen starting from center - works on _picRect area only +// Horizontally displays new screen starting from center - works on _picRect +// area only void GfxTransitions::horizontalRollFromCenter(bool blackoutFlag) { Common::Rect upperRect = Common::Rect(_picRect.left, _picRect.top + (_picRect.height() / 2) - 1, _picRect.right, _picRect.top + (_picRect.height() / 2)); Common::Rect lowerRect = Common::Rect(upperRect.left, upperRect.bottom, upperRect.right, upperRect.bottom + 1); @@ -547,7 +565,8 @@ void GfxTransitions::horizontalRollFromCenter(bool blackoutFlag) { } } -// horizontally displays new screen starting from upper and lower edge - works on _picRect area only +// Horizontally displays new screen starting from upper and lower edge - works +// on _picRect area only void GfxTransitions::horizontalRollToCenter(bool blackoutFlag) { Common::Rect upperRect = Common::Rect(_picRect.left, _picRect.top, _picRect.right, _picRect.top + 1); Common::Rect lowerRect = Common::Rect(upperRect.left, _picRect.bottom - 1, upperRect.right, _picRect.bottom); @@ -559,8 +578,8 @@ void GfxTransitions::horizontalRollToCenter(bool blackoutFlag) { } } -// diagonally displays new screen starting from center - works on _picRect area only -// assumes that height of rect is larger than width +// Diagonally displays new screen starting from center - works on _picRect area +// only. Assumes that height of rect is larger than width. void GfxTransitions::diagonalRollFromCenter(bool blackoutFlag) { int16 halfHeight = _picRect.height() / 2; Common::Rect upperRect(_picRect.left + halfHeight - 2, _picRect.top + halfHeight, _picRect.right - halfHeight + 1, _picRect.top + halfHeight + 1); @@ -589,8 +608,8 @@ void GfxTransitions::diagonalRollFromCenter(bool blackoutFlag) { } } -// diagonally displays new screen starting from edges - works on _picRect area only -// assumes that height of rect is larger than width +// Diagonally displays new screen starting from edges - works on _picRect area +// only. Assumes that height of rect is larger than width. void GfxTransitions::diagonalRollToCenter(bool blackoutFlag) { Common::Rect upperRect(_picRect.left, _picRect.top, _picRect.right, _picRect.top + 1); Common::Rect lowerRect(_picRect.left, _picRect.bottom - 1, _picRect.right, _picRect.bottom); diff --git a/engines/sci/graphics/transitions.h b/engines/sci/graphics/transitions.h index 9a1a412d5b..788cefabca 100644 --- a/engines/sci/graphics/transitions.h +++ b/engines/sci/graphics/transitions.h @@ -65,7 +65,7 @@ class Screen; */ class GfxTransitions { public: - GfxTransitions(SciGui *gui, GfxScreen *screen, GfxPalette *palette, bool isVGA); + GfxTransitions(GfxScreen *screen, GfxPalette *palette, bool isVGA); ~GfxTransitions(); void setup(int16 number, bool blackoutFlag); @@ -92,7 +92,6 @@ private: void diagonalRollToCenter(bool blackoutFlag); void updateScreenAndWait(int msec); - SciGui *_gui; GfxScreen *_screen; GfxPalette *_palette; diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index 2ba14fbd8f..93df45820c 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -62,6 +62,7 @@ void GfxView::initData(GuiResourceId resourceId) { error("view resource %d not found", resourceId); } _resourceData = _resource->data; + _resourceSize = _resource->size; byte *celData, *loopData; uint16 celOffset; @@ -75,12 +76,28 @@ void GfxView::initData(GuiResourceId resourceId) { byte seekEntry; bool isEGA = false; bool isCompressed = true; + ViewType curViewType = _resMan->getViewType(); _loopCount = 0; _embeddedPal = false; _EGAmapping = NULL; + _isSci2Hires = false; + + // If we find an SCI1/SCI1.1 view (not amiga), we switch to that type for + // EGA. This could get used to make view patches for EGA games, where the + // new views include more colors. Users could manually adjust old views to + // make them look better (like removing dithered colors that aren't caught + // by our undithering or even improve the graphics overall). + if (curViewType == kViewEga) { + if (_resourceData[1] == 0x80) { + curViewType = kViewVga; + } else { + if (READ_LE_UINT16(_resourceData + 4) == 1) + curViewType = kViewVga11; + } + } - switch (_resMan->getViewType()) { + switch (curViewType) { case kViewEga: // View-format SCI0 (and Amiga 16 colors) isEGA = true; case kViewAmiga: // View-format Amiga (32 colors) @@ -95,19 +112,21 @@ void GfxView::initData(GuiResourceId resourceId) { palOffset = READ_LE_UINT16(_resourceData + 6); if (palOffset && palOffset != 0x100) { - // Some SCI0/SCI01 games also have an offset set. It seems that it points to a 16-byte mapping table - // but on those games using that mapping will actually screw things up. - // On the other side: vga sci1 games have this pointing to a VGA palette - // and ega sci1 games have this pointing to a 8x16 byte mapping table that needs to get applied then + // Some SCI0/SCI01 games also have an offset set. It seems that it + // points to a 16-byte mapping table but on those games using that + // mapping will actually screw things up. On the other side: VGA + // SCI1 games have this pointing to a VGA palette and EGA SCI1 games + // have this pointing to a 8x16 byte mapping table that needs to get + // applied then. if (!isEGA) { - _palette->createFromData(&_resourceData[palOffset], &_viewPalette); + _palette->createFromData(&_resourceData[palOffset], _resourceSize - palOffset, &_viewPalette); _embeddedPal = true; } else { // Only use the EGA-mapping, when being SCI1 if (getSciVersion() >= SCI_VERSION_1_EGA) { _EGAmapping = &_resourceData[palOffset]; for (EGAmapNr = 0; EGAmapNr < SCI_VIEW_EGAMAPPING_COUNT; EGAmapNr++) { - if (memcmp(_EGAmapping, EGAmappingStraight, SCI_VIEW_EGAMAPPING_SIZE)!=0) + if (memcmp(_EGAmapping, EGAmappingStraight, SCI_VIEW_EGAMAPPING_SIZE) != 0) break; _EGAmapping += SCI_VIEW_EGAMAPPING_SIZE; } @@ -169,12 +188,14 @@ void GfxView::initData(GuiResourceId resourceId) { case kViewVga11: // View-format SCI1.1+ // HeaderSize:WORD LoopCount:BYTE Unknown:BYTE Version:WORD Unknown:WORD PaletteOffset:WORD - headerSize = READ_SCI11ENDIAN_UINT16(_resourceData + 0) + 2; // headerSize is not part of the header, so its added + headerSize = READ_SCI11ENDIAN_UINT16(_resourceData + 0) + 2; // headerSize is not part of the header, so it's added assert(headerSize >= 16); _loopCount = _resourceData[2]; assert(_loopCount); + _isSci2Hires = _resourceData[5] == 1 ? true : false; palOffset = READ_SCI11ENDIAN_UINT32(_resourceData + 8); - // FIXME: After LoopCount there is another byte and its set for view 50 within Laura Bow 2 CD, check what it means + // FIXME: After LoopCount there is another byte and its set for view 50 + // within Laura Bow 2 CD, check what it means. loopData = _resourceData + headerSize; loopSize = _resourceData[12]; @@ -183,7 +204,7 @@ void GfxView::initData(GuiResourceId resourceId) { assert(celSize >= 32); if (palOffset) { - _palette->createFromData(&_resourceData[palOffset], &_viewPalette); + _palette->createFromData(&_resourceData[palOffset], _resourceSize - palOffset, &_viewPalette); _embeddedPal = true; } @@ -221,6 +242,9 @@ void GfxView::initData(GuiResourceId resourceId) { cel->offsetEGA = 0; cel->offsetRLE = READ_SCI11ENDIAN_UINT32(celData + 24); cel->offsetLiteral = READ_SCI11ENDIAN_UINT32(celData + 28); + // GK1-hires content is actually uncompressed, we need to swap both so that we process it as such + if ((cel->offsetRLE) && (!cel->offsetLiteral)) + SWAP(cel->offsetRLE, cel->offsetLiteral); cel->rawBitmap = 0; if (_loop[loopNo].mirrorFlag) @@ -236,65 +260,68 @@ void GfxView::initData(GuiResourceId resourceId) { } } -GuiResourceId GfxView::getResourceId() { +GuiResourceId GfxView::getResourceId() const { return _resourceId; } -int16 GfxView::getWidth(int16 loopNo, int16 celNo) { - loopNo = CLIP<int16>(loopNo, 0, _loopCount - 1); - celNo = CLIP<int16>(celNo, 0, _loop[loopNo].celCount - 1); - return _loopCount ? _loop[loopNo].cel[celNo].width : 0; +int16 GfxView::getWidth(int16 loopNo, int16 celNo) const { + return _loopCount ? getCelInfo(loopNo, celNo)->width : 0; } -int16 GfxView::getHeight(int16 loopNo, int16 celNo) { - loopNo = CLIP<int16>(loopNo, 0, _loopCount -1); - celNo = CLIP<int16>(celNo, 0, _loop[loopNo].celCount - 1); - return _loopCount ? _loop[loopNo].cel[celNo].height : 0; +int16 GfxView::getHeight(int16 loopNo, int16 celNo) const { + return _loopCount ? getCelInfo(loopNo, celNo)->height : 0; } -CelInfo *GfxView::getCelInfo(int16 loopNo, int16 celNo) { +const CelInfo *GfxView::getCelInfo(int16 loopNo, int16 celNo) const { + assert(_loopCount); loopNo = CLIP<int16>(loopNo, 0, _loopCount - 1); celNo = CLIP<int16>(celNo, 0, _loop[loopNo].celCount - 1); - return _loopCount ? &_loop[loopNo].cel[celNo] : NULL; + return &_loop[loopNo].cel[celNo]; } -LoopInfo *GfxView::getLoopInfo(int16 loopNo) { +uint16 GfxView::getCelCount(int16 loopNo) const { + assert(_loopCount); loopNo = CLIP<int16>(loopNo, 0, _loopCount - 1); - return _loopCount ? &_loop[loopNo] : NULL; + return _loop[loopNo].celCount; } -void GfxView::getCelRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, Common::Rect *outRect) { - CelInfo *celInfo = getCelInfo(loopNo, celNo); - if (celInfo) { - outRect->left = x + celInfo->displaceX - (celInfo->width >> 1); - outRect->right = outRect->left + celInfo->width; - outRect->bottom = y + celInfo->displaceY - z + 1; - outRect->top = outRect->bottom - celInfo->height; - } +Palette *GfxView::getPalette() { + return _embeddedPal ? &_viewPalette : NULL; +} + +bool GfxView::isSci2Hires() { + return _isSci2Hires; +} + +void GfxView::getCelRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, Common::Rect &outRect) const { + const CelInfo *celInfo = getCelInfo(loopNo, celNo); + outRect.left = x + celInfo->displaceX - (celInfo->width >> 1); + outRect.right = outRect.left + celInfo->width; + outRect.bottom = y + celInfo->displaceY - z + 1; + outRect.top = outRect.bottom - celInfo->height; } -void GfxView::getCelScaledRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, int16 scaleX, int16 scaleY, Common::Rect *outRect) { +void GfxView::getCelScaledRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, int16 scaleX, int16 scaleY, Common::Rect &outRect) const { int16 scaledDisplaceX, scaledDisplaceY; int16 scaledWidth, scaledHeight; - CelInfo *celInfo = getCelInfo(loopNo, celNo); - if (celInfo) { - // Scaling displaceX/Y, Width/Height - scaledDisplaceX = (celInfo->displaceX * scaleX) >> 7; - scaledDisplaceY = (celInfo->displaceY * scaleY) >> 7; - scaledWidth = (celInfo->width * scaleX) >> 7; - scaledHeight = (celInfo->height * scaleY) >> 7; - scaledWidth = CLIP<int16>(scaledWidth, 0, _screen->getWidth()); - scaledHeight = CLIP<int16>(scaledHeight, 0, _screen->getHeight()); - - outRect->left = x + scaledDisplaceX - (scaledWidth >> 1); - outRect->right = outRect->left + scaledWidth; - outRect->bottom = y + scaledDisplaceY - z + 1; - outRect->top = outRect->bottom - scaledHeight; - } + const CelInfo *celInfo = getCelInfo(loopNo, celNo); + + // Scaling displaceX/Y, Width/Height + scaledDisplaceX = (celInfo->displaceX * scaleX) >> 7; + scaledDisplaceY = (celInfo->displaceY * scaleY) >> 7; + scaledWidth = (celInfo->width * scaleX) >> 7; + scaledHeight = (celInfo->height * scaleY) >> 7; + scaledWidth = CLIP<int16>(scaledWidth, 0, _screen->getWidth()); + scaledHeight = CLIP<int16>(scaledHeight, 0, _screen->getHeight()); + + outRect.left = x + scaledDisplaceX - (scaledWidth >> 1); + outRect.right = outRect.left + scaledWidth; + outRect.bottom = y + scaledDisplaceY - z + 1; + outRect.top = outRect.bottom - scaledHeight; } void GfxView::unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount) { - CelInfo *celInfo = getCelInfo(loopNo, celNo); + const CelInfo *celInfo = getCelInfo(loopNo, celNo); byte *rlePtr; byte *literalPtr; uint32 pixelNo = 0, runLength; @@ -309,78 +336,42 @@ void GfxView::unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCou memset(outPtr + pixelNo, pixel & 0x0F, MIN<uint32>(runLength, pixelCount - pixelNo)); pixelNo += runLength; } - return; - } - - rlePtr = _resourceData + celInfo->offsetRLE; - if (!celInfo->offsetLiteral) { // no additional literal data - if (_resMan->isAmiga32color()) { - // decompression for amiga views - while (pixelNo < pixelCount) { - pixel = *rlePtr++; - if (pixel & 0x07) { // fill with color - runLength = pixel & 0x07; - pixel = pixel >> 3; - while (runLength-- && pixelNo < pixelCount) { - outPtr[pixelNo++] = pixel; - } - } else { // fill with transparent - runLength = pixel >> 3; - pixelNo += runLength; - } - } - return; - } else { - // decompression for data that has just one combined stream - while (pixelNo < pixelCount) { - pixel = *rlePtr++; - runLength = pixel & 0x3F; - switch (pixel & 0xC0) { - case 0: // copy bytes as-is - while (runLength-- && pixelNo < pixelCount) - outPtr[pixelNo++] = *rlePtr++; - break; - case 0x80: // fill with color - memset(outPtr + pixelNo, *rlePtr++, MIN<uint32>(runLength, pixelCount - pixelNo)); - pixelNo += runLength; - break; - case 0xC0: // fill with transparent - pixelNo += runLength; - break; - } - } - return; - } } else { - literalPtr = _resourceData + celInfo->offsetLiteral; - if (celInfo->offsetRLE) { - if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1) { - // Crazy-Ass compression for SCI1.1+ Mac + // We fill the buffer with transparent pixels, so that we can later skip + // over pixels to automatically have them transparent + // Also some RLE compressed cels are possibly ending with the last + // non-transparent pixel (is this even possible with the current code?) + memset(outPtr, _loop[loopNo].cel[celNo].clearKey, pixelCount); + + rlePtr = _resourceData + celInfo->offsetRLE; + if (!celInfo->offsetLiteral) { // no additional literal data + if (_resMan->isAmiga32color()) { + // decompression for amiga views while (pixelNo < pixelCount) { - uint32 pixelLine = pixelNo; - runLength = *rlePtr++; - pixelNo += runLength; - runLength = *rlePtr++; - while (runLength-- && pixelNo < pixelCount) { - outPtr[pixelNo] = *literalPtr++; - if (outPtr[pixelNo] == 255) - outPtr[pixelNo] = 0; - pixelNo++; + pixel = *rlePtr++; + if (pixel & 0x07) { // fill with color + runLength = pixel & 0x07; + pixel = pixel >> 3; + while (runLength-- && pixelNo < pixelCount) { + outPtr[pixelNo++] = pixel; + } + } else { // fill with transparent + runLength = pixel >> 3; + pixelNo += runLength; } - pixelNo = pixelLine + celInfo->width; } } else { - // decompression for data that has separate rle and literal streams + // decompression for data that has just one combined stream while (pixelNo < pixelCount) { pixel = *rlePtr++; runLength = pixel & 0x3F; switch (pixel & 0xC0) { case 0: // copy bytes as-is while (runLength-- && pixelNo < pixelCount) - outPtr[pixelNo++] = *literalPtr++; + outPtr[pixelNo++] = *rlePtr++; break; case 0x80: // fill with color - memset(outPtr + pixelNo, *literalPtr++, MIN<uint32>(runLength, pixelCount - pixelNo)); + memset(outPtr + pixelNo, *rlePtr++, MIN<uint32>(runLength, pixelCount - pixelNo)); pixelNo += runLength; break; case 0xC0: // fill with transparent @@ -390,15 +381,53 @@ void GfxView::unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCou } } } else { - // literal stream only, so no compression - memcpy(outPtr, literalPtr, pixelCount); + literalPtr = _resourceData + celInfo->offsetLiteral; + if (celInfo->offsetRLE) { + if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1) { + // compression for SCI1.1+ Mac + while (pixelNo < pixelCount) { + uint32 pixelLine = pixelNo; + runLength = *rlePtr++; + pixelNo += runLength; + runLength = *rlePtr++; + while (runLength-- && pixelNo < pixelCount) { + outPtr[pixelNo] = *literalPtr++; + if (outPtr[pixelNo] == 255) + outPtr[pixelNo] = 0; + pixelNo++; + } + pixelNo = pixelLine + celInfo->width; + } + } else { + // decompression for data that has separate rle and literal streams + while (pixelNo < pixelCount) { + pixel = *rlePtr++; + runLength = pixel & 0x3F; + switch (pixel & 0xC0) { + case 0: // copy bytes as-is + while (runLength-- && pixelNo < pixelCount) + outPtr[pixelNo++] = *literalPtr++; + break; + case 0x80: // fill with color + memset(outPtr + pixelNo, *literalPtr++, MIN<uint32>(runLength, pixelCount - pixelNo)); + pixelNo += runLength; + break; + case 0xC0: // fill with transparent + pixelNo += runLength; + break; + } + } + } + } else { + // literal stream only, so no compression + memcpy(outPtr, literalPtr, pixelCount); + pixelNo = pixelCount; + } } - return; } - error("Unable to decompress view"); } -byte *GfxView::getBitmap(int16 loopNo, int16 celNo) { +const byte *GfxView::getBitmap(int16 loopNo, int16 celNo) { loopNo = CLIP<int16>(loopNo, 0, _loopCount -1); celNo = CLIP<int16>(celNo, 0, _loop[loopNo].celCount - 1); if (_loop[loopNo].cel[celNo].rawBitmap) @@ -411,9 +440,7 @@ byte *GfxView::getBitmap(int16 loopNo, int16 celNo) { _loop[loopNo].cel[celNo].rawBitmap = new byte[pixelCount]; byte *pBitmap = _loop[loopNo].cel[celNo].rawBitmap; - // Some RLE compressed cels end with the last non-transparent pixel, thats why we fill it up here - // FIXME: change this to fill the remaining bytes within unpackCel() - memset(pBitmap, _loop[loopNo].cel[celNo].clearKey, pixelCount); + // unpack the actual cel bitmap data unpackCel(loopNo, celNo, pBitmap, pixelCount); if (!_resMan->isVGA()) { @@ -429,17 +456,21 @@ byte *GfxView::getBitmap(int16 loopNo, int16 celNo) { return _loop[loopNo].cel[celNo].rawBitmap; } -// Called after unpacking an EGA cel, this will try to undither (parts) of the cel if the dithering in here -// matches dithering used by the current picture +/** + * Called after unpacking an EGA cel, this will try to undither (parts) of the + * cel if the dithering in here matches dithering used by the current picture. + */ void GfxView::unditherBitmap(byte *bitmapPtr, int16 width, int16 height, byte clearKey) { int16 *unditherMemorial = _screen->unditherGetMemorial(); - // It makes no sense to go further, if no memorial data from current picture is available + // It makes no sense to go further, if no memorial data from current picture + // is available if (!unditherMemorial) return; // Makes no sense to process bitmaps that are 3 pixels wide or less - if (width <= 3) return; + if (width <= 3) + return; // If EGA mapping is used for this view, dont do undithering as well if (_EGAmapping) @@ -453,7 +484,8 @@ void GfxView::unditherBitmap(byte *bitmapPtr, int16 width, int16 height, byte cl memset(&bitmapMemorial, 0, sizeof(bitmapMemorial)); - // Count all seemingly dithered pixel-combinations as soon as at least 4 pixels are adjacent + // Count all seemingly dithered pixel-combinations as soon as at least 4 + // pixels are adjacent curPtr = bitmapPtr; for (y = 0; y < height; y++) { color1 = curPtr[0]; color2 = (curPtr[1] << 4) | curPtr[2]; @@ -466,17 +498,20 @@ void GfxView::unditherBitmap(byte *bitmapPtr, int16 width, int16 height, byte cl } } - // Now compare both memorial tables to find out matching dithering-combinations + // Now compare both memorial tables to find out matching + // dithering-combinations bool unditherTable[SCI_SCREEN_UNDITHERMEMORIAL_SIZE]; byte color, unditherCount = 0; memset(&unditherTable, false, sizeof(unditherTable)); for (color = 0; color < 255; color++) { if ((bitmapMemorial[color] > 5) && (unditherMemorial[color] > 200)) { - // match found, check if colorKey is contained -> if so, we ignore of course + // match found, check if colorKey is contained -> if so, we ignore + // of course color1 = color & 0x0F; color2 = color >> 4; if ((color1 != clearKey) && (color2 != clearKey) && (color1 != color2)) { // so set this and the reversed color-combination for undithering - unditherTable[color] = true; unditherTable[(color1 << 4) | color2] = true; + unditherTable[color] = true; + unditherTable[(color1 << 4) | color2] = true; unditherCount++; } } @@ -493,8 +528,9 @@ void GfxView::unditherBitmap(byte *bitmapPtr, int16 width, int16 height, byte cl for (x = 1; x < width; x++) { color = (color << 4) | curPtr[1]; if (unditherTable[color]) { - // some color with black? turn colors around otherwise it wont be the right color at all - if ((color & 0xF0)==0) + // Some color with black? Turn colors around, otherwise it won't + // be the right color at all. + if ((color & 0xF0) == 0) color = (color << 4) | (color >> 4); curPtr[0] = color; curPtr[1] = color; } @@ -504,15 +540,15 @@ void GfxView::unditherBitmap(byte *bitmapPtr, int16 width, int16 height, byte cl } } -void GfxView::draw(Common::Rect rect, Common::Rect clipRect, Common::Rect clipRectTranslated, int16 loopNo, int16 celNo, byte priority, uint16 EGAmappingNr, bool upscaledHires) { - Palette *palette = _embeddedPal ? &_viewPalette : &_palette->_sysPalette; - CelInfo *celInfo = getCelInfo(loopNo, celNo); - byte *bitmap = getBitmap(loopNo, celNo); - int16 celHeight = celInfo->height, celWidth = celInfo->width; - int16 width, height; - byte clearKey = celInfo->clearKey; - byte color; - byte drawMask = priority == 255 ? GFX_SCREEN_MASK_VISUAL : GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY; +void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const Common::Rect &clipRectTranslated, + int16 loopNo, int16 celNo, byte priority, uint16 EGAmappingNr, bool upscaledHires) { + const Palette *palette = _embeddedPal ? &_viewPalette : &_palette->_sysPalette; + const CelInfo *celInfo = getCelInfo(loopNo, celNo); + const byte *bitmap = getBitmap(loopNo, celNo); + const int16 celHeight = celInfo->height; + const int16 celWidth = celInfo->width; + const byte clearKey = celInfo->clearKey; + const byte drawMask = (priority == 255) ? GFX_SCREEN_MASK_VISUAL : GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY; int x, y; if (_embeddedPal) { @@ -520,24 +556,27 @@ void GfxView::draw(Common::Rect rect, Common::Rect clipRect, Common::Rect clipRe _palette->set(&_viewPalette, false); } - width = MIN(clipRect.width(), celWidth); - height = MIN(clipRect.height(), celHeight); + const int16 width = MIN(clipRect.width(), celWidth); + const int16 height = MIN(clipRect.height(), celHeight); bitmap += (clipRect.top - rect.top) * celWidth + (clipRect.left - rect.left); if (!_EGAmapping) { for (y = 0; y < height; y++, bitmap += celWidth) { for (x = 0; x < width; x++) { - color = bitmap[x]; + const byte color = bitmap[x]; if (color != clearKey) { + const int x2 = clipRectTranslated.left + x; + const int y2 = clipRectTranslated.top + y; if (!upscaledHires) { - if (priority >= _screen->getPriority(clipRectTranslated.left + x, clipRectTranslated.top + y)) - _screen->putPixel(clipRectTranslated.left + x, clipRectTranslated.top + y, drawMask, palette->mapping[color], priority, 0); + if (priority >= _screen->getPriority(x2, y2)) + _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); } else { - // UpscaledHires means view is hires and is supposed to get drawn onto lowres screen - // FIXME(?): we can't read priority directly with the hires coordinates. may not be needed at all - // in kq6 - _screen->putPixelOnDisplay(clipRectTranslated.left + x, clipRectTranslated.top + y, palette->mapping[color]); + // UpscaledHires means view is hires and is supposed to + // get drawn onto lowres screen. + // FIXME(?): we can't read priority directly with the + // hires coordinates. may not be needed at all in kq6 + _screen->putPixelOnDisplay(x2, y2, palette->mapping[color]); } } } @@ -546,30 +585,34 @@ void GfxView::draw(Common::Rect rect, Common::Rect clipRect, Common::Rect clipRe byte *EGAmapping = _EGAmapping + (EGAmappingNr * SCI_VIEW_EGAMAPPING_SIZE); for (y = 0; y < height; y++, bitmap += celWidth) { for (x = 0; x < width; x++) { - color = EGAmapping[bitmap[x]]; - if (color != clearKey && priority >= _screen->getPriority(clipRectTranslated.left + x, clipRectTranslated.top + y)) - _screen->putPixel(clipRectTranslated.left + x, clipRectTranslated.top + y, drawMask, color, priority, 0); + const byte color = EGAmapping[bitmap[x]]; + 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, color, priority, 0); } } } } -// We don't fully follow sierra sci here, I did the scaling algo myself and it's definitely not pixel-perfect -// with the one sierra is using. It shouldn't matter because the scaled cel rect is definitely the same as in sierra sci -void GfxView::drawScaled(Common::Rect rect, Common::Rect clipRect, Common::Rect clipRectTranslated, int16 loopNo, int16 celNo, byte priority, int16 scaleX, int16 scaleY) { - Palette *palette = _embeddedPal ? &_viewPalette : &_palette->_sysPalette; - CelInfo *celInfo = getCelInfo(loopNo, celNo); - byte *bitmap = getBitmap(loopNo, celNo); - int16 celHeight = celInfo->height, celWidth = celInfo->width; - byte clearKey = celInfo->clearKey; - byte color; - byte drawMask = priority == 255 ? GFX_SCREEN_MASK_VISUAL : GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY; - int x, y; - uint16 scalingX[320]; - uint16 scalingY[200]; +/** + * We don't fully follow sierra sci here, I did the scaling algo myself and it + * is definitely not pixel-perfect with the one sierra is using. It shouldn't + * matter because the scaled cel rect is definitely the same as in sierra sci. + */ +void GfxView::drawScaled(const Common::Rect &rect, const Common::Rect &clipRect, const Common::Rect &clipRectTranslated, + int16 loopNo, int16 celNo, byte priority, int16 scaleX, int16 scaleY) { + const Palette *palette = _embeddedPal ? &_viewPalette : &_palette->_sysPalette; + const CelInfo *celInfo = getCelInfo(loopNo, celNo); + const byte *bitmap = getBitmap(loopNo, celNo); + const int16 celHeight = celInfo->height; + const int16 celWidth = celInfo->width; + const byte clearKey = celInfo->clearKey; + const byte drawMask = (priority == 255) ? GFX_SCREEN_MASK_VISUAL : GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY; + uint16 scalingX[640]; + uint16 scalingY[480]; int16 scaledWidth, scaledHeight; int16 pixelNo, scaledPixel, scaledPixelNo, prevScaledPixelNo; - uint16 offsetX, offsetY; if (_embeddedPal) { // Merge view palette in... @@ -590,6 +633,7 @@ void GfxView::drawScaled(Common::Rect rect, Common::Rect clipRect, Common::Rect scaledPixel = scaledPixelNo = prevScaledPixelNo = 0; while (pixelNo < celHeight) { scaledPixelNo = scaledPixel >> 7; + assert(scaledPixelNo < ARRAYSIZE(scalingY)); if (prevScaledPixelNo < scaledPixelNo) memset(&scalingY[prevScaledPixelNo], pixelNo, scaledPixelNo - prevScaledPixelNo); scalingY[scaledPixelNo] = pixelNo; @@ -606,6 +650,7 @@ void GfxView::drawScaled(Common::Rect rect, Common::Rect clipRect, Common::Rect scaledPixel = scaledPixelNo = prevScaledPixelNo = 0; while (pixelNo < celWidth) { scaledPixelNo = scaledPixel >> 7; + assert(scaledPixelNo < ARRAYSIZE(scalingX)); if (prevScaledPixelNo < scaledPixelNo) memset(&scalingX[prevScaledPixelNo], pixelNo, scaledPixelNo - prevScaledPixelNo); scalingX[scaledPixelNo] = pixelNo; @@ -620,27 +665,25 @@ void GfxView::drawScaled(Common::Rect rect, Common::Rect clipRect, Common::Rect scaledWidth = MIN(clipRect.width(), scaledWidth); scaledHeight = MIN(clipRect.height(), scaledHeight); - offsetY = clipRect.top - rect.top; - offsetX = clipRect.left - rect.left; + const int16 offsetY = clipRect.top - rect.top; + const int16 offsetX = clipRect.left - rect.left; + + // Happens in SQ6, first room + if (offsetX < 0 || offsetY < 0) + return; - for (y = 0; y < scaledHeight; y++) { - for (x = 0; x < scaledWidth; x++) { - color = bitmap[scalingY[y + offsetY] * celWidth + scalingX[x + offsetX]]; - if (color != clearKey && priority >= _screen->getPriority(clipRectTranslated.left + x, clipRectTranslated.top + y)) { - _screen->putPixel(clipRectTranslated.left + x, clipRectTranslated.top + y, drawMask, palette->mapping[color], priority, 0); + assert(scaledHeight + offsetY <= ARRAYSIZE(scalingY)); + assert(scaledWidth + offsetX <= ARRAYSIZE(scalingX)); + for (int y = 0; y < scaledHeight; y++) { + for (int x = 0; x < scaledWidth; x++) { + const byte color = bitmap[scalingY[y + offsetY] * celWidth + scalingX[x + offsetX]]; + 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); } } } } -uint16 GfxView::getCelCount(int16 loopNo) { - if ((loopNo < 0) || (loopNo >= _loopCount)) - return 0; - return _loop[loopNo].celCount; -} - -Palette *GfxView::getPalette() { - return _embeddedPal ? &_viewPalette : &_palette->_sysPalette; -} - } // End of namespace Sci diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index a2050dc9d5..6eb1830b99 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -60,20 +60,21 @@ public: GfxView(ResourceManager *resMan, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId); ~GfxView(); - GuiResourceId getResourceId(); - int16 getWidth(int16 loopNo, int16 celNo); - int16 getHeight(int16 loopNo, int16 celNo); - CelInfo *getCelInfo(int16 loopNo, int16 celNo); - LoopInfo *getLoopInfo(int16 loopNo); - void getCelRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, Common::Rect *outRect); - void getCelScaledRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, int16 scaleX, int16 scaleY, Common::Rect *outRect); - byte *getBitmap(int16 loopNo, int16 celNo); - void draw(Common::Rect rect, Common::Rect clipRect, Common::Rect clipRectTranslated, int16 loopNo, int16 celNo, byte priority, uint16 EGAmappingNr, bool upscaledHires); - void drawScaled(Common::Rect rect, Common::Rect clipRect, Common::Rect clipRectTranslated, int16 loopNo, int16 celNo, byte priority, int16 scaleX, int16 scaleY); + GuiResourceId getResourceId() const; + int16 getWidth(int16 loopNo, int16 celNo) const; + int16 getHeight(int16 loopNo, int16 celNo) const; + const CelInfo *getCelInfo(int16 loopNo, int16 celNo) const; + void getCelRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, Common::Rect &outRect) const; + void getCelScaledRect(int16 loopNo, int16 celNo, int16 x, int16 y, int16 z, int16 scaleX, int16 scaleY, Common::Rect &outRect) const; + const byte *getBitmap(int16 loopNo, int16 celNo); + void draw(const Common::Rect &rect, const Common::Rect &clipRect, const Common::Rect &clipRectTranslated, int16 loopNo, int16 celNo, byte priority, uint16 EGAmappingNr, bool upscaledHires); + void drawScaled(const Common::Rect &rect, const Common::Rect &clipRect, const Common::Rect &clipRectTranslated, int16 loopNo, int16 celNo, byte priority, int16 scaleX, int16 scaleY); uint16 getLoopCount() const { return _loopCount; } - uint16 getCelCount(int16 loopNo); + uint16 getCelCount(int16 loopNo) const; Palette *getPalette(); + bool isSci2Hires(); + private: void initData(GuiResourceId resourceId); void unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount); @@ -86,12 +87,16 @@ private: GuiResourceId _resourceId; Resource *_resource; byte *_resourceData; + int _resourceSize; uint16 _loopCount; LoopInfo *_loop; bool _embeddedPal; Palette _viewPalette; + // set for SCI2 views in gk1/windows, means that views are hires and should be handled accordingly + bool _isSci2Hires; + byte *_EGAmapping; }; diff --git a/engines/sci/module.mk b/engines/sci/module.mk index a2cfd38f95..85988b8f1b 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -10,7 +10,6 @@ MODULE_OBJS := \ sci.o \ util.o \ engine/features.o \ - engine/game.o \ engine/gc.o \ engine/kernel.o \ engine/kevent.o \ @@ -44,7 +43,6 @@ MODULE_OBJS := \ graphics/cursor.o \ graphics/font.o \ graphics/fontsjis.o \ - graphics/gui.o \ graphics/maciconbar.o \ graphics/menu.o \ graphics/paint.o \ @@ -69,9 +67,6 @@ MODULE_OBJS := \ sound/drivers/fb01.o \ sound/drivers/midi.o \ sound/drivers/pcjr.o \ - sound/iterator/core.o \ - sound/iterator/iterator.o \ - sound/iterator/songlib.o \ video/seq_decoder.o @@ -79,7 +74,6 @@ ifdef ENABLE_SCI32 MODULE_OBJS += \ engine/kernel32.o \ graphics/frameout.o \ - graphics/gui32.o \ graphics/paint32.o \ graphics/robot.o \ video/vmd_decoder.o diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp index 00448f5d51..e48a9cdfda 100644 --- a/engines/sci/parser/vocabulary.cpp +++ b/engines/sci/parser/vocabulary.cpp @@ -33,59 +33,6 @@ namespace Sci { -#if 0 - -#define VOCAB_RESOURCE_CLASSES 996 -/** - * Vocabulary class names. - * These strange names were taken from an SCI01 interpreter. - */ -const char *class_names[] = {"", - "", - "conj", // conjunction - "ass", // ? - "pos", // preposition ? - "art", // article - "adj", // adjective - "pron", // pronoun - "noun", // noun - "auxv", // auxillary verb - "adv", // adverb - "verb", // verb - "", - "", - "", - "" - }; - -int *vocab_get_classes(ResourceManager *resMan, int* count) { - Resource* r; - int *c; - unsigned int i; - - if ((r = resMan->findResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_CLASSES), 0)) == NULL) - return 0; - - c = (int *)malloc(sizeof(int) * r->size / 2); - for (i = 2; i < r->size; i += 4) { - c[i/4] = READ_LE_UINT16(r->data + i); - } - *count = r->size / 4; - - return c; -} - -int vocab_get_class_count(ResourceManager *resMan) { - Resource* r; - - if ((r = resMan->findResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_CLASSES), 0)) == 0) - return 0; - - return r->size / 4; -} - -#endif - Vocabulary::Vocabulary(ResourceManager *resMan) : _resMan(resMan) { _parserRules = NULL; _vocabVersion = kVocabularySCI0; diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 400f0b1e67..2958ca1e3b 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -26,8 +26,11 @@ // Resource library #include "common/file.h" +#include "common/fs.h" +#include "common/macresman.h" #include "sci/resource.h" +#include "sci/resource_intern.h" #include "sci/util.h" namespace Sci { @@ -111,7 +114,7 @@ static const char *resourceTypeNames[] = { "memory", "vocab", "font", "cursor", "patch", "bitmap", "palette", "cdaudio", "audio", "sync", "message", "map", "heap", - "audio36", "sync36", "", "", "robot" + "audio36", "sync36", "", "", "robot", "vmd" }; static const char *resourceTypeSuffixes[] = { @@ -119,7 +122,7 @@ static const char *resourceTypeSuffixes[] = { " ", "voc", "fon", "cur", "pat", "bit", "pal", "cda", "aud", "syn", "msg", "map", "hep", "aud", "syn", - "trn", " ", "rbt" + "trn", " ", "rbt", "vmd" }; const char *getResourceTypeName(ResourceType restype) { @@ -130,7 +133,7 @@ const char *getResourceTypeName(ResourceType restype) { } //-- Resource main functions -- -Resource::Resource() { +Resource::Resource(ResourceId id) : _id(id) { data = NULL; size = 0; _fileOffset = 0; @@ -143,7 +146,7 @@ Resource::Resource() { Resource::~Resource() { delete[] data; - if (_source && _source->source_type == kSourcePatch) + if (_source && _source->getSourceType() == kSourcePatch) delete _source; } @@ -154,102 +157,76 @@ void Resource::unalloc() { } void Resource::writeToStream(Common::WriteStream *stream) const { - stream->writeByte(_id.type | 0x80); // 0x80 is required by old sierra sci, otherwise it wont accept the patch file + stream->writeByte(getType() | 0x80); // 0x80 is required by old sierra sci, otherwise it wont accept the patch file stream->writeByte(_headerSize); if (_headerSize > 0) stream->write(_header, _headerSize); stream->write(data, size); } -uint32 Resource::getAudioCompressionType() { - return _source->audioCompressionType; +uint32 Resource::getAudioCompressionType() const { + return _source->getAudioCompressionType(); } -//-- resMan helper functions -- +uint32 AudioVolumeResourceSource::getAudioCompressionType() const { + return _audioCompressionType; +} -// Resource source list management -ResourceSource *ResourceManager::addExternalMap(const char *file_name, int volume_nr) { - ResourceSource *newsrc = new ResourceSource(); +ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile) + : _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) { + _scanned = false; +} - newsrc->source_type = kSourceExtMap; - newsrc->location_name = file_name; - newsrc->resourceFile = 0; - newsrc->scanned = false; - newsrc->associated_map = NULL; - newsrc->volume_number = volume_nr; +ResourceSource::~ResourceSource() { +} - _sources.push_back(newsrc); - return newsrc; +MacResourceForkResourceSource::MacResourceForkResourceSource(const Common::String &name, int volNum) + : ResourceSource(kSourceMacResourceFork, name, volNum) { + _macResMan = new Common::MacResManager(); + assert(_macResMan); } -ResourceSource *ResourceManager::addExternalMap(const Common::FSNode *mapFile, int volume_nr) { - ResourceSource *newsrc = new ResourceSource(); +MacResourceForkResourceSource::~MacResourceForkResourceSource() { + delete _macResMan; +} - newsrc->source_type = kSourceExtMap; - newsrc->location_name = mapFile->getName(); - newsrc->resourceFile = mapFile; - newsrc->scanned = false; - newsrc->associated_map = NULL; - newsrc->volume_number = volume_nr; +//-- resMan helper functions -- + +// Resource source list management + +ResourceSource *ResourceManager::addExternalMap(const Common::String &filename, int volume_nr) { + ResourceSource *newsrc = new ExtMapResourceSource(filename, volume_nr); _sources.push_back(newsrc); return newsrc; } -ResourceSource *ResourceManager::addSource(ResourceSource *map, ResSourceType type, const char *filename, int number) { - ResourceSource *newsrc = new ResourceSource(); - - newsrc->source_type = type; - newsrc->scanned = false; - newsrc->location_name = filename; - newsrc->resourceFile = 0; - newsrc->volume_number = number; - newsrc->associated_map = map; - newsrc->audioCompressionType = 0; - newsrc->audioCompressionOffsetMapping = NULL; - if (type == kSourceAudioVolume) - checkIfAudioVolumeIsCompressed(newsrc); +ResourceSource *ResourceManager::addExternalMap(const Common::FSNode *mapFile, int volume_nr) { + ResourceSource *newsrc = new ExtMapResourceSource(mapFile->getName(), volume_nr, mapFile); _sources.push_back(newsrc); return newsrc; } -ResourceSource *ResourceManager::addSource(ResourceSource *map, ResSourceType type, const Common::FSNode *resFile, int number) { - ResourceSource *newsrc = new ResourceSource(); - - newsrc->source_type = type; - newsrc->scanned = false; - newsrc->location_name = resFile->getName(); - newsrc->resourceFile = resFile; - newsrc->volume_number = number; - newsrc->associated_map = map; - newsrc->audioCompressionType = 0; - newsrc->audioCompressionOffsetMapping = NULL; - if (type == kSourceAudioVolume) - checkIfAudioVolumeIsCompressed(newsrc); +ResourceSource *ResourceManager::addSource(ResourceSource *newsrc) { + assert(newsrc); _sources.push_back(newsrc); return newsrc; } -ResourceSource *ResourceManager::addPatchDir(const char *dirname) { - ResourceSource *newsrc = new ResourceSource(); - - newsrc->source_type = kSourceDirectory; - newsrc->resourceFile = 0; - newsrc->scanned = false; - newsrc->location_name = dirname; +ResourceSource *ResourceManager::addPatchDir(const Common::String &dirname) { + ResourceSource *newsrc = new DirectoryResourceSource(dirname); _sources.push_back(newsrc); return 0; } -ResourceSource *ResourceManager::getVolume(ResourceSource *map, int volume_nr) { +ResourceSource *ResourceManager::findVolume(ResourceSource *map, int volume_nr) { for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) { - ResourceSource *src = *it; - if ((src->source_type == kSourceVolume || src->source_type == kSourceAudioVolume) - && src->associated_map == map && src->volume_number == volume_nr) + ResourceSource *src = (*it)->findVolume(map, volume_nr); + if (src) return src; } @@ -258,7 +235,9 @@ ResourceSource *ResourceManager::getVolume(ResourceSource *map, int volume_nr) { // Resource manager constructors and operations -bool ResourceManager::loadPatch(Resource *res, Common::SeekableReadStream *file) { +bool Resource::loadPatch(Common::SeekableReadStream *file) { + Resource *res = this; + // We assume that the resource type matches res->type // We also assume that the current file position is right at the actual data (behind resourceid/headersize byte) @@ -286,27 +265,27 @@ bool ResourceManager::loadPatch(Resource *res, Common::SeekableReadStream *file) return true; } -bool ResourceManager::loadFromPatchFile(Resource *res) { +bool Resource::loadFromPatchFile() { Common::File file; - const char *filename = res->_source->location_name.c_str(); + const Common::String &filename = _source->getLocationName(); if (file.open(filename) == false) { - warning("Failed to open patch file %s", filename); - res->unalloc(); + warning("Failed to open patch file %s", filename.c_str()); + unalloc(); return false; } // Skip resourceid and header size byte file.seek(2, SEEK_SET); - return loadPatch(res, &file); + return loadPatch(&file); } Common::SeekableReadStream *ResourceManager::getVolumeFile(ResourceSource *source) { Common::List<Common::File *>::iterator it = _volumeFiles.begin(); Common::File *file; - if (source->resourceFile) - return source->resourceFile->createReadStream(); + if (source->_resourceFile) + return source->_resourceFile->createReadStream(); - const char *filename = source->location_name.c_str(); + const char *filename = source->getLocationName().c_str(); // check if file is already opened while (it != _volumeFiles.end()) { @@ -340,107 +319,131 @@ Common::SeekableReadStream *ResourceManager::getVolumeFile(ResourceSource *sourc static uint32 resTypeToMacTag(ResourceType type); void ResourceManager::loadResource(Resource *res) { - if (res->_source->source_type == kSourcePatch && loadFromPatchFile(res)) - return; + res->_source->loadResource(this, res); +} - if (res->_source->source_type == kSourceMacResourceFork) { - Common::SeekableReadStream *stream = res->_source->macResMan.getResource(resTypeToMacTag(res->_id.type), res->_id.number); - if (!stream) - error("Could not get Mac resource fork resource: %d %d", res->_id.type, res->_id.number); +void PatchResourceSource::loadResource(ResourceManager *resMan, Resource *res) { + bool result = res->loadFromPatchFile(); + if (!result) { + // TODO: We used to fallback to the "default" code here if loadFromPatchFile + // failed, but I am not sure whether that is really appropriate. + // In fact it looks like a bug to me, so I commented this out for now. + //ResourceSource::loadResource(res); + } +} - int error = decompress(res, stream); - if (error) { - warning("Error %d occured while reading %s from Mac resource file: %s", - error, res->_id.toString().c_str(), sci_error_types[error]); - res->unalloc(); - } - return; +void MacResourceForkResourceSource::loadResource(ResourceManager *resMan, Resource *res) { + Common::SeekableReadStream *stream = _macResMan->getResource(resTypeToMacTag(res->getType()), res->getNumber()); + + if (!stream) + error("Could not get Mac resource fork resource: %d %d", res->getType(), res->getNumber()); + + int error = res->decompress(resMan->getVolVersion(), stream); + if (error) { + warning("Error %d occurred while reading %s from Mac resource file: %s", + error, res->_id.toString().c_str(), sci_error_types[error]); + res->unalloc(); } +} - Common::SeekableReadStream *fileStream = getVolumeFile(res->_source); +Common::SeekableReadStream *ResourceSource::getVolumeFile(ResourceManager *resMan, Resource *res) { + Common::SeekableReadStream *fileStream = resMan->getVolumeFile(this); if (!fileStream) { - warning("Failed to open %s", res->_source->location_name.c_str()); - res->unalloc(); - return; + warning("Failed to open %s", getLocationName().c_str()); + if (res) + res->unalloc(); } - switch(res->_source->source_type) { - case kSourceWave: - fileStream->seek(res->_fileOffset, SEEK_SET); - loadFromWaveFile(res, fileStream); - if (res->_source->resourceFile) - delete fileStream; + return fileStream; +} + +void WaveResourceSource::loadResource(ResourceManager *resMan, Resource *res) { + Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res); + if (!fileStream) return; - case kSourceAudioVolume: - if (res->_source->audioCompressionType) { - // this file is compressed, so lookup our offset in the offset-translation table and get the new offset - // also calculate the compressed size by using the next offset - int32 *mappingTable = res->_source->audioCompressionOffsetMapping; - int32 compressedOffset = 0; - - do { - if (*mappingTable == res->_fileOffset) { - mappingTable++; - compressedOffset = *mappingTable; - // Go to next compressed offset and use that to calculate size of compressed sample - switch (res->_id.type) { - case kResourceTypeSync: - case kResourceTypeSync36: - // we should already have a (valid) size - break; - default: - mappingTable += 2; - res->size = *mappingTable - compressedOffset; - } + fileStream->seek(res->_fileOffset, SEEK_SET); + res->loadFromWaveFile(fileStream); + if (_resourceFile) + delete fileStream; +} + +void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *res) { + Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res); + if (!fileStream) + return; + + if (_audioCompressionType) { + // this file is compressed, so lookup our offset in the offset-translation table and get the new offset + // also calculate the compressed size by using the next offset + int32 *mappingTable = _audioCompressionOffsetMapping; + int32 compressedOffset = 0; + + do { + if (*mappingTable == res->_fileOffset) { + mappingTable++; + compressedOffset = *mappingTable; + // Go to next compressed offset and use that to calculate size of compressed sample + switch (res->getType()) { + case kResourceTypeSync: + case kResourceTypeSync36: + // we should already have a (valid) size break; + default: + mappingTable += 2; + res->size = *mappingTable - compressedOffset; } - mappingTable += 2; - } while (*mappingTable); - - if (!compressedOffset) - error("could not translate offset to compressed offset in audio volume"); - fileStream->seek(compressedOffset, SEEK_SET); - - switch (res->_id.type) { - case kResourceTypeAudio: - case kResourceTypeAudio36: - // Directly read the stream, compressed audio wont have resource type id and header size for SCI1.1 - loadFromAudioVolumeSCI1(res, fileStream); - if (res->_source->resourceFile) - delete fileStream; - return; - default: break; } - } else { - // original file, directly seek to given offset and get SCI1/SCI1.1 audio resource - fileStream->seek(res->_fileOffset, SEEK_SET); + mappingTable += 2; + } while (*mappingTable); + + if (!compressedOffset) + error("could not translate offset to compressed offset in audio volume"); + fileStream->seek(compressedOffset, SEEK_SET); + + switch (res->getType()) { + case kResourceTypeAudio: + case kResourceTypeAudio36: + // Directly read the stream, compressed audio wont have resource type id and header size for SCI1.1 + res->loadFromAudioVolumeSCI1(fileStream); + if (_resourceFile) + delete fileStream; + return; + default: + break; } - if (getSciVersion() < SCI_VERSION_1_1) - loadFromAudioVolumeSCI1(res, fileStream); - else - loadFromAudioVolumeSCI11(res, fileStream); + } else { + // original file, directly seek to given offset and get SCI1/SCI1.1 audio resource + fileStream->seek(res->_fileOffset, SEEK_SET); + } + if (getSciVersion() < SCI_VERSION_1_1) + res->loadFromAudioVolumeSCI1(fileStream); + else + res->loadFromAudioVolumeSCI11(fileStream); - if (res->_source->resourceFile) - delete fileStream; - return; + if (_resourceFile) + delete fileStream; +} - default: - fileStream->seek(res->_fileOffset, SEEK_SET); - int error = decompress(res, fileStream); +void ResourceSource::loadResource(ResourceManager *resMan, Resource *res) { + Common::SeekableReadStream *fileStream = getVolumeFile(resMan, res); + if (!fileStream) + return; - if (res->_source->resourceFile) - delete fileStream; + fileStream->seek(res->_fileOffset, SEEK_SET); - if (error) { - warning("Error %d occured while reading %s from resource file: %s", - error, res->_id.toString().c_str(), sci_error_types[error]); - res->unalloc(); - } + int error = res->decompress(resMan->getVolVersion(), fileStream); + if (error) { + warning("Error %d occurred while reading %s from resource file: %s", + error, res->_id.toString().c_str(), sci_error_types[error]); + res->unalloc(); } + + if (_resourceFile) + delete fileStream; } Resource *ResourceManager::testResource(ResourceId id) { @@ -461,12 +464,12 @@ int ResourceManager::addAppropriateSources() { const char *dot = strrchr(name.c_str(), '.'); int number = atoi(dot + 1); - addSource(map, kSourceVolume, name.c_str(), number); + addSource(new VolumeResourceSource(name, map, number)); } #ifdef ENABLE_SCI32 // GK1CD hires content if (Common::File::exists("alt.map") && Common::File::exists("resource.alt")) - addSource(addExternalMap("alt.map", 10), kSourceVolume, "resource.alt", 10); + addSource(new VolumeResourceSource("resource.alt", addExternalMap("alt.map", 10), 10)); #endif } else if (Common::File::exists("Data1")) { // Mac SCI1.1+ file naming scheme @@ -474,18 +477,19 @@ int ResourceManager::addAppropriateSources() { for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { Common::String filename = (*x)->getName(); - addSource(0, kSourceMacResourceFork, filename.c_str(), atoi(filename.c_str() + 4)); + addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4))); } #ifdef ENABLE_SCI32 // Mac SCI32 games have extra folders for patches addPatchDir("Robot Folder"); addPatchDir("Sound Folder"); addPatchDir("Voices Folder"); + addPatchDir("Voices"); //addPatchDir("VMD Folder"); // There can also be a "Patches" resource fork with patches if (Common::File::exists("Patches")) - addSource(0, kSourceMacResourceFork, "Patches", 100); + addSource(new MacResourceForkResourceSource("Patches", 100)); } else { // SCI2.1-SCI3 file naming scheme Common::ArchiveMemberList mapFiles; @@ -505,7 +509,7 @@ int ResourceManager::addAppropriateSources() { int resNumber = atoi(strrchr(resName.c_str(), '.') + 1); if (mapNumber == resNumber) { - addSource(addExternalMap(mapName.c_str(), mapNumber), kSourceVolume, resName.c_str(), mapNumber); + addSource(new VolumeResourceSource(resName, addExternalMap(mapName, mapNumber), mapNumber)); break; } } @@ -514,7 +518,7 @@ int ResourceManager::addAppropriateSources() { // SCI2.1 resource patches if (Common::File::exists("resmap.pat") && Common::File::exists("ressci.pat")) { // We add this resource with a map which surely won't exist - addSource(addExternalMap("resmap.pat", 100), kSourceVolume, "ressci.pat", 100); + addSource(new VolumeResourceSource("ressci.pat", addExternalMap("resmap.pat", 100), 100)); } } #else @@ -524,7 +528,7 @@ int ResourceManager::addAppropriateSources() { addPatchDir("."); if (Common::File::exists("message.map")) - addSource(addExternalMap("message.map"), kSourceVolume, "resource.msg", 0); + addSource(new VolumeResourceSource("resource.msg", addExternalMap("message.map"), 0)); return 1; } @@ -568,7 +572,7 @@ int ResourceManager::addAppropriateSources(const Common::FSList &fslist) { #ifdef ENABLE_SCI32 if (sci21PatchMap && sci21PatchRes) - addSource(sci21PatchMap, kSourceVolume, sci21PatchRes, 100); + addSource(new VolumeResourceSource(sci21PatchRes->getName(), sci21PatchMap, 100, sci21PatchRes)); #endif // Now find all the resource.0?? files @@ -583,7 +587,7 @@ int ResourceManager::addAppropriateSources(const Common::FSList &fslist) { const char *dot = strrchr(filename.c_str(), '.'); int number = atoi(dot + 1); - addSource(map, kSourceVolume, file, number); + addSource(new VolumeResourceSource(file->getName(), map, number, file)); } } @@ -598,12 +602,12 @@ int ResourceManager::addInternalSources() { Common::List<ResourceId>::iterator itr = resources->begin(); while (itr != resources->end()) { - ResourceSource *src = addSource(NULL, kSourceIntMap, "MAP", itr->number); + ResourceSource *src = addSource(new IntMapResourceSource("MAP", itr->getNumber())); - if ((itr->number == 65535) && Common::File::exists("RESOURCE.SFX")) - addSource(src, kSourceAudioVolume, "RESOURCE.SFX", 0); + if ((itr->getNumber() == 65535) && Common::File::exists("RESOURCE.SFX")) + addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0)); else if (Common::File::exists("RESOURCE.AUD")) - addSource(src, kSourceAudioVolume, "RESOURCE.AUD", 0); + addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0)); ++itr; } @@ -615,38 +619,39 @@ void ResourceManager::scanNewSources() { for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) { ResourceSource *source = *it; - if (!source->scanned) { - source->scanned = true; - switch (source->source_type) { - case kSourceDirectory: - readResourcePatches(source); -#ifdef ENABLE_SCI32 - readResourcePatchesBase36(source); -#endif - readWaveAudioPatches(); - break; - case kSourceExtMap: - if (_mapVersion < kResVersionSci1Late) - readResourceMapSCI0(source); - else - readResourceMapSCI1(source); - break; - case kSourceExtAudioMap: - readAudioMapSCI1(source); - break; - case kSourceIntMap: - readAudioMapSCI11(source); - break; - case kSourceMacResourceFork: - readMacResourceFork(source); - break; - default: - break; - } + if (!source->_scanned) { + source->_scanned = true; + source->scanSource(this); } } } +void DirectoryResourceSource::scanSource(ResourceManager *resMan) { + resMan->readResourcePatches(); + + // We can't use getSciVersion() at this point, thus using _volVersion + if (resMan->_volVersion >= kResVersionSci11) // SCI1.1+ + resMan->readResourcePatchesBase36(); + + resMan->readWaveAudioPatches(); +} + +void ExtMapResourceSource::scanSource(ResourceManager *resMan) { + if (resMan->_mapVersion < kResVersionSci1Late) + resMan->readResourceMapSCI0(this); + else + resMan->readResourceMapSCI1(this); +} + +void ExtAudioMapResourceSource::scanSource(ResourceManager *resMan) { + resMan->readAudioMapSCI1(this); +} + +void IntMapResourceSource::scanSource(ResourceManager *resMan) { + resMan->readAudioMapSCI11(this); +} + + void ResourceManager::freeResourceSources() { for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) delete *it; @@ -655,13 +660,6 @@ void ResourceManager::freeResourceSources() { } ResourceManager::ResourceManager() { - addAppropriateSources(); - init(); -} - -ResourceManager::ResourceManager(const Common::FSList &fslist) { - addAppropriateSources(fslist); - init(); } void ResourceManager::init() { @@ -716,7 +714,7 @@ void ResourceManager::init() { debugC(1, kDebugLevelResMan, "resMan: Detected Amiga graphic resources"); break; default: - warning("resMan: Couldn't determine view type"); + error("resMan: Couldn't determine view type"); } } @@ -795,7 +793,7 @@ Common::List<ResourceId> *ResourceManager::listResources(ResourceType type, int ResourceMap::iterator itr = _resMap.begin(); while (itr != _resMap.end()) { - if ((itr->_value->_id.type == type) && ((mapNumber == -1) || (itr->_value->_id.number == mapNumber))) + if ((itr->_value->getType() == type) && ((mapNumber == -1) || (itr->_value->getNumber() == mapNumber))) resources->push_back(itr->_value->_id); ++itr; } @@ -813,7 +811,7 @@ Resource *ResourceManager::findResource(ResourceId id, bool lock) { loadResource(retval); else if (retval->_status == kResStatusEnqueued) removeFromLRU(retval); - // Unless an error occured, the resource is now either + // Unless an error occurred, the resource is now either // locked or allocated, but never queued or freed. freeOldResources(); @@ -876,7 +874,7 @@ const char *ResourceManager::versionDescription(ResVersion version) const { return "Version not valid"; } -ResourceManager::ResVersion ResourceManager::detectMapVersion() { +ResVersion ResourceManager::detectMapVersion() { Common::SeekableReadStream *fileStream = 0; byte buff[6]; ResourceSource *rsrc= 0; @@ -884,17 +882,17 @@ ResourceManager::ResVersion ResourceManager::detectMapVersion() { for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) { rsrc = *it; - if (rsrc->source_type == kSourceExtMap) { - if (rsrc->resourceFile) { - fileStream = rsrc->resourceFile->createReadStream(); + if (rsrc->getSourceType() == kSourceExtMap) { + if (rsrc->_resourceFile) { + fileStream = rsrc->_resourceFile->createReadStream(); } else { Common::File *file = new Common::File(); - file->open(rsrc->location_name); + file->open(rsrc->getLocationName()); if (file->isOpen()) fileStream = file; } break; - } else if (rsrc->source_type == kSourceMacResourceFork) + } else if (rsrc->getSourceType() == kSourceMacResourceFork) return kResVersionSci11Mac; } @@ -909,7 +907,7 @@ ResourceManager::ResVersion ResourceManager::detectMapVersion() { // check if 0 or 01 - try to read resources in SCI0 format and see if exists fileStream->seek(0, SEEK_SET); while (fileStream->read(buff, 6) == 6 && !(buff[0] == 0xFF && buff[1] == 0xFF && buff[2] == 0xFF)) { - if (getVolume(rsrc, (buff[5] & 0xFC) >> 2) == NULL) + if (findVolume(rsrc, (buff[5] & 0xFC) >> 2) == NULL) return kResVersionSci1Middle; } return kResVersionSci0Sci1Early; @@ -966,24 +964,24 @@ ResourceManager::ResVersion ResourceManager::detectMapVersion() { return kResVersionUnknown; } -ResourceManager::ResVersion ResourceManager::detectVolVersion() { +ResVersion ResourceManager::detectVolVersion() { Common::SeekableReadStream *fileStream = 0; ResourceSource *rsrc; for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end(); ++it) { rsrc = *it; - if (rsrc->source_type == kSourceVolume) { - if (rsrc->resourceFile) { - fileStream = rsrc->resourceFile->createReadStream(); + if (rsrc->getSourceType() == kSourceVolume) { + if (rsrc->_resourceFile) { + fileStream = rsrc->_resourceFile->createReadStream(); } else { Common::File *file = new Common::File(); - file->open(rsrc->location_name); + file->open(rsrc->getLocationName()); if (file->isOpen()) fileStream = file; } break; - } else if (rsrc->source_type == kSourceMacResourceFork) + } else if (rsrc->getSourceType() == kSourceMacResourceFork) return kResVersionSci11Mac; } @@ -1068,41 +1066,41 @@ ResourceManager::ResVersion ResourceManager::detectVolVersion() { // version-agnostic patch application void ResourceManager::processPatch(ResourceSource *source, ResourceType resourceType, uint16 resourceNr, uint32 tuple) { Common::SeekableReadStream *fileStream = 0; - Resource *newrsc; + Resource *newrsc = 0; ResourceId resId = ResourceId(resourceType, resourceNr, tuple); - byte patchType, patchDataOffset; - int fsize; + ResourceType checkForType = resourceType; // base36 encoded patches (i.e. audio36 and sync36) have the same type as their non-base36 encoded counterparts - if (resourceType == kResourceTypeAudio36) - resourceType = kResourceTypeAudio; - - if (resourceType == kResourceTypeSync36) - resourceType = kResourceTypeSync; + if (checkForType == kResourceTypeAudio36) + checkForType = kResourceTypeAudio; + else if (checkForType == kResourceTypeSync36) + checkForType = kResourceTypeSync; - if (source->resourceFile) { - fileStream = source->resourceFile->createReadStream(); + if (source->_resourceFile) { + fileStream = source->_resourceFile->createReadStream(); } else { Common::File *file = new Common::File(); - if (!file->open(source->location_name)) { - warning("ResourceManager::processPatch(): failed to open %s", source->location_name.c_str()); + if (!file->open(source->getLocationName())) { + warning("ResourceManager::processPatch(): failed to open %s", source->getLocationName().c_str()); return; } fileStream = file; } - fsize = fileStream->size(); + + int fsize = fileStream->size(); if (fsize < 3) { - debug("Patching %s failed - file too small", source->location_name.c_str()); + debug("Patching %s failed - file too small", source->getLocationName().c_str()); return; } - patchType = fileStream->readByte() & 0x7F; - patchDataOffset = fileStream->readByte(); + byte patchType = fileStream->readByte() & 0x7F; + byte patchDataOffset = fileStream->readByte(); delete fileStream; - if (patchType != resourceType) { - debug("Patching %s failed - resource type mismatch", source->location_name.c_str()); + if (patchType != checkForType) { + debug("Patching %s failed - resource type mismatch", source->getLocationName().c_str()); + return; } // Fixes SQ5/German, patch file special case logic taken from SCI View disassembly @@ -1118,35 +1116,27 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource patchDataOffset = 8; break; default: - warning("Resource patch unsupported special case %X", patchDataOffset & 0x7F); + error("Resource patch unsupported special case %X", patchDataOffset & 0x7F); return; } } if (patchDataOffset + 2 >= fsize) { debug("Patching %s failed - patch starting at offset %d can't be in file of size %d", - source->location_name.c_str(), patchDataOffset + 2, fsize); + source->getLocationName().c_str(), patchDataOffset + 2, fsize); return; } - // Prepare destination, if neccessary - if (_resMap.contains(resId) == false) { - newrsc = new Resource; - _resMap.setVal(resId, newrsc); - } else - newrsc = _resMap.getVal(resId); + // Overwrite everything, because we're patching - newrsc->_id = resId; - newrsc->_status = kResStatusNoMalloc; - newrsc->_source = source; - newrsc->size = fsize - patchDataOffset - 2; + newrsc = updateResource(resId, source, fsize - patchDataOffset - 2); newrsc->_headerSize = patchDataOffset; newrsc->_fileOffset = 0; - debugC(1, kDebugLevelResMan, "Patching %s - OK", source->location_name.c_str()); -} -#ifdef ENABLE_SCI32 -void ResourceManager::readResourcePatchesBase36(ResourceSource *source) { + debugC(1, kDebugLevelResMan, "Patching %s - OK", source->getLocationName().c_str()); +} + +void ResourceManager::readResourcePatchesBase36() { // The base36 encoded audio36 and sync36 resources use a different naming scheme, because they // cannot be described with a single resource number, but are a result of a // <number, noun, verb, cond, seq> tuple. Please don't be confused with the normal audio patches @@ -1176,42 +1166,61 @@ void ResourceManager::readResourcePatchesBase36(ResourceSource *source) { for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { name = (*x)->getName(); + inputName = (*x)->getName(); inputName.toUppercase(); - if (inputName.hasPrefix("BOOT")) // skip bootdisk.* - continue; - inputName.deleteChar(0); // delete the first character (type) inputName.deleteChar(7); // delete the dot // The base36 encoded resource contains the following: // uint16 resourceId, byte noun, byte verb, byte cond, byte seq - uint16 resourceNr = strtol(Common::String(inputName.c_str(), 3).c_str(), 0, 36); // 3 characters - byte noun = strtol(Common::String(inputName.c_str() + 3, 2).c_str(), 0, 36); // 2 characters - byte verb = strtol(Common::String(inputName.c_str() + 5, 2).c_str(), 0, 36); // 2 characters - byte cond = strtol(Common::String(inputName.c_str() + 7, 2).c_str(), 0, 36); // 2 characters - byte seq = strtol(Common::String(inputName.c_str() + 9, 1).c_str(), 0, 36); // 1 character - ResourceId resource36((ResourceType)i, resourceNr, noun, verb, cond, seq); - - /* - if (i == kResourceTypeAudio36) - debug("audio36 patch: %s => %s. tuple:%d, %s\n", name.c_str(), inputName.c_str(), resource36.tuple, resource36.toString().c_str()); - else - debug("sync36 patch: %s => %s. tuple:%d, %s\n", name.c_str(), inputName.c_str(), resource36.tuple, resource36.toString().c_str()); - */ - - psrcPatch = new ResourceSource; - psrcPatch->source_type = kSourcePatch; - psrcPatch->location_name = name; - psrcPatch->resourceFile = 0; - processPatch(psrcPatch, (ResourceType)i, resourceNr, resource36.tuple); + uint16 resourceNr = strtol(Common::String(inputName.c_str(), 3).c_str(), 0, 36); // 3 characters + uint16 noun = strtol(Common::String(inputName.c_str() + 3, 2).c_str(), 0, 36); // 2 characters + uint16 verb = strtol(Common::String(inputName.c_str() + 5, 2).c_str(), 0, 36); // 2 characters + uint16 cond = strtol(Common::String(inputName.c_str() + 7, 2).c_str(), 0, 36); // 2 characters + uint16 seq = strtol(Common::String(inputName.c_str() + 9, 1).c_str(), 0, 36); // 1 character + + // Check, if we got valid results + if ((noun <= 255) && (verb <= 255) && (cond <= 255) && (seq <= 255)) { + ResourceId resource36((ResourceType)i, resourceNr, noun, verb, cond, seq); + + /* + if (i == kResourceTypeAudio36) + debug("audio36 patch: %s => %s. tuple:%d, %s\n", name.c_str(), inputName.c_str(), resource36.tuple, resource36.toString().c_str()); + else + debug("sync36 patch: %s => %s. tuple:%d, %s\n", name.c_str(), inputName.c_str(), resource36.tuple, resource36.toString().c_str()); + */ + + // Make sure that the audio patch is a valid resource + if (i == kResourceTypeAudio36) { + Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(name); + uint32 tag = stream->readUint32BE(); + + if (tag == MKID_BE('RIFF') || tag == MKID_BE('FORM')) { + delete stream; + processWavePatch(resource36, name); + continue; + } + + // Check for SOL as well + tag = (tag << 16) | stream->readUint16BE(); + + if (tag != MKID_BE('SOL\0')) { + delete stream; + continue; + } + + delete stream; + } + + psrcPatch = new PatchResourceSource(name); + processPatch(psrcPatch, (ResourceType)i, resourceNr, resource36.getTuple()); + } } } } -#endif - -void ResourceManager::readResourcePatches(ResourceSource *source) { +void ResourceManager::readResourcePatches() { // Note: since some SCI1 games(KQ5 floppy, SQ4) might use SCI0 naming scheme for patch files // this function tries to read patch file with any supported naming scheme, // regardless of s_sciVersion value @@ -1237,10 +1246,12 @@ void ResourceManager::readResourcePatches(ResourceSource *source) { for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { bool bAdd = false; name = (*x)->getName(); + // SCI1 scheme if (isdigit(name[0])) { - resourceNr = atoi(name.c_str()); - bAdd = true; + char *end = 0; + resourceNr = strtol(name.c_str(), &end, 10); + bAdd = (*end == '.'); // Ensure the next character is the period } else { // SCI0 scheme int resname_len = strlen(szResType); @@ -1252,10 +1263,7 @@ void ResourceManager::readResourcePatches(ResourceSource *source) { } if (bAdd) { - psrcPatch = new ResourceSource; - psrcPatch->source_type = kSourcePatch; - psrcPatch->location_name = name; - psrcPatch->resourceFile = 0; + psrcPatch = new PatchResourceSource(name); processPatch(psrcPatch, (ResourceType)i, resourceNr); } } @@ -1264,18 +1272,17 @@ void ResourceManager::readResourcePatches(ResourceSource *source) { int ResourceManager::readResourceMapSCI0(ResourceSource *map) { Common::SeekableReadStream *fileStream = 0; - Resource *res; ResourceType type; uint16 number, id; uint32 offset; - if (map->resourceFile) { - fileStream = map->resourceFile->createReadStream(); + if (map->_resourceFile) { + fileStream = map->_resourceFile->createReadStream(); if (!fileStream) return SCI_ERROR_RESMAP_NOT_FOUND; } else { Common::File *file = new Common::File(); - if (!file->open(map->location_name)) + if (!file->open(map->getLocationName())) return SCI_ERROR_RESMAP_NOT_FOUND; fileStream = file; } @@ -1291,7 +1298,7 @@ int ResourceManager::readResourceMapSCI0(ResourceSource *map) { if (fileStream->eos() || fileStream->err()) { delete fileStream; - warning("Error while reading %s", map->location_name.c_str()); + warning("Error while reading %s", map->getLocationName().c_str()); return SCI_ERROR_RESMAP_NOT_FOUND; } if (offset == 0xFFFFFFFF) @@ -1302,9 +1309,8 @@ int ResourceManager::readResourceMapSCI0(ResourceSource *map) { ResourceId resId = ResourceId(type, number); // adding a new resource if (_resMap.contains(resId) == false) { - res = new Resource; - res->_source = getVolume(map, offset >> bShift); - if (!res->_source) { + ResourceSource *source = findVolume(map, offset >> bShift); + if (!source) { warning("Could not get volume for resource %d, VolumeID %d", id, offset >> bShift); if (_mapVersion != _volVersion) { warning("Retrying with the detected volume version instead"); @@ -1312,12 +1318,11 @@ int ResourceManager::readResourceMapSCI0(ResourceSource *map) { _mapVersion = _volVersion; bMask = (_mapVersion == kResVersionSci1Middle) ? 0xF0 : 0xFC; bShift = (_mapVersion == kResVersionSci1Middle) ? 28 : 26; - res->_source = getVolume(map, offset >> bShift); + source = findVolume(map, offset >> bShift); } } - res->_fileOffset = offset & (((~bMask) << 24) | 0xFFFFFF); - res->_id = resId; - _resMap.setVal(resId, res); + + addResource(resId, source, offset & (((~bMask) << 24) | 0xFFFFFF)); } } while (!fileStream->eos()); @@ -1327,15 +1332,14 @@ int ResourceManager::readResourceMapSCI0(ResourceSource *map) { int ResourceManager::readResourceMapSCI1(ResourceSource *map) { Common::SeekableReadStream *fileStream = 0; - Resource *res; - if (map->resourceFile) { - fileStream = map->resourceFile->createReadStream(); + if (map->_resourceFile) { + fileStream = map->_resourceFile->createReadStream(); if (!fileStream) return SCI_ERROR_RESMAP_NOT_FOUND; } else { Common::File *file = new Common::File(); - if (!file->open(map->location_name)) + if (!file->open(map->getLocationName())) return SCI_ERROR_RESMAP_NOT_FOUND; fileStream = file; } @@ -1382,22 +1386,17 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) { } if (fileStream->eos() || fileStream->err()) { delete fileStream; - warning("Error while reading %s", map->location_name.c_str()); + warning("Error while reading %s", map->getLocationName().c_str()); return SCI_ERROR_RESMAP_NOT_FOUND; } resId = ResourceId((ResourceType)type, number); // adding new resource only if it does not exist if (_resMap.contains(resId) == false) { - res = new Resource; - _resMap.setVal(resId, res); - res->_id = resId; - // NOTE: We add the map's volume number here to the specified volume number // for SCI2.1 and SCI3 maps that are not resmap.000. The resmap.* files' numbers // need to be used in concurrence with the volume specified in the map to get // the actual resource file. - res->_source = getVolume(map, volume_nr + map->volume_number); - res->_fileOffset = off; + addResource(resId, findVolume(map, volume_nr + map->_volumeNumber), off); } } } @@ -1438,11 +1437,11 @@ static uint32 resTypeToMacTag(ResourceType type) { return 0; } -int ResourceManager::readMacResourceFork(ResourceSource *source) { - if (!source->macResMan.open(source->location_name.c_str())) - error("%s is not a valid Mac resource fork", source->location_name.c_str()); +void MacResourceForkResourceSource::scanSource(ResourceManager *resMan) { + if (!_macResMan->open(getLocationName().c_str())) + error("%s is not a valid Mac resource fork", getLocationName().c_str()); - Common::MacResTagArray tagArray = source->macResMan.getResTagArray(); + Common::MacResTagArray tagArray = _macResMan->getResTagArray(); for (uint32 i = 0; i < tagArray.size(); i++) { ResourceType type = kResourceTypeInvalid; @@ -1457,11 +1456,11 @@ int ResourceManager::readMacResourceFork(ResourceSource *source) { if (type == kResourceTypeInvalid) continue; - Common::MacResIDArray idArray = source->macResMan.getResIDArray(tagArray[i]); + Common::MacResIDArray idArray = _macResMan->getResIDArray(tagArray[i]); for (uint32 j = 0; j < idArray.size(); j++) { // Get the size of the file - Common::SeekableReadStream *stream = source->macResMan.getResource(tagArray[i], idArray[j]); + Common::SeekableReadStream *stream = _macResMan->getResource(tagArray[i], idArray[j]); // Some IBIS resources have a size of 0, so we skip them if (!stream) @@ -1472,41 +1471,44 @@ int ResourceManager::readMacResourceFork(ResourceSource *source) { ResourceId resId = ResourceId(type, idArray[j]); - Resource *newrsc = NULL; - - // Prepare destination, if neccessary. Resource forks may contain patches. - if (!_resMap.contains(resId)) { - newrsc = new Resource; - _resMap.setVal(resId, newrsc); - } else - newrsc = _resMap.getVal(resId); - - // Overwrite everything - newrsc->_id = resId; - newrsc->_status = kResStatusNoMalloc; - newrsc->_source = source; - newrsc->size = fileSize; - newrsc->_headerSize = 0; + // Overwrite Resource instance. Resource forks may contain patches. + resMan->updateResource(resId, this, fileSize); } } - - return 0; } void ResourceManager::addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size) { // Adding new resource only if it does not exist if (_resMap.contains(resId) == false) { - Resource *res = new Resource; + Resource *res = new Resource(resId); _resMap.setVal(resId, res); - res->_id = resId; res->_source = src; res->_fileOffset = offset; res->size = size; } } -int ResourceManager::readResourceInfo(Resource *res, Common::SeekableReadStream *file, - uint32&szPacked, ResourceCompression &compression) { +Resource *ResourceManager::updateResource(ResourceId resId, ResourceSource *src, uint32 size) { + // Update a patched resource, whether it exists or not + Resource *res = 0; + + if (_resMap.contains(resId)) { + res = _resMap.getVal(resId); + } else { + res = new Resource(resId); + _resMap.setVal(resId, res); + } + + res->_status = kResStatusNoMalloc; + res->_source = src; + res->_headerSize = 0; + res->size = size; + + return res; +} + +int Resource::readResourceInfo(ResVersion volVersion, Common::SeekableReadStream *file, + uint32 &szPacked, ResourceCompression &compression) { // SCI0 volume format: {wResId wPacked+4 wUnpacked wCompression} = 8 bytes // SCI1 volume format: {bResType wResNumber wPacked+4 wUnpacked wCompression} = 9 bytes // SCI1.1 volume format: {bResType wResNumber wPacked wUnpacked wCompression} = 9 bytes @@ -1515,7 +1517,7 @@ int ResourceManager::readResourceInfo(Resource *res, Common::SeekableReadStream uint32 wCompression, szUnpacked; ResourceType type; - switch (_volVersion) { + switch (volVersion) { case kResVersionSci0Sci1Early: case kResVersionSci1Middle: w = file->readUint16LE(); @@ -1542,8 +1544,8 @@ int ResourceManager::readResourceInfo(Resource *res, Common::SeekableReadStream case kResVersionSci11Mac: // Doesn't store this data in the resource. Fortunately, // we already have this data. - type = res->_id.type; - number = res->_id.number; + type = getType(); + number = getNumber(); szPacked = file->size(); szUnpacked = file->size(); wCompression = 0; @@ -1565,8 +1567,8 @@ int ResourceManager::readResourceInfo(Resource *res, Common::SeekableReadStream if ((file->eos() || file->err())) return SCI_ERROR_IO_ERROR; - res->_id = ResourceId(type, number); - res->size = szUnpacked; + _id = ResourceId(type, number); + size = szUnpacked; // checking compression method switch (wCompression) { @@ -1602,15 +1604,15 @@ int ResourceManager::readResourceInfo(Resource *res, Common::SeekableReadStream return compression == kCompUnknown ? SCI_ERROR_UNKNOWN_COMPRESSION : 0; } -int ResourceManager::decompress(Resource *res, Common::SeekableReadStream *file) { - int error; +int Resource::decompress(ResVersion volVersion, Common::SeekableReadStream *file) { + int errorNum; uint32 szPacked = 0; ResourceCompression compression = kCompUnknown; // fill resource info - error = readResourceInfo(res, file, szPacked, compression); - if (error) - return error; + errorNum = readResourceInfo(volVersion, file, szPacked, compression); + if (errorNum) + return errorNum; // getting a decompressor Decompressor *dec = NULL; @@ -1636,18 +1638,18 @@ int ResourceManager::decompress(Resource *res, Common::SeekableReadStream *file) break; #endif default: - warning("Resource %s: Compression method %d not supported", res->_id.toString().c_str(), compression); + error("Resource %s: Compression method %d not supported", _id.toString().c_str(), compression); return SCI_ERROR_UNKNOWN_COMPRESSION; } - res->data = new byte[res->size]; - res->_status = kResStatusAllocated; - error = res->data ? dec->unpack(file, res->data, szPacked, res->size) : SCI_ERROR_RESOURCE_TOO_BIG; - if (error) - res->unalloc(); + data = new byte[size]; + _status = kResStatusAllocated; + errorNum = data ? dec->unpack(file, data, szPacked, size) : SCI_ERROR_RESOURCE_TOO_BIG; + if (errorNum) + unalloc(); delete dec; - return error; + return errorNum; } ResourceCompression ResourceManager::getViewCompression() { @@ -1661,7 +1663,7 @@ ResourceCompression ResourceManager::getViewCompression() { if (!res) continue; - if (res->_source->source_type != kSourceVolume) + if (res->_source->getSourceType() != kSourceVolume) continue; fileStream = getVolumeFile(res->_source); @@ -1673,13 +1675,13 @@ ResourceCompression ResourceManager::getViewCompression() { uint32 szPacked; ResourceCompression compression; - if (readResourceInfo(res, fileStream, szPacked, compression)) { - if (res->_source->resourceFile) + if (res->readResourceInfo(_volVersion, fileStream, szPacked, compression)) { + if (res->_source->_resourceFile) delete fileStream; continue; } - if (res->_source->resourceFile) + if (res->_source->_resourceFile) delete fileStream; if (compression != kCompNone) @@ -1697,6 +1699,10 @@ ViewType ResourceManager::detectViewType() { Resource *res = findResource(ResourceId(kResourceTypeView, i), 0); if (res) { + // Skip views coming from patch files + if (res->_source->getSourceType() == kSourcePatch) + continue; + switch (res->data[1]) { case 128: // If the 2nd byte is 128, it's a VGA game @@ -1752,6 +1758,7 @@ ViewType ResourceManager::detectViewType() { } } + // this may happen if there are serious system issues (or trying to add a broken game) warning("resMan: Couldn't find any views"); return kViewUnknown; } @@ -1862,7 +1869,7 @@ void ResourceManager::detectSciVersion() { } } - warning("Failed to accurately determine SCI version"); + error("Failed to accurately determine SCI version"); // No parser, we assume SCI_VERSION_01. s_sciVersion = SCI_VERSION_01; return; @@ -1911,15 +1918,18 @@ bool ResourceManager::detectHires() { // SCI32 picture uint16 width = READ_LE_UINT16(res->data + 10); uint16 height = READ_LE_UINT16(res->data + 12); + // Surely lowres (e.g. QFG4CD) if ((width == 320) && ((height == 190) || (height == 200))) return false; + // Surely hires if ((width >= 600) || (height >= 400)) return true; } } } - warning("resMan: Couldn't detect hires"); + // We haven't been able to find hires content + return false; #else error("no sci32 support"); @@ -1939,12 +1949,27 @@ bool ResourceManager::detectFontExtended() { return false; } +// detects, if SCI1.1 game uses palette merging or copying - this is supposed to only get used on SCI1.1 games +bool ResourceManager::detectForPaletteMergingForSci11() { + // Load palette 999 (default palette) + Resource *res = findResource(ResourceId(kResourceTypePalette, 999), false); + + if ((res) && (res->size > 30)) { + byte *data = res->data; + // Old palette format used in palette resource? -> it's merging + if ((data[0] == 0 && data[1] == 1) || (data[0] == 0 && data[1] == 0 && READ_LE_UINT16(data + 29) == 0)) + return true; + return false; + } + return false; +} + // Functions below are based on PD code by Brian Provinciano (SCI Studio) bool ResourceManager::hasOldScriptHeader() { Resource *res = findResource(ResourceId(kResourceTypeScript, 0), 0); if (!res) { - warning("resMan: Failed to find script.000"); + error("resMan: Failed to find script.000"); return false; } diff --git a/engines/sci/resource.h b/engines/sci/resource.h index 533c81bdf5..f66b5b3956 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -23,22 +23,23 @@ * */ -#ifndef SCI_SCICORE_RESOURCE_H -#define SCI_SCICORE_RESOURCE_H +#ifndef SCI_RESOURCE_H +#define SCI_RESOURCE_H -#include "common/fs.h" -#include "common/macresman.h" #include "common/str.h" +#include "common/list.h" +#include "common/hashmap.h" #include "sci/graphics/helpers.h" // for ViewType #include "sci/decompressor.h" #include "sci/sci.h" namespace Common { -class ReadStream; -class WriteStream; class File; +class FSList; class FSNode; +class WriteStream; +class SeekableReadStream; } namespace Sci { @@ -73,18 +74,6 @@ enum { MAX_OPENED_VOLUMES = 5 ///< Max number of simultaneously opened volumes }; -enum ResSourceType { - kSourceDirectory = 0, - kSourcePatch, - kSourceVolume, - kSourceExtMap, - kSourceIntMap, - kSourceAudioVolume, - kSourceExtAudioMap, - kSourceWave, - kSourceMacResourceFork -}; - enum ResourceType { kResourceTypeView = 0, kResourceTypePic, @@ -109,6 +98,7 @@ enum ResourceType { kResourceTypeUnknown1, // Translation, currently unsupported kResourceTypeUnknown2, kResourceTypeRobot, + kResourceTypeVMD, kResourceTypeInvalid, // Mac-only resources, these resource types are self-defined @@ -121,152 +111,199 @@ enum ResourceType { const char *getResourceTypeName(ResourceType restype); -class ResourceManager; - -struct ResourceSource { - ResSourceType source_type; - bool scanned; - Common::String location_name; // FIXME: Replace by FSNode ? - const Common::FSNode *resourceFile; - int volume_number; - ResourceSource *associated_map; - uint32 audioCompressionType; - int32 *audioCompressionOffsetMapping; - Common::MacResManager macResMan; +enum ResVersion { + kResVersionUnknown, + kResVersionSci0Sci1Early, + kResVersionSci1Middle, + kResVersionSci1Late, + kResVersionSci11, + kResVersionSci11Mac, + kResVersionSci32 }; +class ResourceManager; +class ResourceSource; + class ResourceId { -public: - ResourceType type; - uint16 number; - uint32 tuple; // Only used for audio36 and sync36 + static inline ResourceType fixupType(ResourceType type) { + if (type < kResourceTypeMacPict || type > kResourceTypeInvalid) + return kResourceTypeInvalid; + return type; + } - ResourceId() : type(kResourceTypeInvalid), number(0), tuple(0) { } + ResourceType _type; + uint16 _number; + uint32 _tuple; // Only used for audio36 and sync36 + +public: + ResourceId() : _type(kResourceTypeInvalid), _number(0), _tuple(0) { } ResourceId(ResourceType type_, uint16 number_, uint32 tuple_ = 0) - : type(type_), number(number_), tuple(tuple_) { - if (type < kResourceTypeMacPict || type > kResourceTypeInvalid) - type = kResourceTypeInvalid; + : _type(fixupType(type_)), _number(number_), _tuple(tuple_) { } ResourceId(ResourceType type_, uint16 number_, byte noun, byte verb, byte cond, byte seq) - : type(type_), number(number_) { - tuple = (noun << 24) | (verb << 16) | (cond << 8) | seq; - - if ((type < kResourceTypeView) || (type > kResourceTypeInvalid)) - type = kResourceTypeInvalid; + : _type(fixupType(type_)), _number(number_) { + _tuple = (noun << 24) | (verb << 16) | (cond << 8) | seq; } - Common::String toString() { + Common::String toString() const { char buf[32]; - snprintf(buf, 32, "%s.%i", getResourceTypeName(type), number); + snprintf(buf, 32, "%s.%i", getResourceTypeName(_type), _number); Common::String retStr = buf; - if (tuple != 0) { - snprintf(buf, 32, "(%i, %i, %i, %i)", tuple >> 24, (tuple >> 16) & 0xff, (tuple >> 8) & 0xff, tuple & 0xff); + if (_tuple != 0) { + snprintf(buf, 32, "(%i, %i, %i, %i)", _tuple >> 24, (_tuple >> 16) & 0xff, (_tuple >> 8) & 0xff, _tuple & 0xff); retStr += buf; } return retStr; } -}; -struct ResourceIdHash : public Common::UnaryFunction<ResourceId, uint> { - uint operator()(ResourceId val) const { return ((uint)((val.type << 16) | val.number)) ^ val.tuple; } -}; + inline ResourceType getType() const { return _type; } + inline uint16 getNumber() const { return _number; } + inline uint32 getTuple() const { return _tuple; } -struct ResourceIdEqualTo : public Common::BinaryFunction<ResourceId, ResourceId, bool> { - bool operator()(const ResourceId &x, const ResourceId &y) const { return (x.type == y.type) && (x.number == y.number) && (x.tuple == y.tuple); } -}; + inline uint hash() const { + return ((uint)((_type << 16) | _number)) ^ _tuple; + } -struct ResourceIdLess : public Common::BinaryFunction<ResourceId, ResourceId, bool> { - bool operator()(const ResourceId &x, const ResourceId &y) const { - return (x.type < y.type) || ((x.type == y.type) && (x.number < y.number)) - || ((x.type == y.type) && (x.number == y.number) && (x.tuple < y.tuple)); + bool operator==(const ResourceId &other) const { + return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple); + } + + bool operator<(const ResourceId &other) const { + return (_type < other._type) || ((_type == other._type) && (_number < other._number)) + || ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple)); } }; +struct ResourceIdHash : public Common::UnaryFunction<ResourceId, uint> { + uint operator()(ResourceId val) const { return val.hash(); } +}; + /** Class for storing resources in memory */ class Resource { friend class ResourceManager; -public: - Resource(); - ~Resource(); - void unalloc(); -// NOTE : Currently all member data has the same name and public visibility -// to let the rest of the engine compile without changes + // FIXME: These 'friend' declarations are meant to be a temporary hack to + // ease transition to the ResourceSource class system. + friend class ResourceSource; + friend class PatchResourceSource; + friend class WaveResourceSource; + friend class AudioVolumeResourceSource; + friend class MacResourceForkResourceSource; + +// NOTE : Currently most member variables lack the underscore prefix and have +// public visibility to let the rest of the engine compile without changes. public: - ResourceId _id; byte *data; uint32 size; byte *_header; uint32 _headerSize; +public: + Resource(ResourceId id); + ~Resource(); + void unalloc(); + + inline ResourceType getType() const { return _id.getType(); } + inline uint16 getNumber() const { return _id.getNumber(); } + bool isLocked() const { return _status == kResStatusLocked; } + /** + * Write the resource to the specified stream. + * This method is used only by the "dump" debugger command. + */ void writeToStream(Common::WriteStream *stream) const; - uint32 getAudioCompressionType(); + + // FIXME: This audio specific method is a hack. After all, why should a + // Resource have audio specific methods? But for now we keep this, as it + // eases transition. + uint32 getAudioCompressionType() const; protected: + ResourceId _id; // TODO: _id could almost be made const, only readResourceInfo() modifies it... int32 _fileOffset; /**< Offset in file */ ResourceStatus _status; uint16 _lockers; /**< Number of places where this resource was locked */ ResourceSource *_source; + + bool loadPatch(Common::SeekableReadStream *file); + bool loadFromPatchFile(); + bool loadFromWaveFile(Common::SeekableReadStream *file); + bool loadFromAudioVolumeSCI1(Common::SeekableReadStream *file); + bool loadFromAudioVolumeSCI11(Common::SeekableReadStream *file); + int decompress(ResVersion volVersion, Common::SeekableReadStream *file); + int readResourceInfo(ResVersion volVersion, Common::SeekableReadStream *file, uint32 &szPacked, ResourceCompression &compression); }; -typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash, ResourceIdEqualTo> ResourceMap; +typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash> ResourceMap; class ResourceManager { -public: - enum ResVersion { - kResVersionUnknown, - kResVersionSci0Sci1Early, - kResVersionSci1Middle, - kResVersionSci1Late, - kResVersionSci11, - kResVersionSci11Mac, - kResVersionSci32 - }; + // FIXME: These 'friend' declarations are meant to be a temporary hack to + // ease transition to the ResourceSource class system. + friend class ResourceSource; + friend class DirectoryResourceSource; + friend class PatchResourceSource; + friend class ExtMapResourceSource; + friend class IntMapResourceSource; + friend class AudioVolumeResourceSource; + friend class ExtAudioMapResourceSource; + friend class WaveResourceSource; + friend class MacResourceForkResourceSource; +public: /** * Creates a new SCI resource manager. */ ResourceManager(); - ResourceManager(const Common::FSList &fslist); ~ResourceManager(); + + /** + * Initializes the resource manager. + */ + void init(); + + int addAppropriateSources(); + int addAppropriateSources(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive? + /** * Looks up a resource's data. - * @param id: The resource type to look for - * @param lock: non-zero iff the resource should be locked - * @return (Resource *): The resource, or NULL if it doesn't exist + * @param id The resource type to look for + * @param lock non-zero iff the resource should be locked + * @return The resource, or NULL if it doesn't exist * @note Locked resources are guaranteed not to have their contents freed until * they are unlocked explicitly (by unlockResource). */ Resource *findResource(ResourceId id, bool lock); - /* Unlocks a previously locked resource - ** (Resource *) res: The resource to free - ** Returns : () - */ + /** + * Unlocks a previously locked resource. + * @param res The resource to free + */ void unlockResource(Resource *res); - /* Tests whether a resource exists - ** (ResourceId) id: Id of the resource to check - ** Returns : (Resource *) non-NULL if the resource exists, NULL otherwise - ** This function may often be much faster than finding the resource - ** and should be preferred for simple tests. - ** The resource object returned is, indeed, the resource in question, but - ** it should be used with care, as it may be unallocated. - ** Use scir_find_resource() if you want to use the data contained in the resource. - */ + /** + * Tests whether a resource exists. + * + * This function may often be much faster than finding the resource + * and should be preferred for simple tests. + * The resource object returned is, indeed, the resource in question, but + * it should be used with care, as it may be unallocated. + * Use scir_find_resource() if you want to use the data contained in the resource. + * + * @param id Id of the resource to check + * @return non-NULL if the resource exists, NULL otherwise + */ Resource *testResource(ResourceId id); /** * Returns a list of all resources of the specified type. - * @param type: The resource type to look for - * @param mapNumber: For audio36 and sync36, limit search to this map - * @return: The resource list + * @param type The resource type to look for + * @param mapNumber For audio36 and sync36, limit search to this map + * @return The resource list */ Common::List<ResourceId> *listResources(ResourceType type, int mapNumber = -1); @@ -278,27 +315,30 @@ public: ViewType getViewType() const { return _viewType; } const char *getMapVersionDesc() const { return versionDescription(_mapVersion); } const char *getVolVersionDesc() const { return versionDescription(_volVersion); } + ResVersion getVolVersion() const { return _volVersion; } /** * Adds the appropriate GM patch from the Sierra MIDI utility as 4.pat, without * requiring the user to rename the file to 4.pat. Thus, the original Sierra * archive can be extracted in the extras directory, and the GM patches can be - * applied per game, if applicable + * applied per game, if applicable. */ - void addNewGMPatch(const Common::String &gameId); + void addNewGMPatch(SciGameId gameId); bool detectHires(); // Detects, if standard font of current game includes extended characters (>0x80) bool detectFontExtended(); + // Detects, if SCI1.1 game uses palette merging + bool detectForPaletteMergingForSci11(); /** - * Finds the internal Sierra ID of the current game from script 0 + * Finds the internal Sierra ID of the current game from script 0. */ Common::String findSierraGameId(); /** - * Finds the location of the game object from script 0 - * @param addSci11ScriptOffset: Adjust the return value for SCI1.1 and newer + * Finds the location of the game object from script 0. + * @param addSci11ScriptOffset Adjust the return value for SCI1.1 and newer * games. Needs to be false when the heap is accessed directly inside * findSierraGameId(). */ @@ -325,85 +365,54 @@ protected: ResVersion _mapVersion; ///< resource.map version /** - * Initializes the resource manager - */ - void init(); - - /** * Add a path to the resource manager's list of sources. * @return a pointer to the added source structure, or NULL if an error occurred. */ - ResourceSource *addPatchDir(const char *path); + ResourceSource *addPatchDir(const Common::String &path); - ResourceSource *getVolume(ResourceSource *map, int volume_nr); + ResourceSource *findVolume(ResourceSource *map, int volume_nr); /** * Adds a source to the resource manager's list of sources. - * @param map The map associated with this source - * @param type The source type - * @param filename The name of the source to add - * @return A pointer to the added source structure, or NULL if an error occurred. + * @param source The new source to add + * @return A pointer to the added source structure, or NULL if an error occurred. */ - ResourceSource *addSource(ResourceSource *map, ResSourceType type, const char *filename, - int number); - - ResourceSource *addSource(ResourceSource *map, ResSourceType type, - const Common::FSNode *resFile, int number); + ResourceSource *addSource(ResourceSource *source); /** - * Add an external (i.e., separate file) map resource to the resource manager's list of sources. - * @param file_name The name of the volume to add + * Add an external (i.e., separate file) map resource to the resource + * manager's list of sources. + * @param filename The name of the volume to add * @param volume_nr The volume number the map starts at, 0 for <SCI2.1 * @return A pointer to the added source structure, or NULL if an error occurred. */ - ResourceSource *addExternalMap(const char *file_name, int volume_nr = 0); + ResourceSource *addExternalMap(const Common::String &filename, int volume_nr = 0); ResourceSource *addExternalMap(const Common::FSNode *mapFile, int volume_nr = 0); /** - * Add an internal (i.e., resource) map to the resource manager's list of sources. - * @param name The name of the resource to add - * @param resNr The map resource number - * @return A pointer to the added source structure, or NULL if an error occurred. - */ - ResourceSource *addInternalMap(const char *name, int resNr); - - /** - * Checks, if an audio volume got compressed by our tool. If that's the case, it will set audioCompressionType - * and read in the offset translation table for later usage. - */ - void checkIfAudioVolumeIsCompressed(ResourceSource *source); - - /** * Scans newly registered resource sources for resources, earliest addition first. - * @param detected_version: Pointer to the detected version number, + * @param detected_version Pointer to the detected version number, * used during startup. May be NULL. * @return One of SCI_ERROR_*. */ void scanNewSources(); - int addAppropriateSources(); - int addAppropriateSources(const Common::FSList &fslist); + int addInternalSources(); void freeResourceSources(); /** - * Returns a string describing a ResVersion - * @param version: The resource version - * @return: The description of version + * Returns a string describing a ResVersion. + * @param version The resource version + * @return The description of version */ const char *versionDescription(ResVersion version) const; Common::SeekableReadStream *getVolumeFile(ResourceSource *source); void loadResource(Resource *res); - bool loadPatch(Resource *res, Common::SeekableReadStream *file); - bool loadFromPatchFile(Resource *res); - bool loadFromWaveFile(Resource *res, Common::SeekableReadStream *file); - bool loadFromAudioVolumeSCI1(Resource *res, Common::SeekableReadStream *file); - bool loadFromAudioVolumeSCI11(Resource *res, Common::SeekableReadStream *file); void freeOldResources(); - int decompress(Resource *res, Common::SeekableReadStream *file); - int readResourceInfo(Resource *res, Common::SeekableReadStream *file, uint32&szPacked, ResourceCompression &compression); void addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size = 0); + Resource *updateResource(ResourceId resId, ResourceSource *src, uint32 size); void removeAudioResource(ResourceId resId); /**--- Resource map decoding functions ---*/ @@ -423,23 +432,16 @@ protected: * @return 0 on success, an SCI_ERROR_* code otherwise */ int readResourceMapSCI1(ResourceSource *map); - - /** - * Reads the SCI1.1+ resource file from a Mac resource fork. - * @param source The source - * @return 0 on success, an SCI_ERROR_* code otherwise - */ - int readMacResourceFork(ResourceSource *source); /** - * Reads SCI1.1 audio map resources + * Reads SCI1.1 audio map resources. * @param map The map * @return 0 on success, an SCI_ERROR_* code otherwise */ int readAudioMapSCI11(ResourceSource *map); /** - * Reads SCI1 audio map files + * Reads SCI1 audio map files. * @param map The map * @param unload Unload the map instead of loading it * @return 0 on success, an SCI_ERROR_* code otherwise @@ -451,16 +453,15 @@ protected: /** * Reads patch files from a local directory. */ - void readResourcePatches(ResourceSource *source); -#ifdef ENABLE_SCI32 - void readResourcePatchesBase36(ResourceSource *source); -#endif + void readResourcePatches(); + void readResourcePatchesBase36(); void processPatch(ResourceSource *source, ResourceType resourceType, uint16 resourceNr, uint32 tuple = 0); /** - * Process wave files as patches for Audio resources + * Process wave files as patches for Audio resources. */ void readWaveAudioPatches(); + void processWavePatch(ResourceId resourceId, Common::String name); /** * Applies to all versions before 0.000.395 (i.e. KQ4 old, XMAS 1988 and LSL2). @@ -491,6 +492,7 @@ public: uint16 prio; uint16 size; byte *data; + uint16 curPos; long time; byte prev; }; @@ -515,7 +517,6 @@ public: Track *getDigitalTrack(); int getChannelFilterMask(int hardwareMask, bool wantsRhythm); byte getInitialVoiceCount(byte channel); - bool isChannelUsed(byte channel) const { return _channelsUsed & (1 << channel); } private: SciVersion _soundVersion; @@ -523,11 +524,8 @@ private: Track *_tracks; Resource *_innerResource; ResourceManager *_resMan; - uint16 _channelsUsed; - - void setChannelUsed(byte channel) { _channelsUsed |= (1 << channel); } }; } // End of namespace Sci -#endif // SCI_SCICORE_RESOURCE_H +#endif // SCI_RESOURCE_H diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 57efbdcb38..13aa81cb24 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -25,20 +25,30 @@ // Resource library +#include "common/archive.h" #include "common/file.h" #include "sci/resource.h" +#include "sci/resource_intern.h" #include "sci/util.h" namespace Sci { -void ResourceManager::checkIfAudioVolumeIsCompressed(ResourceSource *source) { - Common::SeekableReadStream *fileStream = getVolumeFile(source); +AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum) + : VolumeResourceSource(name, map, volNum, kSourceAudioVolume) { - if (!fileStream) { - warning("Failed to open %s", source->location_name.c_str()); + _audioCompressionType = 0; + _audioCompressionOffsetMapping = NULL; + + /* + * Check if this audio volume got compressed by our tool. If that is the + * case, set _audioCompressionType and read in the offset translation + * table for later usage. + */ + + Common::SeekableReadStream *fileStream = getVolumeFile(resMan, 0); + if (!fileStream) return; - } fileStream->seek(0, SEEK_SET); uint32 compressionType = fileStream->readUint32BE(); @@ -47,13 +57,13 @@ void ResourceManager::checkIfAudioVolumeIsCompressed(ResourceSource *source) { case MKID_BE('OGG '): case MKID_BE('FLAC'): // Detected a compressed audio volume - source->audioCompressionType = compressionType; + _audioCompressionType = compressionType; // Now read the whole offset mapping table for later usage int32 recordCount = fileStream->readUint32LE(); if (!recordCount) error("compressed audio volume doesn't contain any entries!"); int32 *offsetMapping = new int32[(recordCount + 1) * 2]; - source->audioCompressionOffsetMapping = offsetMapping; + _audioCompressionOffsetMapping = offsetMapping; for (int recordNo = 0; recordNo < recordCount; recordNo++) { *offsetMapping++ = fileStream->readUint32LE(); *offsetMapping++ = fileStream->readUint32LE(); @@ -63,105 +73,124 @@ void ResourceManager::checkIfAudioVolumeIsCompressed(ResourceSource *source) { *offsetMapping++ = fileStream->size(); } - if (source->resourceFile) + if (_resourceFile) delete fileStream; } -bool ResourceManager::loadFromWaveFile(Resource *res, Common::SeekableReadStream *file) { - res->data = new byte[res->size]; +bool Resource::loadFromWaveFile(Common::SeekableReadStream *file) { + data = new byte[size]; - uint32 really_read = file->read(res->data, res->size); - if (really_read != res->size) - error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size); + uint32 really_read = file->read(data, size); + if (really_read != size) + error("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size); - res->_status = kResStatusAllocated; + _status = kResStatusAllocated; return true; } -bool ResourceManager::loadFromAudioVolumeSCI11(Resource *res, Common::SeekableReadStream *file) { +bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) { // Check for WAVE files here uint32 riffTag = file->readUint32BE(); if (riffTag == MKID_BE('RIFF')) { - res->_headerSize = 0; - res->size = file->readUint32LE(); + _headerSize = 0; + size = file->readUint32LE(); file->seek(-8, SEEK_CUR); - return loadFromWaveFile(res, file); + return loadFromWaveFile(file); } file->seek(-4, SEEK_CUR); ResourceType type = (ResourceType)(file->readByte() & 0x7f); - if (((res->_id.type == kResourceTypeAudio || res->_id.type == kResourceTypeAudio36) && (type != kResourceTypeAudio)) - || ((res->_id.type == kResourceTypeSync || res->_id.type == kResourceTypeSync36) && (type != kResourceTypeSync))) { - warning("Resource type mismatch loading %s", res->_id.toString().c_str()); - res->unalloc(); + if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio)) + || ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) { + warning("Resource type mismatch loading %s", _id.toString().c_str()); + unalloc(); return false; } - res->_headerSize = file->readByte(); + _headerSize = file->readByte(); if (type == kResourceTypeAudio) { - if (res->_headerSize != 11 && res->_headerSize != 12) { + if (_headerSize != 7 && _headerSize != 11 && _headerSize != 12) { warning("Unsupported audio header"); - res->unalloc(); + unalloc(); return false; } - // Load sample size - file->seek(7, SEEK_CUR); - res->size = file->readUint32LE(); - // Adjust offset to point at the header data again - file->seek(-11, SEEK_CUR); + if (_headerSize != 7) { // Size is defined already from the map + // Load sample size + file->seek(7, SEEK_CUR); + size = file->readUint32LE(); + // Adjust offset to point at the header data again + file->seek(-11, SEEK_CUR); + } } - return loadPatch(res, file); + return loadPatch(file); } -bool ResourceManager::loadFromAudioVolumeSCI1(Resource *res, Common::SeekableReadStream *file) { - res->data = new byte[res->size]; +bool Resource::loadFromAudioVolumeSCI1(Common::SeekableReadStream *file) { + data = new byte[size]; - if (res->data == NULL) { - error("Can't allocate %d bytes needed for loading %s", res->size, res->_id.toString().c_str()); + if (data == NULL) { + error("Can't allocate %d bytes needed for loading %s", size, _id.toString().c_str()); } - unsigned int really_read = file->read(res->data, res->size); - if (really_read != res->size) - warning("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size); + unsigned int really_read = file->read(data, size); + if (really_read != size) + warning("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size); - res->_status = kResStatusAllocated; + _status = kResStatusAllocated; return true; } -void ResourceManager::addNewGMPatch(const Common::String &gameId) { +void ResourceManager::addNewGMPatch(SciGameId gameId) { Common::String gmPatchFile; - if (gameId == "ecoquest") + switch (gameId) { + case GID_ECOQUEST: gmPatchFile = "ECO1GM.PAT"; - else if (gameId == "hoyle3") - gmPatchFile = "HOY3GM.PAT"; - else if (gameId == "hoyle3") + break; + case GID_HOYLE3: gmPatchFile = "HOY3GM.PAT"; - else if (gameId == "lsl1sci") + break; + case GID_LSL1: gmPatchFile = "LL1_GM.PAT"; - else if (gameId == "lsl5") + break; + case GID_LSL5: gmPatchFile = "LL5_GM.PAT"; - else if (gameId == "longbow") + break; + case GID_LONGBOW: gmPatchFile = "ROBNGM.PAT"; - else if (gameId == "sq1sci") + break; + case GID_SQ1: gmPatchFile = "SQ1_GM.PAT"; - else if (gameId == "sq4") + break; + case GID_SQ4: gmPatchFile = "SQ4_GM.PAT"; - else if (gameId == "fairytales") + break; + case GID_FAIRYTALES: gmPatchFile = "TALEGM.PAT"; + break; + default: + break; + } if (!gmPatchFile.empty() && Common::File::exists(gmPatchFile)) { - ResourceSource *psrcPatch = new ResourceSource; - psrcPatch->source_type = kSourcePatch; - psrcPatch->resourceFile = 0; - psrcPatch->location_name = gmPatchFile; + ResourceSource *psrcPatch = new PatchResourceSource(gmPatchFile); processPatch(psrcPatch, kResourceTypePatch, 4); } } +void ResourceManager::processWavePatch(ResourceId resourceId, Common::String name) { + ResourceSource *resSrc = new WaveResourceSource(name); + Common::File file; + file.open(name); + + updateResource(resourceId, resSrc, file.size()); + + debugC(1, kDebugLevelResMan, "Patching %s - OK", name.c_str()); +} + void ResourceManager::readWaveAudioPatches() { // Here we do check for SCI1.1+ so we can patch wav files in as audio resources Common::ArchiveMemberList files; @@ -170,39 +199,8 @@ void ResourceManager::readWaveAudioPatches() { for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { Common::String name = (*x)->getName(); - if (isdigit(name[0])) { - int number = atoi(name.c_str()); - ResourceSource *psrcPatch = new ResourceSource; - psrcPatch->source_type = kSourceWave; - psrcPatch->resourceFile = 0; - psrcPatch->location_name = name; - psrcPatch->volume_number = 0; - psrcPatch->audioCompressionType = 0; - - ResourceId resId = ResourceId(kResourceTypeAudio, number); - - Resource *newrsc = NULL; - - // Prepare destination, if neccessary - if (_resMap.contains(resId) == false) { - newrsc = new Resource; - _resMap.setVal(resId, newrsc); - } else - newrsc = _resMap.getVal(resId); - - // Get the size of the file - Common::SeekableReadStream *stream = (*x)->createReadStream(); - uint32 fileSize = stream->size(); - delete stream; - - // Overwrite everything, because we're patching - newrsc->_id = resId; - newrsc->_status = kResStatusNoMalloc; - newrsc->_source = psrcPatch; - newrsc->size = fileSize; - newrsc->_headerSize = 0; - debugC(1, kDebugLevelResMan, "Patching %s - OK", psrcPatch->location_name.c_str()); - } + if (isdigit(name[0])) + processWavePatch(ResourceId(kResourceTypeAudio, atoi(name.c_str())), name); } } @@ -211,7 +209,7 @@ void ResourceManager::removeAudioResource(ResourceId resId) { if (_resMap.contains(resId)) { Resource *res = _resMap.getVal(resId); - if (res->_source->source_type == kSourceAudioVolume) { + if (res->_source->getSourceType() == kSourceAudioVolume) { if (res->_status == kResStatusLocked) { warning("Failed to remove resource %s (still in use)", resId.toString().c_str()); } else { @@ -261,27 +259,31 @@ void ResourceManager::removeAudioResource(ResourceId resId) { // w syncAscSize (iff seq has bit 6 set) int ResourceManager::readAudioMapSCI11(ResourceSource *map) { - bool isEarly = true; uint32 offset = 0; - Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->volume_number), false); + Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_volumeNumber), false); if (!mapRes) { - warning("Failed to open %i.MAP", map->volume_number); + warning("Failed to open %i.MAP", map->_volumeNumber); return SCI_ERROR_RESMAP_NOT_FOUND; } - ResourceSource *src = getVolume(map, 0); + ResourceSource *src = findVolume(map, 0); if (!src) return SCI_ERROR_NO_RESOURCE_FILES_FOUND; byte *ptr = mapRes->data; - if (map->volume_number == 65535) { - // Heuristic to detect late SCI1.1 map format - if ((mapRes->size >= 6) && (ptr[mapRes->size - 6] != 0xff)) - isEarly = false; + // Heuristic to detect entry size + uint32 entrySize = 0; + for (int i = mapRes->size - 1; i >= 0; --i) { + if (ptr[i] == 0xff) + entrySize++; + else + break; + } + if (map->_volumeNumber == 65535) { while (ptr < mapRes->data + mapRes->size) { uint16 n = READ_LE_UINT16(ptr); ptr += 2; @@ -289,7 +291,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { if (n == 0xffff) break; - if (isEarly) { + if (entrySize == 6) { offset = READ_LE_UINT32(ptr); ptr += 4; } else { @@ -299,10 +301,25 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { addResource(ResourceId(kResourceTypeAudio, n), src, offset); } + } else if (map->_volumeNumber == 0 && entrySize == 10 && ptr[3] == 0) { + // QFG3 demo format + // ptr[3] would be 'seq' in the normal format and cannot possibly be 0 + while (ptr < mapRes->data + mapRes->size) { + uint16 n = READ_BE_UINT16(ptr); + ptr += 2; + + if (n == 0xffff) + break; + + offset = READ_LE_UINT32(ptr); + ptr += 4; + uint32 size = READ_LE_UINT32(ptr); + ptr += 4; + + addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); + } } else { - // Heuristic to detect late SCI1.1 map format - if ((mapRes->size >= 11) && (ptr[mapRes->size - 11] == 0xff)) - isEarly = false; + bool isEarly = (entrySize != 11); if (!isEarly) { offset = READ_LE_UINT32(ptr); @@ -330,15 +347,17 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { ptr += 2; if (syncSize > 0) - addResource(ResourceId(kResourceTypeSync36, map->volume_number, n & 0xffffff3f), src, offset, syncSize); + addResource(ResourceId(kResourceTypeSync36, map->_volumeNumber, n & 0xffffff3f), src, offset, syncSize); } if (n & 0x40) { + // This seems to define the size of raw lipsync data (at least + // in kq6), may also just be general appended data. syncSize += READ_LE_UINT16(ptr); ptr += 2; } - addResource(ResourceId(kResourceTypeAudio36, map->volume_number, n & 0xffffff3f), src, offset + syncSize); + addResource(ResourceId(kResourceTypeAudio36, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize); } } @@ -358,7 +377,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { int ResourceManager::readAudioMapSCI1(ResourceSource *map, bool unload) { Common::File file; - if (!file.open(map->location_name)) + if (!file.open(map->getLocationName())) return SCI_ERROR_RESMAP_NOT_FOUND; bool oldFormat = (file.readUint16LE() >> 11) == kResourceTypeAudio; @@ -370,7 +389,7 @@ int ResourceManager::readAudioMapSCI1(ResourceSource *map, bool unload) { uint32 size = file.readUint32LE(); if (file.eos() || file.err()) { - warning("Error while reading %s", map->location_name.c_str()); + warning("Error while reading %s", map->getLocationName().c_str()); return SCI_ERROR_RESMAP_NOT_FOUND; } @@ -388,7 +407,7 @@ int ResourceManager::readAudioMapSCI1(ResourceSource *map, bool unload) { offset &= 0x0fffffff; // least significant 28 bits } - ResourceSource *src = getVolume(map, volume_nr); + ResourceSource *src = findVolume(map, volume_nr); if (src) { if (unload) @@ -405,7 +424,7 @@ int ResourceManager::readAudioMapSCI1(ResourceSource *map, bool unload) { void ResourceManager::setAudioLanguage(int language) { if (_audioMapSCI1) { - if (_audioMapSCI1->volume_number == language) { + if (_audioMapSCI1->_volumeNumber == language) { // This language is already loaded return; } @@ -417,7 +436,7 @@ void ResourceManager::setAudioLanguage(int language) { Common::List<ResourceSource *>::iterator it = _sources.begin(); while (it != _sources.end()) { ResourceSource *src = *it; - if (src->associated_map == _audioMapSCI1) { + if (src->findVolume(_audioMapSCI1, src->_volumeNumber)) { it = _sources.erase(it); delete src; } else { @@ -441,7 +460,7 @@ void ResourceManager::setAudioLanguage(int language) { return; } - _audioMapSCI1 = addSource(NULL, kSourceExtAudioMap, fullname.c_str(), language); + _audioMapSCI1 = addSource(new ExtAudioMapResourceSource(fullname, language)); // Search for audio volumes for this language and add them to the source list Common::ArchiveMemberList files; @@ -451,18 +470,18 @@ void ResourceManager::setAudioLanguage(int language) { const char *dot = strrchr(name.c_str(), '.'); int number = atoi(dot + 1); - addSource(_audioMapSCI1, kSourceAudioVolume, name.c_str(), number); + addSource(new AudioVolumeResourceSource(this, name, _audioMapSCI1, number)); } scanNewSources(); } int ResourceManager::getAudioLanguage() const { - return (_audioMapSCI1 ? _audioMapSCI1->volume_number : 0); + return (_audioMapSCI1 ? _audioMapSCI1->_volumeNumber : 0); } -SoundResource::SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersion soundVersion) : _resMan(resMan), _soundVersion(soundVersion) { - Resource *resource = _resMan->findResource(ResourceId(kResourceTypeSound, resNumber), true); +SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVersion soundVersion) : _resMan(resMan), _soundVersion(soundVersion) { + Resource *resource = _resMan->findResource(ResourceId(kResourceTypeSound, resourceNr), true); int trackNr, channelNr; if (!resource) return; @@ -473,8 +492,6 @@ SoundResource::SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersi byte *dataEnd; Channel *channel, *sampleChannel; - _channelsUsed = 0; - switch (_soundVersion) { case SCI_VERSION_0_EARLY: case SCI_VERSION_0_LATE: @@ -537,6 +554,9 @@ SoundResource::SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersi } _tracks = new Track[_trackCount]; data = resource->data; + + byte channelCount; + for (trackNr = 0; trackNr < _trackCount; trackNr++) { // Track info starts with track type:BYTE // Then the channel information gets appended Unknown:WORD, ChannelOffset:WORD, ChannelSize:WORD @@ -546,35 +566,47 @@ SoundResource::SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersi _tracks[trackNr].type = *data++; // Counting # of channels used data2 = data; - _tracks[trackNr].channelCount = 0; + channelCount = 0; while (*data2 != 0xFF) { data2 += 6; + channelCount++; _tracks[trackNr].channelCount++; } - _tracks[trackNr].channels = new Channel[_tracks[trackNr].channelCount]; + _tracks[trackNr].channels = new Channel[channelCount]; + _tracks[trackNr].channelCount = 0; _tracks[trackNr].digitalChannelNr = -1; // No digital sound associated _tracks[trackNr].digitalSampleRate = 0; _tracks[trackNr].digitalSampleSize = 0; _tracks[trackNr].digitalSampleStart = 0; _tracks[trackNr].digitalSampleEnd = 0; if (_tracks[trackNr].type != 0xF0) { // Digital track marker - not supported currently - for (channelNr = 0; channelNr < _tracks[trackNr].channelCount; channelNr++) { + channelNr = 0; + while (channelCount--) { channel = &_tracks[trackNr].channels[channelNr]; channel->prio = READ_LE_UINT16(data); - channel->data = resource->data + READ_LE_UINT16(data + 2) + 2; - channel->size = READ_LE_UINT16(data + 4) - 2; // Not counting channel header - channel->number = *(channel->data - 2); - setChannelUsed(channel->number); - channel->poly = *(channel->data - 1); - channel->time = channel->prev = 0; - if (channel->number == 0xFE) { // Digital channel - _tracks[trackNr].digitalChannelNr = channelNr; - _tracks[trackNr].digitalSampleRate = READ_LE_UINT16(channel->data); - _tracks[trackNr].digitalSampleSize = READ_LE_UINT16(channel->data + 2); - _tracks[trackNr].digitalSampleStart = READ_LE_UINT16(channel->data + 4); - _tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6); - channel->data += 8; // Skip over header - channel->size -= 8; + uint dataOffset = READ_LE_UINT16(data + 2); + if (dataOffset < resource->size) { + channel->data = resource->data + dataOffset; + channel->size = READ_LE_UINT16(data + 4); + channel->curPos = 0; + channel->number = *channel->data; + channel->poly = *(channel->data + 1); + channel->time = channel->prev = 0; + channel->data += 2; // skip over header + channel->size -= 2; // remove header size + if (channel->number == 0xFE) { // Digital channel + _tracks[trackNr].digitalChannelNr = channelNr; + _tracks[trackNr].digitalSampleRate = READ_LE_UINT16(channel->data); + _tracks[trackNr].digitalSampleSize = READ_LE_UINT16(channel->data + 2); + _tracks[trackNr].digitalSampleStart = READ_LE_UINT16(channel->data + 4); + _tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6); + channel->data += 8; // Skip over header + channel->size -= 8; + } + _tracks[trackNr].channelCount++; + channelNr++; + } else { + warning("Invalid offset inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr); } data += 6; } diff --git a/engines/sci/resource_intern.h b/engines/sci/resource_intern.h new file mode 100644 index 0000000000..45421dd722 --- /dev/null +++ b/engines/sci/resource_intern.h @@ -0,0 +1,193 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SCI_RESOURCE_INTERN_H +#define SCI_RESOURCE_INTERN_H + +#include "sci/resource.h" + +namespace Common { + class MacResManager; +} + +namespace Sci { + +enum ResSourceType { + kSourceDirectory = 0, + kSourcePatch, + kSourceVolume, + kSourceExtMap, + kSourceIntMap, + kSourceAudioVolume, + kSourceExtAudioMap, + kSourceWave, + kSourceMacResourceFork +}; + + +class ResourceSource { +protected: + const ResSourceType _sourceType; + const Common::String _name; + +public: + bool _scanned; + const Common::FSNode * const _resourceFile; + const int _volumeNumber; + +protected: + ResourceSource(ResSourceType type, const Common::String &name, int volNum = 0, const Common::FSNode *resFile = 0); +public: + virtual ~ResourceSource(); + + ResSourceType getSourceType() const { return _sourceType; } + const Common::String &getLocationName() const { return _name; } + + // Auxiliary method, used by loadResource implementations. + Common::SeekableReadStream *getVolumeFile(ResourceManager *resMan, Resource *res); + + /** + * TODO: Document this + */ + virtual ResourceSource *findVolume(ResourceSource *map, int volNum) { + return NULL; + }; + + /** + * Scan this source for TODO. + */ + virtual void scanSource(ResourceManager *resMan) {} + + /** + * Load a resource. + */ + virtual void loadResource(ResourceManager *resMan, Resource *res); + + // FIXME: This audio specific method is a hack. After all, why should a + // ResourceSource or a Resource (which uses this method) have audio + // specific methods? But for now we keep this, as it eases transition. + virtual uint32 getAudioCompressionType() const { return 0; } +}; + +class DirectoryResourceSource : public ResourceSource { +public: + DirectoryResourceSource(const Common::String &name) : ResourceSource(kSourceDirectory, name) {} + + virtual void scanSource(ResourceManager *resMan); +}; + +class PatchResourceSource : public ResourceSource { +public: + PatchResourceSource(const Common::String &name) : ResourceSource(kSourcePatch, name) {} + + virtual void loadResource(ResourceManager *resMan, Resource *res); +}; + +class VolumeResourceSource : public ResourceSource { +protected: + ResourceSource * const _associatedMap; + +public: + VolumeResourceSource(const Common::String &name, ResourceSource *map, int volNum, ResSourceType type = kSourceVolume) + : ResourceSource(type, name, volNum), _associatedMap(map) { + } + + VolumeResourceSource(const Common::String &name, ResourceSource *map, int volNum, const Common::FSNode *resFile) + : ResourceSource(kSourceVolume, name, volNum, resFile), _associatedMap(map) { + } + + virtual ResourceSource *findVolume(ResourceSource *map, int volNum) { + if (_associatedMap == map && _volumeNumber == volNum) + return this; + return NULL; + }; +}; + +class ExtMapResourceSource : public ResourceSource { +public: + ExtMapResourceSource(const Common::String &name, int volNum, const Common::FSNode *resFile = 0) + : ResourceSource(kSourceExtMap, name, volNum, resFile) { + } + + virtual void scanSource(ResourceManager *resMan); +}; + +class IntMapResourceSource : public ResourceSource { +public: + IntMapResourceSource(const Common::String &name, int volNum) + : ResourceSource(kSourceIntMap, name, volNum) { + } + + virtual void scanSource(ResourceManager *resMan); +}; + +class AudioVolumeResourceSource : public VolumeResourceSource { +protected: + uint32 _audioCompressionType; + int32 *_audioCompressionOffsetMapping; + +public: + AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum); + + virtual void loadResource(ResourceManager *resMan, Resource *res); + + virtual uint32 getAudioCompressionType() const; +}; + +class ExtAudioMapResourceSource : public ResourceSource { +public: + ExtAudioMapResourceSource(const Common::String &name, int volNum) + : ResourceSource(kSourceExtAudioMap, name, volNum) { + } + + virtual void scanSource(ResourceManager *resMan); +}; + +class WaveResourceSource : public ResourceSource { +public: + WaveResourceSource(const Common::String &name) : ResourceSource(kSourceWave, name) {} + + virtual void loadResource(ResourceManager *resMan, Resource *res); +}; + +/** + * Reads SCI1.1+ resources from a Mac resource fork. + */ +class MacResourceForkResourceSource : public ResourceSource { +protected: + Common::MacResManager *_macResMan; + +public: + MacResourceForkResourceSource(const Common::String &name, int volNum); + ~MacResourceForkResourceSource(); + + virtual void scanSource(ResourceManager *resMan); + + virtual void loadResource(ResourceManager *resMan, Resource *res); +}; + +} // End of namespace Sci + +#endif // SCI_RESOURCE_INTERN_H diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 929bdf3307..1ebc6a2ba3 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -36,22 +36,32 @@ #include "sci/event.h" #include "sci/engine/features.h" +#include "sci/engine/message.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/script.h" // for script_adjust_opcode_formats +#include "sci/engine/selector.h" // for SELECTOR #include "sci/sound/audio.h" #include "sci/sound/soundcmd.h" -#include "sci/graphics/gui.h" +#include "sci/graphics/animate.h" +#include "sci/graphics/cache.h" +#include "sci/graphics/compare.h" +#include "sci/graphics/controls.h" +#include "sci/graphics/coordadjuster.h" +#include "sci/graphics/cursor.h" #include "sci/graphics/maciconbar.h" +#include "sci/graphics/menu.h" +#include "sci/graphics/paint16.h" +#include "sci/graphics/paint32.h" #include "sci/graphics/ports.h" #include "sci/graphics/palette.h" -#include "sci/graphics/cursor.h" #include "sci/graphics/screen.h" -#include "sci/graphics/cache.h" +#include "sci/graphics/text16.h" +#include "sci/graphics/transitions.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/gui32.h" +#include "sci/graphics/frameout.h" #endif namespace Sci { @@ -61,13 +71,22 @@ SciEngine *g_sci = 0; class GfxDriver; -SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc) - : Engine(syst), _gameDescription(desc), _system(syst) { - _console = NULL; +SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gameId) + : Engine(syst), _gameDescription(desc), _gameId(gameId) { assert(g_sci == 0); g_sci = this; + + _gfxMacIconBar = 0; + + _audio = 0; _features = 0; + _resMan = 0; + _gamestate = 0; + _kernel = 0; + _vocabulary = 0; + _eventMan = 0; + _console = 0; // Set up the engine specific debug levels DebugMan.addDebugChannel(kDebugLevelError, "Error", "Script error debugging"); @@ -78,10 +97,8 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc) DebugMan.addDebugChannel(kDebugLevelFuncCheck, "Func", "Function parameter debugging"); DebugMan.addDebugChannel(kDebugLevelBresen, "Bresenham", "Bresenham algorithms debugging"); DebugMan.addDebugChannel(kDebugLevelSound, "Sound", "Sound debugging"); - DebugMan.addDebugChannel(kDebugLevelGfxDriver, "Gfxdriver", "Gfx driver debugging"); DebugMan.addDebugChannel(kDebugLevelBaseSetter, "Base", "Base Setter debugging"); DebugMan.addDebugChannel(kDebugLevelParser, "Parser", "Parser debugging"); - DebugMan.addDebugChannel(kDebugLevelMenu, "Menu", "Menu handling debugging"); DebugMan.addDebugChannel(kDebugLevelSaid, "Said", "Said specs debugging"); DebugMan.addDebugChannel(kDebugLevelFile, "File", "File I/O debugging"); DebugMan.addDebugChannel(kDebugLevelTime, "Time", "Time debugging"); @@ -91,26 +108,24 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc) DebugMan.addDebugChannel(kDebugLevelVM, "VM", "VM debugging"); DebugMan.addDebugChannel(kDebugLevelScripts, "Scripts", "Notifies when scripts are unloaded"); DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging"); - DebugMan.addDebugChannel(kDebugLevelSci0Pic, "Sci0Pic", "SCI0 pic drawing debugging"); DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging"); DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game"); - _gamestate = 0; - _gfxMacIconBar = 0; - const Common::FSNode gameDataDir(ConfMan.get("path")); SearchMan.addSubDirectoryMatching(gameDataDir, "actors"); // KQ6 hi-res portraits SearchMan.addSubDirectoryMatching(gameDataDir, "aud"); // resource.aud and audio files - SearchMan.addSubDirectoryMatching(gameDataDir, "avi"); // AVI movie files for Windows versions - SearchMan.addSubDirectoryMatching(gameDataDir, "seq"); // SEQ movie files for DOS versions SearchMan.addSubDirectoryMatching(gameDataDir, "wav"); // speech files in WAV format SearchMan.addSubDirectoryMatching(gameDataDir, "sfx"); // music/sound files in WAV format - SearchMan.addSubDirectoryMatching(gameDataDir, "robot"); // robot files + SearchMan.addSubDirectoryMatching(gameDataDir, "avi"); // AVI movie files for Windows versions + SearchMan.addSubDirectoryMatching(gameDataDir, "seq"); // SEQ movie files for DOS versions + SearchMan.addSubDirectoryMatching(gameDataDir, "robot"); // robot movie files + SearchMan.addSubDirectoryMatching(gameDataDir, "movies"); // vmd movie files + SearchMan.addSubDirectoryMatching(gameDataDir, "vmd"); // vmd movie files // Add the patches directory, except for KQ6CD; The patches folder in some versions of KQ6CD // is for the demo of Phantasmagoria, included in the disk - if (strcmp(getGameID(), "kq6")) + if (_gameId != GID_KQ6) SearchMan.addSubDirectoryMatching(gameDataDir, "patches"); // resource patches } @@ -118,14 +133,35 @@ SciEngine::~SciEngine() { // Remove all of our debug levels here DebugMan.clearAllDebugChannels(); +#ifdef ENABLE_SCI32 + delete _gfxFrameout; +#endif + delete _gfxMenu; + delete _gfxControls; + delete _gfxText16; + delete _gfxAnimate; + delete _gfxPaint; + delete _gfxTransitions; + delete _gfxCompare; + delete _gfxCoordAdjuster; + delete _gfxPorts; + delete _gfxCache; + delete _gfxPalette; + delete _gfxCursor; + delete _gfxScreen; + delete _audio; + delete _soundCmd; delete _kernel; delete _vocabulary; delete _console; - delete _resMan; delete _features; delete _gfxMacIconBar; + delete _eventMan; + delete _gamestate->_segMan; + delete _gamestate; + delete _resMan; // should be deleted last g_sci = 0; } @@ -135,166 +171,289 @@ Common::Error SciEngine::run() { ConfMan.registerDefault("enable_fb01", "false"); _resMan = new ResourceManager(); + assert(_resMan); + _resMan->addAppropriateSources(); + _resMan->init(); + // TODO: Add error handling. Check return values of addAppropriateSources + // and init. We first have to *add* sensible return values, though ;). +/* if (!_resMan) { warning("No resources found, aborting"); return Common::kNoGameDataFoundError; } +*/ - SegManager *segMan = new SegManager(_resMan); - - // Scale the screen, if needed - int upscaledHires = GFX_SCREEN_UPSCALED_DISABLED; - - // King's Quest 6 and Gabriel Knight 1 have hires content, gk1/cd was able to provide that under DOS as well, but as - // gk1/floppy does support upscaled hires scriptswise, but doesn't actually have the hires content we need to limit - // it to platform windows. - if (getPlatform() == Common::kPlatformWindows) { - if (!strcmp(getGameID(), "kq6")) - upscaledHires = GFX_SCREEN_UPSCALED_640x440; -#ifdef ENABLE_SCI32 - if (!strcmp(getGameID(), "gk1")) - upscaledHires = GFX_SCREEN_UPSCALED_640x480; -#endif - } - - // Japanese versions of games use hi-res font on upscaled version of the game - if ((getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1)) - upscaledHires = GFX_SCREEN_UPSCALED_640x400; - - // Initialize graphics-related parts - GfxScreen *screen = 0; + // Reset, so that error()s before SoundCommandParser is initialized wont cause a crash + _soundCmd = NULL; - // invokes initGraphics() - if (_resMan->detectHires()) - screen = new GfxScreen(_resMan, 640, 480); - else - screen = new GfxScreen(_resMan, 320, 200, upscaledHires); + // Add the after market GM patches for the specified game, if they exist + _resMan->addNewGMPatch(_gameId); + _gameObj = _resMan->findGameObject(); - if (_resMan->isSci11Mac() && getSciVersion() == SCI_VERSION_1_1) - _gfxMacIconBar = new GfxMacIconBar(); + SegManager *segMan = new SegManager(_resMan); - GfxPalette *palette = new GfxPalette(_resMan, screen); - GfxCache *cache = new GfxCache(_resMan, screen, palette); - GfxCursor *cursor = new GfxCursor(_resMan, palette, screen); + // Initialize the game screen + _gfxScreen = new GfxScreen(_resMan); + _gfxScreen->debugUnditherSetState(ConfMan.getBool("undither")); // Create debugger console. It requires GFX to be initialized _console = new Console(this); - _kernel = new Kernel(_resMan, segMan); + _features = new GameFeatures(segMan, _kernel); // Only SCI0 and SCI01 games used a parser _vocabulary = (getSciVersion() <= SCI_VERSION_1_EGA) ? new Vocabulary(_resMan) : NULL; _audio = new AudioPlayer(_resMan); + _gamestate = new EngineState(segMan); + _eventMan = new EventManager(_resMan->detectFontExtended()); - _features = new GameFeatures(segMan, _kernel); + // The game needs to be initialized before the graphics system is initialized, as + // the graphics code checks parts of the seg manager upon initialization (e.g. for + // the presence of the fastCast object) + if (!initGame()) { /* Initialize */ + warning("Game initialization failed: Aborting..."); + // TODO: Add an "init failed" error? + return Common::kUnknownError; + } - _gamestate = new EngineState(segMan); + script_adjust_opcode_formats(); - _gamestate->_event = new SciEvent(_resMan); + // Must be called after game_init(), as they use _features + _kernel->loadKernelNames(_features); + _soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType()); - if (script_init_engine(_gamestate)) - return Common::kUnknownError; + syncSoundSettings(); -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2) { - _gfxAnimate = 0; - _gfxControls = 0; - _gfxMenu = 0; - _gfxPaint16 = 0; - _gfxPorts = 0; - _gui = 0; - _gui32 = new SciGui32(_gamestate->_segMan, _gamestate->_event, screen, palette, cache, cursor); - } else { -#endif - _gfxPorts = new GfxPorts(segMan, screen); - _gui = new SciGui(_gamestate, screen, palette, cache, cursor, _gfxPorts, _audio); -#ifdef ENABLE_SCI32 - _gui32 = 0; - _gfxFrameout = 0; + // Initialize all graphics related subsystems + initGraphics(); + + debug("Emulating SCI version %s\n", getSciVersionDesc(getSciVersion())); + + // Check whether loading a savestate was requested + int saveSlot = ConfMan.getInt("save_slot"); + if (saveSlot >= 0) { + reg_t restoreArgv[2] = { NULL_REG, make_reg(0, saveSlot) }; // special call (argv[0] is NULL) + kRestoreGame(_gamestate, 2, restoreArgv); + + // Initialize the game menu, if there is one. + // This is not done when loading, so we must do it manually. + reg_t menuBarObj = _gamestate->_segMan->findObjectByName("MenuBar"); + if (menuBarObj.isNull()) + menuBarObj = _gamestate->_segMan->findObjectByName("TheMenuBar"); // LSL2 + if (menuBarObj.isNull()) + menuBarObj = _gamestate->_segMan->findObjectByName("menuBar"); // LSL6 + if (!menuBarObj.isNull()) { + // Reset abortScriptProcessing before initializing the game menu, so that the + // VM call performed by invokeSelector will actually run. + _gamestate->abortScriptProcessing = kAbortNone; + Object *menuBar = _gamestate->_segMan->getObject(menuBarObj); + // Invoke the first method (init) of the menuBar object + invokeSelector(_gamestate, menuBarObj, menuBar->getFuncSelector(0), 0, _gamestate->stack_base); + _gamestate->abortScriptProcessing = kAbortLoadGame; + } } -#endif - _gfxPalette = palette; - _gfxScreen = screen; - _gfxCache = cache; - _gfxCursor = cursor; + runGame(); - if (game_init(_gamestate)) { /* Initialize */ - warning("Game initialization failed: Aborting..."); - // TODO: Add an "init failed" error? - return Common::kUnknownError; + ConfMan.flushToDisk(); + + return Common::kNoError; +} + +bool SciEngine::initGame() { + // Script 0 needs to be allocated here before anything else! + int script0Segment = _gamestate->_segMan->getScriptSegment(0, SCRIPT_GET_LOCK); + DataStack *stack = _gamestate->_segMan->allocateStack(VM_STACK_SIZE, NULL); + + _gamestate->_msgState = new MessageState(_gamestate->_segMan); + _gamestate->gcCountDown = GC_INTERVAL - 1; + + // Script 0 should always be at segment 1 + if (script0Segment != 1) { + debug(2, "Failed to instantiate script.000"); + return false; } - // Add the after market GM patches for the specified game, if they exist - _resMan->addNewGMPatch(getGameID()); + _gamestate->initGlobals(); - script_adjust_opcode_formats(_gamestate); - _kernel->loadKernelNames(getGameID()); + if (_gamestate->abortScriptProcessing == kAbortRestartGame && _gfxMenu) + _gfxMenu->reset(); - SciVersion soundVersion = _features->detectDoSoundType(); + _gamestate->_segMan->initSysStrings(); - _gamestate->_soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, soundVersion); + _gamestate->r_acc = _gamestate->r_prev = NULL_REG; - screen->debugUnditherSetState(ConfMan.getBool("undither")); + _gamestate->_executionStack.clear(); // Start without any execution stack + _gamestate->executionStackBase = -1; // No vm is running yet + _gamestate->_executionStackPosChanged = false; -#ifdef USE_OLD_MUSIC_FUNCTIONS - if (game_init_sound(_gamestate, 0, soundVersion)) { - warning("Game initialization failed: Error in sound subsystem. Aborting..."); - return Common::kUnknownError; + _gamestate->abortScriptProcessing = kAbortNone; + _gamestate->gameWasRestarted = false; + + _gamestate->stack_base = stack->_entries; + _gamestate->stack_top = stack->_entries + stack->_capacity; + + if (!_gamestate->_segMan->instantiateScript(0)) { + error("initGame(): Could not instantiate script 0"); + return false; } -#endif - syncSoundSettings(); + // Reset parser + if (_vocabulary) { + _vocabulary->parserIsValid = false; // Invalidate parser + _vocabulary->parser_event = NULL_REG; // Invalidate parser event + _vocabulary->parser_base = make_reg(_gamestate->_segMan->getSysStringsSegment(), SYS_STRING_PARSER_BASE); + } + _gamestate->gameStartTime = _gamestate->lastWaitTime = _gamestate->_screenUpdateTime = g_system->getMillis(); + + srand(g_system->getMillis()); // Initialize random number generator + + // Load game language into printLang property of game object + setSciLanguage(); + + return true; +} + +void SciEngine::initGraphics() { + + // Reset all graphics objects + _gfxAnimate = 0; + _gfxCache = 0; + _gfxCompare = 0; + _gfxControls = 0; + _gfxCoordAdjuster = 0; + _gfxCursor = 0; + _gfxMacIconBar = 0; + _gfxMenu = 0; + _gfxPaint = 0; + _gfxPaint16 = 0; + _gfxPalette = 0; + _gfxPorts = 0; + _gfxText16 = 0; + _gfxTransitions = 0; #ifdef ENABLE_SCI32 - if (_gui32) - _gui32->init(); - else + _gfxFrameout = 0; + _gfxPaint32 = 0; #endif - _gui->init(_features->usesOldGfxFunctions()); - debug("Emulating SCI version %s\n", getSciVersionDesc(getSciVersion())); + if (_resMan->isSci11Mac() && getSciVersion() == SCI_VERSION_1_1) + _gfxMacIconBar = new GfxMacIconBar(); - // Check whether loading a savestate was requested - if (ConfMan.hasKey("save_slot")) { - _gamestate->loadFromLauncher = ConfMan.getInt("save_slot"); + bool paletteMerging = true; + if (getSciVersion() >= SCI_VERSION_1_1) { + // there are some games that use inbetween SCI1.1 interpreter, so we have to detect if it's merging or copying + if (getSciVersion() == SCI_VERSION_1_1) + paletteMerging = _resMan->detectForPaletteMergingForSci11(); + else + paletteMerging = false; + } + + _gfxPalette = new GfxPalette(_resMan, _gfxScreen, paletteMerging); + _gfxCache = new GfxCache(_resMan, _gfxScreen, _gfxPalette); + _gfxCursor = new GfxCursor(_resMan, _gfxPalette, _gfxScreen); + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + // SCI32 graphic objects creation + _gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan); + _gfxCursor->init(_gfxCoordAdjuster, _eventMan); + _gfxCompare = new GfxCompare(_gamestate->_segMan, g_sci->getKernel(), _gfxCache, _gfxScreen, _gfxCoordAdjuster); + _gfxPaint32 = new GfxPaint32(g_sci->getResMan(), _gamestate->_segMan, g_sci->getKernel(), _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette); + _gfxPaint = _gfxPaint32; + _gfxFrameout = new GfxFrameout(_gamestate->_segMan, g_sci->getResMan(), _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette, _gfxPaint32); } else { - _gamestate->loadFromLauncher = -1; +#endif + // SCI0-SCI1.1 graphic objects creation + _gfxPorts = new GfxPorts(_gamestate->_segMan, _gfxScreen); + _gfxCoordAdjuster = new GfxCoordAdjuster16(_gfxPorts); + _gfxCursor->init(_gfxCoordAdjuster, g_sci->getEventManager()); + _gfxCompare = new GfxCompare(_gamestate->_segMan, g_sci->getKernel(), _gfxCache, _gfxScreen, _gfxCoordAdjuster); + _gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette, g_sci->getResMan()->isVGA()); + _gfxPaint16 = new GfxPaint16(g_sci->getResMan(), _gamestate->_segMan, g_sci->getKernel(), _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette, _gfxTransitions, _audio); + _gfxPaint = _gfxPaint16; + _gfxAnimate = new GfxAnimate(_gamestate, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette, _gfxCursor, _gfxTransitions); + _gfxText16 = new GfxText16(g_sci->getResMan(), _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen); + _gfxControls = new GfxControls(_gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen); + _gfxMenu = new GfxMenu(g_sci->getEventManager(), _gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen, _gfxCursor); + + _gfxMenu->reset(); +#ifdef ENABLE_SCI32 } +#endif - game_run(&_gamestate); // Run the game + if (_gfxPorts) { + _gfxPorts->init(_features->usesOldGfxFunctions(), _gfxPaint16, _gfxText16); + _gfxPaint16->init(_gfxAnimate, _gfxText16); + } + // Set default (EGA, amiga or resource 999) palette + _gfxPalette->setDefault(); +} - game_exit(_gamestate); +void SciEngine::initStackBaseWithSelector(Selector selector) { + _gamestate->stack_base[0] = make_reg(0, (uint16)selector); + _gamestate->stack_base[1] = NULL_REG; - ConfMan.flushToDisk(); + // Register the first element on the execution stack + if (!send_selector(_gamestate, _gameObj, _gameObj, _gamestate->stack_base, 2, _gamestate->stack_base)) { + _console->printObject(_gameObj); + error("initStackBaseWithSelector: error while registering the first selector in the call stack"); + } - delete _gamestate->_soundCmd; - delete _gui; -#ifdef ENABLE_SCI32 - delete _gui32; -#endif - delete _gfxPorts; - delete _gfxCache; - delete _gfxPalette; - delete cursor; - delete _gfxScreen; - delete _gamestate->_event; - delete segMan; - delete _gamestate; +} - return Common::kNoError; +void SciEngine::runGame() { + initStackBaseWithSelector(SELECTOR(play)); // Call the play selector + + // Attach the debug console on game startup, if requested + if (DebugMan.isDebugChannelEnabled(kDebugLevelOnStartup)) + _console->attach(); + + do { + _gamestate->_executionStackPosChanged = false; + run_vm(_gamestate, (_gamestate->abortScriptProcessing == kAbortLoadGame)); + exitGame(); + + if (_gamestate->abortScriptProcessing == kAbortRestartGame) { + _gamestate->_segMan->resetSegMan(); + initGame(); + initStackBaseWithSelector(SELECTOR(play)); + _gamestate->gameWasRestarted = true; + } else if (_gamestate->abortScriptProcessing == kAbortLoadGame) { + _gamestate->abortScriptProcessing = kAbortNone; + initStackBaseWithSelector(SELECTOR(replay)); + } else { + break; // exit loop + } + } while (true); +} + +void SciEngine::exitGame() { + if (_gamestate->abortScriptProcessing != kAbortLoadGame) { + _gamestate->_executionStack.clear(); + _audio->stopAllAudio(); + g_sci->_soundCmd->clearPlayList(); + } + + // TODO Free parser segment here + + // TODO Free scripts here + + // Close all opened file handles + _gamestate->_fileHandles.clear(); + _gamestate->_fileHandles.resize(5); } // Invoked by error() when a severe error occurs GUI::Debugger *SciEngine::getDebugger() { if (_gamestate) { ExecStack *xs = &(_gamestate->_executionStack.back()); - xs->addr.pc.offset = g_debugState.old_pc_offset; - xs->sp = g_debugState.old_sp; + xs->addr.pc.offset = _debugState.old_pc_offset; + xs->sp = _debugState.old_sp; } - g_debugState.runningStep = 0; // Stop multiple execution - g_debugState.seeking = kDebugSeekNothing; // Stop special seeks + _debugState.runningStep = 0; // Stop multiple execution + _debugState.seeking = kDebugSeekNothing; // Stop special seeks return _console; } @@ -304,7 +463,7 @@ Console *SciEngine::getSciDebugger() { return _console; } -const char* SciEngine::getGameID() const { +const char *SciEngine::getGameIdStr() const { return _gameDescription->gameid; } @@ -316,12 +475,8 @@ Common::Platform SciEngine::getPlatform() const { return _gameDescription->platform; } -uint32 SciEngine::getFlags() const { - return _gameDescription->flags; -} - bool SciEngine::isDemo() const { - return getFlags() & ADGF_DEMO; + return _gameDescription->flags & ADGF_DEMO; } Common::String SciEngine::getSavegameName(int nr) const { @@ -333,14 +488,12 @@ Common::String SciEngine::getSavegamePattern() const { } Common::String SciEngine::getFilePrefix() const { - const char* gameID = getGameID(); - if (!strcmp(gameID, "qfg2")) { + if (_gameId == GID_QFG2) { // Quest for Glory 2 wants to read files from Quest for Glory 1 (EGA/VGA) to import character data if (_gamestate->currentRoomNumber() == 805) return "qfg1"; // TODO: Include import-room for qfg1vga - } - if (!strcmp(gameID, "qfg3")) { + } else if (_gameId == GID_QFG3) { // Quest for Glory 3 wants to read files from Quest for Glory 2 to import character data if (_gamestate->currentRoomNumber() == 54) return "qfg2"; @@ -361,27 +514,22 @@ Common::String SciEngine::unwrapFilename(const Common::String &name) const { } void SciEngine::pauseEngineIntern(bool pause) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - _gamestate->_sound.sfx_suspend(pause); -#endif _mixer->pauseAll(pause); } void SciEngine::syncSoundSettings() { Engine::syncSoundSettings(); -#ifndef USE_OLD_MUSIC_FUNCTIONS bool mute = false; if (ConfMan.hasKey("mute")) mute = ConfMan.getBool("mute"); int soundVolumeMusic = (mute ? 0 : ConfMan.getInt("music_volume")); - if (_gamestate && _gamestate->_soundCmd) { + if (_gamestate && g_sci->_soundCmd) { int vol = (soundVolumeMusic + 1) * SoundCommandParser::kMaxSciVolume / Audio::Mixer::kMaxMixerVolume; - _gamestate->_soundCmd->setMasterVolume(vol); + g_sci->_soundCmd->setMasterVolume(vol); } -#endif } } // End of namespace Sci diff --git a/engines/sci/sci.h b/engines/sci/sci.h index 685f05e685..c15f87e4e2 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -28,6 +28,8 @@ #include "engines/engine.h" #include "common/util.h" +#include "sci/engine/vm_types.h" // for Selector +#include "sci/debug.h" // for DebugState struct ADGameDescription; @@ -41,9 +43,6 @@ struct ADGameDescription; */ namespace Sci { -// Uncomment this to use old music functions -//#define USE_OLD_MUSIC_FUNCTIONS - struct EngineState; class Vocabulary; class ResourceManager; @@ -51,6 +50,8 @@ class Kernel; class GameFeatures; class Console; class AudioPlayer; +class SoundCommandParser; +class EventManager; class GfxAnimate; class GfxCache; @@ -58,14 +59,16 @@ class GfxCompare; class GfxControls; class GfxCoordAdjuster; class GfxCursor; +class GfxMacIconBar; class GfxMenu; class GfxPaint; class GfxPaint16; +class GfxPaint32; class GfxPalette; class GfxPorts; class GfxScreen; -class SciGui; -class GfxMacIconBar; +class GfxText16; +class GfxTransitions; #ifdef ENABLE_SCI32 class SciGui32; @@ -82,22 +85,89 @@ enum kDebugLevels { kDebugLevelFuncCheck = 1 << 5, kDebugLevelBresen = 1 << 6, kDebugLevelSound = 1 << 7, - kDebugLevelGfxDriver = 1 << 8, - kDebugLevelBaseSetter = 1 << 9, - kDebugLevelParser = 1 << 10, - kDebugLevelMenu = 1 << 11, - kDebugLevelSaid = 1 << 12, - kDebugLevelFile = 1 << 13, - kDebugLevelTime = 1 << 14, - kDebugLevelRoom = 1 << 15, - kDebugLevelAvoidPath = 1 << 16, - kDebugLevelDclInflate = 1 << 17, - kDebugLevelVM = 1 << 18, - kDebugLevelScripts = 1 << 19, - kDebugLevelGC = 1 << 20, - kDebugLevelSci0Pic = 1 << 21, - kDebugLevelResMan = 1 << 22, - kDebugLevelOnStartup = 1 << 23 + kDebugLevelBaseSetter = 1 << 8, + kDebugLevelParser = 1 << 9, + kDebugLevelSaid = 1 << 10, + kDebugLevelFile = 1 << 11, + kDebugLevelTime = 1 << 12, + kDebugLevelRoom = 1 << 13, + kDebugLevelAvoidPath = 1 << 14, + kDebugLevelDclInflate = 1 << 15, + kDebugLevelVM = 1 << 16, + kDebugLevelScripts = 1 << 17, + kDebugLevelGC = 1 << 18, + kDebugLevelResMan = 1 << 19, + kDebugLevelOnStartup = 1 << 20 +}; + +enum SciGameId { + GID_ASTROCHICKEN, + GID_CAMELOT, + GID_CASTLEBRAIN, + GID_CHRISTMAS1988, + GID_CHRISTMAS1990, + GID_CHRISTMAS1992, + GID_CNICK_KQ, + GID_CNICK_LAURABOW, + GID_CNICK_LONGBOW, + GID_CNICK_LSL, + GID_CNICK_SQ, + GID_ECOQUEST, + GID_ECOQUEST2, + GID_FAIRYTALES, + GID_FREDDYPHARKAS, + GID_FUNSEEKER, + GID_GK1, + GID_GK2, + GID_HOYLE1, + GID_HOYLE2, + GID_HOYLE3, + GID_HOYLE4, + GID_ICEMAN, + GID_ISLANDBRAIN, + GID_JONES, + GID_KQ1, + GID_KQ4, + GID_KQ5, + GID_KQ6, + GID_KQ7, + GID_LAURABOW, + GID_LAURABOW2, + GID_LIGHTHOUSE, + GID_LONGBOW, + GID_LSL1, + GID_LSL2, + GID_LSL3, + GID_LSL5, + GID_LSL6, + GID_LSL6HIRES, // We have a separate ID for LSL6 SCI32, because it's actually a completely different game + GID_LSL7, + GID_MOTHERGOOSE, + GID_MSASTROCHICKEN, + GID_PEPPER, + GID_PHANTASMAGORIA, + GID_PHANTASMAGORIA2, + GID_PQ1, + GID_PQ2, + GID_PQ3, + GID_PQ4, + GID_PQSWAT, + GID_QFG1, + GID_QFG2, + GID_QFG3, + GID_QFG4, + GID_RAMA, + GID_SHIVERS, + GID_SHIVERS2, + GID_SLATER, + GID_SQ1, + GID_SQ3, + GID_SQ4, + GID_SQ5, + GID_SQ6, + GID_TORIN, + + GID_FANMADE // FIXME: Do we really need/want this? }; /** SCI versions */ @@ -116,12 +186,6 @@ enum SciVersion { SCI_VERSION_3 // LSL7, RAMA, Lighthouse }; -enum MoveCountType { - kMoveCountUninitialized, - kIgnoreMoveCount, - kIncrementMoveCount -}; - /** Supported languages */ enum kLanguage { K_LANG_NONE = 0, @@ -138,7 +202,7 @@ enum kLanguage { class SciEngine : public Engine { friend class Console; public: - SciEngine(OSystem *syst, const ADGameDescription *desc); + SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gameId); ~SciEngine(); // Engine APIs @@ -153,17 +217,19 @@ public: bool canSaveGameStateCurrently(); void syncSoundSettings(); - const char* getGameID() const; + const SciGameId &getGameId() const { return _gameId; } + const char *getGameIdStr() const; int getResourceVersion() const; Common::Language getLanguage() const; Common::Platform getPlatform() const; - uint32 getFlags() const; bool isDemo() const; inline ResourceManager *getResMan() const { return _resMan; } inline Kernel *getKernel() const { return _kernel; } inline EngineState *getEngineState() const { return _gamestate; } inline Vocabulary *getVocabulary() const { return _vocabulary; } + inline EventManager *getEventManager() const { return _eventMan; } + inline reg_t getGameObject() const { return _gameObj; } Common::String getSavegameName(int nr) const; Common::String getSavegamePattern() const; @@ -176,6 +242,12 @@ public: /** Remove the 'TARGET-' prefix of the given filename, if present. */ Common::String unwrapFilename(const Common::String &name) const; + void sleep(uint32 msecs); + + void scriptDebug(); + bool checkExportBreakpoint(uint16 script, uint16 pubfunct); + bool checkSelectorBreakpoint(reg_t send_obj, int selector); + public: /** @@ -189,9 +261,14 @@ public: Common::String strSplit(const char *str, const char *sep = "\r----------\r"); kLanguage getSciLanguage(); + void setSciLanguage(kLanguage lang); + void setSciLanguage(); Common::String getSciLanguageString(const char *str, kLanguage lang, kLanguage *lang2 = NULL) const; + // Initializes ports and paint16 for non-sci32 games, also sets default palette + void initGraphics(); + public: GfxAnimate *_gfxAnimate; // Animate for 16-bit gfx GfxCache *_gfxCache; @@ -203,27 +280,60 @@ public: GfxPalette *_gfxPalette; GfxPaint *_gfxPaint; GfxPaint16 *_gfxPaint16; // Painting in 16-bit gfx + GfxPaint32 *_gfxPaint32; // Painting in 32-bit gfx GfxPorts *_gfxPorts; // Port managment for 16-bit gfx GfxScreen *_gfxScreen; - SciGui *_gui; /* Currently active Gui */ + GfxText16 *_gfxText16; + GfxTransitions *_gfxTransitions; // transitions between screens for 16-bit gfx GfxMacIconBar *_gfxMacIconBar; // Mac Icon Bar manager #ifdef ENABLE_SCI32 - SciGui32 *_gui32; // GUI for SCI32 games GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx #endif AudioPlayer *_audio; + SoundCommandParser *_soundCmd; GameFeatures *_features; + DebugState _debugState; + private: + /** + * Initializes a SCI game + * This function must be run before script_run() is executed. Graphics data + * is initialized iff s->gfx_state != NULL. + * @param[in] s The state to operate on + * @return true on success, false if an error occurred. + */ + bool initGame(); + + /** + * Runs a SCI game + * This is the main function for SCI games. It takes a valid state, loads + * script 0 to it, finds the game object, allocates a stack, and runs the + * init method of the game object. In layman's terms, this runs a SCI game. + * @param[in] s Pointer to the pointer of the state to operate on + */ + void runGame(); + + /** + * Uninitializes an initialized SCI game + * This function should be run after each script_run() call. + * @param[in] s The state to operate on + */ + void exitGame(); + + void initStackBaseWithSelector(Selector selector); + const ADGameDescription *_gameDescription; + const SciGameId _gameId; ResourceManager *_resMan; /**< The resource manager */ EngineState *_gamestate; Kernel *_kernel; Vocabulary *_vocabulary; + EventManager *_eventMan; + reg_t _gameObj; /**< Pointer to the game object */ Console *_console; - OSystem *_system; }; diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index 7ecce3b2a3..d9e9d2e8db 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -24,20 +24,21 @@ */ #include "sci/resource.h" -#include "sci/engine/selector.h" #include "sci/engine/kernel.h" +#include "sci/engine/selector.h" #include "sci/engine/seg_manager.h" #include "sci/sound/audio.h" -#include "common/system.h" #include "common/file.h" +#include "common/system.h" #include "sound/audiostream.h" -#include "sound/decoders/raw.h" -#include "sound/decoders/wave.h" +#include "sound/decoders/aiff.h" #include "sound/decoders/flac.h" #include "sound/decoders/mp3.h" +#include "sound/decoders/raw.h" #include "sound/decoders/vorbis.h" +#include "sound/decoders/wave.h" namespace Sci { @@ -45,6 +46,7 @@ AudioPlayer::AudioPlayer(ResourceManager *resMan) : _resMan(resMan), _audioRate( _syncResource(NULL), _syncOffset(0), _audioCdStart(0) { _mixer = g_system->getMixer(); + _wPlayFlag = false; } AudioPlayer::~AudioPlayer() { @@ -63,11 +65,30 @@ int AudioPlayer::startAudio(uint16 module, uint32 number) { Audio::AudioStream *audioStream = getAudioStream(number, module, &sampleLen); if (audioStream) { + _wPlayFlag = false; _mixer->playStream(Audio::Mixer::kSpeechSoundType, &_audioHandle, audioStream); return sampleLen; + } else { + // Don't throw a warning in this case. getAudioStream() already has. Some games + // do miss audio entries (perhaps because of a typo, or because they were simply + // forgotten). + return 0; } +} - return 0; +int AudioPlayer::wPlayAudio(uint16 module, uint32 tuple) { + // Get the audio sample length and set the wPlay flag so we return 0 on + // position. SSCI pre-loads the audio here, but it's much easier for us to + // just get the sample length and return that. wPlayAudio should *not* + // actually start the sample. + + int sampleLen = 0; + Audio::AudioStream *audioStream = getAudioStream(tuple, module, &sampleLen); + if (!audioStream) + warning("wPlayAudio: unable to create stream for audio tuple %d, module %d", tuple, module); + delete audioStream; + _wPlayFlag = true; + return sampleLen; } void AudioPlayer::stopAudio() { @@ -85,6 +106,8 @@ void AudioPlayer::resumeAudio() { int AudioPlayer::getAudioPosition() { if (_mixer->isSoundHandleActive(_audioHandle)) return _mixer->getSoundElapsedTime(_audioHandle) * 6 / 100; // return elapsed time in ticks + else if (_wPlayFlag) + return 0; // Sound has "loaded" so return that it hasn't started else return -1; // Sound finished } @@ -160,17 +183,28 @@ static void deDPCM8(byte *soundBuf, Common::SeekableReadStream &audioStream, uin // Sierra SOL audio file reader // Check here for more info: http://wiki.multimedia.cx/index.php?title=Sierra_Audio -static bool readSOLHeader(Common::SeekableReadStream *audioStream, int headerSize, uint32 &size, uint16 &audioRate, byte &audioFlags) { - if (headerSize != 11 && headerSize != 12) { +static bool readSOLHeader(Common::SeekableReadStream *audioStream, int headerSize, uint32 &size, uint16 &audioRate, byte &audioFlags, uint32 resSize) { + if (headerSize != 7 && headerSize != 11 && headerSize != 12) { warning("SOL audio header of size %i not supported", headerSize); return false; } - audioStream->readUint32LE(); // skip "SOL" + 0 (4 bytes) + uint32 tag = audioStream->readUint32BE(); + + if (tag != MKID_BE('SOL\0')) { + warning("No 'SOL' FourCC found"); + return false; + } + audioRate = audioStream->readUint16LE(); audioFlags = audioStream->readByte(); - size = audioStream->readUint32LE(); + // For the QFG3 demo format, just use the resource size + // Otherwise, load it from the header + if (headerSize == 7) + size = resSize; + else + size = audioStream->readUint32LE(); return true; } @@ -238,9 +272,11 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 // Compressed audio made by our tool byte *compressedData = (byte *)malloc(audioRes->size); assert(compressedData); - // We copy over the compressed data in our own buffer. If we don't do this resourcemanager may free the data - // later. All other compression-types already decompress completely into an additional buffer here. - // MP3/OGG/FLAC decompression works on-the-fly instead. + // We copy over the compressed data in our own buffer. We have to do + // this, because ResourceManager may free the original data late. All + // other compression types already decompress completely into an + // additional buffer here. MP3/OGG/FLAC decompression works on-the-fly + // instead. memcpy(compressedData, audioRes->data, audioRes->size); Common::MemoryReadStream *compressedStream = new Common::MemoryReadStream(compressedData, audioRes->size, DisposeAfterUse::YES); @@ -270,7 +306,7 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 // SCI1.1 Common::MemoryReadStream headerStream(audioRes->_header, audioRes->_headerSize, DisposeAfterUse::NO); - if (readSOLHeader(&headerStream, audioRes->_headerSize, size, _audioRate, audioFlags)) { + if (readSOLHeader(&headerStream, audioRes->_headerSize, size, _audioRate, audioFlags, audioRes->size)) { Common::MemoryReadStream dataStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); data = readSOLAudio(&dataStream, size, audioFlags, flags); } @@ -286,6 +322,18 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 waveStream->seek(0, SEEK_SET); audioStream = Audio::makeWAVStream(waveStream, DisposeAfterUse::YES); + } else if (audioRes->size > 4 && READ_BE_UINT32(audioRes->data) == MKID_BE('FORM')) { + // AIFF detected + Common::MemoryReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + + // Calculate samplelen from AIFF header + int waveSize = 0, waveRate = 0; + byte waveFlags = 0; + Audio::loadAIFFFromStream(*waveStream, waveSize, waveRate, waveFlags); + *sampleLen = (waveFlags & Audio::FLAG_16BITS ? waveSize >> 1 : waveSize) * 60 / waveRate; + + waveStream->seek(0, SEEK_SET); + audioStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES); } else if (audioRes->size > 14 && READ_BE_UINT16(audioRes->data) == 1 && READ_BE_UINT16(audioRes->data + 2) == 1 && READ_BE_UINT16(audioRes->data + 4) == 5 && READ_BE_UINT32(audioRes->data + 10) == 0x00018051) { // Mac snd detected @@ -317,13 +365,12 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 } if (audioSeekStream) { - *sampleLen = (audioSeekStream->getLength().msecs() * 10000) / 166666; // we translate msecs to ticks - // Original code - //*sampleLen = (flags & Audio::FLAG_16BITS ? size >> 1 : size) * 60 / _audioRate; + *sampleLen = (audioSeekStream->getLength().msecs() * 60) / 1000; // we translate msecs to ticks audioStream = audioSeekStream; } - // We have to make sure that we don't depend on resource manager pointers after this point, because the actual - // audio resource may get unloaded by resource manager at any time + // We have to make sure that we don't depend on resource manager pointers + // after this point, because the actual audio resource may get unloaded by + // resource manager at any time. if (audioStream) return audioStream; diff --git a/engines/sci/sound/audio.h b/engines/sci/sound/audio.h index 9fc3cbac51..7c1221fc4c 100644 --- a/engines/sci/sound/audio.h +++ b/engines/sci/sound/audio.h @@ -33,8 +33,7 @@ namespace Sci { enum AudioCommands { - // TODO: find the difference between kSci1AudioWPlay and kSci1AudioPlay - kSciAudioWPlay = 1, /* Plays an audio stream */ + kSciAudioWPlay = 1, /* Loads an audio stream */ kSciAudioPlay = 2, /* Plays an audio stream */ kSciAudioStop = 3, /* Stops an audio stream */ kSciAudioPause = 4, /* Pauses an audio stream */ @@ -69,6 +68,7 @@ public: Audio::RewindableAudioStream *getAudioStream(uint32 number, uint32 volume, int *sampleLen); int getAudioPosition(); int startAudio(uint16 module, uint32 tuple); + int wPlayAudio(uint16 module, uint32 tuple); void stopAudio(); void pauseAudio(); void resumeAudio(); @@ -92,6 +92,7 @@ private: Resource *_syncResource; /**< Used by kDoSync for speech syncing in CD talkie games */ uint _syncOffset; uint32 _audioCdStart; + bool _wPlayFlag; }; } // End of namespace Sci diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index a743e4b5d9..55c3640c9d 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -704,6 +704,7 @@ void MidiDriver_AdLib::setVelocityReg(int regOffset, int velocity, int kbScaleLe void MidiDriver_AdLib::setPatch(int voice, int patch) { if ((patch < 0) || ((uint)patch >= _patches.size())) { warning("ADLIB: Invalid patch %i requested", patch); + // Substitute instrument 0 patch = 0; } @@ -749,17 +750,24 @@ void MidiDriver_AdLib::playSwitch(bool play) { bool MidiDriver_AdLib::loadResource(const byte *data, uint size) { if ((size != 1344) && (size != 2690) && (size != 5382)) { - warning("ADLIB: Unsupported patch format (%i bytes)", size); + error("ADLIB: Unsupported patch format (%i bytes)", size); return false; } for (int i = 0; i < 48; i++) loadInstrument(data + (28 * i)); - if (size == 2690) { + if (size == 1344) { + byte dummy[28] = {0}; + + // Only 48 instruments, add dummies + for (int i = 0; i < 48; i++) + loadInstrument(dummy); + } else if (size == 2690) { for (int i = 48; i < 96; i++) loadInstrument(data + 2 + (28 * i)); - } else if (size == 5382) { + } else { + // SCI1.1 and later for (int i = 48; i < 190; i++) loadInstrument(data + (28 * i)); _rhythmKeyMap = new byte[kRhythmKeys]; diff --git a/engines/sci/sound/drivers/fb01.cpp b/engines/sci/sound/drivers/fb01.cpp index 7e9fbd51a1..ab9b2e3df5 100644 --- a/engines/sci/sound/drivers/fb01.cpp +++ b/engines/sci/sound/drivers/fb01.cpp @@ -128,8 +128,8 @@ private: }; MidiPlayer_Fb01::MidiPlayer_Fb01(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _timerParam(NULL), _timerProc(NULL) { - MidiDriverType midiType = MidiDriver::detectMusicDriver(MDT_MIDI); - _driver = createMidi(midiType); + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI); + _driver = createMidi(dev); _sysExBuf[0] = 0x43; _sysExBuf[1] = 0x75; diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp index 2432a8fab0..1ef0781906 100644 --- a/engines/sci/sound/drivers/midi.cpp +++ b/engines/sci/sound/drivers/midi.cpp @@ -55,6 +55,7 @@ public: bool hasRhythmChannel() const { return true; } byte getPlayId(); int getPolyphony() const { return kVoices; } + int getFirstChannel(); void setVolume(byte volume); int getVolume(); void setReverb(byte reverb); @@ -119,10 +120,10 @@ private: }; MidiPlayer_Midi::MidiPlayer_Midi(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _isMt32(false), _hasReverb(false), _isOldPatchFormat(true) { - MidiDriverType midiType = MidiDriver::detectMusicDriver(MDT_MIDI); - _driver = createMidi(midiType); + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI); + _driver = createMidi(dev); - if (midiType == MD_MT32 || ConfMan.getBool("native_mt32")) + if (MidiDriver::getMusicType(dev) == MT_MT32 || ConfMan.getBool("native_mt32")) _isMt32 = true; _sysExBuf[0] = 0x41; @@ -271,6 +272,17 @@ void MidiPlayer_Midi::setPatch(int channel, int patch) { _driver->setPitchBendRange(channel, bendRange); _driver->send(0xc0 | channel, _patchMap[patch], 0); + + // Send a pointless command to work around a firmware bug in common + // USB-MIDI cables. If the first MIDI command in a USB packet is a + // Cx or Dx command, the second command in the packet is dropped + // somewhere. + // FIXME: consider putting a workaround in the MIDI backend drivers + // instead. + // Known to be affected: alsa, coremidi + // Known *not* to be affected: windows (only seems to send one MIDI + // command per USB packet even if the device allows larger packets). + _driver->send(0xb0 | channel, 0x0a, _channels[channel].pan); } void MidiPlayer_Midi::send(uint32 b) { @@ -306,6 +318,13 @@ void MidiPlayer_Midi::send(uint32 b) { } } +// We return 1 for mt32, because if we remap channels to 0 for mt32, those won't get played at all +int MidiPlayer_Midi::getFirstChannel() { + if (_isMt32) + return 1; + return 0; +} + void MidiPlayer_Midi::setVolume(byte volume) { _masterVolume = volume; diff --git a/engines/sci/sound/drivers/mididriver.h b/engines/sci/sound/drivers/mididriver.h index 12d3e57f5d..58803db260 100644 --- a/engines/sci/sound/drivers/mididriver.h +++ b/engines/sci/sound/drivers/mididriver.h @@ -86,6 +86,7 @@ public: virtual byte getPlayId() = 0; virtual int getPolyphony() const = 0; + virtual int getFirstChannel() { return 0; }; virtual void setVolume(byte volume) { if(_driver) diff --git a/engines/sci/sound/iterator/core.cpp b/engines/sci/sound/iterator/core.cpp deleted file mode 100644 index 7cd730b3e2..0000000000 --- a/engines/sci/sound/iterator/core.cpp +++ /dev/null @@ -1,1013 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -/* Sound subsystem core: Event handler, sound player dispatching */ - -#include "sci/sci.h" -#ifdef USE_OLD_MUSIC_FUNCTIONS - -#include "sci/sound/iterator/core.h" -#include "sci/sound/iterator/iterator.h" -#include "sci/sound/drivers/mididriver.h" - -#include "common/system.h" -#include "common/timer.h" - -#include "sound/mixer.h" - -namespace Sci { - -/* Plays a song iterator that found a PCM through a PCM device, if possible -** Parameters: (SongIterator *) it: The iterator to play -** (SongHandle) handle: Debug handle -** Returns : (int) 0 if the effect will not be played, nonzero if it will -** This assumes that the last call to 'it->next()' returned SI_PCM. -*/ -static int sfx_play_iterator_pcm(SongIterator *it, SongHandle handle); - - -#pragma mark - - - -class SfxPlayer { -public: - /** Number of voices that can play simultaneously */ - int _polyphony; - -protected: - SciVersion _soundVersion; - MidiPlayer *_mididrv; - - SongIterator *_iterator; - Audio::Timestamp _wakeupTime; - Audio::Timestamp _currentTime; - uint32 _pauseTimeDiff; - - bool _paused; - bool _iteratorIsDone; - uint32 _tempo; - - Common::Mutex _mutex; - int _volume; - - void play_song(SongIterator *it); - static void player_timer_callback(void *refCon); - -public: - SfxPlayer(SciVersion soundVersion); - ~SfxPlayer(); - - /** - * Initializes the player. - * @param resMan a resource manager for driver initialization - * @param expected_latency expected delay in between calls to 'maintenance' (in microseconds) - * @return Common::kNoError on success, Common::kUnknownError on failure - */ - Common::Error init(ResourceManager *resMan, int expected_latency); - - /** - * Adds an iterator to the song player - * @param it The iterator to play - * @param start_time The time to assume as the time the first MIDI command executes at - * @return Common::kNoError on success, Common::kUnknownError on failure - * - * The iterator should not be cloned (to avoid memory leaks) and - * may be modified according to the needs of the player. - * Implementors may use the 'sfx_iterator_combine()' function - * to add iterators onto their already existing iterators. - */ - Common::Error add_iterator(SongIterator *it, uint32 start_time); - - /** - * Stops the currently playing song and deletes the associated iterator. - * @return Common::kNoError on success, Common::kUnknownError on failure - */ - Common::Error stop(); - - /** - * Transmits a song iterator message to the active song. - * @param msg the message to transmit - * @return Common::kNoError on success, Common::kUnknownError on failure - */ - Common::Error iterator_message(const SongIterator::Message &msg); - - /** - * Pauses song playing. - * @return Common::kNoError on success, Common::kUnknownError on failure - */ - Common::Error pause(); - - /** - * Resumes song playing after a pause. - * @return Common::kNoError on success, Common::kUnknownError on failure - */ - Common::Error resume(); - - /** - * Pass a raw MIDI event to the synth. - * @param argc length of buffer holding the midi event - * @param argv the buffer itself - */ - void tell_synth(int buf_nr, byte *buf); - - void setVolume(int vol); - - int getVolume(); -}; - -SfxPlayer::SfxPlayer(SciVersion soundVersion) - : _soundVersion(soundVersion), _wakeupTime(0, SFX_TICKS_PER_SEC), _currentTime(0, 1) { - _polyphony = 0; - - _mididrv = 0; - - _iterator = NULL; - _pauseTimeDiff = 0; - - _paused = false; - _iteratorIsDone = false; - _tempo = 0; - - _volume = 15; -} - -SfxPlayer::~SfxPlayer() { - if (_mididrv) { - _mididrv->close(); - delete _mididrv; - } - delete _iterator; - _iterator = NULL; -} - -void SfxPlayer::play_song(SongIterator *it) { - while (_iterator && _wakeupTime.msecsDiff(_currentTime) <= 0) { - int delay; - byte buf[8]; - int result; - - switch ((delay = songit_next(&(_iterator), - buf, &result, - IT_READER_MASK_ALL - | IT_READER_MAY_FREE - | IT_READER_MAY_CLEAN))) { - - case SI_FINISHED: - delete _iterator; - _iterator = NULL; - _iteratorIsDone = true; - return; - - case SI_IGNORE: - case SI_LOOP: - case SI_RELATIVE_CUE: - case SI_ABSOLUTE_CUE: - break; - - case SI_PCM: - sfx_play_iterator_pcm(_iterator, 0); - break; - - case 0: - static_cast<MidiDriver *>(_mididrv)->send(buf[0], buf[1], buf[2]); - - break; - - default: - _wakeupTime = _wakeupTime.addFrames(delay); - } - } -} - -void SfxPlayer::tell_synth(int buf_nr, byte *buf) { - byte op1 = (buf_nr < 2 ? 0 : buf[1]); - byte op2 = (buf_nr < 3 ? 0 : buf[2]); - - static_cast<MidiDriver *>(_mididrv)->send(buf[0], op1, op2); -} - -void SfxPlayer::player_timer_callback(void *refCon) { - SfxPlayer *thePlayer = (SfxPlayer *)refCon; - assert(refCon); - Common::StackLock lock(thePlayer->_mutex); - - if (thePlayer->_iterator && !thePlayer->_iteratorIsDone && !thePlayer->_paused) { - thePlayer->play_song(thePlayer->_iterator); - } - - thePlayer->_currentTime = thePlayer->_currentTime.addFrames(1); -} - -/* API implementation */ - -Common::Error SfxPlayer::init(ResourceManager *resMan, int expected_latency) { - MidiDriverType musicDriver = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB); - - switch (musicDriver) { - case MD_ADLIB: - // FIXME: There's no Amiga sound option, so we hook it up to AdLib - if (g_sci->getPlatform() == Common::kPlatformAmiga) - _mididrv = MidiPlayer_Amiga_create(_soundVersion); - else - _mididrv = MidiPlayer_AdLib_create(_soundVersion); - break; - case MD_PCJR: - _mididrv = MidiPlayer_PCJr_create(_soundVersion); - break; - case MD_PCSPK: - _mididrv = MidiPlayer_PCSpeaker_create(_soundVersion); - break; - default: - break; - } - - assert(_mididrv); - - _polyphony = _mididrv->getPolyphony(); - - _tempo = _mididrv->getBaseTempo(); - uint32 time = g_system->getMillis(); - _currentTime = Audio::Timestamp(time, 1000000 / _tempo); - _wakeupTime = Audio::Timestamp(time, SFX_TICKS_PER_SEC); - - _mididrv->setTimerCallback(this, player_timer_callback); - _mididrv->open(resMan); - _mididrv->setVolume(_volume); - - return Common::kNoError; -} - -Common::Error SfxPlayer::add_iterator(SongIterator *it, uint32 start_time) { - Common::StackLock lock(_mutex); - SIMSG_SEND(it, SIMSG_SET_PLAYMASK(_mididrv->getPlayId())); - SIMSG_SEND(it, SIMSG_SET_RHYTHM(_mididrv->hasRhythmChannel())); - - if (_iterator == NULL) { - // Resync with clock - _currentTime = Audio::Timestamp(g_system->getMillis(), 1000000 / _tempo); - _wakeupTime = Audio::Timestamp(start_time, SFX_TICKS_PER_SEC); - } - - _iterator = sfx_iterator_combine(_iterator, it); - _iteratorIsDone = false; - - return Common::kNoError; -} - -Common::Error SfxPlayer::stop() { - debug(3, "Player: Stopping song iterator %p", (void *)_iterator); - Common::StackLock lock(_mutex); - delete _iterator; - _iterator = NULL; - for (int i = 0; i < MIDI_CHANNELS; i++) - static_cast<MidiDriver *>(_mididrv)->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0); - - return Common::kNoError; -} - -Common::Error SfxPlayer::iterator_message(const SongIterator::Message &msg) { - Common::StackLock lock(_mutex); - if (!_iterator) { - return Common::kUnknownError; - } - - songit_handle_message(&_iterator, msg); - - return Common::kNoError; -} - -Common::Error SfxPlayer::pause() { - Common::StackLock lock(_mutex); - - _paused = true; - _pauseTimeDiff = _wakeupTime.msecsDiff(_currentTime); - - _mididrv->playSwitch(false); - - return Common::kNoError; -} - -Common::Error SfxPlayer::resume() { - Common::StackLock lock(_mutex); - - _wakeupTime = Audio::Timestamp(_currentTime.msecs() + _pauseTimeDiff, SFX_TICKS_PER_SEC); - _mididrv->playSwitch(true); - _paused = false; - - return Common::kNoError; -} - -void SfxPlayer::setVolume(int vol) { - _mididrv->setVolume(vol); -} - -int SfxPlayer::getVolume() { - return _mididrv->getVolume(); -} - -#pragma mark - - -void SfxState::sfx_reset_player() { - if (_player) - _player->stop(); -} - -void SfxState::sfx_player_tell_synth(int buf_nr, byte *buf) { - if (_player) - _player->tell_synth(buf_nr, buf); -} - -int SfxState::sfx_get_player_polyphony() { - if (_player) - return _player->_polyphony; - else - return 0; -} - -SfxState::SfxState() { - _player = NULL; - _it = NULL; - _flags = 0; - _song = NULL; - _suspended = 0; -} - -SfxState::~SfxState() { -} - - -void SfxState::freezeTime() { - /* Freezes the top song delay time */ - const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); - Song *song = _song; - - while (song) { - song->_delay = song->_wakeupTime.frameDiff(ctime); - if (song->_delay < 0) - song->_delay = 0; - - song = song->_nextPlaying; - } -} - -void SfxState::thawTime() { - /* inverse of freezeTime() */ - const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); - Song *song = _song; - - while (song) { - song->_wakeupTime = ctime.addFrames(song->_delay); - - song = song->_nextPlaying; - } -} - -#if 0 -// Unreferenced - removed -static void _dump_playing_list(SfxState *self, char *msg) { - Song *song = self->_song; - - fprintf(stderr, "[] Song list : [ "); - song = *(self->_songlib.lib); - while (song) { - fprintf(stderr, "%08lx:%d ", song->handle, song->_status); - song = song->_nextPlaying; - } - fprintf(stderr, "]\n"); - - fprintf(stderr, "[] Play list (%s) : [ " , msg); - - while (song) { - fprintf(stderr, "%08lx ", song->handle); - song = song->_nextPlaying; - } - - fprintf(stderr, "]\n"); -} -#endif - -#if 0 -static void _dump_songs(SfxState *self) { - Song *song = self->_song; - - fprintf(stderr, "Cue iterators:\n"); - song = *(self->_songlib.lib); - while (song) { - fprintf(stderr, " **\tHandle %08x (p%d): status %d\n", - song->handle, song->_priority, song->_status); - SIMSG_SEND(song->_it, SIMSG_PRINT(1)); - song = song->_next; - } - - if (self->_player) { - fprintf(stderr, "Audio iterator:\n"); - self->_player->iterator_message(SongIterator::Message(0, SIMSG_PRINT(1))); - } -} -#endif - -bool SfxState::isPlaying(Song *song) { - Song *playing_song = _song; - - /* _dump_playing_list(this, "is-playing");*/ - - while (playing_song) { - if (playing_song == song) - return true; - playing_song = playing_song->_nextPlaying; - } - return false; -} - -void SfxState::setSongStatus(Song *song, int status) { - const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); - - switch (status) { - - case SOUND_STATUS_STOPPED: - // Reset - song->_it->init(); - break; - - case SOUND_STATUS_SUSPENDED: - case SOUND_STATUS_WAITING: - if (song->_status == SOUND_STATUS_PLAYING) { - // Update delay, set wakeup_time - song->_delay += song->_wakeupTime.frameDiff(ctime); - song->_wakeupTime = ctime; - } - if (status == SOUND_STATUS_SUSPENDED) - break; - - /* otherwise... */ - - case SOUND_STATUS_PLAYING: - if (song->_status == SOUND_STATUS_STOPPED) { - // Starting anew - song->_wakeupTime = ctime; - } - - if (isPlaying(song)) - status = SOUND_STATUS_PLAYING; - else - status = SOUND_STATUS_WAITING; - break; - - default: - fprintf(stderr, "%s L%d: Attempt to set invalid song" - " state %d!\n", __FILE__, __LINE__, status); - return; - - } - song->_status = status; -} - -/* Update internal state iff only one song may be played */ -void SfxState::updateSingleSong() { - Song *newsong = _songlib.findFirstActive(); - - if (newsong != _song) { - freezeTime(); /* Store song delay time */ - - if (_player) - _player->stop(); - - if (newsong) { - if (!newsong->_it) - return; /* Restore in progress and not ready for this yet */ - - /* Change song */ - if (newsong->_status == SOUND_STATUS_WAITING) - setSongStatus(newsong, SOUND_STATUS_PLAYING); - - /* Change instrument mappings */ - } else { - /* Turn off sound */ - } - if (_song) { - if (_song->_status == SOUND_STATUS_PLAYING) - setSongStatus(newsong, SOUND_STATUS_WAITING); - } - - Common::String debugMessage = "[SFX] Changing active song:"; - if (!_song) { - debugMessage += " New song:"; - } else { - char tmp[50]; - sprintf(tmp, " pausing %08lx, now playing ", _song->_handle); - debugMessage += tmp; - } - - if (newsong) { - char tmp[20]; - sprintf(tmp, "%08lx\n", newsong->_handle); - debugMessage += tmp; - } else { - debugMessage += " none\n"; - } - - debugC(2, kDebugLevelSound, "%s", debugMessage.c_str()); - - _song = newsong; - thawTime(); /* Recover song delay time */ - - if (newsong && _player) { - SongIterator *clonesong = newsong->_it->clone(newsong->_delay); - - _player->add_iterator(clonesong, newsong->_wakeupTime.msecs()); - } - } -} - - -void SfxState::updateMultiSong() { - Song *oldfirst = _song; - Song *oldseeker; - Song *newsong = _songlib.findFirstActive(); - Song *newseeker; - Song not_playing_anymore; /* Dummy object, referenced by - ** songs which are no longer - ** active. */ - - /* _dump_playing_list(this, "before");*/ - freezeTime(); /* Store song delay time */ - - // WORKAROUND: sometimes, newsong can be NULL (e.g. in SQ4). - // Handle this here, so that we avoid a crash - if (!newsong) { - // Iterators should get freed when there's only one song left playing - if(oldfirst && oldfirst->_status == SOUND_STATUS_STOPPED) { - debugC(2, kDebugLevelSound, "[SFX] Stopping song %lx", oldfirst->_handle); - if (_player && oldfirst->_it) - _player->iterator_message(SongIterator::Message(oldfirst->_it->ID, SIMSG_STOP)); - } - return; - } - - for (newseeker = newsong; newseeker; - newseeker = newseeker->_nextPlaying) { - if (!newseeker || !newseeker->_it) - return; /* Restore in progress and not ready for this yet */ - } - - /* First, put all old songs into the 'stopping' list and - ** mark their 'next-playing' as not_playing_anymore. */ - for (oldseeker = oldfirst; oldseeker; - oldseeker = oldseeker->_nextStopping) { - oldseeker->_nextStopping = oldseeker->_nextPlaying; - oldseeker->_nextPlaying = ¬_playing_anymore; - - if (oldseeker == oldseeker->_nextPlaying) { - error("updateMultiSong() failed. Breakpoint in %s, line %d", __FILE__, __LINE__); - } - } - - /* Second, re-generate the new song queue. */ - for (newseeker = newsong; newseeker; newseeker = newseeker->_nextPlaying) { - newseeker->_nextPlaying = _songlib.findNextActive(newseeker); - - if (newseeker == newseeker->_nextPlaying) { - error("updateMultiSong() failed. Breakpoint in %s, line %d", __FILE__, __LINE__); - } - } - /* We now need to update the currently playing song list, because we're - ** going to use some functions that require this list to be in a sane - ** state (particularly isPlaying(), indirectly */ - _song = newsong; - - /* Third, stop all old songs */ - for (oldseeker = oldfirst; oldseeker; - oldseeker = oldseeker->_nextStopping) - if (oldseeker->_nextPlaying == ¬_playing_anymore) { - setSongStatus(oldseeker, SOUND_STATUS_SUSPENDED); - debugC(2, kDebugLevelSound, "[SFX] Stopping song %lx", oldseeker->_handle); - - if (_player && oldseeker->_it) - _player->iterator_message(SongIterator::Message(oldseeker->_it->ID, SIMSG_STOP)); - oldseeker->_nextPlaying = NULL; /* Clear this pointer; we don't need the tag anymore */ - } - - for (newseeker = newsong; newseeker; newseeker = newseeker->_nextPlaying) { - if (newseeker->_status != SOUND_STATUS_PLAYING && _player) { - debugC(2, kDebugLevelSound, "[SFX] Adding song %lx", newseeker->_it->ID); - - SongIterator *clonesong = newseeker->_it->clone(newseeker->_delay); - _player->add_iterator(clonesong, g_system->getMillis()); - } - setSongStatus(newseeker, SOUND_STATUS_PLAYING); - } - - _song = newsong; - thawTime(); - /* _dump_playing_list(this, "after");*/ -} - -/* Update internal state */ -void SfxState::update() { - if (_flags & SFX_STATE_FLAG_MULTIPLAY) - updateMultiSong(); - else - updateSingleSong(); -} - -static int sfx_play_iterator_pcm(SongIterator *it, SongHandle handle) { -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Playing PCM: %08lx\n", handle); -#endif - if (g_system->getMixer()->isReady()) { - Audio::AudioStream *newfeed = it->getAudioStream(); - if (newfeed) { - g_system->getMixer()->playStream(Audio::Mixer::kSFXSoundType, 0, newfeed); - return 1; - } - } - return 0; -} - -#define DELAY (1000000 / SFX_TICKS_PER_SEC) - -void SfxState::sfx_init(ResourceManager *resMan, int flags, SciVersion soundVersion) { - _songlib._lib = 0; - _song = NULL; - _flags = flags; - - _player = NULL; - - if (flags & SFX_STATE_FLAG_NOSOUND) { - warning("[SFX] Sound disabled"); - return; - } - -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Initialising: flags=%x\n", flags); -#endif - - /*-------------------*/ - /* Initialise player */ - /*-------------------*/ - - if (!resMan) { - warning("[SFX] Warning: No resource manager present, cannot initialise player"); - return; - } - - _player = new SfxPlayer(soundVersion); - - if (!_player) { - warning("[SFX] No song player found"); - return; - } - - if (_player->init(resMan, DELAY / 1000)) { - warning("[SFX] Song player reported error, disabled"); - delete _player; - _player = NULL; - } - - _resMan = resMan; -} - -void SfxState::sfx_exit() { -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Uninitialising\n"); -#endif - - delete _player; - _player = 0; - - g_system->getMixer()->stopAll(); - - _songlib.freeSounds(); -} - -void SfxState::sfx_suspend(bool suspend) { -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Suspending? = %d\n", suspend); -#endif - if (suspend && (!_suspended)) { - /* suspend */ - - freezeTime(); - if (_player) - _player->pause(); - /* Suspend song player */ - - } else if (!suspend && (_suspended)) { - /* unsuspend */ - - thawTime(); - if (_player) - _player->resume(); - - /* Unsuspend song player */ - } - - _suspended = suspend; -} - -int SfxState::sfx_poll(SongHandle *handle, int *cue) { - if (!_song) - return 0; /* No milk today */ - - *handle = _song->_handle; - -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Polling any (%08lx)\n", *handle); -#endif - return sfx_poll_specific(*handle, cue); -} - -int SfxState::sfx_poll_specific(SongHandle handle, int *cue) { - const Audio::Timestamp ctime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); - Song *song = _song; - - while (song && song->_handle != handle) - song = song->_nextPlaying; - - if (!song) - return 0; /* Song not playing */ - - debugC(2, kDebugLevelSound, "[SFX:CUE] Polled song %08lx ", handle); - - while (1) { - if (song->_wakeupTime.frameDiff(ctime) > 0) - return 0; /* Patience, young hacker! */ - - byte buf[8]; - int result = songit_next(&(song->_it), buf, cue, IT_READER_MASK_ALL); - - switch (result) { - - case SI_FINISHED: - setSongStatus(song, SOUND_STATUS_STOPPED); - update(); - /* ...fall through... */ - case SI_LOOP: - case SI_RELATIVE_CUE: - case SI_ABSOLUTE_CUE: - if (result == SI_FINISHED) - debugC(2, kDebugLevelSound, " => finished"); - else { - if (result == SI_LOOP) - debugC(2, kDebugLevelSound, " => Loop: %d (0x%x)", *cue, *cue); - else - debugC(2, kDebugLevelSound, " => Cue: %d (0x%x)", *cue, *cue); - - } - return result; - - default: - if (result > 0) - song->_wakeupTime = song->_wakeupTime.addFrames(result); - - /* Delay */ - break; - } - } - -} - - -/*****************/ -/* Song basics */ -/*****************/ - -void SfxState::sfx_add_song(SongIterator *it, int priority, SongHandle handle, int number) { - Song *song = _songlib.findSong(handle); - -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Adding song: %08lx at %d, it=%p\n", handle, priority, it); -#endif - if (!it) { - error("[SFX] Attempt to add empty song with handle %08lx", handle); - return; - } - - it->init(); - - /* If we're already playing this, stop it */ - /* Tell player to shut up */ -// _dump_songs(this); - - if (_player) - _player->iterator_message(SongIterator::Message(handle, SIMSG_STOP)); - - if (song) { - setSongStatus( song, SOUND_STATUS_STOPPED); - - fprintf(stderr, "Overwriting old song (%08lx) ...\n", handle); - if (song->_status == SOUND_STATUS_PLAYING || song->_status == SOUND_STATUS_SUSPENDED) { - delete it; - error("Unexpected (error): Song %ld still playing/suspended (%d)", - handle, song->_status); - return; - } else { - _songlib.removeSong(handle); /* No duplicates */ - } - - } - - song = new Song(handle, it, priority); - song->_resourceNum = number; - song->_hold = 0; - song->_loops = 0; - song->_wakeupTime = Audio::Timestamp(g_system->getMillis(), SFX_TICKS_PER_SEC); - _songlib.addSong(song); - _song = NULL; /* As above */ - update(); - - return; -} - -void SfxState::sfx_remove_song(SongHandle handle) { -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Removing song: %08lx\n", handle); -#endif - if (_song && _song->_handle == handle) - _song = NULL; - - _songlib.removeSong(handle); - update(); -} - - - -/**********************/ -/* Song modifications */ -/**********************/ - -#define ASSERT_SONG(s) if (!(s)) { warning("Looking up song handle %08lx failed in %s, L%d", handle, __FILE__, __LINE__); return; } - -void SfxState::sfx_song_set_status(SongHandle handle, int status) { - Song *song = _songlib.findSong(handle); - ASSERT_SONG(song); -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Setting song status to %d" - " (0:stop, 1:play, 2:susp, 3:wait): %08lx\n", status, handle); -#endif - - setSongStatus(song, status); - - update(); -} - -void SfxState::sfx_song_set_fade(SongHandle handle, fade_params_t *params) { -#ifdef DEBUG_SONG_API - static const char *stopmsg[] = {"??? Should not happen", "Do not stop afterwards", "Stop afterwards"}; -#endif - Song *song = _songlib.findSong(handle); - - ASSERT_SONG(song); - -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Setting fade params of %08lx to " - "final volume %d in steps of %d per %d ticks. %s.", - handle, fade->final_volume, fade->step_size, fade->ticks_per_step, - stopmsg[fade->action]); -#endif - - SIMSG_SEND_FADE(song->_it, params); - - update(); -} - -void SfxState::sfx_song_renice(SongHandle handle, int priority) { - Song *song = _songlib.findSong(handle); - ASSERT_SONG(song); -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Renicing song %08lx to %d\n", - handle, priority); -#endif - - song->_priority = priority; - - update(); -} - -void SfxState::sfx_song_set_loops(SongHandle handle, int loops) { - Song *song = _songlib.findSong(handle); - SongIterator::Message msg = SongIterator::Message(handle, SIMSG_SET_LOOPS(loops)); - ASSERT_SONG(song); - - song->_loops = loops; -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Setting loops on %08lx to %d\n", - handle, loops); -#endif - songit_handle_message(&(song->_it), msg); - - if (_player/* && _player->send_iterator_message*/) - /* FIXME: The above should be optional! */ - _player->iterator_message(msg); -} - -void SfxState::sfx_song_set_hold(SongHandle handle, int hold) { - Song *song = _songlib.findSong(handle); - SongIterator::Message msg = SongIterator::Message(handle, SIMSG_SET_HOLD(hold)); - ASSERT_SONG(song); - - song->_hold = hold; -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] Setting hold on %08lx to %d\n", - handle, hold); -#endif - songit_handle_message(&(song->_it), msg); - - if (_player/* && _player->send_iterator_message*/) - /* FIXME: The above should be optional! */ - _player->iterator_message(msg); -} - -/* Different from the one in iterator.c */ -static const int MIDI_cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0, - 3, 3, 0, 3, 2, 0, 3, 0 - }; - -static const SongHandle midi_send_base = 0xffff0000; - -Common::Error SfxState::sfx_send_midi(SongHandle handle, int channel, - int command, int arg1, int arg2) { - byte buffer[5]; - - /* Yes, in that order. SCI channel mutes are actually done via - a counting semaphore. 0 means to decrement the counter, 1 - to increment it. */ - static const char *channel_state[] = {"ON", "OFF"}; - - if (command == 0xb0 && - arg1 == SCI_MIDI_CHANNEL_MUTE) { - warning("TODO: channel mute (channel %d %s)", channel, channel_state[arg2]); - /* We need to have a GET_PLAYMASK interface to use - here. SET_PLAYMASK we've got. - */ - return Common::kNoError; - } - - buffer[0] = channel | command; /* No channel remapping yet */ - - switch (command) { - case 0x80 : - case 0x90 : - case 0xb0 : - buffer[1] = arg1 & 0xff; - buffer[2] = arg2 & 0xff; - break; - case 0xc0 : - buffer[1] = arg1 & 0xff; - break; - case 0xe0 : - buffer[1] = (arg1 & 0x7f) | 0x80; - buffer[2] = (arg1 & 0xff00) >> 7; - break; - default: - warning("Unexpected explicit MIDI command %02x", command); - return Common::kUnknownError; - } - - if (_player) - _player->tell_synth(MIDI_cmdlen[command >> 4], buffer); - return Common::kNoError; -} - -int SfxState::sfx_getVolume() { - return _player->getVolume(); -} - -void SfxState::sfx_setVolume(int volume) { - _player->setVolume(volume); -} - -void SfxState::sfx_all_stop() { -#ifdef DEBUG_SONG_API - fprintf(stderr, "[sfx-core] All stop\n"); -#endif - - _songlib.freeSounds(); - update(); -} - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS diff --git a/engines/sci/sound/iterator/core.h b/engines/sci/sound/iterator/core.h deleted file mode 100644 index a44fe2ecae..0000000000 --- a/engines/sci/sound/iterator/core.h +++ /dev/null @@ -1,209 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -/* Sound engine */ -#ifndef SCI_SFX_CORE_H -#define SCI_SFX_CORE_H - -#include "common/error.h" - -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS - -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/songlib.h" -#include "sci/resource.h" - -namespace Sci { - -class SfxPlayer; -class SongIterator; -struct fade_params_t; - -#define SFX_TICKS_PER_SEC 60 /* MIDI ticks per second */ - - -#define SFX_STATE_FLAG_MULTIPLAY (1 << 0) /* More than one song playable -** simultaneously ? */ -#define SFX_STATE_FLAG_NOSOUND (1 << 1) /* Completely disable sound playing */ - -class SfxState { -private: - SfxPlayer *_player; - -public: // FIXME, make private - SongIterator *_it; /**< The song iterator at the heart of things */ - uint _flags; /**< SFX_STATE_FLAG_* */ - SongLibrary _songlib; /**< Song library */ - Song *_song; /**< Active song, or start of active song chain */ - bool _suspended; /**< Whether we are suspended */ - ResourceManager *_resMan; - -public: - SfxState(); - ~SfxState(); - - /***********/ - /* General */ - /***********/ - - /* Initializes the sound engine - ** Parameters: (ResourceManager *) resMan: Resource manager for initialization - ** (int) flags: SFX_STATE_FLAG_* - */ - void sfx_init(ResourceManager *resMan, int flags, SciVersion soundVersion); - - /** Deinitializes the sound subsystem. */ - void sfx_exit(); - - /* Suspends/unsuspends the sound sybsystem - ** Parameters: (int) suspend: Whether to suspend (non-null) or to unsuspend - */ - void sfx_suspend(bool suspend); - - /* Polls the sound server for cues etc. - ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise - ** (SongHandle) *handle: The affected handle - ** (int) *cue: The sound cue number (if SI_CUE), or the loop number (if SI_LOOP) - */ - int sfx_poll(SongHandle *handle, int *cue); - - /* Polls the sound server for cues etc. - ** Parameters: (SongHandle) handle: The handle to poll - ** Returns : (int) 0 if the cue queue is empty, SI_LOOP, SI_CUE, or SI_FINISHED otherwise - ** (int) *cue: The sound cue number (if SI_CUE), or the loop number (if SI_LOOP) - */ - int sfx_poll_specific(SongHandle handle, int *cue); - - /* Determines the current global volume settings - ** Returns : (int) The global volume, between 0 (silent) and 127 (max. volume) - */ - int sfx_getVolume(); - - /* Determines the current global volume settings - ** Parameters: (int) volume: The new global volume, between 0 and 127 (see above) - */ - void sfx_setVolume(int volume); - - /* Stops all songs currently playing, purges song library - */ - void sfx_all_stop(); - - - /*****************/ - /* Song basics */ - /*****************/ - - /* Adds a song to the internal sound library - ** Parameters: (SongIterator *) it: The iterator describing the song - ** (int) priority: Initial song priority (higher <-> more important) - ** (SongHandle) handle: The handle to associate with the song - */ - void sfx_add_song(SongIterator *it, int priority, SongHandle handle, int resnum); - - - /* Deletes a song and its associated song iterator from the song queue - ** Parameters: (SongHandle) handle: The song to remove - */ - void sfx_remove_song(SongHandle handle); - - - /**********************/ - /* Song modifications */ - /**********************/ - - - /* Sets the song status, i.e. whether it is playing, suspended, or stopped. - ** Parameters: (SongHandle) handle: Handle of the song to modify - ** (int) status: The song status the song should assume - ** WAITING and PLAYING are set implicitly and essentially describe the same state - ** as far as this function is concerned. - */ - void sfx_song_set_status(SongHandle handle, int status); - - /* Sets the new song priority - ** Parameters: (SongHandle) handle: The handle to modify - ** (int) priority: The priority to set - */ - void sfx_song_renice(SongHandle handle, int priority); - - /* Sets the number of loops for the specified song - ** Parameters: (SongHandle) handle: The song handle to reference - ** (int) loops: Number of loops to set - */ - void sfx_song_set_loops(SongHandle handle, int loops); - - /* Sets the number of loops for the specified song - ** Parameters: (SongHandle) handle: The song handle to reference - ** (int) hold: Number of loops to setn - */ - void sfx_song_set_hold(SongHandle handle, int hold); - - /* Instructs a song to be faded out - ** Parameters: (SongHandle) handle: The song handle to reference - ** (fade_params_t *) fade_setup: The precise fade-out configuration to use - */ - void sfx_song_set_fade(SongHandle handle, fade_params_t *fade_setup); - - - // Previously undocumented: - Common::Error sfx_send_midi(SongHandle handle, int channel, - int command, int arg1, int arg2); - - // misc - - /** - * Determines the polyphony of the player in use. - * @return Number of voices the active player can emit - */ - int sfx_get_player_polyphony(); - - /** - * Tells the player to stop its internal iterator. - */ - void sfx_reset_player(); - - /** - * Pass a raw MIDI event to the synth of the player. - * @param argc Length of buffer holding the midi event - * @param argv The buffer itself - */ - void sfx_player_tell_synth(int buf_nr, byte *buf); - -protected: - void freezeTime(); - void thawTime(); - - bool isPlaying(Song *song); - void setSongStatus(Song *song, int status); - void updateSingleSong(); - void updateMultiSong(); - void update(); -}; - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS - -#endif // SCI_SFX_CORE_H diff --git a/engines/sci/sound/iterator/iterator.cpp b/engines/sci/sound/iterator/iterator.cpp deleted file mode 100644 index 5d9d63e5af..0000000000 --- a/engines/sci/sound/iterator/iterator.cpp +++ /dev/null @@ -1,1686 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -/* Song iterators */ - -#include "common/util.h" - -#include "sci/sci.h" -#ifdef USE_OLD_MUSIC_FUNCTIONS - -#include "sci/sound/iterator/iterator_internal.h" -#include "sci/engine/state.h" // for sfx_player_tell_synth :/ -#include "sci/sound/iterator/core.h" // for sfx_player_tell_synth - -#include "sound/audiostream.h" -#include "sound/mixer.h" -#include "sound/decoders/raw.h" - -namespace Sci { - - -static const int MIDI_cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0, - 2, 2, 2, 2, 1, 1, 2, 0 - }; - -/*#define DEBUG_DECODING*/ -/*#define DEBUG_VERBOSE*/ - -/** Find first set bit in bits and return its index. Returns 0 if bits is 0. */ -static int sci_ffs(int bits) { - if (!bits) - return 0; - - int retval = 1; - - while (!(bits & 1)) { - retval++; - bits >>= 1; - } - - return retval; -} - -static void print_tabs_id(int nr, songit_id_t id) { - while (nr-- > 0) - fprintf(stderr, "\t"); - - fprintf(stderr, "[%08lx] ", id); -} - -BaseSongIterator::BaseSongIterator(byte *data, uint size, songit_id_t id) - : _data(data, size) { - ID = id; -} - -/************************************/ -/*-- SCI0 iterator implementation --*/ -/************************************/ - -#define SCI0_MIDI_OFFSET 33 -#define SCI0_END_OF_SONG 0xfc /* proprietary MIDI command */ - -#define SCI0_PCM_SAMPLE_RATE_OFFSET 0x0e -#define SCI0_PCM_SIZE_OFFSET 0x20 -#define SCI0_PCM_DATA_OFFSET 0x2c - -#define CHECK_FOR_END_ABSOLUTE(offset) \ - if (offset > _data.size()) { \ - warning("Reached end of song without terminator (%x/%x) at %d", offset, _data.size(), __LINE__); \ - return SI_FINISHED; \ - } - -#define CHECK_FOR_END(offset_augment) \ - if ((channel->offset + (offset_augment)) > channel->end) { \ - channel->state = SI_STATE_FINISHED; \ - warning("Reached end of track %d without terminator (%x+%x/%x) at %d", channel->id, channel->offset, offset_augment, channel->end, __LINE__); \ - return SI_FINISHED; \ - } - - -static int _parse_ticks(byte *data, int *offset_p, int size) { - int ticks = 0; - int tempticks; - int offset = 0; - - do { - tempticks = data[offset++]; - ticks += (tempticks == SCI_MIDI_TIME_EXPANSION_PREFIX) ? - SCI_MIDI_TIME_EXPANSION_LENGTH : tempticks; - } while (tempticks == SCI_MIDI_TIME_EXPANSION_PREFIX - && offset < size); - - if (offset_p) - *offset_p = offset; - - return ticks; -} - - -static int _sci0_get_pcm_data(Sci0SongIterator *self, int *rate, int *xoffset, uint *xsize); - - -#define PARSE_FLAG_LOOPS_UNLIMITED (1 << 0) /* Unlimited # of loops? */ -#define PARSE_FLAG_PARAMETRIC_CUE (1 << 1) /* Assume that cues take an additional "cue value" argument */ -/* This implements a difference between SCI0 and SCI1 cues. */ - -void SongIteratorChannel::init(int id_, int offset_, int end_) { - playmask = PLAYMASK_NONE; /* Disable all channels */ - id = id_; - state = SI_STATE_DELTA_TIME; - loop_timepos = 0; - total_timepos = 0; - timepos_increment = 0; - delay = 0; /* Only used for more than one channel */ - last_cmd = 0xfe; - - offset = loop_offset = initial_offset = offset_; - end = end_; -} - -void SongIteratorChannel::resetSynthChannels() { - byte buf[5]; - - // FIXME: Evil hack - SfxState &sound = g_sci->getEngineState()->_sound; - - for (int i = 0; i < MIDI_CHANNELS; i++) { - if (playmask & (1 << i)) { - buf[0] = 0xe0 | i; /* Pitch bend */ - buf[1] = 0x80; /* Wheel center */ - buf[2] = 0x40; - sound.sfx_player_tell_synth(3, buf); - - buf[0] = 0xb0 | i; // Set control - buf[1] = 0x40; // Hold pedal - buf[2] = 0x00; // Off - sound.sfx_player_tell_synth(3, buf); - /* TODO: Reset other controls? */ - } - } -} - -int BaseSongIterator::parseMidiCommand(byte *buf, int *result, SongIteratorChannel *channel, int flags) { - byte cmd; - int paramsleft; - int midi_op; - int midi_channel; - - channel->state = SI_STATE_DELTA_TIME; - - cmd = _data[channel->offset++]; - - if (!(cmd & 0x80)) { - /* 'Running status' mode */ - channel->offset--; - cmd = channel->last_cmd; - } - - if (cmd == 0xfe) { - warning("song iterator subsystem: Corrupted sound resource detected."); - return SI_FINISHED; - } - - midi_op = cmd >> 4; - midi_channel = cmd & 0xf; - paramsleft = MIDI_cmdlen[midi_op]; - -#if 0 - if (1) { - fprintf(stderr, "[IT]: off=%x, cmd=%02x, takes %d args ", - channel->offset - 1, cmd, paramsleft); - fprintf(stderr, "[%02x %02x <%02x> %02x %02x %02x]\n", - _data[channel->offset-3], - _data[channel->offset-2], - _data[channel->offset-1], - _data[channel->offset], - _data[channel->offset+1], - _data[channel->offset+2]); - } -#endif - - buf[0] = cmd; - - - CHECK_FOR_END(paramsleft); - memcpy(buf + 1, _data.begin() + channel->offset, paramsleft); - *result = 1 + paramsleft; - - channel->offset += paramsleft; - - channel->last_cmd = cmd; - - /* Are we supposed to play this channel? */ - if ( - /* First, exclude "global" properties-- such as cues-- from consideration */ - (midi_op < 0xf - && !(cmd == SCI_MIDI_SET_SIGNAL) - && !(SCI_MIDI_CONTROLLER(cmd) - && buf[1] == SCI_MIDI_CUMULATIVE_CUE)) - - /* Next, check if the channel is allowed */ - && (!((1 << midi_channel) & channel->playmask))) - return /* Execute next command */ - nextCommand(buf, result); - - - if (cmd == SCI_MIDI_EOT) { - /* End of track? */ - channel->resetSynthChannels(); - if (_loops > 1) { - /* If allowed, decrement the number of loops */ - if (!(flags & PARSE_FLAG_LOOPS_UNLIMITED)) - *result = --_loops; - -#ifdef DEBUG_DECODING - fprintf(stderr, "%s L%d: (%p):%d Looping ", __FILE__, __LINE__, this, channel->id); - if (flags & PARSE_FLAG_LOOPS_UNLIMITED) - fprintf(stderr, "(indef.)"); - else - fprintf(stderr, "(%d)", _loops); - fprintf(stderr, " %x -> %x\n", - channel->offset, channel->loop_offset); -#endif - channel->offset = channel->loop_offset; - channel->state = SI_STATE_DELTA_TIME; - channel->total_timepos = channel->loop_timepos; - channel->last_cmd = 0xfe; - debugC(2, kDebugLevelSound, "Looping song iterator %08lx.", ID); - return SI_LOOP; - } else { - channel->state = SI_STATE_FINISHED; - return SI_FINISHED; - } - - } else if (cmd == SCI_MIDI_SET_SIGNAL) { - if (buf[1] == SCI_MIDI_SET_SIGNAL_LOOP) { - channel->loop_offset = channel->offset; - channel->loop_timepos = channel->total_timepos; - - return /* Execute next command */ - nextCommand(buf, result); - } else { - /* Used to be conditional <= 127 */ - *result = buf[1]; /* Absolute cue */ - return SI_ABSOLUTE_CUE; - } - } else if (SCI_MIDI_CONTROLLER(cmd)) { - switch (buf[1]) { - - case SCI_MIDI_CUMULATIVE_CUE: - if (flags & PARSE_FLAG_PARAMETRIC_CUE) - _ccc += buf[2]; - else { /* No parameter to CC */ - _ccc++; - /* channel->offset--; */ - } - *result = _ccc; - return SI_RELATIVE_CUE; - - case SCI_MIDI_RESET_ON_SUSPEND: - _resetflag = buf[2]; - break; - - case SCI_MIDI_SET_POLYPHONY: - _polyphony[midi_channel] = buf[2]; - -#if 0 - { - Sci1SongIterator *self1 = (Sci1SongIterator *)this; - int i; - int voices = 0; - for (i = 0; i < self1->_numChannels; i++) { - voices += _polyphony[i]; - } - - printf("SET_POLYPHONY(%d, %d) for a total of %d voices\n", midi_channel, buf[2], voices); - printf("[iterator] DEBUG: Polyphony = [ "); - for (i = 0; i < self1->_numChannels; i++) - printf("%d ", _polyphony[i]); - printf("]\n"); - printf("[iterator] DEBUG: Importance = [ "); - printf("]\n"); - } -#endif - break; - - case SCI_MIDI_SET_REVERB: - break; - - case SCI_MIDI_CHANNEL_MUTE: - warning("CHANNEL_MUTE(%d, %d)", midi_channel, buf[2]); - break; - - case SCI_MIDI_HOLD: { - // Safe cast: This controller is only used in SCI1 - Sci1SongIterator *self1 = (Sci1SongIterator *)this; - - if (buf[2] == self1->_hold) { - channel->offset = channel->initial_offset; - channel->state = SI_STATE_COMMAND; - channel->total_timepos = 0; - - self1->_numLoopedChannels = self1->_numActiveChannels - 1; - - // FIXME: - // This implementation of hold breaks getting out of the - // limo when visiting the airport near the start of LSL5. - // It seems like all channels should be reset here somehow, - // but not sure how. - // Forcing all channel offsets to 0 seems to fix the hang, - // but somehow slows the exit sequence down to take 20 seconds - // instead of about 3. - - return SI_LOOP; - } - - break; - } - case 0x04: /* UNKNOWN NYI (happens in LSL2 gameshow) */ - case 0x46: /* UNKNOWN NYI (happens in LSL3 binoculars) */ - case 0x61: /* UNKNOWN NYI (special for AdLib? Iceman) */ - case 0x73: /* UNKNOWN NYI (happens in Hoyle) */ - case 0xd1: /* UNKNOWN NYI (happens in KQ4 when riding the unicorn) */ - return /* Execute next command */ - nextCommand(buf, result); - - case 0x01: /* modulation */ - case 0x07: /* volume */ - case 0x0a: /* panpot */ - case 0x0b: /* expression */ - case 0x40: /* hold */ - case 0x79: /* reset all */ - /* No special treatment neccessary */ - break; - - } - return 0; - - } else { -#if 0 - /* Perform remapping, if neccessary */ - if (cmd != SCI_MIDI_SET_SIGNAL - && cmd < 0xf0) { /* Not a generic command */ - int chan = cmd & 0xf; - int op = cmd & 0xf0; - - chan = channel_remap[chan]; - buf[0] = chan | op; - } -#endif - - /* Process as normal MIDI operation */ - return 0; - } -} - -int BaseSongIterator::processMidi(byte *buf, int *result, - SongIteratorChannel *channel, int flags) { - CHECK_FOR_END(0); - - switch (channel->state) { - - case SI_STATE_PCM: { - if (_data[channel->offset] == 0 - && _data[channel->offset + 1] == SCI_MIDI_EOT) - /* Fake one extra tick to trick the interpreter into not killing the song iterator right away */ - channel->state = SI_STATE_PCM_MAGIC_DELTA; - else - channel->state = SI_STATE_DELTA_TIME; - return SI_PCM; - } - - case SI_STATE_PCM_MAGIC_DELTA: { - int rate; - int offset; - uint size; - int delay; - if (_sci0_get_pcm_data((Sci0SongIterator *)this, &rate, &offset, &size)) - return SI_FINISHED; /* 'tis broken */ - channel->state = SI_STATE_FINISHED; - delay = (size * 50 + rate - 1) / rate; /* number of ticks to completion*/ - - debugC(2, kDebugLevelSound, "delaying %d ticks", delay); - return delay; - } - - case SI_STATE_UNINITIALISED: - warning("Attempt to read command from uninitialized iterator"); - init(); - return nextCommand(buf, result); - - case SI_STATE_FINISHED: - return SI_FINISHED; - - case SI_STATE_DELTA_TIME: { - int offset; - int ticks = _parse_ticks(_data.begin() + channel->offset, - &offset, - _data.size() - channel->offset); - - channel->offset += offset; - channel->delay += ticks; - channel->timepos_increment = ticks; - - CHECK_FOR_END(0); - - channel->state = SI_STATE_COMMAND; - - if (ticks) - return ticks; - } - - /* continute otherwise... */ - - case SI_STATE_COMMAND: { - int retval; - channel->total_timepos += channel->timepos_increment; - channel->timepos_increment = 0; - - retval = parseMidiCommand(buf, result, channel, flags); - - if (retval == SI_FINISHED) { - if (_numActiveChannels) - --(_numActiveChannels); -#ifdef DEBUG_DECODING - fprintf(stderr, "%s L%d: (%p):%d Finished channel, %d channels left\n", - __FILE__, __LINE__, this, channel->id, - _numActiveChannels); -#endif - /* If we still have channels left... */ - if (_numActiveChannels) { - return nextCommand(buf, result); - } - - /* Otherwise, we have reached the end */ - _loops = 0; - } - - return retval; - } - - default: - error("Invalid iterator state %d", channel->state); - return SI_FINISHED; - } -} - -int Sci0SongIterator::nextCommand(byte *buf, int *result) { - return processMidi(buf, result, &_channel, PARSE_FLAG_PARAMETRIC_CUE); -} - -static int _sci0_header_magic_p(byte *data, int offset, int size) { - if (offset + 0x10 > size) - return 0; - return (data[offset] == 0x1a) - && (data[offset + 1] == 0x00) - && (data[offset + 2] == 0x01) - && (data[offset + 3] == 0x00); -} - - -static int _sci0_get_pcm_data(Sci0SongIterator *self, - int *rate, int *xoffset, uint *xsize) { - int tries = 2; - bool found_it = false; - byte *pcm_data; - int size; - uint offset = SCI0_MIDI_OFFSET; - - if (self->_data[0] != 2) - return 1; - /* No such luck */ - - while ((tries--) && (offset < self->_data.size()) && (!found_it)) { - // Search through the garbage manually - // FIXME: Replace offset by an iterator - Common::Array<byte>::iterator iter = Common::find(self->_data.begin() + offset, self->_data.end(), SCI0_END_OF_SONG); - - if (iter == self->_data.end()) { - warning("Playing unterminated song"); - return 1; - } - - // add one to move it past the END_OF_SONG marker - iter++; - offset = iter - self->_data.begin(); // FIXME - - - if (_sci0_header_magic_p(self->_data.begin(), offset, self->_data.size())) - found_it = true; - } - - if (!found_it) { - warning("Song indicates presence of PCM, but" - " none found (finally at offset %04x)", offset); - - return 1; - } - - pcm_data = self->_data.begin() + offset; - - size = READ_LE_UINT16(pcm_data + SCI0_PCM_SIZE_OFFSET); - - /* Two of the format parameters are fixed by design: */ - *rate = READ_LE_UINT16(pcm_data + SCI0_PCM_SAMPLE_RATE_OFFSET); - - if (offset + SCI0_PCM_DATA_OFFSET + size != self->_data.size()) { - int d = offset + SCI0_PCM_DATA_OFFSET + size - self->_data.size(); - - warning("PCM advertizes %d bytes of data, but %d" - " bytes are trailing in the resource", - size, self->_data.size() - (offset + SCI0_PCM_DATA_OFFSET)); - - if (d > 0) - size -= d; /* Fix this */ - } - - *xoffset = offset; - *xsize = size; - - return 0; -} - -static Audio::AudioStream *makeStream(byte *data, int size, int rate) { - debugC(2, kDebugLevelSound, "Playing PCM data of size %d, rate %d", size, rate); - - // Duplicate the data - byte *sound = (byte *)malloc(size); - memcpy(sound, data, size); - - // Convert stream format flags - int flags = Audio::FLAG_UNSIGNED; - return Audio::makeRawStream(sound, size, rate, flags); -} - -Audio::AudioStream *Sci0SongIterator::getAudioStream() { - int rate; - int offset; - uint size; - if (_sci0_get_pcm_data(this, &rate, &offset, &size)) - return NULL; - - _channel.state = SI_STATE_FINISHED; /* Don't play both PCM and music */ - - return makeStream(_data.begin() + offset + SCI0_PCM_DATA_OFFSET, size, rate); -} - -SongIterator *Sci0SongIterator::handleMessage(Message msg) { - if (msg._class == _SIMSG_BASE) { - switch (msg._type) { - - case _SIMSG_BASEMSG_PRINT: - print_tabs_id(msg._arg.i, ID); - debugC(2, kDebugLevelSound, "SCI0: dev=%d, active-chan=%d, size=%d, loops=%d", - _deviceId, _numActiveChannels, _data.size(), _loops); - break; - - case _SIMSG_BASEMSG_SET_LOOPS: - _loops = msg._arg.i; - break; - - case _SIMSG_BASEMSG_STOP: { - songit_id_t sought_id = msg.ID; - - if (sought_id == ID) - _channel.state = SI_STATE_FINISHED; - break; - } - - case _SIMSG_BASEMSG_SET_PLAYMASK: { - int i; - _deviceId = msg._arg.i; - - /* Set all but the rhytm channel mask bits */ - _channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL); - - for (i = 0; i < MIDI_CHANNELS; i++) - if (_data[2 + (i << 1)] & _deviceId - && i != MIDI_RHYTHM_CHANNEL) - _channel.playmask |= (1 << i); - } - break; - - case _SIMSG_BASEMSG_SET_RHYTHM: - _channel.playmask &= ~(1 << MIDI_RHYTHM_CHANNEL); - if (msg._arg.i) - _channel.playmask |= (1 << MIDI_RHYTHM_CHANNEL); - break; - - case _SIMSG_BASEMSG_SET_FADE: { - fade_params_t *fp = (fade_params_t *) msg._arg.p; - fade.action = fp->action; - fade.final_volume = fp->final_volume; - fade.ticks_per_step = fp->ticks_per_step; - fade.step_size = fp->step_size; - break; - } - - default: - return NULL; - } - - return this; - } - return NULL; -} - -int Sci0SongIterator::getTimepos() { - return _channel.total_timepos; -} - -Sci0SongIterator::Sci0SongIterator(byte *data, uint size, songit_id_t id) - : BaseSongIterator(data, size, id) { - channel_mask = 0xffff; // Allocate all channels by default - _channel.state = SI_STATE_UNINITIALISED; - - for (int i = 0; i < MIDI_CHANNELS; i++) - _polyphony[i] = data[1 + (i << 1)]; - - init(); -} - -void Sci0SongIterator::init() { - fade.action = FADE_ACTION_NONE; - _resetflag = 0; - _loops = 0; - priority = 0; - - _ccc = 0; /* Reset cumulative cue counter */ - _numActiveChannels = 1; - _channel.init(0, SCI0_MIDI_OFFSET, _data.size()); - _channel.resetSynthChannels(); - - if (_data[0] == 2) /* Do we have an embedded PCM? */ - _channel.state = SI_STATE_PCM; -} - -SongIterator *Sci0SongIterator::clone(int delta) { - Sci0SongIterator *newit = new Sci0SongIterator(*this); - return newit; -} - - -/***************************/ -/*-- SCI1 song iterators --*/ -/***************************/ - -int Sci1SongIterator::initSample(const int offset) { - Sci1Sample sample; - int rate; - int length; - int begin; - int end; - - CHECK_FOR_END_ABSOLUTE((uint)offset + 10); - if (_data[offset + 1] != 0) - warning("[iterator-1] In sample at offset 0x04x: Byte #1 is %02x instead of zero", - _data[offset + 1]); - - rate = (int16)READ_LE_UINT16(_data.begin() + offset + 2); - length = READ_LE_UINT16(_data.begin() + offset + 4); - begin = (int16)READ_LE_UINT16(_data.begin() + offset + 6); - end = (int16)READ_LE_UINT16(_data.begin() + offset + 8); - - CHECK_FOR_END_ABSOLUTE((uint)(offset + 10 + length)); - - sample.delta = begin; - sample.size = length; - sample._data = _data.begin() + offset + 10; - -#ifdef DEBUG_VERBOSE - fprintf(stderr, "[SAMPLE] %x/%x/%x/%x l=%x\n", - offset + 10, begin, end, _data.size(), length); -#endif - - sample.rate = rate; - - sample.announced = false; - - /* Insert into the sample list at the right spot, keeping it sorted by delta */ - Common::List<Sci1Sample>::iterator seeker = _samples.begin(); - while (seeker != _samples.end() && seeker->delta < begin) - ++seeker; - _samples.insert(seeker, sample); - - return 0; /* Everything's fine */ -} - -int Sci1SongIterator::initSong() { - int last_time; - uint offset = 0; - _numChannels = 0; - _samples.clear(); -// _deviceId = 0x0c; - - if (_data[offset] == 0xf0) { - priority = _data[offset + 1]; - - offset += 8; - } - - while (_data[offset] != 0xff - && _data[offset] != _deviceId) { - offset++; - CHECK_FOR_END_ABSOLUTE(offset + 1); - while (_data[offset] != 0xff) { - CHECK_FOR_END_ABSOLUTE(offset + 7); - offset += 6; - } - offset++; - } - - if (_data[offset] == 0xff) { - warning("[iterator] Song does not support hardware 0x%02x", _deviceId); - return 1; - } - - offset++; - - while (_data[offset] != 0xff) { /* End of list? */ - uint track_offset; - int end; - offset += 2; - - CHECK_FOR_END_ABSOLUTE(offset + 4); - - track_offset = READ_LE_UINT16(_data.begin() + offset); - end = READ_LE_UINT16(_data.begin() + offset + 2); - - CHECK_FOR_END_ABSOLUTE(track_offset - 1); - - if (_data[track_offset] == 0xfe) { - if (initSample(track_offset)) - return 1; /* Error */ - } else { - /* Regular MIDI channel */ - if (_numChannels >= MIDI_CHANNELS) { - warning("[iterator] Song has more than %d channels, cutting them off", - MIDI_CHANNELS); - break; /* Scan for remaining samples */ - } else { - int channel_nr = _data[track_offset] & 0xf; - SongIteratorChannel &channel = _channels[_numChannels++]; - - /* - if (_data[track_offset] & 0xf0) - printf("Channel %d has mapping bits %02x\n", - channel_nr, _data[track_offset] & 0xf0); - */ - - // Add 2 to skip over header bytes */ - channel.init(channel_nr, track_offset + 2, track_offset + end); - channel.resetSynthChannels(); - - _polyphony[_numChannels - 1] = _data[channel.offset - 1] & 15; - - channel.playmask = ~0; /* Enable all */ - channel_mask |= (1 << channel_nr); - - CHECK_FOR_END_ABSOLUTE(offset + end); - } - } - offset += 4; - CHECK_FOR_END_ABSOLUTE(offset); - } - - /* Now ensure that sample deltas are relative to the previous sample */ - last_time = 0; - _numActiveChannels = _numChannels; - _numLoopedChannels = 0; - - for (Common::List<Sci1Sample>::iterator seeker = _samples.begin(); - seeker != _samples.end(); ++seeker) { - int prev_last_time = last_time; - //printf("[iterator] Detected sample: %d Hz, %d bytes at time %d\n", - // seeker->format.rate, seeker->size, seeker->delta); - last_time = seeker->delta; - seeker->delta -= prev_last_time; - } - - return 0; /* Success */ -} - -int Sci1SongIterator::getSmallestDelta() const { - int d = -1; - for (int i = 0; i < _numChannels; i++) - if (_channels[i].state == SI_STATE_COMMAND - && (d == -1 || _channels[i].delay < d)) - d = _channels[i].delay; - - if (!_samples.empty() && _samples.begin()->delta < d) - return _samples.begin()->delta; - else - return d; -} - -void Sci1SongIterator::updateDelta(int delta) { - if (!_samples.empty()) - _samples.begin()->delta -= delta; - - for (int i = 0; i < _numChannels; i++) - if (_channels[i].state == SI_STATE_COMMAND) - _channels[i].delay -= delta; -} - -bool Sci1SongIterator::noDeltaTime() const { - for (int i = 0; i < _numChannels; i++) - if (_channels[i].state == SI_STATE_DELTA_TIME) - return false; - return true; -} - -#define COMMAND_INDEX_NONE -1 -#define COMMAND_INDEX_PCM -2 - -int Sci1SongIterator::getCommandIndex() const { - /* Determine the channel # of the next active event, or -1 */ - int i; - int base_delay = 0x7ffffff; - int best_chan = COMMAND_INDEX_NONE; - - for (i = 0; i < _numChannels; i++) - if ((_channels[i].state != SI_STATE_PENDING) - && (_channels[i].state != SI_STATE_FINISHED)) { - - if ((_channels[i].state == SI_STATE_DELTA_TIME) - && (_channels[i].delay == 0)) - return i; - /* First, read all unknown delta times */ - - if (_channels[i].delay < base_delay) { - best_chan = i; - base_delay = _channels[i].delay; - } - } - - if (!_samples.empty() && base_delay >= _samples.begin()->delta) - return COMMAND_INDEX_PCM; - - return best_chan; -} - - -Audio::AudioStream *Sci1SongIterator::getAudioStream() { - Common::List<Sci1Sample>::iterator sample = _samples.begin(); - if (sample != _samples.end() && sample->delta <= 0) { - Audio::AudioStream *feed = makeStream(sample->_data, sample->size, sample->rate); - _samples.erase(sample); - - return feed; - } else - return NULL; -} - -int Sci1SongIterator::nextCommand(byte *buf, int *result) { - - if (!_initialised) { - //printf("[iterator] DEBUG: Initialising for %d\n", _deviceId); - _initialised = true; - if (initSong()) - return SI_FINISHED; - } - - - if (_delayRemaining) { - int delay = _delayRemaining; - _delayRemaining = 0; - return delay; - } - - int retval = 0; - do { /* All delays must be processed separately */ - int chan = getCommandIndex(); - - if (chan == COMMAND_INDEX_NONE) { - return SI_FINISHED; - } - - if (chan == COMMAND_INDEX_PCM) { - - if (_samples.begin()->announced) { - /* Already announced; let's discard it */ - Audio::AudioStream *feed = getAudioStream(); - delete feed; - } else { - int delay = _samples.begin()->delta; - - if (delay) { - updateDelta(delay); - return delay; - } - /* otherwise we're touching a PCM */ - _samples.begin()->announced = true; - return SI_PCM; - } - } else { /* Not a PCM */ - - retval = processMidi(buf, result, - &(_channels[chan]), - PARSE_FLAG_LOOPS_UNLIMITED); - - if (retval == SI_LOOP) { - _numLoopedChannels++; - _channels[chan].state = SI_STATE_PENDING; - _channels[chan].delay = 0; - - if (_numLoopedChannels == _numActiveChannels) { - int i; - - /* Everyone's ready: Let's loop */ - for (i = 0; i < _numChannels; i++) - if (_channels[i].state == SI_STATE_PENDING) - _channels[i].state = SI_STATE_DELTA_TIME; - - _numLoopedChannels = 0; - return SI_LOOP; - } - } else if (retval == SI_FINISHED) { -#ifdef DEBUG - fprintf(stderr, "FINISHED some channel\n"); -#endif - } else if (retval > 0) { - int sd ; - sd = getSmallestDelta(); - - if (noDeltaTime() && sd) { - /* No other channel is ready */ - updateDelta(sd); - - /* Only from here do we return delta times */ - return sd; - } - } - - } /* Not a PCM */ - - } while (retval > 0); - - return retval; -} - -SongIterator *Sci1SongIterator::handleMessage(Message msg) { - if (msg._class == _SIMSG_BASE) { /* May extend this in the future */ - switch (msg._type) { - - case _SIMSG_BASEMSG_PRINT: { - int playmask = 0; - int i; - - for (i = 0; i < _numChannels; i++) - playmask |= _channels[i].playmask; - - print_tabs_id(msg._arg.i, ID); - debugC(2, kDebugLevelSound, "SCI1: chan-nr=%d, playmask=%04x", - _numChannels, playmask); - } - break; - - case _SIMSG_BASEMSG_STOP: { - songit_id_t sought_id = msg.ID; - int i; - - if (sought_id == ID) { - ID = 0; - - for (i = 0; i < _numChannels; i++) - _channels[i].state = SI_STATE_FINISHED; - } - break; - } - - case _SIMSG_BASEMSG_SET_PLAYMASK: - if (msg.ID == ID) { - channel_mask = 0; - - _deviceId = msg._arg.i; - - if (_initialised) { - int i; - int toffset = -1; - - for (i = 0; i < _numChannels; i++) - if (_channels[i].state != SI_STATE_FINISHED - && _channels[i].total_timepos > toffset) { - toffset = _channels[i].total_timepos - + _channels[i].timepos_increment - - _channels[i].delay; - } - - /* Find an active channel so that we can - ** get the correct time offset */ - - initSong(); - - toffset -= _delayRemaining; - _delayRemaining = 0; - - if (toffset > 0) - return new_fast_forward_iterator(this, toffset); - } else { - initSong(); - _initialised = true; - } - - break; - - } - - case _SIMSG_BASEMSG_SET_LOOPS: - if (msg.ID == ID) - _loops = (msg._arg.i > 32767) ? 99 : 0; - /* 99 is arbitrary, but we can't use '1' because of - ** the way we're testing in the decoding section. */ - break; - - case _SIMSG_BASEMSG_SET_HOLD: - _hold = msg._arg.i; - break; - case _SIMSG_BASEMSG_SET_RHYTHM: - /* Ignore */ - break; - - case _SIMSG_BASEMSG_SET_FADE: { - fade_params_t *fp = (fade_params_t *) msg._arg.p; - fade.action = fp->action; - fade.final_volume = fp->final_volume; - fade.ticks_per_step = fp->ticks_per_step; - fade.step_size = fp->step_size; - break; - } - - default: - warning("Unsupported command %d to SCI1 iterator", msg._type); - } - return this; - } - return NULL; -} - -Sci1SongIterator::Sci1SongIterator(byte *data, uint size, songit_id_t id) - : BaseSongIterator(data, size, id) { - channel_mask = 0; // Defer channel allocation - - for (int i = 0; i < MIDI_CHANNELS; i++) - _polyphony[i] = 0; // Unknown - - init(); -} - -void Sci1SongIterator::init() { - fade.action = FADE_ACTION_NONE; - _resetflag = 0; - _loops = 0; - priority = 0; - - _ccc = 0; - _deviceId = 0x00; // Default to Sound Blaster/AdLib for purposes of cue computation - _numChannels = 0; - _initialised = false; - _delayRemaining = 0; - _loops = 0; - _hold = 0; - memset(_polyphony, 0, sizeof(_polyphony)); -} - -Sci1SongIterator::~Sci1SongIterator() { -} - - -SongIterator *Sci1SongIterator::clone(int delta) { - Sci1SongIterator *newit = new Sci1SongIterator(*this); - newit->_delayRemaining = delta; - return newit; -} - -int Sci1SongIterator::getTimepos() { - int max = 0; - int i; - - for (i = 0; i < _numChannels; i++) - if (_channels[i].total_timepos > max) - max = _channels[i].total_timepos; - - return max; -} - -/** - * A song iterator with the purpose of sending notes-off channel commands. - */ -class CleanupSongIterator : public SongIterator { -public: - CleanupSongIterator(uint channels) { - channel_mask = channels; - ID = 17; - } - - int nextCommand(byte *buf, int *result); - Audio::AudioStream *getAudioStream() { return NULL; } - SongIterator *handleMessage(Message msg); - int getTimepos() { return 0; } - SongIterator *clone(int delta) { return new CleanupSongIterator(*this); } -}; - -SongIterator *CleanupSongIterator::handleMessage(Message msg) { - if (msg._class == _SIMSG_BASEMSG_PRINT && msg._type == _SIMSG_BASEMSG_PRINT) { - print_tabs_id(msg._arg.i, ID); - debugC(2, kDebugLevelSound, "CLEANUP"); - } - - return NULL; -} - -int CleanupSongIterator::nextCommand(byte *buf, int *result) { - /* Task: Return channel-notes-off for each channel */ - if (channel_mask) { - int bs = sci_ffs(channel_mask) - 1; - - channel_mask &= ~(1 << bs); - buf[0] = 0xb0 | bs; /* Controller */ - buf[1] = SCI_MIDI_CHANNEL_NOTES_OFF; - buf[2] = 0; /* Hmm... */ - *result = 3; - return 0; - } else - return SI_FINISHED; -} - -/**********************/ -/*-- Timer iterator --*/ -/**********************/ -int TimerSongIterator::nextCommand(byte *buf, int *result) { - if (_delta) { - int d = _delta; - _delta = 0; - return d; - } - return SI_FINISHED; -} - -SongIterator *new_timer_iterator(int delta) { - return new TimerSongIterator(delta); -} - -/**********************************/ -/*-- Fast-forward song iterator --*/ -/**********************************/ - -int FastForwardSongIterator::nextCommand(byte *buf, int *result) { - if (_delta <= 0) - return SI_MORPH; /* Did our duty */ - - while (1) { - int rv = _delegate->nextCommand(buf, result); - - if (rv > 0) { - /* Subtract from the delta we want to wait */ - _delta -= rv; - - /* Done */ - if (_delta < 0) - return -_delta; - } - - if (rv <= 0) - return rv; - } -} - -Audio::AudioStream *FastForwardSongIterator::getAudioStream() { - return _delegate->getAudioStream(); -} - -SongIterator *FastForwardSongIterator::handleMessage(Message msg) { - if (msg._class == _SIMSG_PLASTICWRAP) { - assert(msg._type == _SIMSG_PLASTICWRAP_ACK_MORPH); - - if (_delta <= 0) { - SongIterator *it = _delegate; - delete this; - return it; - } - - warning("[ff-iterator] Morphing without need"); - return this; - } - - if (msg._class == _SIMSG_BASE && msg._type == _SIMSG_BASEMSG_PRINT) { - print_tabs_id(msg._arg.i, ID); - debugC(2, kDebugLevelSound, "FASTFORWARD:"); - msg._arg.i++; - } - - // And continue with the delegate - songit_handle_message(&_delegate, msg); - - return NULL; -} - - -int FastForwardSongIterator::getTimepos() { - return _delegate->getTimepos(); -} - -FastForwardSongIterator::FastForwardSongIterator(SongIterator *capsit, int delta) - : _delegate(capsit), _delta(delta) { - - channel_mask = capsit->channel_mask; -} - -SongIterator *FastForwardSongIterator::clone(int delta) { - FastForwardSongIterator *newit = new FastForwardSongIterator(*this); - newit->_delegate = _delegate->clone(delta); - return newit; -} - -SongIterator *new_fast_forward_iterator(SongIterator *capsit, int delta) { - if (capsit == NULL) - return NULL; - - FastForwardSongIterator *it = new FastForwardSongIterator(capsit, delta); - return it; -} - - -/********************/ -/*-- Tee iterator --*/ -/********************/ - - -static void song_iterator_add_death_listener(SongIterator *it, TeeSongIterator *client) { - for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) { - if (it->_deathListeners[i] == 0) { - it->_deathListeners[i] = client; - return; - } - } - error("FATAL: Too many death listeners for song iterator"); -} - -static void song_iterator_remove_death_listener(SongIterator *it, TeeSongIterator *client) { - for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) { - if (it->_deathListeners[i] == client) { - it->_deathListeners[i] = 0; - return; - } - } -} - -static void song_iterator_transfer_death_listeners(SongIterator *it, SongIterator *it_from) { - for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) { - if (it_from->_deathListeners[i]) - song_iterator_add_death_listener(it, it_from->_deathListeners[i]); - it_from->_deathListeners[i] = 0; - } -} - -static void songit_tee_death_notification(TeeSongIterator *self, SongIterator *corpse) { - if (corpse == self->_children[TEE_LEFT].it) { - self->_status &= ~TEE_LEFT_ACTIVE; - self->_children[TEE_LEFT].it = NULL; - } else if (corpse == self->_children[TEE_RIGHT].it) { - self->_status &= ~TEE_RIGHT_ACTIVE; - self->_children[TEE_RIGHT].it = NULL; - } else { - error("songit_tee_death_notification() failed: Breakpoint in %s, line %d", __FILE__, __LINE__); - } -} - -TeeSongIterator::TeeSongIterator(SongIterator *left, SongIterator *right) { - int i; - int firstfree = 1; /* First free channel */ - int incomplete_map = 0; - - _readyToMorph = false; - _status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE; - - _children[TEE_LEFT].it = left; - _children[TEE_RIGHT].it = right; - - /* Default to lhs channels */ - channel_mask = left->channel_mask; - for (i = 0; i < 16; i++) - if (channel_mask & (1 << i) & right->channel_mask - && (i != MIDI_RHYTHM_CHANNEL) /* Share rhythm */) { /*conflict*/ - while ((firstfree == MIDI_RHYTHM_CHANNEL) - /* Either if it's the rhythm channel or if it's taken */ - || (firstfree < MIDI_CHANNELS - && ((1 << firstfree) & channel_mask))) - ++firstfree; - - if (firstfree == MIDI_CHANNELS) { - incomplete_map = 1; - //warning("[songit-tee <%08lx,%08lx>] Could not remap right channel #%d: Out of channels", - // left->ID, right->ID, i); - } else { - _children[TEE_RIGHT].it->channel_remap[i] = firstfree; - - channel_mask |= (1 << firstfree); - } - } -#ifdef DEBUG_TEE_ITERATOR - if (incomplete_map) { - int c; - fprintf(stderr, "[songit-tee <%08lx,%08lx>] Channels:" - " %04x <- %04x | %04x\n", - left->ID, right->ID, - channel_mask, - left->channel_mask, right->channel_mask); - for (c = 0 ; c < 2; c++) - for (i = 0 ; i < 16; i++) - fprintf(stderr, " map [%d][%d] -> %d\n", - c, i, _children[c].it->channel_remap[i]); - } -#endif - - - song_iterator_add_death_listener(left, this); - song_iterator_add_death_listener(right, this); -} - -TeeSongIterator::~TeeSongIterator() { - // When we die, remove any listeners from our children - if (_children[TEE_LEFT].it) { - song_iterator_remove_death_listener(_children[TEE_LEFT].it, this); - } - - if (_children[TEE_RIGHT].it) { - song_iterator_remove_death_listener(_children[TEE_RIGHT].it, this); - } -} - - -int TeeSongIterator::nextCommand(byte *buf, int *result) { - static const int ready_masks[2] = {TEE_LEFT_READY, TEE_RIGHT_READY}; - static const int active_masks[2] = {TEE_LEFT_ACTIVE, TEE_RIGHT_ACTIVE}; - static const int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM}; - int i; - int retid; - -#ifdef DEBUG_TEE_ITERATOR - fprintf(stderr, "[Tee] %02x\n", _status); -#endif - - if (!(_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) - /* None is active? */ - return SI_FINISHED; - - if (_readyToMorph) - return SI_MORPH; - - if ((_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) - != (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE)) { - /* Not all are is active? */ - int which = 0; -#ifdef DEBUG_TEE_ITERATOR - fprintf(stderr, "\tRequesting transformation...\n"); -#endif - if (_status & TEE_LEFT_ACTIVE) - which = TEE_LEFT; - else if (_status & TEE_RIGHT_ACTIVE) - which = TEE_RIGHT; - memcpy(buf, _children[which].buf, sizeof(buf)); - *result = _children[which].result; - _readyToMorph = true; - return _children[which].retval; - } - - /* First, check for unreported PCMs */ - for (i = TEE_LEFT; i <= TEE_RIGHT; i++) - if ((_status & (ready_masks[i] | pcm_masks[i])) - == (ready_masks[i] | pcm_masks[i])) { - _status &= ~ready_masks[i]; - return SI_PCM; - } - - for (i = TEE_LEFT; i <= TEE_RIGHT; i++) - if (!(_status & ready_masks[i])) { - - /* Buffers aren't ready yet */ - _children[i].retval = - songit_next(&(_children[i].it), - _children[i].buf, - &(_children[i].result), - IT_READER_MASK_ALL - | IT_READER_MAY_FREE - | IT_READER_MAY_CLEAN); - - _status |= ready_masks[i]; -#ifdef DEBUG_TEE_ITERATOR - fprintf(stderr, "\t Must check %d: %d\n", i, _children[i].retval); -#endif - - if (_children[i].retval == SI_ABSOLUTE_CUE || - _children[i].retval == SI_RELATIVE_CUE) - return _children[i].retval; - if (_children[i].retval == SI_FINISHED) { - _status &= ~active_masks[i]; - /* Recurse to complete */ -#ifdef DEBUG_TEE_ITERATOR - fprintf(stderr, "\t Child %d signalled completion, recursing w/ status %02x\n", i, _status); -#endif - return nextCommand(buf, result); - } else if (_children[i].retval == SI_PCM) { - _status |= pcm_masks[i]; - _status &= ~ready_masks[i]; - return SI_PCM; - } - } - - - /* We've already handled PCM, MORPH and FINISHED, CUEs & LOOP remain */ - - retid = TEE_LEFT; - if ((_children[TEE_LEFT].retval > 0) - /* Asked to delay */ - && (_children[TEE_RIGHT].retval <= _children[TEE_LEFT].retval)) - /* Is not delaying or not delaying as much */ - retid = TEE_RIGHT; - -#ifdef DEBUG_TEE_ITERATOR - fprintf(stderr, "\tl:%d / r:%d / chose %d\n", - _children[TEE_LEFT].retval, _children[TEE_RIGHT].retval, retid); -#endif - - /* Adjust delta times */ - if (_children[retid].retval > 0 - && _children[1-retid].retval > 0) { - if (_children[1-retid].retval - == _children[retid].retval) - /* If both _children wait the same amount of time, - ** we have to re-fetch commands from both */ - _status &= ~ready_masks[1-retid]; - else - /* If they don't, we can/must re-use the other - ** child's delay time */ - _children[1-retid].retval - -= _children[retid].retval; - } - - _status &= ~ready_masks[retid]; - memcpy(buf, _children[retid].buf, sizeof(buf)); - *result = _children[retid].result; - - return _children[retid].retval; -} - -Audio::AudioStream *TeeSongIterator::getAudioStream() { - static const int pcm_masks[2] = {TEE_LEFT_PCM, TEE_RIGHT_PCM}; - int i; - - for (i = TEE_LEFT; i <= TEE_RIGHT; i++) - if (_status & pcm_masks[i]) { - _status &= ~pcm_masks[i]; - return _children[i].it->getAudioStream(); - } - - return NULL; // No iterator -} - -SongIterator *TeeSongIterator::handleMessage(Message msg) { - if (msg._class == _SIMSG_PLASTICWRAP) { - assert(msg._type == _SIMSG_PLASTICWRAP_ACK_MORPH); - - SongIterator *old_it; - if (!(_status & (TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE))) { - delete this; - return NULL; - } else if (!(_status & TEE_LEFT_ACTIVE)) { - delete _children[TEE_LEFT].it; - _children[TEE_LEFT].it = 0; - old_it = _children[TEE_RIGHT].it; - song_iterator_remove_death_listener(old_it, this); - song_iterator_transfer_death_listeners(old_it, this); - delete this; - return old_it; - } else if (!(_status & TEE_RIGHT_ACTIVE)) { - delete _children[TEE_RIGHT].it; - _children[TEE_RIGHT].it = 0; - old_it = _children[TEE_LEFT].it; - song_iterator_remove_death_listener(old_it, this); - song_iterator_transfer_death_listeners(old_it, this); - delete this; - return old_it; - } - - warning("[tee-iterator] Morphing without need"); - return this; - } - - if (msg._class == _SIMSG_BASE && msg._type == _SIMSG_BASEMSG_PRINT) { - print_tabs_id(msg._arg.i, ID); - debugC(2, kDebugLevelSound, "TEE:"); - msg._arg.i++; - } - - // And continue with the children - if (_children[TEE_LEFT].it) - songit_handle_message(&(_children[TEE_LEFT].it), msg); - if (_children[TEE_RIGHT].it) - songit_handle_message(&(_children[TEE_RIGHT].it), msg); - - return NULL; -} - -void TeeSongIterator::init() { - _status = TEE_LEFT_ACTIVE | TEE_RIGHT_ACTIVE; - _children[TEE_LEFT].it->init(); - _children[TEE_RIGHT].it->init(); -} - -SongIterator *TeeSongIterator::clone(int delta) { - TeeSongIterator *newit = new TeeSongIterator(*this); - - if (_children[TEE_LEFT].it) - newit->_children[TEE_LEFT].it = _children[TEE_LEFT].it->clone(delta); - if (_children[TEE_RIGHT].it) - newit->_children[TEE_RIGHT].it = _children[TEE_RIGHT].it->clone(delta); - - return newit; -} - - -/*************************************/ -/*-- General purpose functionality --*/ -/*************************************/ - -int songit_next(SongIterator **it, byte *buf, int *result, int mask) { - int retval; - - if (!*it) - return SI_FINISHED; - - do { - retval = (*it)->nextCommand(buf, result); - if (retval == SI_MORPH) { - debugC(2, kDebugLevelSound, " Morphing %p (stored at %p)", (void *)*it, (void *)it); - if (!SIMSG_SEND((*it), SIMSG_ACK_MORPH)) { - error("SI_MORPH failed. Breakpoint in %s, line %d", __FILE__, __LINE__); - } else - debugC(2, kDebugLevelSound, "SI_MORPH successful"); - } - - if (retval == SI_FINISHED) - debugC(2, kDebugLevelSound, "[song-iterator] Song finished. mask = %04x, cm=%04x", - mask, (*it)->channel_mask); - if (retval == SI_FINISHED - && (mask & IT_READER_MAY_CLEAN) - && (*it)->channel_mask) { /* This last test will fail - ** with a terminated - ** cleanup iterator */ - int channel_mask = (*it)->channel_mask; - - SongIterator *old_it = *it; - *it = new CleanupSongIterator(channel_mask); - for(uint i = 0; i < MIDI_CHANNELS; i++) - (*it)->channel_remap[i] = old_it->channel_remap[i]; - song_iterator_transfer_death_listeners(*it, old_it); - if (mask & IT_READER_MAY_FREE) - delete old_it; - retval = -9999; /* Continue */ - } - } while (!( /* Until one of the following holds */ - (retval > 0 && (mask & IT_READER_MASK_DELAY)) - || (retval == 0 && (mask & IT_READER_MASK_MIDI)) - || (retval == SI_LOOP && (mask & IT_READER_MASK_LOOP)) - || (retval == SI_ABSOLUTE_CUE && - (mask & IT_READER_MASK_CUE)) - || (retval == SI_RELATIVE_CUE && - (mask & IT_READER_MASK_CUE)) - || (retval == SI_PCM && (mask & IT_READER_MASK_PCM)) - || (retval == SI_FINISHED) - )); - - if (retval == SI_FINISHED && (mask & IT_READER_MAY_FREE)) { - delete *it; - *it = NULL; - } - - return retval; -} - -SongIterator::SongIterator() { - ID = 0; - channel_mask = 0; - fade.action = FADE_ACTION_NONE; - priority = 0; - memset(_deathListeners, 0, sizeof(_deathListeners)); - - // By default, don't remap - for (uint i = 0; i < 16; i++) - channel_remap[i] = i; -} - -SongIterator::SongIterator(const SongIterator &si) { - ID = si.ID; - channel_mask = si.channel_mask; - fade = si.fade; - priority = si.priority; - memset(_deathListeners, 0, sizeof(_deathListeners)); - - for (uint i = 0; i < 16; i++) - channel_remap[i] = si.channel_remap[i]; -} - - -SongIterator::~SongIterator() { - for (int i = 0; i < SONGIT_MAX_LISTENERS; ++i) - if (_deathListeners[i]) - songit_tee_death_notification(_deathListeners[i], this); -} - -SongIterator *songit_new(byte *data, uint size, SongIteratorType type, songit_id_t id) { - BaseSongIterator *it; - - if (!data || size < 22) { - warning("Attempt to instantiate song iterator for null song data"); - return NULL; - } - - - switch (type) { - case SCI_SONG_ITERATOR_TYPE_SCI0: - it = new Sci0SongIterator(data, size, id); - break; - - case SCI_SONG_ITERATOR_TYPE_SCI1: - it = new Sci1SongIterator(data, size, id); - break; - - default: - /**-- Invalid/unsupported sound resources --**/ - warning("Attempt to instantiate invalid/unknown song iterator type %d", type); - return NULL; - } - - return it; -} - -int songit_handle_message(SongIterator **it_reg_p, SongIterator::Message msg) { - SongIterator *it = *it_reg_p; - SongIterator *newit; - - newit = it->handleMessage(msg); - - if (!newit) - return 0; /* Couldn't handle */ - - *it_reg_p = newit; /* Might have self-morphed */ - return 1; -} - -SongIterator *sfx_iterator_combine(SongIterator *it1, SongIterator *it2) { - if (it1 == NULL) - return it2; - if (it2 == NULL) - return it1; - - /* Both are non-NULL: */ - return new TeeSongIterator(it1, it2); -} - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS diff --git a/engines/sci/sound/iterator/iterator.h b/engines/sci/sound/iterator/iterator.h deleted file mode 100644 index e5c8f50702..0000000000 --- a/engines/sci/sound/iterator/iterator.h +++ /dev/null @@ -1,326 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -/* Song iterator declarations */ - -#ifndef SCI_SFX_SFX_ITERATOR_H -#define SCI_SFX_SFX_ITERATOR_H - -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS - -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/drivers/mididriver.h" - -namespace Audio { - class AudioStream; -} - -namespace Sci { - -enum SongIteratorStatus { - SI_FINISHED = -1, /**< Song finished playing */ - SI_LOOP = -2, /**< Song just looped */ - SI_ABSOLUTE_CUE = -3, /**< Found a song cue (absolute) */ - SI_RELATIVE_CUE = -4, /**< Found a song cue (relative) */ - SI_PCM = -5, /**< Found a PCM */ - SI_IGNORE = -6, /**< This event got edited out by the remapper */ - SI_MORPH = -255 /**< Song iterator requested self-morph. */ -}; - -#define FADE_ACTION_NONE 0 -#define FADE_ACTION_FADE_AND_STOP 1 -#define FADE_ACTION_FADE_AND_CONT 2 - -struct fade_params_t { - int ticks_per_step; - int final_volume; - int step_size; - int action; -}; - -/* Helper defs for messages */ -enum { - _SIMSG_BASE, /* Any base decoder */ - _SIMSG_PLASTICWRAP /* Any "Plastic" (discardable) wrapper decoder */ -}; - -/* Base messages */ -enum { - _SIMSG_BASEMSG_SET_LOOPS, /* Set loops */ - _SIMSG_BASEMSG_SET_PLAYMASK, /* Set the current playmask for filtering */ - _SIMSG_BASEMSG_SET_RHYTHM, /* Activate/deactivate rhythm channel */ - _SIMSG_BASEMSG_ACK_MORPH, /* Acknowledge self-morph */ - _SIMSG_BASEMSG_STOP, /* Stop iterator */ - _SIMSG_BASEMSG_PRINT, /* Print self to stderr, after printing param1 tabs */ - _SIMSG_BASEMSG_SET_HOLD, /* Set value of hold parameter to expect */ - _SIMSG_BASEMSG_SET_FADE /* Set fade parameters */ -}; - -/* "Plastic" (discardable) wrapper messages */ -enum { - _SIMSG_PLASTICWRAP_ACK_MORPH = _SIMSG_BASEMSG_ACK_MORPH /* Acknowledge self-morph */ -}; - -/* Messages */ -#define SIMSG_SET_LOOPS(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_LOOPS,(x) -#define SIMSG_SET_PLAYMASK(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_PLAYMASK,(x) -#define SIMSG_SET_RHYTHM(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_RHYTHM,(x) -#define SIMSG_ACK_MORPH _SIMSG_PLASTICWRAP,_SIMSG_PLASTICWRAP_ACK_MORPH,0 -#define SIMSG_STOP _SIMSG_BASE,_SIMSG_BASEMSG_STOP,0 -#define SIMSG_PRINT(indentation) _SIMSG_BASE,_SIMSG_BASEMSG_PRINT,(indentation) -#define SIMSG_SET_HOLD(x) _SIMSG_BASE,_SIMSG_BASEMSG_SET_HOLD,(x) - -/* Message transmission macro: Takes song reference, message reference */ -#define SIMSG_SEND(o, m) songit_handle_message(&(o), SongIterator::Message((o)->ID, m)) -#define SIMSG_SEND_FADE(o, m) songit_handle_message(&(o), SongIterator::Message((o)->ID, _SIMSG_BASE, _SIMSG_BASEMSG_SET_FADE, m)) - -typedef unsigned long songit_id_t; - - -#define SONGIT_MAX_LISTENERS 2 - -class TeeSongIterator; - -class SongIterator { -public: - struct Message { - songit_id_t ID; - uint _class; /* Type of iterator supposed to receive this */ - uint _type; - union { - uint i; - void *p; - } _arg; - - Message() : ID(0), _class(0xFFFF), _type(0xFFFF) {} - - /** - * Create a song iterator message. - * - * @param id: song ID the message is targeted to - * @param recipient_class: Message recipient class - * @param type message type - * @param a argument - * - * @note You should only use this with the SIMSG_* macros - */ - Message(songit_id_t id, int recipient_class, int type, int a) - : ID(id), _class(recipient_class), _type(type) { - _arg.i = a; - } - - /** - * Create a song iterator message, wherein the first parameter is a pointer. - * - * @param id: song ID the message is targeted to - * @param recipient_class: Message recipient class - * @param type message type - * @param a argument - * - * @note You should only use this with the SIMSG_* macros - */ - Message(songit_id_t id, int recipient_class, int type, void *a) - : ID(id), _class(recipient_class), _type(type) { - _arg.p = a; - } - }; - -public: - songit_id_t ID; - uint16 channel_mask; /* Bitmask of all channels this iterator will use */ - fade_params_t fade; - int priority; - - /* Death listeners */ - /* These are not reset during initialisation */ - TeeSongIterator *_deathListeners[SONGIT_MAX_LISTENERS]; - - /* See songit_* for the constructor and non-virtual member functions */ - - byte channel_remap[MIDI_CHANNELS]; ///< Remapping for channels - -public: - SongIterator(); - SongIterator(const SongIterator &); - virtual ~SongIterator(); - - /** - * Resets/initializes the sound iterator. - */ - virtual void init() {} - - /** - * Reads the next MIDI operation _or_ delta time. - * @param buf The buffer to write to (needs to be able to store at least 4 bytes) - * @param result Number of bytes written to the buffer - * (equals the number of bytes that need to be passed - * to the lower layers) for 0, the cue value for SI_CUE, - * or the number of loops remaining for SI_LOOP. - * @return zero if a MIDI operation was written, SI_FINISHED - * if the song has finished playing, SI_LOOP if looping - * (after updating the loop variable), SI_CUE if we found - * a cue, SI_PCM if a PCM was found, or the number of ticks - * to wait before this function should be called next. - * - * @note If SI_PCM is returned, get_pcm() may be used to retrieve the associated - * PCM, but this must be done before any subsequent calls to next(). - * - * @todo The actual buffer size should either be specified or passed in, so that - * we can detect buffer overruns. - */ - virtual int nextCommand(byte *buf, int *result) = 0; - - /** - Checks for the presence of a pcm sample. - * @return NULL if no PCM data was found, an AudioStream otherwise. - */ - virtual Audio::AudioStream *getAudioStream() = 0; - - /** - * Handles a message to the song iterator. - * @param msg the message to handle - * @return NULL if the message was not understood, - * this if the message could be handled, or a new song iterator - * if the current iterator had to be morphed (but the message could - * still be handled) - * - * @note This function is not supposed to be called directly; use - * songit_handle_message() instead. It should not recurse, since songit_handle_message() - * takes care of that and makes sure that its delegate received the message (and - * was morphed) before self. - */ - virtual SongIterator *handleMessage(Message msg) = 0; - - /** - * Gets the song position to store in a savegame. - */ - virtual int getTimepos() = 0; - - /** - * Clone this song iterator. - * @param delta number of ticks that still need to elapse until the - * next item should be read from the song iterator - */ - virtual SongIterator *clone(int delta) = 0; - - -private: - // Make the assignment operator unreachable, just in case... - SongIterator& operator=(const SongIterator&); -}; - - -/********************************/ -/*-- Song iterator operations --*/ -/********************************/ - -enum SongIteratorType { - SCI_SONG_ITERATOR_TYPE_SCI0 = 0, - SCI_SONG_ITERATOR_TYPE_SCI1 = 1 -}; - -#define IT_READER_MASK_MIDI (1 << 0) -#define IT_READER_MASK_DELAY (1 << 1) -#define IT_READER_MASK_LOOP (1 << 2) -#define IT_READER_MASK_CUE (1 << 3) -#define IT_READER_MASK_PCM (1 << 4) -#define IT_READER_MAY_FREE (1 << 10) /* Free SI_FINISHED iterators */ -#define IT_READER_MAY_CLEAN (1 << 11) -/* MAY_CLEAN: May instantiate cleanup iterators -** (use for players; this closes open channels at the end of a song) */ - -#define IT_READER_MASK_ALL ( IT_READER_MASK_MIDI \ - | IT_READER_MASK_DELAY \ - | IT_READER_MASK_LOOP \ - | IT_READER_MASK_CUE \ - | IT_READER_MASK_PCM ) - -/* Convenience wrapper around it->next -** Parameters: (SongIterator **it) Reference to the iterator to access -** (byte *) buf: The buffer to write to (needs to be able to -** store at least 4 bytes) -** (int) mask: IT_READER_MASK options specifying the events to -** listen for -** Returns : (int) zero if a MIDI operation was written, SI_FINISHED -** if the song has finished playing, SI_LOOP if looping -** (after updating the loop variable), SI_CUE if we found -** a cue, SI_PCM if a PCM was found, or the number of ticks -** to wait before this function should be called next. -** (int) *result: Number of bytes written to the buffer -** (equals the number of bytes that need to be passed -** to the lower layers) for 0, the cue value for SI_CUE, -** or the number of loops remaining for SI_LOOP. -*/ -int songit_next(SongIterator **it, byte *buf, int *result, int mask); - -/* Constructs a new song iterator object -** Parameters: (byte *) data: The song data to iterate over -** (uint) size: Number of bytes in the song -** (int) type: One of the SCI_SONG_ITERATOR_TYPEs -** (songit_id_t) id: An ID for addressing the song iterator -** Returns : (SongIterator *) A newly allocated but uninitialized song -** iterator, or NULL if 'type' was invalid or unsupported -*/ -SongIterator *songit_new(byte *data, uint size, SongIteratorType type, songit_id_t id); - -/* Constructs a new song timer iterator object -** Parameters: (int) delta: The delta after which to fire SI_FINISHED -** Returns : (SongIterator *) A newly allocated but uninitialized song -** iterator -*/ -SongIterator *new_timer_iterator(int delta); - -/* Handles a message to the song iterator -** Parameters: (SongIterator **): A reference to the variable storing the song iterator -** Returns : (int) Non-zero if the message was understood -** The song iterator may polymorph as result of msg, so a writeable reference is required. -*/ -int songit_handle_message(SongIterator **it_reg, SongIterator::Message msg); - - -/* Creates a new song iterator which fast-forwards -** Parameters: (SongIterator *) it: The iterator to wrap -** (int) delta: The number of ticks to skip -** Returns : (SongIterator) A newly created song iterator -** which skips all delta times -** until 'delta' has been used up -*/ -SongIterator *new_fast_forward_iterator(SongIterator *it, int delta); - -/* Combines two song iterators into one -** Parameters: (sfx_iterator_t *) it1: One of the two iterators, or NULL -** (sfx_iterator_t *) it2: The other iterator, or NULL -** Returns : (sfx_iterator_t *) A combined iterator -** If a combined iterator is returned, it will be flagged to be allowed to -** dispose of 'it1' and 'it2', where applicable. This means that this -** call should be used by song players, but not by the core sound system -*/ -SongIterator *sfx_iterator_combine(SongIterator *it1, SongIterator *it2); - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS - -#endif // SCI_SFX_SFX_ITERATOR_H diff --git a/engines/sci/sound/iterator/iterator_internal.h b/engines/sci/sound/iterator/iterator_internal.h deleted file mode 100644 index 5a0f0d3ec9..0000000000 --- a/engines/sci/sound/iterator/iterator_internal.h +++ /dev/null @@ -1,276 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#ifndef SCI_SFX_SFX_ITERATOR_INTERNAL -#define SCI_SFX_SFX_ITERATOR_INTERNAL - -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS - -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/iterator.h" -#include "sci/sound/drivers/mididriver.h" - -#include "common/array.h" -#include "common/list.h" - -namespace Sci { - -/* Iterator types */ - -enum { - SI_STATE_UNINITIALISED = -1, - SI_STATE_DELTA_TIME = 0, ///< Now at a delta time - SI_STATE_COMMAND = 1, ///< Now at a MIDI operation - SI_STATE_PENDING = 2, ///< Pending for loop - SI_STATE_FINISHED = 3, ///< End of song - SI_STATE_PCM = 4, ///< Should report a PCM next (-> DELTA_TIME) - SI_STATE_PCM_MAGIC_DELTA = 5 ///< Should report a ``magic'' one tick delta time next (goes on to FINISHED) -}; - -struct SongIteratorChannel { - - int state; ///< State of this song iterator channel - int offset; ///< Offset into the data chunk */ - int end; ///< Last allowed byte in track */ - int id; ///< Some channel ID */ - - /** - * Number of ticks before the specified channel is next used, or - * CHANNEL_DELAY_MISSING to indicate that the delay has not yet - * been read. - */ - int delay; - - /* Two additional offsets for recovering: */ - int loop_offset; - int initial_offset; - - int playmask; ///< Active playmask (MIDI channels to play in here) */ - int loop_timepos; ///< Total delay for this channel's loop marker */ - int total_timepos; ///< Number of ticks since the beginning, ignoring loops */ - int timepos_increment; ///< Number of ticks until the next command (to add) */ - - byte last_cmd; ///< Last operation executed, for running status */ - -public: - void init(int id, int offset, int end); - void resetSynthChannels(); -}; - -class BaseSongIterator : public SongIterator { -public: - int _polyphony[MIDI_CHANNELS]; ///< # of simultaneous notes on each - - int _ccc; ///< Cumulative cue counter, for those who need it - byte _resetflag; ///< for 0x4C -- on DoSound StopSound, do we return to start? - int _deviceId; ///< ID of the device we generating events for - int _numActiveChannels; ///< Number of active channels - Common::Array<byte> _data; ///< Song data - - int _loops; ///< Number of loops remaining - -public: - BaseSongIterator(byte *data, uint size, songit_id_t id); - -protected: - int parseMidiCommand(byte *buf, int *result, SongIteratorChannel *channel, int flags); - int processMidi(byte *buf, int *result, SongIteratorChannel *channel, int flags); -}; - -/********************************/ -/*--------- SCI 0 --------------*/ -/********************************/ - -class Sci0SongIterator : public BaseSongIterator { -public: - SongIteratorChannel _channel; - -public: - Sci0SongIterator(byte *data, uint size, songit_id_t id); - - int nextCommand(byte *buf, int *result); - Audio::AudioStream *getAudioStream(); - SongIterator *handleMessage(Message msg); - void init(); - int getTimepos(); - SongIterator *clone(int delta); -}; - - -/********************************/ -/*--------- SCI 1 --------------*/ -/********************************/ - - -struct Sci1Sample { - /** - * Time left-- initially, this is 'Sample point 1'. - * After initialisation, it is 'sample point 1 minus the sample - * point of the previous sample' - */ - int delta; - int size; - bool announced; /* Announced for download (SI_PCM) */ - int rate; - byte *_data; -}; - -class Sci1SongIterator : public BaseSongIterator { -public: - SongIteratorChannel _channels[MIDI_CHANNELS]; - - /* Invariant: Whenever channels[i].delay == CHANNEL_DELAY_MISSING, - ** channel_offset[i] points to a delta time object. */ - - bool _initialised; /**!< Whether the MIDI channel setup has been initialised */ - int _numChannels; /**!< Number of channels actually used */ - Common::List<Sci1Sample> _samples; - int _numLoopedChannels; /**!< Number of channels that are ready to loop */ - - int _delayRemaining; /**!< Number of ticks that haven't been polled yet */ - int _hold; - -public: - Sci1SongIterator(byte *data, uint size, songit_id_t id); - ~Sci1SongIterator(); - - int nextCommand(byte *buf, int *result); - Audio::AudioStream *getAudioStream(); - SongIterator *handleMessage(Message msg); - void init(); - int getTimepos(); - SongIterator *clone(int delta); - -private: - int initSample(const int offset); - int initSong(); - - int getSmallestDelta() const; - - void updateDelta(int delta); - - /** Checks that none of the channels is waiting for its delta to be read */ - bool noDeltaTime() const; - - /** Determine the channel # of the next active event, or -1 */ - int getCommandIndex() const; -}; - -#define PLAYMASK_NONE 0x0 - -/***************************/ -/*--------- Timer ---------*/ -/***************************/ - -/** - * A song iterator which waits a specified time and then fires - * SI_FINISHED. Used by DoSound, where audio resources are played (SCI1) - */ -class TimerSongIterator : public SongIterator { -protected: - int _delta; /**!< Remaining time */ - -public: - TimerSongIterator(int delta) : _delta(delta) {} - - int nextCommand(byte *buf, int *result); - Audio::AudioStream *getAudioStream() { return NULL; } - SongIterator *handleMessage(Message msg) { return NULL; } - int getTimepos() { return 0; } - SongIterator *clone(int delta) { return new TimerSongIterator(*this); } -}; - -/**********************************/ -/*--------- Fast Forward ---------*/ -/**********************************/ - -/** - * A song iterator which fast-forwards another iterator. - * Skips all delta times until a specified 'delta' has been used up. - */ -class FastForwardSongIterator : public SongIterator { -protected: - SongIterator *_delegate; - int _delta; /**!< Remaining time */ - -public: - FastForwardSongIterator(SongIterator *capsit, int delta); - - int nextCommand(byte *buf, int *result); - Audio::AudioStream *getAudioStream(); - SongIterator *handleMessage(Message msg); - int getTimepos(); - SongIterator *clone(int delta); -}; - - -/**********************************/ -/*--------- Tee iterator ---------*/ -/**********************************/ - -enum { - TEE_LEFT = 0, - TEE_RIGHT = 1, - TEE_LEFT_ACTIVE = (1<<0), - TEE_RIGHT_ACTIVE = (1<<1), - TEE_LEFT_READY = (1<<2), /**!< left result is ready */ - TEE_RIGHT_READY = (1<<3), /**!< right result is ready */ - TEE_LEFT_PCM = (1<<4), - TEE_RIGHT_PCM = (1<<5) -}; - -/** - * This iterator combines two iterators, returns the next event available from either. - */ -class TeeSongIterator : public SongIterator { -public: - int _status; - - bool _readyToMorph; /**!< One of TEE_MORPH_* above */ - - struct { - SongIterator *it; - byte buf[4]; - int result; - int retval; - } _children[2]; - -public: - TeeSongIterator(SongIterator *left, SongIterator *right); - ~TeeSongIterator(); - - int nextCommand(byte *buf, int *result); - Audio::AudioStream *getAudioStream(); - SongIterator *handleMessage(Message msg); - void init(); - int getTimepos() { return 0; } - SongIterator *clone(int delta); -}; - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS - -#endif // SCI_SFX_SFX_ITERATOR_INTERNAL diff --git a/engines/sci/sound/iterator/songlib.cpp b/engines/sci/sound/iterator/songlib.cpp deleted file mode 100644 index 8bc2e8f476..0000000000 --- a/engines/sci/sound/iterator/songlib.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS - -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/core.h" -#include "sci/sound/iterator/iterator.h" - -namespace Sci { - -#define debug_stream stderr - -Song::Song() : _wakeupTime(0, SFX_TICKS_PER_SEC) { - _handle = 0; - _resourceNum = 0; - _priority = 0; - _status = SOUND_STATUS_STOPPED; - - _restoreBehavior = RESTORE_BEHAVIOR_CONTINUE; - _restoreTime = 0; - - _loops = 0; - _hold = 0; - - _it = 0; - _delay = 0; - - _next = NULL; - _nextPlaying = NULL; - _nextStopping = NULL; -} - -Song::Song(SongHandle handle, SongIterator *it, int priority) : _wakeupTime(0, SFX_TICKS_PER_SEC) { - _handle = handle; - _resourceNum = 0; - _priority = priority; - _status = SOUND_STATUS_STOPPED; - - _restoreBehavior = RESTORE_BEHAVIOR_CONTINUE; - _restoreTime = 0; - - _loops = 0; - _hold = 0; - - _it = it; - _delay = 0; - - _next = NULL; - _nextPlaying = NULL; - _nextStopping = NULL; -} - -void SongLibrary::addSong(Song *song) { - Song **seeker = NULL; - int pri = song->_priority; - - if (NULL == song) { - warning("addSong(): NULL passed for song"); - return; - } - - seeker = &_lib; - while (*seeker && ((*seeker)->_priority > pri)) - seeker = &((*seeker)->_next); - - song->_next = *seeker; - *seeker = song; -} - -void SongLibrary::freeSounds() { - Song *next = _lib; - while (next) { - Song *song = next; - delete song->_it; - song->_it = NULL; - next = song->_next; - delete song; - } - _lib = NULL; -} - - -Song *SongLibrary::findSong(SongHandle handle) { - Song *seeker = _lib; - - while (seeker) { - if (seeker->_handle == handle) - break; - seeker = seeker->_next; - } - - return seeker; -} - -Song *SongLibrary::findNextActive(Song *other) { - Song *seeker = other ? other->_next : _lib; - - while (seeker) { - if ((seeker->_status == SOUND_STATUS_WAITING) || - (seeker->_status == SOUND_STATUS_PLAYING)) - break; - seeker = seeker->_next; - } - - /* Only return songs that have equal priority */ - if (other && seeker && other->_priority > seeker->_priority) - return NULL; - - return seeker; -} - -Song *SongLibrary::findFirstActive() { - return findNextActive(NULL); -} - -int SongLibrary::removeSong(SongHandle handle) { - int retval; - Song *goner = _lib; - - if (!goner) - return -1; - - if (goner->_handle == handle) - _lib = goner->_next; - - else { - while ((goner->_next) && (goner->_next->_handle != handle)) - goner = goner->_next; - - if (goner->_next) { /* Found him? */ - Song *oldnext = goner->_next; - - goner->_next = goner->_next->_next; - goner = oldnext; - } else return -1; /* No. */ - } - - retval = goner->_status; - - delete goner->_it; - delete goner; - - return retval; -} - -int SongLibrary::countSongs() { - Song *seeker = _lib; - int retval = 0; - - while (seeker) { - retval++; - seeker = seeker->_next; - } - - return retval; -} - -void SongLibrary::setSongRestoreBehavior(SongHandle handle, RESTORE_BEHAVIOR action) { - Song *seeker = findSong(handle); - - seeker->_restoreBehavior = action; -} - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS diff --git a/engines/sci/sound/iterator/songlib.h b/engines/sci/sound/iterator/songlib.h deleted file mode 100644 index acb704edaa..0000000000 --- a/engines/sci/sound/iterator/songlib.h +++ /dev/null @@ -1,171 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -/* Song library */ - -#ifndef SCI_SFX_SFX_SONGLIB_H -#define SCI_SFX_SFX_SONGLIB_H - -#include "common/scummsys.h" -#include "sound/timestamp.h" - -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS -#ifdef USE_OLD_MUSIC_FUNCTIONS - -namespace Sci { - -class SongIterator; - -#define SOUND_STATUS_STOPPED 0 -#define SOUND_STATUS_PLAYING 1 -#define SOUND_STATUS_SUSPENDED 2 -/* suspended: only if ordered from kernel space */ -#define SOUND_STATUS_WAITING 3 -/* "waiting" means "tagged for playing, but not active right now" */ - -typedef unsigned long SongHandle; - -enum RESTORE_BEHAVIOR { - RESTORE_BEHAVIOR_CONTINUE, /* restart a song when restored from - a saved game */ - RESTORE_BEHAVIOR_RESTART /* continue it from where it was */ -}; - -class Song { -public: - SongHandle _handle; - int _resourceNum; /**<! Resource number */ - int _priority; /**!< Song priority (more important if priority is higher) */ - int _status; /* See above */ - - int _restoreBehavior; - int _restoreTime; - - /* Grabbed from the sound iterator, for save/restore purposes */ - int _loops; - int _hold; - - SongIterator *_it; - int _delay; /**!< Delay before accessing the iterator, in ticks */ - - Audio::Timestamp _wakeupTime; /**!< Timestamp indicating the next MIDI event */ - - Song *_next; /**!< Next song or NULL if this is the last one */ - - /** - * Next playing song. Used by the core song system. - */ - Song *_nextPlaying; - - /** - * Next song pending stopping. Used exclusively by the core song system's - * _update_multi_song() - */ - Song *_nextStopping; - -public: - - Song(); - - /** - * Initializes a new song. - * @param handle the sound handle - * @param it the song - * @param priority the song's priority - * @return a freshly allocated song - */ - Song(SongHandle handle, SongIterator *it, int priority); -}; - - -class SongLibrary { -public: - Song *_lib; - -public: - SongLibrary() : _lib(0) {} - - /** Frees a song library. */ - void freeSounds(); - - /** - * Adds a song to a song library. - * @param song song to add - */ - void addSong(Song *song); - - /** - * Looks up the song with the specified handle. - * @param handle sound handle to look for - * @return the song or NULL if it wasn't found - */ - Song *findSong(SongHandle handle); - - /** - * Finds the first song playing with the highest priority. - * @return the song that should be played next, or NULL if there is none - */ - Song *findFirstActive(); - - /** - * Finds the next song playing with the highest priority. - * - * The functions 'findFirstActive' and 'findNextActive' - * allow to iterate over all songs that satisfy the requirement of - * being 'playable'. - * - * @param song a song previously returned from the song library - * @return the next song to play relative to 'song', or NULL if none are left - */ - Song *findNextActive(Song *song); - - /** - * Removes a song from the library. - * @param handle handle of the song to remove - * @return the status of the song that was removed - */ - int removeSong(SongHandle handle); - - /** - * Counts the number of songs in a song library. - * @return the number of songs - */ - int countSongs(); - - /** - * Determines what should be done with the song "handle" when restoring - * it from a saved game. - * @param handle sound handle being restored - * @param action desired action - */ - void setSongRestoreBehavior(SongHandle handle, - RESTORE_BEHAVIOR action); -}; - -} // End of namespace Sci - -#endif // USE_OLD_MUSIC_FUNCTIONS - -#endif // SCI_SSFX_SFX_SONGLIB_H diff --git a/engines/sci/sound/iterator/test-iterator.cpp b/engines/sci/sound/iterator/test-iterator.cpp deleted file mode 100644 index 0d603a89fd..0000000000 --- a/engines/sci/sound/iterator/test-iterator.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#include "iterator.h" -#include "iterator_internal.h" -#include <stdarg.h> -#include <stdio.h> - -using namespace Sci; - -#define ASSERT_S(x) if (!(x)) { error("Failed assertion in L%d: " #x, __LINE__); return; } -#define ASSERT(x) ASSERT_S(x) - -/* Tests the song iterators */ - -int errors = 0; - -void error(char *fmt, ...) { - va_list ap; - - fprintf(stderr, "[ERROR] "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - ++errors; -} - - -/* The simple iterator will finish after a fixed amount of time. Before that, -** it emits (absolute) cues in ascending order. */ -struct simple_iterator : public SongIterator { - int lifetime_remaining; - char *cues; - int cue_counter; - int cue_progress; - int cues_nr; -}; - -int simple_it_next(SongIterator *_self, unsigned char *buf, int *result) { - simple_iterator *self = (simple_iterator *)_self; - - if (self->lifetime_remaining == -1) { - error("Song iterator called post mortem"); - return SI_FINISHED; - } - - if (self->lifetime_remaining) { - - if (self->cue_counter < self->cues_nr) { - int time_to_cue = self->cues[self->cue_counter]; - - if (self->cue_progress == time_to_cue) { - ++self->cue_counter; - self->cue_progress = 0; - *result = self->cue_counter; - return SI_ABSOLUTE_CUE; - } else { - int retval = time_to_cue - self->cue_progress; - self->cue_progress = time_to_cue; - - if (retval > self->lifetime_remaining) { - retval = self->lifetime_remaining; - self->lifetime_remaining = 0; - self->cue_progress = retval; - return retval; - } - - self->lifetime_remaining -= retval; - return retval; - } - } else { - int retval = self->lifetime_remaining; - self->lifetime_remaining = 0; - return retval; - } - - } else { - self->lifetime_remaining = -1; - return SI_FINISHED; - } -} - -Audio::AudioStream *simple_it_pcm_feed(SongIterator *_self) { - error("No PCM feed"); - return NULL; -} - -void simple_it_init(SongIterator *_self) { -} - -SongIterator *simple_it_handle_message(SongIterator *_self, SongIterator::Message msg) { - return NULL; -} - -void simple_it_cleanup(SongIterator *_self) { -} - -/* Initialises the simple iterator. -** Parameters: (int) delay: Number of ticks until the iterator finishes -** (int *) cues: An array of cue delays (cue values are [1,2...]) -** (int) cues_nr: Number of cues in ``cues'' -** The first cue is emitted after cues[0] ticks, and it is 1. After cues[1] additional ticks -** the next cue is emitted, and so on. */ -SongIterator *setup_simple_iterator(int delay, char *cues, int cues_nr) { - simple_iterator.lifetime_remaining = delay; - simple_iterator.cues = cues; - simple_iterator.cue_counter = 0; - simple_iterator.cues_nr = cues_nr; - simple_iterator.cue_progress = 0; - - simple_iterator.ID = 42; - simple_iterator.channel_mask = 0x004f; - simple_iterator.flags = 0; - simple_iterator.priority = 1; - - simple_iterator.death_listeners_nr = 0; - - simple_iterator.cleanup = simple_it_cleanup; - simple_iterator.init = simple_it_init; - simple_iterator.handle_message = simple_it_handle_message; - simple_iterator.get_pcm_feed = simple_it_pcm_feed; - simple_iterator.next = simple_it_next; - - return (SongIterator *) &simple_iterator; -} - -#define ASSERT_SIT ASSERT(it == simple_it) -#define ASSERT_FFIT ASSERT(it == ff_it) -#define ASSERT_NEXT(n) ASSERT(songit_next(&it, data, &result, IT_READER_MASK_ALL) == n) -#define ASSERT_RESULT(n) ASSERT(result == n) -#define ASSERT_CUE(n) ASSERT_NEXT(SI_ABSOLUTE_CUE); ASSERT_RESULT(n) - -void test_simple_it() { - SongIterator *it; - SongIterator *simple_it = (SongIterator *) & simple_iterator; - unsigned char data[4]; - int result; - puts("[TEST] simple iterator (test artifact)"); - - it = setup_simple_iterator(42, NULL, 0); - - ASSERT_SIT; - ASSERT_NEXT(42); - ASSERT_SIT; - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - it = setup_simple_iterator(42, "\003\004", 2); - ASSERT_SIT; - ASSERT_NEXT(3); - ASSERT_CUE(1); - ASSERT_SIT; - ASSERT_NEXT(4); - ASSERT_CUE(2); - ASSERT_SIT; -// warning("XXX => %d", songit_next(&it, data, &result, IT_READER_MASK_ALL)); - ASSERT_NEXT(35); - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - puts("[TEST] Test OK."); -} - -void test_fastforward() { - SongIterator *it; - SongIterator *simple_it = (SongIterator *) & simple_iterator; - SongIterator *ff_it; - unsigned char data[4]; - int result; - puts("[TEST] fast-forward iterator"); - - it = setup_simple_iterator(42, NULL, 0); - ff_it = it = new_fast_forward_iterator(it, 0); - ASSERT_FFIT; - ASSERT_NEXT(42); - ASSERT_SIT; /* Must have morphed back */ - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - it = setup_simple_iterator(42, NULL, 0); - ff_it = it = new_fast_forward_iterator(it, 1); - ASSERT_FFIT; - ASSERT_NEXT(41); - /* May or may not have morphed back here */ - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - it = setup_simple_iterator(42, NULL, 0); - ff_it = it = new_fast_forward_iterator(it, 41); - ASSERT_FFIT; - ASSERT_NEXT(1); - /* May or may not have morphed back here */ - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - it = setup_simple_iterator(42, NULL, 0); - ff_it = it = new_fast_forward_iterator(it, 42); - ASSERT_NEXT(SI_FINISHED); - /* May or may not have morphed back here */ - - it = setup_simple_iterator(42, NULL, 0); - ff_it = it = new_fast_forward_iterator(it, 10000); - ASSERT_NEXT(SI_FINISHED); - /* May or may not have morphed back here */ - - it = setup_simple_iterator(42, "\003\004", 2); - ff_it = it = new_fast_forward_iterator(it, 2); - ASSERT_FFIT; - ASSERT_NEXT(1); - ASSERT_CUE(1); - ASSERT_SIT; - ASSERT_NEXT(4); - ASSERT_CUE(2); - ASSERT_SIT; - ASSERT_NEXT(35); - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - it = setup_simple_iterator(42, "\003\004", 2); - ff_it = it = new_fast_forward_iterator(it, 5); - ASSERT_FFIT; - ASSERT_CUE(1); - ASSERT_FFIT; - ASSERT_NEXT(2); - ASSERT_CUE(2); - ASSERT_SIT; - ASSERT_NEXT(35); - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - it = setup_simple_iterator(42, "\003\004", 2); - ff_it = it = new_fast_forward_iterator(it, 41); - ASSERT_FFIT; - ASSERT_CUE(1); - ASSERT_FFIT; - ASSERT_CUE(2); - ASSERT_FFIT; - ASSERT_NEXT(1); - ASSERT_NEXT(SI_FINISHED); - ASSERT_SIT; - - puts("[TEST] Test OK."); -} - -#define SIMPLE_SONG_SIZE 50 - -static unsigned char simple_song[SIMPLE_SONG_SIZE] = { - 0x00, /* Regular song */ - /* Only use channel 0 for all devices */ - 0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* Song begins here */ - 42, 0x90, 60, 0x7f, /* Play C after 42 ticks */ - 02, 64, 0x42, /* Play E after 2 more ticks, using running status mode */ - 0xf8, 10, 0x80, 60, 0x02, /* Stop C after 250 ticks */ - 0, 64, 0x00, /* Stop E immediately */ - 00, 0xfc /* Stop song */ -}; - -#define ASSERT_MIDI3(cmd, arg0, arg1) \ - ASSERT(data[0] == cmd); \ - ASSERT(data[1] == arg0); \ - ASSERT(data[2] == arg1); - -void test_iterator_sci0() { - SongIterator *it = songit_new(simple_song, SIMPLE_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l); - unsigned char data[4]; - int result; - SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */ - - puts("[TEST] SCI0-style song"); - ASSERT_NEXT(42); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 60, 0x7f); - ASSERT_NEXT(2); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 64, 0x42); - ASSERT_NEXT(250); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 60, 0x02); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 64, 0x00); - ASSERT_NEXT(SI_FINISHED); - puts("[TEST] Test OK."); -} - - - -void test_iterator_sci0_loop() { - SongIterator *it = songit_new(simple_song, SIMPLE_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l); - unsigned char data[4]; - int result; - SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */ - SIMSG_SEND(it, SIMSG_SET_LOOPS(2)); /* Loop one additional time */ - - puts("[TEST] SCI0-style song with looping"); - ASSERT_NEXT(42); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 60, 0x7f); - ASSERT_NEXT(2); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 64, 0x42); - ASSERT_NEXT(250); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 60, 0x02); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 64, 0x00); - ASSERT_NEXT(SI_LOOP); - ASSERT_NEXT(42); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 60, 0x7f); - ASSERT_NEXT(2); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 64, 0x42); - ASSERT_NEXT(250); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 60, 0x02); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 64, 0x00); - ASSERT_NEXT(SI_FINISHED); - puts("[TEST] Test OK."); -} - - - -#define LOOP_SONG_SIZE 54 - -unsigned char loop_song[LOOP_SONG_SIZE] = { - 0x00, /* Regular song song */ - /* Only use channel 0 for all devices */ - 0x02, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* Song begins here */ - 42, 0x90, 60, 0x7f, /* Play C after 42 ticks */ - 13, 0x80, 60, 0x00, /* Stop C after 13 ticks */ - 00, 0xCF, 0x7f, /* Set loop point */ - 02, 0x90, 64, 0x42, /* Play E after 2 more ticks, using running status mode */ - 03, 0x80, 64, 0x00, /* Stop E after 3 ticks */ - 00, 0xfc /* Stop song/loop */ -}; - - -void test_iterator_sci0_mark_loop() { - SongIterator *it = songit_new(loop_song, LOOP_SONG_SIZE, SCI_SONG_ITERATOR_TYPE_SCI0, 0l); - unsigned char data[4]; - int result; - SIMSG_SEND(it, SIMSG_SET_PLAYMASK(0x0001)); /* Initialise song, enabling channel 0 */ - SIMSG_SEND(it, SIMSG_SET_LOOPS(3)); /* Loop once more */ - - puts("[TEST] SCI0-style song with loop mark, looping"); - ASSERT_NEXT(42); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 60, 0x7f); - ASSERT_NEXT(13); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 60, 0x00); - /* Loop point here: we don't observe that in the iterator interface yet, though */ - ASSERT_NEXT(2); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 64, 0x42); - ASSERT_NEXT(3); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 64, 0x00); - /* Now we loop back to the loop pont */ - ASSERT_NEXT(SI_LOOP); - ASSERT_NEXT(2); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 64, 0x42); - ASSERT_NEXT(3); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 64, 0x00); - /* ...and one final time */ - ASSERT_NEXT(SI_LOOP); - ASSERT_NEXT(2); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x90, 64, 0x42); - ASSERT_NEXT(3); - ASSERT_NEXT(0); - ASSERT_MIDI3(0x80, 64, 0x00); - - ASSERT_NEXT(SI_FINISHED); - puts("[TEST] Test OK."); -} - - - -int main(int argc, char **argv) { - test_simple_it(); - test_fastforward(); - test_iterator_sci0(); - test_iterator_sci0_loop(); - test_iterator_sci0_mark_loop(); - if (errors != 0) - warning("[ERROR] %d errors total", errors); - return (errors != 0); -} diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp index 3ee8a3a83d..cc09ba79f0 100644 --- a/engines/sci/sound/midiparser_sci.cpp +++ b/engines/sci/sound/midiparser_sci.cpp @@ -43,30 +43,39 @@ enum SciMidiCommands { // MidiParser_SCI // -MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion) : +MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion, SciMusic *music) : MidiParser() { _soundVersion = soundVersion; + _music = music; _mixedData = NULL; // mididata contains delta in 1/60th second // values of ppqn and tempo are found experimentally and may be wrong _ppqn = 1; setTempo(16667); - _volume = 0; + _volume = 127; _signalSet = false; _signalToSet = 0; _dataincAdd = false; _dataincToAdd = 0; _resetOnPause = false; - _channelsUsed = 0; - - for (int i = 0; i < 16; i++) - _channelRemap[i] = i; + _pSnd = 0; } MidiParser_SCI::~MidiParser_SCI() { unloadMusic(); + // we do this, so that MidiParser won't be able to call his own ::allNotesOff() + // this one would affect all channels and we can't let that happen + _driver = 0; +} + +void MidiParser_SCI::mainThreadBegin() { + _mainThreadCalled = true; +} + +void MidiParser_SCI::mainThreadEnd() { + _mainThreadCalled = false; } bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion) { @@ -75,7 +84,14 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in _pSnd = psnd; _soundVersion = soundVersion; - setVolume(psnd->volume); + for (int i = 0; i < 16; i++) { + _channelUsed[i] = false; + _channelRemap[i] = -1; + _channelMuted[i] = false; + _channelVolume[i] = 127; + } + _channelRemap[9] = 9; // never map channel 9, because that's used for percussion + _channelRemap[15] = 15; // never map channel 15, because thats used by sierra internally if (channelFilterMask) { // SCI0 only has 1 data stream, but we need to filter out channels depending on music hardware selection @@ -86,30 +102,272 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in _num_tracks = 1; _tracks[0] = _mixedData; - setTrack(0); + if (_pSnd) + setTrack(0); _loopTick = 0; - if (_soundVersion <= SCI_VERSION_0_LATE) { - // Set initial voice count - for (int i = 0; i < 16; ++i) { - byte voiceCount = 0; - if (channelFilterMask & (1 << i)) - voiceCount = psnd->soundRes->getInitialVoiceCount(i); - _driver->send(0xB0 | i, 0x4B, voiceCount); + return true; +} + +byte MidiParser_SCI::midiGetNextChannel(long ticker) { + byte curr = 0xFF; + long closest = ticker + 1000000, next = 0; + + for (int i = 0; i < _track->channelCount; i++) { + if (_track->channels[i].time == -1) // channel ended + continue; + SoundResource::Channel *curChannel = &_track->channels[i]; + if (curChannel->curPos >= curChannel->size) + continue; + next = curChannel->data[curChannel->curPos]; // when the next event should occur + if (next == 0xF8) // 0xF8 means 240 ticks delay + next = 240; + next += _track->channels[i].time; + if (next < closest) { + curr = i; + closest = next; + } + } + + return curr; +} + +byte *MidiParser_SCI::midiMixChannels() { + int totalSize = 0; + + for (int i = 0; i < _track->channelCount; i++) { + _track->channels[i].time = 0; + _track->channels[i].prev = 0; + _track->channels[i].curPos = 0; + totalSize += _track->channels[i].size; + } + + byte *outData = new byte[totalSize * 2]; // FIXME: creates overhead and still may be not enough to hold all data + _mixedData = outData; + long ticker = 0; + byte channelNr, curDelta; + byte midiCommand = 0, midiParam, global_prev = 0; + long newDelta; + SoundResource::Channel *channel; + + while ((channelNr = midiGetNextChannel(ticker)) != 0xFF) { // there is still an active channel + channel = &_track->channels[channelNr]; + curDelta = channel->data[channel->curPos++]; + channel->time += (curDelta == 0xF8 ? 240 : curDelta); // when the command is supposed to occur + if (curDelta == 0xF8) + continue; + newDelta = channel->time - ticker; + ticker += newDelta; + + midiCommand = channel->data[channel->curPos++]; + if (midiCommand != kEndOfTrack) { + // Write delta + while (newDelta > 240) { + *outData++ = 0xF8; + newDelta -= 240; + } + *outData++ = (byte)newDelta; + } + // Write command + switch (midiCommand) { + case 0xF0: // sysEx + *outData++ = midiCommand; + do { + midiParam = channel->data[channel->curPos++]; + *outData++ = midiParam; + } while (midiParam != 0xF7); + break; + case kEndOfTrack: // end of channel + channel->time = -1; + break; + default: // MIDI command + if (midiCommand & 0x80) { + midiParam = channel->data[channel->curPos++]; + } else {// running status + midiParam = midiCommand; + midiCommand = channel->prev; + } + + // remember which channel got used for channel remapping + byte midiChannel = midiCommand & 0xF; + _channelUsed[midiChannel] = true; + + if (midiCommand != global_prev) + *outData++ = midiCommand; + *outData++ = midiParam; + if (nMidiParams[(midiCommand >> 4) - 8] == 2) + *outData++ = channel->data[channel->curPos++]; + channel->prev = midiCommand; + global_prev = midiCommand; + } + } + + // Insert stop event + *outData++ = 0; // Delta + *outData++ = 0xFF; // Meta event + *outData++ = 0x2F; // End of track (EOT) + *outData++ = 0x00; + *outData++ = 0x00; + return _mixedData; +} + +// This is used for SCI0 sound-data. SCI0 only has one stream that may +// contain several channels and according to output device we remove +// certain channels from that data. +byte *MidiParser_SCI::midiFilterChannels(int channelMask) { + SoundResource::Channel *channel = &_track->channels[0]; + byte *channelData = channel->data; + byte *channelDataEnd = channel->data + channel->size; + byte *outData = new byte[channel->size + 5]; + byte curChannel = 15, curByte, curDelta; + byte command = 0, lastCommand = 0; + int delta = 0; + int midiParamCount = 0; + + _mixedData = outData; + + while (channelData < channelDataEnd) { + curDelta = *channelData++; + if (curDelta == 0xF8) { + delta += 240; + continue; + } + delta += curDelta; + curByte = *channelData++; + + switch (curByte) { + case 0xF0: // sysEx + case kEndOfTrack: // end of channel + command = curByte; + curChannel = 15; + break; + default: + if (curByte & 0x80) { + command = curByte; + curChannel = command & 0x0F; + midiParamCount = nMidiParams[(command >> 4) - 8]; + } + } + if ((1 << curChannel) & channelMask) { + if (command != kEndOfTrack) { + // Write delta + while (delta > 240) { + *outData++ = 0xF8; + delta -= 240; + } + *outData++ = (byte)delta; + delta = 0; + } + // Write command + switch (command) { + case 0xF0: // sysEx + *outData++ = command; + do { + curByte = *channelData++; + *outData++ = curByte; // out + } while (curByte != 0xF7); + lastCommand = command; + break; + + case kEndOfTrack: // end of channel + break; + + default: // MIDI command + // remember which channel got used for channel remapping + byte midiChannel = command & 0xF; + _channelUsed[midiChannel] = true; + + if (lastCommand != command) { + *outData++ = command; + lastCommand = command; + } + if (midiParamCount > 0) { + if (curByte & 0x80) + *outData++ = *channelData++; + else + *outData++ = curByte; + } + if (midiParamCount > 1) { + *outData++ = *channelData++; + } + } + } else { + if (curByte & 0x80) + channelData += midiParamCount; + else + channelData += midiParamCount - 1; + } + } + + // Insert stop event + *outData++ = 0; // Delta + *outData++ = 0xFF; // Meta event + *outData++ = 0x2F; // End of track (EOT) + *outData++ = 0x00; + *outData++ = 0x00; + + return _mixedData; +} + +// This will get called right before actual playing and will try to own the used channels +void MidiParser_SCI::tryToOwnChannels() { + // We don't have SciMusic in case debug command show_instruments is used + if (!_music) + return; + for (int curChannel = 0; curChannel < 15; curChannel++) { + if (_channelUsed[curChannel]) { + if (_channelRemap[curChannel] == -1) { + _channelRemap[curChannel] = _music->tryToOwnChannel(_pSnd, curChannel); + } + } + } +} + +void MidiParser_SCI::lostChannels() { + for (int curChannel = 0; curChannel < 15; curChannel++) + if ((_channelUsed[curChannel]) && (curChannel != 9)) + _channelRemap[curChannel] = -1; +} + +void MidiParser_SCI::sendInitCommands() { + // reset our "global" volume and channel volumes + _volume = 127; + for (int i = 0; i < 16; i++) + _channelVolume[i] = 127; + + // Set initial voice count + if (_pSnd) { + if (_soundVersion <= SCI_VERSION_0_LATE) { + for (int i = 0; i < 15; ++i) { + byte voiceCount = 0; + if (_channelUsed[i]) { + voiceCount = _pSnd->soundRes->getInitialVoiceCount(i); + sendToDriver(0xB0 | i, 0x4B, voiceCount); + } + } } } // Send a velocity off signal to all channels - for (int i = 0; i < 16; ++i) { - _driver->send(0xB0 | i, 0x4E, 0); // Reset velocity + for (int i = 0; i < 15; ++i) { + if (_channelUsed[i]) + sendToDriver(0xB0 | i, 0x4E, 0); // Reset velocity } - return true; + // Center the pitch wheels and hold pedal in preparation for the next piece of music + for (int i = 0; i < 16; ++i) { + if (_channelUsed[i]) { + sendToDriver(0xE0 | i, 0, 0x40); // Reset pitch wheel + sendToDriver(0xB0 | i, 0x40, 0); // Reset hold pedal + } + } } void MidiParser_SCI::unloadMusic() { - resetTracking(); - allNotesOff(); + if (_pSnd) { + resetTracking(); + allNotesOff(); + } _num_tracks = 0; _active_track = 255; _resetOnPause = false; @@ -118,36 +376,76 @@ void MidiParser_SCI::unloadMusic() { delete[] _mixedData; _mixedData = NULL; } +} - // Center the pitch wheels and hold pedal in preparation for the next piece of music - if (_driver) { - for (int i = 0; i < 16; ++i) { - if (isChannelUsed(i)) { - _driver->send(0xE0 | i, 0, 0x40); // Reset pitch wheel - _driver->send(0xB0 | i, 0x40, 0); // Reset hold pedal - } +// this is used for scripts sending midi commands to us. we verify in that case that the channel is actually +// used, so that channel remapping will work as well and then send them on +void MidiParser_SCI::sendFromScriptToDriver(uint32 midi) { + byte midiChannel = midi & 0xf; + + if (!_channelUsed[midiChannel]) { + // trying to send to an unused channel + // this happens for cmdSendMidi at least in sq1vga right at the start, it's a script issue + return; + } + if (_channelRemap[midiChannel] == -1) { + // trying to send to an unmapped channel + // this happens for cmdSendMidi at least in sq1vga right at the start, scripts are pausing the sound + // and then sending manually. it's a script issue + return; + } + sendToDriver(midi); +} + +void MidiParser_SCI::sendToDriver(uint32 midi) { + byte midiChannel = midi & 0xf; + + if ((midi & 0xFFF0) == 0x4EB0) { + // this is channel mute only for sci1 + // it's velocity control for sci0 + if (_soundVersion >= SCI_VERSION_1_EARLY) { + _channelMuted[midiChannel] = midi & 0xFF0000 ? true : false; + return; // don't send this to driver at all } } - for (int i = 0; i < 16; i++) - _channelRemap[i] = i; + // Is channel muted? if so, don't send command + if (_channelMuted[midiChannel]) + return; + + if ((midi & 0xFFF0) == 0x07B0) { + // someone trying to set channel volume? + int channelVolume = (midi >> 16) & 0xFF; + // Remember, if we need to set it ourselves + _channelVolume[midiChannel] = channelVolume; + // Adjust volume accordingly to current "global" volume + channelVolume = channelVolume * _volume / 127; + midi = (midi & 0xFFF0) | ((channelVolume & 0xFF) << 16); + } + + // Channel remapping + int16 realChannel = _channelRemap[midiChannel]; + assert(realChannel != -1); + + midi = (midi & 0xFFFFFFF0) | realChannel; + if (_mainThreadCalled) + _music->putMidiCommandInQueue(midi); + else + _driver->send(midi); } void MidiParser_SCI::parseNextEvent(EventInfo &info) { - // Monitor which channels are used by this song - setChannelUsed(info.channel()); - // Set signal AFTER waiting for delta, otherwise we would set signal too soon resulting in all sorts of bugs if (_dataincAdd) { _dataincAdd = false; _pSnd->dataInc += _dataincToAdd; _pSnd->signal = 0x7f + _pSnd->dataInc; - debugC(2, kDebugLevelSound, "datainc %04x", _dataincToAdd); + debugC(4, kDebugLevelSound, "datainc %04x", _dataincToAdd); } if (_signalSet) { _signalSet = false; _pSnd->signal = _signalToSet; - debugC(2, kDebugLevelSound, "signal %04x", _signalToSet); + debugC(4, kDebugLevelSound, "signal %04x", _signalToSet); } info.start = _position._play_pos; @@ -173,10 +471,16 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { info.basic.param2 = 0; if (info.channel() == 0xF) {// SCI special case if (info.basic.param1 != kSetSignalLoop) { - _signalSet = true; - _signalToSet = info.basic.param1; + // at least in kq5/french&mac the first scene in the intro has a song that sets signal to 4 immediately + // on tick 0. Signal isn't set at that point by sierra sci and it would cause the castle daventry text to + // get immediately removed, so we currently filter it. + // Sierra SCI ignores them as well at that time + if (_position._play_tick) { + _signalSet = true; + _signalToSet = info.basic.param1; + } } else { - _loopTick = _position._play_tick; + _loopTick = _position._play_tick + info.delta; } } break; @@ -235,7 +539,6 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { case 0x0A: // pan case 0x0B: // expression case 0x40: // sustain - case 0x4E: // velocity control case 0x79: // reset all case 0x7B: // notes off // These are all handled by the music driver, so ignore them @@ -249,8 +552,6 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { break; } } - if (info.basic.param1 == 7) // channel volume change -scale it - info.basic.param2 = info.basic.param2 * _volume / MUSIC_VOLUME_MAX; info.length = 0; break; @@ -307,7 +608,7 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { _pSnd->status = kSoundStopped; _pSnd->signal = SIGNAL_OFFSET; - debugC(2, kDebugLevelSound, "signal EOT"); + debugC(4, kDebugLevelSound, "signal EOT"); } } break; @@ -319,254 +620,66 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { }// switch (info.command()) } +void MidiParser_SCI::allNotesOff() { + if (!_driver) + return; -byte MidiParser_SCI::midiGetNextChannel(long ticker) { - byte curr = 0xFF; - long closest = ticker + 1000000, next = 0; - - for (int i = 0; i < _track->channelCount; i++) { - if (_track->channels[i].time == -1) // channel ended - continue; - next = *_track->channels[i].data; // when the next event should occur - if (next == 0xF8) // 0xF8 means 240 ticks delay - next = 240; - next += _track->channels[i].time; - if (next < closest) { - curr = i; - closest = next; - } - } - - return curr; -} - -byte *MidiParser_SCI::midiMixChannels() { - int totalSize = 0; - byte **dataPtr = new byte *[_track->channelCount]; - - for (int i = 0; i < _track->channelCount; i++) { - dataPtr[i] = _track->channels[i].data; - _track->channels[i].time = 0; - _track->channels[i].prev = 0; - totalSize += _track->channels[i].size; - } - - byte *outData = new byte[totalSize * 2]; // FIXME: creates overhead and still may be not enough to hold all data - _mixedData = outData; - long ticker = 0; - byte curr, curDelta; - byte command = 0, par1, global_prev = 0; - long new_delta; - SoundResource::Channel *channel; - - while ((curr = midiGetNextChannel(ticker)) != 0xFF) { // there is still active channel - channel = &_track->channels[curr]; - curDelta = *channel->data++; - channel->time += (curDelta == 0xF8 ? 240 : curDelta); // when the comamnd is supposed to occur - if (curDelta == 0xF8) - continue; - new_delta = channel->time - ticker; - ticker += new_delta; - - command = *channel->data++; - if (command != kEndOfTrack) { - debugC(2, kDebugLevelSound, "\nDELTA "); - // Write delta - while (new_delta > 240) { - *outData++ = 0xF8; - debugC(2, kDebugLevelSound, "F8 "); - new_delta -= 240; - } - *outData++ = (byte)new_delta; - debugC(2, kDebugLevelSound, "%02X ", (uint32)new_delta); - } - // Write command - switch (command) { - case 0xF0: // sysEx - *outData++ = command; - debugC(2, kDebugLevelSound, "%02X ", command); - do { - par1 = *channel->data++; - *outData++ = par1; // out - } while (par1 != 0xF7); - break; - case kEndOfTrack: // end of channel - channel->time = -1; // FIXME - break; - default: // MIDI command - if (command & 0x80) { - par1 = *channel->data++; - - // TODO: Fix remapping + int i, j; -#if 0 - // Remap channel. Keep the upper 4 bits (command code) and change - // the lower 4 bits (channel) - byte remappedChannel = _channelRemap[par1 & 0xF]; - par1 = (par1 & 0xF0) | (remappedChannel & 0xF); -#endif - } else {// running status - par1 = command; - command = channel->prev; + // 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)){ + sendToDriver(0x80 | j, i, 0); } - if (command != global_prev) - *outData++ = command; // out command - *outData++ = par1;// pout par1 - if (nMidiParams[(command >> 4) - 8] == 2) - *outData++ = *channel->data++; // out par2 - channel->prev = command; - global_prev = command; - }// switch(command) - }// while (curr) - - // Insert stop event - *outData++ = 0; // Delta - *outData++ = 0xFF; // Meta event - *outData++ = 0x2F; // End of track (EOT) - *outData++ = 0x00; - *outData++ = 0x00; - - for (int channelNr = 0; channelNr < _track->channelCount; channelNr++) - _track->channels[channelNr].data = dataPtr[channelNr]; - - delete[] dataPtr; - return _mixedData; -} - -// This is used for SCI0 sound-data. SCI0 only has one stream that may -// contain several channels and according to output device we remove -// certain channels from that data. -byte *MidiParser_SCI::midiFilterChannels(int channelMask) { - SoundResource::Channel *channel = &_track->channels[0]; - byte *channelData = channel->data; - byte *channelDataEnd = channel->data + channel->size; - byte *outData = new byte[channel->size + 5]; - byte curChannel = 15, curByte, curDelta; - byte command = 0, lastCommand = 0; - int delta = 0; - int midiParamCount = 0; - - _mixedData = outData; - - while (channelData < channelDataEnd) { - curDelta = *channelData++; - if (curDelta == 0xF8) { - delta += 240; - continue; } - delta += curDelta; - curByte = *channelData++; + } - switch (curByte) { - case 0xF0: // sysEx - case kEndOfTrack: // end of channel - command = curByte; - curChannel = 15; - break; - default: - if (curByte & 0x80) { - command = curByte; - curChannel = command & 0x0F; - midiParamCount = nMidiParams[(command >> 4) - 8]; - } + // 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; } - if ((1 << curChannel) & channelMask) { - if (command != kEndOfTrack) { - debugC(2, kDebugLevelSound, "\nDELTA "); - // Write delta - while (delta > 240) { - *outData++ = 0xF8; - debugC(2, kDebugLevelSound, "F8 "); - delta -= 240; - } - *outData++ = (byte)delta; - debugC(2, kDebugLevelSound, "%02X ", delta); - delta = 0; - } - // Write command - switch (command) { - case 0xF0: // sysEx - *outData++ = command; - debugC(2, kDebugLevelSound, "%02X ", command); - do { - curByte = *channelData++; - *outData++ = curByte; // out - } while (curByte != 0xF7); - lastCommand = command; - break; + } + _hanging_notes_count = 0; - case kEndOfTrack: // end of channel - break; + // To be sure, send an "All Note Off" event (but not all MIDI devices + // support this...). - default: // MIDI command - if (lastCommand != command) { - *outData++ = command; - debugC(2, kDebugLevelSound, "%02X ", command); - lastCommand = command; - } - if (midiParamCount > 0) { - if (curByte & 0x80) { - debugC(2, kDebugLevelSound, "%02X ", *channelData); - *outData++ = *channelData++; - } else { - debugC(2, kDebugLevelSound, "%02X ", curByte); - *outData++ = curByte; - } - } - if (midiParamCount > 1) { - debugC(2, kDebugLevelSound, "%02X ", *channelData); - *outData++ = *channelData++; - } - } - } else { - if (curByte & 0x80) { - channelData += midiParamCount; - } else { - channelData += midiParamCount - 1; - } - } + for (i = 0; i < 16; ++i) { + if (_channelRemap[i] != -1) + sendToDriver(0xB0 | i, 0x7b, 0); // All notes off } - // Insert stop event - *outData++ = 0; // Delta - *outData++ = 0xFF; // Meta event - *outData++ = 0x2F; // End of track (EOT) - *outData++ = 0x00; - *outData++ = 0x00; - - return _mixedData; + memset(_active_notes, 0, sizeof(_active_notes)); } void MidiParser_SCI::setVolume(byte volume) { - // FIXME: This receives values > 127... throw a warning for now and clip the variable - if (volume > MUSIC_VOLUME_MAX) { - warning("attempted to set an invalid volume(%d)", volume); - volume = MUSIC_VOLUME_MAX; // reset - } - assert(volume <= MUSIC_VOLUME_MAX); - if (_volume != volume) { - _volume = volume; - - switch (_soundVersion) { - case SCI_VERSION_0_EARLY: - case SCI_VERSION_0_LATE: { - int16 globalVolume = _volume * 15 / 127; - ((MidiPlayer *)_driver)->setVolume(globalVolume); - break; - } + _volume = volume; + + switch (_soundVersion) { + case SCI_VERSION_0_EARLY: + case SCI_VERSION_0_LATE: { + // SCI0 adlib driver doesn't support channel volumes, so we need to go this way + // TODO: this should take the actual master volume into account + int16 globalVolume = _volume * 15 / 127; + ((MidiPlayer *)_driver)->setVolume(globalVolume); + break; + } - case SCI_VERSION_1_EARLY: - case SCI_VERSION_1_LATE: - // sending volume change to all active channels - for (int i = 0; i < _track->channelCount; i++) - if (_track->channels[i].number <= 0xF) - _driver->send(0xB0 + _track->channels[i].number, 7, _volume); - break; + case SCI_VERSION_1_EARLY: + case SCI_VERSION_1_LATE: + // Send previous channel volumes again to actually update the volume + for (int i = 0; i < 15; i++) + if (_channelRemap[i] != -1) + sendToDriver(0xB0 + i, 7, _channelVolume[i]); + break; - default: - error("MidiParser_SCI::setVolume: Unsupported soundVersion"); - } + default: + error("MidiParser_SCI::setVolume: Unsupported soundVersion"); } } diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h index 9d4b5a39da..90db06e539 100644 --- a/engines/sci/sound/midiparser_sci.h +++ b/engines/sci/sound/midiparser_sci.h @@ -53,12 +53,17 @@ namespace Sci { */ class MidiParser_SCI : public MidiParser { public: - MidiParser_SCI(SciVersion soundVersion); + MidiParser_SCI(SciVersion soundVersion, SciMusic *music); ~MidiParser_SCI(); + + void mainThreadBegin(); + void mainThreadEnd(); + bool loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion); bool loadMusic(byte *, uint32) { return false; } + void sendInitCommands(); void unloadMusic(); void setVolume(byte volume); void stop() { @@ -71,23 +76,29 @@ public: jumpToTick(0); } - void remapChannel(byte channel, byte newChannel) { - assert(channel < 0xF); // don't touch special SCI channel 15 - assert(newChannel < 0xF); // don't touch special SCI channel 15 - _channelRemap[channel] = newChannel; - } + void allNotesOff(); - void clearUsedChannels() { _channelsUsed = 0; } + const byte *getMixedData() const { return _mixedData; } -protected: - bool isChannelUsed(byte channel) const { return _channelsUsed & (1 << channel); } - void setChannelUsed(byte channel) { _channelsUsed |= (1 << channel); } + void tryToOwnChannels(); + void lostChannels(); + void sendFromScriptToDriver(uint32 midi); + void sendToDriver(uint32 midi); + void sendToDriver(byte status, byte firstOp, byte secondOp) { + sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); + } +protected: void parseNextEvent(EventInfo &info); byte *midiMixChannels(); byte *midiFilterChannels(int channelMask); byte midiGetNextChannel(long ticker); + SciMusic *_music; + + // this is set, when main thread calls us -> we send commands to queue instead to driver + bool _mainThreadCalled; + SciVersion _soundVersion; byte *_mixedData; SoundResource::Track *_track; @@ -101,11 +112,10 @@ protected: int16 _dataincToAdd; bool _resetOnPause; - // A 16-bit mask, containing the channels used - // by the currently parsed song - uint16 _channelsUsed; - - byte _channelRemap[16]; + bool _channelUsed[16]; + int16 _channelRemap[16]; + bool _channelMuted[16]; + byte _channelVolume[16]; }; } // End of namespace Sci diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index fa5716e7cc..55a7e1fdc4 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -43,6 +43,11 @@ SciMusic::SciMusic(SciVersion soundVersion) // Reserve some space in the playlist, to avoid expensive insertion // operations _playList.reserve(10); + + for (int i = 0; i < 16; i++) + _usedChannel[i] = 0; + + _queuedCommands.reserve(1000); } SciMusic::~SciMusic() { @@ -58,31 +63,30 @@ void SciMusic::init() { // SCI sound init _dwTempo = 0; - MidiDriverType midiType; - // Default to MIDI in SCI32 games, as many don't have AdLib support. - // WORKAROUND: Default to MIDI in Amiga SCI1_EGA+ games as we don't support those patches yet. - // We also don't yet support the 7.pat file of SCI1+ Mac games or SCI0 Mac patches, so we - // default to MIDI in those games to let them run. + // WORKAROUND: Default to MIDI in Amiga SCI1_EGA+ games as we don't support + // those patches yet. We also don't yet support the 7.pat file of SCI1+ Mac + // games or SCI0 Mac patches, so we default to MIDI in those games to let + // them run. Common::Platform platform = g_sci->getPlatform(); - - if (getSciVersion() >= SCI_VERSION_2 || platform == Common::kPlatformMacintosh || (platform == Common::kPlatformAmiga && getSciVersion() >= SCI_VERSION_1_EGA)) - midiType = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MIDI); - else - midiType = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB | MDT_MIDI); - - switch (midiType) { - case MD_ADLIB: + uint32 dev = MidiDriver::detectDevice( + (getSciVersion() >= SCI_VERSION_2 || platform == Common::kPlatformMacintosh || + (platform == Common::kPlatformAmiga && getSciVersion() >= SCI_VERSION_1_EGA)) + ? (MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM) + : (MDT_PCSPK | MDT_ADLIB | MDT_MIDI)); + + switch (MidiDriver::getMusicType(dev)) { + case MT_ADLIB: // FIXME: There's no Amiga sound option, so we hook it up to AdLib if (g_sci->getPlatform() == Common::kPlatformAmiga) _pMidiDrv = MidiPlayer_Amiga_create(_soundVersion); else _pMidiDrv = MidiPlayer_AdLib_create(_soundVersion); break; - case MD_PCJR: + case MT_PCJR: _pMidiDrv = MidiPlayer_PCJr_create(_soundVersion); break; - case MD_PCSPK: + case MT_PCSPK: _pMidiDrv = MidiPlayer_PCSpeaker_create(_soundVersion); break; default: @@ -100,6 +104,49 @@ void SciMusic::init() { } _bMultiMidi = ConfMan.getBool("multi_midi"); + + // Find out what the first possible channel is (used, when doing channel + // remapping). + _driverFirstChannel = _pMidiDrv->getFirstChannel(); +} + +void SciMusic::miditimerCallback(void *p) { + SciMusic *sciMusic = (SciMusic *)p; + + Common::StackLock lock(sciMusic->_mutex); + sciMusic->onTimer(); +} + +void SciMusic::onTimer() { + const MusicList::iterator end = _playList.end(); + // sending out queued commands that were "sent" via main thread + sendMidiCommandsFromQueue(); + + for (MusicList::iterator i = _playList.begin(); i != end; ++i) + (*i)->onTimer(); +} + +void SciMusic::putMidiCommandInQueue(byte status, byte firstOp, byte secondOp) { + putMidiCommandInQueue(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); +} + +void SciMusic::putMidiCommandInQueue(uint32 midi) { + _queuedCommands.push_back(midi); +} + +// This sends the stored commands from queue to driver (is supposed to get +// called only during onTimer()). At least mt32 emulation doesn't like getting +// note-on commands from main thread (if we directly send, we would get a crash +// during piano scene in lsl5). +void SciMusic::sendMidiCommandsFromQueue() { + uint curCommand = 0; + uint commandCount = _queuedCommands.size(); + + while (curCommand < commandCount) { + _pMidiDrv->send(_queuedCommands[curCommand]); + curCommand++; + } + _queuedCommands.clear(); } void SciMusic::clearPlayList() { @@ -119,22 +166,12 @@ void SciMusic::pauseAll(bool pause) { } void SciMusic::stopAll() { - Common::StackLock lock(_mutex); - const MusicList::iterator end = _playList.end(); for (MusicList::iterator i = _playList.begin(); i != end; ++i) { soundStop(*i); } } - -void SciMusic::miditimerCallback(void *p) { - SciMusic *aud = (SciMusic *)p; - - Common::StackLock lock(aud->_mutex); - aud->onTimer(); -} - void SciMusic::soundSetSoundOn(bool soundOnFlag) { Common::StackLock lock(_mutex); @@ -160,6 +197,24 @@ MusicEntry *SciMusic::getSlot(reg_t obj) { return NULL; } +// We return the currently active music slot for SCI0 +MusicEntry *SciMusic::getActiveSci0MusicSlot() { + const MusicList::iterator end = _playList.end(); + MusicEntry *highestPrioritySlot = NULL; + for (MusicList::iterator i = _playList.begin(); i != end; ++i) { + MusicEntry *playSlot = *i; + if (playSlot->pMidiParser) { + if (playSlot->status == kSoundPlaying) + return playSlot; + if (playSlot->status == kSoundPaused) { + if ((!highestPrioritySlot) || (highestPrioritySlot->priority < playSlot->priority)) + highestPrioritySlot = playSlot; + } + } + } + return highestPrioritySlot; +} + void SciMusic::setReverb(byte reverb) { Common::StackLock lock(_mutex); _pMidiDrv->setReverb(reverb); @@ -174,28 +229,14 @@ void SciMusic::sortPlayList() { Common::sort(_playList.begin(), _playList.end(), musicEntryCompare); } -void SciMusic::findUsedChannels() { - // Reset list - for (int k = 0; k < 16; k++) - _usedChannels[k] = false; - - const MusicList::const_iterator end = _playList.end(); - for (MusicList::const_iterator i = _playList.begin(); i != end; ++i) { - for (int channel = 0; channel < 16; channel++) { - if ((*i)->soundRes && (*i)->soundRes->isChannelUsed(channel)) - _usedChannels[channel] = true; - } - } -} - void SciMusic::soundInitSnd(MusicEntry *pSnd) { int channelFilterMask = 0; SoundResource::Track *track = pSnd->soundRes->getTrackByType(_pMidiDrv->getPlayId()); - // If MIDI device is selected but there is no digital track in sound resource - // try to use adlib's digital sample if possible - // Also, if the track couldn't be found, load the digital track, as some games - // depend on this (e.g. the Longbow demo) + // If MIDI device is selected but there is no digital track in sound + // resource try to use adlib's digital sample if possible. Also, if the + // track couldn't be found, load the digital track, as some games depend on + // this (e.g. the Longbow demo). if (!track || (_bMultiMidi && track->digitalChannelNr == -1)) { SoundResource::Track *digital = pSnd->soundRes->getDigitalTrack(); if (digital) @@ -224,49 +265,53 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) { _mutex.lock(); pSnd->soundType = Audio::Mixer::kMusicSoundType; if (pSnd->pMidiParser == NULL) { - pSnd->pMidiParser = new MidiParser_SCI(_soundVersion); + pSnd->pMidiParser = new MidiParser_SCI(_soundVersion, this); pSnd->pMidiParser->setMidiDriver(_pMidiDrv); pSnd->pMidiParser->setTimerRate(_dwTempo); } pSnd->pauseCounter = 0; - // TODO: Fix channel remapping. This doesn't quite work... (e.g. no difference in LSL1VGA) -#if 0 - // Remap channels - findUsedChannels(); - - pSnd->pMidiParser->clearUsedChannels(); - - for (int i = 0; i < 16; i++) { - if (_usedChannels[i] && pSnd->soundRes->isChannelUsed(i)) { - int16 newChannel = getNextUnusedChannel(); - if (newChannel >= 0) { - _usedChannels[newChannel] = true; - debug("Remapping channel %d to %d\n", i, newChannel); - pSnd->pMidiParser->remapChannel(i, newChannel); - } else { - warning("Attempt to remap channel %d, but no unused channels exist", i); - } - } - } -#endif - // Find out what channels to filter for SCI0 channelFilterMask = pSnd->soundRes->getChannelFilterMask(_pMidiDrv->getPlayId(), _pMidiDrv->hasRhythmChannel()); - pSnd->pMidiParser->loadMusic(track, pSnd, channelFilterMask, _soundVersion); - // Fast forward to the last position and perform associated events when loading - pSnd->pMidiParser->jumpToTick(pSnd->ticker, true); + pSnd->pMidiParser->mainThreadBegin(); + pSnd->pMidiParser->loadMusic(track, pSnd, channelFilterMask, _soundVersion); + pSnd->pMidiParser->mainThreadEnd(); _mutex.unlock(); } } } -void SciMusic::onTimer() { - const MusicList::iterator end = _playList.end(); - for (MusicList::iterator i = _playList.begin(); i != end; ++i) - (*i)->onTimer(); +// This one checks, if requested channel is available -> in that case give +// caller that channel. Otherwise look for an unused one +int16 SciMusic::tryToOwnChannel(MusicEntry *caller, int16 bestChannel) { + // Don't even try this for SCI0 + if (_soundVersion <= SCI_VERSION_0_LATE) + return bestChannel; + if (!_usedChannel[bestChannel]) { + // currently unused, so give it to caller directly + _usedChannel[bestChannel] = caller; + return bestChannel; + } + // otherwise look for unused channel + for (int channelNr = _driverFirstChannel; channelNr < 15; channelNr++) { + if (!_usedChannel[channelNr]) { + _usedChannel[channelNr] = caller; + return channelNr; + } + } + error("no free channels"); +} + +void SciMusic::freeChannels(MusicEntry *caller) { + // Remove used channels + for (int i = 0; i < 15; i++) { + if (_usedChannel[i] == caller) + _usedChannel[i] = 0; + } + // Also tell midiparser, that he lost ownership + caller->pMidiParser->lostChannels(); } void SciMusic::soundPlay(MusicEntry *pSnd) { @@ -294,7 +339,7 @@ void SciMusic::soundPlay(MusicEntry *pSnd) { if ((_soundVersion <= SCI_VERSION_0_LATE) && (alreadyPlaying)) { // Music already playing in SCI0? if (pSnd->priority > alreadyPlaying->priority) { - // And new priority higher? pause previous music and play new one immediately + // And new priority higher? pause previous music and play new one immediately. // Example of such case: lsl3, when getting points (jingle is played then) soundPause(alreadyPlaying); alreadyPlaying->isQueued = true; @@ -317,34 +362,54 @@ void SciMusic::soundPlay(MusicEntry *pSnd) { pSnd->pLoopStream, -1, pSnd->volume, 0, DisposeAfterUse::NO); } else { + // Rewind in case we play the same sample multiple times + // (non-looped) like in pharkas right at the start + pSnd->pStreamAud->rewind(); _pMixer->playStream(pSnd->soundType, &pSnd->hCurrentAud, pSnd->pStreamAud, -1, pSnd->volume, 0, DisposeAfterUse::NO); } } else { - _mutex.lock(); if (pSnd->pMidiParser) { - pSnd->pMidiParser->setVolume(pSnd->volume); + _mutex.lock(); + pSnd->pMidiParser->mainThreadBegin(); + pSnd->pMidiParser->tryToOwnChannels(); if (pSnd->status == kSoundStopped) + pSnd->pMidiParser->sendInitCommands(); + pSnd->pMidiParser->setVolume(pSnd->volume); + if (pSnd->status == kSoundStopped) { pSnd->pMidiParser->jumpToTick(0); + } else { + // Fast forward to the last position and perform associated events when loading + pSnd->pMidiParser->jumpToTick(pSnd->ticker, true); + } + pSnd->pMidiParser->mainThreadEnd(); + _mutex.unlock(); } - _mutex.unlock(); } pSnd->status = kSoundPlaying; } void SciMusic::soundStop(MusicEntry *pSnd) { + SoundStatus previousStatus = pSnd->status; pSnd->status = kSoundStopped; if (_soundVersion <= SCI_VERSION_0_LATE) pSnd->isQueued = false; if (pSnd->pStreamAud) _pMixer->stopHandle(pSnd->hCurrentAud); - _mutex.lock(); - if (pSnd->pMidiParser) - pSnd->pMidiParser->stop(); - _mutex.unlock(); + if (pSnd->pMidiParser) { + _mutex.lock(); + pSnd->pMidiParser->mainThreadBegin(); + // We shouldn't call stop in case it's paused, otherwise we would send + // allNotesOff() again + if (previousStatus == kSoundPlaying) + pSnd->pMidiParser->stop(); + freeChannels(pSnd); + pSnd->pMidiParser->mainThreadEnd(); + _mutex.unlock(); + } } void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) { @@ -353,7 +418,9 @@ void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) { _pMixer->setChannelVolume(pSnd->hCurrentAud, volume * 2); // Mixer is 0-255, SCI is 0-127 } else if (pSnd->pMidiParser) { _mutex.lock(); + pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->setVolume(volume); + pSnd->pMidiParser->mainThreadEnd(); _mutex.unlock(); } } @@ -368,13 +435,15 @@ void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) { void SciMusic::soundKill(MusicEntry *pSnd) { pSnd->status = kSoundStopped; - _mutex.lock(); if (pSnd->pMidiParser) { + _mutex.lock(); + pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->unloadMusic(); + pSnd->pMidiParser->mainThreadEnd(); delete pSnd->pMidiParser; pSnd->pMidiParser = NULL; + _mutex.unlock(); } - _mutex.unlock(); if (pSnd->pStreamAud) { _pMixer->stopHandle(pSnd->hCurrentAud); @@ -406,10 +475,14 @@ void SciMusic::soundPause(MusicEntry *pSnd) { if (pSnd->pStreamAud) { _pMixer->pauseHandle(pSnd->hCurrentAud, true); } else { - _mutex.lock(); - if (pSnd->pMidiParser) + if (pSnd->pMidiParser) { + _mutex.lock(); + pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->pause(); - _mutex.unlock(); + freeChannels(pSnd); + pSnd->pMidiParser->mainThreadEnd(); + _mutex.unlock(); + } } } @@ -448,6 +521,21 @@ void SciMusic::soundSetMasterVolume(uint16 vol) { _pMidiDrv->setVolume(vol); } +void SciMusic::sendMidiCommand(uint32 cmd) { + Common::StackLock lock(_mutex); + _pMidiDrv->send(cmd); +} + +void SciMusic::sendMidiCommand(MusicEntry *pSnd, uint32 cmd) { + Common::StackLock lock(_mutex); + if (!pSnd->pMidiParser) + error("tried to cmdSendMidi on non midi slot (%04x:%04x)", PRINT_REG(pSnd->soundObj)); + + pSnd->pMidiParser->mainThreadBegin(); + pSnd->pMidiParser->sendFromScriptToDriver(cmd); + pSnd->pMidiParser->mainThreadEnd(); +} + void SciMusic::printPlayList(Console *con) { Common::StackLock lock(_mutex); @@ -567,14 +655,6 @@ void MusicEntry::doFade() { fadeStep = 0; fadeCompleted = true; } -#ifdef ENABLE_SCI32 - // Disable fading for SCI32 - sound drivers have issues when fading in (gabriel knight 1 sierra title) - if (getSciVersion() >= SCI_VERSION_2) { - volume = fadeTo; - fadeStep = 0; - fadeCompleted = true; - } -#endif // Only process MIDI streams in this thread, not digital sound effects if (pMidiParser) { diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h index 83cd59e89b..943a5bd2a8 100644 --- a/engines/sci/sound/music.h +++ b/engines/sci/sound/music.h @@ -26,15 +26,11 @@ #ifndef SCI_MUSIC_H #define SCI_MUSIC_H -#ifndef USE_OLD_MUSIC_FUNCTIONS #include "common/serializer.h" -#endif #include "common/mutex.h" #include "sound/mixer.h" #include "sound/audiostream.h" -//#include "sound/mididrv.h" -//#include "sound/midiparser.h" #include "sci/sci.h" #include "sci/resource.h" @@ -55,11 +51,7 @@ enum SoundStatus { class MidiParser_SCI; class SegManager; -class MusicEntry -#ifndef USE_OLD_MUSIC_FUNCTIONS - : public Common::Serializable -#endif -{ +class MusicEntry : public Common::Serializable { public: // Do not get these directly for the sound objects! // It's a bad idea, as the sound code (i.e. the SciMusic @@ -96,9 +88,6 @@ public: Audio::Mixer::SoundType soundType; -#ifndef USE_OLD_MUSIC_FUNCTIONS -//protected: -#endif MidiParser_SCI *pMidiParser; // TODO: We need to revise how we store the different @@ -114,25 +103,28 @@ public: void doFade(); void onTimer(); -#ifndef USE_OLD_MUSIC_FUNCTIONS virtual void saveLoadWithSerializer(Common::Serializer &ser); -#endif }; typedef Common::Array<MusicEntry *> MusicList; +typedef Common::Array<uint32> MidiCommandQueue; -class SciMusic -#ifndef USE_OLD_MUSIC_FUNCTIONS - : public Common::Serializable -#endif -{ +class SciMusic : public Common::Serializable { public: SciMusic(SciVersion soundVersion); ~SciMusic(); void init(); + void onTimer(); + void putMidiCommandInQueue(byte status, byte firstOp, byte secondOp); + void putMidiCommandInQueue(uint32 midi); +private: + static void miditimerCallback(void *p); + void sendMidiCommandsFromQueue(); + +public: void clearPlayList(); void pauseAll(bool pause); void stopAll(); @@ -165,6 +157,7 @@ public: } MusicEntry *getSlot(reg_t obj); + MusicEntry *getActiveSci0MusicSlot(); void pushBackSlot(MusicEntry *slotEntry) { Common::StackLock lock(_mutex); @@ -179,16 +172,12 @@ public: MusicList::iterator getPlayListStart() { return _playList.begin(); } MusicList::iterator getPlayListEnd() { return _playList.end(); } - void sendMidiCommand(uint32 cmd) { - Common::StackLock lock(_mutex); - _pMidiDrv->send(cmd); - } + void sendMidiCommand(uint32 cmd); + void sendMidiCommand(MusicEntry *pSnd, uint32 cmd); void setReverb(byte reverb); -#ifndef USE_OLD_MUSIC_FUNCTIONS virtual void saveLoadWithSerializer(Common::Serializer &ser); -#endif // Mutex for music code. Used to guard access to the song playlist, to the // MIDI parser and to the MIDI driver/player. Note that guarded code must NOT @@ -196,6 +185,9 @@ public: // where a deadlock can occur Common::Mutex _mutex; + int16 tryToOwnChannel(MusicEntry *caller, int16 bestChannel); + void freeChannels(MusicEntry *caller); + protected: void sortPlayList(); @@ -208,22 +200,16 @@ protected: // Mixed AdLib/MIDI mode: when enabled from the ScummVM sound options screen, // and a sound has a digital track, the sound from the AdLib track is played bool _bMultiMidi; -private: - static void miditimerCallback(void *p); - void findUsedChannels(); - int16 getNextUnusedChannel() const { - for (int i = 0; i < 16; i++) { - if (!_usedChannels[i]) - return i; - } - - return -1; - } +private: MusicList _playList; bool _soundOn; byte _masterVolume; - bool _usedChannels[16]; + MusicEntry *_usedChannel[16]; + + MidiCommandQueue _queuedCommands; + + int _driverFirstChannel; }; } // End of namespace Sci diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index ece4c1430c..51832af09f 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -23,17 +23,12 @@ * */ -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS - -#ifdef USE_OLD_MUSIC_FUNCTIONS -#include "sci/sound/iterator/iterator.h" // for SongIteratorStatus -#endif - #include "common/config-manager.h" #include "sci/sound/audio.h" #include "sci/sound/music.h" #include "sci/sound/soundcmd.h" +#include "sci/engine/kernel.h" #include "sci/engine/selector.h" namespace Sci { @@ -41,258 +36,29 @@ namespace Sci { #define SCI1_SOUND_FLAG_MAY_PAUSE 1 /* Only here for completeness; The interpreter doesn't touch this bit */ #define SCI1_SOUND_FLAG_SCRIPTED_PRI 2 /* but does touch this */ -#ifdef USE_OLD_MUSIC_FUNCTIONS -#define FROBNICATE_HANDLE(reg) ((reg).segment << 16 | (reg).offset) -#define DEFROBNICATE_HANDLE(handle) (make_reg((handle >> 16) & 0xffff, handle & 0xffff)) -#endif - -#define SOUNDCOMMAND(x) _soundCommands.push_back(new MusicEntryCommand(#x, &SoundCommandParser::x)) - -#ifdef USE_OLD_MUSIC_FUNCTIONS -static void script_set_priority(ResourceManager *resMan, SegManager *segMan, SfxState *state, reg_t obj, int priority) { - int song_nr = readSelectorValue(segMan, obj, SELECTOR(number)); - Resource *song = resMan->findResource(ResourceId(kResourceTypeSound, song_nr), 0); - int flags = readSelectorValue(segMan, obj, SELECTOR(flags)); - - if (priority == -1) { - if (song->data[0] == 0xf0) - priority = song->data[1]; - else - warning("Attempt to unset song priority when there is no built-in value"); - - flags &= ~SCI1_SOUND_FLAG_SCRIPTED_PRI; - } else flags |= SCI1_SOUND_FLAG_SCRIPTED_PRI; - - state->sfx_song_renice(FROBNICATE_HANDLE(obj), priority); - writeSelectorValue(segMan, obj, SELECTOR(flags), flags); -} - -SongIterator *build_iterator(ResourceManager *resMan, int song_nr, SongIteratorType type, songit_id_t id) { - Resource *song = resMan->findResource(ResourceId(kResourceTypeSound, song_nr), 0); - - if (!song) - return NULL; - - return songit_new(song->data, song->size, type, id); -} - -void process_sound_events(EngineState *s) { /* Get all sound events, apply their changes to the heap */ - int result; - SongHandle handle; - int cue; - SegManager *segMan = s->_segMan; - - if (getSciVersion() > SCI_VERSION_01) - return; - // SCI1 and later explicitly poll for everything - - while ((result = s->_sound.sfx_poll(&handle, &cue))) { - reg_t obj = DEFROBNICATE_HANDLE(handle); - if (!s->_segMan->isObject(obj)) { - warning("Non-object %04x:%04x received sound signal (%d/%d)", PRINT_REG(obj), result, cue); - return; - } - - switch (result) { - - case SI_LOOP: - debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x looped (to %d)", - PRINT_REG(obj), cue); - /* writeSelectorValue(segMan, obj, SELECTOR(loops), readSelectorValue(segMan, obj, SELECTOR(loop));; - 1);*/ - writeSelectorValue(segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - break; - - case SI_RELATIVE_CUE: - debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x received relative cue %d", - PRINT_REG(obj), cue); - writeSelectorValue(segMan, obj, SELECTOR(signal), cue + 0x7f); - break; - - case SI_ABSOLUTE_CUE: - debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x received absolute cue %d", - PRINT_REG(obj), cue); - writeSelectorValue(segMan, obj, SELECTOR(signal), cue); - break; - - case SI_FINISHED: - debugC(2, kDebugLevelSound, "[process-sound] Song %04x:%04x finished", - PRINT_REG(obj)); - writeSelectorValue(segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - writeSelectorValue(segMan, obj, SELECTOR(state), kSoundStopped); - break; - - default: - warning("Unexpected result from sfx_poll: %d", result); - break; - } - } -} - -#endif SoundCommandParser::SoundCommandParser(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, AudioPlayer *audio, SciVersion soundVersion) : _resMan(resMan), _segMan(segMan), _kernel(kernel), _audio(audio), _soundVersion(soundVersion) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - // The following hack is needed to ease the change from old to new sound code (because the new sound code does not use SfxState) - _state = &g_sci->getEngineState()->_sound; // HACK -#endif - - #ifndef USE_OLD_MUSIC_FUNCTIONS - _music = new SciMusic(_soundVersion); - _music->init(); - #endif - - switch (_soundVersion) { - case SCI_VERSION_0_EARLY: - case SCI_VERSION_0_LATE: - SOUNDCOMMAND(cmdInitSound); - SOUNDCOMMAND(cmdPlaySound); - SOUNDCOMMAND(cmdDummy); - SOUNDCOMMAND(cmdDisposeSound); - SOUNDCOMMAND(cmdMuteSound); - SOUNDCOMMAND(cmdStopSound); - SOUNDCOMMAND(cmdPauseSound); - SOUNDCOMMAND(cmdResumeSound); - SOUNDCOMMAND(cmdMasterVolume); - SOUNDCOMMAND(cmdUpdateSound); - SOUNDCOMMAND(cmdFadeSound); - SOUNDCOMMAND(cmdGetPolyphony); - SOUNDCOMMAND(cmdStopAllSounds); - _cmdUpdateCuesIndex = -1; - break; - case SCI_VERSION_1_EARLY: - SOUNDCOMMAND(cmdMasterVolume); - SOUNDCOMMAND(cmdMuteSound); - SOUNDCOMMAND(cmdDummy); - SOUNDCOMMAND(cmdGetPolyphony); - SOUNDCOMMAND(cmdUpdateSound); - SOUNDCOMMAND(cmdInitSound); - SOUNDCOMMAND(cmdDisposeSound); - SOUNDCOMMAND(cmdPlaySound); - SOUNDCOMMAND(cmdStopSound); - SOUNDCOMMAND(cmdPauseSound); - SOUNDCOMMAND(cmdFadeSound); - SOUNDCOMMAND(cmdUpdateCues); - SOUNDCOMMAND(cmdSendMidi); - SOUNDCOMMAND(cmdReverb); - SOUNDCOMMAND(cmdSetSoundHold); - _cmdUpdateCuesIndex = 11; - break; - case SCI_VERSION_1_LATE: - SOUNDCOMMAND(cmdMasterVolume); - SOUNDCOMMAND(cmdMuteSound); - SOUNDCOMMAND(cmdDummy); - SOUNDCOMMAND(cmdGetPolyphony); - SOUNDCOMMAND(cmdGetAudioCapability); - SOUNDCOMMAND(cmdSuspendSound); - SOUNDCOMMAND(cmdInitSound); - SOUNDCOMMAND(cmdDisposeSound); - SOUNDCOMMAND(cmdPlaySound); - SOUNDCOMMAND(cmdStopSound); - SOUNDCOMMAND(cmdPauseSound); - SOUNDCOMMAND(cmdFadeSound); - SOUNDCOMMAND(cmdSetSoundHold); - SOUNDCOMMAND(cmdDummy); - SOUNDCOMMAND(cmdSetSoundVolume); - SOUNDCOMMAND(cmdSetSoundPriority); - SOUNDCOMMAND(cmdSetSoundLoop); - SOUNDCOMMAND(cmdUpdateCues); - SOUNDCOMMAND(cmdSendMidi); - SOUNDCOMMAND(cmdReverb); - SOUNDCOMMAND(cmdUpdateSound); - _cmdUpdateCuesIndex = 17; - break; - default: - warning("Sound command parser: unknown sound version %d", _soundVersion); - break; - } + _music = new SciMusic(_soundVersion); + _music->init(); } SoundCommandParser::~SoundCommandParser() { - for (SoundCommandContainer::iterator i = _soundCommands.begin(); i != _soundCommands.end(); ++i) - delete *i; - -#ifndef USE_OLD_MUSIC_FUNCTIONS delete _music; -#endif } -reg_t SoundCommandParser::parseCommand(int argc, reg_t *argv, reg_t acc) { - uint16 command = argv[0].toUint16(); - reg_t obj = (argc > 1) ? argv[1] : NULL_REG; - int16 value = (argc > 2) ? argv[2].toSint16() : 0; - _acc = acc; - _argc = argc; - _argv = argv; - - if (argc == 6) { // cmdSendMidi - byte channel = argv[2].toUint16() & 0xf; - byte midiCmd = argv[3].toUint16() & 0xff; - - uint16 controller = argv[4].toUint16(); - uint16 param = argv[5].toUint16(); - - _midiCommand = (channel | midiCmd) | ((uint32)controller << 8) | ((uint32)param << 16); - } - - if (command < _soundCommands.size()) { - if (command != _cmdUpdateCuesIndex) { - //printf("%s, object %04x:%04x\n", _soundCommands[command]->desc, PRINT_REG(obj)); // debug - debugC(2, kDebugLevelSound, "%s, object %04x:%04x", _soundCommands[command]->desc, PRINT_REG(obj)); - } - - (this->*(_soundCommands[command]->sndCmd))(obj, value); - } else { - warning("Invalid sound command requested (%d), valid range is 0-%d", command, _soundCommands.size() - 1); - } - - return _acc; +reg_t SoundCommandParser::kDoSoundInit(int argc, reg_t *argv, reg_t acc) { + processInitSound(argv[0]); + return acc; } -void SoundCommandParser::cmdInitSound(reg_t obj, int16 value) { - if (!obj.segment) - return; - +void SoundCommandParser::processInitSound(reg_t obj) { int resourceId = readSelectorValue(_segMan, obj, SELECTOR(number)); -#ifdef USE_OLD_MUSIC_FUNCTIONS - - SongHandle handle = FROBNICATE_HANDLE(obj); - - if (_soundVersion != SCI_VERSION_1_LATE) { - if (!obj.segment) - return; - } - - SongIteratorType type = (_soundVersion <= SCI_VERSION_0_LATE) ? SCI_SONG_ITERATOR_TYPE_SCI0 : SCI_SONG_ITERATOR_TYPE_SCI1; - - if (_soundVersion <= SCI_VERSION_0_LATE) { - if (readSelectorValue(_segMan, obj, SELECTOR(nodePtr))) { - _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - _state->sfx_remove_song(handle); - } - } - - if (!obj.segment || !_resMan->testResource(ResourceId(kResourceTypeSound, resourceId))) - return; - - _state->sfx_add_song(build_iterator(_resMan, resourceId, type, handle), 0, handle, resourceId); - - - // Notify the engine - if (_soundVersion <= SCI_VERSION_0_LATE) - writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundInitialized); - else - writeSelector(_segMan, obj, SELECTOR(nodePtr), obj); - - writeSelector(_segMan, obj, SELECTOR(handle), obj); - -#else - // Check if a track with the same sound object is already playing MusicEntry *oldSound = _music->getSlot(obj); if (oldSound) - cmdDisposeSound(obj, value); + processDisposeSound(obj); MusicEntry *newSound = new MusicEntry(); newSound->resourceId = resourceId; @@ -307,6 +73,9 @@ void SoundCommandParser::cmdInitSound(reg_t obj, int16 value) { if (_soundVersion >= SCI_VERSION_1_EARLY) newSound->volume = CLIP<int>(readSelectorValue(_segMan, obj, SELECTOR(vol)), 0, MUSIC_VOLUME_MAX); + debugC(2, kDebugLevelSound, "kDoSound(init): number %d, loop %d, prio %d, vol %d", resourceId, + newSound->loop, newSound->priority, newSound->volume); + // In SCI1.1 games, sound effects are started from here. If we can find // a relevant audio resource, play it, otherwise switch to synthesized // effects. If the resource exists, play it using map 65535 (sound @@ -333,98 +102,28 @@ void SoundCommandParser::cmdInitSound(reg_t obj, int16 value) { writeSelector(_segMan, obj, SELECTOR(handle), obj); } -#endif - } -void SoundCommandParser::cmdPlaySound(reg_t obj, int16 value) { - if (!obj.segment) - return; - -#ifdef USE_OLD_MUSIC_FUNCTIONS - SongHandle handle = FROBNICATE_HANDLE(obj); - - if (_soundVersion <= SCI_VERSION_0_LATE) { - _state->sfx_song_set_status(handle, SOUND_STATUS_PLAYING); - _state->sfx_song_set_loops(handle, readSelectorValue(_segMan, obj, SELECTOR(loop))); - writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundPlaying); - } else if (_soundVersion == SCI_VERSION_1_EARLY) { - _state->sfx_song_set_status(handle, SOUND_STATUS_PLAYING); - _state->sfx_song_set_loops(handle, readSelectorValue(_segMan, obj, SELECTOR(loop))); - _state->sfx_song_renice(handle, readSelectorValue(_segMan, obj, SELECTOR(pri))); - RESTORE_BEHAVIOR rb = (RESTORE_BEHAVIOR) value; /* Too lazy to look up a default value for this */ - _state->_songlib.setSongRestoreBehavior(handle, rb); - writeSelectorValue(_segMan, obj, SELECTOR(signal), 0); - } else if (_soundVersion == SCI_VERSION_1_LATE) { - int looping = readSelectorValue(_segMan, obj, SELECTOR(loop)); - //int vol = readSelectorValue(_segMan, obj, SELECTOR(vol)); - int pri = readSelectorValue(_segMan, obj, SELECTOR(pri)); - int sampleLen = 0; - Song *song = _state->_songlib.findSong(handle); - int songNumber = readSelectorValue(_segMan, obj, SELECTOR(number)); - - if (readSelectorValue(_segMan, obj, SELECTOR(nodePtr)) && (song && songNumber != song->_resourceNum)) { - _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - _state->sfx_remove_song(handle); - writeSelector(_segMan, obj, SELECTOR(nodePtr), NULL_REG); - } - - if (!readSelectorValue(_segMan, obj, SELECTOR(nodePtr)) && obj.segment) { - // In SCI1.1 games, sound effects are started from here. If we can find - // a relevant audio resource, play it, otherwise switch to synthesized - // effects. If the resource exists, play it using map 65535 (sound - // effects map) - if (_resMan->testResource(ResourceId(kResourceTypeAudio, songNumber)) && - getSciVersion() >= SCI_VERSION_1_1) { - // Found a relevant audio resource, play it - _audio->stopAudio(); - warning("Initializing audio resource instead of requested sound resource %d", songNumber); - sampleLen = _audio->startAudio(65535, songNumber); - // Also create iterator, that will fire SI_FINISHED event, when the sound is done playing - _state->sfx_add_song(new_timer_iterator(sampleLen), 0, handle, songNumber); - } else { - if (!_resMan->testResource(ResourceId(kResourceTypeSound, songNumber))) { - warning("Could not open song number %d", songNumber); - // Send a "stop handle" event so that the engine won't wait forever here - _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - return; - } - debugC(2, kDebugLevelSound, "Initializing song number %d", songNumber); - _state->sfx_add_song(build_iterator(_resMan, songNumber, SCI_SONG_ITERATOR_TYPE_SCI1, - handle), 0, handle, songNumber); - } - - writeSelector(_segMan, obj, SELECTOR(nodePtr), obj); - writeSelector(_segMan, obj, SELECTOR(handle), obj); - } - - if (obj.segment) { - _state->sfx_song_set_status(handle, SOUND_STATUS_PLAYING); - _state->sfx_song_set_loops(handle, looping); - _state->sfx_song_renice(handle, pri); - writeSelectorValue(_segMan, obj, SELECTOR(signal), 0); - } - } - -#else +reg_t SoundCommandParser::kDoSoundPlay(int argc, reg_t *argv, reg_t acc) { + processPlaySound(argv[0]); + return acc; +} +void SoundCommandParser::processPlaySound(reg_t obj) { MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdPlaySound: Slot not found (%04x:%04x)", PRINT_REG(obj)); + warning("kDoSound(play): Slot not found (%04x:%04x)", PRINT_REG(obj)); return; } int resourceId = obj.segment ? readSelectorValue(_segMan, obj, SELECTOR(number)) : -1; if (musicSlot->resourceId != resourceId) { // another sound loaded into struct - cmdDisposeSound(obj, value); - cmdInitSound(obj, value); + processDisposeSound(obj); + processInitSound(obj); // Find slot again :) musicSlot = _music->getSlot(obj); } - int16 loop = readSelectorValue(_segMan, obj, SELECTOR(loop)); - debugC(2, kDebugLevelSound, "cmdPlaySound: resource number %d, loop %d", resourceId, loop); writeSelector(_segMan, obj, SELECTOR(handle), obj); @@ -442,51 +141,31 @@ void SoundCommandParser::cmdPlaySound(reg_t obj, int16 value) { musicSlot->priority = readSelectorValue(_segMan, obj, SELECTOR(priority)); if (_soundVersion >= SCI_VERSION_1_EARLY) musicSlot->volume = readSelectorValue(_segMan, obj, SELECTOR(vol)); - _music->soundPlay(musicSlot); -#endif + debugC(2, kDebugLevelSound, "kDoSound(play): number %d, loop %d, prio %d, vol %d", resourceId, + musicSlot->loop, musicSlot->priority, musicSlot->volume); + _music->soundPlay(musicSlot); } -void SoundCommandParser::cmdDummy(reg_t obj, int16 value) { +reg_t SoundCommandParser::kDoSoundDummy(int argc, reg_t *argv, reg_t acc) { warning("cmdDummy invoked"); // not supposed to occur + return acc; } -#ifdef USE_OLD_MUSIC_FUNCTIONS -void SoundCommandParser::changeSoundStatus(reg_t obj, int newStatus) { - SongHandle handle = FROBNICATE_HANDLE(obj); - if (obj.segment) { - _state->sfx_song_set_status(handle, newStatus); - if (_soundVersion <= SCI_VERSION_0_LATE) - writeSelectorValue(_segMan, obj, SELECTOR(state), newStatus); - } +reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) { + processDisposeSound(argv[0]); + return acc; } -#endif - -void SoundCommandParser::cmdDisposeSound(reg_t obj, int16 value) { - if (!obj.segment) - return; - -#ifdef USE_OLD_MUSIC_FUNCTIONS - SongHandle handle = FROBNICATE_HANDLE(obj); - changeSoundStatus(obj, SOUND_STATUS_STOPPED); - - if (obj.segment) { - _state->sfx_remove_song(handle); - - if (_soundVersion <= SCI_VERSION_0_LATE) - writeSelectorValue(_segMan, obj, SELECTOR(handle), 0x0000); - } - -#else +void SoundCommandParser::processDisposeSound(reg_t obj) { MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdDisposeSound: Slot not found (%04x:%04x)", PRINT_REG(obj)); + warning("kDoSound(dispose): Slot not found (%04x:%04x)", PRINT_REG(obj)); return; } - cmdStopSound(obj, value); + processStopSound(obj, false); _music->soundKill(musicSlot); writeSelectorValue(_segMan, obj, SELECTOR(handle), 0); @@ -494,26 +173,17 @@ void SoundCommandParser::cmdDisposeSound(reg_t obj, int16 value) { writeSelector(_segMan, obj, SELECTOR(nodePtr), NULL_REG); else writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundStopped); -#endif } -void SoundCommandParser::cmdStopSound(reg_t obj, int16 value) { - processStopSound(obj, value, false); +reg_t SoundCommandParser::kDoSoundStop(int argc, reg_t *argv, reg_t acc) { + processStopSound(argv[0], false); + return acc; } -void SoundCommandParser::processStopSound(reg_t obj, int16 value, bool sampleFinishedPlaying) { - if (!obj.segment) - return; - -#ifdef USE_OLD_MUSIC_FUNCTIONS - changeSoundStatus(obj, SOUND_STATUS_STOPPED); - - if (_soundVersion >= SCI_VERSION_1_EARLY) - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); -#else +void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying) { MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdStopSound: Slot not found (%04x:%04x)", PRINT_REG(obj)); + warning("kDoSound(stop): Slot not found (%04x:%04x)", PRINT_REG(obj)); return; } @@ -523,204 +193,156 @@ void SoundCommandParser::processStopSound(reg_t obj, int16 value, bool sampleFin writeSelectorValue(_segMan, obj, SELECTOR(handle), 0); } - // Set signal selector in sound SCI0 games only, when the sample has finished playing - // If we don't set it at all, we get a problem when using vaporizer on the 2 guys - // If we set it all the time, we get no music in sq3new and kq1 - // FIXME: this *may* be wrong, it's impossible to find out in sierra DOS sci, because SCI0 under DOS didn't have - // sfx drivers included - // We need to set signal in sound SCI1+ games all the time + // Set signal selector in sound SCI0 games only, when the sample has + // finished playing. If we don't set it at all, we get a problem when using + // vaporizer on the 2 guys. If we set it all the time, we get no music in + // sq3new and kq1. + // FIXME: This *may* be wrong, it's impossible to find out in Sierra DOS + // SCI, because SCI0 under DOS didn't have sfx drivers included. + // We need to set signal in sound SCI1+ games all the time. if ((_soundVersion > SCI_VERSION_0_LATE) || sampleFinishedPlaying) writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); musicSlot->dataInc = 0; musicSlot->signal = 0; _music->soundStop(musicSlot); -#endif } -void SoundCommandParser::cmdPauseSound(reg_t obj, int16 value) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - if (!obj.segment) - return; - - if (_soundVersion <= SCI_VERSION_0_LATE) - changeSoundStatus(obj, SOUND_STATUS_SUSPENDED); - else - changeSoundStatus(obj, value ? SOUND_STATUS_SUSPENDED : SOUND_STATUS_PLAYING); -#else +reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) { + if (_soundVersion <= SCI_VERSION_0_LATE) { + // SCI0 games give us 0/1 for either resuming or pausing the current music + // this one doesn't count, so pausing 2 times and resuming once means here that we are supposed to resume + uint16 value = argv[0].toUint16(); + MusicEntry *musicSlot = _music->getActiveSci0MusicSlot(); + switch (value) { + case 1: + if ((musicSlot) && (musicSlot->status == kSoundPlaying)) { + _music->soundPause(musicSlot); + writeSelectorValue(_segMan, musicSlot->soundObj, SELECTOR(state), kSoundPaused); + } + return make_reg(0, 0); + case 0: + if ((musicSlot) && (musicSlot->status == kSoundPaused)) { + _music->soundResume(musicSlot); + writeSelectorValue(_segMan, musicSlot->soundObj, SELECTOR(state), kSoundPlaying); + return make_reg(0, 1); + } + return make_reg(0, 0); + default: + error("kDoSound(pause): parameter 0 is invalid for sound-sci0"); + } + } + reg_t obj = argv[0]; + uint16 value = argc > 1 ? argv[1].toUint16() : 0; if (!obj.segment) { // pause the whole playlist - // Pausing/Resuming the whole playlist was introduced - // in the SCI1 late sound scheme - if (_soundVersion <= SCI_VERSION_1_EARLY) - return; - _music->pauseAll(value); } else { // pause a playlist slot MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdPauseSound: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + warning("kDoSound(pause): Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; } - if (_soundVersion <= SCI_VERSION_0_LATE) { - // Always pause the sound in SCI0 games. It's resumed in cmdResumeSound() - writeSelectorValue(_segMan, musicSlot->soundObj, SELECTOR(state), kSoundPaused); - _music->soundPause(musicSlot); - } else { - _music->soundToggle(musicSlot, value); - } + _music->soundToggle(musicSlot, value); } - -#endif + return acc; } -void SoundCommandParser::cmdResumeSound(reg_t obj, int16 value) { - // SCI0 only command - - if (!obj.segment) - return; +// SCI0 only command +reg_t SoundCommandParser::kDoSoundResume(int argc, reg_t *argv, reg_t acc) { + // this doesn't seem to do what we think it's doing + // it's called with no arguments at all (just restore a game in qfg1) + return acc; + reg_t obj = argv[0]; -#ifdef USE_OLD_MUSIC_FUNCTIONS - changeSoundStatus(obj, SOUND_STATUS_PLAYING); -#else MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdResumeSound: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + warning("kDoSound(resume):: Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; } writeSelectorValue(_segMan, musicSlot->soundObj, SELECTOR(state), kSoundPlaying); _music->soundResume(musicSlot); -#endif + return acc; } -void SoundCommandParser::cmdMuteSound(reg_t obj, int16 value) { -#ifndef USE_OLD_MUSIC_FUNCTIONS - if (_argc > 1) // the first parameter is the sound command - _music->soundSetSoundOn(obj.toUint16()); - _acc = make_reg(0, _music->soundGetSoundOn()); -#endif +reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) { + if (argc > 0) + _music->soundSetSoundOn(argv[0].toUint16()); + return make_reg(0, _music->soundGetSoundOn()); } -void SoundCommandParser::cmdMasterVolume(reg_t obj, int16 value) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - _acc = make_reg(0, _state->sfx_getVolume()); - - if (obj != SIGNAL_REG) - _state->sfx_setVolume(obj.toSint16()); -#else - debugC(2, kDebugLevelSound, "cmdMasterVolume: %d", value); - _acc = make_reg(0, _music->soundGetMasterVolume()); +reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc) { + acc = make_reg(0, _music->soundGetMasterVolume()); - if (_argc > 1) { // the first parameter is the sound command - int vol = CLIP<int16>(obj.toSint16(), 0, kMaxSciVolume); + if (argc > 0) { + debugC(2, kDebugLevelSound, "kDoSound(masterVolume): %d", argv[0].toSint16()); + int vol = CLIP<int16>(argv[0].toSint16(), 0, kMaxSciVolume); vol = vol * Audio::Mixer::kMaxMixerVolume / kMaxSciVolume; ConfMan.setInt("music_volume", vol); ConfMan.setInt("sfx_volume", vol); g_engine->syncSoundSettings(); } -#endif + return acc; } -void SoundCommandParser::cmdFadeSound(reg_t obj, int16 value) { - if (!obj.segment) - return; +reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; -#ifdef USE_OLD_MUSIC_FUNCTIONS - SongHandle handle = FROBNICATE_HANDLE(obj); - if (_soundVersion != SCI_VERSION_1_LATE) { - /* FIXME: The next couple of lines actually STOP the handle, rather - ** than fading it! */ - _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - if (_soundVersion <= SCI_VERSION_0_LATE) - writeSelectorValue(_segMan, obj, SELECTOR(state), SOUND_STATUS_STOPPED); - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - } else { - fade_params_t fade; - fade.final_volume = _argv[2].toUint16(); - fade.ticks_per_step = _argv[3].toUint16(); - fade.step_size = _argv[4].toUint16(); - fade.action = _argv[5].toUint16() ? - FADE_ACTION_FADE_AND_STOP : - FADE_ACTION_FADE_AND_CONT; - - _state->sfx_song_set_fade(handle, &fade); - - /* FIXME: The next couple of lines actually STOP the handle, rather - ** than fading it! */ - if (_argv[5].toUint16()) { - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - _state->sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - } else { - // FIXME: Support fade-and-continue. For now, send signal right away. - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - } - } -#else MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdFadeSound: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + warning("kDoSound(fade): Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; } int volume = musicSlot->volume; - switch (_argc) { - case 2: // SCI0 - // SCI0 fades out all the time and when fadeout is done it will also stop the music from playing + // If sound is not playing currently, set signal directly + if (musicSlot->status != kSoundPlaying) { + debugC(2, kDebugLevelSound, "kDoSound(fade): fading requested, but sound is currently not playing"); + writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); + return acc; + } + + switch (argc) { + case 1: // SCI0 + // SCI0 fades out all the time and when fadeout is done it will also + // stop the music from playing musicSlot->fadeTo = 0; musicSlot->fadeStep = -5; musicSlot->fadeTickerStep = 10 * 16667 / _music->soundGetTempo(); musicSlot->fadeTicker = 0; break; - case 5: // SCI01+ - case 6: // SCI1+ (SCI1 late sound scheme), with fade and continue - musicSlot->fadeTo = CLIP<uint16>(_argv[2].toUint16(), 0, MUSIC_VOLUME_MAX); - musicSlot->fadeStep = volume > _argv[2].toUint16() ? -_argv[4].toUint16() : _argv[4].toUint16(); - musicSlot->fadeTickerStep = _argv[3].toUint16() * 16667 / _music->soundGetTempo(); + 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); + musicSlot->fadeStep = volume > argv[1].toUint16() ? -argv[3].toUint16() : argv[3].toUint16(); + musicSlot->fadeTickerStep = argv[2].toUint16() * 16667 / _music->soundGetTempo(); musicSlot->fadeTicker = 0; - musicSlot->stopAfterFading = (_argc == 6) ? (_argv[5].toUint16() != 0) : false; + musicSlot->stopAfterFading = (argc == 5) ? (argv[4].toUint16() != 0) : false; break; default: - error("cmdFadeSound: unsupported argc %d", _argc); - } - - // If sound is not playing currently, set signal directly - if (musicSlot->status != kSoundPlaying) { - warning("cmdFadeSound: fading requested, but sound is currently not playing"); - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); + error("kDoSound(fade): unsupported argc %d", argc); } - debugC(2, kDebugLevelSound, "cmdFadeSound: to %d, step %d, ticker %d", musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep); -#endif + debugC(2, kDebugLevelSound, "kDoSound(fade): to %d, step %d, ticker %d", musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep); + return acc; } -void SoundCommandParser::cmdGetPolyphony(reg_t obj, int16 value) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - _acc = make_reg(0, _state->sfx_get_player_polyphony()); -#else - _acc = make_reg(0, _music->soundGetVoices()); // Get the number of voices -#endif +reg_t SoundCommandParser::kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc) { + return make_reg(0, _music->soundGetVoices()); // Get the number of voices } -void SoundCommandParser::cmdUpdateSound(reg_t obj, int16 value) { - if (!obj.segment) - return; +reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; -#ifdef USE_OLD_MUSIC_FUNCTIONS - SongHandle handle = FROBNICATE_HANDLE(obj); - if (_soundVersion <= SCI_VERSION_0_LATE && obj.segment) { - _state->sfx_song_set_loops(handle, readSelectorValue(_segMan, obj, SELECTOR(loop))); - script_set_priority(_resMan, _segMan, _state, obj, readSelectorValue(_segMan, obj, SELECTOR(pri))); - } -#else MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdUpdateSound: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + warning("kDoSound(update): Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; } musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop)); @@ -730,88 +352,18 @@ void SoundCommandParser::cmdUpdateSound(reg_t obj, int16 value) { uint32 objPrio = readSelectorValue(_segMan, obj, SELECTOR(pri)); if (objPrio != musicSlot->priority) _music->soundSetPriority(musicSlot, objPrio); - -#endif + return acc; } -void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) { - if (!obj.segment) - return; - -#ifdef USE_OLD_MUSIC_FUNCTIONS - int signal = 0; - int min = 0; - int sec = 0; - int frame = 0; - int result = SI_LOOP; // small hack - SongHandle handle = FROBNICATE_HANDLE(obj); - - while (result == SI_LOOP) - result = _state->sfx_poll_specific(handle, &signal); - - switch (result) { - case SI_ABSOLUTE_CUE: - debugC(2, kDebugLevelSound, "--- [CUE] %04x:%04x Absolute Cue: %d", - PRINT_REG(obj), signal); - debugC(2, kDebugLevelSound, "abs-signal %04X", signal); - writeSelectorValue(_segMan, obj, SELECTOR(signal), signal); - break; - - case SI_RELATIVE_CUE: - debugC(2, kDebugLevelSound, "--- [CUE] %04x:%04x Relative Cue: %d", - PRINT_REG(obj), signal); - - /* FIXME to match commented-out semantics - * below, with proper storage of dataInc and - * signal in the iterator code. */ - writeSelectorValue(_segMan, obj, SELECTOR(dataInc), signal); - debugC(2, kDebugLevelSound, "rel-signal %04X", signal); - if (_soundVersion == SCI_VERSION_1_EARLY) - writeSelectorValue(_segMan, obj, SELECTOR(signal), signal); - else - writeSelectorValue(_segMan, obj, SELECTOR(signal), signal + 127); - break; - - case SI_FINISHED: - debugC(2, kDebugLevelSound, "--- [FINISHED] %04x:%04x", PRINT_REG(obj)); - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - break; - - case SI_LOOP: - break; // Doesn't happen - } +reg_t SoundCommandParser::kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc) { + processUpdateCues(argv[0]); + return acc; +} - //switch (signal) { - //case 0x00: - // if (dataInc != readSelectorValue(segMan, obj, SELECTOR(dataInc))) { - // writeSelectorValue(segMan, obj, SELECTOR(dataInc), dataInc); - // writeSelectorValue(segMan, obj, SELECTOR(signal), dataInc+0x7f); - // } else { - // writeSelectorValue(segMan, obj, SELECTOR(signal), signal); - // } - // break; - //case 0xFF: // May be unnecessary - // s->_sound.sfx_song_set_status(handle, SOUND_STATUS_STOPPED); - // break; - //default : - // if (dataInc != readSelectorValue(segMan, obj, SELECTOR(dataInc))) { - // writeSelectorValue(segMan, obj, SELECTOR(dataInc), dataInc); - // writeSelectorValue(segMan, obj, SELECTOR(signal), dataInc + 0x7f); - // } else { - // writeSelectorValue(segMan, obj, SELECTOR(signal), signal); - // } - // break; - //} - - if (_soundVersion == SCI_VERSION_1_EARLY) { - writeSelectorValue(_segMan, obj, SELECTOR(min), min); - writeSelectorValue(_segMan, obj, SELECTOR(sec), sec); - writeSelectorValue(_segMan, obj, SELECTOR(frame), frame); - } -#else +void SoundCommandParser::processUpdateCues(reg_t obj) { MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdUpdateCues: Slot not found (%04x:%04x)", PRINT_REG(obj)); + warning("kDoSound(updateCues): Slot not found (%04x:%04x)", PRINT_REG(obj)); return; } @@ -828,7 +380,7 @@ void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) { musicSlot->sampleLoopCounter = currentLoopCounter; } if ((!_music->soundIsActive(musicSlot)) && (musicSlot->status != kSoundPaused)) { - processStopSound(obj, 0, true); + processStopSound(obj, true); } else { _music->updateAudioStreamTicker(musicSlot); } @@ -841,7 +393,7 @@ void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) { // Update MIDI slots if (musicSlot->signal == 0) { if (musicSlot->dataInc != readSelectorValue(_segMan, obj, SELECTOR(dataInc))) { - if (_kernel->_selectorCache.dataInc > -1) + if (SELECTOR(dataInc) > -1) writeSelectorValue(_segMan, obj, SELECTOR(dataInc), musicSlot->dataInc); writeSelectorValue(_segMan, obj, SELECTOR(signal), musicSlot->dataInc + 127); } @@ -850,13 +402,15 @@ void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) { writeSelectorValue(_segMan, obj, SELECTOR(signal), musicSlot->signal); // We need to do this especially because state selector needs to get updated if (musicSlot->signal == SIGNAL_OFFSET) - cmdStopSound(obj, 0); + processStopSound(obj, false); } } else { - // Slot actually has no data (which would mean that a sound-resource w/ unsupported data is used + // Slot actually has no data (which would mean that a sound-resource w/ + // unsupported data is used. // (example lsl5 - sound resource 744 - it's roland exclusive writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - // If we don't set signal here, at least the switch to the mud wrestling room in lsl5 will not work + // If we don't set signal here, at least the switch to the mud wrestling + // room in lsl5 will not work. } if (musicSlot->fadeCompleted) { @@ -864,10 +418,10 @@ void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) { // We need signal for sci0 at least in iceman as well (room 14, fireworks) writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); if (_soundVersion <= SCI_VERSION_0_LATE) { - cmdStopSound(obj, 0); + processStopSound(obj, false); } else { if (musicSlot->stopAfterFading) - cmdStopSound(obj, 0); + processStopSound(obj, false); } } @@ -882,48 +436,58 @@ void SoundCommandParser::cmdUpdateCues(reg_t obj, int16 value) { writeSelectorValue(_segMan, obj, SELECTOR(sec), musicSlot->ticker % 3600 / 60); writeSelectorValue(_segMan, obj, SELECTOR(frame), musicSlot->ticker); } - -#endif } -void SoundCommandParser::cmdSendMidi(reg_t obj, int16 value) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - //SongHandle handle = FROBNICATE_HANDLE(obj); - //_state->sfx_send_midi(handle, value, _midiCmd, _controller, _param); -#else - _music->sendMidiCommand(_midiCommand); -#endif +reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; + byte channel = argv[1].toUint16() & 0xf; + byte midiCmd = argv[2].toUint16() & 0xff; + + uint16 controller = argv[3].toUint16(); + uint16 param = argv[4].toUint16(); + + if (channel) + channel--; // channel is given 1-based, we are using 0-based + + uint32 midiCommand = (channel | midiCmd) | ((uint32)controller << 8) | ((uint32)param << 16); + + MusicEntry *musicSlot = _music->getSlot(obj); + if (!musicSlot) { + // TODO: maybe it's possible to call this with obj == 0:0 and send directly?! + // if so, allow it + //_music->sendMidiCommand(_midiCommand); + warning("kDoSound(sendMidi): Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; + } + _music->sendMidiCommand(musicSlot, midiCommand); + return acc; } -void SoundCommandParser::cmdReverb(reg_t obj, int16 value) { -#ifndef USE_OLD_MUSIC_FUNCTIONS - _music->setReverb(obj.toUint16() & 0xF); -#endif +reg_t SoundCommandParser::kDoSoundReverb(int argc, reg_t *argv, reg_t acc) { + _music->setReverb(argv[0].toUint16() & 0xF); + return acc; } -void SoundCommandParser::cmdSetSoundHold(reg_t obj, int16 value) { -#ifdef USE_OLD_MUSIC_FUNCTIONS - SongHandle handle = FROBNICATE_HANDLE(obj); - _state->sfx_song_set_hold(handle, value); -#else +reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; + MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdSetSoundHold: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + warning("kDoSound(setHold): Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; } // Set the special hold marker ID where the song should be looped at. - musicSlot->hold = value; -#endif + musicSlot->hold = argv[1].toSint16(); + return acc; } -void SoundCommandParser::cmdGetAudioCapability(reg_t obj, int16 value) { +reg_t SoundCommandParser::kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc) { // Tests for digital audio support - _acc = make_reg(0, 1); + return make_reg(0, 1); } -void SoundCommandParser::cmdStopAllSounds(reg_t obj, int16 value) { -#ifndef USE_OLD_MUSIC_FUNCTIONS +reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) { Common::StackLock(_music->_mutex); const MusicList::iterator end = _music->getPlayListEnd(); @@ -931,32 +495,31 @@ void SoundCommandParser::cmdStopAllSounds(reg_t obj, int16 value) { if (_soundVersion <= SCI_VERSION_0_LATE) { writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(state), kSoundStopped); } else { - writeSelectorValue(_segMan, obj, SELECTOR(handle), 0); + writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(handle), 0); writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(signal), SIGNAL_OFFSET); } (*i)->dataInc = 0; _music->soundStop(*i); } -#endif + return acc; } -void SoundCommandParser::cmdSetSoundVolume(reg_t obj, int16 value) { - if (!obj.segment) - return; +reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; + int16 value = argv[1].toSint16(); -#ifndef USE_OLD_MUSIC_FUNCTIONS MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { // Do not throw a warning if the sound can't be found, as in some games - // this is called before the actual sound is loaded (e.g. SQ4CD, with the - // drum sounds of the energizer bunny at the beginning), so this is normal - // behavior + // this is called before the actual sound is loaded (e.g. SQ4CD, with + // the drum sounds of the energizer bunny at the beginning), so this is + // normal behavior. //warning("cmdSetSoundVolume: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + return acc; } - debugC(2, kDebugLevelSound, "cmdSetSoundVolume: %d", value); + debugC(2, kDebugLevelSound, "kDoSound(setVolume): %d", value); value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX); @@ -965,20 +528,17 @@ void SoundCommandParser::cmdSetSoundVolume(reg_t obj, int16 value) { _music->soundSetVolume(musicSlot, value); writeSelectorValue(_segMan, obj, SELECTOR(vol), value); } -#endif + return acc; } -void SoundCommandParser::cmdSetSoundPriority(reg_t obj, int16 value) { - if (!obj.segment) - return; +reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; + int16 value = argv[1].toSint16(); -#ifdef USE_OLD_MUSIC_FUNCTIONS - script_set_priority(_resMan, _segMan, _state, obj, value); -#else MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { - warning("cmdSetSoundPriority: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return; + warning("kDoSound(setPriority): Slot not found (%04x:%04x)", PRINT_REG(obj)); + return acc; } if (value == -1) { @@ -987,7 +547,7 @@ void SoundCommandParser::cmdSetSoundPriority(reg_t obj, int16 value) { if (song->data[0] == 0xf0) _music->soundSetPriority(musicSlot, song->data[1]); else - warning("cmdSetSoundPriority: Attempt to unset song priority when there is no built-in value"); + warning("kDoSound(setPriority): Attempt to unset song priority when there is no built-in value"); //pSnd->prio=0;field_15B=0 writeSelectorValue(_segMan, obj, SELECTOR(flags), readSelectorValue(_segMan, obj, SELECTOR(flags)) & 0xFD); @@ -998,32 +558,26 @@ void SoundCommandParser::cmdSetSoundPriority(reg_t obj, int16 value) { writeSelectorValue(_segMan, obj, SELECTOR(flags), readSelectorValue(_segMan, obj, SELECTOR(flags)) | 2); //DoSOund(0xF,hobj,w) } -#endif + return acc; } -void SoundCommandParser::cmdSetSoundLoop(reg_t obj, int16 value) { - if (!obj.segment) - return; +reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) { + reg_t obj = argv[0]; + int16 value = argv[1].toSint16(); -#ifdef USE_OLD_MUSIC_FUNCTIONS - if (!readSelector(_segMan, obj, SELECTOR(nodePtr)).isNull()) { - SongHandle handle = FROBNICATE_HANDLE(obj); - _state->sfx_song_set_loops(handle, value); - } -#else MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { // Apparently, it's perfectly normal for a game to call cmdSetSoundLoop // before actually initializing the sound and adding it to the playlist // with cmdInitSound. Usually, it doesn't matter if the game doesn't // request to loop the sound, so in this case, don't throw any warning, - // otherwise do, because the sound won't be looped + // otherwise do, because the sound won't be looped. if (value == -1) { - warning("cmdSetSoundLoop: Slot not found (%04x:%04x) and the song was requested to be looped", PRINT_REG(obj)); + warning("kDoSound(setLoop): Slot not found (%04x:%04x) and the song was requested to be looped", PRINT_REG(obj)); } else { // Doesn't really matter } - return; + return acc; } if (value == -1) { musicSlot->loop = 0xFFFF; @@ -1032,40 +586,37 @@ void SoundCommandParser::cmdSetSoundLoop(reg_t obj, int16 value) { } writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop); -#endif + return acc; } -void SoundCommandParser::cmdSuspendSound(reg_t obj, int16 value) { +reg_t SoundCommandParser::kDoSoundSuspend(int argc, reg_t *argv, reg_t acc) { // TODO - warning("STUB: cmdSuspendSound"); + warning("kDoSound(suspend): STUB"); + return acc; } -#ifndef USE_OLD_MUSIC_FUNCTIONS - void SoundCommandParser::updateSci0Cues() { bool noOnePlaying = true; MusicEntry *pWaitingForPlay = NULL; - _music->_mutex.lock(); - const MusicList::iterator end = _music->getPlayListEnd(); for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) { // Is the sound stopped, and the sound object updated too? If yes, skip - // this sound, as SCI0 only allows one active song + // this sound, as SCI0 only allows one active song. if ((*i)->isQueued) { pWaitingForPlay = (*i); - // FIXME (?) - in iceman 2 songs are queued when playing the door sound - if we use the first song for resuming - // then it's the wrong one. Both songs have same priority. Maybe the new sound function in sci0 - // is somehow responsible + // FIXME(?): In iceman 2 songs are queued when playing the door + // sound - if we use the first song for resuming then it's the wrong + // one. Both songs have same priority. Maybe the new sound function + // in sci0 is somehow responsible. continue; } if ((*i)->signal == 0 && (*i)->status != kSoundPlaying) continue; - cmdUpdateCues((*i)->soundObj, 0); + processUpdateCues((*i)->soundObj); noOnePlaying = false; } - _music->_mutex.unlock(); if (noOnePlaying && pWaitingForPlay) { // If there is a queued entry, play it now ffs: SciMusic::soundPlay() @@ -1074,90 +625,40 @@ void SoundCommandParser::updateSci0Cues() { } } -#endif - void SoundCommandParser::clearPlayList() { -#ifndef USE_OLD_MUSIC_FUNCTIONS _music->clearPlayList(); -#endif -} - -void SoundCommandParser::syncPlayList(Common::Serializer &s) { -#ifndef USE_OLD_MUSIC_FUNCTIONS - _music->saveLoadWithSerializer(s); -#endif -} - -void SoundCommandParser::reconstructPlayList(int savegame_version) { -#ifndef USE_OLD_MUSIC_FUNCTIONS - Common::StackLock lock(_music->_mutex); - - const MusicList::iterator end = _music->getPlayListEnd(); - for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) { - if ((*i)->resourceId && _resMan->testResource(ResourceId(kResourceTypeSound, (*i)->resourceId))) { - (*i)->soundRes = new SoundResource((*i)->resourceId, _resMan, _soundVersion); - _music->soundInitSnd(*i); - } else { - (*i)->soundRes = 0; - } - if ((*i)->status == kSoundPlaying) { - if (savegame_version < 14) { - (*i)->dataInc = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(dataInc)); - (*i)->signal = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(signal)); - - if (_soundVersion >= SCI_VERSION_1_LATE) - (*i)->volume = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol)); - } - - cmdPlaySound((*i)->soundObj, 0); - } - } - -#endif } void SoundCommandParser::printPlayList(Console *con) { -#ifndef USE_OLD_MUSIC_FUNCTIONS _music->printPlayList(con); -#endif } void SoundCommandParser::printSongInfo(reg_t obj, Console *con) { -#ifndef USE_OLD_MUSIC_FUNCTIONS _music->printSongInfo(obj, con); -#endif } void SoundCommandParser::stopAllSounds() { -#ifndef USE_OLD_MUSIC_FUNCTIONS _music->stopAll(); -#endif } void SoundCommandParser::startNewSound(int number) { -#ifndef USE_OLD_MUSIC_FUNCTIONS Common::StackLock lock(_music->_mutex); // Overwrite the first sound in the playlist MusicEntry *song = *_music->getPlayListStart(); reg_t soundObj = song->soundObj; - cmdDisposeSound(soundObj, 0); + processDisposeSound(soundObj); writeSelectorValue(_segMan, soundObj, SELECTOR(number), number); - cmdInitSound(soundObj, 0); - cmdPlaySound(soundObj, 0); -#endif + processInitSound(soundObj); + processPlaySound(soundObj); } void SoundCommandParser::setMasterVolume(int vol) { -#ifndef USE_OLD_MUSIC_FUNCTIONS _music->soundSetMasterVolume(vol); -#endif } void SoundCommandParser::pauseAll(bool pause) { -#ifndef USE_OLD_MUSIC_FUNCTIONS _music->pauseAll(pause); -#endif } } // End of namespace Sci diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h index 09cca23450..a8bc1eb280 100644 --- a/engines/sci/sound/soundcmd.h +++ b/engines/sci/sound/soundcmd.h @@ -26,8 +26,6 @@ #ifndef SCI_SOUNDCMD_H #define SCI_SOUNDCMD_H -#include "sci/sci.h" // for USE_OLD_MUSIC_FUNCTIONS - #include "common/list.h" #include "sci/engine/state.h" @@ -36,13 +34,13 @@ namespace Sci { class Console; class SciMusic; class SoundCommandParser; -typedef void (SoundCommandParser::*SoundCommand)(reg_t obj, int16 value); +//typedef void (SoundCommandParser::*SoundCommand)(reg_t obj, int16 value); -struct MusicEntryCommand { - MusicEntryCommand(const char *d, SoundCommand c) : sndCmd(c), desc(d) {} - SoundCommand sndCmd; - const char *desc; -}; +//struct MusicEntryCommand { +// MusicEntryCommand(const char *d, SoundCommand c) : sndCmd(c), desc(d) {} +// SoundCommand sndCmd; +// const char *desc; +//}; class SoundCommandParser { public: @@ -53,11 +51,7 @@ public: kMaxSciVolume = 15 }; -#ifdef USE_OLD_MUSIC_FUNCTIONS - void updateSfxState(SfxState *newState) { _state = newState; } -#endif - - reg_t parseCommand(int argc, reg_t *argv, reg_t acc); + //reg_t parseCommand(int argc, reg_t *argv, reg_t acc); // Functions used for game state loading void clearPlayList(); @@ -69,14 +63,14 @@ public: void pauseAll(bool pause); // Debug console functions - void playSound(reg_t obj) { cmdPlaySound(obj, 0); } - void stopSound(reg_t obj) { cmdStopSound(obj, 0); } void startNewSound(int number); void stopAllSounds(); void printPlayList(Console *con); void printSongInfo(reg_t obj, Console *con); -#ifndef USE_OLD_MUSIC_FUNCTIONS + void processPlaySound(reg_t obj); + void processStopSound(reg_t obj, bool sampleFinishedPlaying); + /** * Synchronizes the current state of the music list to the rest of the engine, so that * the changes that the sound thread makes to the music are registered with the engine @@ -85,56 +79,43 @@ public: * by the engine scripts themselves, so the engine itself polls for changes to the music */ void updateSci0Cues(); -#endif + + reg_t kDoSoundInit(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundPlay(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundDummy(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundMute(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundPause(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundResume(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundStop(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundStopAll(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundDispose(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundFade(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundUpdate(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundReverb(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundSetHold(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundSuspend(int argc, reg_t *argv, reg_t acc); private: - typedef Common::Array<MusicEntryCommand *> SoundCommandContainer; - SoundCommandContainer _soundCommands; + //typedef Common::Array<MusicEntryCommand *> SoundCommandContainer; + //SoundCommandContainer _soundCommands; ResourceManager *_resMan; SegManager *_segMan; Kernel *_kernel; -#ifdef USE_OLD_MUSIC_FUNCTIONS - SfxState *_state; - int _midiCmd, _controller, _param; -#else SciMusic *_music; -#endif AudioPlayer *_audio; SciVersion _soundVersion; - int _argc; - reg_t *_argv; // for cmdFadeSound - uint32 _midiCommand; // for cmdSendMidi - reg_t _acc; - int _cmdUpdateCuesIndex; - - void cmdInitSound(reg_t obj, int16 value); - void cmdPlaySound(reg_t obj, int16 value); - void cmdDummy(reg_t obj, int16 value); - void cmdMuteSound(reg_t obj, int16 value); - void cmdPauseSound(reg_t obj, int16 value); - void cmdResumeSound(reg_t obj, int16 value); - void cmdStopSound(reg_t obj, int16 value); - void cmdDisposeSound(reg_t obj, int16 value); - void cmdMasterVolume(reg_t obj, int16 value); - void cmdFadeSound(reg_t obj, int16 value); - void cmdGetPolyphony(reg_t obj, int16 value); - void cmdStopAllSounds(reg_t obj, int16 value); - void cmdUpdateSound(reg_t obj, int16 value); - void cmdUpdateCues(reg_t obj, int16 value); - void cmdSendMidi(reg_t obj, int16 value); - void cmdReverb(reg_t obj, int16 value); - void cmdSetSoundHold(reg_t obj, int16 value); - void cmdGetAudioCapability(reg_t obj, int16 value); - void cmdSetSoundVolume(reg_t obj, int16 value); - void cmdSetSoundPriority(reg_t obj, int16 value); - void cmdSetSoundLoop(reg_t obj, int16 value); - void cmdSuspendSound(reg_t obj, int16 value); - - void processStopSound(reg_t obj, int16 value, bool sampleFinishedPlaying); - -#ifdef USE_OLD_MUSIC_FUNCTIONS - void changeSoundStatus(reg_t obj, int newStatus); -#endif + + void processInitSound(reg_t obj); + void processDisposeSound(reg_t obj); + void processUpdateCues(reg_t obj); }; } // End of namespace Sci |