Common::Error SciEngine::run() { g_eventRec.registerRandomSource(_rng, "sci"); // Assign default values to the config manager, in case settings are missing ConfMan.registerDefault("sci_originalsaveload", "false"); ConfMan.registerDefault("native_fb01", "false"); ConfMan.registerDefault("windows_cursors", "false"); // Windows cursors for KQ6 Windows _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; } */ // Reset, so that error()s before SoundCommandParser is initialized wont cause a crash _soundCmd = NULL; // Add the after market GM patches for the specified game, if they exist _resMan->addNewGMPatch(_gameId); _gameObjectAddress = _resMan->findGameObject(); SegManager *segMan = new SegManager(_resMan); // Initialize the game screen _gfxScreen = new GfxScreen(_resMan); _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering")); // 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, SCI01 and SCI1 EGA games used a parser _vocabulary = (getSciVersion() <= SCI_VERSION_1_EGA_ONLY) ? new Vocabulary(_resMan, false) : NULL; // Also, XMAS1990 apparently had a parser too. Refer to http://forums.scummvm.org/viewtopic.php?t=9135 if (getGameId() == GID_CHRISTMAS1990) _vocabulary = new Vocabulary(_resMan, false); _audio = new AudioPlayer(_resMan); _gamestate = new EngineState(segMan); _eventMan = new EventManager(_resMan->detectFontExtended()); // 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; } // we try to find the super class address of the game object, we can't do that earlier const Object *gameObject = segMan->getObject(_gameObjectAddress); if (!gameObject) { warning("Could not get game object, aborting..."); return Common::kUnknownError; } script_adjust_opcode_formats(); // Must be called after game_init(), as they use _features _kernel->loadKernelNames(_features); _soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType()); syncSoundSettings(); syncIngameAudioOptions(); // Load our Mac executable here for icon bar palettes and high-res fonts loadMacExecutable(); // Initialize all graphics related subsystems initGraphics(); // Patch in our save/restore code, so that dialogs are replaced patchGameSaveRestore(); setLauncherLanguage(); // Check whether loading a savestate was requested int directSaveSlotLoading = ConfMan.getInt("save_slot"); if (directSaveSlotLoading >= 0) { // call GameObject::play (like normally) initStackBaseWithSelector(SELECTOR(play)); // We set this, so that the game automatically quit right after init _gamestate->variables[VAR_GLOBAL][4] = TRUE_REG; _gamestate->_executionStackPosChanged = false; run_vm(_gamestate); // As soon as we get control again, actually restore the game reg_t restoreArgv[2] = { NULL_REG, make_reg(0, directSaveSlotLoading) }; // special call (argv[0] is NULL) kRestoreGame(_gamestate, 2, restoreArgv); // this indirectly calls GameObject::init, which will setup menu, text font/color codes etc. // without this games would be pretty badly broken } // Show any special warnings for buggy scripts with severe game bugs, // which have been patched by Sierra if (getGameId() == GID_LONGBOW) { // Longbow 1.0 has a buggy script which prevents the game // from progressing during the Green Man riddle sequence. // A patch for this buggy script has been released by Sierra, // and is necessary to complete the game without issues. // The patched script is included in Longbow 1.1. // Refer to bug #3036609. Resource *buggyScript = _resMan->findResource(ResourceId(kResourceTypeScript, 180), 0); if (buggyScript && (buggyScript->size == 12354 || buggyScript->size == 12362)) { showScummVMDialog("A known buggy game script has been detected, which could " "prevent you from progressing later on in the game, during " "the sequence with the Green Man's riddles. Please, apply " "the latest patch for this game by Sierra to avoid possible " "problems"); } } // Show a warning if the user has selected a General MIDI device, no GM patch exists // (i.e. patch 4) and the game is one of the known 8 SCI1 games that Sierra has provided // after market patches for in their "General MIDI Utility". if (_soundCmd->getMusicType() == MT_GM && !ConfMan.getBool("native_mt32")) { if (!_resMan->findResource(ResourceId(kResourceTypePatch, 4), 0)) { switch (getGameId()) { case GID_ECOQUEST: case GID_HOYLE3: case GID_LSL1: case GID_LSL5: case GID_LONGBOW: case GID_SQ1: case GID_SQ4: case GID_FAIRYTALES: showScummVMDialog("You have selected General MIDI as a sound device. Sierra " "has provided after-market support for General MIDI for this " "game in their \"General MIDI Utility\". Please, apply this " "patch in order to enjoy MIDI music with this game. Once you " "have obtained it, you can unpack all of the included *.PAT " "files in your ScummVM extras folder and ScummVM will add the " "appropriate patch automatically. Alternatively, you can follow " "the instructions in the READ.ME file included in the patch and " "rename the associated *.PAT file to 4.PAT and place it in the " "game folder. Without this patch, General MIDI music for this " "game will sound badly distorted."); break; default: break; } } } if (gameHasFanMadePatch()) { showScummVMDialog("Your game is patched with a fan made script patch. Such patches have " "been reported to cause issues, as they modify game scripts extensively. " "The issues that these patches fix do not occur in ScummVM, so you are " "advised to remove this patch from your game folder in order to avoid " "having unexpected errors and/or issues later on."); } runGame(); ConfMan.flushToDisk(); return Common::kNoError; }
reg_t_hash_map *find_all_used_references(EngineState *s) { SegManager *sm = s->seg_manager; reg_t_hash_map *normal_map = NULL; WorklistManager wm; uint i; // Initialise // Init: Registers wm.push(s->r_acc); wm.push(s->r_prev); // Init: Value Stack // We do this one by hand since the stack doesn't know the current execution stack { ExecStack &xs = s->_executionStack.back(); reg_t *pos; for (pos = s->stack_base; pos < xs.sp; pos++) wm.push(*pos); } debugC(2, kDebugLevelGC, "[GC] -- Finished adding value stack"); // Init: Execution Stack Common::List<ExecStack>::iterator iter; for (iter = s->_executionStack.begin(); iter != s->_executionStack.end(); ++iter) { ExecStack &es = *iter; if (es.type != EXEC_STACK_TYPE_KERNEL) { wm.push(es.objp); wm.push(es.sendp); if (es.type == EXEC_STACK_TYPE_VARSELECTOR) wm.push(*(es.getVarPointer(s))); } } debugC(2, kDebugLevelGC, "[GC] -- Finished adding execution stack"); // Init: Explicitly loaded scripts for (i = 1; i < sm->_heap.size(); i++) if (sm->_heap[i] && sm->_heap[i]->getType() == MEM_OBJ_SCRIPT) { Script *script = (Script *)sm->_heap[i]; if (script->lockers) { // Explicitly loaded? // Locals, if present wm.push(make_reg(script->locals_segment, 0)); // All objects (may be classes, may be indirectly reachable) for (uint obj_nr = 0; obj_nr < script->_objects.size(); obj_nr++) { wm.push(script->_objects[obj_nr].pos); } } } debugC(2, kDebugLevelGC, "[GC] -- Finished explicitly loaded scripts, done with root set\n"); // Run Worklist Algorithm while (!wm._worklist.empty()) { reg_t reg = wm._worklist.back(); wm._worklist.pop_back(); if (reg.segment != s->stack_segment) { // No need to repeat this one debugC(2, kDebugLevelGC, "[GC] Checking %04x:%04x\n", PRINT_REG(reg)); if (reg.segment < sm->_heap.size() && sm->_heap[reg.segment]) sm->_heap[reg.segment]->listAllOutgoingReferences(s, reg, &wm, add_outgoing_refs); } } // Normalise normal_map = normalise_hashmap_ptrs(sm, wm._map); return normal_map; }
int main(int argc,char *argv[]) { FILE *fpA; FILE *fpB; FILE *fpC; int c; unsigned int alpha=0xff; if (argc !=1) sscanf(argv[1],"%x",&alpha); fpA=fopen("paltable.c","w"); fpB=fopen("stdkey.h","w"); fprintf(stderr,"making spectra.\n"); make_reg(); fprintf(stderr,"making color bars.\n"); make_root(); make_out_reg(256,17,256); fprintf(stderr,"printing palette table.\n"); print_reg(fpA,"KeyPalReg",256); print_reg_hdr(fpB,"KeyPalReg",256); fprintf(stderr,"printing rgb tables.\n"); make_map(256); print_map(fpA,"KeyLinear",256,alpha); print_hdr(fpB,"KeyLinear",256); fpC=fopen("linear-mag.key","w"); fprintf(fpC,"# linear magnitude\n"); fprintf(fpC,"%d\n",256); for (c=0;c<256;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,mag[c][0],mag[c][1],mag[c][2]); fclose(fpC); fpC=fopen("linear-sgn.key","w"); fprintf(fpC,"# linear signed\n"); fprintf(fpC,"%d\n",256); for (c=0;c<256;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,sgn[c][0],sgn[c][1],sgn[c][2]); fclose(fpC); make_map(20); print_map(fpA,"KeyTwentyBlk",20,alpha); print_hdr(fpB,"KeyTwentyBlk",20); fpC=fopen("twentyblk-mag.key","w"); fprintf(fpC,"# twentyblk magnitude\n"); fprintf(fpC,"%d\n",20); for (c=0;c<20;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,mag[c][0],mag[c][1],mag[c][2]); fclose(fpC); fpC=fopen("twentyblk-sgn.key","w"); fprintf(fpC,"# twentyblk signed\n"); fprintf(fpC,"%d\n",20); for (c=0;c<20;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,sgn[c][0],sgn[c][1],sgn[c][2]); fclose(fpC); make_map(10); print_map(fpA,"KeyTenBlk",10,alpha); print_hdr(fpB,"KeyTenBlk",10); fpC=fopen("tenblk-mag.key","w"); fprintf(fpC,"# tenblk magnitude\n"); fprintf(fpC,"%d\n",10); for (c=0;c<10;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,mag[c][0],mag[c][1],mag[c][2]); fclose(fpC); fpC=fopen("tenblk-sgn.key","w"); fprintf(fpC,"# tenblk signed\n"); fprintf(fpC,"%d\n",10); for (c=0;c<10;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,sgn[c][0],sgn[c][1],sgn[c][2]); fclose(fpC); make_map(5); print_map(fpA,"KeyFiveBlk",5,alpha); print_hdr(fpB,"KeyFiveBlk",5); fpC=fopen("fiveblk-mag.key","w"); fprintf(fpC,"# fiveblk magnitude\n"); fprintf(fpC,"%d\n",256); for (c=0;c<5;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,mag[c][0],mag[c][1],mag[c][2]); fclose(fpC); fpC=fopen("fiveblk-sgn.key","w"); fprintf(fpC,"# fiveblk signed\n"); fprintf(fpC,"%d\n",256); for (c=0;c<5;c++) fprintf(fpC,"%.2x%.2x%.2x%.2x\n",alpha,sgn[c][0],sgn[c][1],sgn[c][2]); fclose(fpC); make_cmap(256); print_cmap(fpA,"KeyLinearC",256); print_chdr(fpB,"KeyLinearC",256); make_cmap(20); print_cmap(fpA,"KeyTwentyblkC",20); print_chdr(fpB,"KeyTwentyBlkC",20); make_cmap(10); print_cmap(fpA,"KeyTenBlkC",10); print_chdr(fpB,"KeyTenBlkC",10); make_cmap(5); print_cmap(fpA,"KeyFiveBlkC",5); print_chdr(fpB,"KeyFiveBlkC",5); fclose(fpA); fclose(fpB); return 0; }
reg_t SoundCommandParser::kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc) { return make_reg(0, _music->soundGetVoices()); // Get the number of voices }
reg_t kRespondsTo(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; int selector = argv[1].toUint16(); return make_reg(0, s->_segMan->isHeapObject(obj) && lookupSelector(s->_segMan, obj, selector, NULL, NULL) != kSelectorNone); }
reg_t kIsObject(EngineState *s, int argc, reg_t *argv) { if (argv[0].offset == SIGNAL_OFFSET) // Treated specially return NULL_REG; else return make_reg(0, s->_segMan->isHeapObject(argv[0])); }
// Returns script dispatch address index in the supplied script reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { int script = argv[0].toUint16(); uint16 index = (argc > 1) ? argv[1].toUint16() : 0; if (argv[0].segment) return argv[0]; SegmentId scriptSeg = s->_segMan->getScriptSegment(script, SCRIPT_GET_LOAD); if (!scriptSeg) return NULL_REG; Script *scr = s->_segMan->getScript(scriptSeg); if (!scr->getExportsNr()) { // This is normal. Some scripts don't have a dispatch (exports) table, // 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. If an export is requested, then // it will most certainly fail with OOB access. if (argc == 2) error("Script 0x%x does not have a dispatch table and export %d " "was requested from it", script, index); return NULL_REG; } if (index > scr->getExportsNr()) { error("Dispatch index too big: %d > %d", index, scr->getExportsNr()); return NULL_REG; } uint16 address = scr->validateExportFunc(index, true); // Point to the heap for SCI1.1 - SCI2.1 games if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) address += scr->getScriptSize(); // Bugfix for the intro speed in PQ2 version 1.002.011. // This is taken from the patch by NewRisingSun(NRS) / Belzorash. Global 3 // is used for timing during the intro, and in the problematic version it's // initialized to 0, whereas it's 6 in other versions. Thus, we assign it // to 6 here, fixing the speed of the introduction. Refer to bug #3102071. if (g_sci->getGameId() == GID_PQ2 && script == 200) { if (s->variables[VAR_GLOBAL][3].isNull()) { warning("Fixing speed in the intro of PQ2, version 1.002.011"); s->variables[VAR_GLOBAL][3] = make_reg(0, 6); } } // HACK: Prevent the murderer from getting stuck behind the door in // Colonel's Bequest, room 215. A temporary fix for bug #3122075. // TODO/FIXME: Add a proper fix for this. There is a regression in this // scene with the new kInitBresen and kDoBresen functions (r52467). Using // just the "old" kInitBresen works. This hack is added for now because the // two functions are quite complex. The "old" versions were created based // on observations, and not on the interpreter itself, thus they have a lot // of differences in the way they behave and set variables to the mover object. // Since this is just a death scene where Laura is supposed to die anyway, // figuring out the exact cause of this is just not worth the effort. // Differences between the new and the old kInitBresen to the MoveTo object: // dy: 1 (new) - 2 (old) // b-i1: 20 (new) - 12 (old) // b-di: 65526 (new) - 65516 (old) // Performing the changes above to MoveTo (0017:033a) allows the killer to // move. Note that the actual issue might not be with kInitBresen/kDoBresen, // and there might be another underlying problem here. if (g_sci->getGameId() == GID_LAURABOW && script == 215) { warning("Moving actor position for the shower scene of Colonel's Bequest"); writeSelectorValue(s->_segMan, s->_segMan->findObjectByName("killer"), SELECTOR(x), 6); } return make_reg(scriptSeg, address); }
reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { int mask = argv[0].toUint16(); reg_t obj = argv[1]; SciEvent curEvent; int modifier_mask = getSciVersion() <= SCI_VERSION_01 ? SCI_KEYMOD_ALL : SCI_KEYMOD_NO_FOOLOCK; uint16 modifiers = 0; SegManager *segMan = s->_segMan; Common::Point mousePos; // For Mac games with an icon bar, handle possible icon bar events first if (g_sci->hasMacIconBar()) { reg_t iconObj = g_sci->_gfxMacIconBar->handleEvents(); if (!iconObj.isNull()) invokeSelector(s, iconObj, SELECTOR(select), argc, argv, 0, NULL); } // If there's a simkey pending, and the game wants a keyboard event, use the // simkey instead of a normal event if (g_debug_simulated_key && (mask & SCI_EVENT_KEYBOARD)) { // In case we use a simulated event we query the current mouse position mousePos = g_sci->_gfxCursor->getPosition(); #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2_1_EARLY) g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); #endif // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_KEYBOARD); // Keyboard event writeSelectorValue(segMan, obj, SELECTOR(message), g_debug_simulated_key); writeSelectorValue(segMan, obj, SELECTOR(modifiers), SCI_KEYMOD_NUMLOCK); // Numlock on writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x); writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y); g_debug_simulated_key = 0; return make_reg(0, 1); } curEvent = g_sci->getEventManager()->getSciEvent(mask); if (s->_delayedRestoreGame) { // delayed restore game from ScummVM menu got triggered gamestate_delayedrestore(s); return NULL_REG; } // For a real event we use its associated mouse position mousePos = curEvent.mousePos; #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2_1_EARLY) g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); #endif // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); if (g_sci->getVocabulary()) g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event if (s->_cursorWorkaroundActive) { // We check if the actual cursor position is inside specific rectangles // where the cursor itself should be moved to. If this is the case, we // set the mouse cursor's position to be within the rectangle in // question. Check GfxCursor::setPosition(), for a more detailed // explanation and a list of cursor position workarounds. if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) { // For OpenPandora and possibly other platforms, that support analog-stick control + touch screen // control at the same time: in case the cursor is currently at the coordinate set by the scripts, // we will count down instead of immediately disabling the workaround. // On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the // touch screen. In this case we would sometimes disable the workaround, simply because the touch // screen hasn't yet overwritten the position and thus the workaround would not work anymore. // On OpenPandora it would sometimes work and sometimes not without this. if (s->_cursorWorkaroundPoint == mousePos) { // Cursor is still at the same spot as set by the scripts if (s->_cursorWorkaroundPosCount > 0) { s->_cursorWorkaroundPosCount--; } else { // Was for quite a bit of time at that spot, so disable workaround now s->_cursorWorkaroundActive = false; } } else { // Cursor has moved, but is within the rect -> disable workaround immediately s->_cursorWorkaroundActive = false; } } else { mousePos.x = s->_cursorWorkaroundPoint.x; mousePos.y = s->_cursorWorkaroundPoint.y; } } writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x); writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y); // Get current keyboard modifiers, only keep relevant bits modifiers = curEvent.modifiers & modifier_mask; if (g_sci->getPlatform() == Common::kPlatformDOS) { // We are supposed to emulate SCI running in DOS // We set the higher byte of the modifiers to 02h // Original SCI also did that indirectly, because it asked BIOS for shift status // via AH=0x02 INT16, which then sets the shift flags in AL // AH is supposed to be destroyed in that case and it's not defined that 0x02 // is still in it on return. The value of AX was then set into the modifiers selector. // At least one fan-made game (Betrayed Alliance) requires 0x02 to be in the upper byte, // otherwise the darts game (script 111) will not work properly. // It seems Sierra fixed this behaviour (effectively bug) in the SCI1 keyboard driver. // SCI32 also resets the upper byte. // This was verified in SSCI itself by creating a SCI game and checking behavior. if (getSciVersion() <= SCI_VERSION_01) { modifiers |= 0x0200; } } switch (curEvent.type) { case SCI_EVENT_QUIT: s->abortScriptProcessing = kAbortQuitGame; // Terminate VM g_sci->_debugState.seeking = kDebugSeekNothing; g_sci->_debugState.runningStep = 0; break; case SCI_EVENT_KEYBOARD: writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_KEYBOARD); // Keyboard event s->r_acc = make_reg(0, 1); writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.character); // We only care about the translated character writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers); break; case SCI_EVENT_MOUSE_RELEASE: case SCI_EVENT_MOUSE_PRESS: // track left buttton clicks, if requested if (curEvent.type == SCI_EVENT_MOUSE_PRESS && curEvent.modifiers == 0 && g_debug_track_mouse_clicks) { g_sci->getSciDebugger()->debugPrintf("Mouse clicked at %d, %d\n", mousePos.x, mousePos.y); } if (mask & curEvent.type) { writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type); writeSelectorValue(segMan, obj, SELECTOR(message), 0); writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers); s->r_acc = make_reg(0, 1); } break; default: // Return a null event writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_NONE); writeSelectorValue(segMan, obj, SELECTOR(message), 0); writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers); s->r_acc = NULL_REG; } if ((s->r_acc.getOffset()) && (g_sci->_debugState.stopOnEvent)) { g_sci->_debugState.stopOnEvent = false; // A SCI event occurred, and we have been asked to stop, so open the debug console Console *con = g_sci->getSciDebugger(); con->debugPrintf("SCI event occurred: "); switch (curEvent.type) { case SCI_EVENT_QUIT: con->debugPrintf("quit event\n"); break; case SCI_EVENT_KEYBOARD: con->debugPrintf("keyboard event\n"); break; case SCI_EVENT_MOUSE_RELEASE: case SCI_EVENT_MOUSE_PRESS: con->debugPrintf("mouse click event\n"); break; default: con->debugPrintf("unknown or no event (event type %d)\n", curEvent.type); } con->attach(); con->onFrame(); } if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) { // If we're running a sound-SCI0 game, update the sound cues, to // compensate for the fact that sound-SCI0 does not poll to update // the sound cues itself, like sound-SCI1 and later do with // cmdUpdateSoundCues. kGetEvent is called quite often, so emulate // the sound-SCI1 behavior of cmdUpdateSoundCues with this call g_sci->_soundCmd->updateSci0Cues(); } // Wait a bit here, so that the CPU isn't maxed out when the game // is waiting for user input (e.g. when showing text boxes) - bug // #3037874. Make sure that we're not delaying while the game is // benchmarking, as that will affect the final benchmarked result - // check bugs #3058865 and #3127824 if (s->_gameIsBenchmarking) { // Game is benchmarking, don't add a delay } else { g_system->delayMillis(10); } return s->r_acc; }