+ tape.single_step = !tape.single_step;
+
+ DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
+}
+
+void TapeQuickSave(void)
+{
+ if (game_status == GAME_MODE_MAIN)
+ {
+ Request("No game that can be saved!", REQ_CONFIRM);
+
+ return;
+ }
+
+ if (game_status != GAME_MODE_PLAYING)
+ return;
+
+ if (tape.recording)
+ TapeHaltRecording(); // prepare tape for saving on-the-fly
+
+ if (TAPE_IS_EMPTY(tape))
+ {
+ Request("No tape that can be saved!", REQ_CONFIRM);
+
+ return;
+ }
+
+ if (SaveTapeChecked(tape.level_nr))
+ SaveEngineSnapshotSingle();
+}
+
+void TapeQuickLoad(void)
+{
+ char *filename = getTapeFilename(level_nr);
+
+ if (!fileExists(filename))
+ {
+ Request("No tape for this level!", REQ_CONFIRM);
+
+ return;
+ }
+
+ if (tape.recording && !Request("Stop recording and load tape?",
+ REQ_ASK | REQ_STAY_CLOSED))
+ {
+ OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+
+ return;
+ }
+
+ if (game_status != GAME_MODE_PLAYING && game_status != GAME_MODE_MAIN)
+ return;
+
+ if (CheckEngineSnapshotSingle())
+ {
+ TapeStartGamePlaying();
+
+ LoadEngineSnapshotSingle();
+
+ DrawCompleteVideoDisplay();
+
+ tape.playing = TRUE;
+ tape.pausing = TRUE;
+
+ TapeStopWarpForward();
+ TapeAppendRecording();
+
+ // restart step/move snapshots after quick loading tape
+ SaveEngineSnapshotToListInitial();
+
+ if (FrameCounter > 0)
+ return;
+ }
+
+ TapeStop();
+ TapeErase();
+
+ LoadTape(level_nr);
+
+ if (!TAPE_IS_EMPTY(tape))
+ {
+ TapeStartGamePlaying();
+ TapeStartWarpForward(AUTOPLAY_MODE_WARP_NO_DISPLAY);
+
+ tape.quick_resume = TRUE;
+ }
+ else // this should not happen (basically checked above)
+ {
+ int reopen_door = (game_status == GAME_MODE_PLAYING ? REQ_REOPEN : 0);
+
+ Request("No tape for this level!", REQ_CONFIRM | reopen_door);
+ }
+}
+
+boolean hasSolutionTape(void)
+{
+ boolean tape_file_exists = fileExists(getSolutionTapeFilename(level_nr));
+ boolean level_has_tape = (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
+ level.native_sp_level->demo.is_available);
+
+ return (tape_file_exists || level_has_tape);
+}
+
+boolean InsertSolutionTape(void)
+{
+ if (!hasSolutionTape())
+ {
+ Request("No solution tape for this level!", REQ_CONFIRM);
+
+ return FALSE;
+ }
+
+ if (!TAPE_IS_STOPPED(tape))
+ TapeStop();
+
+ // if tape recorder already contains a tape, remove it without asking
+ TapeErase();
+
+ LoadSolutionTape(level_nr);
+
+ DrawCompleteVideoDisplay();
+
+ if (TAPE_IS_EMPTY(tape))
+ {
+ Request("Loading solution tape for this level failed!", REQ_CONFIRM);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+boolean PlaySolutionTape(void)
+{
+ if (!InsertSolutionTape())
+ return FALSE;
+
+ TapeStartGamePlaying();
+
+ return TRUE;
+}
+
+static boolean checkTapesFromSameLevel(struct TapeInfo *t1, struct TapeInfo *t2)
+{
+ return (strEqual(t1->level_identifier, t2->level_identifier) &&
+ t1->level_nr == t2->level_nr);
+}
+
+static void CopyTape(struct TapeInfo *tape_from, struct TapeInfo *tape_to)
+{
+ if (tape_to->level_identifier != NULL)
+ checked_free(tape_to->level_identifier);
+
+ *tape_to = *tape_from;
+
+ tape_to->level_identifier = getStringCopy(tape_from->level_identifier);
+}
+
+static void SwapTapes(struct TapeInfo *t1, struct TapeInfo *t2)
+{
+ struct TapeInfo tmp = *t1;
+
+ *t1 = *t2;
+ *t2 = tmp;
+}
+
+static void CopyTapeToUndoBuffer(void)
+{
+ // copy tapes to undo buffer if large enough (or larger than last undo tape)
+ // or if the last undo tape is from a different level set or level number
+ if (tape.length_seconds >= TAPE_MIN_SECONDS_FOR_UNDO_BUFFER ||
+ tape.length_seconds >= tape_undo_buffer.length_seconds ||
+ !checkTapesFromSameLevel(&tape, &tape_undo_buffer))
+ {
+ CopyTape(&tape, &tape_undo_buffer);
+ }