mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -347,6 +347,7 @@ set(vcmiclientcommon_HEADERS | |||||||
| 	widgets/CArtifactsOfHeroAltar.h | 	widgets/CArtifactsOfHeroAltar.h | ||||||
| 	widgets/CArtifactsOfHeroMarket.h | 	widgets/CArtifactsOfHeroMarket.h | ||||||
| 	widgets/CArtifactsOfHeroBackpack.h | 	widgets/CArtifactsOfHeroBackpack.h | ||||||
|  | 	widgets/IVideoHolder.h | ||||||
| 	widgets/RadialMenu.h | 	widgets/RadialMenu.h | ||||||
| 	widgets/VideoWidget.h | 	widgets/VideoWidget.h | ||||||
| 	widgets/markets/CAltarArtifacts.h | 	widgets/markets/CAltarArtifacts.h | ||||||
|   | |||||||
| @@ -205,7 +205,7 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc | |||||||
| 	} | 	} | ||||||
| 	else | 	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); | 		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) { | int CHighScoreInputScreen::addEntry(std::string text) { | ||||||
| 	std::vector<JsonNode> baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"].Vector(); | 	std::vector<JsonNode> baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"].Vector(); | ||||||
| 	 | 	 | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "../widgets/IVideoHolder.h" | ||||||
| #include "../windows/CWindowObject.h" | #include "../windows/CWindowObject.h" | ||||||
| #include "../../lib/gameState/HighScore.h" | #include "../../lib/gameState/HighScore.h" | ||||||
| #include "../../lib/gameState/GameStatistics.h" | #include "../../lib/gameState/GameStatistics.h" | ||||||
| @@ -69,7 +71,7 @@ public: | |||||||
| 	CHighScoreInput(std::string playerName, std::function<void(std::string text)> readyCB); | 	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::vector<std::shared_ptr<CLabel>> texts; | ||||||
| 	std::shared_ptr<CHighScoreInput> input; | 	std::shared_ptr<CHighScoreInput> input; | ||||||
| @@ -82,6 +84,8 @@ class CHighScoreInputScreen : public CWindowObject | |||||||
| 	bool won; | 	bool won; | ||||||
| 	HighScoreCalculation calc; | 	HighScoreCalculation calc; | ||||||
| 	StatisticDataSet stat; | 	StatisticDataSet stat; | ||||||
|  |  | ||||||
|  | 	void onVideoPlaybackFinished() override; | ||||||
| public: | public: | ||||||
| 	CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic); | 	CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								client/widgets/IVideoHolder.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/widgets/IVideoHolder.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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,6 +10,7 @@ | |||||||
| #include "StdInc.h" | #include "StdInc.h" | ||||||
| #include "VideoWidget.h" | #include "VideoWidget.h" | ||||||
| #include "TextControls.h" | #include "TextControls.h" | ||||||
|  | #include "IVideoHolder.h" | ||||||
|  |  | ||||||
| #include "../CGameInfo.h" | #include "../CGameInfo.h" | ||||||
| #include "../gui/CGuiHandler.h" | #include "../gui/CGuiHandler.h" | ||||||
| @@ -172,15 +173,17 @@ void VideoWidgetBase::tick(uint32_t msPassed) | |||||||
| 	{ | 	{ | ||||||
| 		videoInstance->tick(msPassed); | 		videoInstance->tick(msPassed); | ||||||
|  |  | ||||||
|  | 		if(subTitle) | ||||||
|  | 			subTitle->setText(getSubTitleLine(videoInstance->timeStamp())); | ||||||
|  |  | ||||||
| 		if(videoInstance->videoEnded()) | 		if(videoInstance->videoEnded()) | ||||||
| 		{ | 		{ | ||||||
| 			videoInstance.reset(); | 			videoInstance.reset(); | ||||||
| 			stopAudio(); | 			stopAudio(); | ||||||
| 			onPlaybackFinished(); | 			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) | VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio) | ||||||
| @@ -200,19 +203,19 @@ void VideoWidget::onPlaybackFinished() | |||||||
| 	playVideo(loopedVideo); | 	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) | 	: 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) | 	: VideoWidgetBase(position, video, playAudio, scaleFactor) | ||||||
| 	, callback(callback) | 	, owner(owner) | ||||||
| { | { | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoWidgetOnce::onPlaybackFinished() | void VideoWidgetOnce::onPlaybackFinished() | ||||||
| { | { | ||||||
| 	callback(); | 	owner->onVideoPlaybackFinished(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| #include "../lib/filesystem/ResourcePath.h" | #include "../lib/filesystem/ResourcePath.h" | ||||||
| #include "../lib/json/JsonNode.h" | #include "../lib/json/JsonNode.h" | ||||||
|  |  | ||||||
|  | class IVideoHolder; | ||||||
| class IVideoInstance; | class IVideoInstance; | ||||||
| class CMultiLineLabel; | class CMultiLineLabel; | ||||||
|  |  | ||||||
| @@ -64,10 +65,10 @@ public: | |||||||
|  |  | ||||||
| class VideoWidgetOnce final: public VideoWidgetBase | class VideoWidgetOnce final: public VideoWidgetBase | ||||||
| { | { | ||||||
| 	std::function<void()> callback; | 	IVideoHolder * owner; | ||||||
|  |  | ||||||
| 	void onPlaybackFinished() final; | 	void onPlaybackFinished() final; | ||||||
| public: | public: | ||||||
| 	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, 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, const std::function<void()> & callback); | 	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, IVideoHolder * owner); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -498,20 +498,20 @@ void CSpellWindow::turnPageLeft() | |||||||
| { | { | ||||||
| 	OBJECT_CONSTRUCTION; | 	OBJECT_CONSTRUCTION; | ||||||
| 	if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook) | 	if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook) | ||||||
| 		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNLFT.SMK"), false, [this](){ | 		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNLFT.SMK"), false, this); | ||||||
| 			video.reset(); |  | ||||||
| 			redraw(); |  | ||||||
| 		}); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void CSpellWindow::turnPageRight() | void CSpellWindow::turnPageRight() | ||||||
| { | { | ||||||
| 	OBJECT_CONSTRUCTION; | 	OBJECT_CONSTRUCTION; | ||||||
| 	if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook) | 	if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook) | ||||||
| 		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNRGH.SMK"), false, [this](){ | 		video = std::make_shared<VideoWidgetOnce>(Point(13, 14), VideoPath::builtin("PGTRNRGH.SMK"), false, this); | ||||||
| 			video.reset(); | } | ||||||
| 			redraw(); |  | ||||||
| 		}); | void CSpellWindow::onVideoPlaybackFinished() | ||||||
|  | { | ||||||
|  | 	video.reset(); | ||||||
|  | 	redraw(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CSpellWindow::keyPressed(EShortcut key) | void CSpellWindow::keyPressed(EShortcut key) | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "CWindowObject.h" | #include "CWindowObject.h" | ||||||
|  | #include "../widgets/IVideoHolder.h" | ||||||
|  |  | ||||||
| VCMI_LIB_NAMESPACE_BEGIN | VCMI_LIB_NAMESPACE_BEGIN | ||||||
|  |  | ||||||
| @@ -31,7 +32,7 @@ class CToggleButton; | |||||||
| class VideoWidgetOnce; | class VideoWidgetOnce; | ||||||
|  |  | ||||||
| /// The spell window | /// The spell window | ||||||
| class CSpellWindow : public CWindowObject | class CSpellWindow : public CWindowObject, public IVideoHolder | ||||||
| { | { | ||||||
| 	class SpellArea : public CIntObject | 	class SpellArea : public CIntObject | ||||||
| 	{ | 	{ | ||||||
| @@ -116,6 +117,8 @@ class CSpellWindow : public CWindowObject | |||||||
| 	void turnPageLeft(); | 	void turnPageLeft(); | ||||||
| 	void turnPageRight(); | 	void turnPageRight(); | ||||||
|  |  | ||||||
|  | 	void onVideoPlaybackFinished() override; | ||||||
|  |  | ||||||
| 	bool openOnBattleSpells; | 	bool openOnBattleSpells; | ||||||
| 	std::function<void(SpellID)> onSpellSelect; //external processing of selected spell | 	std::function<void(SpellID)> onSpellSelect; //external processing of selected spell | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1670,22 +1670,27 @@ VideoWindow::VideoWindow(const VideoPath & video, const ImagePath & rim, bool sh | |||||||
| 	if(!rim.empty()) | 	if(!rim.empty()) | ||||||
| 	{ | 	{ | ||||||
| 		setBackground(rim); | 		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)); | 		pos = center(Rect(0, 0, 800, 600)); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		blackBackground = std::make_shared<GraphicalPrimitiveCanvas>(Rect(0, 0, GH.screenDimensions().x, GH.screenDimensions().y)); | 		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)); | 		pos = center(Rect(0, 0, videoPlayer->pos.w, videoPlayer->pos.h)); | ||||||
| 		blackBackground->addBox(Point(0, 0), Point(pos.x, pos.y), Colors::BLACK); | 		blackBackground->addBox(Point(0, 0), Point(pos.x, pos.y), Colors::BLACK); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(backgroundAroundWindow) | 	if(backgroundAroundWindow) | ||||||
| 		backgroundAroundWindow->pos.moveTo(Point(0, 0)); | 		backgroundAroundWindow->pos.moveTo(Point(0, 0)); | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void VideoWindow::onVideoPlaybackFinished() | ||||||
|  | { | ||||||
|  | 	exit(false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void VideoWindow::exit(bool skipped) | void VideoWindow::exit(bool skipped) | ||||||
| { | { | ||||||
| 	close(); | 	close(); | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ | |||||||
| #include "CWindowObject.h" | #include "CWindowObject.h" | ||||||
| #include "../lib/ResourceSet.h" | #include "../lib/ResourceSet.h" | ||||||
| #include "../widgets/Images.h" | #include "../widgets/Images.h" | ||||||
|  | #include "../widgets/IVideoHolder.h" | ||||||
|  |  | ||||||
| VCMI_LIB_NAMESPACE_BEGIN | VCMI_LIB_NAMESPACE_BEGIN | ||||||
|  |  | ||||||
| @@ -509,7 +510,7 @@ public: | |||||||
| 	CThievesGuildWindow(const CGObjectInstance * _owner); | 	CThievesGuildWindow(const CGObjectInstance * _owner); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class VideoWindow : public CWindowObject | class VideoWindow : public CWindowObject, public IVideoHolder | ||||||
| { | { | ||||||
| 	std::shared_ptr<VideoWidgetOnce> videoPlayer; | 	std::shared_ptr<VideoWidgetOnce> videoPlayer; | ||||||
| 	std::shared_ptr<CFilledTexture> backgroundAroundWindow; | 	std::shared_ptr<CFilledTexture> backgroundAroundWindow; | ||||||
| @@ -517,6 +518,7 @@ class VideoWindow : public CWindowObject | |||||||
|  |  | ||||||
| 	std::function<void(bool)> closeCb; | 	std::function<void(bool)> closeCb; | ||||||
|  |  | ||||||
|  | 	void onVideoPlaybackFinished() override; | ||||||
| 	void exit(bool skipped); | 	void exit(bool skipped); | ||||||
| public: | public: | ||||||
| 	VideoWindow(const VideoPath & video, const ImagePath & rim, bool showBackground, float scaleFactor, const std::function<void(bool)> & closeCb); | 	VideoWindow(const VideoPath & video, const ImagePath & rim, bool showBackground, float scaleFactor, const std::function<void(bool)> & closeCb); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user