소스 검색

Fix possible memory corruption in video player

Fixes two bugs, one was definitely happening, and 2nd one that is causing
undefined behavior and may work only in some std implementations

- VideoPlayer would attempt to access subtitles widget after VideoPlayer
itself was destroyed in onPlaybackFinished call
- std::function was destroyed from a function that is being called by
it. Replaced with 1-method interface to avoid usage of std::function in
this scenario
Ivan Savenko 10 달 전
부모
커밋
9fbeacb688

+ 1 - 0
client/CMakeLists.txt

@@ -347,6 +347,7 @@ set(vcmiclientcommon_HEADERS
 	widgets/CArtifactsOfHeroAltar.h
 	widgets/CArtifactsOfHeroMarket.h
 	widgets/CArtifactsOfHeroBackpack.h
+	widgets/IVideoHolder.h
 	widgets/RadialMenu.h
 	widgets/VideoWidget.h
 	widgets/markets/CAltarArtifacts.h

+ 6 - 1
client/mainmenu/CHighScoreScreen.cpp

@@ -205,7 +205,7 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
 	}
 	else
 	{
-		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), VideoPath::builtin("LOSEGAME.SMK"), true, [this](){close();});
+		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), VideoPath::builtin("LOSEGAME.SMK"), true, this);
 		CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true);
 	}
 
@@ -216,6 +216,11 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
 	}
 }
 
+void CHighScoreInputScreen::onVideoPlaybackFinished()
+{
+	close();
+}
+
 int CHighScoreInputScreen::addEntry(std::string text) {
 	std::vector<JsonNode> baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"].Vector();
 	

+ 5 - 1
client/mainmenu/CHighScoreScreen.h

@@ -8,6 +8,8 @@
  *
  */
 #pragma once
+
+#include "../widgets/IVideoHolder.h"
 #include "../windows/CWindowObject.h"
 #include "../../lib/gameState/HighScore.h"
 #include "../../lib/gameState/GameStatistics.h"
@@ -69,7 +71,7 @@ public:
 	CHighScoreInput(std::string playerName, std::function<void(std::string text)> readyCB);
 };
 
-class CHighScoreInputScreen : public CWindowObject
+class CHighScoreInputScreen : public CWindowObject, public IVideoHolder
 {
 	std::vector<std::shared_ptr<CLabel>> texts;
 	std::shared_ptr<CHighScoreInput> input;
@@ -82,6 +84,8 @@ class CHighScoreInputScreen : public CWindowObject
 	bool won;
 	HighScoreCalculation calc;
 	StatisticDataSet stat;
+
+	void onVideoPlaybackFinished() override;
 public:
 	CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic);
 

+ 17 - 0
client/widgets/IVideoHolder.h

