1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-23 12:08:45 +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:
Ivan Savenko 2024-12-07 15:37:03 +00:00
parent 3115894307
commit 9fbeacb688
10 changed files with 66 additions and 25 deletions

View File

@ -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

View File

@ -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();

View File

@ -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);

View 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;
};

View File

@ -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();
} }

View File

@ -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);
}; };

View File

@ -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)

View File

@ -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

View File

@ -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();

View File

@ -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);