1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +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/CArtifactsOfHeroMarket.h
widgets/CArtifactsOfHeroBackpack.h
widgets/IVideoHolder.h
widgets/RadialMenu.h
widgets/VideoWidget.h
widgets/markets/CAltarArtifacts.h

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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