@@ -0,0 +1,17 @@
+/*
+ * IVideoHolder.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+class IVideoHolder
+{
+public:
+	virtual ~IVideoHolder() = default;
+	virtual void onVideoPlaybackFinished() = 0;
+};

+ 10 - 7
client/widgets/VideoWidget.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "VideoWidget.h"
 #include "TextControls.h"
+#include "IVideoHolder.h"
 
 #include "../CGameInfo.h"
 #include "../gui/CGuiHandler.h"
@@ -172,15 +173,17 @@ void VideoWidgetBase::tick(uint32_t msPassed)
 	{
 		videoInstance->tick(msPassed);
 
+		if(subTitle)
+			subTitle->setText(getSubTitleLine(videoInstance->timeStamp()));
+
 		if(videoInstance->videoEnded())
 		{
 			videoInstance.reset();
 			stopAudio();
 			onPlaybackFinished();
+			// WARNING: onPlaybackFinished call may destoy `this`. Make sure that this is the very last operation in this method!
 		}
 	}
-	if(subTitle && videoInstance)
-		subTitle->setText(getSubTitleLine(videoInstance->timeStamp()));
 }
 
 VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio)
@@ -200,19 +203,19 @@ void VideoWidget::onPlaybackFinished()
 	playVideo(loopedVideo);
 }
 
-VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function<void()> & callback)
+VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, IVideoHolder * owner)
 	: VideoWidgetBase(position, video, playAudio)
-	, callback(callback)
+	, owner(owner)
 {
 }
 
-VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, const std::function<void()> & callback)
+VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, IVideoHolder * owner)
 	: VideoWidgetBase(position, video, playAudio, scaleFactor)
-	, callback(callback)
+	, owner(owner)
 {
 }
 
 void VideoWidgetOnce::onPlaybackFinished()
 {
-	callback();
+	owner->onVideoPlaybackFinished();
 }

+ 4 - 3
client/widgets/VideoWidget.h

@@ -14,6 +14,7 @@
 #include "../lib/filesystem/ResourcePath.h"
 #include "../lib/json/JsonNode.h"
 
+class IVideoHolder;
 class IVideoInstance;
 class CMultiLineLabel;
 
@@ -64,10 +65,10 @@ public:
 
 class VideoWidgetOnce final: public VideoWidgetBase
 {
-	std::function<void()> callback;
+	IVideoHolder * owner;
 
 	void onPlaybackFinished() final;
 public:
-	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function<void()> & callback);
-	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, const std::function<void()> & callback);
+	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, IVideoHolder * owner);
+	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, IVideoHolder * owner);
 };

+ 8 - 8
client/windows/CSpellWindow.cpp

@@ -498,20 +498,20 @@ void CSpellWindow::turnPageLeft()
 {
 	OBJECT_CONSTRUCTION;
 	if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook)
-		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNLFT.SMK"), false, [this](){
-			video.reset();
-			redraw();
-		});
+		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNLFT.SMK"), false, this);
 }
 
 void CSpellWindow::turnPageRight()
 {
 	OBJECT_CONSTRUCTION;
 	if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook)
-		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNRGH.SMK"), false, [this](){
-			video.reset();
-			redraw();
-		});
+		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNRGH.SMK"), false, this);
+}
+
+void CSpellWindow::onVideoPlaybackFinished()
+{
+	video.reset();
+	redraw();
 }
 
 void CSpellWindow::keyPressed(EShortcut key)

+ 4 - 1
client/windows/CSpellWindow.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "CWindowObject.h"
+#include "../widgets/IVideoHolder.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -31,7 +32,7 @@ class CToggleButton;
 class VideoWidgetOnce;
 
 /// The spell window
-class CSpellWindow : public CWindowObject
+class CSpellWindow : public CWindowObject, public IVideoHolder
 {
 	class SpellArea : public CIntObject
 	{
@@ -116,6 +117,8 @@ class CSpellWindow : public CWindowObject
 	void turnPageLeft();
 	void turnPageRight();
 
+	void onVideoPlaybackFinished() override;
+
 	bool openOnBattleSpells;
 	std::function<void(SpellID)> onSpellSelect; //external processing of selected spell
 

+ 7 - 2
client/windows/GUIClasses.cpp

@@ -1670,22 +1670,27 @@ VideoWindow::VideoWindow(const VideoPath & video, const ImagePath & rim, bool sh
 	if(!rim.empty())
 	{
 		setBackground(rim);
-		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, [this](){ exit(false); });
+		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, this);
 		pos = center(Rect(0, 0, 800, 600));
 	}
 	else
 	{
 		blackBackground = std::make_shared<GraphicalPrimitiveCanvas>(Rect(0, 0, GH.screenDimensions().x, GH.screenDimensions().y));
-		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), video, true, scaleFactor, [this](){ exit(false); });
+		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), video, true, scaleFactor, this);
 		pos = center(Rect(0, 0, videoPlayer->pos.w, videoPlayer->pos.h));
 		blackBackground->addBox(Point(0, 0), Point(pos.x, pos.y), Colors::BLACK);
 	}
 
 	if(backgroundAroundWindow)
 		backgroundAroundWindow->pos.moveTo(Point(0, 0));
+}
 
+void VideoWindow::onVideoPlaybackFinished()
+{
+	exit(false);
 }
 
+
 void VideoWindow::exit(bool skipped)
 {
 	close();

+ 3 - 1
client/windows/GUIClasses.h

@@ -12,6 +12,7 @@
 #include "CWindowObject.h"
 #include "../lib/ResourceSet.h"
 #include "../widgets/Images.h"
+#include "../widgets/IVideoHolder.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -509,7 +510,7 @@ public:
 	CThievesGuildWindow(const CGObjectInstance * _owner);
 };
 
-class VideoWindow : public CWindowObject
+class VideoWindow : public CWindowObject, public IVideoHolder
 {
 	std::shared_ptr<VideoWidgetOnce> videoPlayer;
 	std::shared_ptr<CFilledTexture> backgroundAroundWindow;
@@ -517,6 +518,7 @@ class VideoWindow : public CWindowObject
 
 	std::function<void(bool)> closeCb;
 
+	void onVideoPlaybackFinished() override;
 	void exit(bool skipped);
 public:
 	VideoWindow(const VideoPath & video, const ImagePath & rim, bool showBackground, float scaleFactor, const std::function<void(bool)> & closeCb);