[Scummvm-git-logs] scummvm master -> f0772668a7011da60b0f36d6e3738f72ef060ba4

sev- noreply at scummvm.org
Thu Oct 22 17:00:08 UTC 2026


This automated email contains information about 30 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
73b4e53b30 HAROLD: BCB: Register game detection and bootstrap the engine
6495145e19 HAROLD: BCB: Load program and disc resources
c5634b525e HAROLD: BCB: Add BCB control and resource tables
c459bdf1a4 HAROLD: BCB: Load game state and interpret script expressions
5474e8358a HAROLD: BCB: Load executable text and Japanese and Korean fonts
d3942269c2 HAROLD: BCB: Play interface sounds and background audio
aef791fa10 HAROLD: BCB: Display the logo and title menu
c8ea2ba021 HAROLD: BCB: Play numbered movies and dispatch completion
9ae51aa779 HAROLD: BCB: Read timed captions from all.bin
22dec7d6fa HAROLD: BCB: Render captions and restore their backdrop
07c82a3499 HAROLD: BCB: Add movie controls and queued replay
ed1eb0c917 HAROLD: BCB: Draw the clock and apply elapsed game time
b95075a087 HAROLD: BCB: Compose conversation screens and backing
d965780f3b HAROLD: BCB: Display and select conversation choices
c3d8e70c72 HAROLD: BCB: Add map travel and gated destinations
a193a2927d HAROLD: BCB: Display chapter stills and game over
c3aded185b HAROLD: BCB: Dispatch script requests to game screens
b5b73fb8db HAROLD: BCB: Add computer categories and evidence icons
35467ac424 HAROLD: BCB: Display computer pages and transitions
09f13dc114 HAROLD: BCB: Handle computer navigation and page input
65e7fb2b8c HAROLD: BCB: Save and restore BCB game state
5e7eee3119 HAROLD: BCB: Add the in-game system menu
e70c430195 HAROLD: BCB: Add persistent volume controls
e317656c71 HAROLD: BCB: Add save slot lists and confirmation dialogs
f38062681f HAROLD: BCB: Connect game input and screen timers
d88edb28e2 HAROLD: BCB: Register engine keymaps and debugger commands
3a257cfdd1 HAROLD: BCB: Reduce rendering, font and menu audio overhead
e7e04ce66d HAROLD: BCB: Fix volume input and open System with mapped Escape
ac076b0fa2 HAROLD: BCB: Rename game to match official naming
f0772668a7 HAROLD: BCB: Use shared font loading and address engine integration review


Commit: 73b4e53b3083489b2c2eaeda9c1d646a9215a44a
    https://github.com/scummvm/scummvm/commit/73b4e53b3083489b2c2eaeda9c1d646a9215a44a
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Register game detection and bootstrap the engine

Changed paths:
  A engines/harold/bcb/bcb.cpp
  A engines/harold/bcb/bcb.h
  A engines/harold/bcb/input.cpp
  A engines/harold/bcb/ui.h
  A engines/harold/configure.engine
  A engines/harold/detection.cpp
  A engines/harold/detection.h
  A engines/harold/detection_tables.h
  A engines/harold/metaengine.cpp
  A engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
new file mode 100644
index 00000000000..7b7e0a924b0
--- /dev/null
+++ b/engines/harold/bcb/bcb.cpp
@@ -0,0 +1,73 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "harold/bcb/bcb.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "engines/util.h"
+#include "graphics/cursorman.h"
+#include "graphics/font.h"
+#include "graphics/fonts/ttf.h"
+#include "graphics/sjis.h"
+#include "graphics/surface.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+static const int kCaptionTextNativeCharacterHeight = 18;
+static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
+
+BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
+	: Engine(syst), _gameDescription(desc) {
+}
+
+BCBEngine::~BCBEngine() {
+}
+
+Common::Error BCBEngine::run() {
+	const Graphics::PixelFormat screenFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
+
+	initGraphics(640, 480, &screenFormat);
+	CursorMan.lock(false);
+	CursorMan.setDefaultArrowCursor();
+	CursorMan.showMouse(true);
+	g_system->updateScreen();
+	_lastCursorRefreshMillis = g_system->getMillis();
+
+	mainLoop();
+	return Common::kNoError;
+}
+
+void BCBEngine::mainLoop() {
+	while (!shouldQuit()) {
+		bool screenUpdated = false;
+
+		if (!pollEvents())
+			return;
+
+		serviceCursorRefresh(screenUpdated);
+
+		delayWithEventPolling(10);
+	}
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
new file mode 100644
index 00000000000..24f494beace
--- /dev/null
+++ b/engines/harold/bcb/bcb.h
@@ -0,0 +1,81 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_H
+#define BCB_H
+
+#include "audio/mixer.h"
+#include "common/archive.h"
+#include "common/array.h"
+#include "common/events.h"
+#include "common/fs.h"
+#include "common/rect.h"
+#include "common/str-enc.h"
+#include "common/ustr.h"
+#include "engines/engine.h"
+#include "harold/detection.h"
+#include "harold/bcb/ui.h"
+
+namespace Graphics {
+class Font;
+class FontSJIS;
+struct Surface;
+}
+
+namespace Video {
+class AVIDecoder;
+}
+
+namespace BCB {
+
+enum {
+	kBCBDebugGeneral = 1
+};
+
+class Debugger;
+
+class BCBEngine : public Engine {
+	friend class Debugger;
+
+public:
+	BCBEngine(OSystem *syst, const HaroldGameDescription *desc);
+	~BCBEngine();
+
+	Common::Error run() override;
+
+private:
+	void handleLeftButtonDown(const Common::Point &mousePosition);
+	bool pollEvents();
+	bool pollEvents(bool handleClicks);
+	void serviceCursorRefresh(bool screenUpdated = false);
+	void delayWithEventPolling(uint32 millis);
+	void delayWithEventPolling(uint32 millis, bool handleClicks);
+	void mainLoop();
+
+	const HaroldGameDescription *_gameDescription;
+	uint32 _lastCursorRefreshMillis = 0;
+	Common::Point _mousePosition;
+	bool _suppressClickHandling = false;
+};
+
+} // End of namespace BCB
+
+#endif // BCB_H
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
new file mode 100644
index 00000000000..c506e28215d
--- /dev/null
+++ b/engines/harold/bcb/input.cpp
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/events.h"
+#include "common/system.h"
+#include "harold/bcb/bcb.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+static const StaticRect kMainSceneMciControlHitRect = {16, 48, 416, 248};
+static const StaticRect kTitleMciControlHitRect = {120, 100, 520, 300};
+static const StaticRect kMovieQueueControlHitRect = {341, 414, 407, 452};
+static const StaticRect kSystemMenuControlHitRect = {265, 414, 330, 452};
+static const uint32 kCursorRefreshMillis = 10;
+static const int kMovieControlStripNormalSourceX = 0;
+static const int kMovieControlStripSkipPressedSourceX = 0x98;
+static const int kMovieControlStripPausePressedSourceX = 0x130;
+
+void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+}
+
+bool BCBEngine::pollEvents() {
+	return pollEvents(true);
+}
+
+bool BCBEngine::pollEvents(bool handleClicks) {
+	Common::Event event;
+
+	while (g_system->getEventManager()->pollEvent(event)) {
+		switch (event.type) {
+		case Common::EVENT_QUIT:
+		case Common::EVENT_RETURN_TO_LAUNCHER:
+			quitGame();
+			return false;
+		case Common::EVENT_MOUSEMOVE:
+			_mousePosition = event.mouse;
+			break;
+		case Common::EVENT_LBUTTONDOWN:
+			_mousePosition = event.mouse;
+			if (handleClicks && !_suppressClickHandling)
+				handleLeftButtonDown(_mousePosition);
+			break;
+		default:
+			break;
+		}
+	}
+
+	return !shouldQuit();
+}
+
+void BCBEngine::serviceCursorRefresh(bool screenUpdated) {
+	const uint32 now = g_system->getMillis();
+
+	if (screenUpdated) {
+		_lastCursorRefreshMillis = now;
+		return;
+	}
+
+	if (now - _lastCursorRefreshMillis >= kCursorRefreshMillis) {
+		_lastCursorRefreshMillis = now;
+		g_system->updateScreen();
+	}
+}
+
+void BCBEngine::delayWithEventPolling(uint32 millis) {
+	delayWithEventPolling(millis, true);
+}
+
+void BCBEngine::delayWithEventPolling(uint32 millis, bool handleClicks) {
+	const uint32 start = g_system->getMillis();
+
+	while (!shouldQuit() && g_system->getMillis() - start < millis) {
+		pollEvents(handleClicks);
+		serviceCursorRefresh();
+
+		const uint32 elapsed = g_system->getMillis() - start;
+		if (elapsed >= millis)
+			break;
+
+		g_system->delayMillis(MIN<uint32>(10, millis - elapsed));
+	}
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/ui.h b/engines/harold/bcb/ui.h
new file mode 100644
index 00000000000..839d8e1bf6c
--- /dev/null
+++ b/engines/harold/bcb/ui.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_UI_H
+#define BCB_UI_H
+
+#include "common/rect.h"
+
+namespace BCB {
+
+struct StaticRect {
+	int16 left;
+	int16 top;
+	int16 right;
+	int16 bottom;
+
+	operator Common::Rect() const { return Common::Rect(left, top, right, bottom); }
+	bool contains(const Common::Point &point) const { return Common::Rect(left, top, right, bottom).contains(point); }
+	int16 width() const { return right - left; }
+	int16 height() const { return bottom - top; }
+};
+
+extern const StaticRect kMapSubmodeMciControlHitRect;
+extern const StaticRect kMapSubmodeMovieBackdropRect;
+extern const int kMapSubmodeMovieBackdropSourceX;
+extern const int kMapSubmodeMovieBackdropSourceY;
+
+struct ButtonControlRecord {
+	int controlId;
+	int x;
+	int y;
+	int width;
+	int height;
+};
+
+struct StaticHitRect {
+	int16 left;
+	int16 top;
+	int16 right;
+	int16 bottom;
+
+	bool contains(const Common::Point &point) const { return Common::Rect(left, top, right, bottom).contains(point); }
+};
+
+} // End of namespace BCB
+
+#endif // BCB_UI_H
diff --git a/engines/harold/configure.engine b/engines/harold/configure.engine
new file mode 100644
index 00000000000..14c4908336b
--- /dev/null
+++ b/engines/harold/configure.engine
@@ -0,0 +1,4 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
+add_engine harold "J.B. Harold series" no "bcb"
+add_engine bcb "Blue Chicago Blues" no "" "" "" "msmpeg4"
diff --git a/engines/harold/detection.cpp b/engines/harold/detection.cpp
new file mode 100644
index 00000000000..fbe08e10e6f
--- /dev/null
+++ b/engines/harold/detection.cpp
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "base/plugins.h"
+#include "engines/advancedDetector.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/detection.h"
+#include "harold/detection_tables.h"
+
+static const PlainGameDescriptor haroldGames[] = {
+	{"bcb", "Blue Chicago Blues"},
+	{nullptr, nullptr}};
+
+static const DebugChannelDef debugFlagList[] = {
+	{BCB::kBCBDebugGeneral, "general", "General"},
+	DEBUG_CHANNEL_END};
+
+class HaroldMetaEngineDetection : public AdvancedMetaEngineDetection<BCB::HaroldGameDescription> {
+public:
+	HaroldMetaEngineDetection() : AdvancedMetaEngineDetection(BCB::gameDescriptions, haroldGames) {
+		_guiOptions = GUIO2(GUIO_NOMIDI, GUIO_NOSUBTITLES);
+		_maxScanDepth = 3;
+		_directoryGlobs = directoryGlobs;
+	}
+
+	const char *getName() const override {
+		return "harold";
+	}
+
+	const char *getEngineName() const override {
+		return "J.B. Harold series";
+	}
+
+	const char *getOriginalCopyright() const override {
+		return "J.B. Harold series (C) Riverhill Soft";
+	}
+
+	const DebugChannelDef *getDebugChannels() const override {
+		return debugFlagList;
+	}
+
+private:
+	static const char *const directoryGlobs[];
+};
+
+const char *const HaroldMetaEngineDetection::directoryGlobs[] = {
+	"bcb",
+	"program",
+	nullptr};
+
+REGISTER_PLUGIN_STATIC(HAROLD_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, HaroldMetaEngineDetection);
diff --git a/engines/harold/detection.h b/engines/harold/detection.h
new file mode 100644
index 00000000000..7a5fbf6fb5e
--- /dev/null
+++ b/engines/harold/detection.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef HAROLD_DETECTION_H
+#define HAROLD_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace BCB {
+
+enum HaroldGameType {
+	kHaroldGameBCB
+};
+
+struct HaroldGameDescription {
+	AD_GAME_DESCRIPTION_HELPERS(desc);
+
+	ADGameDescription desc;
+	HaroldGameType gameType;
+};
+
+} // End of namespace BCB
+
+#endif // HAROLD_DETECTION_H
diff --git a/engines/harold/detection_tables.h b/engines/harold/detection_tables.h
new file mode 100644
index 00000000000..2ec09feb116
--- /dev/null
+++ b/engines/harold/detection_tables.h
@@ -0,0 +1,62 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace BCB {
+
+static const HaroldGameDescription gameDescriptions[] = {
+	{
+		// Blue Chicago Blues Japanese Windows release.
+		{
+			"bcb",
+			nullptr,
+			AD_ENTRY5s(
+				"check.bin", "f3b5ba01c47a9fca664293c73c721826", 37094,
+				"l_tbl.bin", "dbbba1934727aa42f88754454e05ee09", 204,
+				"l_tim.bin", "497a7a5e13a6bf496f6ab63b694746f3", 816,
+				"all.bin", "49abcc9b900820dc4c0190d29b07cade", 142072,
+				"bcb.exe", "dcb035ac343adc54dfa1ddd5dbd1500a", 92672),
+			Common::JA_JPN,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE,
+			GUIO0()},
+		kHaroldGameBCB
+	},
+
+	{
+		{
+			"bcb",
+			nullptr,
+			AD_ENTRY5s(
+				"check.bin", "f3b5ba01c47a9fca664293c73c721826", 37094,
+				"l_tbl.bin", "dbbba1934727aa42f88754454e05ee09", 204,
+				"l_tim.bin", "497a7a5e13a6bf496f6ab63b694746f3", 816,
+				"all.bin", "49abcc9b900820dc4c0190d29b07cade", 142072,
+				"bcb.exe", "6eeccaa692b3012a24097738773722db", 153600),
+			Common::KO_KOR,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE,
+			GUIO0()},
+		kHaroldGameBCB
+	},
+
+	{AD_TABLE_END_MARKER, kHaroldGameBCB}};
+
+} // End of namespace BCB
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
new file mode 100644
index 00000000000..b2a5e28861c
--- /dev/null
+++ b/engines/harold/metaengine.cpp
@@ -0,0 +1,58 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/events.h"
+#include "common/translation.h"
+
+#include "backends/keymapper/action.h"
+#include "backends/keymapper/keymap.h"
+#include "backends/keymapper/standard-actions.h"
+
+#include "engines/advancedDetector.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/detection.h"
+
+class HaroldMetaEngine : public AdvancedMetaEngine<BCB::HaroldGameDescription> {
+public:
+	const char *getName() const override {
+		return "harold";
+	}
+
+	int getMaximumSaveSlot() const override { return 8; }
+	Common::Error createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const override;
+};
+
+Common::Error HaroldMetaEngine::createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const {
+	switch (gd->gameType) {
+	case BCB::kHaroldGameBCB:
+		*engine = new BCB::BCBEngine(syst, gd);
+		return Common::kNoError;
+	default:
+		return Common::kUnsupportedGameidError;
+	}
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(HAROLD)
+REGISTER_PLUGIN_DYNAMIC(HAROLD, PLUGIN_TYPE_ENGINE, HaroldMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(HAROLD, PLUGIN_TYPE_ENGINE, HaroldMetaEngine);
+#endif
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
new file mode 100644
index 00000000000..54fa5e7e465
--- /dev/null
+++ b/engines/harold/module.mk
@@ -0,0 +1,21 @@
+MODULE := engines/harold
+
+MODULE_OBJS := \
+	bcb/bcb.o \
+	bcb/input.o \
+	metaengine.o
+
+MODULE_DIRS += \
+	engines/harold \
+	engines/harold/bcb
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_HAROLD), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o


Commit: 6495145e19e21c831198f6895092517964ebf344
    https://github.com/scummvm/scummvm/commit/6495145e19e21c831198f6895092517964ebf344
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Load program and disc resources

Changed paths:
  A engines/harold/bcb/resources.cpp
  A engines/harold/bcb/script.h
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 7b7e0a924b0..998cfd95647 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -29,6 +29,7 @@
 #include "graphics/fonts/ttf.h"
 #include "graphics/sjis.h"
 #include "graphics/surface.h"
+#include "harold/bcb/script.h"
 #include "video/avi_decoder.h"
 
 namespace BCB {
@@ -41,11 +42,22 @@ BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
 }
 
 BCBEngine::~BCBEngine() {
+	if (_uiBitmap) {
+		_uiBitmap->free();
+		delete _uiBitmap;
+	}
+	if (_chipBitmap) {
+		_chipBitmap->free();
+		delete _chipBitmap;
+	}
 }
 
 Common::Error BCBEngine::run() {
 	const Graphics::PixelFormat screenFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
 
+	if (!initializeSearchPaths())
+		return Common::kPathDoesNotExist;
+
 	initGraphics(640, 480, &screenFormat);
 	CursorMan.lock(false);
 	CursorMan.setDefaultArrowCursor();
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 24f494beace..e2e0b486687 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -62,6 +62,18 @@ public:
 	Common::Error run() override;
 
 private:
+	bool initializeSearchPaths();
+	Common::SeekableReadStream *createMovieReadStream(int movieId) const;
+	bool switchActiveMediaDisc(int discNumber);
+	Graphics::Surface *loadBitmap(const char *fileName);
+	const Graphics::Surface *getUiBitmap(const char *fileName);
+	Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
+	Graphics::Surface *loadDiscBitmap(const char *relativePath) const;
+	Graphics::Surface *loadDiscBitmapFromAnyDisc(const char *relativePath) const;
+	Common::SeekableReadStream *createProgramReadStream(const char *relativePath) const;
+	Common::SeekableReadStream *createDiscReadStream(int discNumber, const char *relativePath) const;
+	Common::SeekableReadStream *createActiveDiscReadStream(const char *relativePath) const;
+	Common::SeekableReadStream *createActiveDiscHighColorReadStream(const char *relativePath) const;
 	void handleLeftButtonDown(const Common::Point &mousePosition);
 	bool pollEvents();
 	bool pollEvents(bool handleClicks);
@@ -71,6 +83,12 @@ private:
 	void mainLoop();
 
 	const HaroldGameDescription *_gameDescription;
+	Common::SearchSet _programFiles;
+	Common::SearchSet _discFiles[5];
+	Graphics::Surface *_uiBitmap = nullptr;
+	Common::String _uiBitmapName;
+	Graphics::Surface *_chipBitmap = nullptr;
+	int _activeMediaDiscNumber = 1;
 	uint32 _lastCursorRefreshMillis = 0;
 	Common::Point _mousePosition;
 	bool _suppressClickHandling = false;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index c506e28215d..8f69fb6edb5 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -22,6 +22,7 @@
 #include "common/events.h"
 #include "common/system.h"
 #include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
 #include "video/avi_decoder.h"
 
 namespace BCB {
diff --git a/engines/harold/bcb/resources.cpp b/engines/harold/bcb/resources.cpp
new file mode 100644
index 00000000000..7588cc92460
--- /dev/null
+++ b/engines/harold/bcb/resources.cpp
@@ -0,0 +1,162 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/config-manager.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "image/bmp.h"
+
+namespace BCB {
+
+bool BCBEngine::initializeSearchPaths() {
+	const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+	_programFiles.addDirectory(gameDataDir, 0, 1, false);
+	_programFiles.addSubDirectoryMatching(gameDataDir, "program", 0, 1, false);
+	_programFiles.addSubDirectoryMatching(gameDataDir, "bcb/program", 0, 2, false);
+	for (int i = 0; i < 5; ++i)
+		_discFiles[i].addSubDirectoryMatching(gameDataDir, Common::String::format("bcb%d", i + 1), 0, 8);
+
+	return _programFiles.hasFile("check.bin");
+}
+
+Common::SeekableReadStream *BCBEngine::createProgramReadStream(const char *relativePath) const {
+	return _programFiles.createReadStreamForMember(Common::Path(relativePath, '/'));
+}
+
+Common::SeekableReadStream *BCBEngine::createDiscReadStream(int discNumber, const char *relativePath) const {
+	if (discNumber < 1 || discNumber > 5)
+		return nullptr;
+
+	return _discFiles[discNumber - 1].createReadStreamForMember(Common::Path(relativePath, '/'));
+}
+
+Common::SeekableReadStream *BCBEngine::createActiveDiscReadStream(const char *relativePath) const {
+	return createDiscReadStream(CLIP<int>(_activeMediaDiscNumber, 1, 5), relativePath);
+}
+
+Common::SeekableReadStream *BCBEngine::createActiveDiscHighColorReadStream(const char *relativePath) const {
+	Common::SeekableReadStream *stream = createActiveDiscReadStream(relativePath);
+	if (stream)
+		return stream;
+
+	Common::String fallbackPath(relativePath);
+	int marker = fallbackPath.find("/16m/");
+	if (marker < 0)
+		return nullptr;
+
+	fallbackPath = fallbackPath.substr(0, marker) + "/256/" + fallbackPath.substr(marker + 5);
+	return createActiveDiscReadStream(fallbackPath.c_str());
+}
+
+Graphics::Surface *BCBEngine::loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const {
+	Common::ScopedPtr<Common::SeekableReadStream> file(stream);
+	if (!file)
+		return nullptr;
+
+	Image::BitmapDecoder decoder;
+	if (!decoder.loadStream(*file)) {
+		warning("BCBEngine::loadBitmapFromStream(): Could not decode %s", debugName);
+		return nullptr;
+	}
+
+	const Graphics::Surface *source = decoder.getSurface();
+	if (!source)
+		return nullptr;
+
+	return source->convertTo(g_system->getScreenFormat(), decoder.getPalette().data(), decoder.getPalette().size());
+}
+
+const Graphics::Surface *BCBEngine::getUiBitmap(const char *fileName) {
+	const bool isChip = !strcmp(fileName, "16mchip.bmp");
+	Graphics::Surface *&bitmap = isChip ? _chipBitmap : _uiBitmap;
+	if (bitmap && ((!isChip && _uiBitmapName != fileName) || bitmap->format != g_system->getScreenFormat())) {
+		bitmap->free();
+		delete bitmap;
+		bitmap = nullptr;
+	}
+	if (!bitmap) {
+		bitmap = loadBitmapFromStream(createProgramReadStream(fileName), fileName);
+		if (!isChip)
+			_uiBitmapName = fileName;
+	}
+	return bitmap;
+}
+
+Graphics::Surface *BCBEngine::loadBitmap(const char *fileName) {
+	if (!strcmp(fileName, "16mmain.bmp") || !strcmp(fileName, "16mmainn.bmp") ||
+		!strcmp(fileName, "16mmap.bmp") || !strcmp(fileName, "16msys.bmp")) {
+		const Graphics::Surface *source = getUiBitmap(fileName);
+		if (!source)
+			return nullptr;
+		Graphics::Surface *copy = new Graphics::Surface();
+		copy->copyFrom(*source);
+		return copy;
+	}
+	return loadBitmapFromStream(createProgramReadStream(fileName), fileName);
+}
+
+Graphics::Surface *BCBEngine::loadDiscBitmap(const char *relativePath) const {
+	return loadBitmapFromStream(createActiveDiscHighColorReadStream(relativePath), relativePath);
+}
+
+Graphics::Surface *BCBEngine::loadDiscBitmapFromAnyDisc(const char *relativePath) const {
+	for (int discNumber = 1; discNumber <= 5; ++discNumber) {
+		Common::SeekableReadStream *stream = createDiscReadStream(discNumber, relativePath);
+		if (stream)
+			return loadBitmapFromStream(stream, relativePath);
+
+		Common::String fallbackPath(relativePath);
+		const int marker = fallbackPath.find("/16m/");
+		if (marker >= 0) {
+			fallbackPath = fallbackPath.substr(0, marker) + "/256/" + fallbackPath.substr(marker + 5);
+			stream = createDiscReadStream(discNumber, fallbackPath.c_str());
+			if (stream)
+				return loadBitmapFromStream(stream, fallbackPath.c_str());
+		}
+	}
+
+	return nullptr;
+}
+
+Common::SeekableReadStream *BCBEngine::createMovieReadStream(int movieId) const {
+	const Common::String movieName = Common::String::format("mov%04d.avi", movieId);
+	const Common::String moviePath = Common::String::format("movie/%s", movieName.c_str());
+	return createActiveDiscReadStream(moviePath.c_str());
+}
+
+bool BCBEngine::switchActiveMediaDisc(int discNumber) {
+	if (discNumber < 1 || discNumber > 5)
+		return false;
+
+	Common::ArchiveMemberList discMembers;
+	if (_discFiles[discNumber - 1].listMembers(discMembers) == 0)
+		return false;
+
+	_activeMediaDiscNumber = discNumber;
+	return true;
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/script.h b/engines/harold/bcb/script.h
new file mode 100644
index 00000000000..1f6121e15d0
--- /dev/null
+++ b/engines/harold/bcb/script.h
@@ -0,0 +1,46 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_SCRIPT_H
+#define BCB_SCRIPT_H
+
+namespace BCB {
+
+enum GameStateOffset {
+	kStateDispatcherControlCode = 0x0000,
+	kStateMovieToPlayId = 0x0004,
+	kStateRequiredDiscIndex = 0x0008,
+	kStateGameClockTicks = 0x000c,
+	kStateClearDisplayBeforeMediaCheckFlag = 0x0014,
+	kStateSceneChoiceUiSetToShowId = 0x001c,
+	kStateCurrentScriptSequenceId = 0x0020,
+	kStateSelectedSceneChoiceValue = 0x0024,
+	kStateSceneChoiceExtraButtonFlag = 0x0028,
+	kStateStillImageToShowId = 0x0030,
+	kStateMovieHistory = 0x0040,
+	kStateMovieHistoryCount = 0x0088,
+	kStateComputerChoiceStateByIndex = 0x0098,
+	kStateMainSceneNightFlag = 0x0160
+};
+
+} // End of namespace BCB
+
+#endif // BCB_SCRIPT_H
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 54fa5e7e465..cdbfe187f10 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -2,6 +2,7 @@ MODULE := engines/harold
 
 MODULE_OBJS := \
 	bcb/bcb.o \
+	bcb/resources.o \
 	bcb/input.o \
 	metaengine.o
 


Commit: c5634b525eeb959cbd66c7970937577d059c6532
    https://github.com/scummvm/scummvm/commit/c5634b525eeb959cbd66c7970937577d059c6532
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add BCB control and resource tables

Changed paths:
  A engines/harold/bcb/static_data.cpp
  A engines/harold/bcb/static_data.h
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/input.cpp
    engines/harold/bcb/resources.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 998cfd95647..b669524ee2a 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -30,6 +30,7 @@
 #include "graphics/sjis.h"
 #include "graphics/surface.h"
 #include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
 #include "video/avi_decoder.h"
 
 namespace BCB {
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 8f69fb6edb5..a7595b18a0c 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -23,6 +23,7 @@
 #include "common/system.h"
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
 #include "video/avi_decoder.h"
 
 namespace BCB {
diff --git a/engines/harold/bcb/resources.cpp b/engines/harold/bcb/resources.cpp
index 7588cc92460..5d748dbc3f5 100644
--- a/engines/harold/bcb/resources.cpp
+++ b/engines/harold/bcb/resources.cpp
@@ -27,6 +27,7 @@
 #include "graphics/surface.h"
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
 #include "image/bmp.h"
 
 namespace BCB {
diff --git a/engines/harold/bcb/static_data.cpp b/engines/harold/bcb/static_data.cpp
new file mode 100644
index 00000000000..29300854e33
--- /dev/null
+++ b/engines/harold/bcb/static_data.cpp
@@ -0,0 +1,213 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "harold/bcb/static_data.h"
+
+namespace BCB {
+
+const StaticRect kMapSubmodeMciControlHitRect = {120, 56, 520, 256};
+const StaticRect kMapSubmodeMovieBackdropRect = {120, 56, 540, 342};
+const int kMapSubmodeMovieBackdropSourceX = 0;
+const int kMapSubmodeMovieBackdropSourceY = 0x210;
+
+const ButtonControlRecord kTitleButtonTable[5] = {
+	{50, 0, 0, 0, 0},
+	{51, 429, 263, 162, 32},
+	{52, 429, 302, 162, 32},
+	{53, 429, 341, 162, 32},
+	{54, 429, 386, 162, 32}};
+
+const ButtonControlRecord kSystemSubmodeButtonTable[5] = {
+	{500, 0, 0, 0, 0},
+	{501, 380, 53, 240, 44},
+	{502, 380, 120, 240, 44},
+	{503, 380, 216, 240, 44},
+	{504, 505, 404, 96, 48}};
+
+const ButtonControlRecord kSystemButtonTable[9] = {
+	{550, 0, 0, 0, 0},
+	{551, 41, 46, 288, 32},
+	{552, 41, 78, 288, 32},
+	{553, 41, 110, 288, 32},
+	{554, 41, 142, 288, 32},
+	{555, 41, 174, 288, 32},
+	{556, 41, 206, 288, 32},
+	{557, 41, 238, 288, 32},
+	{558, 41, 270, 288, 32}};
+
+const ButtonControlRecord kSceneChoiceButtonTable[10] = {
+	{100, 0, 0, 0, 0},
+	{101, 432, 48, 192, 40},
+	{102, 432, 94, 192, 40},
+	{103, 432, 140, 192, 40},
+	{104, 432, 186, 192, 40},
+	{105, 432, 232, 192, 40},
+	{106, 432, 278, 192, 40},
+	{107, 432, 324, 192, 40},
+	{108, 432, 370, 192, 40},
+	{109, 432, 416, 192, 40}};
+
+const ButtonControlRecord kMapButtonTable[19] = {
+	{200, 0, 0, 0, 0},
+	{201, 371, 359, 16, 16},
+	{202, 285, 35, 16, 16},
+	{203, 395, 187, 16, 16},
+	{204, 412, 210, 16, 16},
+	{205, 283, 245, 16, 16},
+	{206, 401, 228, 16, 16},
+	{207, 438, 254, 16, 16},
+	{208, 344, 278, 16, 16},
+	{209, 371, 301, 16, 16},
+	{210, 285, 52, 16, 16},
+	{211, 495, 408, 16, 16},
+	{212, 197, 19, 16, 16},
+	{213, 308, 164, 16, 16},
+	{214, 302, 335, 16, 16},
+	{215, 234, 115, 16, 16},
+	{216, 270, 225, 16, 16},
+	{217, 317, 392, 16, 16},
+	{218, 393, 375, 16, 16}};
+
+const int kMapButtonSecondaryStateIndexTable[19] = {
+	0, 0, 0, 41, 42, 0, 42, 44, 0, 0, 40, 0, 0, 39, 45, 46, 43, 47, 48};
+
+const ButtonControlRecord kComputerPrimaryButtonTable[2] = {
+	{600, 0, 0, 0, 0},
+	{601, 49, 40, 16, 16}};
+
+const ButtonControlRecord kComputerDetailButtonTable[3] = {
+	{750, 0, 0, 0, 0},
+	{751, 54, 50, 17, 17},
+	{752, 508, 402, 80, 24}};
+
+const ButtonControlRecord kComputerChoiceLayoutSlots[21] = {
+	{0, 0, 0, 0, 0},
+	{0, 83, 125, 64, 64},
+	{0, 171, 125, 64, 64},
+	{0, 259, 125, 64, 64},
+	{0, 347, 125, 64, 64},
+	{0, 435, 125, 64, 64},
+	{0, 83, 195, 64, 64},
+	{0, 171, 195, 64, 64},
+	{0, 259, 195, 64, 64},
+	{0, 347, 195, 64, 64},
+	{0, 435, 195, 64, 64},
+	{0, 83, 265, 64, 64},
+	{0, 171, 265, 64, 64},
+	{0, 259, 265, 64, 64},
+	{0, 347, 265, 64, 64},
+	{0, 435, 265, 64, 64},
+	{0, 83, 335, 64, 64},
+	{0, 171, 335, 64, 64},
+	{0, 259, 335, 64, 64},
+	{0, 347, 335, 64, 64},
+	{0, 435, 335, 64, 64}};
+
+const ButtonControlRecord kComputerChoiceButtonTable[30] = {
+	{701, 0, 480, 64, 64},
+	{702, 64, 480, 64, 64},
+	{703, 128, 480, 64, 64},
+	{704, 192, 480, 64, 64},
+	{705, 256, 480, 64, 64},
+	{706, 320, 480, 64, 64},
+	{707, 384, 480, 64, 64},
+	{708, 448, 480, 64, 64},
+	{709, 512, 480, 64, 64},
+	{710, 576, 480, 64, 64},
+	{711, 0, 544, 64, 64},
+	{712, 64, 544, 64, 64},
+	{713, 128, 544, 64, 64},
+	{714, 192, 544, 64, 64},
+	{715, 256, 544, 64, 64},
+	{716, 320, 544, 64, 64},
+	{717, 384, 544, 64, 64},
+	{718, 448, 544, 64, 64},
+	{719, 512, 544, 64, 64},
+	{720, 0, 0, 64, 64},
+	{721, 576, 544, 64, 64},
+	{722, 0, 608, 64, 64},
+	{723, 64, 608, 64, 64},
+	{724, 128, 608, 64, 64},
+	{725, 192, 608, 64, 64},
+	{726, 256, 608, 64, 64},
+	{727, 320, 608, 64, 64},
+	{728, 384, 608, 64, 64},
+	{729, 448, 608, 64, 64},
+	{730, 512, 608, 64, 64}};
+
+const StaticHitRect kComputerChoiceGroupHitRects[3] = {
+	{60, 67, 131, 89},
+	{137, 67, 208, 89},
+	{214, 67, 285, 89}};
+
+const byte kComputerChoiceDependencyStateIndex[30] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 30,
+	31, 32, 33, 34, 0, 0, 0, 0, 35, 0,
+	37, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+const CompositeBitmapVariantSet kComputerDetailBitmapVariantsBySelectedValue[31] = {
+	{0, {}},
+	{1, {"bmp/comp/pcom_01.bmp"}},
+	{1, {"bmp/comp/pcom_02.bmp"}},
+	{1, {"bmp/comp/pcom_03.bmp"}},
+	{1, {"bmp/comp/pcom_04.bmp"}},
+	{1, {"bmp/comp/pcom_05.bmp"}},
+	{1, {"bmp/comp/pcom_06.bmp"}},
+	{1, {"bmp/comp/pcom_07.bmp"}},
+	{1, {"bmp/comp/pcom_08.bmp"}},
+	{1, {"bmp/comp/pcom_09.bmp"}},
+	{1, {"bmp/comp/pcom_10.bmp"}},
+	{1, {"bmp/comp/pcom_11.bmp"}},
+	{1, {"bmp/comp/pcom_12.bmp"}},
+	{1, {"bmp/comp/pcom_13.bmp"}},
+	{1, {"bmp/comp/pcom_14.bmp"}},
+	{1, {"bmp/comp/pcom_15.bmp"}},
+	{1, {"bmp/comp/pcom_16.bmp"}},
+	{1, {"bmp/comp/pcom_17.bmp"}},
+	{1, {"bmp/comp/pcom_18.bmp"}},
+	{1, {"bmp/comp/pcom_19.bmp"}},
+	{1, {"bmp/comp/pcom_31a.bmp"}},
+	{1, {"bmp/comp/pcom_21.bmp"}},
+	{4, {"bmp/comp/pcom_22a.bmp", "bmp/comp/pcom_22b.bmp", "bmp/comp/pcom_22c.bmp", "bmp/comp/pcom_22d.bmp"}},
+	{4, {"bmp/comp/pcom_23a.bmp", "bmp/comp/pcom_23b.bmp", "bmp/comp/pcom_23c.bmp", "bmp/comp/pcom_23d.bmp"}},
+	{3, {"bmp/comp/pcom_24a.bmp", "bmp/comp/pcom_24b.bmp", "bmp/comp/pcom_24c.bmp"}},
+	{2, {"bmp/comp/pcom_25a.bmp", "bmp/comp/pcom_25b.bmp"}},
+	{2, {"bmp/comp/pcom_26a.bmp", "bmp/comp/pcom_26b.bmp"}},
+	{1, {"bmp/comp/pcom_27a.bmp"}},
+	{3, {"bmp/comp/pcom_28a.bmp", "bmp/comp/pcom_28b.bmp", "bmp/comp/pcom_28c.bmp"}},
+	{3, {"bmp/comp/pcom_29a.bmp", "bmp/comp/pcom_29b.bmp", "bmp/comp/pcom_29c.bmp"}},
+	{1, {"bmp/comp/pcom_30a.bmp"}}};
+
+const byte kSceneChoiceValueByButtonIndex[10] = {0, 1, 2, 4, 8, 16, 32, 64, 128, 255};
+
+const int kStillImageDurationTicks[8] = {100, 100, 100, 100, 100, 600, 600, 200};
+
+const char *const kStillImageHighColorPaths[8] = {
+	"bmp/still/16m/pstill_1.bmp",
+	"bmp/still/16m/pstill_2.bmp",
+	"bmp/still/16m/pstill_3.bmp",
+	"bmp/still/16m/pstill_4.bmp",
+	"bmp/still/16m/pstill_5.bmp",
+	"bmp/still/16m/pbad.bmp",
+	"bmp/still/16m/pgood.bmp",
+	"bmp/still/16m/pstaff.bmp"};
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/static_data.h b/engines/harold/bcb/static_data.h
new file mode 100644
index 00000000000..a7a9012561f
--- /dev/null
+++ b/engines/harold/bcb/static_data.h
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_STATIC_DATA_H
+#define BCB_STATIC_DATA_H
+
+#include "common/rect.h"
+#include "common/scummsys.h"
+#include "harold/bcb/ui.h"
+
+namespace BCB {
+
+struct CompositeBitmapVariantSet {
+	uint pathCount;
+	const char *paths[4];
+};
+
+extern const ButtonControlRecord kTitleButtonTable[5];
+extern const ButtonControlRecord kSystemSubmodeButtonTable[5];
+extern const ButtonControlRecord kSystemButtonTable[9];
+extern const ButtonControlRecord kSceneChoiceButtonTable[10];
+extern const ButtonControlRecord kMapButtonTable[19];
+extern const int kMapButtonSecondaryStateIndexTable[19];
+extern const ButtonControlRecord kComputerPrimaryButtonTable[2];
+extern const ButtonControlRecord kComputerDetailButtonTable[3];
+extern const ButtonControlRecord kComputerChoiceLayoutSlots[21];
+extern const ButtonControlRecord kComputerChoiceButtonTable[30];
+extern const StaticHitRect kComputerChoiceGroupHitRects[3];
+extern const byte kComputerChoiceDependencyStateIndex[30];
+extern const CompositeBitmapVariantSet kComputerDetailBitmapVariantsBySelectedValue[31];
+extern const byte kSceneChoiceValueByButtonIndex[10];
+extern const int kStillImageDurationTicks[8];
+extern const char *const kStillImageHighColorPaths[8];
+
+} // End of namespace BCB
+
+#endif // BCB_STATIC_DATA_H
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index cdbfe187f10..d6b3f28b8f7 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
 	bcb/bcb.o \
 	bcb/resources.o \
 	bcb/input.o \
+	bcb/static_data.o \
 	metaengine.o
 
 MODULE_DIRS += \


Commit: c459bdf1a45338c7d9eae835be4c67c55f5a013c
    https://github.com/scummvm/scummvm/commit/c459bdf1a45338c7d9eae835be4c67c55f5a013c
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Load game state and interpret script expressions

Changed paths:
  A engines/harold/bcb/scene.cpp
  A engines/harold/bcb/script.cpp
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index b669524ee2a..9294751ec00 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -66,6 +66,11 @@ Common::Error BCBEngine::run() {
 	g_system->updateScreen();
 	_lastCursorRefreshMillis = g_system->getMillis();
 
+	if (!loadScriptData())
+		return Common::kReadingFailed;
+
+	resetGameState();
+
 	mainLoop();
 	return Common::kNoError;
 }
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index e2e0b486687..15e2885202f 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -65,6 +65,19 @@ private:
 	bool initializeSearchPaths();
 	Common::SeekableReadStream *createMovieReadStream(int movieId) const;
 	bool switchActiveMediaDisc(int discNumber);
+	void resetGameState();
+	bool loadScriptData();
+	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
+	void executeCurrentScriptSequence();
+	uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
+	bool pushScriptValue(uint32 value);
+	uint32 popScriptValue();
+	void popScriptOperands(uint32 &leftValue, uint32 &rightValue);
+	uint32 dereferenceScriptValue(uint32 value);
+	uint32 &scriptOperandDword(uint32 encodedOperand);
+	uint32 &stateDword(uint byteOffset);
+	uint32 stateDword(uint byteOffset) const;
+	void notifyGameClockTicksChanged();
 	Graphics::Surface *loadBitmap(const char *fileName);
 	const Graphics::Surface *getUiBitmap(const char *fileName);
 	Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
@@ -85,12 +98,34 @@ private:
 	const HaroldGameDescription *_gameDescription;
 	Common::SearchSet _programFiles;
 	Common::SearchSet _discFiles[5];
+	Common::Array<byte> _scriptData;
+	Common::Array<byte> _locationTableData;
+	Common::Array<byte> _locationTimingData;
+	Common::Array<byte> _allBinData;
+	Common::Array<byte> _timedCaptionRecordData;
 	Graphics::Surface *_uiBitmap = nullptr;
 	Common::String _uiBitmapName;
 	Graphics::Surface *_chipBitmap = nullptr;
+	uint32 _gameState[0x100] = {};
+	uint32 _scriptValueStack[0x100] = {};
+	int _scriptStackDepth = 0;
+	bool _scriptStopFlag = false;
+	int _pendingStillElapsedTimerTicks = 0;
+	int _gameOverElapsedTimerTicks = 0;
+	int _pendingStillDurationIndex = 0;
+	int _pendingClockTickDelta = 0;
 	int _activeMediaDiscNumber = 1;
+	uint _timedCaptionCursorOffset = 0;
+	int _nextTimedCaptionThreshold = 99999;
+	int _queuedMoviePlaybackIndex = 0;
+	int _previousDisplayStateBeforeSystem = 0;
+	int _sceneChoiceTerminatorButtonIndex = 0;
+	int _sceneChoiceEnabledButtonLimit = 0;
+	Common::U32String _timedCaptionText;
+	int _lastLoggedGameClockTicks = -1;
 	uint32 _lastCursorRefreshMillis = 0;
 	Common::Point _mousePosition;
+	bool _sceneChoiceActive = false;
 	bool _suppressClickHandling = false;
 };
 
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
new file mode 100644
index 00000000000..51238c7d389
--- /dev/null
+++ b/engines/harold/bcb/scene.cpp
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+#include "video/avi_decoder.h"
+
+#include <math.h>
+
+namespace BCB {
+
+void BCBEngine::notifyGameClockTicksChanged() {
+	const int ticks = stateDword(kStateGameClockTicks);
+	if (_lastLoggedGameClockTicks == ticks)
+		return;
+
+	_lastLoggedGameClockTicks = ticks;
+	const int clockTicks = ticks % 1440;
+	const int dayOfMonth = 10 + CLIP<int>(stateDword(kStateRequiredDiscIndex), 0, 4);
+	const int hour = clockTicks / 60;
+	const int minute = clockTicks % 60;
+	const Common::String message = Common::String::format("BCB clock: 11/%02d %02d:%02d (ticks=%d / 0x%04x)\n", dayOfMonth, hour, minute, ticks, ticks);
+	g_system->logMessage(LogMessageType::kInfo, message.c_str());
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/script.cpp b/engines/harold/bcb/script.cpp
new file mode 100644
index 00000000000..42f10090a70
--- /dev/null
+++ b/engines/harold/bcb/script.cpp
@@ -0,0 +1,287 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/file.h"
+#include "common/fs.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+
+namespace BCB {
+
+static const uint32 kScriptFalse = 0xc000;
+static const uint32 kScriptTrue = 0xffff;
+static const uint32 kScriptEndExpression = 0x7fff;
+static const uint32 kScriptSecondaryStateByteOffset = 0x98;
+
+bool BCBEngine::loadScriptData() {
+	if (!loadByteFile("check.bin", _scriptData))
+		return false;
+
+	if (!loadByteFile("l_tbl.bin", _locationTableData))
+		return false;
+
+	if (!loadByteFile("l_tim.bin", _locationTimingData))
+		return false;
+
+	if (!loadByteFile("all.bin", _allBinData))
+		return false;
+
+	return true;
+}
+
+bool BCBEngine::loadByteFile(const char *fileName, Common::Array<byte> &data) const {
+	Common::ScopedPtr<Common::SeekableReadStream> stream(createProgramReadStream(fileName));
+	if (!stream)
+		return false;
+
+	const int32 fileSize = stream->size();
+	if (fileSize <= 0)
+		return false;
+
+	data.resize(fileSize);
+	return stream->read(&data[0], fileSize) == (uint32)fileSize;
+}
+
+void BCBEngine::resetGameState() {
+	Common::fill(&_gameState[0], &_gameState[ARRAYSIZE(_gameState)], 0);
+	stateDword(kStateGameClockTicks) = 0x474;
+	stateDword(kStateCurrentScriptSequenceId) = 1;
+	_pendingStillElapsedTimerTicks = 0;
+	_pendingStillDurationIndex = 0;
+	_gameOverElapsedTimerTicks = 0;
+	_pendingClockTickDelta = 0;
+	_sceneChoiceActive = false;
+	_sceneChoiceTerminatorButtonIndex = 0;
+	_sceneChoiceEnabledButtonLimit = 0;
+	_timedCaptionRecordData.clear();
+	_timedCaptionCursorOffset = 0;
+	_nextTimedCaptionThreshold = 99999;
+	_queuedMoviePlaybackIndex = 0;
+	_previousDisplayStateBeforeSystem = 0;
+	_timedCaptionText.clear();
+	_scriptStopFlag = false;
+}
+
+void BCBEngine::executeCurrentScriptSequence() {
+	stateDword(kStateDispatcherControlCode) = 0;
+
+	const uint sequenceId = stateDword(kStateCurrentScriptSequenceId);
+	const uint sequenceTableOffset = sequenceId * 2 - 2;
+	if (sequenceId == 0 || sequenceTableOffset + 1 >= _scriptData.size()) {
+		warning("BCBEngine::executeCurrentScriptSequence(): Invalid sequence id %u", sequenceId);
+		_scriptStopFlag = true;
+		return;
+	}
+
+	uint32 scriptPc = _scriptData[sequenceTableOffset] | (_scriptData[sequenceTableOffset + 1] << 8);
+	while (!_scriptStopFlag) {
+		int branchTokenSeen = -1;
+		const uint32 result = executeScriptExpression(scriptPc, branchTokenSeen);
+		if (_scriptStopFlag || branchTokenSeen != 0)
+			break;
+
+		if (scriptPc + 1 >= _scriptData.size()) {
+			_scriptStopFlag = true;
+			break;
+		}
+
+		const int branchOffset = (int8)_scriptData[scriptPc] + (int8)_scriptData[scriptPc + 1] * 0x100;
+		if ((result & 0x1fff) == 0)
+			scriptPc += branchOffset;
+		else
+			scriptPc += 2;
+	}
+}
+
+uint32 BCBEngine::executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen) {
+	_scriptStackDepth = 0;
+
+	while (!_scriptStopFlag) {
+		if (scriptPc + 1 >= _scriptData.size()) {
+			_scriptStopFlag = true;
+			return 0xffffffff;
+		}
+
+		const uint32 token = _scriptData[scriptPc] | (_scriptData[scriptPc + 1] << 8);
+		scriptPc += 2;
+
+		if (((token >> 8) & 0xc0) != 0x40) {
+			pushScriptValue(token);
+			continue;
+		}
+
+		if (token == kScriptEndExpression) {
+			if (_scriptStackDepth == 0)
+				_scriptStackDepth = 1;
+			return popScriptValue();
+		}
+
+		const uint opcode = token & 0xff;
+		uint32 leftValue = 0;
+		uint32 rightValue = 0;
+		switch (opcode) {
+		case 0x00:
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue & leftValue);
+			break;
+		case 0x01:
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue | leftValue);
+			break;
+		case 0x02:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue == leftValue ? kScriptTrue : kScriptFalse);
+			break;
+		case 0x03:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue != leftValue ? kScriptTrue : kScriptFalse);
+			break;
+		case 0x04: {
+			uint32 assignedValue = popScriptValue();
+			assignedValue = dereferenceScriptValue(assignedValue);
+			const uint32 encodedOperand = popScriptValue();
+			if (((encodedOperand >> 8) & 0xc0) != 0x80) {
+				_scriptStopFlag = true;
+				return 0xffffffff;
+			}
+			const uint byteOffset = (encodedOperand & 0x1ffe) * 2;
+			const bool writesPrimaryState = (encodedOperand & 0x2000) == 0;
+			scriptOperandDword(encodedOperand) = assignedValue;
+			if (writesPrimaryState && byteOffset == kStateGameClockTicks)
+				notifyGameClockTicksChanged();
+			pushScriptValue(assignedValue == 0 ? kScriptFalse : kScriptTrue);
+			break;
+		}
+		case 0x05:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(leftValue < rightValue ? kScriptTrue : kScriptFalse);
+			break;
+		case 0x06:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(leftValue > rightValue ? kScriptTrue : kScriptFalse);
+			break;
+		case 0x07:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(leftValue <= rightValue ? kScriptTrue : kScriptFalse);
+			break;
+		case 0x08:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(leftValue >= rightValue ? kScriptTrue : kScriptFalse);
+			break;
+		case 0x09:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue & leftValue);
+			break;
+		case 0x0a:
+			branchTokenSeen = 0;
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue | leftValue);
+			break;
+		case 0x0b:
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue ^ leftValue);
+			break;
+		case 0x0c:
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(rightValue + leftValue);
+			break;
+		case 0x0d:
+			popScriptOperands(leftValue, rightValue);
+			pushScriptValue(leftValue - rightValue);
+			break;
+		default:
+			warning("BCBEngine::executeScriptExpression(): Unknown script opcode 0x%02x", opcode);
+			_scriptStopFlag = true;
+			return 0xffffffff;
+		}
+	}
+
+	return 0xffffffff;
+}
+
+bool BCBEngine::pushScriptValue(uint32 value) {
+	if (_scriptStackDepth < 0 || _scriptStackDepth >= ARRAYSIZE(_scriptValueStack)) {
+		_scriptStopFlag = true;
+		return false;
+	}
+
+	_scriptValueStack[_scriptStackDepth++] = value;
+	return true;
+}
+
+uint32 BCBEngine::popScriptValue() {
+	if (_scriptStackDepth <= 0 || _scriptStackDepth > ARRAYSIZE(_scriptValueStack)) {
+		_scriptStopFlag = true;
+		return 0xffffffff;
+	}
+
+	return _scriptValueStack[--_scriptStackDepth];
+}
+
+void BCBEngine::popScriptOperands(uint32 &leftValue, uint32 &rightValue) {
+	rightValue = dereferenceScriptValue(popScriptValue());
+	leftValue = dereferenceScriptValue(popScriptValue());
+}
+
+uint32 BCBEngine::dereferenceScriptValue(uint32 value) {
+	if (((value >> 8) & 0xc0) == 0x80)
+		return scriptOperandDword(value);
+
+	return value;
+}
+
+uint32 &BCBEngine::scriptOperandDword(uint32 encodedOperand) {
+	const uint byteOffset = (encodedOperand & 0x1ffe) * 2;
+	if ((encodedOperand & 0x2000) == 0)
+		return stateDword(byteOffset);
+
+	return stateDword(kScriptSecondaryStateByteOffset + byteOffset);
+}
+
+uint32 &BCBEngine::stateDword(uint byteOffset) {
+	if ((byteOffset & 3) || byteOffset + 4 > sizeof(_gameState)) {
+		warning("BCBEngine::stateDword(): Invalid state offset 0x%04x", byteOffset);
+		_scriptStopFlag = true;
+		return _gameState[0];
+	}
+
+	return _gameState[byteOffset / 4];
+}
+
+uint32 BCBEngine::stateDword(uint byteOffset) const {
+	if ((byteOffset & 3) || byteOffset + 4 > sizeof(_gameState))
+		return 0;
+
+	return _gameState[byteOffset / 4];
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index d6b3f28b8f7..521827de166 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -3,7 +3,9 @@ MODULE := engines/harold
 MODULE_OBJS := \
 	bcb/bcb.o \
 	bcb/resources.o \
+	bcb/scene.o \
 	bcb/input.o \
+	bcb/script.o \
 	bcb/static_data.o \
 	metaengine.o
 


Commit: 5474e8358a6e12ee2637be8d1758aa3074541f54
    https://github.com/scummvm/scummvm/commit/5474e8358a6e12ee2637be8d1758aa3074541f54
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Load executable text and Japanese and Korean fonts

Changed paths:
  A engines/harold/bcb/executable_data.cpp
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/module.mk
    graphics/sjis.h


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 9294751ec00..17b363f21d4 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -38,6 +38,36 @@ namespace BCB {
 static const int kCaptionTextNativeCharacterHeight = 18;
 static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
 
+static Graphics::Font *loadLocalizedCjkFont(Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
+	if (language == Common::KO_KOR) {
+		Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+		if (font)
+			return font;
+		font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+		if (font)
+			return font;
+		return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+	}
+
+	Graphics::Font *font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+	if (font)
+		return font;
+	return Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+}
+
+static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Language language) {
+	if (language == Common::KO_KOR) {
+		Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+		if (font)
+			return font;
+	}
+
+	Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+	if (font)
+		return font;
+	return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+}
+
 BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
 	: Engine(syst), _gameDescription(desc) {
 }
@@ -51,6 +81,10 @@ BCBEngine::~BCBEngine() {
 		_chipBitmap->free();
 		delete _chipBitmap;
 	}
+	delete _sjisFont;
+	delete _captionTextFont;
+	delete _gameOverTextFont;
+	delete _ownerDrawUiTextFont;
 }
 
 Common::Error BCBEngine::run() {
@@ -65,10 +99,19 @@ Common::Error BCBEngine::run() {
 	CursorMan.showMouse(true);
 	g_system->updateScreen();
 	_lastCursorRefreshMillis = g_system->getMillis();
+	_sjisFont = Graphics::FontSJIS::createFont();
+	_captionTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+	_gameOverTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
+	_ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(_gameDescription->desc.language);
+	if (!_sjisFont && !_captionTextFont && !_gameOverTextFont && !_ownerDrawUiTextFont)
+		warning("BCBEngine::run(): No CJK font available; captions and choice text will not render");
 
 	if (!loadScriptData())
 		return Common::kReadingFailed;
 
+	if (!loadExecutableDataTables())
+		return Common::kReadingFailed;
+
 	resetGameState();
 
 	mainLoop();
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 15e2885202f..f1c23a52226 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -67,6 +67,7 @@ private:
 	bool switchActiveMediaDisc(int discNumber);
 	void resetGameState();
 	bool loadScriptData();
+	bool loadExecutableDataTables();
 	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
 	void executeCurrentScriptSequence();
 	uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
@@ -93,6 +94,7 @@ private:
 	void serviceCursorRefresh(bool screenUpdated = false);
 	void delayWithEventPolling(uint32 millis);
 	void delayWithEventPolling(uint32 millis, bool handleClicks);
+	Common::U32String decodeExecutableTextForDisplay(const Common::String &text) const;
 	void mainLoop();
 
 	const HaroldGameDescription *_gameDescription;
@@ -103,6 +105,25 @@ private:
 	Common::Array<byte> _locationTimingData;
 	Common::Array<byte> _allBinData;
 	Common::Array<byte> _timedCaptionRecordData;
+	Common::Array<Common::Array<Common::U32String> > _sceneChoiceText;
+	Common::CodePage _executableTextEncoding = Common::kWindows932;
+	Common::String _sceneChoiceMoveTextSjis;
+	Common::U32String _sceneChoiceMoveText;
+	Common::Array<Common::String> _mapButtonTextSjis;
+	Common::Array<Common::U32String> _mapButtonText;
+	Common::Array<Common::U32String> _gameOverText;
+	Common::U32String _titleExitConfirmText;
+	Common::String _saveSlotOccupiedTextFormatSjis;
+	Common::String _saveSlotEmptyTextFormatSjis;
+	Common::String _loadSlotConfirmTextFormatSjis;
+	Common::String _saveSlotOverwriteConfirmTextFormatSjis;
+	Common::String _saveSlotEmptyConfirmTextFormatSjis;
+	Common::String _noSaveDataMessageSjis;
+	Common::String _returnToTitleConfirmTextSjis;
+	Graphics::FontSJIS *_sjisFont = nullptr;
+	const Graphics::Font *_captionTextFont = nullptr;
+	const Graphics::Font *_gameOverTextFont = nullptr;
+	const Graphics::Font *_ownerDrawUiTextFont = nullptr;
 	Graphics::Surface *_uiBitmap = nullptr;
 	Common::String _uiBitmapName;
 	Graphics::Surface *_chipBitmap = nullptr;
diff --git a/engines/harold/bcb/executable_data.cpp b/engines/harold/bcb/executable_data.cpp
new file mode 100644
index 00000000000..b5d37e61074
--- /dev/null
+++ b/engines/harold/bcb/executable_data.cpp
@@ -0,0 +1,311 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/file.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "harold/bcb/bcb.h"
+
+namespace BCB {
+
+namespace {
+
+struct PeSection {
+	uint32 virtualAddress;
+	uint32 size;
+	uint32 rawOffset;
+	uint32 rawSize;
+};
+
+struct ExecutableDataProfile {
+	Common::Language language;
+	const char *executableName;
+	Common::CodePage encoding;
+	uint32 sceneChoiceTextTableVa;
+	uint32 sceneChoiceTextTableEndVa;
+	uint32 mapButtonTextPointerTableVa;
+	uint32 gameOverTextPointerTableVa;
+	uint32 saveSlotOccupiedTextFormatVa;
+	uint32 saveSlotEmptyTextFormatVa;
+	uint32 noSaveDataMessageVa;
+	uint32 sceneChoiceMoveTextVa;
+	uint32 loadSlotConfirmTextFormatVa;
+	uint32 saveSlotOverwriteConfirmTextFormatVa;
+	uint32 saveSlotEmptyConfirmTextFormatVa;
+	uint32 returnToTitleConfirmTextVa;
+	uint32 titleExitConfirmTextVa;
+};
+
+static const ExecutableDataProfile kJapaneseExecutableDataProfile = {
+	Common::JA_JPN,
+	"bcb.exe",
+	Common::kWindows932,
+	0x00412000,
+	0x00412d20,
+	0x00412cd4,
+	0x00413648,
+	0x00416394,
+	0x004163b4,
+	0x004163bb,
+	0x004163d0,
+	0x004163d8,
+	0x00416418,
+	0x00416444,
+	0x00416460,
+	0x00416474};
+
+static const ExecutableDataProfile kKoreanExecutableDataProfile = {
+	Common::KO_KOR,
+	"bcb.exe",
+	Common::kWindows949,
+	0x00421878,
+	0x00422598,
+	0x0041e404,
+	0x0041f2a8,
+	0x00423a10,
+	0x004239f4,
+	0x00423980,
+	0x004239dc,
+	0x004239a0,
+	0x0042394c,
+	0x00423934,
+	0x004238fc,
+	0x004238cc};
+
+static const ExecutableDataProfile *getExecutableDataProfile(Common::Language language) {
+	if (language == Common::KO_KOR)
+		return &kKoreanExecutableDataProfile;
+
+	return &kJapaneseExecutableDataProfile;
+}
+
+static const uint kSceneChoiceTextSlotsPerSet = 8;
+static const uint kMapButtonTextCount = 19;
+static const uint kGameOverTextCount = 10;
+
+static bool readExecutableBytes(Common::SeekableReadStream *stream, Common::Array<byte> &data) {
+	Common::ScopedPtr<Common::SeekableReadStream> executableStream(stream);
+	if (!executableStream)
+		return false;
+
+	const int32 size = executableStream->size();
+	if (size <= 0)
+		return false;
+
+	data.resize(size);
+	return executableStream->read(&data[0], size) == (uint32)size;
+}
+
+static uint16 readUint16LE(const Common::Array<byte> &data, uint offset) {
+	return data[offset] | (data[offset + 1] << 8);
+}
+
+static uint32 readUint32LE(const Common::Array<byte> &data, uint offset) {
+	return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
+}
+
+static bool virtualAddressToFileOffset(uint32 virtualAddress, uint32 imageBase, const Common::Array<PeSection> &sections, uint &fileOffset) {
+	const uint32 relativeAddress = virtualAddress - imageBase;
+	for (uint i = 0; i < sections.size(); ++i) {
+		const PeSection &section = sections[i];
+		if (relativeAddress < section.virtualAddress || relativeAddress >= section.virtualAddress + section.size)
+			continue;
+
+		fileOffset = section.rawOffset + relativeAddress - section.virtualAddress;
+		return fileOffset < section.rawOffset + section.rawSize;
+	}
+
+	return false;
+}
+
+static bool readExecutableString(const Common::Array<byte> &executableData, uint32 virtualAddress, uint32 imageBase, const Common::Array<PeSection> &sections, Common::String &sjisText) {
+	uint textOffset = 0;
+	if (!virtualAddressToFileOffset(virtualAddress, imageBase, sections, textOffset))
+		return false;
+
+	uint textEnd = textOffset;
+	while (textEnd < executableData.size() && executableData[textEnd] != 0)
+		++textEnd;
+
+	if (textEnd == textOffset)
+		return false;
+
+	sjisText = Common::String((const char *)&executableData[textOffset], textEnd - textOffset);
+	return true;
+}
+
+static bool readExecutableString(const Common::Array<byte> &executableData, uint32 virtualAddress, uint32 imageBase, const Common::Array<PeSection> &sections, Common::CodePage encoding, Common::U32String &text) {
+	Common::String sjisText;
+	if (!readExecutableString(executableData, virtualAddress, imageBase, sections, sjisText))
+		return false;
+
+	text = Common::U32String(sjisText.c_str(), sjisText.size(), encoding);
+	return true;
+}
+
+} // End of anonymous namespace
+
+bool BCBEngine::loadExecutableDataTables() {
+	const ExecutableDataProfile &profile = *getExecutableDataProfile(_gameDescription->desc.language);
+	_executableTextEncoding = profile.encoding;
+	Common::Array<byte> executableData;
+	if (!readExecutableBytes(createProgramReadStream(profile.executableName), executableData))
+		return false;
+
+	if (executableData.size() < 0x40 || executableData[0] != 'M' || executableData[1] != 'Z')
+		return false;
+
+	const uint32 peHeaderOffset = readUint32LE(executableData, 0x3c);
+	if (peHeaderOffset + 0x18 >= executableData.size() || executableData[peHeaderOffset] != 'P' || executableData[peHeaderOffset + 1] != 'E')
+		return false;
+
+	const uint sectionCount = readUint16LE(executableData, peHeaderOffset + 6);
+	const uint optionalHeaderSize = readUint16LE(executableData, peHeaderOffset + 20);
+	const uint optionalHeaderOffset = peHeaderOffset + 24;
+	const uint sectionTableOffset = optionalHeaderOffset + optionalHeaderSize;
+	if (sectionTableOffset + sectionCount * 40 > executableData.size())
+		return false;
+
+	const uint32 imageBase = readUint32LE(executableData, optionalHeaderOffset + 28);
+	Common::Array<PeSection> sections;
+	for (uint i = 0; i < sectionCount; ++i) {
+		const uint sectionOffset = sectionTableOffset + i * 40;
+		PeSection section;
+		const uint32 virtualSize = readUint32LE(executableData, sectionOffset + 8);
+		section.virtualAddress = readUint32LE(executableData, sectionOffset + 12);
+		section.rawSize = readUint32LE(executableData, sectionOffset + 16);
+		section.rawOffset = readUint32LE(executableData, sectionOffset + 20);
+		section.size = MAX<uint32>(virtualSize, section.rawSize);
+		sections.push_back(section);
+	}
+
+	uint tableOffset = 0;
+	uint tableEndOffset = 0;
+	if (!virtualAddressToFileOffset(profile.sceneChoiceTextTableVa, imageBase, sections, tableOffset))
+		return false;
+	if (!virtualAddressToFileOffset(profile.sceneChoiceTextTableEndVa, imageBase, sections, tableEndOffset))
+		return false;
+
+	const uint setCount = (tableEndOffset - tableOffset) / (kSceneChoiceTextSlotsPerSet * 4);
+	_sceneChoiceText.clear();
+	_sceneChoiceText.resize(setCount + 1);
+
+	for (uint setId = 1; setId <= setCount; ++setId) {
+		for (uint slot = 0; slot < kSceneChoiceTextSlotsPerSet; ++slot) {
+			const uint pointerOffset = tableOffset + ((setId - 1) * kSceneChoiceTextSlotsPerSet + slot) * 4;
+			const uint32 textVa = readUint32LE(executableData, pointerOffset);
+			uint textOffset = 0;
+			if (textVa == 0 || !virtualAddressToFileOffset(textVa, imageBase, sections, textOffset))
+				break;
+
+			uint textEnd = textOffset;
+			while (textEnd < executableData.size() && executableData[textEnd] != 0)
+				++textEnd;
+			if (textEnd == textOffset)
+				break;
+
+			const char *textStart = (const char *)&executableData[textOffset];
+			_sceneChoiceText[setId].push_back(Common::U32String(textStart, textEnd - textOffset, profile.encoding));
+		}
+	}
+
+	if (!readExecutableString(executableData, profile.sceneChoiceMoveTextVa, imageBase, sections, _sceneChoiceMoveTextSjis))
+		return false;
+	_sceneChoiceMoveText = Common::U32String(_sceneChoiceMoveTextSjis.c_str(), _sceneChoiceMoveTextSjis.size(), profile.encoding);
+
+	uint mapTextTableOffset = 0;
+	if (!virtualAddressToFileOffset(profile.mapButtonTextPointerTableVa, imageBase, sections, mapTextTableOffset))
+		return false;
+
+	_mapButtonTextSjis.clear();
+	_mapButtonText.clear();
+	_mapButtonTextSjis.resize(kMapButtonTextCount);
+	_mapButtonText.resize(kMapButtonTextCount);
+	for (uint i = 0; i < kMapButtonTextCount; ++i) {
+		const uint pointerOffset = mapTextTableOffset + i * 4;
+		const uint32 textVa = readUint32LE(executableData, pointerOffset);
+		uint textOffset = 0;
+		if (textVa == 0 || !virtualAddressToFileOffset(textVa, imageBase, sections, textOffset))
+			continue;
+
+		uint textEnd = textOffset;
+		while (textEnd < executableData.size() && executableData[textEnd] != 0)
+			++textEnd;
+
+		if (textEnd == textOffset)
+			continue;
+
+		const char *textStart = (const char *)&executableData[textOffset];
+		_mapButtonTextSjis[i] = Common::String(textStart, textEnd - textOffset);
+		_mapButtonText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
+	}
+
+	uint gameOverTextTableOffset = 0;
+	if (!virtualAddressToFileOffset(profile.gameOverTextPointerTableVa, imageBase, sections, gameOverTextTableOffset))
+		return false;
+
+	_gameOverText.clear();
+	_gameOverText.resize(kGameOverTextCount);
+	for (uint i = 0; i < kGameOverTextCount; ++i) {
+		const uint pointerOffset = gameOverTextTableOffset + i * 4;
+		const uint32 textVa = readUint32LE(executableData, pointerOffset);
+		uint textOffset = 0;
+		if (textVa == 0 || !virtualAddressToFileOffset(textVa, imageBase, sections, textOffset))
+			continue;
+
+		uint textEnd = textOffset;
+		while (textEnd < executableData.size() && executableData[textEnd] != 0)
+			++textEnd;
+
+		if (textEnd == textOffset)
+			continue;
+
+		const char *textStart = (const char *)&executableData[textOffset];
+		_gameOverText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
+	}
+
+	if (!readExecutableString(executableData, profile.titleExitConfirmTextVa, imageBase, sections, profile.encoding, _titleExitConfirmText))
+		return false;
+	if (!readExecutableString(executableData, profile.saveSlotOccupiedTextFormatVa, imageBase, sections, _saveSlotOccupiedTextFormatSjis))
+		return false;
+	if (!readExecutableString(executableData, profile.saveSlotEmptyTextFormatVa, imageBase, sections, _saveSlotEmptyTextFormatSjis))
+		return false;
+	if (!readExecutableString(executableData, profile.loadSlotConfirmTextFormatVa, imageBase, sections, _loadSlotConfirmTextFormatSjis))
+		return false;
+	if (!readExecutableString(executableData, profile.saveSlotOverwriteConfirmTextFormatVa, imageBase, sections, _saveSlotOverwriteConfirmTextFormatSjis))
+		return false;
+	if (!readExecutableString(executableData, profile.saveSlotEmptyConfirmTextFormatVa, imageBase, sections, _saveSlotEmptyConfirmTextFormatSjis))
+		return false;
+	if (!readExecutableString(executableData, profile.noSaveDataMessageVa, imageBase, sections, _noSaveDataMessageSjis))
+		return false;
+	if (!readExecutableString(executableData, profile.returnToTitleConfirmTextVa, imageBase, sections, _returnToTitleConfirmTextSjis))
+		return false;
+
+	return true;
+}
+
+Common::U32String BCBEngine::decodeExecutableTextForDisplay(const Common::String &text) const {
+	return Common::U32String(text.c_str(), text.size(), _executableTextEncoding);
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 521827de166..18c8286067a 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS := \
 	bcb/scene.o \
 	bcb/input.o \
 	bcb/script.o \
+	bcb/executable_data.o \
 	bcb/static_data.o \
 	metaengine.o
 
diff --git a/graphics/sjis.h b/graphics/sjis.h
index 03df49a3c02..c801c54be57 100644
--- a/graphics/sjis.h
+++ b/graphics/sjis.h
@@ -19,13 +19,13 @@
  *
  */
 
-// The code in this file is currently used in KYRA, SCI, SCUMM, SAGA,
-// and AGOS. If none of those are enabled, we will skip compiling it.
+// The code in this file is currently used in BCB, KYRA, SCI, SCUMM,
+// SAGA, and AGOS. If none of those are enabled, we will skip compiling it.
 // We also enable this code for ScummVM builds including support
 // for dynamic engine plugins.
 // If you plan to use this code in another engine, you will have
 // to add the proper define check here.
-#if !(defined(ENABLE_KYRA) || defined(ENABLE_SCI) || defined(ENABLE_SCUMM) || defined(ENABLE_SAGA) || defined(ENABLE_AGOS) || defined(DYNAMIC_MODULES))
+#if !(defined(ENABLE_BCB) || defined(ENABLE_KYRA) || defined(ENABLE_SCI) || defined(ENABLE_SCUMM) || defined(ENABLE_SAGA) || defined(ENABLE_AGOS) || defined(DYNAMIC_MODULES))
 
 // If neither of the above mentioned is enabled, do not include the SJIS code.
 


Commit: d3942269c2bd3851db943d738826f329b4deb000
    https://github.com/scummvm/scummvm/commit/d3942269c2bd3851db943d738826f329b4deb000
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Play interface sounds and background audio

Changed paths:
  A engines/harold/bcb/video.cpp
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 17b363f21d4..98671bf39dd 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -81,10 +81,14 @@ BCBEngine::~BCBEngine() {
 		_chipBitmap->free();
 		delete _chipBitmap;
 	}
+	delete _activeMovieVideo;
+	delete _sceneIdleVideo;
+	delete _gameOverVideo;
 	delete _sjisFont;
 	delete _captionTextFont;
 	delete _gameOverTextFont;
 	delete _ownerDrawUiTextFont;
+	stopMenuBackgroundMusic();
 }
 
 Common::Error BCBEngine::run() {
@@ -125,6 +129,18 @@ void BCBEngine::mainLoop() {
 		if (!pollEvents())
 			return;
 
+		if (_menuBackgroundVideo) {
+			if (_menuBackgroundVideo->endOfVideo()) {
+				_menuBackgroundVideo->stop();
+				_menuBackgroundVideo->rewind();
+				_menuBackgroundVideo->decodeNextFrame();
+				_menuBackgroundVideo->start();
+			}
+
+			if (_menuBackgroundVideo->needsUpdate())
+				_menuBackgroundVideo->decodeNextFrame();
+		}
+
 		serviceCursorRefresh(screenUpdated);
 
 		delayWithEventPolling(10);
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index f1c23a52226..0e14c431e29 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -65,6 +65,13 @@ private:
 	bool initializeSearchPaths();
 	Common::SeekableReadStream *createMovieReadStream(int movieId) const;
 	bool switchActiveMediaDisc(int discNumber);
+	void playUiSound();
+	void startMenuBackgroundMusic();
+	void stopMenuBackgroundMusic();
+	void startGameOverMusic();
+	void stopGameOverMusic();
+	void startSceneIdleMovie();
+	void stopSceneIdleMovie();
 	void resetGameState();
 	bool loadScriptData();
 	bool loadExecutableDataTables();
@@ -124,17 +131,24 @@ private:
 	const Graphics::Font *_captionTextFont = nullptr;
 	const Graphics::Font *_gameOverTextFont = nullptr;
 	const Graphics::Font *_ownerDrawUiTextFont = nullptr;
+	Video::AVIDecoder *_menuBackgroundVideo = nullptr;
+	Video::AVIDecoder *_gameOverVideo = nullptr;
+	Video::AVIDecoder *_sceneIdleVideo = nullptr;
+	Video::AVIDecoder *_activeMovieVideo = nullptr;
 	Graphics::Surface *_uiBitmap = nullptr;
 	Common::String _uiBitmapName;
 	Graphics::Surface *_chipBitmap = nullptr;
+	Audio::SoundHandle _uiSoundHandle;
 	uint32 _gameState[0x100] = {};
 	uint32 _scriptValueStack[0x100] = {};
 	int _scriptStackDepth = 0;
 	bool _scriptStopFlag = false;
+	int _currentDisplayState = 0;
 	int _pendingStillElapsedTimerTicks = 0;
 	int _gameOverElapsedTimerTicks = 0;
 	int _pendingStillDurationIndex = 0;
 	int _pendingClockTickDelta = 0;
+	int _sceneIdleMovieTimerTicks = 0;
 	int _activeMediaDiscNumber = 1;
 	uint _timedCaptionCursorOffset = 0;
 	int _nextTimedCaptionThreshold = 99999;
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
new file mode 100644
index 00000000000..42e008b066c
--- /dev/null
+++ b/engines/harold/bcb/video.cpp
@@ -0,0 +1,136 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+void BCBEngine::playUiSound() {
+	Common::SeekableReadStream *stream = createProgramReadStream("se.wav");
+	if (!stream)
+		return;
+
+	Audio::SeekableAudioStream *wavStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
+	if (!wavStream)
+		return;
+
+	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_uiSoundHandle, wavStream, -1, Audio::Mixer::kMaxChannelVolume);
+}
+
+void BCBEngine::startMenuBackgroundMusic() {
+	stopMenuBackgroundMusic();
+
+	Common::SeekableReadStream *stream = createDiscReadStream(1, "bgm/system.avi");
+	if (!stream)
+		return;
+
+	_menuBackgroundVideo = new Video::AVIDecoder();
+	_menuBackgroundVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+	if (!_menuBackgroundVideo->loadStream(stream)) {
+		delete _menuBackgroundVideo;
+		_menuBackgroundVideo = nullptr;
+		return;
+	}
+
+	_menuBackgroundVideo->decodeNextFrame();
+	_menuBackgroundVideo->start();
+}
+
+void BCBEngine::stopMenuBackgroundMusic() {
+	if (_menuBackgroundVideo) {
+		delete _menuBackgroundVideo;
+		_menuBackgroundVideo = nullptr;
+	}
+}
+
+void BCBEngine::startGameOverMusic() {
+	stopGameOverMusic();
+
+	Common::SeekableReadStream *stream = createActiveDiscReadStream("bgm/gameover.avi");
+	if (!stream) {
+		warning("BCBEngine::startGameOverMusic(): Missing gameover.avi for active media disc %d", _activeMediaDiscNumber);
+		return;
+	}
+
+	_gameOverVideo = new Video::AVIDecoder();
+	_gameOverVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+	if (!_gameOverVideo->loadStream(stream)) {
+		warning("BCBEngine::startGameOverMusic(): Could not load gameover.avi");
+		delete _gameOverVideo;
+		_gameOverVideo = nullptr;
+		return;
+	}
+
+	_gameOverVideo->decodeNextFrame();
+	_gameOverVideo->start();
+}
+
+void BCBEngine::stopGameOverMusic() {
+	if (!_gameOverVideo)
+		return;
+
+	_gameOverVideo->stop();
+	delete _gameOverVideo;
+	_gameOverVideo = nullptr;
+}
+
+void BCBEngine::startSceneIdleMovie() {
+	if (_sceneIdleVideo || _activeMovieVideo || _currentDisplayState != 1)
+		return;
+
+	const char *idleMoviePath = stateDword(kStateMainSceneNightFlag) != 0 ? "bgm/night.avi" : "bgm/day.avi";
+	Common::SeekableReadStream *stream = nullptr;
+	for (int discNumber = 1; discNumber <= 5 && !stream; ++discNumber)
+		stream = createDiscReadStream(discNumber, idleMoviePath);
+	if (!stream)
+		return;
+
+	_sceneIdleVideo = new Video::AVIDecoder();
+	_sceneIdleVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+	if (!_sceneIdleVideo->loadStream(stream)) {
+		delete _sceneIdleVideo;
+		_sceneIdleVideo = nullptr;
+		return;
+	}
+
+	_sceneIdleVideo->setOutputPixelFormat(g_system->getScreenFormat());
+	_sceneIdleVideo->start();
+	_sceneIdleMovieTimerTicks = 0;
+}
+
+void BCBEngine::stopSceneIdleMovie() {
+	if (!_sceneIdleVideo)
+		return;
+
+	_sceneIdleVideo->stop();
+	delete _sceneIdleVideo;
+	_sceneIdleVideo = nullptr;
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 18c8286067a..78f6202fe47 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/harold
 MODULE_OBJS := \
 	bcb/bcb.o \
 	bcb/resources.o \
+	bcb/video.o \
 	bcb/scene.o \
 	bcb/input.o \
 	bcb/script.o \


Commit: aef791fa1043549711c473789c554606cf77c85d
    https://github.com/scummvm/scummvm/commit/aef791fa1043549711c473789c554606cf77c85d
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Display the logo and title menu

Changed paths:
  A engines/harold/bcb/screens.cpp
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 98671bf39dd..f5ebe22f114 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -88,6 +88,10 @@ BCBEngine::~BCBEngine() {
 	delete _captionTextFont;
 	delete _gameOverTextFont;
 	delete _ownerDrawUiTextFont;
+	if (_titleBitmap) {
+		_titleBitmap->free();
+		delete _titleBitmap;
+	}
 	stopMenuBackgroundMusic();
 }
 
@@ -118,6 +122,9 @@ Common::Error BCBEngine::run() {
 
 	resetGameState();
 
+	if (!showInitialLogoScreen())
+		return Common::kReadingFailed;
+
 	mainLoop();
 	return Common::kNoError;
 }
@@ -129,6 +136,16 @@ void BCBEngine::mainLoop() {
 		if (!pollEvents())
 			return;
 
+		if (_currentDisplayState == 10) {
+			const uint32 now = g_system->getMillis();
+			while (now - _lastTimerTick >= 50 && _currentDisplayState == 10) {
+				_lastTimerTick += 50;
+				++_initialLogoElapsedTimerTicks;
+				if (_initialLogoElapsedTimerTicks > 0x3c)
+					transitionFromLogoToTitle();
+			}
+		}
+
 		if (_menuBackgroundVideo) {
 			if (_menuBackgroundVideo->endOfVideo()) {
 				_menuBackgroundVideo->stop();
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 0e14c431e29..3132d557e89 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -63,6 +63,9 @@ public:
 
 private:
 	bool initializeSearchPaths();
+	bool showInitialLogoScreen();
+	bool showTitleScreen();
+	bool transitionFromLogoToTitle();
 	Common::SeekableReadStream *createMovieReadStream(int movieId) const;
 	bool switchActiveMediaDisc(int discNumber);
 	void playUiSound();
@@ -72,6 +75,7 @@ private:
 	void stopGameOverMusic();
 	void startSceneIdleMovie();
 	void stopSceneIdleMovie();
+	void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
 	void resetGameState();
 	bool loadScriptData();
 	bool loadExecutableDataTables();
@@ -95,6 +99,8 @@ private:
 	Common::SeekableReadStream *createDiscReadStream(int discNumber, const char *relativePath) const;
 	Common::SeekableReadStream *createActiveDiscReadStream(const char *relativePath) const;
 	Common::SeekableReadStream *createActiveDiscHighColorReadStream(const char *relativePath) const;
+	void blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame = true);
+	void drawTitleButton(uint buttonIndex, bool pressed);
 	void handleLeftButtonDown(const Common::Point &mousePosition);
 	bool pollEvents();
 	bool pollEvents(bool handleClicks);
@@ -135,6 +141,7 @@ private:
 	Video::AVIDecoder *_gameOverVideo = nullptr;
 	Video::AVIDecoder *_sceneIdleVideo = nullptr;
 	Video::AVIDecoder *_activeMovieVideo = nullptr;
+	Graphics::Surface *_titleBitmap = nullptr;
 	Graphics::Surface *_uiBitmap = nullptr;
 	Common::String _uiBitmapName;
 	Graphics::Surface *_chipBitmap = nullptr;
@@ -144,6 +151,7 @@ private:
 	int _scriptStackDepth = 0;
 	bool _scriptStopFlag = false;
 	int _currentDisplayState = 0;
+	int _initialLogoElapsedTimerTicks = 0;
 	int _pendingStillElapsedTimerTicks = 0;
 	int _gameOverElapsedTimerTicks = 0;
 	int _pendingStillDurationIndex = 0;
@@ -157,11 +165,15 @@ private:
 	int _sceneChoiceTerminatorButtonIndex = 0;
 	int _sceneChoiceEnabledButtonLimit = 0;
 	Common::U32String _timedCaptionText;
+	uint32 _lastTimerTick = 0;
 	int _lastLoggedGameClockTicks = -1;
 	uint32 _lastCursorRefreshMillis = 0;
 	Common::Point _mousePosition;
 	bool _sceneChoiceActive = false;
 	bool _suppressClickHandling = false;
+	bool _titleButtonsActive = false;
+	bool _systemButtonsVisible = false;
+	bool _titleLoadSlotsActive = false;
 };
 
 } // End of namespace BCB
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index a7595b18a0c..f2742d758a7 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -38,6 +38,30 @@ static const int kMovieControlStripSkipPressedSourceX = 0x98;
 static const int kMovieControlStripPausePressedSourceX = 0x130;
 
 void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+	if (_currentDisplayState == 10) {
+		transitionFromLogoToTitle();
+		return;
+	}
+
+	if (_currentDisplayState != 0 || !_titleButtonsActive)
+		return;
+
+	for (uint i = 1; i < ARRAYSIZE(kTitleButtonTable); ++i) {
+		const ButtonControlRecord &button = kTitleButtonTable[i];
+		const Common::Rect rect(button.x, button.y, button.x + button.width, button.y + button.height);
+		if (!rect.contains(mousePosition))
+			continue;
+
+		drawTitleButton(i, true);
+		delayWithEventPolling(80);
+
+		switch (button.controlId) {
+		default:
+			drawTitleButton(i, false);
+			break;
+		}
+		return;
+	}
 }
 
 bool BCBEngine::pollEvents() {
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
new file mode 100644
index 00000000000..3f160a72806
--- /dev/null
+++ b/engines/harold/bcb/screens.cpp
@@ -0,0 +1,144 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/system.h"
+#include "graphics/font.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+namespace BCB {
+
+static const int kGameOverTextLineGap = 2;
+
+bool BCBEngine::showInitialLogoScreen() {
+	Graphics::Surface *logo = loadBitmap("16mlogo.bmp");
+	if (!logo)
+		return false;
+
+	blitScreenWithLineReveal(*logo);
+
+	logo->free();
+	delete logo;
+
+	_currentDisplayState = 10;
+	_titleButtonsActive = false;
+	_systemButtonsVisible = false;
+	_initialLogoElapsedTimerTicks = 0;
+	_lastTimerTick = g_system->getMillis();
+	return true;
+}
+
+bool BCBEngine::showTitleScreen() {
+	stopGameOverMusic();
+	if (_titleBitmap && _titleBitmap->format != g_system->getScreenFormat()) {
+		_titleBitmap->free();
+		delete _titleBitmap;
+		_titleBitmap = nullptr;
+	}
+
+	if (!_titleBitmap)
+		_titleBitmap = loadBitmap("16mtitle.bmp");
+	if (!_titleBitmap)
+		return false;
+
+	blitScreenWithLineReveal(*_titleBitmap);
+
+	_currentDisplayState = 0;
+	_titleButtonsActive = true;
+	_systemButtonsVisible = false;
+	_titleLoadSlotsActive = false;
+	return true;
+}
+
+void BCBEngine::restoreTitleScreenRegion(const Common::Rect &rect, bool present) {
+	if (!_titleBitmap)
+		return;
+
+	const Common::Rect titleBounds(0, 0, _titleBitmap->w, _titleBitmap->h);
+	Common::Rect clippedRect = rect;
+	clippedRect.clip(titleBounds);
+	if (clippedRect.isEmpty())
+		return;
+
+	g_system->copyRectToScreen(_titleBitmap->getBasePtr(clippedRect.left, clippedRect.top), _titleBitmap->pitch,
+							   clippedRect.left, clippedRect.top, clippedRect.width(), clippedRect.height());
+	if (present)
+		g_system->updateScreen();
+}
+
+bool BCBEngine::transitionFromLogoToTitle() {
+	if (!showTitleScreen())
+		return false;
+
+	startMenuBackgroundMusic();
+	return true;
+}
+
+void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
+	if (!_titleBitmap || buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kTitleButtonTable))
+		return;
+
+	const ButtonControlRecord &button = kTitleButtonTable[buttonIndex];
+	const int sourceX = pressed ? 0xa2 : button.x;
+	const int sourceY = pressed ? (int)((buttonIndex + 0x16) * 0x20) : button.y;
+
+	if (sourceX + button.width > _titleBitmap->w || sourceY + button.height > _titleBitmap->h)
+		return;
+
+	g_system->copyRectToScreen(_titleBitmap->getBasePtr(sourceX, sourceY), _titleBitmap->pitch, button.x, button.y, button.width, button.height);
+	g_system->updateScreen();
+}
+
+void BCBEngine::blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame) {
+	const int width = MIN<int>(surface.w, 640);
+	const int height = MIN<int>(surface.h, 480);
+	const int revealSteps = (height + 1) / 2;
+
+	const uint32 revealStartMillis = g_system->getMillis();
+	int revealedSteps = 0;
+	while (revealedSteps < revealSteps && !shouldQuit()) {
+		uint32 elapsedMillis = g_system->getMillis() - revealStartMillis;
+		int targetSteps = MIN<int>(elapsedMillis / 2 + 1, revealSteps);
+		if (targetSteps <= revealedSteps) {
+			pollEvents(false);
+			serviceCursorRefresh();
+			g_system->delayMillis(1);
+			continue;
+		}
+
+		for (; revealedSteps < targetSteps; ++revealedSteps) {
+			const int topY = revealedSteps * 2;
+			const int bottomY = height - 1 - revealedSteps * 2;
+			if (topY < height)
+				g_system->copyRectToScreen(surface.getBasePtr(0, topY), surface.pitch, 0, topY, width, 1);
+			if (bottomY != topY && bottomY >= 0)
+				g_system->copyRectToScreen(surface.getBasePtr(0, bottomY), surface.pitch, 0, bottomY, width, 1);
+		}
+
+		if (presentFinalFrame || revealedSteps < revealSteps)
+			g_system->updateScreen();
+		pollEvents(false);
+	}
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 78f6202fe47..83807ec72d9 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
 	bcb/resources.o \
 	bcb/video.o \
 	bcb/scene.o \
+	bcb/screens.o \
 	bcb/input.o \
 	bcb/script.o \
 	bcb/executable_data.o \


Commit: c8ea2ba02193cad441a9be91df86e746d493513e
    https://github.com/scummvm/scummvm/commit/c8ea2ba02193cad441a9be91df86e746d493513e
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Play numbered movies and dispatch completion

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/scene.cpp
    engines/harold/bcb/screens.cpp
    engines/harold/bcb/video.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index f5ebe22f114..7e061644ced 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -158,10 +158,48 @@ void BCBEngine::mainLoop() {
 				_menuBackgroundVideo->decodeNextFrame();
 		}
 
+		if (_activeMovieVideo) {
+			bool movieUpdated = false;
+			if (_activeMovieVideo->needsUpdate()) {
+				const Graphics::Surface *frame = _activeMovieVideo->decodeNextFrame();
+				if (frame) {
+					const Common::Point moviePosition = getActiveMoviePosition();
+					g_system->copyRectToScreen(frame->getPixels(), frame->pitch, moviePosition.x, moviePosition.y, frame->w, frame->h);
+					movieUpdated = true;
+				}
+			}
+			if (movieUpdated) {
+				g_system->updateScreen();
+				screenUpdated = true;
+			}
+
+			if (_activeMovieVideo->endOfVideo()) {
+				const bool dispatchAfterEnd = _activeMovieDispatchOnFinish;
+				stopActiveMovie();
+				if (dispatchAfterEnd) {
+					_activeMovieDispatchOnFinish = false;
+					if (_currentDisplayState == 7 || _currentDisplayState == 8) {
+						processQueuedMovieSequence();
+					} else if (_currentDisplayState == 1) {
+						drawMainSceneScreen(false);
+						processGameStateDispatcher(true);
+					} else {
+						if (_currentDisplayState == 2)
+							drawClockAndChipIndicator();
+						processGameStateDispatcher(true);
+					}
+				}
+			}
+		}
+
 		serviceCursorRefresh(screenUpdated);
 
 		delayWithEventPolling(10);
 	}
 }
 
+void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
+	error("BCB: processGameStateDispatcher is not implemented");
+}
+
 } // End of namespace BCB
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 3132d557e89..30558d458b3 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -66,8 +66,11 @@ private:
 	bool showInitialLogoScreen();
 	bool showTitleScreen();
 	bool transitionFromLogoToTitle();
+	bool startNewGameFromTitle();
+	bool playNumberedMovie(int movieId, bool blankScreenMovie = false);
 	Common::SeekableReadStream *createMovieReadStream(int movieId) const;
 	bool switchActiveMediaDisc(int discNumber);
+	void stopActiveMovie();
 	void playUiSound();
 	void startMenuBackgroundMusic();
 	void stopMenuBackgroundMusic();
@@ -80,6 +83,7 @@ private:
 	bool loadScriptData();
 	bool loadExecutableDataTables();
 	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
+	void processGameStateDispatcher(bool runScriptBeforeDispatch);
 	void executeCurrentScriptSequence();
 	uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
 	bool pushScriptValue(uint32 value);
@@ -89,7 +93,13 @@ private:
 	uint32 &scriptOperandDword(uint32 encodedOperand);
 	uint32 &stateDword(uint byteOffset);
 	uint32 stateDword(uint byteOffset) const;
+	bool processPendingMovieRequest();
+	Common::Point getActiveMoviePosition() const;
+	void stopActiveMovieAndDispatch();
+	void processQueuedMovieSequence();
+	bool drawMainSceneScreen(bool lineReveal);
 	void notifyGameClockTicksChanged();
+	void drawClockAndChipIndicator(bool present = true);
 	Graphics::Surface *loadBitmap(const char *fileName);
 	const Graphics::Surface *getUiBitmap(const char *fileName);
 	Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
@@ -169,7 +179,11 @@ private:
 	int _lastLoggedGameClockTicks = -1;
 	uint32 _lastCursorRefreshMillis = 0;
 	Common::Point _mousePosition;
+	bool _activeMovieDispatchOnFinish = false;
+	bool _activeMovieOnBlankScreen = false;
+	bool _activeMoviePaused = false;
 	bool _sceneChoiceActive = false;
+	bool _mapControlsActive = false;
 	bool _suppressClickHandling = false;
 	bool _titleButtonsActive = false;
 	bool _systemButtonsVisible = false;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index f2742d758a7..039cdde6fcb 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -38,6 +38,16 @@ static const int kMovieControlStripSkipPressedSourceX = 0x98;
 static const int kMovieControlStripPausePressedSourceX = 0x130;
 
 void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+	const bool activeMovieHit =
+		(_currentDisplayState == 0 && kTitleMciControlHitRect.contains(mousePosition)) ||
+		((_currentDisplayState == 1 || _currentDisplayState == 7) && kMainSceneMciControlHitRect.contains(mousePosition)) ||
+		(_currentDisplayState == 8 && kMapSubmodeMciControlHitRect.contains(mousePosition));
+	if (_activeMovieVideo && activeMovieHit) {
+		delayWithEventPolling(160, false);
+		stopActiveMovieAndDispatch();
+		return;
+	}
+
 	if (_currentDisplayState == 10) {
 		transitionFromLogoToTitle();
 		return;
@@ -56,6 +66,9 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 		delayWithEventPolling(80);
 
 		switch (button.controlId) {
+		case 51:
+			startNewGameFromTitle();
+			break;
 		default:
 			drawTitleButton(i, false);
 			break;
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 51238c7d389..0104a0899df 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -47,4 +47,12 @@ void BCBEngine::notifyGameClockTicksChanged() {
 	g_system->logMessage(LogMessageType::kInfo, message.c_str());
 }
 
+void BCBEngine::drawClockAndChipIndicator(bool present) {
+	error("BCB: drawClockAndChipIndicator is not implemented");
+}
+
+bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
+	error("BCB: drawMainSceneScreen is not implemented");
+}
+
 } // End of namespace BCB
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 3f160a72806..3d0418a68d5 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -94,6 +94,19 @@ bool BCBEngine::transitionFromLogoToTitle() {
 	return true;
 }
 
+bool BCBEngine::startNewGameFromTitle() {
+	_titleButtonsActive = false;
+	_systemButtonsVisible = false;
+	stopMenuBackgroundMusic();
+	stopGameOverMusic();
+
+	g_system->fillScreen(g_system->getScreenFormat().RGBToColor(0, 0, 0));
+	g_system->updateScreen();
+
+	stateDword(kStateCurrentScriptSequenceId) = 1;
+	return playNumberedMovie(6, true);
+}
+
 void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
 	if (!_titleBitmap || buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kTitleButtonTable))
 		return;
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 42e008b066c..eefa74a3722 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -31,6 +31,95 @@
 
 namespace BCB {
 
+bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
+	stopSceneIdleMovie();
+	stopActiveMovie();
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+	_activeMovieOnBlankScreen = blankScreenMovie;
+
+	const Common::String movieName = Common::String::format("mov%04d.avi", movieId);
+	Common::SeekableReadStream *stream = createMovieReadStream(movieId);
+	if (!stream) {
+		warning("BCBEngine::playNumberedMovie(): Missing %s", movieName.c_str());
+		stream = createMovieReadStream(0);
+	}
+	if (!stream)
+		return false;
+
+	_activeMovieVideo = new Video::AVIDecoder();
+	_activeMovieVideo->setSoundType(Audio::Mixer::kSFXSoundType);
+	if (!_activeMovieVideo->loadStream(stream)) {
+		delete _activeMovieVideo;
+		_activeMovieVideo = nullptr;
+		return false;
+	}
+
+	_activeMovieVideo->setOutputPixelFormat(g_system->getScreenFormat());
+	_activeMovieVideo->start();
+	_activeMoviePaused = false;
+	_activeMovieDispatchOnFinish = true;
+	return true;
+}
+
+Common::Point BCBEngine::getActiveMoviePosition() const {
+	if (_currentDisplayState == 0)
+		return Common::Point(0x78, 0x64);
+	if (_currentDisplayState == 8)
+		return Common::Point(0x78, 0x38);
+	return Common::Point(0x10, 0x30);
+}
+
+void BCBEngine::stopActiveMovie() {
+	if (!_activeMovieVideo)
+		return;
+
+	_activeMovieVideo->stop();
+	delete _activeMovieVideo;
+	_activeMovieVideo = nullptr;
+	_activeMovieOnBlankScreen = false;
+	_activeMoviePaused = false;
+}
+
+void BCBEngine::stopActiveMovieAndDispatch() {
+	if (!_activeMovieVideo)
+		return;
+
+	const bool dispatchAfterStop = _activeMovieDispatchOnFinish;
+	stopActiveMovie();
+	playUiSound();
+	if (!dispatchAfterStop)
+		return;
+
+	_activeMovieDispatchOnFinish = false;
+	if (_currentDisplayState == 7 || _currentDisplayState == 8) {
+		processQueuedMovieSequence();
+	} else if (_currentDisplayState == 1) {
+		drawMainSceneScreen(false);
+		processGameStateDispatcher(true);
+	} else {
+		if (_currentDisplayState == 2)
+			drawClockAndChipIndicator();
+		processGameStateDispatcher(true);
+	}
+}
+
+bool BCBEngine::processPendingMovieRequest() {
+	const int movieId = stateDword(kStateMovieToPlayId);
+	if (movieId == 0)
+		return false;
+
+	playNumberedMovie(movieId);
+
+	uint32 &movieHistoryCount = stateDword(kStateMovieHistoryCount);
+	stateDword(kStateMovieHistory + MIN<uint32>(movieHistoryCount, 15) * 4) = movieId;
+	if (movieHistoryCount < 15)
+		++movieHistoryCount;
+
+	stateDword(kStateMovieToPlayId) = 0;
+	return true;
+}
+
 void BCBEngine::playUiSound() {
 	Common::SeekableReadStream *stream = createProgramReadStream("se.wav");
 	if (!stream)
@@ -133,4 +222,8 @@ void BCBEngine::stopSceneIdleMovie() {
 	_sceneIdleVideo = nullptr;
 }
 
+void BCBEngine::processQueuedMovieSequence() {
+	error("BCB: processQueuedMovieSequence is not implemented");
+}
+
 } // End of namespace BCB


Commit: 9ae51aa779de0837d2f9fe24e3d8b73d43bd7389
    https://github.com/scummvm/scummvm/commit/9ae51aa779de0837d2f9fe24e3d8b73d43bd7389
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Read timed captions from all.bin

Changed paths:
  A engines/harold/bcb/captions.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/video.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 30558d458b3..5a57ee547ed 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -94,6 +94,10 @@ private:
 	uint32 &stateDword(uint byteOffset);
 	uint32 stateDword(uint byteOffset) const;
 	bool processPendingMovieRequest();
+	void loadTimedCaptionRecord(int movieId);
+	void loadNextTimedCaptionText();
+	bool readTimedCaptionLine(Common::String &line);
+	const byte *findAllBinRecordData(const Common::String &recordName, uint32 &recordSize) const;
 	Common::Point getActiveMoviePosition() const;
 	void stopActiveMovieAndDispatch();
 	void processQueuedMovieSequence();
@@ -175,6 +179,7 @@ private:
 	int _sceneChoiceTerminatorButtonIndex = 0;
 	int _sceneChoiceEnabledButtonLimit = 0;
 	Common::U32String _timedCaptionText;
+	Common::String _timedCaptionTextSjis;
 	uint32 _lastTimerTick = 0;
 	int _lastLoggedGameClockTicks = -1;
 	uint32 _lastCursorRefreshMillis = 0;
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
new file mode 100644
index 00000000000..4878b5a9ed4
--- /dev/null
+++ b/engines/harold/bcb/captions.cpp
@@ -0,0 +1,181 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/system.h"
+#include "graphics/font.h"
+#include "graphics/sjis.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+static const StaticRect kTitleCaptionTextRect = {120, 312, 520, 352};
+static const StaticRect kTitleCaptionBackingRect = {120, 312, 520, 362};
+static const StaticRect kMainSceneCaptionTextRect = {48, 260, 384, 300};
+static const StaticRect kMainSceneCaptionShadowRect = {50, 262, 384, 300};
+static const StaticRect kMapSubmodeCaptionTextRect = {152, 268, 488, 308};
+
+static uint32 readUint32BE(const byte *data) {
+	return ((uint32)data[0] << 24) | ((uint32)data[1] << 16) | ((uint32)data[2] << 8) | data[3];
+}
+
+const byte *BCBEngine::findAllBinRecordData(const Common::String &recordName, uint32 &recordSize) const {
+	recordSize = 0;
+	if (_allBinData.size() < 4)
+		return nullptr;
+
+	const uint32 entryCount = readUint32BE(&_allBinData[0]);
+	for (uint32 entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
+		const uint entryOffset = 4 + entryIndex * 0x28;
+		if (entryOffset + 0x28 > _allBinData.size())
+			return nullptr;
+		if (readUint32BE(&_allBinData[entryOffset]) == 0xffffffff)
+			return nullptr;
+
+		uint nameLength = 0;
+		while (nameLength < 0x20 && _allBinData[entryOffset + 8 + nameLength] != 0)
+			++nameLength;
+		const Common::String entryName((const char *)&_allBinData[entryOffset + 8], nameLength);
+		if (!recordName.equals(entryName))
+			continue;
+
+		const uint32 dataOffset = readUint32BE(&_allBinData[entryOffset]);
+		const uint32 dataSize = readUint32BE(&_allBinData[entryOffset + 4]);
+		if (dataOffset + dataSize > _allBinData.size())
+			return nullptr;
+
+		recordSize = dataSize;
+		return &_allBinData[dataOffset];
+	}
+
+	return nullptr;
+}
+
+void BCBEngine::loadTimedCaptionRecord(int movieId) {
+	_timedCaptionRecordData.clear();
+	_timedCaptionCursorOffset = 0;
+
+	uint32 recordSize = 0;
+	const Common::String recordName = Common::String::format("MES%04d.TXT", movieId);
+	const byte *recordData = findAllBinRecordData(recordName, recordSize);
+	if (recordData && recordSize) {
+		_timedCaptionRecordData.resize(recordSize);
+		Common::copy(recordData, recordData + recordSize, _timedCaptionRecordData.begin());
+	}
+
+	loadNextTimedCaptionText();
+}
+
+bool BCBEngine::readTimedCaptionLine(Common::String &line) {
+	line.clear();
+	if (_timedCaptionCursorOffset >= _timedCaptionRecordData.size())
+		return false;
+
+	int copiedCharCount = 0;
+	while (_timedCaptionCursorOffset + 1 < _timedCaptionRecordData.size()) {
+		if (_timedCaptionRecordData[_timedCaptionCursorOffset] == '\r' && _timedCaptionRecordData[_timedCaptionCursorOffset + 1] == '\n') {
+			_timedCaptionCursorOffset += 2;
+			return true;
+		}
+
+		line += (char)_timedCaptionRecordData[_timedCaptionCursorOffset++];
+		++copiedCharCount;
+		if (copiedCharCount > 0x27)
+			return false;
+	}
+
+	return false;
+}
+
+static bool isCaptionEdgeSpace(byte c) {
+	return c == ' ' || c == '\t';
+}
+
+static void trimTimedCaptionLine(Common::String &line) {
+	const byte *text = (const byte *)line.c_str();
+	uint start = 0;
+	uint end = line.size();
+
+	while (start < end) {
+		if (isCaptionEdgeSpace(text[start])) {
+			++start;
+		} else if (start + 1 < end && ((text[start] == 0x81 && text[start + 1] == 0x40) ||
+									   (text[start] == 0xa1 && text[start + 1] == 0xa1))) {
+			start += 2;
+		} else {
+			break;
+		}
+	}
+
+	while (end > start) {
+		if (isCaptionEdgeSpace(text[end - 1])) {
+			--end;
+		} else if (end >= start + 2 && ((text[end - 2] == 0x81 && text[end - 1] == 0x40) ||
+										(text[end - 2] == 0xa1 && text[end - 1] == 0xa1))) {
+			end -= 2;
+		} else {
+			break;
+		}
+	}
+
+	if (start != 0 || end != line.size())
+		line = Common::String(line.c_str() + start, line.c_str() + end);
+}
+
+void BCBEngine::loadNextTimedCaptionText() {
+	if (_timedCaptionRecordData.empty() || _timedCaptionCursorOffset >= _timedCaptionRecordData.size()) {
+		_timedCaptionText.clear();
+		_timedCaptionTextSjis.clear();
+		_nextTimedCaptionThreshold = 99999;
+		return;
+	}
+
+	Common::String thresholdText;
+	Common::String firstLine;
+	Common::String secondLine;
+	if (!readTimedCaptionLine(thresholdText) || !readTimedCaptionLine(firstLine) || !readTimedCaptionLine(secondLine)) {
+		_timedCaptionText.clear();
+		_timedCaptionTextSjis.clear();
+		_nextTimedCaptionThreshold = 99999;
+		return;
+	}
+
+	trimTimedCaptionLine(firstLine);
+	trimTimedCaptionLine(secondLine);
+
+	_nextTimedCaptionThreshold = atoi(thresholdText.c_str());
+	_timedCaptionTextSjis = firstLine;
+	if (!secondLine.empty()) {
+		_timedCaptionTextSjis += '\n';
+		_timedCaptionTextSjis += secondLine;
+	}
+	_timedCaptionText = Common::U32String(firstLine.c_str(), firstLine.size(), _executableTextEncoding);
+	if (!secondLine.empty()) {
+		_timedCaptionText += U'\n';
+		_timedCaptionText += Common::U32String(secondLine.c_str(), secondLine.size(), _executableTextEncoding);
+	}
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index eefa74a3722..4d9398e761f 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -37,6 +37,7 @@ bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
 	_sceneChoiceActive = false;
 	_mapControlsActive = false;
 	_activeMovieOnBlankScreen = blankScreenMovie;
+	loadTimedCaptionRecord(movieId);
 
 	const Common::String movieName = Common::String::format("mov%04d.avi", movieId);
 	Common::SeekableReadStream *stream = createMovieReadStream(movieId);
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 83807ec72d9..6b26e82f8ea 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
 	bcb/bcb.o \
 	bcb/resources.o \
 	bcb/video.o \
+	bcb/captions.o \
 	bcb/scene.o \
 	bcb/screens.o \
 	bcb/input.o \


Commit: 22dec7d6fa3c80665238db69e8d3d7b6838b48aa
    https://github.com/scummvm/scummvm/commit/22dec7d6fa3c80665238db69e8d3d7b6838b48aa
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Render captions and restore their backdrop

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/captions.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 7e061644ced..f8e058a0be6 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -168,6 +168,8 @@ void BCBEngine::mainLoop() {
 					movieUpdated = true;
 				}
 			}
+			if (drawTimedVideoTextOverlay())
+				movieUpdated = true;
 			if (movieUpdated) {
 				g_system->updateScreen();
 				screenUpdated = true;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 5a57ee547ed..b2efb006a2e 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -98,6 +98,11 @@ private:
 	void loadNextTimedCaptionText();
 	bool readTimedCaptionLine(Common::String &line);
 	const byte *findAllBinRecordData(const Common::String &recordName, uint32 &recordSize) const;
+	bool drawTimedVideoTextOverlay();
+	void restoreActiveCaptionBackingRect();
+	Common::Rect getCaptionBackingRect(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &nativeRect) const;
+	void drawCaptionTextBlock(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &rect, uint32 color);
+	void restoreCaptionBackingRect(const Common::Rect &rect);
 	Common::Point getActiveMoviePosition() const;
 	void stopActiveMovieAndDispatch();
 	void processQueuedMovieSequence();
@@ -180,6 +185,7 @@ private:
 	int _sceneChoiceEnabledButtonLimit = 0;
 	Common::U32String _timedCaptionText;
 	Common::String _timedCaptionTextSjis;
+	Common::Rect _activeCaptionBackingRect;
 	uint32 _lastTimerTick = 0;
 	int _lastLoggedGameClockTicks = -1;
 	uint32 _lastCursorRefreshMillis = 0;
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
index 4878b5a9ed4..cd8ae11c738 100644
--- a/engines/harold/bcb/captions.cpp
+++ b/engines/harold/bcb/captions.cpp
@@ -178,4 +178,168 @@ void BCBEngine::loadNextTimedCaptionText() {
 	}
 }
 
+void BCBEngine::restoreCaptionBackingRect(const Common::Rect &rect) {
+	if (_activeMovieOnBlankScreen) {
+		Graphics::Surface *screen = g_system->lockScreen();
+		if (screen) {
+			screen->fillRect(rect, screen->format.RGBToColor(0, 0, 0));
+			g_system->unlockScreen();
+		}
+		return;
+	}
+
+	const Graphics::Surface *backing = nullptr;
+	if (_currentDisplayState == 0 && _titleBitmap) {
+		backing = _titleBitmap;
+	} else if (_currentDisplayState == 1 || _currentDisplayState == 7) {
+		backing = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
+	} else if (_currentDisplayState == 8) {
+		Common::Rect restoreRect = rect;
+		restoreRect.clip(kMapSubmodeMovieBackdropRect);
+
+		const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+		if (map && !restoreRect.isEmpty()) {
+			const int sourceX = kMapSubmodeMovieBackdropSourceX + restoreRect.left - kMapSubmodeMovieBackdropRect.left;
+			const int sourceY = kMapSubmodeMovieBackdropSourceY + restoreRect.top - kMapSubmodeMovieBackdropRect.top;
+			if (sourceX >= 0 && sourceY >= 0 && sourceX + restoreRect.width() <= map->w && sourceY + restoreRect.height() <= map->h)
+				g_system->copyRectToScreen(map->getBasePtr(sourceX, sourceY), map->pitch, restoreRect.left, restoreRect.top, restoreRect.width(), restoreRect.height());
+		}
+
+		return;
+	}
+
+	if (backing)
+		g_system->copyRectToScreen(backing->getBasePtr(rect.left, rect.top), backing->pitch, rect.left, rect.top, rect.width(), rect.height());
+}
+
+Common::Rect BCBEngine::getCaptionBackingRect(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &nativeRect) const {
+	int lineCount = 1;
+	int fontHeight = nativeRect.height();
+
+	if (_captionTextFont) {
+		lineCount = 0;
+		for (uint i = 0; i <= unicodeText.size(); ++i) {
+			if (i == unicodeText.size() || unicodeText[i] == U'\n')
+				++lineCount;
+		}
+		fontHeight = _captionTextFont->getFontHeight();
+	} else if (_sjisFont) {
+		lineCount = 0;
+		for (uint i = 0; i <= sjisText.size(); ++i) {
+			if (i == sjisText.size() || sjisText[i] == '\n')
+				++lineCount;
+		}
+		fontHeight = _sjisFont->getFontHeight();
+	}
+
+	Common::Rect backingRect = nativeRect;
+	backingRect.bottom = MAX<int>(backingRect.bottom, nativeRect.top + MAX<int>(lineCount, 1) * fontHeight);
+	backingRect.clip(Common::Rect(0, 0, 640, 480));
+	return backingRect;
+}
+
+static Common::U32String makeU32Substring(const Common::U32String &text, uint start, uint end) {
+	return Common::U32String(text.c_str() + start, end - start);
+}
+
+void BCBEngine::drawCaptionTextBlock(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &rect, uint32 color) {
+	if (!_captionTextFont && !_sjisFont)
+		return;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (!screen)
+		return;
+
+	if (_captionTextFont) {
+		int y = rect.top;
+		for (uint lineStart = 0; lineStart <= unicodeText.size() && y < rect.bottom;) {
+			uint lineEnd = lineStart;
+			while (lineEnd < unicodeText.size() && unicodeText[lineEnd] != U'\n')
+				++lineEnd;
+
+			_captionTextFont->drawString(screen, makeU32Substring(unicodeText, lineStart, lineEnd), rect.left, y, rect.width(), color, Graphics::kTextAlignCenter);
+			y += _captionTextFont->getFontHeight();
+			lineStart = lineEnd + 1;
+		}
+	} else {
+		int y = rect.top;
+		for (uint lineStart = 0; lineStart <= sjisText.size() && y < rect.bottom;) {
+			uint lineEnd = lineStart;
+			while (lineEnd < sjisText.size() && sjisText[lineEnd] != '\n')
+				++lineEnd;
+
+			Common::Array<uint16> chars;
+			int textWidth = 0;
+			for (uint charOffset = lineStart; charOffset < lineEnd;) {
+				const byte firstByte = (byte)sjisText[charOffset++];
+				uint16 sjisChar = firstByte;
+				if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && charOffset < lineEnd)
+					sjisChar |= (uint16)((byte)sjisText[charOffset++]) << 8;
+				chars.push_back(sjisChar);
+				textWidth += _sjisFont->getCharWidth(sjisChar);
+			}
+
+			int lineX = rect.left + MAX<int>((rect.width() - textWidth) / 2, 0);
+			for (uint i = 0; i < chars.size(); ++i) {
+				if (lineX >= rect.right)
+					break;
+				_sjisFont->drawChar(*screen, chars[i], lineX, y, color, 0);
+				lineX += _sjisFont->getCharWidth(chars[i]);
+			}
+
+			y += _sjisFont->getFontHeight();
+			lineStart = lineEnd + 1;
+		}
+	}
+
+	g_system->unlockScreen();
+}
+
+bool BCBEngine::drawTimedVideoTextOverlay() {
+	if (!_activeMovieVideo || _nextTimedCaptionThreshold == 99999 || _activeMovieVideo->getCurFrame() < _nextTimedCaptionThreshold)
+		return false;
+
+	const Graphics::PixelFormat format = g_system->getScreenFormat();
+	const uint32 white = format.RGBToColor(255, 255, 255);
+	const uint32 black = format.RGBToColor(0, 0, 0);
+
+	restoreActiveCaptionBackingRect();
+	if (_timedCaptionTextSjis.empty() || _timedCaptionText.empty()) {
+		loadNextTimedCaptionText();
+		return true;
+	}
+
+	if (_currentDisplayState == 0) {
+		_activeCaptionBackingRect = getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kTitleCaptionBackingRect);
+		drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kTitleCaptionTextRect, white);
+	} else if (_currentDisplayState == 1 || _currentDisplayState == 7) {
+		_activeCaptionBackingRect = getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionTextRect);
+		_activeCaptionBackingRect.extend(getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionShadowRect));
+		drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionShadowRect, black);
+		drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionTextRect, white);
+	} else if (_currentDisplayState == 8) {
+		_activeCaptionBackingRect = getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kMapSubmodeCaptionTextRect);
+		drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kMapSubmodeCaptionTextRect, white);
+	}
+
+	loadNextTimedCaptionText();
+	return true;
+}
+
+void BCBEngine::restoreActiveCaptionBackingRect() {
+	if (!_activeCaptionBackingRect.isEmpty()) {
+		restoreCaptionBackingRect(_activeCaptionBackingRect);
+		_activeCaptionBackingRect = Common::Rect();
+		return;
+	}
+
+	if (_currentDisplayState == 0) {
+		restoreCaptionBackingRect(kTitleCaptionBackingRect);
+	} else if (_currentDisplayState == 1 || _currentDisplayState == 7) {
+		restoreCaptionBackingRect(kMainSceneCaptionTextRect);
+	} else if (_currentDisplayState == 8) {
+		restoreCaptionBackingRect(kMapSubmodeCaptionTextRect);
+	}
+}
+
 } // End of namespace BCB


Commit: 07c82a3499c51b0784f4e752053b772ff5b75604
    https://github.com/scummvm/scummvm/commit/07c82a3499c51b0784f4e752053b772ff5b75604
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add movie controls and queued replay

Changed paths:
  A engines/harold/bcb/map.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/scene.cpp
    engines/harold/bcb/video.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index b2efb006a2e..9356a27840e 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -104,11 +104,18 @@ private:
 	void drawCaptionTextBlock(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &rect, uint32 color);
 	void restoreCaptionBackingRect(const Common::Rect &rect);
 	Common::Point getActiveMoviePosition() const;
+	bool shouldDrawMovieControlStrip() const;
+	void drawMapSubmodeMovieBackdrop(bool present = true);
+	void drawSceneChoiceControlStrip(int sourceX = 0, bool present = true);
+	void drawMovieControlStrip(int sourceX = 0, bool present = true);
 	void stopActiveMovieAndDispatch();
 	void processQueuedMovieSequence();
+	void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
 	bool drawMainSceneScreen(bool lineReveal);
+	void applyPendingClockTickDelta();
 	void notifyGameClockTicksChanged();
 	void drawClockAndChipIndicator(bool present = true);
+	void renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks);
 	Graphics::Surface *loadBitmap(const char *fileName);
 	const Graphics::Surface *getUiBitmap(const char *fileName);
 	Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 039cdde6fcb..318c82849ae 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -38,11 +38,29 @@ static const int kMovieControlStripSkipPressedSourceX = 0x98;
 static const int kMovieControlStripPausePressedSourceX = 0x130;
 
 void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+	if (_activeMovieVideo && shouldDrawMovieControlStrip()) {
+		if (kSystemMenuControlHitRect.contains(mousePosition)) {
+			_activeMoviePaused = !_activeMoviePaused;
+			_activeMovieVideo->pauseVideo(_activeMoviePaused);
+			drawMovieControlStrip(_activeMoviePaused ? kMovieControlStripPausePressedSourceX : kMovieControlStripNormalSourceX);
+			return;
+		}
+
+		if (kMovieQueueControlHitRect.contains(mousePosition)) {
+			drawMovieControlStrip(kMovieControlStripSkipPressedSourceX);
+			delayWithEventPolling(160, false);
+			stopActiveMovieAndDispatch();
+			return;
+		}
+	}
+
 	const bool activeMovieHit =
 		(_currentDisplayState == 0 && kTitleMciControlHitRect.contains(mousePosition)) ||
 		((_currentDisplayState == 1 || _currentDisplayState == 7) && kMainSceneMciControlHitRect.contains(mousePosition)) ||
 		(_currentDisplayState == 8 && kMapSubmodeMciControlHitRect.contains(mousePosition));
 	if (_activeMovieVideo && activeMovieHit) {
+		if (shouldDrawMovieControlStrip())
+			drawMovieControlStrip(kMovieControlStripSkipPressedSourceX);
 		delayWithEventPolling(160, false);
 		stopActiveMovieAndDispatch();
 		return;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
new file mode 100644
index 00000000000..e22c93a29fd
--- /dev/null
+++ b/engines/harold/bcb/map.cpp
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/fs.h"
+#include "common/ustr.h"
+#include "graphics/font.h"
+#include "graphics/sjis.h"
+#include "graphics/surface.h"
+#include "video/avi_decoder.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+#include "common/textconsole.h"
+
+namespace BCB {
+
+static const uint32 kScriptSecondaryStateByteOffset = 0x98;
+void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt) {
+	error("BCB: composeMapControlsToSurface is not implemented");
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 0104a0899df..4765c019bff 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -47,6 +47,10 @@ void BCBEngine::notifyGameClockTicksChanged() {
 	g_system->logMessage(LogMessageType::kInfo, message.c_str());
 }
 
+void BCBEngine::applyPendingClockTickDelta() {
+	error("BCB: applyPendingClockTickDelta is not implemented");
+}
+
 void BCBEngine::drawClockAndChipIndicator(bool present) {
 	error("BCB: drawClockAndChipIndicator is not implemented");
 }
@@ -55,4 +59,8 @@ bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
 	error("BCB: drawMainSceneScreen is not implemented");
 }
 
+void BCBEngine::renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks) {
+	error("BCB: renderClockAndChipToSurface is not implemented");
+}
+
 } // End of namespace BCB
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 4d9398e761f..9b0c7f7d9a1 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -60,6 +60,7 @@ bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
 	_activeMovieVideo->start();
 	_activeMoviePaused = false;
 	_activeMovieDispatchOnFinish = true;
+	drawMovieControlStrip();
 	return true;
 }
 
@@ -71,6 +72,84 @@ Common::Point BCBEngine::getActiveMoviePosition() const {
 	return Common::Point(0x10, 0x30);
 }
 
+bool BCBEngine::shouldDrawMovieControlStrip() const {
+	return !_activeMovieOnBlankScreen && (_currentDisplayState == 1 || _currentDisplayState == 7 || _currentDisplayState == 8);
+}
+
+void BCBEngine::drawMapSubmodeMovieBackdrop(bool present) {
+	const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+	if (map) {
+		const Common::Rect sourceRect(kMapSubmodeMovieBackdropSourceX, kMapSubmodeMovieBackdropSourceY,
+									  kMapSubmodeMovieBackdropSourceX + 0x1a4, kMapSubmodeMovieBackdropSourceY + 0x11e);
+		if (sourceRect.right <= map->w && sourceRect.bottom <= map->h)
+			g_system->copyRectToScreen(map->getBasePtr(sourceRect.left, sourceRect.top), map->pitch,
+									   kMapSubmodeMovieBackdropRect.left, kMapSubmodeMovieBackdropRect.top,
+									   sourceRect.width(), sourceRect.height());
+	} else {
+		Graphics::Surface *screen = g_system->lockScreen();
+		if (!screen)
+			return;
+
+		const uint32 backingColor = screen->format.RGBToColor(0, 0, 0);
+		screen->fillRect(kMapSubmodeMciControlHitRect, backingColor);
+		g_system->unlockScreen();
+	}
+
+	if (present)
+		g_system->updateScreen();
+}
+
+void BCBEngine::drawMovieControlStrip(int sourceX, bool present) {
+	if (!shouldDrawMovieControlStrip())
+		return;
+
+	const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+	if (chip) {
+		if (sourceX + 0x98 <= chip->w && 0xe0 + 0x30 <= chip->h)
+			g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xe0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+		if (present)
+			g_system->updateScreen();
+	}
+}
+
+void BCBEngine::drawSceneChoiceControlStrip(int sourceX, bool present) {
+	const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+	if (chip) {
+		if (sourceX + 0x98 <= chip->w && 0xb0 + 0x30 <= chip->h)
+			g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xb0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+		if (present)
+			g_system->updateScreen();
+	}
+}
+
+void BCBEngine::processQueuedMovieSequence() {
+	const int movieHistoryCount = stateDword(kStateMovieHistoryCount);
+	if (_queuedMoviePlaybackIndex < movieHistoryCount) {
+		const int movieId = stateDword(kStateMovieHistory + _queuedMoviePlaybackIndex * 4);
+		playNumberedMovie(movieId);
+		++_queuedMoviePlaybackIndex;
+		return;
+	}
+
+	if (_previousDisplayStateBeforeSystem == 1) {
+		drawSceneChoiceControlStrip();
+	} else if (_previousDisplayStateBeforeSystem == 2) {
+		Graphics::Surface *map = loadBitmap("16mmap.bmp");
+		if (map) {
+			applyPendingClockTickDelta();
+			renderClockAndChipToSurface(*map, 0, stateDword(kStateGameClockTicks));
+			composeMapControlsToSurface(*map, *map);
+			g_system->copyRectToScreen(map->getPixels(), map->pitch, 0, 0, MIN<int>(map->w, 640), MIN<int>(map->h, 480));
+			map->free();
+			delete map;
+		}
+		drawSceneChoiceControlStrip(0, false);
+		g_system->updateScreen();
+	}
+
+	processGameStateDispatcher(false);
+}
+
 void BCBEngine::stopActiveMovie() {
 	if (!_activeMovieVideo)
 		return;
@@ -223,8 +302,4 @@ void BCBEngine::stopSceneIdleMovie() {
 	_sceneIdleVideo = nullptr;
 }
 
-void BCBEngine::processQueuedMovieSequence() {
-	error("BCB: processQueuedMovieSequence is not implemented");
-}
-
 } // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 6b26e82f8ea..bf42103d2ce 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -8,6 +8,7 @@ MODULE_OBJS := \
 	bcb/scene.o \
 	bcb/screens.o \
 	bcb/input.o \
+	bcb/map.o \
 	bcb/script.o \
 	bcb/executable_data.o \
 	bcb/static_data.o \


Commit: ed1eb0c917349e76bac58ba52426fe522136b93f
    https://github.com/scummvm/scummvm/commit/ed1eb0c917349e76bac58ba52426fe522136b93f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Draw the clock and apply elapsed game time

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/scene.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 9356a27840e..2ff09af23a6 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -115,6 +115,7 @@ private:
 	void applyPendingClockTickDelta();
 	void notifyGameClockTicksChanged();
 	void drawClockAndChipIndicator(bool present = true);
+	void drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, bool present = true);
 	void renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks);
 	Graphics::Surface *loadBitmap(const char *fileName);
 	const Graphics::Surface *getUiBitmap(const char *fileName);
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 4765c019bff..c9e1ddcf942 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -33,6 +33,12 @@
 
 namespace BCB {
 
+void BCBEngine::applyPendingClockTickDelta() {
+	stateDword(kStateGameClockTicks) += _pendingClockTickDelta;
+	_pendingClockTickDelta = 0;
+	notifyGameClockTicksChanged();
+}
+
 void BCBEngine::notifyGameClockTicksChanged() {
 	const int ticks = stateDword(kStateGameClockTicks);
 	if (_lastLoggedGameClockTicks == ticks)
@@ -47,20 +53,59 @@ void BCBEngine::notifyGameClockTicksChanged() {
 	g_system->logMessage(LogMessageType::kInfo, message.c_str());
 }
 
-void BCBEngine::applyPendingClockTickDelta() {
-	error("BCB: applyPendingClockTickDelta is not implemented");
+void BCBEngine::drawClockAndChipIndicator(bool present) {
+	applyPendingClockTickDelta();
+	drawAnalogClockAndChip(0, stateDword(kStateGameClockTicks), present);
 }
 
-void BCBEngine::drawClockAndChipIndicator(bool present) {
-	error("BCB: drawClockAndChipIndicator is not implemented");
+void BCBEngine::renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks) {
+	static const double kClockDegreesToRadians = 0.017453292519943295769;
+	const int clockTicks = gameClockTicks % 1440;
+	const double hourAngle = (30.0 * (baseHourOffset % 24) + clockTicks / 2.0) * kClockDegreesToRadians;
+	const double minuteAngle = (clockTicks * 6.0) * kClockDegreesToRadians;
+	const int centerX = 0x55;
+	const int centerY = 0x198;
+
+	const uint32 handColor = surface.format.RGBToColor(0x50, 0x50, 0x50);
+	const int hourEndX = centerX + (int)floor(sin(hourAngle) * 25.0 + 0.5);
+	const int hourEndY = centerY - (int)floor(cos(hourAngle) * 25.0 + 0.5);
+	const int minuteEndX = centerX + (int)floor(sin(minuteAngle) * 32.0 + 0.5);
+	const int minuteEndY = centerY - (int)floor(cos(minuteAngle) * 32.0 + 0.5);
+
+	surface.drawThickLine(centerX, centerY, hourEndX, hourEndY, 4, 4, handColor);
+	surface.drawThickLine(centerX, centerY, minuteEndX, minuteEndY, 3, 3, handColor);
+
+	const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+	if (chip) {
+		const int sourceX = CLIP<int>(stateDword(kStateRequiredDiscIndex), 0, 4) * 0x58;
+		if (sourceX + 0x58 <= chip->w && 0x32 <= chip->h)
+			surface.copyRectToSurface(*chip, 0x8c, 0x16c, Common::Rect(sourceX, 0, sourceX + 0x58, 0x32));
+	}
 }
 
-bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
-	error("BCB: drawMainSceneScreen is not implemented");
+void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, bool present) {
+	const Graphics::Surface *clockBacking = nullptr;
+	if (_currentDisplayState == 2 || _currentDisplayState == 8) {
+		clockBacking = getUiBitmap("16mmap.bmp");
+	} else {
+		clockBacking = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
+	}
+	if (clockBacking) {
+		g_system->copyRectToScreen(clockBacking->getBasePtr(0x25, 0x167), clockBacking->pitch, 0x25, 0x167, 0x60, 0x60);
+	}
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		renderClockAndChipToSurface(*screen, baseHourOffset, gameClockTicks);
+		g_system->unlockScreen();
+	}
+
+	if (present)
+		g_system->updateScreen();
 }
 
-void BCBEngine::renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks) {
-	error("BCB: renderClockAndChipToSurface is not implemented");
+bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
+	error("BCB: drawMainSceneScreen is not implemented");
 }
 
 } // End of namespace BCB


Commit: b95075a0870044cbf1eeb68d609622c3e66352b1
    https://github.com/scummvm/scummvm/commit/b95075a0870044cbf1eeb68d609622c3e66352b1
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Compose conversation screens and backing

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/scene.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index f8e058a0be6..309bdf7b0e6 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -84,6 +84,7 @@ BCBEngine::~BCBEngine() {
 	delete _activeMovieVideo;
 	delete _sceneIdleVideo;
 	delete _gameOverVideo;
+	freeSceneChoiceBacking();
 	delete _sjisFont;
 	delete _captionTextFont;
 	delete _gameOverTextFont;
@@ -158,6 +159,24 @@ void BCBEngine::mainLoop() {
 				_menuBackgroundVideo->decodeNextFrame();
 		}
 
+		if (_sceneIdleVideo) {
+			if (_sceneIdleVideo->endOfVideo()) {
+				_sceneIdleVideo->stop();
+				_sceneIdleVideo->rewind();
+				_sceneIdleVideo->decodeNextFrame();
+				_sceneIdleVideo->start();
+			}
+
+			if (_sceneIdleVideo->needsUpdate()) {
+				const Graphics::Surface *frame = _sceneIdleVideo->decodeNextFrame();
+				if (frame) {
+					g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0x10, 0x30, frame->w, frame->h);
+					g_system->updateScreen();
+					screenUpdated = true;
+				}
+			}
+		}
+
 		if (_activeMovieVideo) {
 			bool movieUpdated = false;
 			if (_activeMovieVideo->needsUpdate()) {
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 2ff09af23a6..5f045715b08 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -111,7 +111,13 @@ private:
 	void stopActiveMovieAndDispatch();
 	void processQueuedMovieSequence();
 	void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
+	bool showMainSceneScreen();
 	bool drawMainSceneScreen(bool lineReveal);
+	void captureSceneChoiceBacking();
+	void restoreSceneChoiceBacking(bool present = true);
+	void freeSceneChoiceBacking();
+	void clearSceneChoiceAfterSelection();
+	bool isSceneChoiceButtonVisible(int buttonIndex) const;
 	void applyPendingClockTickDelta();
 	void notifyGameClockTicksChanged();
 	void drawClockAndChipIndicator(bool present = true);
@@ -172,6 +178,8 @@ private:
 	Graphics::Surface *_uiBitmap = nullptr;
 	Common::String _uiBitmapName;
 	Graphics::Surface *_chipBitmap = nullptr;
+	Graphics::Surface *_sceneChoiceBacking = nullptr;
+	Common::Rect _sceneChoiceBackingRect;
 	Audio::SoundHandle _uiSoundHandle;
 	uint32 _gameState[0x100] = {};
 	uint32 _scriptValueStack[0x100] = {};
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index c9e1ddcf942..127945c02bd 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -33,6 +33,103 @@
 
 namespace BCB {
 
+bool BCBEngine::showMainSceneScreen() {
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+	freeSceneChoiceBacking();
+	return drawMainSceneScreen(true);
+}
+
+bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
+	const bool mainSceneNightFlag = stateDword(kStateMainSceneNightFlag) != 0;
+	Graphics::Surface *mainScene = loadBitmap(mainSceneNightFlag ? "16mmainn.bmp" : "16mmain.bmp");
+	if (!mainScene)
+		return false;
+
+	applyPendingClockTickDelta();
+	renderClockAndChipToSurface(*mainScene, 0, stateDword(kStateGameClockTicks));
+	if (lineReveal) {
+		blitScreenWithLineReveal(*mainScene, false);
+	} else {
+		g_system->copyRectToScreen(mainScene->getPixels(), mainScene->pitch, 0, 0, MIN<int>(mainScene->w, 640), MIN<int>(mainScene->h, 480));
+	}
+	mainScene->free();
+	delete mainScene;
+	_currentDisplayState = 1;
+	_sceneIdleMovieTimerTicks = 0;
+	_lastTimerTick = g_system->getMillis();
+	g_system->updateScreen();
+	return true;
+}
+
+void BCBEngine::captureSceneChoiceBacking() {
+	freeSceneChoiceBacking();
+	for (uint i = 1; i < ARRAYSIZE(kSceneChoiceButtonTable); ++i) {
+		const ButtonControlRecord &button = kSceneChoiceButtonTable[i];
+		const Common::Rect rect(button.x, button.y, button.x + button.width, button.y + button.height);
+		if (i == 1)
+			_sceneChoiceBackingRect = rect;
+		else
+			_sceneChoiceBackingRect.extend(rect);
+	}
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (!screen)
+		return;
+
+	_sceneChoiceBacking = new Graphics::Surface();
+	_sceneChoiceBacking->create(_sceneChoiceBackingRect.width(), _sceneChoiceBackingRect.height(), screen->format);
+	_sceneChoiceBacking->copyRectToSurface(*screen, 0, 0, _sceneChoiceBackingRect);
+	g_system->unlockScreen();
+}
+
+void BCBEngine::restoreSceneChoiceBacking(bool present) {
+	if (!_sceneChoiceBacking)
+		return;
+
+	for (int buttonIndex = 1; buttonIndex <= _sceneChoiceEnabledButtonLimit && buttonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable); ++buttonIndex) {
+		if (!isSceneChoiceButtonVisible(buttonIndex))
+			continue;
+
+		const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+		g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+	}
+
+	if (present)
+		g_system->updateScreen();
+}
+
+void BCBEngine::freeSceneChoiceBacking() {
+	if (!_sceneChoiceBacking)
+		return;
+
+	_sceneChoiceBacking->free();
+	delete _sceneChoiceBacking;
+	_sceneChoiceBacking = nullptr;
+}
+
+void BCBEngine::clearSceneChoiceAfterSelection() {
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		screen->fillRect(Common::Rect(0x10, 0x30, 0x1a0, 0xf8), screen->format.RGBToColor(0, 0, 0));
+		g_system->unlockScreen();
+	}
+
+	freeSceneChoiceBacking();
+	g_system->updateScreen();
+}
+
+bool BCBEngine::isSceneChoiceButtonVisible(int buttonIndex) const {
+	if (buttonIndex <= 0 || buttonIndex >= (int)ARRAYSIZE(kSceneChoiceButtonTable))
+		return false;
+
+	const int sceneChoiceUiSetToShowId = stateDword(kStateSceneChoiceUiSetToShowId);
+	const int choiceCount = (sceneChoiceUiSetToShowId >= 0 && sceneChoiceUiSetToShowId < (int)_sceneChoiceText.size()) ? MIN<int>(_sceneChoiceText[sceneChoiceUiSetToShowId].size(), 8) : 0;
+
+	return buttonIndex <= choiceCount ||
+		   (buttonIndex == _sceneChoiceTerminatorButtonIndex && stateDword(kStateSceneChoiceExtraButtonFlag) != 0);
+}
+
 void BCBEngine::applyPendingClockTickDelta() {
 	stateDword(kStateGameClockTicks) += _pendingClockTickDelta;
 	_pendingClockTickDelta = 0;
@@ -104,8 +201,4 @@ void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, b
 		g_system->updateScreen();
 }
 
-bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
-	error("BCB: drawMainSceneScreen is not implemented");
-}
-
 } // End of namespace BCB


Commit: d965780f3b73ec0e8176b576013034e86e705023
    https://github.com/scummvm/scummvm/commit/d965780f3b73ec0e8176b576013034e86e705023
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Display and select conversation choices

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/scene.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 5f045715b08..a1f315098e5 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -113,11 +113,15 @@ private:
 	void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
 	bool showMainSceneScreen();
 	bool drawMainSceneScreen(bool lineReveal);
+	bool showActiveSceneChoiceControls();
 	void captureSceneChoiceBacking();
 	void restoreSceneChoiceBacking(bool present = true);
 	void freeSceneChoiceBacking();
 	void clearSceneChoiceAfterSelection();
 	bool isSceneChoiceButtonVisible(int buttonIndex) const;
+	void drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool present = true, int pressedButtonIndex = -1);
+	void handleSceneChoiceClick(const Common::Point &mousePosition);
+	void handleSceneChoiceSelection(int buttonIndex);
 	void applyPendingClockTickDelta();
 	void notifyGameClockTicksChanged();
 	void drawClockAndChipIndicator(bool present = true);
@@ -135,6 +139,7 @@ private:
 	void blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame = true);
 	void drawTitleButton(uint buttonIndex, bool pressed);
 	void handleLeftButtonDown(const Common::Point &mousePosition);
+	void handleSceneChoiceKeyboardSelection(int choiceNumber);
 	bool pollEvents();
 	bool pollEvents(bool handleClicks);
 	void serviceCursorRefresh(bool screenUpdated = false);
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 318c82849ae..3ce39fdc093 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -71,6 +71,11 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 		return;
 	}
 
+	if (_currentDisplayState == 1 && _sceneChoiceActive) {
+		handleSceneChoiceClick(mousePosition);
+		return;
+	}
+
 	if (_currentDisplayState != 0 || !_titleButtonsActive)
 		return;
 
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 127945c02bd..e80ec6a5a80 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -62,6 +62,43 @@ bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
 	return true;
 }
 
+bool BCBEngine::showActiveSceneChoiceControls() {
+	const int sceneChoiceUiSetToShowId = stateDword(kStateSceneChoiceUiSetToShowId);
+	if (_currentDisplayState != 1 && _currentDisplayState != 7)
+		showMainSceneScreen();
+	_currentDisplayState = 1;
+
+	if (sceneChoiceUiSetToShowId == 0)
+		return false;
+
+	if (sceneChoiceUiSetToShowId < 0 || sceneChoiceUiSetToShowId >= (int)_sceneChoiceText.size() || _sceneChoiceText[sceneChoiceUiSetToShowId].empty()) {
+		warning("BCBEngine::showActiveSceneChoiceControls(): Scene choice set %d has no parsed text", sceneChoiceUiSetToShowId);
+		return false;
+	}
+	uint32 &sceneChoiceExtraButtonFlag = stateDword(kStateSceneChoiceExtraButtonFlag);
+	const uint locationTableOffset = sceneChoiceUiSetToShowId * 2 - 1;
+	sceneChoiceExtraButtonFlag = locationTableOffset < _locationTableData.size() ? _locationTableData[locationTableOffset] : 0;
+
+	_sceneChoiceTerminatorButtonIndex = MIN<int>(_sceneChoiceText[sceneChoiceUiSetToShowId].size() + 1, 9);
+	_sceneChoiceEnabledButtonLimit = sceneChoiceExtraButtonFlag == 0 ? _sceneChoiceTerminatorButtonIndex - 1 : _sceneChoiceTerminatorButtonIndex;
+
+	const Common::String overlayPath = Common::String::format("bmp/select/16m/sel%03d.bmp", sceneChoiceUiSetToShowId);
+	Graphics::Surface *overlay = loadDiscBitmap(overlayPath.c_str());
+	if (overlay) {
+		g_system->copyRectToScreen(overlay->getPixels(), overlay->pitch, 16, 48, MIN<int>(overlay->w, 400), MIN<int>(overlay->h, 200));
+		overlay->free();
+		delete overlay;
+	}
+
+	drawSceneChoiceControlStrip(0, false);
+
+	captureSceneChoiceBacking();
+	drawSceneChoiceButtons(sceneChoiceUiSetToShowId);
+	_sceneChoiceActive = true;
+	_sceneIdleMovieTimerTicks = 0;
+	return true;
+}
+
 void BCBEngine::captureSceneChoiceBacking() {
 	freeSceneChoiceBacking();
 	for (uint i = 1; i < ARRAYSIZE(kSceneChoiceButtonTable); ++i) {
@@ -130,6 +167,129 @@ bool BCBEngine::isSceneChoiceButtonVisible(int buttonIndex) const {
 		   (buttonIndex == _sceneChoiceTerminatorButtonIndex && stateDword(kStateSceneChoiceExtraButtonFlag) != 0);
 }
 
+void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool present, int pressedButtonIndex) {
+	const Graphics::Font *font = _ownerDrawUiTextFont;
+	if (!font)
+		font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+	if (!font)
+		font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
+
+	if (pressedButtonIndex < 0) {
+		restoreSceneChoiceBacking(false);
+	} else if (_sceneChoiceBacking && pressedButtonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable)) {
+		const ButtonControlRecord &button = kSceneChoiceButtonTable[pressedButtonIndex];
+		g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+	}
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (!screen)
+		return;
+
+	const uint32 normalShadowColor = screen->format.RGBToColor(0xf6, 0xd3, 0x68);
+	const uint32 normalTextColor = screen->format.RGBToColor(0x73, 0x4b, 0x20);
+	const uint32 pressedShadowColor = screen->format.RGBToColor(0xff, 0xd3, 0x83);
+	const uint32 pressedTextColor = screen->format.RGBToColor(0x8c, 0x3c, 0x05);
+	const Common::Array<Common::U32String> &originalLabels = _sceneChoiceText[sceneChoiceUiSetToShowId];
+	const auto getChoiceLabel = [&](uint choiceIndex) -> Common::U32String {
+		return originalLabels[choiceIndex];
+	};
+	const auto drawChoiceLabel = [&](const Common::U32String &label, int buttonIndex) {
+		if (!font)
+			return;
+		const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+		const bool pressed = buttonIndex == pressedButtonIndex;
+		const uint32 shadowColor = pressed ? pressedShadowColor : normalShadowColor;
+		const uint32 textColor = pressed ? pressedTextColor : normalTextColor;
+		const int textX = button.x + 10;
+		const int textY = button.y + (button.height - font->getFontHeight()) / 2 - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
+		const int textWidth = button.width - 10;
+		font->drawString(screen, label, textX + 1, textY + 1, textWidth, shadowColor, Graphics::kTextAlignLeft);
+		font->drawString(screen, label, textX, textY, textWidth, textColor, Graphics::kTextAlignLeft);
+	};
+
+	g_system->unlockScreen();
+
+	const Graphics::Surface *mainScene = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
+	if (mainScene) {
+		const int firstButtonIndex = pressedButtonIndex < 0 ? 1 : pressedButtonIndex;
+		const int lastButtonIndex = pressedButtonIndex < 0 ? _sceneChoiceEnabledButtonLimit : pressedButtonIndex;
+		for (int buttonIndex = firstButtonIndex; buttonIndex <= lastButtonIndex && buttonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable); ++buttonIndex) {
+			if (!isSceneChoiceButtonVisible(buttonIndex))
+				continue;
+
+			const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+			if (button.width <= mainScene->w && 0x210 + button.height <= mainScene->h)
+				g_system->copyRectToScreen(mainScene->getBasePtr(0, 0x210), mainScene->pitch, button.x, button.y, button.width, button.height);
+		}
+	}
+
+	screen = g_system->lockScreen();
+	if (!screen)
+		return;
+
+	if (pressedButtonIndex < 0) {
+		for (uint i = 0; i < originalLabels.size() && (int)i < _sceneChoiceEnabledButtonLimit && i + 1 < ARRAYSIZE(kSceneChoiceButtonTable); ++i)
+			drawChoiceLabel(getChoiceLabel(i), i + 1);
+	} else if (pressedButtonIndex - 1 >= 0 && pressedButtonIndex - 1 < (int)originalLabels.size()) {
+		drawChoiceLabel(getChoiceLabel(pressedButtonIndex - 1), pressedButtonIndex);
+	}
+
+	if ((pressedButtonIndex < 0 || pressedButtonIndex == _sceneChoiceTerminatorButtonIndex) &&
+		stateDword(kStateSceneChoiceExtraButtonFlag) != 0 &&
+		_sceneChoiceTerminatorButtonIndex > 0 &&
+		_sceneChoiceTerminatorButtonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable)) {
+		drawChoiceLabel(_sceneChoiceMoveText, _sceneChoiceTerminatorButtonIndex);
+	}
+
+	g_system->unlockScreen();
+	if (present)
+		g_system->updateScreen();
+}
+
+void BCBEngine::handleSceneChoiceClick(const Common::Point &mousePosition) {
+	if (!_sceneChoiceActive)
+		return;
+
+	for (int buttonIndex = 1; buttonIndex <= _sceneChoiceEnabledButtonLimit && buttonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable); ++buttonIndex) {
+		if (!isSceneChoiceButtonVisible(buttonIndex))
+			continue;
+
+		const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+		const Common::Rect rect(button.x, button.y, button.x + button.width, button.y + button.height);
+		if (!rect.contains(mousePosition))
+			continue;
+
+		handleSceneChoiceSelection(buttonIndex);
+		return;
+	}
+}
+
+void BCBEngine::handleSceneChoiceSelection(int buttonIndex) {
+	if (!_sceneChoiceActive || buttonIndex <= 0 || buttonIndex > _sceneChoiceEnabledButtonLimit ||
+		buttonIndex >= (int)ARRAYSIZE(kSceneChoiceButtonTable) || !isSceneChoiceButtonVisible(buttonIndex))
+		return;
+
+	const int sceneChoiceUiSetToShowId = stateDword(kStateSceneChoiceUiSetToShowId);
+	drawSceneChoiceButtons(sceneChoiceUiSetToShowId, true, buttonIndex);
+	delayWithEventPolling(80);
+
+	stopSceneIdleMovie();
+	_sceneChoiceActive = false;
+	stateDword(kStateMovieHistoryCount) = 0;
+
+	if (buttonIndex == _sceneChoiceTerminatorButtonIndex && stateDword(kStateSceneChoiceExtraButtonFlag) != 0) {
+		stateDword(kStateSelectedSceneChoiceValue) = 0xff;
+	} else {
+		stateDword(kStateSelectedSceneChoiceValue) = kSceneChoiceValueByButtonIndex[buttonIndex];
+		const uint timingOffset = (sceneChoiceUiSetToShowId - 1) * 8 + (buttonIndex - 1);
+		if (timingOffset < _locationTimingData.size())
+			_pendingClockTickDelta = (int8)_locationTimingData[timingOffset];
+	}
+
+	clearSceneChoiceAfterSelection();
+	processGameStateDispatcher(true);
+}
+
 void BCBEngine::applyPendingClockTickDelta() {
 	stateDword(kStateGameClockTicks) += _pendingClockTickDelta;
 	_pendingClockTickDelta = 0;
@@ -201,4 +361,16 @@ void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, b
 		g_system->updateScreen();
 }
 
+void BCBEngine::handleSceneChoiceKeyboardSelection(int choiceNumber) {
+	if (!_sceneChoiceActive || choiceNumber <= 0 || choiceNumber > 9)
+		return;
+
+	if (choiceNumber == 9 && stateDword(kStateSceneChoiceExtraButtonFlag) != 0) {
+		handleSceneChoiceSelection(_sceneChoiceTerminatorButtonIndex);
+		return;
+	}
+
+	handleSceneChoiceSelection(choiceNumber);
+}
+
 } // End of namespace BCB


Commit: c3d8e70c725029d58e251896e6f6dae7d1733532
    https://github.com/scummvm/scummvm/commit/c3d8e70c725029d58e251896e6f6dae7d1733532
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add map travel and gated destinations

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/map.cpp
    engines/harold/bcb/scene.cpp
    engines/harold/bcb/video.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 309bdf7b0e6..aab18cfa356 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -82,6 +82,7 @@ BCBEngine::~BCBEngine() {
 		delete _chipBitmap;
 	}
 	delete _activeMovieVideo;
+	delete _mapMoveVideo;
 	delete _sceneIdleVideo;
 	delete _gameOverVideo;
 	freeSceneChoiceBacking();
@@ -177,6 +178,18 @@ void BCBEngine::mainLoop() {
 			}
 		}
 
+		if (_mapMoveVideo) {
+			if (_mapMoveVideo->endOfVideo()) {
+				_mapMoveVideo->stop();
+				_mapMoveVideo->rewind();
+				_mapMoveVideo->decodeNextFrame();
+				_mapMoveVideo->start();
+			}
+
+			if (_mapMoveVideo->needsUpdate())
+				_mapMoveVideo->decodeNextFrame();
+		}
+
 		if (_activeMovieVideo) {
 			bool movieUpdated = false;
 			if (_activeMovieVideo->needsUpdate()) {
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index a1f315098e5..fd9b16f147f 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -79,11 +79,15 @@ private:
 	void startSceneIdleMovie();
 	void stopSceneIdleMovie();
 	void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
+	bool openMapMoveMovie();
+	void startMapMoveMovie();
+	void stopMapMoveMovie();
 	void resetGameState();
 	bool loadScriptData();
 	bool loadExecutableDataTables();
 	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
 	void processGameStateDispatcher(bool runScriptBeforeDispatch);
+	bool dispatchMapMoveControls();
 	void executeCurrentScriptSequence();
 	uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
 	bool pushScriptValue(uint32 value);
@@ -110,9 +114,14 @@ private:
 	void drawMovieControlStrip(int sourceX = 0, bool present = true);
 	void stopActiveMovieAndDispatch();
 	void processQueuedMovieSequence();
+	bool isMapButtonEnabled(uint buttonIndex) const;
+	void drawMapButtonPressed(uint buttonIndex, bool present = true);
 	void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
+	void drawMapControls(bool present = true);
+	bool handleMapControlClick(const Common::Point &mousePosition);
 	bool showMainSceneScreen();
 	bool drawMainSceneScreen(bool lineReveal);
+	bool showMapScreen();
 	bool showActiveSceneChoiceControls();
 	void captureSceneChoiceBacking();
 	void restoreSceneChoiceBacking(bool present = true);
@@ -178,6 +187,7 @@ private:
 	Video::AVIDecoder *_menuBackgroundVideo = nullptr;
 	Video::AVIDecoder *_gameOverVideo = nullptr;
 	Video::AVIDecoder *_sceneIdleVideo = nullptr;
+	Video::AVIDecoder *_mapMoveVideo = nullptr;
 	Video::AVIDecoder *_activeMovieVideo = nullptr;
 	Graphics::Surface *_titleBitmap = nullptr;
 	Graphics::Surface *_uiBitmap = nullptr;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 3ce39fdc093..cc9943f926b 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -71,11 +71,36 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 		return;
 	}
 
+	if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
+		if (kMovieQueueControlHitRect.contains(mousePosition)) {
+			playUiSound();
+			drawSceneChoiceControlStrip(kMovieControlStripSkipPressedSourceX);
+			delayWithEventPolling(160, false);
+			_previousDisplayStateBeforeSystem = _currentDisplayState;
+			_sceneChoiceActive = false;
+			_mapControlsActive = false;
+			if (_currentDisplayState == 1) {
+				stopSceneIdleMovie();
+				_currentDisplayState = 7;
+			} else {
+				stopMapMoveMovie();
+				drawMapSubmodeMovieBackdrop();
+				_currentDisplayState = 8;
+			}
+			_queuedMoviePlaybackIndex = 0;
+			processQueuedMovieSequence();
+			return;
+		}
+	}
+
 	if (_currentDisplayState == 1 && _sceneChoiceActive) {
 		handleSceneChoiceClick(mousePosition);
 		return;
 	}
 
+	if (handleMapControlClick(mousePosition))
+		return;
+
 	if (_currentDisplayState != 0 || !_titleButtonsActive)
 		return;
 
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index e22c93a29fd..c19dc9ffde3 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -31,13 +31,190 @@
 #include "harold/bcb/script.h"
 #include "harold/bcb/static_data.h"
 
-#include "common/textconsole.h"
-
 namespace BCB {
 
 static const uint32 kScriptSecondaryStateByteOffset = 0x98;
+bool BCBEngine::showMapScreen() {
+	Graphics::Surface *map = loadBitmap("16mmap.bmp");
+	if (!map)
+		return false;
+
+	applyPendingClockTickDelta();
+	renderClockAndChipToSurface(*map, 0, stateDword(kStateGameClockTicks));
+	if (_mapControlsActive)
+		composeMapControlsToSurface(*map, *map);
+	blitScreenWithLineReveal(*map, false);
+	map->free();
+	delete map;
+	_currentDisplayState = 2;
+	_lastTimerTick = g_system->getMillis();
+	g_system->updateScreen();
+	return true;
+}
+
+bool BCBEngine::openMapMoveMovie() {
+	if (_mapMoveVideo)
+		return true;
+
+	Common::SeekableReadStream *stream = createActiveDiscReadStream("bgm/move.avi");
+	if (!stream)
+		return false;
+
+	_mapMoveVideo = new Video::AVIDecoder();
+	_mapMoveVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+	if (!_mapMoveVideo->loadStream(stream)) {
+		delete _mapMoveVideo;
+		_mapMoveVideo = nullptr;
+		return false;
+	}
+
+	_mapMoveVideo->setOutputPixelFormat(g_system->getScreenFormat());
+	return true;
+}
+
+void BCBEngine::startMapMoveMovie() {
+	if (!_mapMoveVideo && !openMapMoveMovie())
+		return;
+
+	_mapMoveVideo->start();
+}
+
+void BCBEngine::stopMapMoveMovie() {
+	if (!_mapMoveVideo)
+		return;
+
+	_mapMoveVideo->stop();
+	delete _mapMoveVideo;
+	_mapMoveVideo = nullptr;
+}
+
+bool BCBEngine::isMapButtonEnabled(uint buttonIndex) const {
+	if (buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kMapButtonTable))
+		return false;
+
+	const int secondaryStateIndex = kMapButtonSecondaryStateIndexTable[buttonIndex];
+	return secondaryStateIndex == 0 || stateDword(kScriptSecondaryStateByteOffset + secondaryStateIndex * 4) != 0;
+}
+
+void BCBEngine::drawMapButtonPressed(uint buttonIndex, bool present) {
+	if (buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kMapButtonTable))
+		return;
+
+	const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+	if (!map)
+		return;
+
+	const ButtonControlRecord &button = kMapButtonTable[buttonIndex];
+	if (0x1c8 + button.width <= map->w && 0x1e0 + button.height <= map->h)
+		g_system->copyRectToScreen(map->getBasePtr(0x1c8, 0x1e0), map->pitch, button.x, button.y, button.width, button.height);
+
+	if (present)
+		g_system->updateScreen();
+}
+
 void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt) {
-	error("BCB: composeMapControlsToSurface is not implemented");
+	for (uint i = 1; i < ARRAYSIZE(kMapButtonTable); ++i) {
+		if (!isMapButtonEnabled(i))
+			continue;
+
+		const ButtonControlRecord &button = kMapButtonTable[i];
+		if (0x1d8 + button.width <= mapArt.w && 0x1e0 + button.height <= mapArt.h)
+			surface.copyRectToSurface(mapArt, button.x, button.y, Common::Rect(0x1d8, 0x1e0, 0x1d8 + button.width, 0x1e0 + button.height));
+	}
+
+	const uint32 shadowColor = surface.format.RGBToColor(0, 0, 0);
+	const uint32 textColor = surface.format.RGBToColor(255, 255, 255);
+	for (uint i = 1; i < ARRAYSIZE(kMapButtonTable); ++i) {
+		if (!isMapButtonEnabled(i))
+			continue;
+		if (i >= _mapButtonTextSjis.size() || _mapButtonTextSjis[i].empty())
+			continue;
+
+		const ButtonControlRecord &button = kMapButtonTable[i];
+		const int textX = button.x + 0x14;
+		const int textY = button.y - (_gameDescription->desc.language == Common::KO_KOR ? 5 : 4);
+		const int textWidth = MAX<int>(0, surface.w - textX - 2);
+		if (_gameDescription->desc.language == Common::JA_JPN && _sjisFont) {
+			int x = textX + 1;
+			for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+				const byte firstByte = *text++;
+				uint16 sjisChar = firstByte;
+				if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
+					sjisChar |= (uint16)(*text++) << 8;
+				_sjisFont->drawChar(surface, sjisChar, x, textY + 1, shadowColor, 0);
+				x += _sjisFont->getCharWidth(sjisChar);
+			}
+			x = textX;
+			for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+				const byte firstByte = *text++;
+				uint16 sjisChar = firstByte;
+				if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
+					sjisChar |= (uint16)(*text++) << 8;
+				_sjisFont->drawChar(surface, sjisChar, x, textY, textColor, 0);
+				x += _sjisFont->getCharWidth(sjisChar);
+			}
+		} else if (_ownerDrawUiTextFont && i < _mapButtonText.size()) {
+			_ownerDrawUiTextFont->drawString(&surface, _mapButtonText[i], textX + 1, textY + 1, textWidth, shadowColor, Graphics::kTextAlignLeft);
+			_ownerDrawUiTextFont->drawString(&surface, _mapButtonText[i], textX, textY, textWidth, textColor, Graphics::kTextAlignLeft);
+		}
+	}
+}
+
+void BCBEngine::drawMapControls(bool present) {
+	const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+	if (!map)
+		return;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		composeMapControlsToSurface(*screen, *map);
+		g_system->unlockScreen();
+	}
+
+	if (present)
+		g_system->updateScreen();
+}
+
+bool BCBEngine::handleMapControlClick(const Common::Point &mousePosition) {
+	if (_currentDisplayState != 2 || !_mapControlsActive)
+		return false;
+
+	for (uint i = 1; i < ARRAYSIZE(kMapButtonTable); ++i) {
+		if (!isMapButtonEnabled(i))
+			continue;
+
+		const ButtonControlRecord &button = kMapButtonTable[i];
+		if (!Common::Rect(button.x, button.y, button.x + button.width, button.y + button.height).contains(mousePosition))
+			continue;
+
+		drawMapButtonPressed(i);
+		stateDword(kStateSelectedSceneChoiceValue) = (button.controlId - 0xc2) & 0xff;
+		_mapControlsActive = false;
+		stateDword(kStateMovieHistoryCount) = 0;
+		stopMapMoveMovie();
+		playUiSound();
+		showMainSceneScreen();
+		processGameStateDispatcher(true);
+		return true;
+	}
+
+	return false;
+}
+
+bool BCBEngine::dispatchMapMoveControls() {
+	freeSceneChoiceBacking();
+	_sceneChoiceActive = false;
+	stopSceneIdleMovie();
+	openMapMoveMovie();
+	_mapControlsActive = true;
+	const bool showNewMapScreen = (_currentDisplayState != 2 && _currentDisplayState != 8);
+	if (showNewMapScreen)
+		showMapScreen();
+	_currentDisplayState = 2;
+	if (!showNewMapScreen)
+		drawMapControls();
+	startMapMoveMovie();
+	return true;
 }
 
 } // End of namespace BCB
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index e80ec6a5a80..e6c719d9550 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -34,6 +34,7 @@
 namespace BCB {
 
 bool BCBEngine::showMainSceneScreen() {
+	stopMapMoveMovie();
 	_sceneChoiceActive = false;
 	_mapControlsActive = false;
 	freeSceneChoiceBacking();
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 9b0c7f7d9a1..24d959a5901 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -33,6 +33,7 @@ namespace BCB {
 
 bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
 	stopSceneIdleMovie();
+	stopMapMoveMovie();
 	stopActiveMovie();
 	_sceneChoiceActive = false;
 	_mapControlsActive = false;


Commit: a193a2927d0532b44fedd5fa5d22fe55361a9ff8
    https://github.com/scummvm/scummvm/commit/a193a2927d0532b44fedd5fa5d22fe55361a9ff8
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Display chapter stills and game over

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/screens.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index aab18cfa356..665766846b9 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -146,6 +146,33 @@ void BCBEngine::mainLoop() {
 				if (_initialLogoElapsedTimerTicks > 0x3c)
 					transitionFromLogoToTitle();
 			}
+		} else if (_currentDisplayState == 1 && !_activeMovieVideo && !_sceneIdleVideo) {
+			const uint32 now = g_system->getMillis();
+			while (now - _lastTimerTick >= 50 && _currentDisplayState == 1 && !_activeMovieVideo && !_sceneIdleVideo) {
+				_lastTimerTick += 50;
+				++_sceneIdleMovieTimerTicks;
+				if (_sceneIdleMovieTimerTicks > 200)
+					startSceneIdleMovie();
+			}
+		} else if (_currentDisplayState == 6) {
+			const uint32 now = g_system->getMillis();
+			while (now - _lastTimerTick >= 50 && _currentDisplayState == 6) {
+				_lastTimerTick += 50;
+				++_pendingStillElapsedTimerTicks;
+				if (kStillImageDurationTicks[_pendingStillDurationIndex] < _pendingStillElapsedTimerTicks)
+					completePendingStillImageScreen();
+			}
+		} else if (_currentDisplayState == 9) {
+			const uint32 now = g_system->getMillis();
+			while (now - _lastTimerTick >= 50 && _currentDisplayState == 9) {
+				_lastTimerTick += 50;
+				++_gameOverElapsedTimerTicks;
+				if (stateDword(kStateClearDisplayBeforeMediaCheckFlag) != 0 || _gameOverElapsedTimerTicks > 300) {
+					resetGameState();
+					showTitleScreen();
+					startMenuBackgroundMusic();
+				}
+			}
 		}
 
 		if (_menuBackgroundVideo) {
@@ -190,6 +217,13 @@ void BCBEngine::mainLoop() {
 				_mapMoveVideo->decodeNextFrame();
 		}
 
+		if (_gameOverVideo) {
+			if (_gameOverVideo->needsUpdate())
+				_gameOverVideo->decodeNextFrame();
+			if (_gameOverVideo->endOfVideo())
+				stopGameOverMusic();
+		}
+
 		if (_activeMovieVideo) {
 			bool movieUpdated = false;
 			if (_activeMovieVideo->needsUpdate()) {
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index fd9b16f147f..21aac781edb 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -88,6 +88,8 @@ private:
 	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
 	void processGameStateDispatcher(bool runScriptBeforeDispatch);
 	bool dispatchMapMoveControls();
+	bool dispatchGameOverScreen();
+	bool dispatchEndingStillDisplayState();
 	void executeCurrentScriptSequence();
 	uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
 	bool pushScriptValue(uint32 value);
@@ -119,6 +121,8 @@ private:
 	void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
 	void drawMapControls(bool present = true);
 	bool handleMapControlClick(const Common::Point &mousePosition);
+	bool showPendingStillImageScreen();
+	void completePendingStillImageScreen();
 	bool showMainSceneScreen();
 	bool drawMainSceneScreen(bool lineReveal);
 	bool showMapScreen();
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index cc9943f926b..1aff33f17e5 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -71,6 +71,12 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 		return;
 	}
 
+	if (_currentDisplayState == 6) {
+		playUiSound();
+		completePendingStillImageScreen();
+		return;
+	}
+
 	if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
 		if (kMovieQueueControlHitRect.contains(mousePosition)) {
 			playUiSound();
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 3d0418a68d5..2bd2dd08009 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -107,6 +107,107 @@ bool BCBEngine::startNewGameFromTitle() {
 	return playNumberedMovie(6, true);
 }
 
+bool BCBEngine::showPendingStillImageScreen() {
+	const int stillImageToShowId = stateDword(kStateStillImageToShowId);
+	if (stillImageToShowId == 0)
+		return false;
+
+	const int stillIndex = stillImageToShowId - 0x34;
+	if (stillIndex < 0 || stillIndex >= ARRAYSIZE(kStillImageHighColorPaths)) {
+		warning("BCBEngine::showPendingStillImageScreen(): Unsupported still id %d", stillImageToShowId);
+		stateDword(kStateStillImageToShowId) = 0;
+		return false;
+	}
+
+	Graphics::Surface *still = loadDiscBitmap(kStillImageHighColorPaths[stillIndex]);
+	if (!still)
+		return false;
+
+	g_system->copyRectToScreen(still->getPixels(), still->pitch, 0, 0, MIN<int>(still->w, 640), MIN<int>(still->h, 480));
+	g_system->updateScreen();
+	still->free();
+	delete still;
+
+	_currentDisplayState = 6;
+	_pendingStillElapsedTimerTicks = 0;
+	_lastTimerTick = g_system->getMillis();
+	_pendingStillDurationIndex = stillIndex;
+	stateDword(kStateStillImageToShowId) = 0;
+	return true;
+}
+
+void BCBEngine::completePendingStillImageScreen() {
+	if (_currentDisplayState != 6)
+		return;
+
+	if (stateDword(kStateClearDisplayBeforeMediaCheckFlag) == 0)
+		showMainSceneScreen();
+	processGameStateDispatcher(true);
+}
+
+bool BCBEngine::dispatchGameOverScreen() {
+	stopSceneIdleMovie();
+	stopMapMoveMovie();
+	stopActiveMovie();
+	stopMenuBackgroundMusic();
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+	freeSceneChoiceBacking();
+
+	Graphics::Surface *gameOver = loadDiscBitmapFromAnyDisc("bmp/still/16m/pover.bmp");
+	if (!gameOver)
+		return false;
+
+	const uint selectedSceneChoiceValue = stateDword(kStateSelectedSceneChoiceValue);
+	if (selectedSceneChoiceValue < _gameOverText.size() && !_gameOverText[selectedSceneChoiceValue].empty()) {
+		const uint32 white = gameOver->format.RGBToColor(255, 255, 255);
+		const Common::Rect textRect(100, 200, 540, 400);
+		if (_gameOverTextFont) {
+			const Common::U32String &text = _gameOverText[selectedSceneChoiceValue];
+			int y = textRect.top - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
+			for (uint lineStart = 0; lineStart < text.size() && y < textRect.bottom;) {
+				uint lineEnd = lineStart;
+				uint lastFit = lineStart;
+				while (lineEnd < text.size() && text[lineEnd] != U'\n') {
+					++lineEnd;
+					const Common::U32String candidate(text.c_str() + lineStart, lineEnd - lineStart);
+					if (_gameOverTextFont->getStringWidth(candidate) <= textRect.width())
+						lastFit = lineEnd;
+					else
+						break;
+				}
+
+				if (lastFit == lineStart && lineEnd > lineStart)
+					lastFit = lineStart + 1;
+				if (lineEnd < text.size() && text[lineEnd] == U'\n')
+					lastFit = lineEnd;
+
+				_gameOverTextFont->drawString(gameOver, Common::U32String(text.c_str() + lineStart, lastFit - lineStart), textRect.left, y, textRect.width(), white, Graphics::kTextAlignLeft);
+				y += _gameOverTextFont->getFontHeight() + kGameOverTextLineGap;
+				lineStart = lastFit;
+				if (lineStart < text.size() && text[lineStart] == U'\n')
+					++lineStart;
+			}
+		}
+	}
+
+	blitScreenWithLineReveal(*gameOver, false);
+	gameOver->free();
+	delete gameOver;
+
+	_currentDisplayState = 9;
+	_gameOverElapsedTimerTicks = 0;
+	_lastTimerTick = g_system->getMillis();
+	g_system->updateScreen();
+	startGameOverMusic();
+	return true;
+}
+
+bool BCBEngine::dispatchEndingStillDisplayState() {
+	_currentDisplayState = 9;
+	return true;
+}
+
 void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
 	if (!_titleBitmap || buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kTitleButtonTable))
 		return;


Commit: c3aded185bfe9fb5a96ee4cd13d89f0a17e58f7f
    https://github.com/scummvm/scummvm/commit/c3aded185bfe9fb5a96ee4cd13d89f0a17e58f7f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Dispatch script requests to game screens

Changed paths:
  A engines/harold/bcb/computer.cpp
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/resources.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 665766846b9..5029638844d 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -131,6 +131,81 @@ Common::Error BCBEngine::run() {
 	return Common::kNoError;
 }
 
+void BCBEngine::clearDisplayAndResetState() {
+	_currentDisplayState = 0;
+	stateDword(kStateRequiredDiscIndex) = 0;
+	stopSceneIdleMovie();
+	stopMapMoveMovie();
+	restoreActiveCaptionBackingRect();
+	freeSceneChoiceBacking();
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		const uint32 black = screen->format.RGBToColor(0, 0, 0);
+		screen->fillRect(Common::Rect(0, 0, 640, 480), black);
+		g_system->unlockScreen();
+	}
+	g_system->updateScreen();
+}
+
+void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
+	_scriptStopFlag = false;
+
+	while (!shouldQuit() && !_scriptStopFlag) {
+		checkCurrentMediaPathReady();
+		if (showPendingStillImageScreen())
+			return;
+
+		if (processPendingMovieRequest())
+			return;
+
+		if (runScriptBeforeDispatch)
+			executeCurrentScriptSequence();
+
+		if (stateDword(kStateDispatcherControlCode) > 6) {
+			warning("BCBEngine::processGameStateDispatcher(): Invalid dispatcher control code %u", stateDword(kStateDispatcherControlCode));
+			_scriptStopFlag = true;
+			return;
+		}
+
+		if (dispatchGameStateControlCode(stateDword(kStateDispatcherControlCode)))
+			return;
+	}
+}
+
+bool BCBEngine::dispatchGameStateControlCode(uint32 dispatcherControlCode) {
+	switch (dispatcherControlCode) {
+	case 0:
+		return dispatchGameStateNoOp();
+	case 1:
+		return dispatchSceneChoiceControls();
+	case 2:
+		return dispatchMapMoveControls();
+	case 3:
+		return dispatchComputerMainControls();
+	case 4:
+		return dispatchComputerDetailControls();
+	case 5:
+		return dispatchGameOverScreen();
+	case 6:
+		return dispatchEndingStillDisplayState();
+	default:
+		warning("BCBEngine::dispatchGameStateControlCode(): Unhandled dispatcher control code %u", dispatcherControlCode);
+		_scriptStopFlag = true;
+		return true;
+	}
+}
+
+bool BCBEngine::dispatchGameStateNoOp() {
+	return false;
+}
+
+bool BCBEngine::dispatchSceneChoiceControls() {
+	return showActiveSceneChoiceControls();
+}
+
 void BCBEngine::mainLoop() {
 	while (!shouldQuit()) {
 		bool screenUpdated = false;
@@ -266,8 +341,4 @@ void BCBEngine::mainLoop() {
 	}
 }
 
-void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
-	error("BCB: processGameStateDispatcher is not implemented");
-}
-
 } // End of namespace BCB
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 21aac781edb..ec3f1630a94 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -87,9 +87,16 @@ private:
 	bool loadExecutableDataTables();
 	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
 	void processGameStateDispatcher(bool runScriptBeforeDispatch);
+	bool dispatchGameStateControlCode(uint32 dispatcherControlCode);
+	bool dispatchGameStateNoOp();
+	bool dispatchSceneChoiceControls();
 	bool dispatchMapMoveControls();
+	bool dispatchComputerMainControls();
+	bool dispatchComputerDetailControls();
 	bool dispatchGameOverScreen();
 	bool dispatchEndingStillDisplayState();
+	void checkCurrentMediaPathReady();
+	void clearDisplayAndResetState();
 	void executeCurrentScriptSequence();
 	uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
 	bool pushScriptValue(uint32 value);
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
new file mode 100644
index 00000000000..84372cd95a0
--- /dev/null
+++ b/engines/harold/bcb/computer.cpp
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this source distribution. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "graphics/surface.h"
+
+#include <math.h>
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+#include "common/textconsole.h"
+
+namespace BCB {
+
+static const int kComputerCompositeTransitionLeft = 36;
+static const int kComputerCompositeTransitionTop = 36;
+static const int kComputerCompositeTransitionRight = 606;
+static const int kComputerCompositeTransitionBottom = 444;
+
+bool BCBEngine::dispatchComputerDetailControls() {
+	error("BCB: dispatchComputerDetailControls is not implemented");
+}
+
+bool BCBEngine::dispatchComputerMainControls() {
+	error("BCB: dispatchComputerMainControls is not implemented");
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/resources.cpp b/engines/harold/bcb/resources.cpp
index 5d748dbc3f5..65da108503f 100644
--- a/engines/harold/bcb/resources.cpp
+++ b/engines/harold/bcb/resources.cpp
@@ -160,4 +160,51 @@ bool BCBEngine::switchActiveMediaDisc(int discNumber) {
 	return true;
 }
 
+void BCBEngine::checkCurrentMediaPathReady() {
+	if (stateDword(kStateClearDisplayBeforeMediaCheckFlag) != 0)
+		clearDisplayAndResetState();
+
+	const int requiredDiscNumber = stateDword(kStateRequiredDiscIndex) + 1;
+	if (requiredDiscNumber == _activeMediaDiscNumber)
+		return;
+
+	if (requiredDiscNumber < 1 || requiredDiscNumber > 5) {
+		warning("BCBEngine::checkCurrentMediaPathReady(): Invalid required media index %d from script state %u",
+				requiredDiscNumber, stateDword(kStateRequiredDiscIndex));
+		_scriptStopFlag = true;
+		return;
+	}
+
+	Common::ArchiveMemberList requiredDiscMembers;
+	if (_discFiles[requiredDiscNumber - 1].listMembers(requiredDiscMembers) == 0) {
+		warning("BCBEngine::checkCurrentMediaPathReady(): Missing installed media directory bcb%d",
+				requiredDiscNumber);
+		debugC(1, kBCBDebugGeneral,
+			   "BCB required media dump: active=bcb%d required=bcb%d scriptRequiredDiscIndex=%u",
+			   _activeMediaDiscNumber, requiredDiscNumber, stateDword(kStateRequiredDiscIndex));
+		_scriptStopFlag = true;
+		return;
+	}
+
+	_currentDisplayState = 0;
+	stopSceneIdleMovie();
+	restoreActiveCaptionBackingRect();
+	freeSceneChoiceBacking();
+	_sceneChoiceActive = false;
+
+	Graphics::Surface blackScreen;
+	blackScreen.create(640, 480, g_system->getScreenFormat());
+	blackScreen.fillRect(Common::Rect(0, 0, 640, 480), blackScreen.format.RGBToColor(0, 0, 0));
+	blitScreenWithLineReveal(blackScreen);
+	blackScreen.free();
+
+	if (!switchActiveMediaDisc(requiredDiscNumber)) {
+		warning("BCBEngine::checkCurrentMediaPathReady(): Failed to switch to installed media directory bcb%d", requiredDiscNumber);
+		_scriptStopFlag = true;
+		return;
+	}
+
+	stateDword(kStateMovieHistoryCount) = 0;
+}
+
 } // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index bf42103d2ce..e7461a46f44 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -8,6 +8,7 @@ MODULE_OBJS := \
 	bcb/scene.o \
 	bcb/screens.o \
 	bcb/input.o \
+	bcb/computer.o \
 	bcb/map.o \
 	bcb/script.o \
 	bcb/executable_data.o \


Commit: b5b73fb8db0bce1ee58837e1218f2b9358322caf
    https://github.com/scummvm/scummvm/commit/b5b73fb8db0bce1ee58837e1218f2b9358322caf
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add computer categories and evidence icons

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/computer.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 5029638844d..ca76a37b336 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -81,6 +81,10 @@ BCBEngine::~BCBEngine() {
 		_chipBitmap->free();
 		delete _chipBitmap;
 	}
+	if (_computerBitmap) {
+		_computerBitmap->free();
+		delete _computerBitmap;
+	}
 	delete _activeMovieVideo;
 	delete _mapMoveVideo;
 	delete _sceneIdleVideo;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index ec3f1630a94..4e04675e640 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -95,6 +95,13 @@ private:
 	bool dispatchComputerDetailControls();
 	bool dispatchGameOverScreen();
 	bool dispatchEndingStillDisplayState();
+	const Graphics::Surface *getComputerBitmap();
+	bool showComputerScreen();
+	void drawComputerChoiceGroupControls();
+	void drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount);
+	void drawComputerButton(const ButtonControlRecord &sourceButton, const ButtonControlRecord &destButton);
+	void drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY);
+	void drawComputerUnavailableMarker(const ButtonControlRecord &destButton);
 	void checkCurrentMediaPathReady();
 	void clearDisplayAndResetState();
 	void executeCurrentScriptSequence();
@@ -206,6 +213,7 @@ private:
 	Graphics::Surface *_chipBitmap = nullptr;
 	Graphics::Surface *_sceneChoiceBacking = nullptr;
 	Common::Rect _sceneChoiceBackingRect;
+	Graphics::Surface *_computerBitmap = nullptr;
 	Audio::SoundHandle _uiSoundHandle;
 	uint32 _gameState[0x100] = {};
 	uint32 _scriptValueStack[0x100] = {};
@@ -223,8 +231,13 @@ private:
 	int _nextTimedCaptionThreshold = 99999;
 	int _queuedMoviePlaybackIndex = 0;
 	int _previousDisplayStateBeforeSystem = 0;
+	int _computerChoiceGroupIndex = 0;
 	int _sceneChoiceTerminatorButtonIndex = 0;
 	int _sceneChoiceEnabledButtonLimit = 0;
+	bool _computerControlsActive = false;
+	bool _computerDetailControlsActive = false;
+	Common::Array<int> _computerActiveChoiceIds;
+	Common::Array<Common::Rect> _computerActiveChoiceRects;
 	Common::U32String _timedCaptionText;
 	Common::String _timedCaptionTextSjis;
 	Common::Rect _activeCaptionBackingRect;
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 84372cd95a0..a343b3850cd 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -40,12 +40,152 @@ static const int kComputerCompositeTransitionTop = 36;
 static const int kComputerCompositeTransitionRight = 606;
 static const int kComputerCompositeTransitionBottom = 444;
 
-bool BCBEngine::dispatchComputerDetailControls() {
-	error("BCB: dispatchComputerDetailControls is not implemented");
+const Graphics::Surface *BCBEngine::getComputerBitmap() {
+	if (!_computerBitmap) {
+		_computerBitmap = loadBitmap("256com.bmp");
+	}
+	return _computerBitmap;
 }
 
 bool BCBEngine::dispatchComputerMainControls() {
-	error("BCB: dispatchComputerMainControls is not implemented");
+	if (!showComputerScreen())
+		return false;
+
+	_computerControlsActive = true;
+	_computerDetailControlsActive = false;
+	_computerActiveChoiceIds.clear();
+	_computerActiveChoiceRects.clear();
+	drawComputerChoiceGroupControls();
+	drawComputerButton(kComputerPrimaryButtonTable[1], kComputerPrimaryButtonTable[1]);
+
+	g_system->updateScreen();
+	return true;
+}
+
+bool BCBEngine::showComputerScreen() {
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return false;
+
+	g_system->copyRectToScreen(computer->getPixels(), computer->pitch, 0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480));
+	_currentDisplayState = 4;
+	return true;
+}
+
+void BCBEngine::drawComputerChoiceGroupControls() {
+	_computerActiveChoiceIds.clear();
+	_computerActiveChoiceRects.clear();
+
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		if (_computerChoiceGroupIndex >= 0 && _computerChoiceGroupIndex <= 2) {
+			const int sourceY = _computerChoiceGroupIndex * 0x16 + 0x2a0;
+			if (sourceY + 0x16 <= computer->h)
+				screen->copyRectToSurface(*computer, 0x3c, 0x43, Common::Rect(0, sourceY, 0xe8, sourceY + 0x16));
+		}
+
+		for (uint i = 1; i < ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
+			const ButtonControlRecord &slot = kComputerChoiceLayoutSlots[i];
+			if (slot.x + slot.width <= computer->w && slot.y + slot.height <= computer->h)
+				screen->copyRectToSurface(*computer, slot.x, slot.y, Common::Rect(slot.x, slot.y, slot.x + slot.width, slot.y + slot.height));
+		}
+		g_system->unlockScreen();
+	}
+
+	if (_computerChoiceGroupIndex == 0)
+		drawComputerConditionalChoiceControls(kComputerChoiceButtonTable + 21, 21, 6);
+	else if (_computerChoiceGroupIndex == 1)
+		drawComputerConditionalChoiceControls(kComputerChoiceButtonTable, 0, 21);
+	else if (_computerChoiceGroupIndex == 2)
+		drawComputerConditionalChoiceControls(kComputerChoiceButtonTable + 27, 27, 3);
+}
+
+void BCBEngine::drawComputerButton(const ButtonControlRecord &sourceButton, const ButtonControlRecord &destButton) {
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen && sourceButton.x + sourceButton.width <= computer->w && sourceButton.y + sourceButton.height <= computer->h)
+		screen->copyRectToSurface(*computer, destButton.x, destButton.y, Common::Rect(sourceButton.x, sourceButton.y, sourceButton.x + sourceButton.width, sourceButton.y + sourceButton.height));
+	if (screen)
+		g_system->unlockScreen();
+}
+
+void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY) {
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return;
+
+	if (sourceX + button.width <= computer->w && sourceY + button.height <= computer->h) {
+		g_system->copyRectToScreen(computer->getBasePtr(sourceX, sourceY), computer->pitch, button.x, button.y, button.width, button.height);
+		g_system->updateScreen();
+	}
+}
+
+void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destButton) {
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return;
+
+	const int markerSourceX = 0x240;
+	const int markerSourceY = 0x260;
+	const int markerWidth = 0x36;
+	const int markerHeight = 0x18;
+	if (markerSourceX + markerWidth <= computer->w && markerSourceY + markerHeight <= computer->h)
+		g_system->copyRectToScreen(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
+}
+
+void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount) {
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	int visibleButtonSlot = 1;
+	for (int i = 0; screen && i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
+		const int stateIndex = firstStateIndex + i;
+		const byte dependencyStateIndex = stateIndex >= 0 && stateIndex < (int)ARRAYSIZE(kComputerChoiceDependencyStateIndex) ? kComputerChoiceDependencyStateIndex[stateIndex] : 0;
+		const bool directlyAvailable = stateDword(kStateComputerChoiceStateByIndex + stateIndex * 4) != 0;
+		const bool dependencyOnlyAvailable =
+			dependencyStateIndex != 0 && stateDword(kStateComputerChoiceStateByIndex + dependencyStateIndex * 4) == 1;
+		const bool visible = directlyAvailable || dependencyOnlyAvailable;
+		if (!visible)
+			continue;
+
+		const ButtonControlRecord &source = choiceButtonRecords[i];
+		const ButtonControlRecord &dest = kComputerChoiceLayoutSlots[visibleButtonSlot++];
+		if (source.x + source.width <= computer->w && source.y + source.height <= computer->h)
+			screen->copyRectToSurface(*computer, dest.x, dest.y, Common::Rect(source.x, source.y, source.x + source.width, source.y + source.height));
+		if (directlyAvailable) {
+			_computerActiveChoiceIds.push_back(source.controlId);
+			_computerActiveChoiceRects.push_back(Common::Rect(dest.x, dest.y, dest.x + dest.width, dest.y + dest.height));
+		}
+	}
+	if (screen)
+		g_system->unlockScreen();
+
+	visibleButtonSlot = 1;
+	for (int i = 0; i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
+		const int stateIndex = firstStateIndex + i;
+		const byte dependencyStateIndex = stateIndex >= 0 && stateIndex < (int)ARRAYSIZE(kComputerChoiceDependencyStateIndex) ? kComputerChoiceDependencyStateIndex[stateIndex] : 0;
+		const bool directlyAvailable = stateDword(kStateComputerChoiceStateByIndex + stateIndex * 4) != 0;
+		const bool dependencyOnlyAvailable =
+			dependencyStateIndex != 0 && stateDword(kStateComputerChoiceStateByIndex + dependencyStateIndex * 4) == 1;
+		if (!directlyAvailable && !dependencyOnlyAvailable)
+			continue;
+		const ButtonControlRecord &dest = kComputerChoiceLayoutSlots[visibleButtonSlot++];
+		if (!directlyAvailable && dependencyOnlyAvailable)
+			drawComputerUnavailableMarker(dest);
+	}
+}
+
+bool BCBEngine::dispatchComputerDetailControls() {
+	error("BCB: dispatchComputerDetailControls is not implemented");
 }
 
 } // End of namespace BCB


Commit: 35467ac424915868e28a871353df568ce8e67660
    https://github.com/scummvm/scummvm/commit/35467ac424915868e28a871353df568ce8e67660
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Display computer pages and transitions

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/computer.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index ca76a37b336..b1086851277 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -90,6 +90,10 @@ BCBEngine::~BCBEngine() {
 	delete _sceneIdleVideo;
 	delete _gameOverVideo;
 	freeSceneChoiceBacking();
+	if (_computerDetailBacking) {
+		_computerDetailBacking->free();
+		delete _computerDetailBacking;
+	}
 	delete _sjisFont;
 	delete _captionTextFont;
 	delete _gameOverTextFont;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 4e04675e640..b1ce6fd0aef 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -101,7 +101,11 @@ private:
 	void drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount);
 	void drawComputerButton(const ButtonControlRecord &sourceButton, const ButtonControlRecord &destButton);
 	void drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY);
+	void drawComputerDetailButtonNormal(const ButtonControlRecord &button);
 	void drawComputerUnavailableMarker(const ButtonControlRecord &destButton);
+	void drawXorRectangleBorder(const Common::Rect &rect);
+	void processXorRectangleBorderAnimation(bool expand, const Common::Rect &sourceRect, const Common::Rect &targetRect);
+	bool showComputerDetailScreen(int compositeIndex);
 	void checkCurrentMediaPathReady();
 	void clearDisplayAndResetState();
 	void executeCurrentScriptSequence();
@@ -214,6 +218,7 @@ private:
 	Graphics::Surface *_sceneChoiceBacking = nullptr;
 	Common::Rect _sceneChoiceBackingRect;
 	Graphics::Surface *_computerBitmap = nullptr;
+	Graphics::Surface *_computerDetailBacking = nullptr;
 	Audio::SoundHandle _uiSoundHandle;
 	uint32 _gameState[0x100] = {};
 	uint32 _scriptValueStack[0x100] = {};
@@ -232,10 +237,12 @@ private:
 	int _queuedMoviePlaybackIndex = 0;
 	int _previousDisplayStateBeforeSystem = 0;
 	int _computerChoiceGroupIndex = 0;
+	int _computerDetailVariantIndex = 0;
 	int _sceneChoiceTerminatorButtonIndex = 0;
 	int _sceneChoiceEnabledButtonLimit = 0;
 	bool _computerControlsActive = false;
 	bool _computerDetailControlsActive = false;
+	bool _computerDetailNextVariantEnabled = false;
 	Common::Array<int> _computerActiveChoiceIds;
 	Common::Array<Common::Rect> _computerActiveChoiceRects;
 	Common::U32String _timedCaptionText;
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index a343b3850cd..7b16e8d2cf2 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -31,8 +31,6 @@
 #include "harold/bcb/script.h"
 #include "harold/bcb/static_data.h"
 
-#include "common/textconsole.h"
-
 namespace BCB {
 
 static const int kComputerCompositeTransitionLeft = 36;
@@ -62,6 +60,15 @@ bool BCBEngine::dispatchComputerMainControls() {
 	return true;
 }
 
+bool BCBEngine::dispatchComputerDetailControls() {
+	_computerDetailVariantIndex = 0;
+	_computerControlsActive = false;
+	_computerDetailControlsActive = true;
+	_computerActiveChoiceIds.clear();
+	_computerActiveChoiceRects.clear();
+	return showComputerDetailScreen(stateDword(kStateSelectedSceneChoiceValue));
+}
+
 bool BCBEngine::showComputerScreen() {
 	const Graphics::Surface *computer = getComputerBitmap();
 	if (!computer)
@@ -127,6 +134,16 @@ void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int
 	}
 }
 
+void BCBEngine::drawComputerDetailButtonNormal(const ButtonControlRecord &button) {
+	if (!_computerDetailBacking)
+		return;
+
+	if (button.x + button.width <= _computerDetailBacking->w && button.y + button.height <= _computerDetailBacking->h) {
+		g_system->copyRectToScreen(_computerDetailBacking->getBasePtr(button.x, button.y), _computerDetailBacking->pitch, button.x, button.y, button.width, button.height);
+		g_system->updateScreen();
+	}
+}
+
 void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destButton) {
 	const Graphics::Surface *computer = getComputerBitmap();
 	if (!computer)
@@ -140,6 +157,87 @@ void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destBut
 		g_system->copyRectToScreen(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
 }
 
+void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (!screen)
+		return;
+
+	const Common::Rect clipped = rect.findIntersectingRect(Common::Rect(0, 0, screen->w, screen->h));
+	if (clipped.width() > 0 && clipped.height() > 0) {
+		const auto invertPixel = [&](int x, int y) {
+			byte *pixel = (byte *)screen->getBasePtr(x, y);
+			switch (screen->format.bytesPerPixel) {
+			case 1:
+				pixel[0] ^= 0xff;
+				break;
+			case 2:
+				WRITE_UINT16(pixel, READ_UINT16(pixel) ^ 0xffff);
+				break;
+			case 3:
+				pixel[0] ^= 0xff;
+				pixel[1] ^= 0xff;
+				pixel[2] ^= 0xff;
+				break;
+			case 4:
+				pixel[0] ^= 0xff;
+				pixel[1] ^= 0xff;
+				pixel[2] ^= 0xff;
+				break;
+			default:
+				break;
+			}
+		};
+
+		for (int x = clipped.left; x < clipped.right; ++x) {
+			invertPixel(x, clipped.top);
+			if (rect.bottom != rect.top && rect.bottom >= 0 && rect.bottom < screen->h)
+				invertPixel(x, rect.bottom);
+		}
+		for (int y = clipped.top; y < clipped.bottom; ++y) {
+			invertPixel(clipped.left, y);
+			if (rect.right != rect.left && rect.right >= 0 && rect.right < screen->w)
+				invertPixel(rect.right, y);
+		}
+	}
+
+	g_system->unlockScreen();
+	g_system->updateScreen();
+}
+
+void BCBEngine::processXorRectangleBorderAnimation(bool expand, const Common::Rect &sourceRect, const Common::Rect &targetRect) {
+	const double base = 0.7;
+	double drawScale = pow(base, 15.0);
+	double eraseScale = drawScale;
+	const double divisor = expand ? base : (1.0 / base);
+	if (!expand) {
+		drawScale = 1.0 - drawScale;
+		eraseScale = drawScale;
+	}
+
+	const auto transitionRect = [&](double scale) {
+		return Common::Rect(
+			sourceRect.left + (int16)(int)((targetRect.left - sourceRect.left) * scale),
+			sourceRect.top + (int16)(int)((targetRect.top - sourceRect.top) * scale),
+			sourceRect.right + (int16)(int)((targetRect.right - sourceRect.right) * scale),
+			sourceRect.bottom + (int16)(int)((targetRect.bottom - sourceRect.bottom) * scale));
+	};
+
+	for (int step = 0; step < 22; ++step) {
+		if (step < 14) {
+			drawScale /= divisor;
+			drawXorRectangleBorder(transitionRect(drawScale));
+		}
+
+		if (step > 7) {
+			eraseScale /= divisor;
+			drawXorRectangleBorder(transitionRect(eraseScale));
+		}
+
+		g_system->delayMillis(20);
+		pollEvents(false);
+	}
+}
+
 void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount) {
 	const Graphics::Surface *computer = getComputerBitmap();
 	if (!computer)
@@ -184,8 +282,49 @@ void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord
 	}
 }
 
-bool BCBEngine::dispatchComputerDetailControls() {
-	error("BCB: dispatchComputerDetailControls is not implemented");
+bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
+	if (compositeIndex < 0 || compositeIndex >= (int)ARRAYSIZE(kComputerDetailBitmapVariantsBySelectedValue))
+		return false;
+
+	const CompositeBitmapVariantSet &variantSet = kComputerDetailBitmapVariantsBySelectedValue[compositeIndex];
+	if (variantSet.pathCount == 0 || _computerDetailVariantIndex < 0 || _computerDetailVariantIndex >= (int)variantSet.pathCount)
+		return false;
+	const int displayedVariantIndex = _computerDetailVariantIndex;
+	const char *const compositePath = variantSet.paths[_computerDetailVariantIndex];
+
+	const Graphics::Surface *computer = getComputerBitmap();
+	if (!computer)
+		return false;
+
+	Graphics::Surface *composite = loadDiscBitmap(compositePath);
+	if (!composite)
+		return false;
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		screen->copyRectToSurface(*computer, 0, 0, Common::Rect(0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480)));
+		screen->copyRectToSurface(*composite, 0x24, 0x23, Common::Rect(0, 0, MIN<int>(composite->w, 640 - 0x24), MIN<int>(composite->h, 480 - 0x23)));
+		if (_computerDetailBacking) {
+			_computerDetailBacking->free();
+			delete _computerDetailBacking;
+			_computerDetailBacking = nullptr;
+		}
+		_computerDetailBacking = new Graphics::Surface();
+		_computerDetailBacking->copyFrom(*screen);
+		g_system->unlockScreen();
+	}
+
+	composite->free();
+	delete composite;
+
+	_computerDetailNextVariantEnabled = variantSet.pathCount > 1;
+
+	if (_computerDetailNextVariantEnabled && displayedVariantIndex + 1 >= (int)variantSet.pathCount)
+		_computerDetailVariantIndex = -1;
+
+	_currentDisplayState = 5;
+	g_system->updateScreen();
+	return true;
 }
 
 } // End of namespace BCB


Commit: 09f13dc11485d9c262d511d8718840c625a8dcac
    https://github.com/scummvm/scummvm/commit/09f13dc11485d9c262d511d8718840c625a8dcac
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Handle computer navigation and page input

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/computer.cpp
    engines/harold/bcb/input.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index b1ce6fd0aef..e42f723d422 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -153,6 +153,8 @@ private:
 	void drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool present = true, int pressedButtonIndex = -1);
 	void handleSceneChoiceClick(const Common::Point &mousePosition);
 	void handleSceneChoiceSelection(int buttonIndex);
+	bool handleComputerClick(const Common::Point &mousePosition);
+	bool handleComputerKey(Common::KeyCode keycode);
 	void applyPendingClockTickDelta();
 	void notifyGameClockTicksChanged();
 	void drawClockAndChipIndicator(bool present = true);
@@ -245,6 +247,7 @@ private:
 	bool _computerDetailNextVariantEnabled = false;
 	Common::Array<int> _computerActiveChoiceIds;
 	Common::Array<Common::Rect> _computerActiveChoiceRects;
+	Common::Rect _computerSelectionXorRect;
 	Common::U32String _timedCaptionText;
 	Common::String _timedCaptionTextSjis;
 	Common::Rect _activeCaptionBackingRect;
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 7b16e8d2cf2..fe33c6e87f4 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -327,4 +327,97 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
 	return true;
 }
 
+bool BCBEngine::handleComputerClick(const Common::Point &mousePosition) {
+	if (_currentDisplayState == 4 && _computerControlsActive) {
+		const ButtonControlRecord &cancelButton = kComputerPrimaryButtonTable[1];
+		if (Common::Rect(cancelButton.x, cancelButton.y, cancelButton.x + cancelButton.width, cancelButton.y + cancelButton.height).contains(mousePosition)) {
+			drawComputerButtonPressed(cancelButton, 0xf8, 0x2d0);
+			playUiSound();
+			_computerControlsActive = false;
+			_computerActiveChoiceIds.clear();
+			_computerActiveChoiceRects.clear();
+			stateDword(kStateSelectedSceneChoiceValue) = 0xff;
+			processGameStateDispatcher(true);
+			return true;
+		}
+
+		for (uint groupIndex = 0; groupIndex < ARRAYSIZE(kComputerChoiceGroupHitRects); ++groupIndex) {
+			if (!kComputerChoiceGroupHitRects[groupIndex].contains(mousePosition))
+				continue;
+
+			if (_computerChoiceGroupIndex != (int)groupIndex) {
+				playUiSound();
+				_computerChoiceGroupIndex = groupIndex;
+				drawComputerChoiceGroupControls();
+				g_system->updateScreen();
+			}
+			return true;
+		}
+
+		for (uint i = 0; i < _computerActiveChoiceRects.size(); ++i) {
+			if (!_computerActiveChoiceRects[i].contains(mousePosition))
+				continue;
+
+			playUiSound();
+			_computerSelectionXorRect = _computerActiveChoiceRects[i];
+			processXorRectangleBorderAnimation(true, _computerSelectionXorRect, Common::Rect(kComputerCompositeTransitionLeft, kComputerCompositeTransitionTop, kComputerCompositeTransitionRight, kComputerCompositeTransitionBottom));
+			_computerControlsActive = false;
+			stateDword(kStateSelectedSceneChoiceValue) = _computerActiveChoiceIds[i] - 700;
+			_computerActiveChoiceIds.clear();
+			_computerActiveChoiceRects.clear();
+			processGameStateDispatcher(true);
+			return true;
+		}
+	}
+
+	if (_currentDisplayState == 5 && _computerDetailControlsActive) {
+		const ButtonControlRecord &continueButton = kComputerDetailButtonTable[1];
+		if (Common::Rect(continueButton.x, continueButton.y, continueButton.x + continueButton.width, continueButton.y + continueButton.height).contains(mousePosition)) {
+			drawComputerButtonPressed(continueButton, 0x149, 0x2b8);
+			playUiSound();
+			if (!_computerSelectionXorRect.isEmpty())
+				processXorRectangleBorderAnimation(false, _computerSelectionXorRect, Common::Rect(kComputerCompositeTransitionLeft, kComputerCompositeTransitionTop, kComputerCompositeTransitionRight, kComputerCompositeTransitionBottom));
+			_computerDetailControlsActive = false;
+			processGameStateDispatcher(true);
+			return true;
+		}
+
+		const ButtonControlRecord &nextButton = kComputerDetailButtonTable[2];
+		if (_computerDetailNextVariantEnabled &&
+				Common::Rect(nextButton.x, nextButton.y, nextButton.x + nextButton.width, nextButton.y + nextButton.height).contains(mousePosition)) {
+			drawComputerButtonPressed(nextButton, _computerDetailVariantIndex >= 0 ? 0x188 : 0x228, 0x2a0);
+			playUiSound();
+			if (_computerDetailVariantIndex < 0) {
+				_computerDetailVariantIndex = 0;
+			} else {
+				++_computerDetailVariantIndex;
+			}
+			showComputerDetailScreen(stateDword(kStateSelectedSceneChoiceValue));
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool BCBEngine::handleComputerKey(Common::KeyCode keycode) {
+	if (_currentDisplayState == 4 && _computerControlsActive && keycode == Common::KEYCODE_ESCAPE) {
+		const ButtonControlRecord &cancelButton = kComputerPrimaryButtonTable[1];
+		return handleComputerClick(Common::Point(cancelButton.x + 1, cancelButton.y + 1));
+	}
+
+	if (_currentDisplayState == 5 && _computerDetailControlsActive) {
+		if (keycode == Common::KEYCODE_ESCAPE) {
+			const ButtonControlRecord &continueButton = kComputerDetailButtonTable[1];
+			return handleComputerClick(Common::Point(continueButton.x + 1, continueButton.y + 1));
+		}
+
+		if (keycode == Common::KEYCODE_SPACE && _computerDetailNextVariantEnabled) {
+			const ButtonControlRecord &nextButton = kComputerDetailButtonTable[2];
+			return handleComputerClick(Common::Point(nextButton.x + 1, nextButton.y + 1));
+		}
+	}
+
+	return false;
+}
 } // End of namespace BCB
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 1aff33f17e5..a66200a6d84 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -104,6 +104,9 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 		return;
 	}
 
+	if (handleComputerClick(mousePosition))
+		return;
+
 	if (handleMapControlClick(mousePosition))
 		return;
 


Commit: 65e7fb2b8c2d8f98d636eef91da7ba57c4856feb
    https://github.com/scummvm/scummvm/commit/65e7fb2b8c2d8f98d636eef91da7ba57c4856feb
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Save and restore BCB game state

Changed paths:
  A engines/harold/bcb/saveload.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/map.cpp
    engines/harold/bcb/scene.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index e42f723d422..fb44cfc184d 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -60,6 +60,12 @@ public:
 	~BCBEngine();
 
 	Common::Error run() override;
+	bool hasFeature(EngineFeature f) const override;
+	Common::Error loadGameState(int slot) override;
+	bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
+	Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
+	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
+	bool canSaveAutosaveCurrently() override;
 
 private:
 	bool initializeSearchPaths();
@@ -79,10 +85,12 @@ private:
 	void startSceneIdleMovie();
 	void stopSceneIdleMovie();
 	void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
+	void writeContinueState();
 	bool openMapMoveMovie();
 	void startMapMoveMovie();
 	void stopMapMoveMovie();
 	void resetGameState();
+	void resetTransientRuntimeStateAfterLoad();
 	bool loadScriptData();
 	bool loadExecutableDataTables();
 	bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
@@ -179,6 +187,8 @@ private:
 	void delayWithEventPolling(uint32 millis);
 	void delayWithEventPolling(uint32 millis, bool handleClicks);
 	Common::U32String decodeExecutableTextForDisplay(const Common::String &text) const;
+	Common::U32String formatSystemTextForDisplay(const Common::String &originalFormat, int slot) const;
+	Common::U32String formatSystemClockTextForDisplay(const Common::String &originalFormat, int slot, int savedDay, int savedClockTicks) const;
 	void mainLoop();
 
 	const HaroldGameDescription *_gameDescription;
@@ -263,6 +273,7 @@ private:
 	bool _suppressClickHandling = false;
 	bool _titleButtonsActive = false;
 	bool _systemButtonsVisible = false;
+	int _systemSaveLoadSlotActionMode = 0;
 	bool _titleLoadSlotsActive = false;
 };
 
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index a66200a6d84..15158da2c9d 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -126,6 +126,13 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 		case 51:
 			startNewGameFromTitle();
 			break;
+		case 52:
+			drawTitleButton(i, false);
+			_titleButtonsActive = false;
+			_titleLoadSlotsActive = false;
+			if (loadGameState(0).getCode() != Common::kNoError)
+				_titleButtonsActive = true;
+			break;
 		default:
 			drawTitleButton(i, false);
 			break;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index c19dc9ffde3..a4d78397061 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -211,6 +211,7 @@ bool BCBEngine::dispatchMapMoveControls() {
 	if (showNewMapScreen)
 		showMapScreen();
 	_currentDisplayState = 2;
+	writeContinueState();
 	if (!showNewMapScreen)
 		drawMapControls();
 	startMapMoveMovie();
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
new file mode 100644
index 00000000000..d3cbbb01599
--- /dev/null
+++ b/engines/harold/bcb/saveload.cpp
@@ -0,0 +1,226 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/config-manager.h"
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/savefile.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/surface.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+namespace BCB {
+
+enum {
+	kSavegameMagic = MKTAG('B', 'C', 'B', 'S'),
+	kSavegameVersion = 1,
+	kManualSaveSlotCount = 8,
+	kNativeGameStateDwordCount = 0x100,
+	kNativeGameStateByteSize = kNativeGameStateDwordCount * 4
+};
+
+static const StaticRect kSystemWaveVolumeDownRect = {268, 407, 290, 439};
+static const StaticRect kSystemWaveVolumeUpRect = {444, 407, 466, 439};
+static const StaticRect kSystemWaveVolumeSeekRect = {294, 414, 442, 439};
+static const StaticRect kSystemWaveVolumeIndicatorRect = {294, 425, 442, 449};
+
+static const Common::Point kSystemSubmodeNormalSources[5] = {
+	Common::Point(0, 0),
+	Common::Point(0, 736),
+	Common::Point(0, 824),
+	Common::Point(240, 736),
+	Common::Point(274, 824)};
+
+static const Common::Point kSystemSubmodeSelectedSources[5] = {
+	Common::Point(0, 0),
+	Common::Point(0, 780),
+	Common::Point(0, 868),
+	Common::Point(240, 780),
+	Common::Point(368, 824)};
+
+static Common::String formatEnglishSaveDescription(int savedDay, int savedClockTicks) {
+	const int discIndex = CLIP<int>(savedDay, 0, 4);
+	const int dayOfMonth = 10 + discIndex;
+	const int clockTicks = savedClockTicks % (24 * 60);
+	const int hour24 = clockTicks / 60;
+	const int minute = clockTicks % 60;
+	const int hour12 = hour24 % 12 == 0 ? 12 : hour24 % 12;
+	const char *period = hour24 < 12 ? "AM" : "PM";
+
+	const Common::String chapterName = discIndex == 0 ? Common::String("Prologue") : Common::String::format("Chapter %d", discIndex);
+	return Common::String::format("%s: November %d, %d:%02d %s",
+								  chapterName.c_str(), dayOfMonth, hour12, minute, period);
+}
+
+static bool isValidBCBSaveSlot(int slot) {
+	return slot >= 0 && slot <= kManualSaveSlotCount;
+}
+
+bool BCBEngine::hasFeature(EngineFeature f) const {
+	return f == kSupportsLoadingDuringRuntime ||
+		   f == kSupportsSavingDuringRuntime ||
+		   f == kSupportsReturnToLauncher;
+}
+
+bool BCBEngine::canLoadGameStateCurrently(Common::U32String *msg) {
+	return true;
+}
+
+bool BCBEngine::canSaveGameStateCurrently(Common::U32String *msg) {
+	return true;
+}
+
+bool BCBEngine::canSaveAutosaveCurrently() {
+	return false;
+}
+
+Common::Error BCBEngine::saveGameState(int slot, const Common::String &, bool isAutosave) {
+	if (!isValidBCBSaveSlot(slot))
+		return Common::kWritingFailed;
+
+	Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(getSaveStateName(slot));
+	if (!saveFile)
+		return Common::kWritingFailed;
+
+	saveFile->writeUint32BE(kSavegameMagic);
+	saveFile->writeUint16LE(kSavegameVersion);
+	saveFile->writeUint16LE(kNativeGameStateByteSize);
+
+	for (uint i = 0; i < kNativeGameStateDwordCount; ++i)
+		saveFile->writeUint32LE(_gameState[i]);
+
+	Common::Error result = saveFile->err() ? Common::kWritingFailed : Common::kNoError;
+	if (result.getCode() == Common::kNoError) {
+		const Common::String saveDescription = formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks));
+		getMetaEngine()->appendExtendedSave(saveFile, getTotalPlayTime(), saveDescription, isAutosave);
+	}
+
+	delete saveFile;
+	return result;
+}
+
+Common::Error BCBEngine::loadGameState(int slot) {
+	if (!isValidBCBSaveSlot(slot))
+		return Common::kReadingFailed;
+
+	Common::InSaveFile *saveFile = _saveFileMan->openForLoading(getSaveStateName(slot));
+	if (!saveFile)
+		return Common::kReadingFailed;
+
+	bool validSave = false;
+	const uint32 magic = saveFile->readUint32BE();
+	if (magic == kSavegameMagic) {
+		const uint16 version = saveFile->readUint16LE();
+		const uint16 stateByteSize = saveFile->readUint16LE();
+		validSave = version == kSavegameVersion && stateByteSize == kNativeGameStateByteSize;
+	} else if (saveFile->size() == kNativeGameStateByteSize) {
+		saveFile->seek(0);
+		validSave = true;
+	}
+
+	if (validSave) {
+		for (uint i = 0; i < kNativeGameStateDwordCount; ++i)
+			_gameState[i] = saveFile->readUint32LE();
+		validSave = !saveFile->err();
+	}
+
+	delete saveFile;
+	if (!validSave)
+		return Common::kReadingFailed;
+
+	resetTransientRuntimeStateAfterLoad();
+	processGameStateDispatcher(false);
+	return Common::kNoError;
+}
+
+void BCBEngine::resetTransientRuntimeStateAfterLoad() {
+	stopActiveMovie();
+	stopMenuBackgroundMusic();
+	stopSceneIdleMovie();
+	stopMapMoveMovie();
+	stopGameOverMusic();
+	restoreActiveCaptionBackingRect();
+	freeSceneChoiceBacking();
+
+	_scriptStackDepth = 0;
+	_scriptStopFlag = false;
+	_timedCaptionRecordData.clear();
+	_timedCaptionCursorOffset = 0;
+	_nextTimedCaptionThreshold = 99999;
+	_timedCaptionText.clear();
+	_timedCaptionTextSjis.clear();
+	_activeCaptionBackingRect = Common::Rect();
+	_queuedMoviePlaybackIndex = 0;
+	_previousDisplayStateBeforeSystem = 0;
+	_computerChoiceGroupIndex = 0;
+	_computerDetailVariantIndex = 0;
+	_sceneChoiceTerminatorButtonIndex = 0;
+	_sceneChoiceEnabledButtonLimit = 0;
+	_computerControlsActive = false;
+	_computerDetailControlsActive = false;
+	_computerDetailNextVariantEnabled = false;
+	_computerActiveChoiceIds.clear();
+	_computerActiveChoiceRects.clear();
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+	_suppressClickHandling = false;
+	_titleButtonsActive = false;
+	_systemButtonsVisible = false;
+	_titleLoadSlotsActive = false;
+	_activeMovieDispatchOnFinish = false;
+	_activeMovieOnBlankScreen = false;
+	_activeMoviePaused = false;
+	_pendingStillElapsedTimerTicks = 0;
+	_gameOverElapsedTimerTicks = 0;
+	_pendingStillDurationIndex = 0;
+	_pendingClockTickDelta = 0;
+	_sceneIdleMovieTimerTicks = 0;
+	_systemSaveLoadSlotActionMode = 0;
+	_lastTimerTick = g_system->getMillis();
+	_lastLoggedGameClockTicks = -1;
+	_currentDisplayState = 0;
+}
+
+void BCBEngine::writeContinueState() {
+	saveGameState(0, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)), true);
+}
+
+Common::U32String BCBEngine::formatSystemTextForDisplay(const Common::String &originalFormat, int slot) const {
+	const Common::String formatted = Common::String::format(originalFormat.c_str(), slot);
+	return decodeExecutableTextForDisplay(formatted);
+}
+
+Common::U32String BCBEngine::formatSystemClockTextForDisplay(const Common::String &originalFormat, int slot, int savedDay, int savedClockTicks) const {
+	const int hour = savedClockTicks / 60;
+	const int minute = savedClockTicks % 60;
+	const Common::String formatted = Common::String::format(originalFormat.c_str(), slot, savedDay, hour, minute);
+	return decodeExecutableTextForDisplay(formatted);
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index e6c719d9550..35b22557865 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -97,6 +97,7 @@ bool BCBEngine::showActiveSceneChoiceControls() {
 	drawSceneChoiceButtons(sceneChoiceUiSetToShowId);
 	_sceneChoiceActive = true;
 	_sceneIdleMovieTimerTicks = 0;
+	writeContinueState();
 	return true;
 }
 
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index e7461a46f44..39510a47034 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
 	bcb/screens.o \
 	bcb/input.o \
 	bcb/computer.o \
+	bcb/saveload.o \
 	bcb/map.o \
 	bcb/script.o \
 	bcb/executable_data.o \


Commit: 5e7eee3119ce1bf39d02705c182caacb46fa74d8
    https://github.com/scummvm/scummvm/commit/5e7eee3119ce1bf39d02705c182caacb46fa74d8
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add the in-game system menu

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/saveload.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index fb44cfc184d..c4901b515e6 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -84,8 +84,20 @@ private:
 	void stopGameOverMusic();
 	void startSceneIdleMovie();
 	void stopSceneIdleMovie();
+	void enterSystemMenu();
+	bool showSystemScreen();
 	void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
+	void drawSystemSaveLoadSlots(bool present = true);
+	void drawSystemSubmodeButtons(bool present = true);
+	void drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present = true);
+	void syncSystemWaveVolumeFromMixer();
+	bool handleSystemWaveVolumeClick(const Common::Point &mousePosition);
+	bool handleSystemClick(const Common::Point &mousePosition);
+	void activateSystemExitButton();
 	void writeContinueState();
+	bool showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton);
+	void returnFromSystemMenuToPreviousState();
+	bool handleSaveLoadSlotClick(const Common::Point &mousePosition);
 	bool openMapMoveMovie();
 	void startMapMoveMovie();
 	void stopMapMoveMovie();
@@ -274,6 +286,7 @@ private:
 	bool _titleButtonsActive = false;
 	bool _systemButtonsVisible = false;
 	int _systemSaveLoadSlotActionMode = 0;
+	int _systemWaveVolume = 0xffff;
 	bool _titleLoadSlotsActive = false;
 };
 
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 15158da2c9d..4d63eb404dc 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -78,6 +78,12 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 	}
 
 	if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
+		if (kSystemMenuControlHitRect.contains(mousePosition)) {
+			drawSceneChoiceControlStrip(kMovieControlStripPausePressedSourceX);
+			enterSystemMenu();
+			return;
+		}
+
 		if (kMovieQueueControlHitRect.contains(mousePosition)) {
 			playUiSound();
 			drawSceneChoiceControlStrip(kMovieControlStripSkipPressedSourceX);
@@ -110,6 +116,9 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 	if (handleMapControlClick(mousePosition))
 		return;
 
+	if (handleSystemClick(mousePosition))
+		return;
+
 	if (_currentDisplayState != 0 || !_titleButtonsActive)
 		return;
 
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index d3cbbb01599..d67436722cf 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -207,6 +207,154 @@ void BCBEngine::resetTransientRuntimeStateAfterLoad() {
 	_currentDisplayState = 0;
 }
 
+void BCBEngine::enterSystemMenu() {
+	_previousDisplayStateBeforeSystem = _currentDisplayState;
+	_systemSaveLoadSlotActionMode = 0;
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+
+	if (_previousDisplayStateBeforeSystem == 1)
+		stopSceneIdleMovie();
+	else if (_previousDisplayStateBeforeSystem == 2)
+		stopMapMoveMovie();
+
+	stopActiveMovie();
+	playUiSound();
+	startMenuBackgroundMusic();
+	showSystemScreen();
+}
+
+bool BCBEngine::showSystemScreen() {
+	Graphics::Surface *system = loadBitmap("16msys.bmp");
+	if (!system)
+		return false;
+
+	applyPendingClockTickDelta();
+	syncSystemWaveVolumeFromMixer();
+	renderClockAndChipToSurface(*system, 0, stateDword(kStateGameClockTicks));
+	const int fillWidth = (_systemWaveVolume * kSystemWaveVolumeIndicatorRect.width()) / 0xffff;
+	if (fillWidth > 0) {
+		const Common::Rect fillRect(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
+									kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
+		system->fillRect(fillRect, system->format.RGBToColor(255, 255, 255));
+	}
+	g_system->copyRectToScreen(system->getPixels(), system->pitch, 0, 0, MIN<int>(system->w, 640), MIN<int>(system->h, 480));
+	system->free();
+	delete system;
+
+	_currentDisplayState = 3;
+	drawSystemSubmodeButtons(false);
+	g_system->updateScreen();
+	return true;
+}
+
+void BCBEngine::drawSystemSubmodeButtons(bool present) {
+	const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+	if (!system)
+		return;
+
+	for (uint i = 1; i < ARRAYSIZE(kSystemSubmodeButtonTable); ++i) {
+		const ButtonControlRecord &button = kSystemSubmodeButtonTable[i];
+		Common::Point source = kSystemSubmodeNormalSources[i];
+		if ((_systemSaveLoadSlotActionMode == 1 && i == 1) ||
+			(_systemSaveLoadSlotActionMode == 2 && i == 2))
+			source = kSystemSubmodeSelectedSources[i];
+		if (source.x + button.width <= system->w && source.y + button.height <= system->h)
+			g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch,
+									   button.x, button.y, button.width, button.height);
+	}
+
+	if (present)
+		g_system->updateScreen();
+}
+
+void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present) {
+	if (buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kSystemSubmodeButtonTable))
+		return;
+
+	const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+	if (!system)
+		return;
+
+	const ButtonControlRecord &button = kSystemSubmodeButtonTable[buttonIndex];
+	const Common::Point source = selected ? kSystemSubmodeSelectedSources[buttonIndex] : kSystemSubmodeNormalSources[buttonIndex];
+	if (source.x + button.width <= system->w && source.y + button.height <= system->h)
+		g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch, button.x, button.y, button.width, button.height);
+
+	if (present)
+		g_system->updateScreen();
+}
+
+bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
+	if (_currentDisplayState == 0 && _titleLoadSlotsActive)
+		return handleSaveLoadSlotClick(mousePosition);
+
+	if (_currentDisplayState != 3)
+		return false;
+
+	const Common::Rect saveRect(kSystemSubmodeButtonTable[1].x, kSystemSubmodeButtonTable[1].y,
+								kSystemSubmodeButtonTable[1].x + kSystemSubmodeButtonTable[1].width,
+								kSystemSubmodeButtonTable[1].y + kSystemSubmodeButtonTable[1].height);
+	const Common::Rect loadRect(kSystemSubmodeButtonTable[2].x, kSystemSubmodeButtonTable[2].y,
+								kSystemSubmodeButtonTable[2].x + kSystemSubmodeButtonTable[2].width,
+								kSystemSubmodeButtonTable[2].y + kSystemSubmodeButtonTable[2].height);
+	const Common::Rect titleRect(kSystemSubmodeButtonTable[3].x, kSystemSubmodeButtonTable[3].y,
+								 kSystemSubmodeButtonTable[3].x + kSystemSubmodeButtonTable[3].width,
+								 kSystemSubmodeButtonTable[3].y + kSystemSubmodeButtonTable[3].height);
+	const Common::Rect exitRect(kSystemSubmodeButtonTable[4].x, kSystemSubmodeButtonTable[4].y,
+								kSystemSubmodeButtonTable[4].x + kSystemSubmodeButtonTable[4].width,
+								kSystemSubmodeButtonTable[4].y + kSystemSubmodeButtonTable[4].height);
+
+	if (handleSystemWaveVolumeClick(mousePosition))
+		return true;
+
+	if (saveRect.contains(mousePosition)) {
+		_systemSaveLoadSlotActionMode = 1;
+		drawSystemSubmodeButtons(false);
+		drawSystemSaveLoadSlots();
+		return true;
+	}
+
+	if (loadRect.contains(mousePosition)) {
+		_systemSaveLoadSlotActionMode = 2;
+		drawSystemSubmodeButtons(false);
+		drawSystemSaveLoadSlots();
+		return true;
+	}
+
+	if (titleRect.contains(mousePosition)) {
+		drawSystemSubmodeButton(3, true);
+		delayWithEventPolling(80, false);
+		playUiSound();
+		_systemSaveLoadSlotActionMode = 0;
+		showSystemScreen();
+		if (showSystemConfirmationDialog(decodeExecutableTextForDisplay(_returnToTitleConfirmTextSjis), true)) {
+			stopMenuBackgroundMusic();
+			resetGameState();
+			showTitleScreen();
+			startMenuBackgroundMusic();
+		}
+		return true;
+	}
+
+	if (exitRect.contains(mousePosition)) {
+		activateSystemExitButton();
+		return true;
+	}
+
+	if (handleSaveLoadSlotClick(mousePosition))
+		return true;
+
+	return false;
+}
+
+void BCBEngine::activateSystemExitButton() {
+	drawSystemSubmodeButton(4, true);
+	delayWithEventPolling(80, false);
+	playUiSound();
+	returnFromSystemMenuToPreviousState();
+}
+
 void BCBEngine::writeContinueState() {
 	saveGameState(0, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)), true);
 }
@@ -223,4 +371,28 @@ Common::U32String BCBEngine::formatSystemClockTextForDisplay(const Common::Strin
 	return decodeExecutableTextForDisplay(formatted);
 }
 
+void BCBEngine::drawSystemSaveLoadSlots(bool present) {
+	error("BCB: drawSystemSaveLoadSlots is not implemented");
+}
+
+bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
+	error("BCB: handleSaveLoadSlotClick is not implemented");
+}
+
+bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
+	error("BCB: handleSystemWaveVolumeClick is not implemented");
+}
+
+void BCBEngine::returnFromSystemMenuToPreviousState() {
+	error("BCB: returnFromSystemMenuToPreviousState is not implemented");
+}
+
+bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton) {
+	error("BCB: showSystemConfirmationDialog is not implemented");
+}
+
+void BCBEngine::syncSystemWaveVolumeFromMixer() {
+	error("BCB: syncSystemWaveVolumeFromMixer is not implemented");
+}
+
 } // End of namespace BCB


Commit: e70c430195b2179b09527caccb173b61e7c699b9
    https://github.com/scummvm/scummvm/commit/e70c430195b2179b09527caccb173b61e7c699b9
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add persistent volume controls

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/saveload.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index c4901b515e6..5e73b335f5b 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -90,7 +90,9 @@ private:
 	void drawSystemSaveLoadSlots(bool present = true);
 	void drawSystemSubmodeButtons(bool present = true);
 	void drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present = true);
+	void drawSystemWaveVolumeIndicator(bool present = true);
 	void syncSystemWaveVolumeFromMixer();
+	void applySystemWaveVolume();
 	bool handleSystemWaveVolumeClick(const Common::Point &mousePosition);
 	bool handleSystemClick(const Common::Point &mousePosition);
 	void activateSystemExitButton();
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index d67436722cf..2d4a5b45cef 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -285,6 +285,104 @@ void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool pr
 		g_system->updateScreen();
 }
 
+void BCBEngine::applySystemWaveVolume() {
+	const int mixerVolume = CLIP<int>((_systemWaveVolume * Audio::Mixer::kMaxMixerVolume + 0x7fff) / 0xffff, 0, Audio::Mixer::kMaxMixerVolume);
+	const int configVolume = CLIP<int>((_systemWaveVolume * Audio::Mixer::kMaxChannelVolume + 0x7fff) / 0xffff, 0, Audio::Mixer::kMaxChannelVolume);
+	_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, mixerVolume);
+	_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, mixerVolume);
+	ConfMan.setInt("music_volume", configVolume);
+	ConfMan.setInt("sfx_volume", configVolume);
+}
+
+void BCBEngine::syncSystemWaveVolumeFromMixer() {
+	const int configMusicVolume = ConfMan.hasKey("music_volume") ? ConfMan.getInt("music_volume") : Audio::Mixer::kMaxChannelVolume;
+	const int configSfxVolume = ConfMan.hasKey("sfx_volume") ? ConfMan.getInt("sfx_volume") : Audio::Mixer::kMaxChannelVolume;
+	const int configVolume = MAX<int>(configMusicVolume, configSfxVolume);
+	if (configVolume >= Audio::Mixer::kMaxChannelVolume) {
+		_systemWaveVolume = 0xffff;
+		return;
+	}
+
+	_systemWaveVolume = CLIP<int>((configVolume * 0xffff + Audio::Mixer::kMaxChannelVolume / 2) / Audio::Mixer::kMaxChannelVolume, 0, 0xffff);
+}
+
+void BCBEngine::drawSystemWaveVolumeIndicator(bool present) {
+	const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+	if (!system)
+		return;
+
+	if (kSystemWaveVolumeIndicatorRect.right <= system->w && kSystemWaveVolumeIndicatorRect.bottom <= system->h) {
+		g_system->copyRectToScreen(system->getBasePtr(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top),
+								   system->pitch, kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
+								   kSystemWaveVolumeIndicatorRect.width(), kSystemWaveVolumeIndicatorRect.height());
+	}
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		const int fillWidth = (_systemWaveVolume * kSystemWaveVolumeIndicatorRect.width()) / 0xffff;
+		if (fillWidth > 0) {
+			const Common::Rect fillRect(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
+										kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
+			screen->fillRect(fillRect, screen->format.RGBToColor(255, 255, 255));
+		}
+		g_system->unlockScreen();
+	}
+
+	if (present)
+		g_system->updateScreen();
+}
+
+bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
+	if (kSystemWaveVolumeSeekRect.contains(mousePosition)) {
+		const int seekX = CLIP<int>(mousePosition.x - kSystemWaveVolumeIndicatorRect.left, 0, kSystemWaveVolumeIndicatorRect.width());
+		const int volume = CLIP<int>((seekX * 0xffff) / kSystemWaveVolumeIndicatorRect.width(), 0, 0xffff);
+		if (volume != _systemWaveVolume) {
+			_systemWaveVolume = volume;
+			applySystemWaveVolume();
+			drawSystemWaveVolumeIndicator();
+		}
+		ConfMan.flushToDisk();
+		return true;
+	}
+
+	int delta = 0;
+	if (kSystemWaveVolumeUpRect.contains(mousePosition))
+		delta = 0x500;
+	else if (kSystemWaveVolumeDownRect.contains(mousePosition))
+		delta = -0x500;
+	else
+		return false;
+
+	bool done = false;
+	while (!done && !shouldQuit()) {
+		const int volume = CLIP<int>(_systemWaveVolume + delta, 0, 0xffff);
+		if (volume != _systemWaveVolume) {
+			_systemWaveVolume = volume;
+			applySystemWaveVolume();
+			drawSystemWaveVolumeIndicator();
+		}
+
+		Common::Event event;
+		while (g_system->getEventManager()->pollEvent(event)) {
+			if (event.type == Common::EVENT_QUIT) {
+				quitGame();
+				done = true;
+				break;
+			}
+			if (event.type == Common::EVENT_MOUSEMOVE) {
+				_mousePosition = event.mouse;
+			} else if (event.type == Common::EVENT_LBUTTONUP) {
+				_mousePosition = event.mouse;
+				done = true;
+			}
+		}
+		serviceCursorRefresh();
+		g_system->delayMillis(1);
+	}
+	ConfMan.flushToDisk();
+	return true;
+}
+
 bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
 	if (_currentDisplayState == 0 && _titleLoadSlotsActive)
 		return handleSaveLoadSlotClick(mousePosition);
@@ -379,10 +477,6 @@ bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
 	error("BCB: handleSaveLoadSlotClick is not implemented");
 }
 
-bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
-	error("BCB: handleSystemWaveVolumeClick is not implemented");
-}
-
 void BCBEngine::returnFromSystemMenuToPreviousState() {
 	error("BCB: returnFromSystemMenuToPreviousState is not implemented");
 }
@@ -391,8 +485,4 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
 	error("BCB: showSystemConfirmationDialog is not implemented");
 }
 
-void BCBEngine::syncSystemWaveVolumeFromMixer() {
-	error("BCB: syncSystemWaveVolumeFromMixer is not implemented");
-}
-
 } // End of namespace BCB


Commit: e317656c71531cbf45d82f7bfa7a109635e00e6d
    https://github.com/scummvm/scummvm/commit/e317656c71531cbf45d82f7bfa7a109635e00e6d
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Add save slot lists and confirmation dialogs

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp
    engines/harold/bcb/saveload.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 5e73b335f5b..9e3c0a7b2aa 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -50,6 +50,21 @@ enum {
 	kBCBDebugGeneral = 1
 };
 
+enum BCBAction {
+	kBCBActionInteract,
+	kBCBActionEscape,
+	kBCBActionSkip,
+	kBCBActionChoice1,
+	kBCBActionChoice2,
+	kBCBActionChoice3,
+	kBCBActionChoice4,
+	kBCBActionChoice5,
+	kBCBActionChoice6,
+	kBCBActionChoice7,
+	kBCBActionChoice8,
+	kBCBActionChoice9
+};
+
 class Debugger;
 
 class BCBEngine : public Engine {
@@ -96,6 +111,8 @@ private:
 	bool handleSystemWaveVolumeClick(const Common::Point &mousePosition);
 	bool handleSystemClick(const Common::Point &mousePosition);
 	void activateSystemExitButton();
+	bool readSaveSlotHeader(int slot, int &savedDay, int &savedClockTicks);
+	bool isSaveSlotOccupied(int slot);
 	void writeContinueState();
 	bool showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton);
 	void returnFromSystemMenuToPreviousState();
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 4d63eb404dc..cc03cc3296f 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -142,6 +142,23 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
 			if (loadGameState(0).getCode() != Common::kNoError)
 				_titleButtonsActive = true;
 			break;
+		case 53:
+			_systemButtonsVisible = true;
+			_systemSaveLoadSlotActionMode = 2;
+			_titleLoadSlotsActive = true;
+			drawTitleButton(i, false);
+			drawSystemSaveLoadSlots();
+			break;
+		case 54:
+			if (_titleLoadSlotsActive) {
+				_titleLoadSlotsActive = false;
+				_systemSaveLoadSlotActionMode = 0;
+				restoreTitleScreenRegion(Common::Rect(0, 0, 640, 480));
+			}
+			drawTitleButton(i, false);
+			if (showSystemConfirmationDialog(_titleExitConfirmText, true))
+				quitGame();
+			break;
 		default:
 			drawTitleButton(i, false);
 			break;
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index 2d4a5b45cef..75546246ea1 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -383,6 +383,75 @@ bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition)
 	return true;
 }
 
+bool BCBEngine::readSaveSlotHeader(int slot, int &savedDay, int &savedClockTicks) {
+	Common::InSaveFile *saveFile = _saveFileMan->openForLoading(getSaveStateName(slot));
+	if (!saveFile)
+		return false;
+
+	bool validSave = false;
+	const uint32 magic = saveFile->readUint32BE();
+	if (magic == kSavegameMagic) {
+		const uint16 version = saveFile->readUint16LE();
+		const uint16 stateByteSize = saveFile->readUint16LE();
+		validSave = version == kSavegameVersion && stateByteSize == kNativeGameStateByteSize;
+	} else if (saveFile->size() == kNativeGameStateByteSize) {
+		saveFile->seek(0);
+		validSave = true;
+	}
+
+	if (!validSave) {
+		delete saveFile;
+		return false;
+	}
+
+	saveFile->seek(magic == kSavegameMagic ? 8 + kStateRequiredDiscIndex : kStateRequiredDiscIndex);
+	savedDay = saveFile->readUint32LE();
+	savedClockTicks = saveFile->readUint32LE();
+	validSave = !saveFile->err();
+	delete saveFile;
+	return validSave;
+}
+
+bool BCBEngine::isSaveSlotOccupied(int slot) {
+	int savedDay = 0;
+	int savedClockTicks = 0;
+	return readSaveSlotHeader(slot, savedDay, savedClockTicks);
+}
+
+void BCBEngine::drawSystemSaveLoadSlots(bool present) {
+	const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+	if (!system)
+		return;
+
+	for (int slot = 1; slot <= kManualSaveSlotCount; ++slot) {
+		const ButtonControlRecord &button = kSystemButtonTable[slot];
+		const int sourceY = 480 + (slot - 1) * button.height;
+		if (sourceY + button.height <= system->h)
+			g_system->copyRectToScreen(system->getBasePtr(0, sourceY), system->pitch, button.x, button.y, button.width, button.height);
+
+		int savedDay = 0;
+		int savedClockTicks = 0;
+		Common::U32String slotText;
+		if (readSaveSlotHeader(slot, savedDay, savedClockTicks)) {
+			slotText = formatSystemClockTextForDisplay(_saveSlotOccupiedTextFormatSjis, slot, savedDay, savedClockTicks);
+		} else {
+			slotText = formatSystemTextForDisplay(_saveSlotEmptyTextFormatSjis, slot);
+		}
+
+		Graphics::Surface *screen = g_system->lockScreen();
+		if (screen) {
+			const uint32 textColor = screen->format.RGBToColor(0, 0, 0);
+			const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+			const int textY = button.y + (button.height - font->getFontHeight()) / 2 - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
+			font->drawString(screen, slotText, button.x, textY, button.width, textColor);
+			g_system->unlockScreen();
+		}
+	}
+
+	if (present)
+		g_system->updateScreen();
+}
+
 bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
 	if (_currentDisplayState == 0 && _titleLoadSlotsActive)
 		return handleSaveLoadSlotClick(mousePosition);
@@ -453,10 +522,172 @@ void BCBEngine::activateSystemExitButton() {
 	returnFromSystemMenuToPreviousState();
 }
 
+bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
+	if (_systemSaveLoadSlotActionMode != 1 && _systemSaveLoadSlotActionMode != 2)
+		return false;
+
+	for (int slot = 1; slot <= kManualSaveSlotCount; ++slot) {
+		const ButtonControlRecord &button = kSystemButtonTable[slot];
+		const Common::Rect slotRect(button.x, button.y, button.x + button.width, button.y + button.height);
+		if (!slotRect.contains(mousePosition))
+			continue;
+
+		if (_systemSaveLoadSlotActionMode == 1) {
+			int savedDay = 0;
+			int savedClockTicks = 0;
+			Common::U32String message;
+			if (readSaveSlotHeader(slot, savedDay, savedClockTicks)) {
+				message = formatSystemClockTextForDisplay(_saveSlotOverwriteConfirmTextFormatSjis, slot, savedDay, savedClockTicks);
+			} else {
+				message = formatSystemTextForDisplay(_saveSlotEmptyConfirmTextFormatSjis, slot);
+			}
+
+			if (showSystemConfirmationDialog(message, true)) {
+				saveGameState(slot, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)));
+				drawSystemSaveLoadSlots();
+			}
+			return true;
+		}
+
+		if (_systemSaveLoadSlotActionMode == 2) {
+			int savedDay = 0;
+			int savedClockTicks = 0;
+			if (!readSaveSlotHeader(slot, savedDay, savedClockTicks)) {
+				showSystemConfirmationDialog(decodeExecutableTextForDisplay(_noSaveDataMessageSjis), false);
+				return true;
+			}
+			Common::U32String message = formatSystemClockTextForDisplay(_loadSlotConfirmTextFormatSjis, slot, savedDay, savedClockTicks);
+			if (showSystemConfirmationDialog(message, true)) {
+				_titleLoadSlotsActive = false;
+				loadGameState(slot);
+			}
+			return true;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
 void BCBEngine::writeContinueState() {
 	saveGameState(0, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)), true);
 }
 
+bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton) {
+	const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+	if (!chip)
+		return false;
+
+	const Common::Rect dialogRect(144, 192, 496, 288);
+	const Common::Rect okRect(showCancelButton ? 344 : 420, 254, showCancelButton ? 408 : 484, 278);
+	const Common::Rect cancelRect(420, 254, 484, 278);
+	const int dialogSourceY = 0x50;
+	const int buttonSourceY = 0x32;
+	Graphics::Surface dialogBacking;
+	dialogBacking.create(dialogRect.width(), dialogRect.height(), g_system->getScreenFormat());
+	Graphics::Surface *screenBacking = g_system->lockScreen();
+	if (screenBacking) {
+		dialogBacking.copyRectToSurface(*screenBacking, 0, 0, dialogRect);
+		g_system->unlockScreen();
+	}
+
+	auto drawDialogButton = [&](const Common::Rect &rect, int sourceX, bool present) {
+		if (sourceX + rect.width() <= chip->w && chip->h >= buttonSourceY + rect.height())
+			g_system->copyRectToScreen(chip->getBasePtr(sourceX, buttonSourceY), chip->pitch,
+									   rect.left, rect.top, rect.width(), rect.height());
+		if (present)
+			g_system->updateScreen();
+	};
+
+	if (dialogRect.width() <= chip->w && chip->h >= dialogSourceY + dialogRect.height())
+		g_system->copyRectToScreen(chip->getBasePtr(0, dialogSourceY), chip->pitch, dialogRect.left, dialogRect.top, dialogRect.width(), dialogRect.height());
+
+	Graphics::Surface *screen = g_system->lockScreen();
+	if (screen) {
+		const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+		const uint32 shadowColor = screen->format.RGBToColor(0, 0, 0);
+		const uint32 textColor = screen->format.RGBToColor(255, 255, 255);
+		const int textYOffset = _gameDescription->desc.language == Common::KO_KOR ? -1 : 0;
+		font->drawString(screen, message, dialogRect.left + 10, dialogRect.top + 20 + textYOffset, 340, shadowColor);
+		font->drawString(screen, message, dialogRect.left + 11, dialogRect.top + 21 + textYOffset, 339, textColor);
+		g_system->unlockScreen();
+	}
+
+	drawDialogButton(okRect, 0x17, false);
+	if (showCancelButton)
+		drawDialogButton(cancelRect, 0x97, false);
+	g_system->updateScreen();
+
+	bool result = false;
+	bool done = false;
+	auto handleDialogClick = [&]() {
+		if (okRect.contains(_mousePosition)) {
+			drawDialogButton(okRect, 0x57, true);
+			delayWithEventPolling(80, false);
+			result = true;
+			done = true;
+		} else if (showCancelButton && cancelRect.contains(_mousePosition)) {
+			drawDialogButton(cancelRect, 0xd7, true);
+			delayWithEventPolling(80, false);
+			done = true;
+		}
+	};
+	while (!done && !shouldQuit()) {
+		Common::Event event;
+		while (g_system->getEventManager()->pollEvent(event)) {
+			if (event.type == Common::EVENT_QUIT) {
+				quitGame();
+				done = true;
+				break;
+			}
+			if (event.type == Common::EVENT_MOUSEMOVE) {
+				_mousePosition = event.mouse;
+			} else if (event.type == Common::EVENT_LBUTTONDOWN) {
+				_mousePosition = event.mouse;
+				handleDialogClick();
+			} else if (event.type == Common::EVENT_KEYDOWN) {
+				if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+					result = true;
+					done = true;
+				} else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
+					done = true;
+				}
+			} else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
+				if (event.customType == kBCBActionEscape) {
+					done = true;
+				} else if (event.customType == kBCBActionInteract) {
+					handleDialogClick();
+				}
+			}
+		}
+		serviceCursorRefresh();
+		g_system->delayMillis(10);
+	}
+
+	g_system->copyRectToScreen(dialogBacking.getPixels(), dialogBacking.pitch, dialogRect.left, dialogRect.top,
+							   dialogBacking.w, dialogBacking.h);
+	dialogBacking.free();
+	g_system->updateScreen();
+	return result;
+}
+
+void BCBEngine::returnFromSystemMenuToPreviousState() {
+	stopMenuBackgroundMusic();
+	_systemSaveLoadSlotActionMode = 0;
+
+	if (_previousDisplayStateBeforeSystem == 2) {
+		processGameStateDispatcher(false);
+		return;
+	} else {
+		showMainSceneScreen();
+		if (stateDword(kStateSceneChoiceUiSetToShowId) != 0)
+			showActiveSceneChoiceControls();
+	}
+
+	processGameStateDispatcher(false);
+}
+
 Common::U32String BCBEngine::formatSystemTextForDisplay(const Common::String &originalFormat, int slot) const {
 	const Common::String formatted = Common::String::format(originalFormat.c_str(), slot);
 	return decodeExecutableTextForDisplay(formatted);
@@ -469,20 +700,4 @@ Common::U32String BCBEngine::formatSystemClockTextForDisplay(const Common::Strin
 	return decodeExecutableTextForDisplay(formatted);
 }
 
-void BCBEngine::drawSystemSaveLoadSlots(bool present) {
-	error("BCB: drawSystemSaveLoadSlots is not implemented");
-}
-
-bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
-	error("BCB: handleSaveLoadSlotClick is not implemented");
-}
-
-void BCBEngine::returnFromSystemMenuToPreviousState() {
-	error("BCB: returnFromSystemMenuToPreviousState is not implemented");
-}
-
-bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton) {
-	error("BCB: showSystemConfirmationDialog is not implemented");
-}
-
 } // End of namespace BCB


Commit: f38062681fd05f58f4fc6ab1df2abe4f5af85216
    https://github.com/scummvm/scummvm/commit/f38062681fd05f58f4fc6ab1df2abe4f5af85216
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Connect game input and screen timers

Changed paths:
    engines/harold/bcb/bcb.h
    engines/harold/bcb/input.cpp


diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 9e3c0a7b2aa..4244c4ba159 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -211,6 +211,9 @@ private:
 	void blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame = true);
 	void drawTitleButton(uint buttonIndex, bool pressed);
 	void handleLeftButtonDown(const Common::Point &mousePosition);
+	void handleKeymapperAction(Common::CustomEventType action);
+	bool handleEscapeAction();
+	void handleSkipKey();
 	void handleSceneChoiceKeyboardSelection(int choiceNumber);
 	bool pollEvents();
 	bool pollEvents(bool handleClicks);
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index cc03cc3296f..187a45a2d97 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -188,6 +188,29 @@ bool BCBEngine::pollEvents(bool handleClicks) {
 			if (handleClicks && !_suppressClickHandling)
 				handleLeftButtonDown(_mousePosition);
 			break;
+		case Common::EVENT_KEYDOWN:
+			if (handleClicks && !_suppressClickHandling) {
+				if (event.kbd.keycode == Common::KEYCODE_ESCAPE && handleEscapeAction()) {
+					break;
+				} else if (handleComputerKey(event.kbd.keycode)) {
+					break;
+				} else if (event.kbd.keycode == Common::KEYCODE_SPACE) {
+					handleSkipKey();
+				} else if (_sceneChoiceActive &&
+						   event.kbd.keycode >= Common::KEYCODE_1 &&
+						   event.kbd.keycode <= Common::KEYCODE_9) {
+					handleSceneChoiceKeyboardSelection(event.kbd.keycode - Common::KEYCODE_1 + 1);
+				} else if (_sceneChoiceActive &&
+						   event.kbd.keycode >= Common::KEYCODE_KP1 &&
+						   event.kbd.keycode <= Common::KEYCODE_KP9) {
+					handleSceneChoiceKeyboardSelection(event.kbd.keycode - Common::KEYCODE_KP1 + 1);
+				}
+			}
+			break;
+		case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
+			if (handleClicks && !_suppressClickHandling)
+				handleKeymapperAction(event.customType);
+			break;
 		default:
 			break;
 		}
@@ -196,6 +219,47 @@ bool BCBEngine::pollEvents(bool handleClicks) {
 	return !shouldQuit();
 }
 
+void BCBEngine::handleKeymapperAction(Common::CustomEventType action) {
+	if (action == kBCBActionInteract) {
+		handleLeftButtonDown(_mousePosition);
+	} else if (action == kBCBActionEscape) {
+		handleEscapeAction();
+	} else if (action == kBCBActionSkip) {
+		handleSkipKey();
+	} else if (action >= kBCBActionChoice1 && action <= kBCBActionChoice9) {
+		handleSceneChoiceKeyboardSelection(action - kBCBActionChoice1 + 1);
+	}
+}
+
+bool BCBEngine::handleEscapeAction() {
+	if (_currentDisplayState == 3) {
+		activateSystemExitButton();
+		return true;
+	}
+
+	return handleComputerKey(Common::KEYCODE_ESCAPE);
+}
+
+void BCBEngine::handleSkipKey() {
+	if (_activeMovieVideo) {
+		if (shouldDrawMovieControlStrip())
+			drawMovieControlStrip(kMovieControlStripSkipPressedSourceX);
+		delayWithEventPolling(160, false);
+		stopActiveMovieAndDispatch();
+		return;
+	}
+
+	if (_currentDisplayState == 10) {
+		transitionFromLogoToTitle();
+		return;
+	}
+
+	if (_currentDisplayState == 6) {
+		playUiSound();
+		completePendingStillImageScreen();
+	}
+}
+
 void BCBEngine::serviceCursorRefresh(bool screenUpdated) {
 	const uint32 now = g_system->getMillis();
 


Commit: d88edb28e2c7fd872aa4f1e576aff363ca35cc78
    https://github.com/scummvm/scummvm/commit/d88edb28e2c7fd872aa4f1e576aff363ca35cc78
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Register engine keymaps and debugger commands

Changed paths:
  A engines/harold/bcb/debugger.cpp
  A engines/harold/bcb/debugger.h
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/metaengine.cpp
    engines/harold/module.mk


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index b1086851277..fb6adcee52a 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -29,6 +29,7 @@
 #include "graphics/fonts/ttf.h"
 #include "graphics/sjis.h"
 #include "graphics/surface.h"
+#include "harold/bcb/debugger.h"
 #include "harold/bcb/script.h"
 #include "harold/bcb/static_data.h"
 #include "video/avi_decoder.h"
@@ -108,6 +109,8 @@ BCBEngine::~BCBEngine() {
 Common::Error BCBEngine::run() {
 	const Graphics::PixelFormat screenFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
 
+	setDebugger(new Debugger(this));
+
 	if (!initializeSearchPaths())
 		return Common::kPathDoesNotExist;
 
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 4244c4ba159..962cb43960b 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -215,6 +215,9 @@ private:
 	bool handleEscapeAction();
 	void handleSkipKey();
 	void handleSceneChoiceKeyboardSelection(int choiceNumber);
+	bool parseCheatNumber(const Common::String &text, uint32 &value) const;
+	void addCheatTime(int minutes);
+	void jumpToCheatSequence(uint sequenceId);
 	bool pollEvents();
 	bool pollEvents(bool handleClicks);
 	void serviceCursorRefresh(bool screenUpdated = false);
diff --git a/engines/harold/bcb/debugger.cpp b/engines/harold/bcb/debugger.cpp
new file mode 100644
index 00000000000..ffcf8fe89eb
--- /dev/null
+++ b/engines/harold/bcb/debugger.cpp
@@ -0,0 +1,303 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/str.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/debugger.h"
+#include "harold/bcb/script.h"
+
+namespace BCB {
+
+Debugger::Debugger(BCBEngine *vm) : GUI::Debugger(), _vm(vm) {
+	registerCmd("time", WRAP_METHOD(Debugger, cmdTime));
+	registerCmd("add_time", WRAP_METHOD(Debugger, cmdAddTime));
+	registerCmd("seq", WRAP_METHOD(Debugger, cmdSequence));
+	registerCmd("state", WRAP_METHOD(Debugger, cmdState));
+	registerCmd("flag", WRAP_METHOD(Debugger, cmdFlag));
+	registerCmd("choice", WRAP_METHOD(Debugger, cmdChoice));
+	registerCmd("gameover", WRAP_METHOD(Debugger, cmdGameOver));
+	registerCmd("clock_chip", WRAP_METHOD(Debugger, cmdClockChip));
+	registerCmd("night", WRAP_METHOD(Debugger, cmdNight));
+}
+
+bool Debugger::parseNumber(const char *text, uint32 &value) const {
+	return _vm->parseCheatNumber(Common::String(text), value);
+}
+
+Common::String Debugger::formatGameClock(uint32 gameClockTicks) const {
+	const uint clockTicks = gameClockTicks % 1440;
+	const uint dayOfMonth = 10 + CLIP<uint32>(_vm->stateDword(kStateRequiredDiscIndex), 0, 4);
+	const uint hour = clockTicks / 60;
+	const uint minute = clockTicks % 60;
+
+	return Common::String::format("11/%02u %02u:%02u", dayOfMonth, hour, minute);
+}
+
+bool Debugger::cmdTime(int argc, const char **argv) {
+	if (argc > 2) {
+		debugPrintf("Format: time [ticks]\n");
+		debugPrintf("Current: %s, ticks=%u / 0x%04x\n",
+					formatGameClock(_vm->stateDword(kStateGameClockTicks)).c_str(),
+					_vm->stateDword(kStateGameClockTicks),
+					_vm->stateDword(kStateGameClockTicks));
+		return true;
+	}
+
+	if (argc == 2) {
+		uint32 ticks = 0;
+		if (!parseNumber(argv[1], ticks)) {
+			debugPrintf("Invalid tick value: %s\n", argv[1]);
+			return true;
+		}
+		_vm->stateDword(kStateGameClockTicks) = ticks;
+		_vm->notifyGameClockTicksChanged();
+		if (_vm->_currentDisplayState == 1 || _vm->_currentDisplayState == 2 ||
+			_vm->_currentDisplayState == 7 || _vm->_currentDisplayState == 8)
+			_vm->drawClockAndChipIndicator();
+	}
+
+	debugPrintf("Current: %s, ticks=%u / 0x%04x\n",
+				formatGameClock(_vm->stateDword(kStateGameClockTicks)).c_str(),
+				_vm->stateDword(kStateGameClockTicks),
+				_vm->stateDword(kStateGameClockTicks));
+	return true;
+}
+
+bool Debugger::cmdAddTime(int argc, const char **argv) {
+	if (argc != 2) {
+		debugPrintf("Format: add_time <minutes>\n");
+		return true;
+	}
+
+	uint32 minutes = 0;
+	if (!parseNumber(argv[1], minutes)) {
+		debugPrintf("Invalid minute value: %s\n", argv[1]);
+		return true;
+	}
+
+	_vm->addCheatTime((int)minutes);
+	debugPrintf("Current: %s, ticks=%u / 0x%04x\n",
+				formatGameClock(_vm->stateDword(kStateGameClockTicks)).c_str(),
+				_vm->stateDword(kStateGameClockTicks),
+				_vm->stateDword(kStateGameClockTicks));
+	return true;
+}
+
+bool Debugger::cmdSequence(int argc, const char **argv) {
+	if (argc != 2) {
+		debugPrintf("Format: seq <sequence-id>\n");
+		return true;
+	}
+
+	uint32 sequenceId = 0;
+	if (!parseNumber(argv[1], sequenceId)) {
+		debugPrintf("Invalid sequence id: %s\n", argv[1]);
+		return true;
+	}
+
+	_vm->jumpToCheatSequence(sequenceId);
+	return false;
+}
+
+bool Debugger::cmdState(int argc, const char **argv) {
+	if (argc != 2 && argc != 3) {
+		debugPrintf("Format: state <byte-offset> [value]\n");
+		return true;
+	}
+
+	uint32 byteOffset = 0;
+	if (!parseNumber(argv[1], byteOffset)) {
+		debugPrintf("Invalid state offset: %s\n", argv[1]);
+		return true;
+	}
+
+	if (argc == 3) {
+		uint32 value = 0;
+		if (!parseNumber(argv[2], value)) {
+			debugPrintf("Invalid value: %s\n", argv[2]);
+			return true;
+		}
+		_vm->stateDword(byteOffset) = value;
+	}
+
+	debugPrintf("state[0x%04x] = %u / 0x%08x\n", byteOffset, _vm->stateDword(byteOffset), _vm->stateDword(byteOffset));
+	return true;
+}
+
+bool Debugger::cmdFlag(int argc, const char **argv) {
+	if (argc != 2 && argc != 3) {
+		debugPrintf("Format: flag <secondary-word-index> [value]\n");
+		return true;
+	}
+
+	uint32 flagIndex = 0;
+	if (!parseNumber(argv[1], flagIndex)) {
+		debugPrintf("Invalid flag index: %s\n", argv[1]);
+		return true;
+	}
+
+	const uint32 byteOffset = 0x98 + flagIndex * 4;
+	if (argc == 3) {
+		uint32 value = 0;
+		if (!parseNumber(argv[2], value)) {
+			debugPrintf("Invalid value: %s\n", argv[2]);
+			return true;
+		}
+		_vm->stateDword(byteOffset) = value;
+	}
+
+	debugPrintf("secondary[%u] state[0x%04x] = %u / 0x%08x\n",
+				flagIndex, byteOffset, _vm->stateDword(byteOffset), _vm->stateDword(byteOffset));
+	return true;
+}
+
+bool Debugger::cmdChoice(int argc, const char **argv) {
+	if (argc != 1 && argc != 2) {
+		debugPrintf("Format: choice [value]\n");
+		return true;
+	}
+
+	if (argc == 2) {
+		uint32 value = 0;
+		if (!parseNumber(argv[1], value)) {
+			debugPrintf("Invalid choice value: %s\n", argv[1]);
+			return true;
+		}
+		_vm->stateDword(kStateSelectedSceneChoiceValue) = value;
+	}
+
+	debugPrintf("selectedSceneChoiceValue = %u / 0x%02x\n",
+				_vm->stateDword(kStateSelectedSceneChoiceValue),
+				_vm->stateDword(kStateSelectedSceneChoiceValue));
+	return true;
+}
+
+bool Debugger::cmdGameOver(int argc, const char **argv) {
+	if (argc != 2) {
+		debugPrintf("Format: gameover <1..9>\n");
+		return true;
+	}
+
+	uint32 messageIndex = 0;
+	if (!parseNumber(argv[1], messageIndex) || messageIndex < 1 || messageIndex > 9) {
+		debugPrintf("Invalid game-over message index: %s\n", argv[1]);
+		return true;
+	}
+
+	_vm->stateDword(kStateSelectedSceneChoiceValue) = messageIndex;
+	if (!_vm->dispatchGameOverScreen())
+		debugPrintf("Could not display game-over screen %u\n", messageIndex);
+	return false;
+}
+
+bool Debugger::cmdClockChip(int argc, const char **argv) {
+	if (argc != 1 && argc != 2) {
+		debugPrintf("Format: clock_chip [0..4]\n");
+		return true;
+	}
+
+	if (argc == 2) {
+		uint32 chipIndex = 0;
+		if (!parseNumber(argv[1], chipIndex) || chipIndex > 4) {
+			debugPrintf("Invalid clock/chip index: %s\n", argv[1]);
+			return true;
+		}
+		_vm->stateDword(kStateRequiredDiscIndex) = chipIndex;
+		if (_vm->_currentDisplayState == 1 || _vm->_currentDisplayState == 2 ||
+			_vm->_currentDisplayState == 7 || _vm->_currentDisplayState == 8)
+			_vm->drawClockAndChipIndicator();
+	}
+
+	debugPrintf("clock/chip state index = %u (state[0x%04x])\n",
+				_vm->stateDword(kStateRequiredDiscIndex), kStateRequiredDiscIndex);
+	return true;
+}
+
+bool Debugger::cmdNight(int argc, const char **argv) {
+	if (argc != 1 && argc != 2) {
+		debugPrintf("Format: night [0|1]\n");
+		return true;
+	}
+
+	if (argc == 2) {
+		uint32 value = 0;
+		if (!parseNumber(argv[1], value)) {
+			debugPrintf("Invalid night flag: %s\n", argv[1]);
+			return true;
+		}
+		_vm->stateDword(kStateMainSceneNightFlag) = value != 0 ? 1 : 0;
+	}
+
+	debugPrintf("mainSceneNightFlag = %u\n", _vm->stateDword(kStateMainSceneNightFlag));
+	return true;
+}
+
+bool BCBEngine::parseCheatNumber(const Common::String &text, uint32 &value) const {
+	if (text.empty())
+		return false;
+
+	const char *cursor = text.c_str();
+	int base = 10;
+	if (text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
+		cursor += 2;
+		base = 16;
+	}
+
+	char *end = nullptr;
+	const unsigned long parsedValue = strtoul(cursor, &end, base);
+	if (!end || *end != 0)
+		return false;
+
+	value = (uint32)parsedValue;
+	return true;
+}
+
+void BCBEngine::addCheatTime(int minutes) {
+	stateDword(kStateGameClockTicks) += minutes;
+	notifyGameClockTicksChanged();
+
+	if (_currentDisplayState == 1 || _currentDisplayState == 2 || _currentDisplayState == 7 || _currentDisplayState == 8)
+		drawClockAndChipIndicator();
+}
+
+void BCBEngine::jumpToCheatSequence(uint sequenceId) {
+	if (sequenceId == 0 || sequenceId * 2 > _scriptData.size()) {
+		warning("BCBEngine::jumpToCheatSequence(): Invalid sequence id %u", sequenceId);
+		return;
+	}
+
+	stopActiveMovie();
+	stopSceneIdleMovie();
+	stopMapMoveMovie();
+	restoreActiveCaptionBackingRect();
+	freeSceneChoiceBacking();
+	_sceneChoiceActive = false;
+	_mapControlsActive = false;
+	_scriptStopFlag = false;
+	stateDword(kStateMovieToPlayId) = 0;
+	stateDword(kStateStillImageToShowId) = 0;
+	stateDword(kStateDispatcherControlCode) = 0;
+	stateDword(kStateCurrentScriptSequenceId) = sequenceId;
+	processGameStateDispatcher(true);
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/debugger.h b/engines/harold/bcb/debugger.h
new file mode 100644
index 00000000000..a8181cff696
--- /dev/null
+++ b/engines/harold/bcb/debugger.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_DEBUGGER_H
+#define BCB_DEBUGGER_H
+
+#include "gui/debugger.h"
+
+namespace BCB {
+
+class BCBEngine;
+
+class Debugger : public GUI::Debugger {
+public:
+	Debugger(BCBEngine *vm);
+
+private:
+	BCBEngine *_vm;
+
+	bool parseNumber(const char *text, uint32 &value) const;
+	Common::String formatGameClock(uint32 gameClockTicks) const;
+
+	bool cmdTime(int argc, const char **argv);
+	bool cmdAddTime(int argc, const char **argv);
+	bool cmdSequence(int argc, const char **argv);
+	bool cmdState(int argc, const char **argv);
+	bool cmdFlag(int argc, const char **argv);
+	bool cmdChoice(int argc, const char **argv);
+	bool cmdGameOver(int argc, const char **argv);
+	bool cmdClockChip(int argc, const char **argv);
+	bool cmdNight(int argc, const char **argv);
+};
+
+} // End of namespace BCB
+
+#endif // BCB_DEBUGGER_H
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
index b2a5e28861c..1b5ffc18146 100644
--- a/engines/harold/metaengine.cpp
+++ b/engines/harold/metaengine.cpp
@@ -38,9 +38,63 @@ public:
 	}
 
 	int getMaximumSaveSlot() const override { return 8; }
+	Common::KeymapArray initKeymaps(const char *target) const override;
 	Common::Error createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const override;
 };
 
+Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
+	using namespace Common;
+	using namespace BCB;
+
+	Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "bcb-default", _("Blue Chicago Blues"));
+	Action *act;
+
+	act = new Action("INTERACT", _("Interact"));
+	act->setCustomEngineActionEvent(kBCBActionInteract);
+	act->addDefaultInputMapping("MOUSE_LEFT");
+	act->addDefaultInputMapping("JOY_A");
+	engineKeyMap->addAction(act);
+
+	act = new Action("ESCAPE", _("Escape"));
+	act->setCustomEngineActionEvent(kBCBActionEscape);
+	act->addDefaultInputMapping("ESCAPE");
+	act->addDefaultInputMapping("MOUSE_RIGHT");
+	act->addDefaultInputMapping("JOY_B");
+	engineKeyMap->addAction(act);
+
+	act = new Action("SKIP", _("Skip / Advance"));
+	act->setCustomEngineActionEvent(kBCBActionSkip);
+	act->addDefaultInputMapping("SPACE");
+	act->addDefaultInputMapping("JOY_X");
+	engineKeyMap->addAction(act);
+
+	act = new Action("MOVE_CHOICE", _("Move"));
+	act->setCustomEngineActionEvent(kBCBActionChoice9);
+	act->addDefaultInputMapping("9");
+	act->addDefaultInputMapping("KP9");
+	engineKeyMap->addAction(act);
+
+	static const char *const choiceActionIds[] = {
+		"CHOICE1", "CHOICE2", "CHOICE3", "CHOICE4",
+		"CHOICE5", "CHOICE6", "CHOICE7", "CHOICE8"
+	};
+	static const char *const choiceActionLabels[] = {
+		_s("Choice 1"), _s("Choice 2"), _s("Choice 3"),
+		_s("Choice 4"), _s("Choice 5"), _s("Choice 6"),
+		_s("Choice 7"), _s("Choice 8")
+	};
+
+	for (int choiceIndex = 1; choiceIndex <= 8; ++choiceIndex) {
+		act = new Action(choiceActionIds[choiceIndex - 1], _(choiceActionLabels[choiceIndex - 1]));
+		act->setCustomEngineActionEvent((Common::CustomEventType)(kBCBActionChoice1 + choiceIndex - 1));
+		act->addDefaultInputMapping(Common::String::format("%d", choiceIndex));
+		act->addDefaultInputMapping(Common::String::format("KP%d", choiceIndex));
+		engineKeyMap->addAction(act);
+	}
+
+	return Keymap::arrayOf(engineKeyMap);
+}
+
 Common::Error HaroldMetaEngine::createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const {
 	switch (gd->gameType) {
 	case BCB::kHaroldGameBCB:
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 39510a47034..276b4a8c8a1 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
 	bcb/screens.o \
 	bcb/input.o \
 	bcb/computer.o \
+	bcb/debugger.o \
 	bcb/saveload.o \
 	bcb/map.o \
 	bcb/script.o \


Commit: 3a257cfdd18e9f586c8bb86c0c6c6a8452e5d86f
    https://github.com/scummvm/scummvm/commit/3a257cfdd18e9f586c8bb86c0c6c6a8452e5d86f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Reduce rendering, font and menu audio overhead

Changed paths:
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/bcb.h
    engines/harold/bcb/captions.cpp
    engines/harold/bcb/computer.cpp
    engines/harold/bcb/input.cpp
    engines/harold/bcb/map.cpp
    engines/harold/bcb/saveload.cpp
    engines/harold/bcb/scene.cpp
    engines/harold/bcb/screens.cpp
    engines/harold/bcb/video.cpp


diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index fb6adcee52a..00b92856c69 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "harold/bcb/bcb.h"
+#include "common/compression/unzip.h"
 #include "common/config-manager.h"
 #include "common/system.h"
 #include "common/textconsole.h"
@@ -27,6 +28,7 @@
 #include "graphics/cursorman.h"
 #include "graphics/font.h"
 #include "graphics/fonts/ttf.h"
+#include "graphics/screen.h"
 #include "graphics/sjis.h"
 #include "graphics/surface.h"
 #include "harold/bcb/debugger.h"
@@ -36,37 +38,71 @@
 
 namespace BCB {
 
+#ifdef USE_FREETYPE2
 static const int kCaptionTextNativeCharacterHeight = 18;
 static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
 
-static Graphics::Font *loadLocalizedCjkFont(Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
+static Graphics::Font *loadFont(Common::Archive &archive, const char *name, int size, Graphics::TTFSizeMode sizeMode) {
+	Common::SeekableReadStream *stream = archive.createReadStreamForMember(Common::Path(name));
+	if (!stream)
+		return nullptr;
+	Graphics::Font *font = Graphics::loadTTFFont(stream, DisposeAfterUse::YES, size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+	if (!font)
+		delete stream;
+	return font;
+}
+
+static Graphics::Font *loadLocalizedCjkFont(Common::Archive &archive, Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
 	if (language == Common::KO_KOR) {
-		Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+		Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", size, sizeMode);
 		if (font)
 			return font;
-		font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+		font = loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
 		if (font)
 			return font;
-		return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+		return loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
 	}
 
-	Graphics::Font *font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+	Graphics::Font *font = loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
 	if (font)
 		return font;
-	return Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+	return loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
 }
 
-static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Language language) {
+static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Archive &archive, Common::Language language) {
 	if (language == Common::KO_KOR) {
-		Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+		Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
 		if (font)
 			return font;
 	}
 
-	Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+	Graphics::Font *font = loadFont(archive, "NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
 	if (font)
 		return font;
-	return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+	return loadFont(archive, "VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+}
+#endif
+
+void BCBEngine::loadFonts() {
+#ifdef USE_FREETYPE2
+	Common::SearchSet fonts;
+	const char *archiveNames[] = {"fonts.dat", "fonts-cjk.dat"};
+	for (const char *name : archiveNames) {
+		Common::SeekableReadStream *stream = nullptr;
+		if (ConfMan.hasKey("extrapath")) {
+			Common::FSDirectory directory(ConfMan.getPath("extrapath"));
+			stream = directory.createReadStreamForMember(name);
+		}
+		if (!stream)
+			stream = SearchMan.createReadStreamForMember(name);
+		Common::Archive *archive = Common::makeZipArchive(stream);
+		if (archive)
+			fonts.add(name, archive);
+	}
+	_captionTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+	_gameOverTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
+	_ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(fonts, _gameDescription->desc.language);
+#endif
 }
 
 BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
@@ -74,6 +110,7 @@ BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
 }
 
 BCBEngine::~BCBEngine() {
+	delete _screen;
 	if (_uiBitmap) {
 		_uiBitmap->free();
 		delete _uiBitmap;
@@ -115,15 +152,14 @@ Common::Error BCBEngine::run() {
 		return Common::kPathDoesNotExist;
 
 	initGraphics(640, 480, &screenFormat);
+	_screen = new Graphics::Screen(640, 480, g_system->getScreenFormat());
 	CursorMan.lock(false);
 	CursorMan.setDefaultArrowCursor();
 	CursorMan.showMouse(true);
-	g_system->updateScreen();
+	_screen->update();
 	_lastCursorRefreshMillis = g_system->getMillis();
 	_sjisFont = Graphics::FontSJIS::createFont();
-	_captionTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
-	_gameOverTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
-	_ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(_gameDescription->desc.language);
+	loadFonts();
 	if (!_sjisFont && !_captionTextFont && !_gameOverTextFont && !_ownerDrawUiTextFont)
 		warning("BCBEngine::run(): No CJK font available; captions and choice text will not render");
 
@@ -152,13 +188,13 @@ void BCBEngine::clearDisplayAndResetState() {
 	_sceneChoiceActive = false;
 	_mapControlsActive = false;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		const uint32 black = screen->format.RGBToColor(0, 0, 0);
 		screen->fillRect(Common::Rect(0, 0, 640, 480), black);
-		g_system->unlockScreen();
+		_screen->makeAllDirty();
 	}
-	g_system->updateScreen();
+	_screen->update();
 }
 
 void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
@@ -261,17 +297,7 @@ void BCBEngine::mainLoop() {
 			}
 		}
 
-		if (_menuBackgroundVideo) {
-			if (_menuBackgroundVideo->endOfVideo()) {
-				_menuBackgroundVideo->stop();
-				_menuBackgroundVideo->rewind();
-				_menuBackgroundVideo->decodeNextFrame();
-				_menuBackgroundVideo->start();
-			}
-
-			if (_menuBackgroundVideo->needsUpdate())
-				_menuBackgroundVideo->decodeNextFrame();
-		}
+		updateMenuBackgroundMusic();
 
 		if (_sceneIdleVideo) {
 			if (_sceneIdleVideo->endOfVideo()) {
@@ -284,8 +310,8 @@ void BCBEngine::mainLoop() {
 			if (_sceneIdleVideo->needsUpdate()) {
 				const Graphics::Surface *frame = _sceneIdleVideo->decodeNextFrame();
 				if (frame) {
-					g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0x10, 0x30, frame->w, frame->h);
-					g_system->updateScreen();
+					_screen->copyRectToSurface(frame->getPixels(), frame->pitch, 0x10, 0x30, frame->w, frame->h);
+					_screen->update();
 					screenUpdated = true;
 				}
 			}
@@ -316,14 +342,14 @@ void BCBEngine::mainLoop() {
 				const Graphics::Surface *frame = _activeMovieVideo->decodeNextFrame();
 				if (frame) {
 					const Common::Point moviePosition = getActiveMoviePosition();
-					g_system->copyRectToScreen(frame->getPixels(), frame->pitch, moviePosition.x, moviePosition.y, frame->w, frame->h);
+					_screen->copyRectToSurface(frame->getPixels(), frame->pitch, moviePosition.x, moviePosition.y, frame->w, frame->h);
 					movieUpdated = true;
 				}
 			}
 			if (drawTimedVideoTextOverlay())
 				movieUpdated = true;
 			if (movieUpdated) {
-				g_system->updateScreen();
+				_screen->update();
 				screenUpdated = true;
 			}
 
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 962cb43960b..d3a95d06502 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -31,14 +31,15 @@
 #include "common/str-enc.h"
 #include "common/ustr.h"
 #include "engines/engine.h"
-#include "harold/detection.h"
 #include "harold/bcb/ui.h"
+#include "harold/detection.h"
 
 namespace Graphics {
 class Font;
 class FontSJIS;
+class Screen;
 struct Surface;
-}
+} // namespace Graphics
 
 namespace Video {
 class AVIDecoder;
@@ -83,6 +84,7 @@ public:
 	bool canSaveAutosaveCurrently() override;
 
 private:
+	void loadFonts();
 	bool initializeSearchPaths();
 	bool showInitialLogoScreen();
 	bool showTitleScreen();
@@ -94,6 +96,7 @@ private:
 	void stopActiveMovie();
 	void playUiSound();
 	void startMenuBackgroundMusic();
+	void updateMenuBackgroundMusic();
 	void stopMenuBackgroundMusic();
 	void startGameOverMusic();
 	void stopGameOverMusic();
@@ -229,6 +232,7 @@ private:
 	void mainLoop();
 
 	const HaroldGameDescription *_gameDescription;
+	Graphics::Screen *_screen = nullptr;
 	Common::SearchSet _programFiles;
 	Common::SearchSet _discFiles[5];
 	Common::Array<byte> _scriptData;
@@ -236,7 +240,7 @@ private:
 	Common::Array<byte> _locationTimingData;
 	Common::Array<byte> _allBinData;
 	Common::Array<byte> _timedCaptionRecordData;
-	Common::Array<Common::Array<Common::U32String> > _sceneChoiceText;
+	Common::Array<Common::Array<Common::U32String>> _sceneChoiceText;
 	Common::CodePage _executableTextEncoding = Common::kWindows932;
 	Common::String _sceneChoiceMoveTextSjis;
 	Common::U32String _sceneChoiceMoveText;
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
index cd8ae11c738..1b868c28681 100644
--- a/engines/harold/bcb/captions.cpp
+++ b/engines/harold/bcb/captions.cpp
@@ -22,6 +22,7 @@
 #include "common/algorithm.h"
 #include "common/system.h"
 #include "graphics/font.h"
+#include "graphics/screen.h"
 #include "graphics/sjis.h"
 #include "graphics/surface.h"
 #include "harold/bcb/bcb.h"
@@ -180,10 +181,9 @@ void BCBEngine::loadNextTimedCaptionText() {
 
 void BCBEngine::restoreCaptionBackingRect(const Common::Rect &rect) {
 	if (_activeMovieOnBlankScreen) {
-		Graphics::Surface *screen = g_system->lockScreen();
+		Graphics::Surface *screen = _screen->surfacePtr();
 		if (screen) {
-			screen->fillRect(rect, screen->format.RGBToColor(0, 0, 0));
-			g_system->unlockScreen();
+			_screen->fillRect(rect, screen->format.RGBToColor(0, 0, 0));
 		}
 		return;
 	}
@@ -202,14 +202,14 @@ void BCBEngine::restoreCaptionBackingRect(const Common::Rect &rect) {
 			const int sourceX = kMapSubmodeMovieBackdropSourceX + restoreRect.left - kMapSubmodeMovieBackdropRect.left;
 			const int sourceY = kMapSubmodeMovieBackdropSourceY + restoreRect.top - kMapSubmodeMovieBackdropRect.top;
 			if (sourceX >= 0 && sourceY >= 0 && sourceX + restoreRect.width() <= map->w && sourceY + restoreRect.height() <= map->h)
-				g_system->copyRectToScreen(map->getBasePtr(sourceX, sourceY), map->pitch, restoreRect.left, restoreRect.top, restoreRect.width(), restoreRect.height());
+				_screen->copyRectToSurface(map->getBasePtr(sourceX, sourceY), map->pitch, restoreRect.left, restoreRect.top, restoreRect.width(), restoreRect.height());
 		}
 
 		return;
 	}
 
 	if (backing)
-		g_system->copyRectToScreen(backing->getBasePtr(rect.left, rect.top), backing->pitch, rect.left, rect.top, rect.width(), rect.height());
+		_screen->copyRectToSurface(backing->getBasePtr(rect.left, rect.top), backing->pitch, rect.left, rect.top, rect.width(), rect.height());
 }
 
 Common::Rect BCBEngine::getCaptionBackingRect(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &nativeRect) const {
@@ -246,7 +246,7 @@ void BCBEngine::drawCaptionTextBlock(const Common::String &sjisText, const Commo
 	if (!_captionTextFont && !_sjisFont)
 		return;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (!screen)
 		return;
 
@@ -292,7 +292,7 @@ void BCBEngine::drawCaptionTextBlock(const Common::String &sjisText, const Commo
 		}
 	}
 
-	g_system->unlockScreen();
+	_screen->addDirtyRect(getCaptionBackingRect(sjisText, unicodeText, rect));
 }
 
 bool BCBEngine::drawTimedVideoTextOverlay() {
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index fe33c6e87f4..4069614da11 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -23,6 +23,7 @@
 #include "common/endian.h"
 #include "common/events.h"
 #include "common/system.h"
+#include "graphics/screen.h"
 #include "graphics/surface.h"
 
 #include <math.h>
@@ -56,7 +57,7 @@ bool BCBEngine::dispatchComputerMainControls() {
 	drawComputerChoiceGroupControls();
 	drawComputerButton(kComputerPrimaryButtonTable[1], kComputerPrimaryButtonTable[1]);
 
-	g_system->updateScreen();
+	_screen->update();
 	return true;
 }
 
@@ -74,7 +75,7 @@ bool BCBEngine::showComputerScreen() {
 	if (!computer)
 		return false;
 
-	g_system->copyRectToScreen(computer->getPixels(), computer->pitch, 0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480));
+	_screen->copyRectToSurface(computer->getPixels(), computer->pitch, 0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480));
 	_currentDisplayState = 4;
 	return true;
 }
@@ -87,20 +88,19 @@ void BCBEngine::drawComputerChoiceGroupControls() {
 	if (!computer)
 		return;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		if (_computerChoiceGroupIndex >= 0 && _computerChoiceGroupIndex <= 2) {
 			const int sourceY = _computerChoiceGroupIndex * 0x16 + 0x2a0;
 			if (sourceY + 0x16 <= computer->h)
-				screen->copyRectToSurface(*computer, 0x3c, 0x43, Common::Rect(0, sourceY, 0xe8, sourceY + 0x16));
+				_screen->copyRectToSurface(*computer, 0x3c, 0x43, Common::Rect(0, sourceY, 0xe8, sourceY + 0x16));
 		}
 
 		for (uint i = 1; i < ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
 			const ButtonControlRecord &slot = kComputerChoiceLayoutSlots[i];
 			if (slot.x + slot.width <= computer->w && slot.y + slot.height <= computer->h)
-				screen->copyRectToSurface(*computer, slot.x, slot.y, Common::Rect(slot.x, slot.y, slot.x + slot.width, slot.y + slot.height));
+				_screen->copyRectToSurface(*computer, slot.x, slot.y, Common::Rect(slot.x, slot.y, slot.x + slot.width, slot.y + slot.height));
 		}
-		g_system->unlockScreen();
 	}
 
 	if (_computerChoiceGroupIndex == 0)
@@ -116,11 +116,9 @@ void BCBEngine::drawComputerButton(const ButtonControlRecord &sourceButton, cons
 	if (!computer)
 		return;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen && sourceButton.x + sourceButton.width <= computer->w && sourceButton.y + sourceButton.height <= computer->h)
-		screen->copyRectToSurface(*computer, destButton.x, destButton.y, Common::Rect(sourceButton.x, sourceButton.y, sourceButton.x + sourceButton.width, sourceButton.y + sourceButton.height));
-	if (screen)
-		g_system->unlockScreen();
+		_screen->copyRectToSurface(*computer, destButton.x, destButton.y, Common::Rect(sourceButton.x, sourceButton.y, sourceButton.x + sourceButton.width, sourceButton.y + sourceButton.height));
 }
 
 void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY) {
@@ -129,8 +127,8 @@ void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int
 		return;
 
 	if (sourceX + button.width <= computer->w && sourceY + button.height <= computer->h) {
-		g_system->copyRectToScreen(computer->getBasePtr(sourceX, sourceY), computer->pitch, button.x, button.y, button.width, button.height);
-		g_system->updateScreen();
+		_screen->copyRectToSurface(computer->getBasePtr(sourceX, sourceY), computer->pitch, button.x, button.y, button.width, button.height);
+		_screen->update();
 	}
 }
 
@@ -139,8 +137,8 @@ void BCBEngine::drawComputerDetailButtonNormal(const ButtonControlRecord &button
 		return;
 
 	if (button.x + button.width <= _computerDetailBacking->w && button.y + button.height <= _computerDetailBacking->h) {
-		g_system->copyRectToScreen(_computerDetailBacking->getBasePtr(button.x, button.y), _computerDetailBacking->pitch, button.x, button.y, button.width, button.height);
-		g_system->updateScreen();
+		_screen->copyRectToSurface(_computerDetailBacking->getBasePtr(button.x, button.y), _computerDetailBacking->pitch, button.x, button.y, button.width, button.height);
+		_screen->update();
 	}
 }
 
@@ -154,11 +152,11 @@ void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destBut
 	const int markerWidth = 0x36;
 	const int markerHeight = 0x18;
 	if (markerSourceX + markerWidth <= computer->w && markerSourceY + markerHeight <= computer->h)
-		g_system->copyRectToScreen(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
+		_screen->copyRectToSurface(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
 }
 
 void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (!screen)
 		return;
 
@@ -200,8 +198,15 @@ void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
 		}
 	}
 
-	g_system->unlockScreen();
-	g_system->updateScreen();
+	if (!clipped.isEmpty()) {
+		_screen->addDirtyRect(Common::Rect(clipped.left, clipped.top, clipped.right, clipped.top + 1));
+		_screen->addDirtyRect(Common::Rect(clipped.left, clipped.top + 1, clipped.left + 1, clipped.bottom));
+		if (rect.bottom != rect.top && rect.bottom >= 0 && rect.bottom < screen->h)
+			_screen->addDirtyRect(Common::Rect(clipped.left, rect.bottom, clipped.right, rect.bottom + 1));
+		if (rect.right != rect.left && rect.right >= 0 && rect.right < screen->w)
+			_screen->addDirtyRect(Common::Rect(rect.right, clipped.top, rect.right + 1, clipped.bottom));
+	}
+	_screen->update();
 }
 
 void BCBEngine::processXorRectangleBorderAnimation(bool expand, const Common::Rect &sourceRect, const Common::Rect &targetRect) {
@@ -243,7 +248,7 @@ void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord
 	if (!computer)
 		return;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	int visibleButtonSlot = 1;
 	for (int i = 0; screen && i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
 		const int stateIndex = firstStateIndex + i;
@@ -258,15 +263,12 @@ void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord
 		const ButtonControlRecord &source = choiceButtonRecords[i];
 		const ButtonControlRecord &dest = kComputerChoiceLayoutSlots[visibleButtonSlot++];
 		if (source.x + source.width <= computer->w && source.y + source.height <= computer->h)
-			screen->copyRectToSurface(*computer, dest.x, dest.y, Common::Rect(source.x, source.y, source.x + source.width, source.y + source.height));
+			_screen->copyRectToSurface(*computer, dest.x, dest.y, Common::Rect(source.x, source.y, source.x + source.width, source.y + source.height));
 		if (directlyAvailable) {
 			_computerActiveChoiceIds.push_back(source.controlId);
 			_computerActiveChoiceRects.push_back(Common::Rect(dest.x, dest.y, dest.x + dest.width, dest.y + dest.height));
 		}
 	}
-	if (screen)
-		g_system->unlockScreen();
-
 	visibleButtonSlot = 1;
 	for (int i = 0; i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
 		const int stateIndex = firstStateIndex + i;
@@ -300,7 +302,7 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
 	if (!composite)
 		return false;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		screen->copyRectToSurface(*computer, 0, 0, Common::Rect(0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480)));
 		screen->copyRectToSurface(*composite, 0x24, 0x23, Common::Rect(0, 0, MIN<int>(composite->w, 640 - 0x24), MIN<int>(composite->h, 480 - 0x23)));
@@ -311,7 +313,7 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
 		}
 		_computerDetailBacking = new Graphics::Surface();
 		_computerDetailBacking->copyFrom(*screen);
-		g_system->unlockScreen();
+		_screen->makeAllDirty();
 	}
 
 	composite->free();
@@ -323,7 +325,7 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
 		_computerDetailVariantIndex = -1;
 
 	_currentDisplayState = 5;
-	g_system->updateScreen();
+	_screen->update();
 	return true;
 }
 
@@ -349,7 +351,7 @@ bool BCBEngine::handleComputerClick(const Common::Point &mousePosition) {
 				playUiSound();
 				_computerChoiceGroupIndex = groupIndex;
 				drawComputerChoiceGroupControls();
-				g_system->updateScreen();
+				_screen->update();
 			}
 			return true;
 		}
@@ -384,7 +386,7 @@ bool BCBEngine::handleComputerClick(const Common::Point &mousePosition) {
 
 		const ButtonControlRecord &nextButton = kComputerDetailButtonTable[2];
 		if (_computerDetailNextVariantEnabled &&
-				Common::Rect(nextButton.x, nextButton.y, nextButton.x + nextButton.width, nextButton.y + nextButton.height).contains(mousePosition)) {
+			Common::Rect(nextButton.x, nextButton.y, nextButton.x + nextButton.width, nextButton.y + nextButton.height).contains(mousePosition)) {
 			drawComputerButtonPressed(nextButton, _computerDetailVariantIndex >= 0 ? 0x188 : 0x228, 0x2a0);
 			playUiSound();
 			if (_computerDetailVariantIndex < 0) {
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 187a45a2d97..ec6ba1f6d4c 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -21,6 +21,7 @@
 
 #include "common/events.h"
 #include "common/system.h"
+#include "graphics/screen.h"
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
 #include "harold/bcb/static_data.h"
@@ -270,7 +271,7 @@ void BCBEngine::serviceCursorRefresh(bool screenUpdated) {
 
 	if (now - _lastCursorRefreshMillis >= kCursorRefreshMillis) {
 		_lastCursorRefreshMillis = now;
-		g_system->updateScreen();
+		_screen->update();
 	}
 }
 
@@ -283,6 +284,7 @@ void BCBEngine::delayWithEventPolling(uint32 millis, bool handleClicks) {
 
 	while (!shouldQuit() && g_system->getMillis() - start < millis) {
 		pollEvents(handleClicks);
+		updateMenuBackgroundMusic();
 		serviceCursorRefresh();
 
 		const uint32 elapsed = g_system->getMillis() - start;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index a4d78397061..3e7ffa61acd 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -23,6 +23,7 @@
 #include "common/fs.h"
 #include "common/ustr.h"
 #include "graphics/font.h"
+#include "graphics/screen.h"
 #include "graphics/sjis.h"
 #include "graphics/surface.h"
 #include "video/avi_decoder.h"
@@ -48,7 +49,7 @@ bool BCBEngine::showMapScreen() {
 	delete map;
 	_currentDisplayState = 2;
 	_lastTimerTick = g_system->getMillis();
-	g_system->updateScreen();
+	_screen->update();
 	return true;
 }
 
@@ -106,10 +107,10 @@ void BCBEngine::drawMapButtonPressed(uint buttonIndex, bool present) {
 
 	const ButtonControlRecord &button = kMapButtonTable[buttonIndex];
 	if (0x1c8 + button.width <= map->w && 0x1e0 + button.height <= map->h)
-		g_system->copyRectToScreen(map->getBasePtr(0x1c8, 0x1e0), map->pitch, button.x, button.y, button.width, button.height);
+		_screen->copyRectToSurface(map->getBasePtr(0x1c8, 0x1e0), map->pitch, button.x, button.y, button.width, button.height);
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt) {
@@ -165,14 +166,14 @@ void BCBEngine::drawMapControls(bool present) {
 	if (!map)
 		return;
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		composeMapControlsToSurface(*screen, *map);
-		g_system->unlockScreen();
+		_screen->makeAllDirty();
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 bool BCBEngine::handleMapControlClick(const Common::Point &mousePosition) {
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index 75546246ea1..70b8c5ee21e 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -29,6 +29,7 @@
 #include "common/textconsole.h"
 #include "graphics/font.h"
 #include "graphics/fontman.h"
+#include "graphics/screen.h"
 #include "graphics/surface.h"
 
 #include "harold/bcb/bcb.h"
@@ -238,13 +239,13 @@ bool BCBEngine::showSystemScreen() {
 									kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
 		system->fillRect(fillRect, system->format.RGBToColor(255, 255, 255));
 	}
-	g_system->copyRectToScreen(system->getPixels(), system->pitch, 0, 0, MIN<int>(system->w, 640), MIN<int>(system->h, 480));
+	_screen->copyRectToSurface(system->getPixels(), system->pitch, 0, 0, MIN<int>(system->w, 640), MIN<int>(system->h, 480));
 	system->free();
 	delete system;
 
 	_currentDisplayState = 3;
 	drawSystemSubmodeButtons(false);
-	g_system->updateScreen();
+	_screen->update();
 	return true;
 }
 
@@ -260,12 +261,12 @@ void BCBEngine::drawSystemSubmodeButtons(bool present) {
 			(_systemSaveLoadSlotActionMode == 2 && i == 2))
 			source = kSystemSubmodeSelectedSources[i];
 		if (source.x + button.width <= system->w && source.y + button.height <= system->h)
-			g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch,
+			_screen->copyRectToSurface(system->getBasePtr(source.x, source.y), system->pitch,
 									   button.x, button.y, button.width, button.height);
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present) {
@@ -279,10 +280,10 @@ void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool pr
 	const ButtonControlRecord &button = kSystemSubmodeButtonTable[buttonIndex];
 	const Common::Point source = selected ? kSystemSubmodeSelectedSources[buttonIndex] : kSystemSubmodeNormalSources[buttonIndex];
 	if (source.x + button.width <= system->w && source.y + button.height <= system->h)
-		g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch, button.x, button.y, button.width, button.height);
+		_screen->copyRectToSurface(system->getBasePtr(source.x, source.y), system->pitch, button.x, button.y, button.width, button.height);
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::applySystemWaveVolume() {
@@ -312,24 +313,23 @@ void BCBEngine::drawSystemWaveVolumeIndicator(bool present) {
 		return;
 
 	if (kSystemWaveVolumeIndicatorRect.right <= system->w && kSystemWaveVolumeIndicatorRect.bottom <= system->h) {
-		g_system->copyRectToScreen(system->getBasePtr(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top),
+		_screen->copyRectToSurface(system->getBasePtr(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top),
 								   system->pitch, kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
 								   kSystemWaveVolumeIndicatorRect.width(), kSystemWaveVolumeIndicatorRect.height());
 	}
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		const int fillWidth = (_systemWaveVolume * kSystemWaveVolumeIndicatorRect.width()) / 0xffff;
 		if (fillWidth > 0) {
 			const Common::Rect fillRect(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
 										kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
-			screen->fillRect(fillRect, screen->format.RGBToColor(255, 255, 255));
+			_screen->fillRect(fillRect, screen->format.RGBToColor(255, 255, 255));
 		}
-		g_system->unlockScreen();
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
@@ -377,6 +377,7 @@ bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition)
 			}
 		}
 		serviceCursorRefresh();
+		updateMenuBackgroundMusic();
 		g_system->delayMillis(1);
 	}
 	ConfMan.flushToDisk();
@@ -427,7 +428,7 @@ void BCBEngine::drawSystemSaveLoadSlots(bool present) {
 		const ButtonControlRecord &button = kSystemButtonTable[slot];
 		const int sourceY = 480 + (slot - 1) * button.height;
 		if (sourceY + button.height <= system->h)
-			g_system->copyRectToScreen(system->getBasePtr(0, sourceY), system->pitch, button.x, button.y, button.width, button.height);
+			_screen->copyRectToSurface(system->getBasePtr(0, sourceY), system->pitch, button.x, button.y, button.width, button.height);
 
 		int savedDay = 0;
 		int savedClockTicks = 0;
@@ -438,18 +439,18 @@ void BCBEngine::drawSystemSaveLoadSlots(bool present) {
 			slotText = formatSystemTextForDisplay(_saveSlotEmptyTextFormatSjis, slot);
 		}
 
-		Graphics::Surface *screen = g_system->lockScreen();
+		Graphics::Surface *screen = _screen->surfacePtr();
 		if (screen) {
 			const uint32 textColor = screen->format.RGBToColor(0, 0, 0);
 			const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
 			const int textY = button.y + (button.height - font->getFontHeight()) / 2 - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
 			font->drawString(screen, slotText, button.x, textY, button.width, textColor);
-			g_system->unlockScreen();
+			_screen->addDirtyRect(Common::Rect(button.x, textY, button.x + button.width, textY + font->getFontHeight()));
 		}
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
@@ -586,24 +587,23 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
 	const int buttonSourceY = 0x32;
 	Graphics::Surface dialogBacking;
 	dialogBacking.create(dialogRect.width(), dialogRect.height(), g_system->getScreenFormat());
-	Graphics::Surface *screenBacking = g_system->lockScreen();
+	Graphics::Surface *screenBacking = _screen->surfacePtr();
 	if (screenBacking) {
 		dialogBacking.copyRectToSurface(*screenBacking, 0, 0, dialogRect);
-		g_system->unlockScreen();
 	}
 
 	auto drawDialogButton = [&](const Common::Rect &rect, int sourceX, bool present) {
 		if (sourceX + rect.width() <= chip->w && chip->h >= buttonSourceY + rect.height())
-			g_system->copyRectToScreen(chip->getBasePtr(sourceX, buttonSourceY), chip->pitch,
+			_screen->copyRectToSurface(chip->getBasePtr(sourceX, buttonSourceY), chip->pitch,
 									   rect.left, rect.top, rect.width(), rect.height());
 		if (present)
-			g_system->updateScreen();
+			_screen->update();
 	};
 
 	if (dialogRect.width() <= chip->w && chip->h >= dialogSourceY + dialogRect.height())
-		g_system->copyRectToScreen(chip->getBasePtr(0, dialogSourceY), chip->pitch, dialogRect.left, dialogRect.top, dialogRect.width(), dialogRect.height());
+		_screen->copyRectToSurface(chip->getBasePtr(0, dialogSourceY), chip->pitch, dialogRect.left, dialogRect.top, dialogRect.width(), dialogRect.height());
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
 		const uint32 shadowColor = screen->format.RGBToColor(0, 0, 0);
@@ -611,13 +611,13 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
 		const int textYOffset = _gameDescription->desc.language == Common::KO_KOR ? -1 : 0;
 		font->drawString(screen, message, dialogRect.left + 10, dialogRect.top + 20 + textYOffset, 340, shadowColor);
 		font->drawString(screen, message, dialogRect.left + 11, dialogRect.top + 21 + textYOffset, 339, textColor);
-		g_system->unlockScreen();
+		_screen->addDirtyRect(dialogRect);
 	}
 
 	drawDialogButton(okRect, 0x17, false);
 	if (showCancelButton)
 		drawDialogButton(cancelRect, 0x97, false);
-	g_system->updateScreen();
+	_screen->update();
 
 	bool result = false;
 	bool done = false;
@@ -662,13 +662,14 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
 			}
 		}
 		serviceCursorRefresh();
+		updateMenuBackgroundMusic();
 		g_system->delayMillis(10);
 	}
 
-	g_system->copyRectToScreen(dialogBacking.getPixels(), dialogBacking.pitch, dialogRect.left, dialogRect.top,
+	_screen->copyRectToSurface(dialogBacking.getPixels(), dialogBacking.pitch, dialogRect.left, dialogRect.top,
 							   dialogBacking.w, dialogBacking.h);
 	dialogBacking.free();
-	g_system->updateScreen();
+	_screen->update();
 	return result;
 }
 
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 35b22557865..e810225c2bb 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -23,6 +23,7 @@
 #include "common/textconsole.h"
 #include "graphics/font.h"
 #include "graphics/fontman.h"
+#include "graphics/screen.h"
 #include "graphics/surface.h"
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
@@ -52,14 +53,14 @@ bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
 	if (lineReveal) {
 		blitScreenWithLineReveal(*mainScene, false);
 	} else {
-		g_system->copyRectToScreen(mainScene->getPixels(), mainScene->pitch, 0, 0, MIN<int>(mainScene->w, 640), MIN<int>(mainScene->h, 480));
+		_screen->copyRectToSurface(mainScene->getPixels(), mainScene->pitch, 0, 0, MIN<int>(mainScene->w, 640), MIN<int>(mainScene->h, 480));
 	}
 	mainScene->free();
 	delete mainScene;
 	_currentDisplayState = 1;
 	_sceneIdleMovieTimerTicks = 0;
 	_lastTimerTick = g_system->getMillis();
-	g_system->updateScreen();
+	_screen->update();
 	return true;
 }
 
@@ -86,7 +87,7 @@ bool BCBEngine::showActiveSceneChoiceControls() {
 	const Common::String overlayPath = Common::String::format("bmp/select/16m/sel%03d.bmp", sceneChoiceUiSetToShowId);
 	Graphics::Surface *overlay = loadDiscBitmap(overlayPath.c_str());
 	if (overlay) {
-		g_system->copyRectToScreen(overlay->getPixels(), overlay->pitch, 16, 48, MIN<int>(overlay->w, 400), MIN<int>(overlay->h, 200));
+		_screen->copyRectToSurface(overlay->getPixels(), overlay->pitch, 16, 48, MIN<int>(overlay->w, 400), MIN<int>(overlay->h, 200));
 		overlay->free();
 		delete overlay;
 	}
@@ -112,14 +113,13 @@ void BCBEngine::captureSceneChoiceBacking() {
 			_sceneChoiceBackingRect.extend(rect);
 	}
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (!screen)
 		return;
 
 	_sceneChoiceBacking = new Graphics::Surface();
 	_sceneChoiceBacking->create(_sceneChoiceBackingRect.width(), _sceneChoiceBackingRect.height(), screen->format);
 	_sceneChoiceBacking->copyRectToSurface(*screen, 0, 0, _sceneChoiceBackingRect);
-	g_system->unlockScreen();
 }
 
 void BCBEngine::restoreSceneChoiceBacking(bool present) {
@@ -131,11 +131,11 @@ void BCBEngine::restoreSceneChoiceBacking(bool present) {
 			continue;
 
 		const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
-		g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+		_screen->copyRectToSurface(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::freeSceneChoiceBacking() {
@@ -148,14 +148,13 @@ void BCBEngine::freeSceneChoiceBacking() {
 }
 
 void BCBEngine::clearSceneChoiceAfterSelection() {
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
-		screen->fillRect(Common::Rect(0x10, 0x30, 0x1a0, 0xf8), screen->format.RGBToColor(0, 0, 0));
-		g_system->unlockScreen();
+		_screen->fillRect(Common::Rect(0x10, 0x30, 0x1a0, 0xf8), screen->format.RGBToColor(0, 0, 0));
 	}
 
 	freeSceneChoiceBacking();
-	g_system->updateScreen();
+	_screen->update();
 }
 
 bool BCBEngine::isSceneChoiceButtonVisible(int buttonIndex) const {
@@ -180,10 +179,10 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
 		restoreSceneChoiceBacking(false);
 	} else if (_sceneChoiceBacking && pressedButtonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable)) {
 		const ButtonControlRecord &button = kSceneChoiceButtonTable[pressedButtonIndex];
-		g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+		_screen->copyRectToSurface(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
 	}
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (!screen)
 		return;
 
@@ -207,10 +206,9 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
 		const int textWidth = button.width - 10;
 		font->drawString(screen, label, textX + 1, textY + 1, textWidth, shadowColor, Graphics::kTextAlignLeft);
 		font->drawString(screen, label, textX, textY, textWidth, textColor, Graphics::kTextAlignLeft);
+		_screen->addDirtyRect(Common::Rect(textX, textY, textX + textWidth + 1, textY + font->getFontHeight() + 1));
 	};
 
-	g_system->unlockScreen();
-
 	const Graphics::Surface *mainScene = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
 	if (mainScene) {
 		const int firstButtonIndex = pressedButtonIndex < 0 ? 1 : pressedButtonIndex;
@@ -221,11 +219,11 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
 
 			const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
 			if (button.width <= mainScene->w && 0x210 + button.height <= mainScene->h)
-				g_system->copyRectToScreen(mainScene->getBasePtr(0, 0x210), mainScene->pitch, button.x, button.y, button.width, button.height);
+				_screen->copyRectToSurface(mainScene->getBasePtr(0, 0x210), mainScene->pitch, button.x, button.y, button.width, button.height);
 		}
 	}
 
-	screen = g_system->lockScreen();
+	screen = _screen->surfacePtr();
 	if (!screen)
 		return;
 
@@ -243,9 +241,8 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
 		drawChoiceLabel(_sceneChoiceMoveText, _sceneChoiceTerminatorButtonIndex);
 	}
 
-	g_system->unlockScreen();
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::handleSceneChoiceClick(const Common::Point &mousePosition) {
@@ -350,17 +347,17 @@ void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, b
 		clockBacking = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
 	}
 	if (clockBacking) {
-		g_system->copyRectToScreen(clockBacking->getBasePtr(0x25, 0x167), clockBacking->pitch, 0x25, 0x167, 0x60, 0x60);
+		_screen->copyRectToSurface(clockBacking->getBasePtr(0x25, 0x167), clockBacking->pitch, 0x25, 0x167, 0x60, 0x60);
 	}
 
-	Graphics::Surface *screen = g_system->lockScreen();
+	Graphics::Surface *screen = _screen->surfacePtr();
 	if (screen) {
 		renderClockAndChipToSurface(*screen, baseHourOffset, gameClockTicks);
-		g_system->unlockScreen();
+		_screen->addDirtyRect(Common::Rect(0x25, 0x167, 0xe4, 0x1c7));
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::handleSceneChoiceKeyboardSelection(int choiceNumber) {
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 2bd2dd08009..657e973bbd6 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -21,6 +21,7 @@
 
 #include "common/system.h"
 #include "graphics/font.h"
+#include "graphics/screen.h"
 #include "graphics/surface.h"
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
@@ -80,10 +81,10 @@ void BCBEngine::restoreTitleScreenRegion(const Common::Rect &rect, bool present)
 	if (clippedRect.isEmpty())
 		return;
 
-	g_system->copyRectToScreen(_titleBitmap->getBasePtr(clippedRect.left, clippedRect.top), _titleBitmap->pitch,
+	_screen->copyRectToSurface(_titleBitmap->getBasePtr(clippedRect.left, clippedRect.top), _titleBitmap->pitch,
 							   clippedRect.left, clippedRect.top, clippedRect.width(), clippedRect.height());
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 bool BCBEngine::transitionFromLogoToTitle() {
@@ -100,8 +101,8 @@ bool BCBEngine::startNewGameFromTitle() {
 	stopMenuBackgroundMusic();
 	stopGameOverMusic();
 
-	g_system->fillScreen(g_system->getScreenFormat().RGBToColor(0, 0, 0));
-	g_system->updateScreen();
+	_screen->clear(g_system->getScreenFormat().RGBToColor(0, 0, 0));
+	_screen->update();
 
 	stateDword(kStateCurrentScriptSequenceId) = 1;
 	return playNumberedMovie(6, true);
@@ -123,8 +124,8 @@ bool BCBEngine::showPendingStillImageScreen() {
 	if (!still)
 		return false;
 
-	g_system->copyRectToScreen(still->getPixels(), still->pitch, 0, 0, MIN<int>(still->w, 640), MIN<int>(still->h, 480));
-	g_system->updateScreen();
+	_screen->copyRectToSurface(still->getPixels(), still->pitch, 0, 0, MIN<int>(still->w, 640), MIN<int>(still->h, 480));
+	_screen->update();
 	still->free();
 	delete still;
 
@@ -198,7 +199,7 @@ bool BCBEngine::dispatchGameOverScreen() {
 	_currentDisplayState = 9;
 	_gameOverElapsedTimerTicks = 0;
 	_lastTimerTick = g_system->getMillis();
-	g_system->updateScreen();
+	_screen->update();
 	startGameOverMusic();
 	return true;
 }
@@ -219,8 +220,8 @@ void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
 	if (sourceX + button.width > _titleBitmap->w || sourceY + button.height > _titleBitmap->h)
 		return;
 
-	g_system->copyRectToScreen(_titleBitmap->getBasePtr(sourceX, sourceY), _titleBitmap->pitch, button.x, button.y, button.width, button.height);
-	g_system->updateScreen();
+	_screen->copyRectToSurface(_titleBitmap->getBasePtr(sourceX, sourceY), _titleBitmap->pitch, button.x, button.y, button.width, button.height);
+	_screen->update();
 }
 
 void BCBEngine::blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame) {
@@ -244,13 +245,13 @@ void BCBEngine::blitScreenWithLineReveal(const Graphics::Surface &surface, bool
 			const int topY = revealedSteps * 2;
 			const int bottomY = height - 1 - revealedSteps * 2;
 			if (topY < height)
-				g_system->copyRectToScreen(surface.getBasePtr(0, topY), surface.pitch, 0, topY, width, 1);
+				_screen->copyRectToSurface(surface.getBasePtr(0, topY), surface.pitch, 0, topY, width, 1);
 			if (bottomY != topY && bottomY >= 0)
-				g_system->copyRectToScreen(surface.getBasePtr(0, bottomY), surface.pitch, 0, bottomY, width, 1);
+				_screen->copyRectToSurface(surface.getBasePtr(0, bottomY), surface.pitch, 0, bottomY, width, 1);
 		}
 
 		if (presentFinalFrame || revealedSteps < revealSteps)
-			g_system->updateScreen();
+			_screen->update();
 		pollEvents(false);
 	}
 }
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 24d959a5901..ed8946dd0ee 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -23,6 +23,7 @@
 #include "audio/decoders/wave.h"
 #include "common/system.h"
 #include "common/textconsole.h"
+#include "graphics/screen.h"
 #include "graphics/surface.h"
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
@@ -83,21 +84,20 @@ void BCBEngine::drawMapSubmodeMovieBackdrop(bool present) {
 		const Common::Rect sourceRect(kMapSubmodeMovieBackdropSourceX, kMapSubmodeMovieBackdropSourceY,
 									  kMapSubmodeMovieBackdropSourceX + 0x1a4, kMapSubmodeMovieBackdropSourceY + 0x11e);
 		if (sourceRect.right <= map->w && sourceRect.bottom <= map->h)
-			g_system->copyRectToScreen(map->getBasePtr(sourceRect.left, sourceRect.top), map->pitch,
+			_screen->copyRectToSurface(map->getBasePtr(sourceRect.left, sourceRect.top), map->pitch,
 									   kMapSubmodeMovieBackdropRect.left, kMapSubmodeMovieBackdropRect.top,
 									   sourceRect.width(), sourceRect.height());
 	} else {
-		Graphics::Surface *screen = g_system->lockScreen();
+		Graphics::Surface *screen = _screen->surfacePtr();
 		if (!screen)
 			return;
 
 		const uint32 backingColor = screen->format.RGBToColor(0, 0, 0);
-		screen->fillRect(kMapSubmodeMciControlHitRect, backingColor);
-		g_system->unlockScreen();
+		_screen->fillRect(kMapSubmodeMciControlHitRect, backingColor);
 	}
 
 	if (present)
-		g_system->updateScreen();
+		_screen->update();
 }
 
 void BCBEngine::drawMovieControlStrip(int sourceX, bool present) {
@@ -107,9 +107,9 @@ void BCBEngine::drawMovieControlStrip(int sourceX, bool present) {
 	const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
 	if (chip) {
 		if (sourceX + 0x98 <= chip->w && 0xe0 + 0x30 <= chip->h)
-			g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xe0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+			_screen->copyRectToSurface(chip->getBasePtr(sourceX, 0xe0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
 		if (present)
-			g_system->updateScreen();
+			_screen->update();
 	}
 }
 
@@ -117,9 +117,9 @@ void BCBEngine::drawSceneChoiceControlStrip(int sourceX, bool present) {
 	const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
 	if (chip) {
 		if (sourceX + 0x98 <= chip->w && 0xb0 + 0x30 <= chip->h)
-			g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xb0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+			_screen->copyRectToSurface(chip->getBasePtr(sourceX, 0xb0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
 		if (present)
-			g_system->updateScreen();
+			_screen->update();
 	}
 }
 
@@ -140,12 +140,12 @@ void BCBEngine::processQueuedMovieSequence() {
 			applyPendingClockTickDelta();
 			renderClockAndChipToSurface(*map, 0, stateDword(kStateGameClockTicks));
 			composeMapControlsToSurface(*map, *map);
-			g_system->copyRectToScreen(map->getPixels(), map->pitch, 0, 0, MIN<int>(map->w, 640), MIN<int>(map->h, 480));
+			_screen->copyRectToSurface(map->getPixels(), map->pitch, 0, 0, MIN<int>(map->w, 640), MIN<int>(map->h, 480));
 			map->free();
 			delete map;
 		}
 		drawSceneChoiceControlStrip(0, false);
-		g_system->updateScreen();
+		_screen->update();
 	}
 
 	processGameStateDispatcher(false);
@@ -232,6 +232,19 @@ void BCBEngine::startMenuBackgroundMusic() {
 	_menuBackgroundVideo->start();
 }
 
+void BCBEngine::updateMenuBackgroundMusic() {
+	if (!_menuBackgroundVideo)
+		return;
+	if (_menuBackgroundVideo->endOfVideo()) {
+		_menuBackgroundVideo->stop();
+		_menuBackgroundVideo->rewind();
+		_menuBackgroundVideo->decodeNextFrame();
+		_menuBackgroundVideo->start();
+	}
+	if (_menuBackgroundVideo->needsUpdate())
+		_menuBackgroundVideo->decodeNextFrame();
+}
+
 void BCBEngine::stopMenuBackgroundMusic() {
 	if (_menuBackgroundVideo) {
 		delete _menuBackgroundVideo;


Commit: e7e04ce66dfd7e44c7b59c04b37516978b7cd58f
    https://github.com/scummvm/scummvm/commit/e7e04ce66dfd7e44c7b59c04b37516978b7cd58f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Fix volume input and open System with mapped Escape

Changed paths:
    engines/harold/bcb/input.cpp
    engines/harold/bcb/saveload.cpp


diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index ec6ba1f6d4c..7b3712cd32e 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -233,6 +233,11 @@ void BCBEngine::handleKeymapperAction(Common::CustomEventType action) {
 }
 
 bool BCBEngine::handleEscapeAction() {
+	if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
+		handleLeftButtonDown(Common::Point(kSystemMenuControlHitRect.left, kSystemMenuControlHitRect.top));
+		return true;
+	}
+
 	if (_currentDisplayState == 3) {
 		activateSystemExitButton();
 		return true;
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index 70b8c5ee21e..89a76adaa92 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -48,7 +48,6 @@ enum {
 
 static const StaticRect kSystemWaveVolumeDownRect = {268, 407, 290, 439};
 static const StaticRect kSystemWaveVolumeUpRect = {444, 407, 466, 439};
-static const StaticRect kSystemWaveVolumeSeekRect = {294, 414, 442, 439};
 static const StaticRect kSystemWaveVolumeIndicatorRect = {294, 425, 442, 449};
 
 static const Common::Point kSystemSubmodeNormalSources[5] = {
@@ -333,7 +332,7 @@ void BCBEngine::drawSystemWaveVolumeIndicator(bool present) {
 }
 
 bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
-	if (kSystemWaveVolumeSeekRect.contains(mousePosition)) {
+	if (kSystemWaveVolumeIndicatorRect.contains(mousePosition)) {
 		const int seekX = CLIP<int>(mousePosition.x - kSystemWaveVolumeIndicatorRect.left, 0, kSystemWaveVolumeIndicatorRect.width());
 		const int volume = CLIP<int>((seekX * 0xffff) / kSystemWaveVolumeIndicatorRect.width(), 0, 0xffff);
 		if (volume != _systemWaveVolume) {
@@ -374,6 +373,8 @@ bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition)
 			} else if (event.type == Common::EVENT_LBUTTONUP) {
 				_mousePosition = event.mouse;
 				done = true;
+			} else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END && event.customType == kBCBActionInteract) {
+				done = true;
 			}
 		}
 		serviceCursorRefresh();


Commit: ac076b0fa2b839b54382ecbc7c2d8d63233f3334
    https://github.com/scummvm/scummvm/commit/ac076b0fa2b839b54382ecbc7c2d8d63233f3334
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Rename game to match official naming

Changed paths:
    engines/harold/bcb/captions.cpp
    engines/harold/bcb/computer.cpp
    engines/harold/bcb/executable_data.cpp
    engines/harold/bcb/map.cpp
    engines/harold/configure.engine
    engines/harold/detection.cpp
    engines/harold/detection_tables.h
    engines/harold/metaengine.cpp


diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
index 1b868c28681..0866c348a0e 100644
--- a/engines/harold/bcb/captions.cpp
+++ b/engines/harold/bcb/captions.cpp
@@ -58,7 +58,7 @@ const byte *BCBEngine::findAllBinRecordData(const Common::String &recordName, ui
 		uint nameLength = 0;
 		while (nameLength < 0x20 && _allBinData[entryOffset + 8 + nameLength] != 0)
 			++nameLength;
-		const Common::String entryName((const char *)&_allBinData[entryOffset + 8], nameLength);
+		const Common::String entryName(reinterpret_cast<const char *>(&_allBinData[entryOffset + 8]), nameLength);
 		if (!recordName.equals(entryName))
 			continue;
 
@@ -115,7 +115,7 @@ static bool isCaptionEdgeSpace(byte c) {
 }
 
 static void trimTimedCaptionLine(Common::String &line) {
-	const byte *text = (const byte *)line.c_str();
+	const byte *text = reinterpret_cast<const byte *>(line.c_str());
 	uint start = 0;
 	uint end = line.size();
 
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 4069614da11..91a01cb5a1d 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -163,7 +163,7 @@ void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
 	const Common::Rect clipped = rect.findIntersectingRect(Common::Rect(0, 0, screen->w, screen->h));
 	if (clipped.width() > 0 && clipped.height() > 0) {
 		const auto invertPixel = [&](int x, int y) {
-			byte *pixel = (byte *)screen->getBasePtr(x, y);
+			byte *pixel = static_cast<byte *>(screen->getBasePtr(x, y));
 			switch (screen->format.bytesPerPixel) {
 			case 1:
 				pixel[0] ^= 0xff;
diff --git a/engines/harold/bcb/executable_data.cpp b/engines/harold/bcb/executable_data.cpp
index b5d37e61074..f40bd6be992 100644
--- a/engines/harold/bcb/executable_data.cpp
+++ b/engines/harold/bcb/executable_data.cpp
@@ -150,7 +150,7 @@ static bool readExecutableString(const Common::Array<byte> &executableData, uint
 	if (textEnd == textOffset)
 		return false;
 
-	sjisText = Common::String((const char *)&executableData[textOffset], textEnd - textOffset);
+	sjisText = Common::String(reinterpret_cast<const char *>(&executableData[textOffset]), textEnd - textOffset);
 	return true;
 }
 
@@ -224,7 +224,7 @@ bool BCBEngine::loadExecutableDataTables() {
 			if (textEnd == textOffset)
 				break;
 
-			const char *textStart = (const char *)&executableData[textOffset];
+			const char *textStart = reinterpret_cast<const char *>(&executableData[textOffset]);
 			_sceneChoiceText[setId].push_back(Common::U32String(textStart, textEnd - textOffset, profile.encoding));
 		}
 	}
@@ -255,7 +255,7 @@ bool BCBEngine::loadExecutableDataTables() {
 		if (textEnd == textOffset)
 			continue;
 
-		const char *textStart = (const char *)&executableData[textOffset];
+		const char *textStart = reinterpret_cast<const char *>(&executableData[textOffset]);
 		_mapButtonTextSjis[i] = Common::String(textStart, textEnd - textOffset);
 		_mapButtonText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
 	}
@@ -280,7 +280,7 @@ bool BCBEngine::loadExecutableDataTables() {
 		if (textEnd == textOffset)
 			continue;
 
-		const char *textStart = (const char *)&executableData[textOffset];
+		const char *textStart = reinterpret_cast<const char *>(&executableData[textOffset]);
 		_gameOverText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
 	}
 
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index 3e7ffa61acd..51943408a13 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -137,7 +137,7 @@ void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Gr
 		const int textWidth = MAX<int>(0, surface.w - textX - 2);
 		if (_gameDescription->desc.language == Common::JA_JPN && _sjisFont) {
 			int x = textX + 1;
-			for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+			for (const byte *text = reinterpret_cast<const byte *>(_mapButtonTextSjis[i].c_str()); *text;) {
 				const byte firstByte = *text++;
 				uint16 sjisChar = firstByte;
 				if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
@@ -146,7 +146,7 @@ void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Gr
 				x += _sjisFont->getCharWidth(sjisChar);
 			}
 			x = textX;
-			for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+			for (const byte *text = reinterpret_cast<const byte *>(_mapButtonTextSjis[i].c_str()); *text;) {
 				const byte firstByte = *text++;
 				uint16 sjisChar = firstByte;
 				if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
diff --git a/engines/harold/configure.engine b/engines/harold/configure.engine
index 14c4908336b..2dd66cc47a6 100644
--- a/engines/harold/configure.engine
+++ b/engines/harold/configure.engine
@@ -1,4 +1,4 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
 add_engine harold "J.B. Harold series" no "bcb"
-add_engine bcb "Blue Chicago Blues" no "" "" "" "msmpeg4"
+add_engine bcb "J.B. Harold: Blue Chicago Blues" no "" "" "" "msmpeg4"
diff --git a/engines/harold/detection.cpp b/engines/harold/detection.cpp
index fbe08e10e6f..478587301ac 100644
--- a/engines/harold/detection.cpp
+++ b/engines/harold/detection.cpp
@@ -27,7 +27,7 @@
 #include "harold/detection_tables.h"
 
 static const PlainGameDescriptor haroldGames[] = {
-	{"bcb", "Blue Chicago Blues"},
+	{"bcb", "J.B. Harold: Blue Chicago Blues"},
 	{nullptr, nullptr}};
 
 static const DebugChannelDef debugFlagList[] = {
diff --git a/engines/harold/detection_tables.h b/engines/harold/detection_tables.h
index 2ec09feb116..483c0501661 100644
--- a/engines/harold/detection_tables.h
+++ b/engines/harold/detection_tables.h
@@ -41,6 +41,7 @@ static const HaroldGameDescription gameDescriptions[] = {
 	},
 
 	{
+		// Blue Chicago Blues Korean Windows release.
 		{
 			"bcb",
 			nullptr,
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
index 1b5ffc18146..a30fcf0f31b 100644
--- a/engines/harold/metaengine.cpp
+++ b/engines/harold/metaengine.cpp
@@ -46,7 +46,7 @@ Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
 	using namespace Common;
 	using namespace BCB;
 
-	Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "bcb-default", _("Blue Chicago Blues"));
+	Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "bcb-default", _("J.B. Harold: Blue Chicago Blues"));
 	Action *act;
 
 	act = new Action("INTERACT", _("Interact"));
@@ -76,13 +76,11 @@ Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
 
 	static const char *const choiceActionIds[] = {
 		"CHOICE1", "CHOICE2", "CHOICE3", "CHOICE4",
-		"CHOICE5", "CHOICE6", "CHOICE7", "CHOICE8"
-	};
+		"CHOICE5", "CHOICE6", "CHOICE7", "CHOICE8"};
 	static const char *const choiceActionLabels[] = {
 		_s("Choice 1"), _s("Choice 2"), _s("Choice 3"),
 		_s("Choice 4"), _s("Choice 5"), _s("Choice 6"),
-		_s("Choice 7"), _s("Choice 8")
-	};
+		_s("Choice 7"), _s("Choice 8")};
 
 	for (int choiceIndex = 1; choiceIndex <= 8; ++choiceIndex) {
 		act = new Action(choiceActionIds[choiceIndex - 1], _(choiceActionLabels[choiceIndex - 1]));


Commit: f0772668a7011da60b0f36d6e3738f72ef060ba4
    https://github.com/scummvm/scummvm/commit/f0772668a7011da60b0f36d6e3738f72ef060ba4
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00

Commit Message:
HAROLD: BCB: Use shared font loading and address engine integration review

Changed paths:
  A engines/harold/POTFILES
    engines/harold/bcb/bcb.cpp
    engines/harold/bcb/computer.cpp
    engines/harold/bcb/scene.cpp
    engines/harold/bcb/screens.cpp
    engines/harold/configure.engine
    engines/harold/metaengine.cpp
    engines/harold/module.mk


diff --git a/engines/harold/POTFILES b/engines/harold/POTFILES
new file mode 100644
index 00000000000..10f9e1eb1e2
--- /dev/null
+++ b/engines/harold/POTFILES
@@ -0,0 +1 @@
+engines/harold/metaengine.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 00b92856c69..36edbf08992 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -20,7 +20,6 @@
  */
 
 #include "harold/bcb/bcb.h"
-#include "common/compression/unzip.h"
 #include "common/config-manager.h"
 #include "common/system.h"
 #include "common/textconsole.h"
@@ -42,66 +41,42 @@ namespace BCB {
 static const int kCaptionTextNativeCharacterHeight = 18;
 static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
 
-static Graphics::Font *loadFont(Common::Archive &archive, const char *name, int size, Graphics::TTFSizeMode sizeMode) {
-	Common::SeekableReadStream *stream = archive.createReadStreamForMember(Common::Path(name));
-	if (!stream)
-		return nullptr;
-	Graphics::Font *font = Graphics::loadTTFFont(stream, DisposeAfterUse::YES, size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
-	if (!font)
-		delete stream;
-	return font;
-}
-
-static Graphics::Font *loadLocalizedCjkFont(Common::Archive &archive, Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
+static Graphics::Font *loadLocalizedCjkFont(Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
 	if (language == Common::KO_KOR) {
-		Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", size, sizeMode);
+		Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
 		if (font)
 			return font;
-		font = loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
+		font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
 		if (font)
 			return font;
-		return loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
+		return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
 	}
 
-	Graphics::Font *font = loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
+	Graphics::Font *font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
 	if (font)
 		return font;
-	return loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
+	return Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
 }
 
-static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Archive &archive, Common::Language language) {
+static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Language language) {
 	if (language == Common::KO_KOR) {
-		Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+		Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
 		if (font)
 			return font;
 	}
 
-	Graphics::Font *font = loadFont(archive, "NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+	Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
 	if (font)
 		return font;
-	return loadFont(archive, "VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+	return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
 }
 #endif
 
 void BCBEngine::loadFonts() {
 #ifdef USE_FREETYPE2
-	Common::SearchSet fonts;
-	const char *archiveNames[] = {"fonts.dat", "fonts-cjk.dat"};
-	for (const char *name : archiveNames) {
-		Common::SeekableReadStream *stream = nullptr;
-		if (ConfMan.hasKey("extrapath")) {
-			Common::FSDirectory directory(ConfMan.getPath("extrapath"));
-			stream = directory.createReadStreamForMember(name);
-		}
-		if (!stream)
-			stream = SearchMan.createReadStreamForMember(name);
-		Common::Archive *archive = Common::makeZipArchive(stream);
-		if (archive)
-			fonts.add(name, archive);
-	}
-	_captionTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
-	_gameOverTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
-	_ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(fonts, _gameDescription->desc.language);
+	_captionTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+	_gameOverTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
+	_ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(_gameDescription->desc.language);
 #endif
 }
 
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 91a01cb5a1d..0ca4f44a1eb 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -26,8 +26,6 @@
 #include "graphics/screen.h"
 #include "graphics/surface.h"
 
-#include <math.h>
-
 #include "harold/bcb/bcb.h"
 #include "harold/bcb/script.h"
 #include "harold/bcb/static_data.h"
@@ -172,10 +170,6 @@ void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
 				WRITE_UINT16(pixel, READ_UINT16(pixel) ^ 0xffff);
 				break;
 			case 3:
-				pixel[0] ^= 0xff;
-				pixel[1] ^= 0xff;
-				pixel[2] ^= 0xff;
-				break;
 			case 4:
 				pixel[0] ^= 0xff;
 				pixel[1] ^= 0xff;
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index e810225c2bb..ec3f4b37850 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -30,8 +30,6 @@
 #include "harold/bcb/static_data.h"
 #include "video/avi_decoder.h"
 
-#include <math.h>
-
 namespace BCB {
 
 bool BCBEngine::showMainSceneScreen() {
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 657e973bbd6..819d238faec 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -169,7 +169,7 @@ bool BCBEngine::dispatchGameOverScreen() {
 			for (uint lineStart = 0; lineStart < text.size() && y < textRect.bottom;) {
 				uint lineEnd = lineStart;
 				uint lastFit = lineStart;
-				while (lineEnd < text.size() && text[lineEnd] != U'\n') {
+				while (lineEnd < text.size() && text[lineEnd] != '\n') {
 					++lineEnd;
 					const Common::U32String candidate(text.c_str() + lineStart, lineEnd - lineStart);
 					if (_gameOverTextFont->getStringWidth(candidate) <= textRect.width())
@@ -180,13 +180,13 @@ bool BCBEngine::dispatchGameOverScreen() {
 
 				if (lastFit == lineStart && lineEnd > lineStart)
 					lastFit = lineStart + 1;
-				if (lineEnd < text.size() && text[lineEnd] == U'\n')
+				if (lineEnd < text.size() && text[lineEnd] == '\n')
 					lastFit = lineEnd;
 
 				_gameOverTextFont->drawString(gameOver, Common::U32String(text.c_str() + lineStart, lastFit - lineStart), textRect.left, y, textRect.width(), white, Graphics::kTextAlignLeft);
 				y += _gameOverTextFont->getFontHeight() + kGameOverTextLineGap;
 				lineStart = lastFit;
-				if (lineStart < text.size() && text[lineStart] == U'\n')
+				if (lineStart < text.size() && text[lineStart] == '\n')
 					++lineStart;
 			}
 		}
diff --git a/engines/harold/configure.engine b/engines/harold/configure.engine
index 2dd66cc47a6..365da369e28 100644
--- a/engines/harold/configure.engine
+++ b/engines/harold/configure.engine
@@ -1,4 +1,4 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
 add_engine harold "J.B. Harold series" no "bcb"
-add_engine bcb "J.B. Harold: Blue Chicago Blues" no "" "" "" "msmpeg4"
+add_engine bcb "J.B. Harold: Blue Chicago Blues" no "" "" "highres 16bit" "msmpeg4"
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
index a30fcf0f31b..eecf771f179 100644
--- a/engines/harold/metaengine.cpp
+++ b/engines/harold/metaengine.cpp
@@ -38,10 +38,15 @@ public:
 	}
 
 	int getMaximumSaveSlot() const override { return 8; }
+	bool hasFeature(MetaEngineFeature f) const override;
 	Common::KeymapArray initKeymaps(const char *target) const override;
 	Common::Error createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const override;
 };
 
+bool HaroldMetaEngine::hasFeature(MetaEngineFeature f) const {
+	return checkExtendedSaves(f);
+}
+
 Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
 	using namespace Common;
 	using namespace BCB;
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 276b4a8c8a1..291366b581d 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -2,19 +2,19 @@ MODULE := engines/harold
 
 MODULE_OBJS := \
 	bcb/bcb.o \
-	bcb/resources.o \
-	bcb/video.o \
 	bcb/captions.o \
-	bcb/scene.o \
-	bcb/screens.o \
-	bcb/input.o \
 	bcb/computer.o \
 	bcb/debugger.o \
-	bcb/saveload.o \
+	bcb/executable_data.o \
+	bcb/input.o \
 	bcb/map.o \
+	bcb/resources.o \
+	bcb/saveload.o \
+	bcb/scene.o \
+	bcb/screens.o \
 	bcb/script.o \
-	bcb/executable_data.o \
 	bcb/static_data.o \
+	bcb/video.o \
 	metaengine.o
 
 MODULE_DIRS += \




More information about the Scummvm-git-logs mailing list