[Scummvm-git-logs] scummvm master -> 8ed3092474f30425fd61351fe78c03a7d9ef4748

bluegr noreply at scummvm.org
Thu May 15 06:21:44 UTC 2025


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

Summary:
d4f1cb93c1 GOB: non English characters must be allowed in INI files
692dc33a85 GOB: implement o7_openItk (Adibou2)
a09ffa3c8d GOB: Adibou2 needs to handle functions indexes > 32767 in playTot
af7d72a18d GOB: loadTot() should not fail on TOT files without resources
91a85c1b6b GOB: int16->uint16 fix in o1_strToLong
a4d4d3c27d VIDEO: Add support for blitting paletted VMD to high-color destinations
e1ce819b02 GOB: High color support in Adibou2
52c88faf0a GOB: Make shaded fillRect available in later version of the engine (>= DrawPlaytoons)
2647651c00 GOB: more int16->uint16 fixes for game variables indexes
84283fd550 GOB: implement o1_palLoad cases 55 and 56 (push/pop palette)
01a36080ca GOB: Check sprite index range in copy/free sprite opcodes
5c4aa16eac VIDEO: int16 -> uint16 for _soundFreq in VMD decoder
950b58cf42 GOB: Read explicit video slot number from scripts
aa906c0c47 GOB: Fix conditions for non-blocking videos
63d093a64a GOB: Update slot-dependent "current frame" game variables
8096fd68cd GOB: Video break key fixes for Adibou2/Sciences
b5864feedb GOB: Fix start_frame<=-100 case in o7_playVmdOrMusic
1459246626 GOB: Many "live" videos fixes for Adibou2/Sciences
f855e7cbc1 GOB: Fix default start and last frames for "live" videos
374e685766 GOB: Seek to arbitrary frame is not allowed for non-blocking videos
02003553ea GOB: Fix live video current frame update in video playback
fc88883e58 GOB: Allow to "play" frames beyond last frame as a no-op in video playback
c4515b6f1f GOB: Show cursor while a video is playing for Adibou2
5c4e0bbb12 GOB: Add synchronization in playFrame (Adibou2/Sciences)
4f06a5f3c4 GOB: Fix conditions for loop videos
fd080b2bf8 GOB: Fix conditions for closing live videos in Adibou2/Sciences
9bb120aca2 GOB: Fix handling of "empty" video filenames, meaning "close video"
c2406ca7ca GOB: Another condition preventing a video to be advanced by updateLive
ace2143af4 GOB: Add stubs for Adibou2/Anglais
365a5827cb GOB: Add stubs for Adibou/Musique
1b9ae2bd91 GOB: More video-related fixes for Adibou2/Sciences
74ef032a1f GOB: Add o7_getFreeDiskSpace opcode
52a8822d38 GOB: Fix o7_draw0x0C opcode
ed0d7f1dfd GOB: Display script function names in log when available
5ad56b545c GOB: Handle special size 0 in read/write opcodes (save all variables)
5de0026c00 GOB: Handle persistent INI files, stored as save games
09d90c3cea GOB: Distinguish CD paths from normal ones when using "copyFile" opcodes
66d52b2ea4 GOB: Do not use "flat directory" logic with patterns in o7_findFile
7c3d4237ca GOB: Fix a possible crash when installing a new Adibou2 application
19ac4e0713 GOB: Add some Adibou2/Sciences save files
9e4abc326a GOB: Set return code variable in o7_setINIValue
39ea8a2537 GOB: Fix maximum length of strings written into game variables
7e8f753fae GOB: Debugging traces for getting/setting .INI files values
649eea6d14 GOB: Fix a crash in Adibou2/Anglais
d1fe0614e4 GOB: Another stub for Adibou2/Anglais
6b4792b8c8 GOB: Use Image::IcoCurDecoder to read cursors
a5ae48518c GOB: Formatting fixes
53b3c90de1 GOB: Make printExpr compatible with newer script constructs
720b682f6c GOB: Implement o7_getFileInfo (Adibou2/Anglais)
afa6439144 GOB: Implement new database opcodes (Adibou2/Anglais)
f0a92e7579 GOB: Fix "no wait sound" condition in Adibou2/Sciences videos
7e3ec46082 GOB: Register save files for Adibou2/Anglais and Adibou2/Musique
2e772ee929 GOB: Add more Adibou/Musique stubs
093fe6f560 GOB: Handle BMP and JPEG image types (Adibou2)
95ca8e315b IMAGE: Add decoder for BRC format
018fffa213 GOB: Handle BRC image type (Adibou2/Sciences)
17e5701023 GOB: Add some opcode stubs for Adibou2/Sciences
a301971ede GOB: Implement o7_ansiToOEM and o7_oemToANSI (Adibou2/Sciences)
386b07e3e4 GOB: Fix OEM/ANSI encoding conversions (Adibou2/Sciences)
cdee55612d GOB: Fix incorrect text or shapes color in high color mode
fc92d000ac GOB: Implement HTML parsing opcodes (Adibou2/Sciences)
15d517ea73 GOB: Fix string buffer length in o7_printText (Adibou2)
699dba6fbf GOB: Do not narrow Expression value type from int32 to int16 too early
7bbf39d791 GOB: Trim keys before reading/writing INI values (Adibou/Sciences)
410f9b888b GOB: Do CLUT->High color conversion on a per-surface basis
1bcada3ddd GOB: Fixes in the "replace color" mode of o7_fillRect (Adibou2/Sciences)
dcf1ff66d5 GOB: o6_removeHotspot must ignore _shouldPush flag
406de77c82 GOB: Use proper kMouseButtons constants in videoplayer.cpp
39b1a20955 GOB: hotspot "leave" callback can point to another script (Adibou2/Sciences)
25ea01708f GOB: Add a missing condition to reset current hotspot (Adibou2/Sciences)
10b3d63c09 GOB: Fix in "replace color mode" of o7_fillRect
8f4d9d64f0 GOB: Fix laggy videos when waiting in o1_keyFunc()
c37e9dc0e1 GOB: Workaround a script bug causing laggy videos in Adibou2/Sciences
6e37e795bd GOB: Fix a bug in Surface::loadImage (Adibou2/Sciences)
73de1d6285 GOB: Another fix related to video slots (Adibou2/Sciences)
c1a87995b4 GOB: Fix a bug in o6_assign when setting multiple strings in a row
f489a1eb84 GOB: Add week day in Inter::renewTimeInVars()
a85a6bfbfa IMAGE: Move BRC decoder to the Gob engine
f3c1d3809a IMAGE: Mention Gob engine in the usage list of TGA decoder
032bb05ff9 GOB: Misc code style and formatting fixes (Adibou2/Sciences)
e90e8b543c GOB: Revert allowing TOT files without resources, except for Adibou2
c36a77da4f GOB: BRC decoder must always use RGB555 format, not the engine one
1d92501129 GOB: getImageInfo() can now handle BRC images
38efd60279 GOB: Split HTML parser classes into a new file (Adibou2/Sciences)
a453821464 GOB: Make the header size explicit when reading dBase MDX index files
03be95faca GOB: Indentation fix
c0ddd03a10 GOB: Simplify cursor handling in Adibou2/Sciences
9da751b597 GOB: Fix conditions for format conversion in loadImage() (Adibou2/Sciences)
ff56cb9c4e GOB: Fix image transparency issues for some pixel formats (Adibou2/Sciences)
c74340d08d GOB: Use the backend's preferred pixel format in high color mode (Adibou2/Sciences)
983ab241dc GOB: Do CLUT->high color conversions for videos on the engine side (Adibou2/Sciences)
45cdcf63d1 VIDEO: Revert "Add support for blitting paletted VMD to high-color destinations"
3679535d7d GOB: Fix a CLUT->High color conversion issue
3e0f26907c GOB: Increase the (fake) amount of memory returned by o7_getSystemProperty("TotalPhys")
9a2de05034 GOB: Fix another missing condition for closing videos (Adibou2/Sciences)
ab1dad0e8f GOB: Code formatting fixes
1108d5d86d GOB: Add a blitShaded() function (Adibou2/Sciences)
d0b1895ded GOB: A few more code style improvements (Adibou2/Sciences)
8ed3092474 GOB: Simplify loadImage() when the pixel format has no alpha bits (Adibou2/Sciences)


Commit: d4f1cb93c1b04e9abd66cc7898bcfbfc39734cae
    https://github.com/scummvm/scummvm/commit/d4f1cb93c1b04e9abd66cc7898bcfbfc39734cae
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: non English characters must be allowed in INI files

Non ASCII characters such as "éèàù" are often found in them.

Changed paths:
    engines/gob/iniconfig.cpp


diff --git a/engines/gob/iniconfig.cpp b/engines/gob/iniconfig.cpp
index d4c361a0a11..416bcf2f1f4 100644
--- a/engines/gob/iniconfig.cpp
+++ b/engines/gob/iniconfig.cpp
@@ -79,6 +79,7 @@ bool INIConfig::getConfig(const Common::String &file, Config &config) {
 
 bool INIConfig::openConfig(const Common::String &file, Config &config) {
 	config.config  = new Common::INIFile();
+	config.config->allowNonEnglishCharacters();
 	config.created = false;
 
 	// GOB uses \ as a path separator but
@@ -97,6 +98,7 @@ bool INIConfig::openConfig(const Common::String &file, Config &config) {
 
 bool INIConfig::createConfig(const Common::String &file, Config &config) {
 	config.config  = new Common::INIFile();
+	config.config->allowNonEnglishCharacters();
 	config.created = true;
 
 	_configs.setVal(file, config);


Commit: 692dc33a85fcf2ecfaaf2c30aa71c94e383a2aea
    https://github.com/scummvm/scummvm/commit/692dc33a85fcf2ecfaaf2c30aa71c94e383a2aea
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: implement o7_openItk (Adibou2)

Same as base version, except it writes "openItk" success status in a game variable.

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 71f15d41e35..47c734d469d 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -717,6 +717,7 @@ protected:
 	void o7_copyFile();
 	void o7_deleteFile();
 	void o7_playVmdOrMusic();
+	void o7_openItk();
 	void o7_initScreen();
 	void o7_setActiveCD();
 	void o7_findFile();
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 9eda38cb06f..a72bd2b7f53 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -80,6 +80,7 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0x62, o7_moveFile);
 	OPCODEDRAW(0x80, o7_initScreen);
 	OPCODEDRAW(0x83, o7_playVmdOrMusic);
+	OPCODEDRAW(0x85, o7_openItk);
 	OPCODEDRAW(0x89, o7_setActiveCD);
 	OPCODEDRAW(0x8A, o7_findFile);
 	OPCODEDRAW(0x8B, o7_findNextFile);
@@ -744,6 +745,15 @@ void Inter_v7::o7_setActiveCD() {
 	storeValue(0);
 }
 
+void Inter_v7::o7_openItk() {
+	Common::String file = getFile(_vm->_game->_script->evalString());
+	if (!file.contains('.'))
+		file += ".ITK";
+
+	bool openSuccess = _vm->_dataIO->openArchive(file, false);
+	WRITE_VAR_OFFSET(108, openSuccess);
+}
+
 void Inter_v7::o7_findFile() {
 	Common::Path file_pattern(getFile(_vm->_game->_script->evalString()));
 	Common::ArchiveMemberList files;


Commit: a09ffa3c8d016abb814977cd51bdd58984061c32
    https://github.com/scummvm/scummvm/commit/a09ffa3c8d016abb814977cd51bdd58984061c32
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Adibou2 needs to handle functions indexes > 32767 in playTot

uint16 is not enough as the negative range is already used for some special values.

Changed paths:
    engines/gob/game.cpp
    engines/gob/game.h


diff --git a/engines/gob/game.cpp b/engines/gob/game.cpp
index b5ae654d1cd..02884a5f5db 100644
--- a/engines/gob/game.cpp
+++ b/engines/gob/game.cpp
@@ -518,7 +518,7 @@ void Game::prepareStart() {
 	_startTimeKey = _vm->_util->getTimeKey();
 }
 
-void Game::playTot(int16 function) {
+void Game::playTot(int32 function) {
 	int16 *oldNestLevel      = _vm->_inter->_nestLevel;
 	int16 *oldBreakFrom      = _vm->_inter->_breakFromLevel;
 	int16 *oldCaptureCounter = _vm->_scenery->_pCaptureCounter;
diff --git a/engines/gob/game.h b/engines/gob/game.h
index 061096d498a..036f05bdbc9 100644
--- a/engines/gob/game.h
+++ b/engines/gob/game.h
@@ -159,7 +159,7 @@ public:
 
 	void prepareStart();
 
-	void playTot(int16 function);
+	void playTot(int32 function);
 
 	void capturePush(int16 left, int16 top, int16 width, int16 height);
 	void capturePop(char doDraw);


Commit: af7d72a18d11b73742f57a1b123cd36d3242bdf6
    https://github.com/scummvm/scummvm/commit/af7d72a18d11b73742f57a1b123cd36d3242bdf6
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: loadTot() should not fail on TOT files without resources

Some "library" TOT files used in Adibou2 have no embed resources, nor external ones.

Changed paths:
    engines/gob/resources.cpp


diff --git a/engines/gob/resources.cpp b/engines/gob/resources.cpp
index c9e8cfb19f6..4c38c01c4d3 100644
--- a/engines/gob/resources.cpp
+++ b/engines/gob/resources.cpp
@@ -177,7 +177,7 @@ bool Resources::load(const Common::String &fileName) {
 	}
 
 	if (!hasTOTRes && !hasEXTRes)
-		return false;
+		return true;
 
 	if (!loadTOTTextTable(_fileBase)) {
 		unload();


Commit: 91a85c1b6b9131e5e6632f0791f2edd34235f1df
    https://github.com/scummvm/scummvm/commit/91a85c1b6b9131e5e6632f0791f2edd34235f1df
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: int16->uint16 fix in o1_strToLong

Changed paths:
    engines/gob/inter_v1.cpp


diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index 950d896ed7d..ec23fdd60dc 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -1642,15 +1642,12 @@ void Inter_v1::o1_drawLine(OpFuncParams &params) {
 
 void Inter_v1::o1_strToLong(OpFuncParams &params) {
 	char str[20];
-	int16 strVar;
-	int16 destVar;
-	int32 res;
 
-	strVar = _vm->_game->_script->readVarIndex();
+	uint16 strVar = _vm->_game->_script->readVarIndex();
 	Common::strlcpy(str, GET_VARO_STR(strVar), 20);
-	res = atoi(str);
+	int32 res = atoi(str);
 
-	destVar = _vm->_game->_script->readVarIndex();
+	uint16 destVar = _vm->_game->_script->readVarIndex();
 	WRITE_VAR_OFFSET(destVar, res);
 }
 


Commit: a4d4d3c27dc02f122dd576f84ebf48a1ccb0879c
    https://github.com/scummvm/scummvm/commit/a4d4d3c27dc02f122dd576f84ebf48a1ccb0879c
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
VIDEO: Add support for blitting paletted VMD to high-color destinations

Needed for Adibou2/Sciences, which uses both paletted and high-color sources.

Changed paths:
    video/coktel_decoder.cpp
    video/coktel_decoder.h


diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index ea87db5a1f7..99c08b0ec55 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -58,7 +58,7 @@ CoktelDecoder::State::State() : flags(0), speechId(0) {
 CoktelDecoder::CoktelDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) :
 	_mixer(mixer), _soundType(soundType), _width(0), _height(0), _x(0), _y(0),
 	_defaultX(0), _defaultY(0), _features(0), _frameCount(0), _palette(256), _paletteDirty(false),
-	_isDouble(false), _ownSurface(true), _frameRate(12), _hasSound(false),
+	_highColorMap(nullptr), _isDouble(false), _ownSurface(true), _frameRate(12), _hasSound(false),
 	_soundEnabled(false), _soundStage(kSoundNone), _audioStream(0), _startTime(0),
 	_pauseStartTime(0), _isPaused(false) {
 
@@ -66,6 +66,7 @@ CoktelDecoder::CoktelDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundT
 }
 
 CoktelDecoder::~CoktelDecoder() {
+	delete[] _highColorMap;
 }
 
 bool CoktelDecoder::evaluateSeekFrame(int32 &frame, int whence) const {
@@ -107,6 +108,30 @@ void CoktelDecoder::setSurfaceMemory(void *mem, uint16 width, uint16 height, uin
 	_ownSurface = false;
 }
 
+void CoktelDecoder::setSurfaceMemoryPalettedToHighColor(void *mem,
+														uint16 width,
+														uint16 height,
+														Graphics::PixelFormat format,
+														uint32 *highColorMap) {
+	freeSurface();
+
+	if (!hasVideo())
+		return;
+
+	// Sanity checks
+	assert((width > 0) && (height > 0));
+	assert(isPaletted() && format.bytesPerPixel > 1);
+
+	_tmpSurfBppConversion.create(width, height, getPixelFormat());
+	delete[] _highColorMap;
+	_highColorMap = highColorMap;
+
+	// Create a surface over this memory
+	_surface.init(width, height, width * format.bytesPerPixel, mem, format);
+
+	_ownSurface = false;
+}
+
 void CoktelDecoder::setSurfaceMemory() {
 	freeSurface();
 	createSurface();
@@ -317,6 +342,10 @@ const byte *CoktelDecoder::getPalette() {
 	return _palette.data();
 }
 
+const uint32 *CoktelDecoder::getHighColorMap() {
+	return _highColorMap;
+}
+
 bool CoktelDecoder::hasDirtyPalette() const {
 	return (_features & kFeaturesPalette) && _paletteDirty;
 }
@@ -2496,12 +2525,14 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
 
 	uint8 type = *dataPtr++;
 
+	bool palettedToHighColor = _surface.format.bytesPerPixel != _bytesPerPixel && _bytesPerPixel == 1;
+
 	if (type & 0x80) {
 		// Frame data is compressed
 
 		type &= 0x7F;
 
-		if ((type == 2) && (rect.width() == _surface.w) && (_x == 0) && (_blitMode == 0)) {
+		if ((type == 2) && (rect.width() == _surface.w) && (_x == 0) && (_blitMode == 0) && !palettedToHighColor) {
 			// Directly uncompress onto the video surface
 			const int offsetX = rect.left * _surface.format.bytesPerPixel;
 			const int offsetY = rect.top * _surface.pitch;
@@ -2529,6 +2560,10 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
 		surface = &_8bppSurface[2];
 	}
 
+	if (palettedToHighColor) {
+		surface = &_tmpSurfBppConversion;
+	}
+
 	// Evaluate the block type
 	if      (type == 0x01) {
 		if (_isDouble)
@@ -2549,7 +2584,10 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
 	else
 		renderBlockSparse2Y(*surface, dataPtr, *blockRect);
 
-	if (_blitMode > 0) {
+	if (palettedToHighColor) {
+		blitPalettedToHighColor(*surface, *blockRect);
+	}
+	else if (_blitMode > 0) {
 		if      (_bytesPerPixel == 2)
 			blit16(*surface, *blockRect);
 		else if (_bytesPerPixel == 3)
@@ -2612,6 +2650,22 @@ bool VMDDecoder::getRenderRects(const Common::Rect &rect,
 	return true;
 }
 
+void VMDDecoder::blitPalettedToHighColor(const Graphics::Surface &srcSurf, Common::Rect &rect) {
+	rect = Common::Rect(rect.left, rect.top, rect.right, rect.bottom);
+
+	rect.clip(_surface.w, _surface.h);
+
+	assert(isPaletted());
+	assert(_highColorMap != nullptr);
+
+	for (int x = rect.left; x < rect.left + rect.width(); x++) {
+		for (int y = rect.top; y < rect.top + rect.height(); y++) {
+			uint32 color = srcSurf.getPixel(x, y);
+			_surface.setPixel(x, y, _highColorMap[color]);
+		}
+	}
+}
+
 void VMDDecoder::blit16(const Graphics::Surface &srcSurf, Common::Rect &rect) {
 	rect = Common::Rect(rect.left / 2, rect.top, rect.right / 2, rect.bottom);
 
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 367753b8d64..88f7e48e4f3 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -95,6 +95,14 @@ public:
 
 	/** Draw directly onto the specified video memory. */
 	void setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp);
+
+	/** Draw directly onto the specified video memory, with a CLUT to high color conversion. */
+	void setSurfaceMemoryPalettedToHighColor(void *mem,
+											 uint16 width,
+											 uint16 height,
+											 Graphics::PixelFormat format,
+											 uint32 *highColorMap);
+
 	/** Reset the video memory. */
 	void setSurfaceMemory();
 
@@ -192,6 +200,7 @@ public:
 
 	const byte *getPalette();
 	bool  hasDirtyPalette() const;
+	const uint32 *getHighColorMap();
 
 	uint32 getTimeToNextFrame() const;
 	uint32 getStaticTimeToNextFrame() const;
@@ -240,10 +249,13 @@ protected:
 	Graphics::Palette _palette;
 	bool _paletteDirty;
 
+	uint32 *_highColorMap;
+
 	bool _isDouble;
 
 	bool    _ownSurface;
 	Graphics::Surface _surface;
+	Graphics::Surface _tmpSurfBppConversion;
 
 	Common::List<Common::Rect> _dirtyRects;
 
@@ -570,6 +582,7 @@ private:
 	bool renderFrame(Common::Rect &rect);
 	bool getRenderRects(const Common::Rect &rect,
 			Common::Rect &realRect, Common::Rect &fakeRect);
+	void blitPalettedToHighColor(const Graphics::Surface &srcSurf, Common::Rect &rect);
 	void blit16(const Graphics::Surface &srcSurf, Common::Rect &rect);
 	void blit24(const Graphics::Surface &srcSurf, Common::Rect &rect);
 


Commit: e1ce819b02c7c62ff85c06bbe1cab5e70bada6d2
    https://github.com/scummvm/scummvm/commit/e1ce819b02c7c62ff85c06bbe1cab5e70bada6d2
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: High color support in Adibou2

Needed for the "Sciences" extension, which mixes paletted and high-color
sources

Paletted surfaces now also keeps up to date a high-color map, mapping
palette index to high color uint32 values according to current pixel
format.

Changed paths:
    engines/gob/draw.cpp
    engines/gob/draw.h
    engines/gob/draw_v7.cpp
    engines/gob/gob.cpp
    engines/gob/gob.h
    engines/gob/inter_v1.cpp
    engines/gob/inter_v7.cpp
    engines/gob/surface.cpp
    engines/gob/surface.h
    engines/gob/video.cpp
    engines/gob/video.h
    engines/gob/video_v2.cpp
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/draw.cpp b/engines/gob/draw.cpp
index 2cff9c08e20..4180f163796 100644
--- a/engines/gob/draw.cpp
+++ b/engines/gob/draw.cpp
@@ -330,9 +330,9 @@ void Draw::dirtiedRect(SurfacePtr surface,
 }
 
 void Draw::initSpriteSurf(int16 index, int16 width, int16 height,
-		int16 flags) {
+						  int16 flags, byte bpp) {
 
-	_spritesArray[index] = _vm->_video->initSurfDesc(width, height, flags);
+	_spritesArray[index] = _vm->_video->initSurfDesc(width, height, flags, bpp);
 	_spritesArray[index]->clear();
 }
 
diff --git a/engines/gob/draw.h b/engines/gob/draw.h
index e61da565222..b0c3e54cd71 100644
--- a/engines/gob/draw.h
+++ b/engines/gob/draw.h
@@ -197,7 +197,7 @@ public:
 	void dirtiedRect(int16 surface, int16 left, int16 top, int16 right, int16 bottom);
 	void dirtiedRect(SurfacePtr surface, int16 left, int16 top, int16 right, int16 bottom);
 
-	void initSpriteSurf(int16 index, int16 width, int16 height, int16 flags);
+	void initSpriteSurf(int16 index, int16 width, int16 height, int16 flags, byte bpp = 0);
 	void freeSprite(int16 index);
 	void adjustCoords(char adjust, int16 *coord1, int16 *coord2);
 	void adjustCoords(char adjust, uint16 *coord1, uint16 *coord2) {
diff --git a/engines/gob/draw_v7.cpp b/engines/gob/draw_v7.cpp
index 339195990c0..d28d29ba06f 100644
--- a/engines/gob/draw_v7.cpp
+++ b/engines/gob/draw_v7.cpp
@@ -27,6 +27,7 @@
 
 #include "common/formats/winexe_ne.h"
 #include "common/formats/winexe_pe.h"
+#include "graphics/blit.h"
 #include "graphics/cursorman.h"
 #include "graphics/wincursor.h"
 
@@ -94,22 +95,63 @@ bool Draw_v7::loadCursorFromFile(Common::String cursorName) {
 
 	_scummvmCursor->clear();
 
-	Surface cursorSurf(cursor->getWidth(), cursor->getHeight(), 1, cursor->getSurface());
-	_scummvmCursor->blit(cursorSurf, 0, 0);
+	uint32 keyColor = cursor->getKeyColor();
+	if (_scummvmCursor->getBPP() == 1) {
+		Graphics::copyBlit(_scummvmCursor->getData(), cursor->getSurface(),
+						   _scummvmCursor->getWidth() *  _scummvmCursor->getBPP(),
+						   cursor->getWidth(),
+						   cursor->getWidth(),
+						   cursor->getHeight(),
+						   1);
+	} else {
+		uint32 map[256];
+		convertPaletteToMap(map + cursor->getPaletteStartIndex(), cursor->getPalette(), cursor->getPaletteCount(), _vm->getPixelFormat());
+		if (!cursor->getMask()) {
+			// Look for a RGB color in [0, 256] not in the map and use it as the new key color.
+			// At least one of this 257 colors is guaranteed to be free, since the map size is 256.
+			bool colorPresent[257] = {false};
+			for (uint32 i = cursor->getPaletteStartIndex();
+				 i < cursor->getPaletteStartIndex() + cursor->getPaletteCount();
+				 ++i) {
+				uint32 color = map[i];
+				if (color <= 256)
+					colorPresent[color] = true;
+			}
+
+			for (keyColor = 0; keyColor <= 256; ++keyColor) {
+				if (!colorPresent[keyColor])
+					break;
+			}
+		}
+
+		map[cursor->getKeyColor()] = keyColor;
+
+		Graphics::crossBlitMap(_scummvmCursor->getData(), cursor->getSurface(),
+							   _scummvmCursor->getWidth() * _scummvmCursor->getBPP(),
+							   cursor->getWidth(),
+							   cursor->getWidth(),
+							   cursor->getHeight(),
+							   _scummvmCursor->getBPP(),
+							   map);
+	}
 
 	CursorMan.replaceCursor(_scummvmCursor->getData(),
 							cursor->getWidth(),
 							cursor->getHeight(),
 							cursor->getHotspotX(),
 							cursor->getHotspotY(),
-							cursor->getKeyColor(),
+							keyColor,
 							false,
 							&_vm->getPixelFormat(),
 							cursor->getMask());
-	CursorMan.replaceCursorPalette(cursor->getPalette(),
-								   cursor->getPaletteStartIndex(),
-								   cursor->getPaletteCount());
-	CursorMan.disableCursorPalette(false);
+	if (_scummvmCursor->getBPP() != 1)
+		CursorMan.disableCursorPalette(true);
+	else {
+		CursorMan.replaceCursorPalette(cursor->getPalette(),
+									   cursor->getPaletteStartIndex(),
+									   cursor->getPaletteCount());
+		CursorMan.disableCursorPalette(false);
+	}
 
 	delete cursorGroup;
 	delete defaultCursor;
diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp
index 4debdc80e65..6d4b0013ba1 100644
--- a/engines/gob/gob.cpp
+++ b/engines/gob/gob.cpp
@@ -268,21 +268,16 @@ const Graphics::PixelFormat &GobEngine::getPixelFormat() const {
 	return _pixelFormat;
 }
 
-void GobEngine::setTrueColor(bool trueColor) {
+void GobEngine::setTrueColor(bool trueColor, bool convertAllSurfaces, Graphics::PixelFormat *trueColorFormat) {
 	if (isTrueColor() == trueColor)
 		return;
 
 	_features = (_features & ~kFeaturesTrueColor) | (trueColor ? kFeaturesTrueColor : 0);
 
-	_video->setSize();
+	_video->setSize(trueColorFormat);
 
 	_pixelFormat = g_system->getScreenFormat();
 
-	Common::Array<SurfacePtr>::iterator surf;
-	for (surf = _draw->_spritesArray.begin(); surf != _draw->_spritesArray.end(); ++surf)
-		if (*surf)
-			(*surf)->setBPP(_pixelFormat.bytesPerPixel);
-
 	if (_draw->_backSurface)
 		_draw->_backSurface->setBPP(_pixelFormat.bytesPerPixel);
 	if (_draw->_frontSurface)
@@ -293,7 +288,13 @@ void GobEngine::setTrueColor(bool trueColor) {
 		_draw->_cursorSpritesBack->setBPP(_pixelFormat.bytesPerPixel);
 	if (_draw->_scummvmCursor)
 		_draw->_scummvmCursor->setBPP(_pixelFormat.bytesPerPixel);
-	SurfacePtr _scummvmCursor;
+
+	if (convertAllSurfaces) {
+		Common::Array<SurfacePtr>::iterator surf;
+		for (surf = _draw->_spritesArray.begin(); surf != _draw->_spritesArray.end(); ++surf)
+			if (*surf)
+				(*surf)->setBPP(_pixelFormat.bytesPerPixel);
+	}
 }
 
 Common::Error GobEngine::run() {
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index c1add27b54e..29a862e2d42 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -250,8 +250,7 @@ public:
 	bool hasResourceSizeWorkaround() const;
 
 	bool isCurrentTot(const Common::String &tot) const;
-
-	void setTrueColor(bool trueColor);
+	void setTrueColor(bool trueColor, bool convertAllSurfaces, Graphics::PixelFormat *format = nullptr);
 
 	const Graphics::PixelFormat &getPixelFormat() const;
 
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index ec23fdd60dc..bf793e685c8 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -1542,7 +1542,8 @@ void Inter_v1::o1_createSprite(OpFuncParams &params) {
 
 	_vm->_draw->adjustCoords(0, &width, &height);
 	flag = _vm->_game->_script->readInt16();
-	_vm->_draw->initSpriteSurf(index, width, height, flag ? 2 : 0);
+	byte bpp = (flag & 0x200) ? 1 : _vm->_pixelFormat.bytesPerPixel;
+	_vm->_draw->initSpriteSurf(index, width, height, flag ? 2 : 0, bpp);
 }
 
 void Inter_v1::o1_freeSprite(OpFuncParams &params) {
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index a72bd2b7f53..6e126450e1c 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -477,15 +477,19 @@ void Inter_v7::o7_moveFile() {
 
 
 void Inter_v7::o7_initScreen() {
-	// TODO: continue implementation
-	int16 offY;
 	int16 videoMode;
 	int16 width, height;
 
-	offY = _vm->_game->_script->readInt16();
+	videoMode = _vm->_game->_script->readInt16();
 
-	videoMode = offY & 0xFF;
-	offY = (offY >> 8) & 0xFF;
+	if (videoMode == 32000 || videoMode == 256) {
+		Graphics::PixelFormat format = Graphics::createPixelFormat<555>();
+		bool trueColor = videoMode == 32000;
+		_vm->setTrueColor(trueColor, false, &format);
+	}
+
+	videoMode &= 0xFF;
+	int16 offY = 0;
 
 	width = _vm->_game->_script->readValExpr();
 	height = _vm->_game->_script->readValExpr();
@@ -870,15 +874,13 @@ void Inter_v7::o7_loadImage() {
 		return;
 	}
 
-	SurfacePtr image = _vm->_video->initSurfDesc(1, 1);
-	if (!image->loadImage(*imageFile)) {
+	int16 right  = left + width  - 1;
+	int16 bottom = top  + height - 1;
+
+	if (!destSprite->loadImage(*imageFile, left, top, right, bottom, x, y, transp, _vm->getPixelFormat())) {
 		warning("o7_loadImage(): Failed to load image \"%s\"", file.c_str());
 		return;
 	}
-
-	int16 right  = left + width  - 1;
-	int16 bottom = top  + height - 1;
-	destSprite->blit(*image, left, top, right, bottom, x, y, (transp == 0) ? -1 : 0);
 }
 
 void Inter_v7::o7_setVolume() {
@@ -1140,7 +1142,8 @@ void Inter_v7::o7_fillRect(OpFuncParams &params) {
 			// if (_vm->_draw->_spritesArray[_vm->_draw->_destSurface].field_10  & 0x80)) {
 			SurfacePtr newSurface = _vm->_video->initSurfDesc(_vm->_draw->_spriteRight,
 															  _vm->_draw->_spriteBottom,
-															  8);
+															  8,
+															  _vm->_draw->_spritesArray[_vm->_draw->_destSurface]->getBPP());
 			newSurface->blit(*_vm->_draw->_spritesArray[_vm->_draw->_destSurface],
 							 _vm->_draw->_destSpriteX,
 							 _vm->_draw->_destSpriteY,
diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 7f2d1e99844..ce4786c99ec 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -34,11 +34,13 @@
 #include "common/textconsole.h"
 #include "common/stack.h"
 
+#include "graphics/palette.h"
 #include "graphics/primitives.h"
 #include "graphics/pixelformat.h"
 #include "graphics/surface.h"
 
 #include "image/iff.h"
+#include "image/tga.h"
 
 namespace Gob {
 
@@ -202,8 +204,10 @@ bool ConstPixel::isValid() const {
 }
 
 
-Surface::Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem) :
-	_width(width), _height(height), _bpp(bpp), _vidMem(vidMem) {
+Surface::Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem, const uint32 *highColorMap, bool ownHighColorMap) :
+	_width(width), _height(height), _bpp(bpp), _vidMem(vidMem),
+	_ownHighColorMap(ownHighColorMap),
+	_highColorMap(highColorMap)  {
 
 	assert((_width > 0) && (_height > 0));
 	assert((_bpp == 1) || (_bpp == 2) || (_bpp == 4));
@@ -215,8 +219,10 @@ Surface::Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem) :
 		_ownVidMem = false;
 }
 
-Surface::Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem) :
-	_width(width), _height(height), _bpp(bpp), _vidMem(nullptr) {
+Surface::Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem, const uint32 *highColorMap, bool ownHighColorMap) :
+	_width(width), _height(height), _bpp(bpp), _vidMem(nullptr),
+	_ownHighColorMap(ownHighColorMap),
+	_highColorMap(highColorMap) {
 
 	assert((_width > 0) && (_height > 0));
 	assert((_bpp == 1) || (_bpp == 2) || (_bpp == 4));
@@ -230,6 +236,9 @@ Surface::Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem) :
 Surface::~Surface() {
 	if (_ownVidMem)
 		delete[] _vidMem;
+
+	if (_ownHighColorMap)
+		delete[] _highColorMap;
 }
 
 uint16 Surface::getWidth() const {
@@ -349,7 +358,7 @@ void Surface::blit(const Surface &from, int16 left, int16 top, int16 right, int1
 		int16 x, int16 y, int32 transp, bool yAxisReflection) {
 
 	// Color depths have to fit
-	assert(_bpp == from._bpp);
+	assert(_bpp == from._bpp || (from._bpp == 1 && from._highColorMap != nullptr));
 
 	// Clip
 	if (!clipBlitRect(left, top, right, bottom, x, y, _width, _height, from._width, from._height))
@@ -363,7 +372,7 @@ void Surface::blit(const Surface &from, int16 left, int16 top, int16 right, int1
 		// Nothing to do
 		return;
 
-	if ((left == 0) && (_width == from._width) && (_width == width) && (transp == -1) && !yAxisReflection) {
+	if ((left == 0) && (_width == from._width) && (_width == width) && (transp == -1) && !yAxisReflection && (from._bpp == _bpp)) {
 		// If these conditions are met, we can directly use memmove
 
 		// Pointers to the blit destination and source start points
@@ -374,7 +383,7 @@ void Surface::blit(const Surface &from, int16 left, int16 top, int16 right, int1
 		return;
 	}
 
-	if (transp == -1 && !yAxisReflection) {
+	if (transp == -1 && !yAxisReflection && from._bpp == _bpp && _bpp == 1) {
 		// We don't have to look for transparency => we can use memmove line-wise
 
 		// Pointers to the blit destination and source start points
@@ -403,14 +412,27 @@ void Surface::blit(const Surface &from, int16 left, int16 top, int16 right, int1
 
 		if (yAxisReflection) {
 			srcRow += width - 1;
-			for (uint16 i = 0; i < width; i++, ++dstRow, --srcRow)
-				if (srcRow.get() != ((uint32) transp))
-					dstRow.set(srcRow.get());
-		}
-		else {
-			for (uint16 i = 0; i < width; i++, ++dstRow, ++srcRow)
-				if (srcRow.get() != ((uint32) transp))
-					dstRow.set(srcRow.get());
+			for (uint16 i = 0; i < width; i++, ++dstRow, --srcRow) {
+				if (srcRow.get() != ((uint32) transp)) {
+					if (_bpp == from._bpp)
+						dstRow.set(srcRow.get());
+					else {
+						uint32 index = srcRow.get();
+						dstRow.set(from._highColorMap[index]);
+					}
+				}
+			}
+		} else {
+			for (uint16 i = 0; i < width; i++, ++dstRow, ++srcRow) {
+				if (srcRow.get() != ((uint32) transp)) {
+					if (_bpp == from._bpp)
+						dstRow.set(srcRow.get());
+					else {
+						uint32 index = srcRow.get();
+						dstRow.set(from._highColorMap[index]);
+					}
+				}
+			}
 		}
 
 		dst +=      _width;
@@ -854,29 +876,31 @@ void Surface::blitToScreen(uint16 left, uint16 top, uint16 right, uint16 bottom,
 	g_system->copyRectToScreen(src, _width * _bpp, x, y, width, height);
 }
 
-bool Surface::loadImage(Common::SeekableReadStream &stream) {
+bool Surface::loadImage(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom,
+						int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	ImageType type = identifyImage(stream);
 	if (type == kImageTypeNone)
 		return false;
 
-	return loadImage(stream, type);
+	return loadImage(stream, type, left, top, right, bottom, x, y, transp, format);
 }
 
-bool Surface::loadImage(Common::SeekableReadStream &stream, ImageType type) {
+bool Surface::loadImage(Common::SeekableReadStream &stream, ImageType type, int16 left, int16 top, int16 right, int16 bottom,
+						int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	if (type == kImageTypeNone)
 		return false;
 
 	switch (type) {
 	case kImageTypeTGA:
-		return loadTGA(stream);
+		return loadTGA(stream, left, top, right, bottom, x, y, transp, format);
 	case kImageTypeIFF:
-		return loadIFF(stream);
+		return loadIFF(stream, left, top, right, bottom, x, y, transp, format);
 	case kImageTypeBRC:
-		return loadBRC(stream);
+		return loadBRC(stream, left, top, right, bottom, x, y, transp, format);
 	case kImageTypeBMP:
-		return loadBMP(stream);
+		return loadBMP(stream, left, top, right, bottom, x, y, transp, format);
 	case kImageTypeJPEG:
-		return loadJPEG(stream);
+		return loadJPEG(stream, left, top, right, bottom, x, y, transp, format);
 
 	default:
 		warning("Surface::loadImage(): Unknown image type: %d", (int)type);
@@ -926,38 +950,102 @@ ImageType Surface::identifyImage(Common::SeekableReadStream &stream) {
 	return kImageTypeTGA;
 }
 
-
-bool Surface::loadTGA(Common::SeekableReadStream &stream) {
-	warning("TODO: Surface::loadTGA()");
-	return false;
-}
-
-bool Surface::loadIFF(Common::SeekableReadStream &stream) {
-	Image::IFFDecoder decoder;
+bool Surface::loadImage(Image::ImageDecoder &decoder, Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom,
+						int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	decoder.loadStream(stream);
 
 	if (!decoder.getSurface())
 		return false;
 
-	resize(decoder.getSurface()->w, decoder.getSurface()->h);
-	memcpy(_vidMem, decoder.getSurface()->getPixels(), decoder.getSurface()->w * decoder.getSurface()->h);
+	const Graphics::Surface *st = decoder.getSurface();
+	bool needConversion = decoder.getSurface()->format.bpp() > 1 && decoder.getSurface()->format != format;
+	if (needConversion)
+		st = st->convertTo(format);
+
+	uint32 *colorMap = nullptr;
+	if (format.bytesPerPixel > 1) {
+		colorMap = new uint32[256];
+		computeHighColorMap(colorMap, decoder.getPalette().data(), format, true);
+	}
+
+	const Surface src(st->w, st->h, st->format.bytesPerPixel,
+					  static_cast<const byte *>(st->getPixels()),
+					  colorMap);
+
+	blit(src, left, top, right, bottom, x, y, (transp == 0) ? -1 : 0);
+	if (colorMap) {
+		if (_bpp == 1) {
+			// The destination must retain the source color map for further conversions
+			_highColorMap = colorMap;
+			_ownHighColorMap = true;
+		} else {
+			delete[] colorMap;
+		}
+	}
+
+	if (needConversion)
+		delete st;
 
 	return true;
 }
 
-bool Surface::loadBRC(Common::SeekableReadStream &stream) {
+bool Surface::loadTGA(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
+	Image::TGADecoder decoder;
+	return loadImage(decoder, stream, left, top, right, bottom, x, y, transp, format);
+}
+
+bool Surface::loadIFF(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
+	Image::IFFDecoder decoder;
+	return loadImage(decoder, stream, left, top, right, bottom, x, y, transp, format);
+}
+
+bool Surface::loadBRC(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	warning("TODO: Surface::loadBRC()");
 	return false;
 }
 
-bool Surface::loadBMP(Common::SeekableReadStream &stream) {
+bool Surface::loadBMP(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	warning("TODO: Surface::loadBMP()");
 	return false;
 }
 
-bool Surface::loadJPEG(Common::SeekableReadStream &stream) {
+bool Surface::loadJPEG(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	warning("TODO: Surface::loadJPEG()");
 	return false;
 }
 
+void Surface::computeHighColorMap(uint32 *highColorMap, const byte *palette,
+								  const Graphics::PixelFormat &format,
+								  bool useSpecialBlackWhiteValues,
+								  int16 startColor, int16 colorCount,
+								  int16 startColorSrc) {
+	if (!palette)
+		return;
+
+	if (startColorSrc < 0)
+		startColorSrc = startColor;
+
+	for (int16 i = 0; i < colorCount; i++) {
+		int indexSrc = (startColorSrc + i ) * 3;
+		byte red = palette[indexSrc];
+		byte green = palette[indexSrc + 1];
+		byte blue = palette[indexSrc + 2];
+
+		int indexDest = startColor + i;
+
+		if (!useSpecialBlackWhiteValues)
+			highColorMap[indexDest] = format.RGBToColor(red, green, blue);
+		else if (i == 0) // Trick from the original engine to handle transparency with high color surfaces
+			highColorMap[indexDest] = 0; // Palette index 0 is always mapped to high color value 0, possibly interpreted as the special transparent color.
+		else if (i == 255)
+			highColorMap[indexDest] = format.RGBToColor(0xFF, 0xFF, 0xFF); // Palette index 255 is always mapped to white.
+		else if (red == 0 && green == 0 && blue == 0)
+			highColorMap[indexDest] = format.RGBToColor((1 << format.rLoss),
+														(1 << format.gLoss),
+														(1 << format.bLoss)); // Blacks at other indexes are mapped to rgb(1, 1, 1) value to prevent interpreting them as transparent.
+		else
+			highColorMap[indexDest] = format.RGBToColor(red, green, blue);
+	}
+}
+
 } // End of namespace Gob
diff --git a/engines/gob/surface.h b/engines/gob/surface.h
index 00480fd9f6b..6d66ae72c3c 100644
--- a/engines/gob/surface.h
+++ b/engines/gob/surface.h
@@ -33,6 +33,9 @@
 #include "common/rational.h"
 #include "common/rect.h"
 
+#include "image/image_decoder.h"
+#include "graphics/pixelformat.h"
+
 namespace Common {
 class SeekableReadStream;
 }
@@ -99,8 +102,8 @@ private:
 
 class Surface {
 public:
-	Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem = 0);
-	Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem);
+	Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem = nullptr, const uint32 *highColorMap = nullptr, bool ownHighColorMap = false);
+	Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem, const uint32 *highColorMap = nullptr, bool ownHighColorMap = false);
 	~Surface();
 
 	uint16 getWidth () const;
@@ -145,10 +148,17 @@ public:
 
 	void blitToScreen(uint16 left, uint16 top, uint16 right, uint16 bottom, uint16 x, uint16 y) const;
 
-	bool loadImage(Common::SeekableReadStream &stream);
-	bool loadImage(Common::SeekableReadStream &stream, ImageType type);
+	bool loadImage(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom,
+				   int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
+	bool loadImage(Common::SeekableReadStream &stream, ImageType type, int16 left, int16 top, int16 right, int16 bottom,
+				   int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
 
 	static ImageType identifyImage(Common::SeekableReadStream &stream);
+	static void computeHighColorMap(uint32 *highColorMap, const byte *palette,
+									const Graphics::PixelFormat &format,
+									bool useSpecialBlackWhiteValues,
+									int16 startColor = 0, int16 colorCount = 256,
+									int16 startColorSrc = -1);
 
 private:
 	uint16 _width;
@@ -158,14 +168,20 @@ private:
 	bool  _ownVidMem;
 	byte *_vidMem;
 
+	bool _ownHighColorMap;
+	const uint32 *_highColorMap;
+
 	static bool clipBlitRect(int16 &left, int16 &top, int16 &right, int16 &bottom, int16 &x, int16 &y,
 	                         uint16 dWidth, uint16 dHeight, uint16 sWidth, uint16 sHeight);
 
-	bool loadTGA (Common::SeekableReadStream &stream);
-	bool loadIFF (Common::SeekableReadStream &stream);
-	bool loadBRC (Common::SeekableReadStream &stream);
-	bool loadBMP (Common::SeekableReadStream &stream);
-	bool loadJPEG(Common::SeekableReadStream &stream);
+	bool loadImage(Image::ImageDecoder &decoder, Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom,
+				   int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
+
+	bool loadTGA (Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
+	bool loadIFF (Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
+	bool loadBRC (Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
+	bool loadBMP (Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
+	bool loadJPEG(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
 };
 
 typedef Common::SharedPtr<Surface> SurfacePtr;
diff --git a/engines/gob/video.cpp b/engines/gob/video.cpp
index db73abf4bd4..81a1d4c9416 100644
--- a/engines/gob/video.cpp
+++ b/engines/gob/video.cpp
@@ -218,7 +218,7 @@ void Video::initPrimary(int16 mode) {
 	}
 }
 
-SurfacePtr Video::initSurfDesc(int16 width, int16 height, int16 flags) {
+SurfacePtr Video::initSurfDesc(int16 width, int16 height, int16 flags, byte bpp) {
 	SurfacePtr descPtr;
 
 	if (flags & PRIMARY_SURFACE)
@@ -237,7 +237,11 @@ SurfacePtr Video::initSurfDesc(int16 width, int16 height, int16 flags) {
 		if (!(flags & SCUMMVM_CURSOR) && _vm->getGameType() != kGameTypeAdibou2)
 			width = (width + 7) & 0xFFF8;
 
-		descPtr = SurfacePtr(new Surface(width, height, _vm->getPixelFormat().bytesPerPixel));
+		descPtr = SurfacePtr(new Surface(width,
+										 height,
+										 bpp ? bpp : _vm->getPixelFormat().bytesPerPixel,
+										 (byte*) nullptr,
+										 _vm->_global->_pPaletteDesc->highColorMap));
 	}
 	return descPtr;
 }
@@ -246,9 +250,9 @@ void Video::clearScreen() {
 	g_system->fillScreen(0);
 }
 
-void Video::setSize() {
+void Video::setSize(Graphics::PixelFormat *trueColorFormat) {
 	if (_vm->isTrueColor())
-		initGraphics(_vm->_width, _vm->_height, nullptr);
+		initGraphics(_vm->_width, _vm->_height, trueColorFormat);
 	else
 		initGraphics(_vm->_width, _vm->_height);
 }
@@ -333,8 +337,12 @@ void Video::drawPacked(byte *sprBuf, int16 width, int16 height,
 
 		for (unsigned int i = 0; i < repeat; ++i) {
 			if (curx < dest.getWidth() && cury < dest.getHeight())
-				if (!transp || val)
-					dst.set(val);
+				if (!transp || val) {
+					if (dest.getBPP() == 1)
+						dst.set(val);
+					else
+						dst.set(_vm->_draw->getColor(val));
+				}
 
 			++dst;
 			curx++;
@@ -384,6 +392,12 @@ void Video::setPalElem(int16 index, char red, char green, char blue,
 
 	if (_vm->getPixelFormat().bytesPerPixel == 1)
 		g_system->getPaletteManager()->setPalette(pal, index, 1);
+	else
+		Surface::computeHighColorMap(_vm->_global->_pPaletteDesc->highColorMap,
+									 pal,
+									 _vm->getPixelFormat(),
+									 _vm->getGameType() == kGameTypeAdibou2,
+									 index, 1, 0);
 }
 
 void Video::setPalette(PalDesc *palDesc) {
@@ -398,6 +412,12 @@ void Video::setPalette(PalDesc *palDesc) {
 
 	if (_vm->getPixelFormat().bytesPerPixel == 1)
 		g_system->getPaletteManager()->setPalette(pal, 0, numcolors);
+	else
+		Surface::computeHighColorMap(palDesc->highColorMap,
+									 pal,
+									 _vm->getPixelFormat(),
+									 _vm->getGameType() == kGameTypeAdibou2,
+									 0, numcolors);
 }
 
 void Video::setFullPalette(PalDesc *palDesc) {
@@ -414,6 +434,11 @@ void Video::setFullPalette(PalDesc *palDesc) {
 
 		if (_vm->getPixelFormat().bytesPerPixel == 1)
 			g_system->getPaletteManager()->setPalette(pal, 0, 256);
+		else
+			Surface::computeHighColorMap(palDesc->highColorMap,
+										 pal,
+										 _vm->getPixelFormat(),
+										 _vm->getGameType() == kGameTypeAdibou2);
 	} else
 		Video::setPalette(palDesc);
 }
diff --git a/engines/gob/video.h b/engines/gob/video.h
index bd95af8a616..f42556fd8c0 100644
--- a/engines/gob/video.h
+++ b/engines/gob/video.h
@@ -93,9 +93,12 @@ public:
 
 	struct PalDesc {
 		Color *vgaPal;
+		uint32 highColorMap[256];
 		int16 *unused1;
 		int16 *unused2;
-		PalDesc() : vgaPal(0), unused1(0), unused2(0) {}
+		PalDesc() : vgaPal(nullptr), unused1(nullptr), unused2(nullptr) {
+			memset(highColorMap, 0, sizeof(highColorMap));
+		}
 	};
 
 	bool _doRangeClamp;
@@ -115,9 +118,9 @@ public:
 	int16 _screenDeltaY;
 
 	void initPrimary(int16 mode);
-	SurfacePtr initSurfDesc(int16 width, int16 height, int16 flags = 0);
+	SurfacePtr initSurfDesc(int16 width, int16 height, int16 flags = 0, byte bpp = 0);
 
-	void setSize();
+	void setSize(Graphics::PixelFormat *highColorFormat = nullptr);
 
 	void clearScreen();
 	void retrace(bool mouse = true);
diff --git a/engines/gob/video_v2.cpp b/engines/gob/video_v2.cpp
index 887294d8a77..783ac4d5c8e 100644
--- a/engines/gob/video_v2.cpp
+++ b/engines/gob/video_v2.cpp
@@ -28,7 +28,9 @@
 #include "common/endian.h"
 
 #include "gob/gob.h"
+#include "gob/global.h"
 #include "gob/video.h"
+#include "gob/draw.h"
 
 namespace Gob {
 
@@ -58,7 +60,7 @@ char Video_v2::spriteUncompressor(byte *sprBuf, int16 srcWidth, int16 srcHeight,
 		return 0;
 
 	if (sprBuf[2] == 2) {
-		Surface sourceDesc(srcWidth, srcHeight, 1, sprBuf + 3);
+		Surface sourceDesc(srcWidth, srcHeight, 1, sprBuf + 3, _vm->_global->_pPaletteDesc->highColorMap);
 		destDesc.blit(sourceDesc, 0, 0, srcWidth - 1, srcHeight - 1, x, y, (transp == 0) ? -1 : 0);
 		return 1;
 	} else if (sprBuf[2] == 1) {
@@ -97,8 +99,12 @@ char Video_v2::spriteUncompressor(byte *sprBuf, int16 srcWidth, int16 srcHeight,
 			if ((cmdVar & 1) != 0) {
 				temp = *srcPtr++;
 
-				if ((temp != 0) || (transp == 0))
-					destPtr.set(temp);
+				if ((temp != 0) || (transp == 0)) {
+					if (destDesc.getBPP() == 1)
+						destPtr.set(temp);
+					else
+						destPtr.set(_vm->_global->_pPaletteDesc->highColorMap[temp]);
+				}
 
 				++destPtr;
 				curWidth++;
@@ -131,8 +137,12 @@ char Video_v2::spriteUncompressor(byte *sprBuf, int16 srcWidth, int16 srcHeight,
 				for (counter2 = 0; counter2 < strLen; counter2++) {
 					temp = memBuffer[(offset + counter2) % 4096];
 
-					if ((temp != 0) || (transp == 0))
-						destPtr.set(temp);
+					if ((temp != 0) || (transp == 0)) {
+						if (destDesc.getBPP() == 1)
+							destPtr.set(temp);
+						else
+							destPtr.set(_vm->_global->_pPaletteDesc->highColorMap[temp]);
+					}
 
 					++destPtr;
 					curWidth++;
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 5f18b82f012..cd4314490df 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -54,7 +54,7 @@ VideoPlayer::Video::Video() : decoder(0), live(false) {
 }
 
 bool VideoPlayer::Video::isEmpty() const {
-	return decoder == 0;
+	return decoder == nullptr;
 }
 
 void VideoPlayer::Video::close() {
@@ -124,12 +124,15 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 
 		if (video->decoder->hasVideo() && !(properties.flags & kFlagNoVideo) &&
 		    (video->decoder->isPaletted() != !_vm->isTrueColor())) {
-			if (!properties.switchColorMode)
-				return -1;
+			if (properties.switchColorMode) {
+				_vm->setTrueColor(!video->decoder->isPaletted(), true);
 
-			_vm->setTrueColor(!video->decoder->isPaletted());
-
-			video->decoder->colorModeChanged();
+				video->decoder->colorModeChanged();
+			}
+			else {
+				if (!video->decoder->isPaletted()) // Paletted to high color is supported
+					return -1;
+			}
 		}
 
 		// Set the filename
@@ -166,7 +169,7 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 		}
 
 		if (!(properties.flags & kFlagNoVideo) && (properties.sprite >= 0)) {
-			bool ownSurf    = (properties.sprite != Draw::kFrontSurface) && (properties.sprite != Draw::kBackSurface);
+			bool ownSurf    = properties.sprite != Draw::kFrontSurface && properties.sprite != Draw::kBackSurface;
 			bool screenSize = properties.flags & kFlagScreenSurface;
 
 			if (ownSurf) {
@@ -176,7 +179,8 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 				if (height > 0 && width > 0) {
 					_vm->_draw->_spritesArray[properties.sprite] =
 						_vm->_video->initSurfDesc(screenSize ? _vm->_width  : video->decoder->getWidth(),
-												  screenSize ? _vm->_height : video->decoder->getHeight(), 0);
+												  screenSize ? _vm->_height : video->decoder->getHeight(), 0,
+												  0);
 				} else {
 					warning("VideoPlayer::openVideo() file=%s:"
 							"Invalid surface dimensions (%dx%d)", file.c_str(), width, height);
@@ -184,8 +188,8 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 			}
 
 			if (!_vm->_draw->_spritesArray[properties.sprite] &&
-			    (properties.sprite != Draw::kFrontSurface) &&
-			    (properties.sprite != Draw::kBackSurface)) {
+				 properties.sprite != Draw::kFrontSurface &&
+				 properties.sprite != Draw::kBackSurface) {
 				properties.sprite = -1;
 				video->surface.reset();
 				video->decoder->setSurfaceMemory();
@@ -197,10 +201,25 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 				if (properties.sprite == Draw::kBackSurface)
 					video->surface = _vm->_draw->_backSurface;
 
-				video->decoder->setSurfaceMemory(video->surface->getData(),
-						video->surface->getWidth(), video->surface->getHeight(), video->surface->getBPP());
+				if (video->decoder->isPaletted() && video->surface->getBPP() > 1) {
+					uint32 *highColorMap = new uint32[256];
+					Surface::computeHighColorMap(highColorMap,
+												 video->decoder->getPalette(),
+												 _vm->getPixelFormat(),
+												 _vm->getGameType() == kGameTypeAdibou2);
+					video->decoder->setSurfaceMemoryPalettedToHighColor(video->surface->getData(),
+																		video->surface->getWidth(),
+																		video->surface->getHeight(),
+																		_vm->getPixelFormat(),
+																		highColorMap);
+				} else {
+					video->decoder->setSurfaceMemory(video->surface->getData(),
+													 video->surface->getWidth(),
+													 video->surface->getHeight(),
+													 video->surface->getBPP());
+				}
 
-				if (!ownSurf || (ownSurf && screenSize)) {
+				if (!ownSurf || screenSize) {
 					if ((properties.x >= 0) || (properties.y >= 0)) {
 						properties.x = (properties.x < 0) ? 0xFFFF : properties.x;
 						properties.y = (properties.y < 0) ? 0xFFFF : properties.y;
@@ -796,9 +815,12 @@ bool VideoPlayer::copyFrame(int slot, Surface &dest,
 	// is only used read-only in this case (as far as I can tell). Not casting
 	// the const qualifier away will lead to an additional allocation and copy
 	// of the frame data which is undesirable.
-	Surface src(surface->w, surface->h, surface->format.bytesPerPixel, (byte *)const_cast<void *>(surface->getPixels()));
+	const Surface src(surface->w, surface->h, surface->format.bytesPerPixel,
+					  static_cast<byte*>(const_cast<void*>(surface->getPixels())),
+					  video->decoder->getHighColorMap());
 
 	dest.blit(src, left, top, left + width - 1, top + height - 1, x, y, transp, yAxisReflection);
+
 	return true;
 }
 
@@ -971,11 +993,14 @@ void VideoPlayer::copyPalette(const Video &video, int16 palStart, int16 palEnd)
 	if (palEnd < 0)
 		palEnd = 255;
 
-	palStart =  palStart      * 3;
-	palEnd   = (palEnd   + 1) * 3;
-
-	for (int i = palStart; i < palEnd; i++)
+	for (int i = palStart * 3; i < (palEnd + 1) * 3; i++)
 		((char *)(_vm->_global->_pPaletteDesc->vgaPal))[i] = video.decoder->getPalette()[i] >> 2;
+
+	Surface::computeHighColorMap(_vm->_global->_pPaletteDesc->highColorMap,
+								 video.decoder->getPalette(),
+								 _vm->getPixelFormat(),
+								 _vm->getGameType() == kGameTypeAdibou2,
+								 palStart, palEnd - palStart + 1);
 }
 
 } // End of namespace Gob


Commit: 52c88faf0ae0a1d12dc9595e5aafc0cbffe1acf4
    https://github.com/scummvm/scummvm/commit/52c88faf0ae0a1d12dc9595e5aafc0cbffe1acf4
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Make shaded fillRect available in later version of the engine (>= DrawPlaytoons)

Was previously only implemented in Draw_v2.
Fixes the transparency effect in the "choose a level" screen of Adibou2/Sciences.

Changed paths:
    engines/gob/draw_playtoons.cpp


diff --git a/engines/gob/draw_playtoons.cpp b/engines/gob/draw_playtoons.cpp
index 2a0ea39aca2..9374c3a3e2f 100644
--- a/engines/gob/draw_playtoons.cpp
+++ b/engines/gob/draw_playtoons.cpp
@@ -206,14 +206,23 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 			dirtiedRect(_destSurface, dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
 			break ;
 		}
-		case 0:
-			_spritesArray[_destSurface]->fillRect(destSpriteX,
-					_destSpriteY, _destSpriteX + _spriteRight - 1,
-					_destSpriteY + _spriteBottom - 1, _backColor);
+		case 0: {
+			if (!(_backColor & 0xFF00) || !(_backColor & 0x0100)) {
+				_spritesArray[_destSurface]->fillRect(_destSpriteX,
+													  _destSpriteY, _destSpriteX + _spriteRight - 1,
+													  _destSpriteY + _spriteBottom - 1, getColor(_backColor));
+			} else {
+				uint8 strength = 16 - (((uint16) _backColor) >> 12);
+
+				_spritesArray[_destSurface]->shadeRect(_destSpriteX,
+													   _destSpriteY, _destSpriteX + _spriteRight - 1,
+													   _destSpriteY + _spriteBottom - 1, getColor(_backColor), strength);
+			}
 
 			dirtiedRect(_destSurface, _destSpriteX, _destSpriteY,
-					_destSpriteX + _spriteRight - 1, _destSpriteY + _spriteBottom - 1);
+						_destSpriteX + _spriteRight - 1, _destSpriteY + _spriteBottom - 1);
 			break;
+		}
 		default:
 			warning("oPlaytoons_spriteOperation: operation DRAW_FILLRECT, unexpected pattern %d", _pattern & 0xFF);
 			break;


Commit: 2647651c00eb37d388e4dc2a4ca3bdcdc3726de6
    https://github.com/scummvm/scummvm/commit/2647651c00eb37d388e4dc2a4ca3bdcdc3726de6
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: more int16->uint16 fixes for game variables indexes

Changed paths:
    engines/gob/hotspots.cpp
    engines/gob/inter_v1.cpp
    engines/gob/inter_v2.cpp
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/hotspots.cpp b/engines/gob/hotspots.cpp
index 6c7b050bab8..d55bff3c18c 100644
--- a/engines/gob/hotspots.cpp
+++ b/engines/gob/hotspots.cpp
@@ -1368,7 +1368,7 @@ void Hotspots::evaluateNew(uint16 i, uint16 *ids, InputDesc *inputs,
 		return;
 	}
 
-	int16 key   = 0;
+	uint16 key   = 0;
 	int16 flags = 0;
 	Font *font = nullptr;
 	uint32 funcEnter = 0, funcLeave = 0;
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index bf793e685c8..bc71b7e0750 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -447,9 +447,9 @@ void Inter_v1::o1_initMult() {
 	int16 oldAnimHeight;
 	int16 oldAnimWidth;
 	int16 oldObjCount;
-	int16 posXVar;
-	int16 posYVar;
-	int16 animDataVar;
+	uint16 posXVar;
+	uint16 posYVar;
+	uint16 animDataVar;
 
 	oldAnimWidth = _vm->_mult->_animWidth;
 	oldAnimHeight = _vm->_mult->_animHeight;
@@ -572,9 +572,9 @@ void Inter_v1::o1_loadMultObject() {
 void Inter_v1::o1_getAnimLayerInfo() {
 	int16 anim;
 	int16 layer;
-	int16 varDX, varDY;
-	int16 varUnk0;
-	int16 varFrames;
+	uint16 varDX, varDY;
+	uint16 varUnk0;
+	uint16 varFrames;
 
 	_vm->_game->_script->evalExpr(&anim);
 	_vm->_game->_script->evalExpr(&layer);
@@ -961,7 +961,7 @@ void Inter_v1::o1_if(OpFuncParams &params) {
 
 void Inter_v1::o1_assign(OpFuncParams &params) {
 	byte destType = _vm->_game->_script->peekByte();
-	int16 dest = _vm->_game->_script->readVarIndex();
+	uint16 dest = _vm->_game->_script->readVarIndex();
 
 	int16 result;
 	int16 srcType = _vm->_game->_script->evalExpr(&result);
@@ -1727,7 +1727,7 @@ void Inter_v1::o1_waitEndPlay(OpFuncParams &params) {
 }
 
 void Inter_v1::o1_playComposition(OpFuncParams &params) {
-	int16 dataVar = _vm->_game->_script->readVarIndex();
+	uint16 dataVar = _vm->_game->_script->readVarIndex();
 	int16 freqVal = _vm->_game->_script->readValExpr();
 
 	int16 composition[50];
@@ -1739,8 +1739,8 @@ void Inter_v1::o1_playComposition(OpFuncParams &params) {
 }
 
 void Inter_v1::o1_getFreeMem(OpFuncParams &params) {
-	int16 freeVar;
-	int16 maxFreeVar;
+	uint16 freeVar;
+	uint16 maxFreeVar;
 
 	freeVar = _vm->_game->_script->readVarIndex();
 	maxFreeVar = _vm->_game->_script->readVarIndex();
@@ -1752,7 +1752,7 @@ void Inter_v1::o1_getFreeMem(OpFuncParams &params) {
 
 void Inter_v1::o1_checkData(OpFuncParams &params) {
 	const char *file   = _vm->_game->_script->evalString();
-	      int16 varOff = _vm->_game->_script->readVarIndex();
+	      uint16 varOff = _vm->_game->_script->readVarIndex();
 
 	if (!_vm->_dataIO->hasFile(file)) {
 		warning("File \"%s\" not found", file);
@@ -1762,7 +1762,7 @@ void Inter_v1::o1_checkData(OpFuncParams &params) {
 }
 
 void Inter_v1::o1_cleanupStr(OpFuncParams &params) {
-	int16 strVar;
+	uint16 strVar;
 
 	strVar = _vm->_game->_script->readVarIndex();
 	_vm->_util->cleanupStr(GET_VARO_FSTR(strVar));
@@ -1770,7 +1770,7 @@ void Inter_v1::o1_cleanupStr(OpFuncParams &params) {
 
 void Inter_v1::o1_insertStr(OpFuncParams &params) {
 	int16 pos;
-	int16 strVar;
+	uint16 strVar;
 
 	strVar = _vm->_game->_script->readVarIndex();
 	_vm->_game->_script->evalExpr(nullptr);
@@ -1781,7 +1781,7 @@ void Inter_v1::o1_insertStr(OpFuncParams &params) {
 }
 
 void Inter_v1::o1_cutStr(OpFuncParams &params) {
-	int16 strVar;
+	uint16 strVar;
 	int16 pos;
 	int16 size;
 
@@ -1792,8 +1792,8 @@ void Inter_v1::o1_cutStr(OpFuncParams &params) {
 }
 
 void Inter_v1::o1_strstr(OpFuncParams &params) {
-	int16 strVar;
-	int16 resVar;
+	uint16 strVar;
+	uint16 resVar;
 	int16 pos;
 
 	strVar = _vm->_game->_script->readVarIndex();
@@ -1807,7 +1807,7 @@ void Inter_v1::o1_strstr(OpFuncParams &params) {
 
 void Inter_v1::o1_istrlen(OpFuncParams &params) {
 	int16 len;
-	int16 strVar;
+	uint16 strVar;
 
 	strVar = _vm->_game->_script->readVarIndex();
 	len = strlen(GET_VARO_STR(strVar));
@@ -1871,7 +1871,7 @@ void Inter_v1::o1_freeFont(OpFuncParams &params) {
 
 void Inter_v1::o1_readData(OpFuncParams &params) {
 	const char *file    = _vm->_game->_script->evalString();
-	      int16 dataVar = _vm->_game->_script->readVarIndex();
+	      uint16 dataVar = _vm->_game->_script->readVarIndex();
 	      int16 size    = _vm->_game->_script->readValExpr();
 	      int16 offset  = _vm->_game->_script->readValExpr();
 	      int16 retSize = 0;
diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp
index 99d08ce602a..7e1fdb8d2dd 100644
--- a/engines/gob/inter_v2.cpp
+++ b/engines/gob/inter_v2.cpp
@@ -610,7 +610,7 @@ void Inter_v2::o2_pushVars() {
 		if ((_vm->_game->_script->peekByte() == 25) ||
 				(_vm->_game->_script->peekByte() == 28)) {
 
-			int16 varOff = _vm->_game->_script->readVarIndex();
+			uint16 varOff = _vm->_game->_script->readVarIndex();
 			_vm->_game->_script->skip(1);
 
 			_varStack.pushData(*_variables, varOff, _vm->_global->_inter_animDataSize * 4);
@@ -1459,7 +1459,7 @@ void Inter_v2::o2_getFreeMem(OpFuncParams &params) {
 
 void Inter_v2::o2_checkData(OpFuncParams &params) {
 	Common::String file = _vm->_game->_script->evalString();
-	int16 varOff = _vm->_game->_script->readVarIndex();
+	uint16 varOff = _vm->_game->_script->readVarIndex();
 
 	// WORKAROUND: For some reason, the variable indicating which TOT to load next
 	// is overwritten in the guard house card game in Woodruff.
@@ -1574,7 +1574,7 @@ void Inter_v2::o2_readData(OpFuncParams &params) {
 void Inter_v2::o2_writeData(OpFuncParams &params) {
 	const char *file = _vm->_game->_script->evalString();
 
-	int16 dataVar = _vm->_game->_script->readVarIndex();
+	uint16 dataVar = _vm->_game->_script->readVarIndex();
 	int32 size    = _vm->_game->_script->readValExpr();
 	int32 offset  = _vm->_game->_script->evalInt();
 
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 6e126450e1c..b544d6909f4 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1407,7 +1407,7 @@ void Inter_v7::o7_readData(OpFuncParams &params) {
 void Inter_v7::o7_writeData(OpFuncParams &params) {
 	Common::String file = getFile(_vm->_game->_script->evalString(), false);
 
-	int16 dataVar = _vm->_game->_script->readVarIndex();
+	uint16 dataVar = _vm->_game->_script->readVarIndex();
 	int32 size    = _vm->_game->_script->readValExpr();
 	int32 offset  = _vm->_game->_script->evalInt();
 


Commit: 84283fd550f3ba22721bd48c8c598f58e821297a
    https://github.com/scummvm/scummvm/commit/84283fd550f3ba22721bd48c8c598f58e821297a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: implement o1_palLoad cases 55 and 56 (push/pop palette)

Changed paths:
    engines/gob/draw.h
    engines/gob/inter_v1.cpp


diff --git a/engines/gob/draw.h b/engines/gob/draw.h
index b0c3e54cd71..ae949860c91 100644
--- a/engines/gob/draw.h
+++ b/engines/gob/draw.h
@@ -28,6 +28,7 @@
 #ifndef GOB_DRAW_H
 #define GOB_DRAW_H
 
+#include "common/stack.h"
 #include "gob/video.h"
 
 namespace Common {
@@ -126,6 +127,8 @@ public:
 	int16 _unusedPalette2[16];
 	Video::Color _vgaPalette[256];
 
+	Common::Stack<Video::Color *> _paletteStack;
+
 	// 0 (00b): No cursor
 	// 1 (01b): Cursor would be on _backSurface
 	// 2 (10b): Cursor would be on _frontSurface
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index bc71b7e0750..b5ac4f395a7 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -1226,17 +1226,33 @@ void Inter_v1::o1_palLoad(OpFuncParams &params) {
 		memset((char *)_vm->_draw->_vgaPalette, 0, 768);
 		break;
 
-	case 55:
-		// TODO case 55 implementation
-		warning("STUB: o1_palLoad case 55 not implemented");
+	case 55: {
+		// Push a copy of the current palette
+		Video::Color *paletteCopy = new Video::Color[256];
+		memcpy((char *)paletteCopy, (char *)_vm->_draw->_vgaPalette, 768);
+		_vm->_draw->_paletteStack.push(paletteCopy);
+
 		_vm->_game->_script->skip(2);
 		_vm->_draw->_applyPal = false;
 		return;
+	}
 
 	case 56:
-		// TODO case 56 implementation
-		warning("STUB: o1_palLoad case 56 not implemented");
-		_vm->_game->_script->skip(2);
+		// Pop the last pushed palette
+		index1 =  _vm->_game->_script->readByte();
+		index2 = (_vm->_game->_script->readByte() - index1 + 1) * 3;
+		if (!_vm->_draw->_paletteStack.empty()) {
+			memcpy((char *)_vm->_draw->_vgaPalette + index1 * 3,
+				   (char *)_vm->_draw->_paletteStack.top() + index1 * 3, index2);
+			delete[] _vm->_draw->_paletteStack.pop();
+		} else {
+			warning("o1_palLoad case 56: empty palette stack, cannot pop");
+		}
+
+		_vm->_draw->_applyPal = true;
+		_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
+		_vm->_draw->_applyPal = false;
+
 		break;
 
 	case 61:


Commit: 01a36080caf6133ef36b8d32ca3d8803a1d4e751
    https://github.com/scummvm/scummvm/commit/01a36080caf6133ef36b8d32ca3d8803a1d4e751
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Check sprite index range in copy/free sprite opcodes

Changed paths:
    engines/gob/inter_playtoons.cpp
    engines/gob/inter_v1.cpp


diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp
index facd5075430..a3d3d305c30 100644
--- a/engines/gob/inter_playtoons.cpp
+++ b/engines/gob/inter_playtoons.cpp
@@ -214,6 +214,12 @@ void Inter_Playtoons::oPlaytoons_freeSprite(OpFuncParams &params) {
 		index = _vm->_game->_script->readInt16();
 	else
 		index = _vm->_game->_script->readValExpr();
+
+	if (index < 0 || index >= Draw::kSpriteCount) {
+		warning("oPlaytoons_freeSprite: invalid sprite index %d", index);
+		return;
+	}
+
 	_vm->_draw->freeSprite(index);
 }
 
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index b5ac4f395a7..c41c69247a9 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -1615,6 +1615,20 @@ void Inter_v1::o1_copySprite(OpFuncParams &params) {
 
 	_vm->_draw->_transparency = _vm->_game->_script->readInt16();
 
+	if (_vm->_draw->_sourceSurface != 100 &&
+		_vm->_draw->_sourceSurface != 101 &&
+		(_vm->_draw->_sourceSurface < 0 || _vm->_draw->_sourceSurface >= Draw::kSpriteCount)) {
+		warning("o1_copySprite(): Invalid source surface index %d", _vm->_draw->_sourceSurface);
+		return;
+	}
+
+	if (_vm->_draw->_destSurface != 100 &&
+		_vm->_draw->_destSurface != 101 &&
+		(_vm->_draw->_destSurface < 0 || _vm->_draw->_destSurface >= Draw::kSpriteCount)) {
+		warning("o1_copySprite(): Invalid destination surface index %d", _vm->_draw->_destSurface);
+		return;
+	}
+
 	_vm->_draw->spriteOperation(DRAW_BLITSURF);
 }
 


Commit: 5c4aa16eace65d0ce8eb947fbb121727f8e72c4c
    https://github.com/scummvm/scummvm/commit/5c4aa16eace65d0ce8eb947fbb121727f8e72c4c
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
VIDEO: int16 -> uint16 for _soundFreq in VMD decoder

Some later games VMDs use 44100 Hz frequency, which does not fit in an int16.

Changed paths:
    video/coktel_decoder.cpp
    video/coktel_decoder.h


diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index 99c08b0ec55..dde521e0955 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -2025,7 +2025,7 @@ bool VMDDecoder::loadStream(Common::SeekableReadStream *stream) {
 		}
 	}
 
-	_soundFreq        = _stream->readSint16LE();
+	_soundFreq        = _stream->readUint16LE();
 	_soundSliceSize   = _stream->readSint16LE();
 	_soundSlicesCount = _stream->readSint16LE();
 	_soundFlags       = _stream->readUint16LE();
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 88f7e48e4f3..0c8d0f60a1c 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -525,7 +525,7 @@ private:
 
 	// Sound properties
 	uint16 _soundFlags;
-	int16  _soundFreq;
+	uint16  _soundFreq;
 	int16  _soundSliceSize;
 	int16  _soundSlicesCount;
 	byte   _soundBytesPerSample;


Commit: 950b58cf42a11be15cc16b145a9530fa10fe734e
    https://github.com/scummvm/scummvm/commit/950b58cf42a11be15cc16b145a9530fa10fe734e
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Read explicit video slot number from scripts

Changed paths:
    engines/gob/inter_v7.cpp
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index b544d6909f4..983252e2c9a 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -686,12 +686,10 @@ void Inter_v7::o7_playVmdOrMusic() {
 	} else if (props.lastFrame <= -10) {
 		_vm->_vidPlayer->closeVideo();
 
-		if (!(props.flags & VideoPlayer::kFlagNoVideo))
-			props.loop = true;
+		//if (!(props.flags & VideoPlayer::kFlagNoVideo))
+		//	props.loop = true;
 
-	} else if (props.lastFrame < 0) {
-		warning("Urban/Playtoons Stub: Unknown Video/Music command: %d, %s", props.lastFrame, file.c_str());
-		return;
+		props.slot = (-props.lastFrame) % 10;
 	}
 
 	// TODO: conditions below for unblocking videos have been found partly from asm, partly by trial and errors.
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index cd4314490df..0dc1babf267 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -45,7 +45,7 @@ VideoPlayer::Properties::Properties() : type(kVideoTypeTry), sprite(Draw::kFront
 	startFrame(-1), lastFrame(-1), endFrame(-1), forceSeek(false),
 	breakKey(kShortKeyEscape), palCmd(8), palStart(0), palEnd(255), palFrame(-1),
 	noBlock(false), loop(false), fade(false), waitEndFrame(true),
-	hasSound(false), canceled(false) {
+	hasSound(false), canceled(false), slot(-1) {
 
 }
 
@@ -98,19 +98,58 @@ void VideoPlayer::evaluateFlags(Properties &properties) {
 }
 
 int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties &properties) {
-	int slot = 0;
+	int slot = kPrimaryVideoSlot;
+
+	Video *video = nullptr;
+	if (_vm->getGameType() == kGameTypeAdibou2) {
+		// Check whether a slot is already open for this file
+		for (int i = 0; i < kVideoSlotCount; i++) {
+			if (_videoSlots[i].isEmpty())
+				continue;
+
+			if (_videoSlots[i].fileName.equalsIgnoreCase(file)) {
+				slot = i;
+				video = &_videoSlots[i];
+				break;
+			}
+		}
 
-	Video *video = 0;
-	if (!primary) {
-		slot = getNextFreeSlot();
-		if (slot < 0) {
-			warning("VideoPlayer::openVideo(): Can't open video \"%s\": No free slot", file.c_str());
-			return -1;
+		if (video == nullptr) {
+			if (properties.slot >= 0 && properties.slot < kVideoSlotCount) {
+				if (properties.slot >= kLiveVideoSlotCount)
+					warning("VideoPlayer::openVideo(): explicit slot index %d is"
+							" beyond reserved spots for live videos (0 - %d)",
+							properties.slot, kLiveVideoSlotCount - 1);
+				slot = properties.slot;
+			} else if (primary) {
+				slot = kPrimaryVideoSlot;
+			} else {
+				if (properties.slot >= 0)
+					warning("VideoPlayer::openVideo(): explicit slot number %d is "
+							"beyond maximum slot index %d, will be ignored",
+							properties.slot, kVideoSlotCount - 1);
+
+				slot = getNextFreeSlot();
+				if (slot < 0) {
+					warning("VideoPlayer::openVideo(): Can't open video \"%s\": No free slot", file.c_str());
+					return -1;
+				}
+			}
+
+			video = &_videoSlots[slot];
 		}
+	} else {
+		if (!primary) {
+			slot = getNextFreeSlot();
+			if (slot < 0) {
+				warning("VideoPlayer::openVideo(): Can't open video \"%s\": No free slot", file.c_str());
+				return -1;
+			}
 
-		video = &_videoSlots[slot];
-	} else
-		video = &_videoSlots[0];
+			video = &_videoSlots[slot];
+		} else
+			video = &_videoSlots[kPrimaryVideoSlot];
+	}
 
 	// Different video already in the slot => close that video
 	if (!video->isEmpty() && (video->fileName.compareToIgnoreCase(file) != 0))
@@ -845,8 +884,8 @@ VideoPlayer::Video *VideoPlayer::getVideoBySlot(int slot) {
 }
 
 int VideoPlayer::getNextFreeSlot() {
-	// Starting with 1, since 0 is reserved for the "primary" video
-	for (int i = 1; i < kVideoSlotCount; i++)
+	// index 0 is reserved for "primary" videos, 1..kLiveVideoSlotCount for "live" videos, so start at kLiveVideoSlotCount + 1
+	for (int i = kLiveVideoSlotCount + 1; i < kVideoSlotCount; i++)
 		if (_videoSlots[i].isEmpty())
 			return i;
 
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index a92a2bd3646..b2de5bdef7c 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -104,6 +104,8 @@ public:
 		bool hasSound; ///< Does the video have sound?
 		bool canceled; ///< Was the video canceled?
 
+		int slot; ///< Explicit slot index (-1 = auto).
+
 		Properties();
 	};
 
@@ -183,6 +185,9 @@ private:
 	};
 
 	static const int kVideoSlotCount = 32;
+	static const int kPrimaryVideoSlot = 0;
+	static const int kLiveVideoSlotCount = 5;
+	static const int kVideoSlotWithCurFrameVarCount = 4;
 
 	static const char *const _extensions[];
 


Commit: aa906c0c4745e30987cf4e12c255f2851c1bcf61
    https://github.com/scummvm/scummvm/commit/aa906c0c4745e30987cf4e12c255f2851c1bcf61
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix conditions for non-blocking videos

Changed paths:
    engines/gob/inter_v7.cpp
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h
    video/coktel_decoder.cpp
    video/coktel_decoder.h


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 983252e2c9a..d3fafbd9b06 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -686,18 +686,15 @@ void Inter_v7::o7_playVmdOrMusic() {
 	} else if (props.lastFrame <= -10) {
 		_vm->_vidPlayer->closeVideo();
 
+		if (props.lastFrame <= -20)
+			props.noBlock    = true;
 		//if (!(props.flags & VideoPlayer::kFlagNoVideo))
 		//	props.loop = true;
 
 		props.slot = (-props.lastFrame) % 10;
 	}
 
-	// TODO: conditions below for unblocking videos have been found partly from asm, partly by trial and errors.
-	// Reality may be more complex...
-	if (props.startFrame == -2 ||
-		(props.startFrame == props.lastFrame &&
-		 props.lastFrame != -1 &&
-		 !(props.flags & VideoPlayer::kFlagOtherSurface))) {
+	if (props.startFrame == -2) {
 		props.startFrame = 0;
 		props.lastFrame  = -1;
 		props.noBlock    = true;
@@ -715,6 +712,12 @@ void Inter_v7::o7_playVmdOrMusic() {
 		return;
 	}
 
+	if (_vm->_vidPlayer->getVideoBufferSize(slot) == 0 || !_vm->_vidPlayer->hasVideo(slot)) {
+		props.startFrame = 0;
+		props.lastFrame  = -1;
+		props.noBlock = true;
+	}
+
 	if (props.hasSound)
 		_vm->_vidPlayer->closeLiveSound();
 
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 0dc1babf267..8a65a3c5031 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -770,6 +770,23 @@ uint32 VideoPlayer::getFlags(int slot) const {
 
 	return video->decoder->getFlags();
 }
+
+uint32 VideoPlayer::getVideoBufferSize(int slot) const {
+	const Video *video = getVideoBySlot(slot);
+	if (!video)
+		return 0;
+
+	return video->decoder->getVideoBufferSize();
+}
+
+bool VideoPlayer::hasVideo(int slot) const {
+	const Video *video = getVideoBySlot(slot);
+	if (!video)
+		return 0;
+
+	return video->decoder->hasVideo();
+}
+
 const Common::List<Common::Rect> *VideoPlayer::getDirtyRects(int slot) const {
 	const Video *video = getVideoBySlot(slot);
 	if (!video)
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index b2de5bdef7c..27052fe322e 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -142,13 +142,15 @@ public:
 
 	Common::String getFileName(int slot = 0) const;
 
-	uint32 getFrameCount  (int slot = 0) const;
-	uint32 getCurrentFrame(int slot = 0) const;
-	uint16 getWidth       (int slot = 0) const;
-	uint16 getHeight      (int slot = 0) const;
-	uint16 getDefaultX    (int slot = 0) const;
-	uint16 getDefaultY    (int slot = 0) const;
-	uint32 getFlags       (int slot = 0) const;
+	uint32 getFrameCount     (int slot = 0) const;
+	uint32 getCurrentFrame   (int slot = 0) const;
+	uint16 getWidth          (int slot = 0) const;
+	uint16 getHeight         (int slot = 0) const;
+	uint16 getDefaultX       (int slot = 0) const;
+	uint16 getDefaultY       (int slot = 0) const;
+	uint32 getFlags          (int slot = 0) const;
+	uint32 getVideoBufferSize(int slot = 0) const;
+	bool   hasVideo          (int slot = 0) const;
 
 
 	const Common::List<Common::Rect> *getDirtyRects(int slot = 0) const;
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index dde521e0955..d9c565a7b25 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -994,6 +994,10 @@ uint32 PreIMDDecoder::getFlags() const {
 	return 0;
 }
 
+uint32 PreIMDDecoder::getVideoBufferSize() const {
+	return _videoBufferSize;
+}
+
 Graphics::PixelFormat PreIMDDecoder::getPixelFormat() const {
 	return Graphics::PixelFormat::createFormatCLUT8();
 }
@@ -1676,6 +1680,10 @@ uint32 IMDDecoder::getFlags() const {
 	return _flags;
 }
 
+uint32 IMDDecoder::getVideoBufferSize() const {
+	return _videoBufferSize;
+}
+
 Graphics::PixelFormat IMDDecoder::getPixelFormat() const {
 	return Graphics::PixelFormat::createFormatCLUT8();
 }
@@ -2910,6 +2918,10 @@ uint32 VMDDecoder::getFlags() const {
 	return _flags;
 }
 
+uint32 VMDDecoder::getVideoBufferSize() const {
+	return _videoBufferSize;
+}
+
 Graphics::PixelFormat VMDDecoder::getPixelFormat() const {
 #ifdef USE_INDEO3
 	if (_externalCodec) {
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 0c8d0f60a1c..0dfc24d7f32 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -159,6 +159,8 @@ public:
 	/** Is the video paletted or true color? */
 	virtual bool isPaletted() const;
 
+	virtual uint32 getVideoBufferSize() const = 0;
+
 	/**
 	 * Get the current frame
 	 * @see VideoDecoder::getCurFrame()
@@ -316,6 +318,7 @@ public:
 	const Graphics::Surface *decodeNextFrame();
 
 	uint32 getFlags() const;
+	uint32 getVideoBufferSize() const;
 
 	Graphics::PixelFormat getPixelFormat() const;
 
@@ -350,6 +353,7 @@ public:
 	const Graphics::Surface *decodeNextFrame();
 
 	uint32 getFlags() const;
+	uint32 getVideoBufferSize() const;
 
 	Graphics::PixelFormat getPixelFormat() const;
 
@@ -457,6 +461,7 @@ public:
 	const Graphics::Surface *decodeNextFrame();
 
 	uint32 getFlags() const;
+	uint32 getVideoBufferSize() const;
 
 	Graphics::PixelFormat getPixelFormat() const;
 


Commit: 63d093a64a4f951c16d8deec75cf15e6d2f84635
    https://github.com/scummvm/scummvm/commit/63d093a64a4f951c16d8deec75cf15e6d2f84635
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Update slot-dependent "current frame" game variables

Those variables are very often used as a stop condition by the scripts.

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 8a65a3c5031..810946a5e02 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -421,6 +421,12 @@ bool VideoPlayer::play(int slot, Properties &properties) {
 		if (properties.canceled)
 			break;
 
+		if (_vm->getGameType() == kGameTypeAdibou2) {
+			WRITE_VAR(11, properties.startFrame + 1);
+			if (slot >= 0 && slot < kVideoSlotWithCurFrameVarCount)
+				WRITE_VAR(53 + slot, properties.startFrame + 1);
+		}
+
 		properties.startFrame += backwards ? -1 : 1;
 
 		evalBgShading(*video);
@@ -476,8 +482,12 @@ bool VideoPlayer::isSoundPlaying() const {
 }
 
 void VideoPlayer::updateLive(bool force) {
-	for (int i = 0; i < kVideoSlotCount; i++)
+	for (int i = 0; i < kVideoSlotCount; i++) {
+		if (_vm->getGameType() == kGameTypeAdibou2 && i >= 0 && i < kVideoSlotWithCurFrameVarCount)
+			WRITE_VAR(53 + i, -1);
+
 		updateLive(i, force);
+	}
 }
 
 void VideoPlayer::updateLive(int slot, bool force) {
@@ -485,6 +495,9 @@ void VideoPlayer::updateLive(int slot, bool force) {
 	if (!video || !video->live)
 		return;
 
+	if (_vm->getGameType() == kGameTypeAdibou2 && slot < kVideoSlotWithCurFrameVarCount)
+		WRITE_VAR(53 + slot, video->decoder->getCurFrame());
+
 	int nbrOfLiveVideos = 0;
 	for (int i = 0; i < kVideoSlotCount; i++) {
 		Video *otherVideo = getVideoBySlot(i);
@@ -496,8 +509,11 @@ void VideoPlayer::updateLive(int slot, bool force) {
 		// Video ended
 
 		if (!video->properties.loop) {
-			if (!(video->properties.flags & kFlagNoVideo) || nbrOfLiveVideos == 1)
-				WRITE_VAR_OFFSET(212, (uint32)-1);
+			if (_vm->getGameType() != kGameTypeAdibou2) {
+				if (!(video->properties.flags & kFlagNoVideo) || nbrOfLiveVideos == 1)
+					WRITE_VAR(53, (uint32)-1);
+			}
+
 			_vm->_vidPlayer->closeVideo(slot);
 			return;
 		} else {
@@ -513,14 +529,18 @@ void VideoPlayer::updateLive(int slot, bool force) {
 	if (!force && (video->decoder->getTimeToNextFrame() > 0))
 		return;
 
-	if (!(video->properties.flags & kFlagNoVideo) || nbrOfLiveVideos == 1)
-		WRITE_VAR_OFFSET(212, video->properties.startFrame + 1);
+	if (_vm->getGameType() != kGameTypeAdibou2  &&
+		(!(video->properties.flags & kFlagNoVideo) || nbrOfLiveVideos == 1))
+		WRITE_VAR(53, video->properties.startFrame + 1);
 
 	bool backwards = video->properties.startFrame > video->properties.lastFrame;
 	playFrame(slot, video->properties);
 
 	video->properties.startFrame += backwards ? -1 : 1;
 
+	if (_vm->getGameType() == kGameTypeAdibou2 && slot < kVideoSlotWithCurFrameVarCount)
+		WRITE_VAR(53 + slot, video->decoder->getCurFrame());
+
 	if (video->properties.fade) {
 		_vm->_palAnim->fade(_vm->_global->_pPaletteDesc, -2, 0);
 		video->properties.fade = false;
@@ -591,7 +611,8 @@ bool VideoPlayer::playFrame(int slot, Properties &properties) {
 
 	const Graphics::Surface *surface = video->decoder->decodeNextFrame();
 
-	WRITE_VAR(11, video->decoder->getCurFrame());
+	if (_vm->getGameType() != kGameTypeAdibou2)
+		WRITE_VAR(11, video->decoder->getCurFrame());
 
 	uint32 ignoreBorder = 0;
 	if (_woodruffCohCottWorkaround && (properties.startFrame == 31)) {


Commit: 8096fd68cd13219790bf65dc476cc9e9203c08cb
    https://github.com/scummvm/scummvm/commit/8096fd68cd13219790bf65dc476cc9e9203c08cb
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Video break key fixes for Adibou2/Sciences

- Fix break key conditions
- Implement key and mouse button forwarding after interrupting a video

Changed paths:
    engines/gob/game.cpp
    engines/gob/game.h
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/game.cpp b/engines/gob/game.cpp
index 02884a5f5db..d5677d5f81d 100644
--- a/engines/gob/game.cpp
+++ b/engines/gob/game.cpp
@@ -462,6 +462,9 @@ Game::Game(GobEngine *vm) : _vm(vm), _environments(_vm), _totFunctions(_vm) {
 
 	_handleMouse = 0;
 	_forceHandleMouse = 0;
+	_hasForwardedEventsFromVideo = false;
+	_forwardedMouseButtonsFromVideo = kMouseButtonsNone;
+	_forwardedKeyFromVideo = 0;
 	_noScroll = true;
 	_preventScroll = false;
 
@@ -840,7 +843,15 @@ int16 Game::checkKeys(int16 *pMouseX, int16 *pMouseY,
 			*pButtons = kMouseButtonsNone;
 	}
 
-	return _vm->_util->checkKey();
+	int16 key = _vm->_util->checkKey();
+	if (_vm->_game->_hasForwardedEventsFromVideo) {
+		if (pButtons)
+			*pButtons = _vm->_game->_forwardedMouseButtonsFromVideo;
+		key = _vm->_game->_forwardedKeyFromVideo;
+		_vm->_game->_hasForwardedEventsFromVideo = false;
+	}
+
+	return key;
 }
 
 void Game::start() {
diff --git a/engines/gob/game.h b/engines/gob/game.h
index 036f05bdbc9..ba507514636 100644
--- a/engines/gob/game.h
+++ b/engines/gob/game.h
@@ -144,6 +144,10 @@ public:
 	int32 _startTimeKey;
 	MouseButtons _mouseButtons;
 
+	bool _hasForwardedEventsFromVideo;
+	MouseButtons _forwardedMouseButtonsFromVideo;
+	int16 _forwardedKeyFromVideo;
+
 	bool _noScroll;
 	bool _preventScroll;
 
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 810946a5e02..bb5848181e9 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -674,7 +674,13 @@ bool VideoPlayer::playFrame(int slot, Properties &properties) {
 			_vm->_palAnim->fade(_vm->_global->_pPaletteDesc, -2, 0);
 	}
 
-	if (primary && properties.waitEndFrame)
+	bool needCheckAbort = false;
+	if (_vm->getGameType() == kGameTypeAdibou2)
+		needCheckAbort = properties.breakKey != 0;
+	else
+		needCheckAbort = primary && properties.waitEndFrame;
+
+	if (needCheckAbort)
 		checkAbort(*video, properties);
 
 	if ((video->decoder->getCurFrame() - 1) < properties.startFrame)
@@ -698,28 +704,52 @@ void VideoPlayer::checkAbort(Video &video, Properties &properties) {
 		_vm->_util->getMouseState(&_vm->_global->_inter_mouseX,
 				&_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons);
 
-		_vm->_inter->storeKey(_vm->_util->checkKey());
+		int16 key = _vm->_util->checkKey();
+		_vm->_inter->storeKey(key);
 
 		// Check for that specific key
 		bool pressedBreak = (VAR(0) == (unsigned)properties.breakKey);
+		if (_vm->getGameType() == kGameTypeAdibou2) {
+			if (pressedBreak ||
+				_vm->_game->_mouseButtons == properties.breakKey) {
+				properties.canceled = true;
+				return;
+			}
 
-		// Mouse buttons
-		if (properties.breakKey < 4)
-			if (_vm->_game->_mouseButtons & properties.breakKey)
-				pressedBreak = true;
-
-		// Any key
-		if (properties.breakKey == 4)
-			if (VAR(0) != 0)
-				pressedBreak = true;
-
-		if (pressedBreak) {
-			video.decoder->disableSound();
-
-			// Seek to the last frame. Some scripts depend on that.
-			video.decoder->seek(properties.endFrame + 1, SEEK_SET, true);
+			if (properties.breakKey == 4) {
+				if (_vm->_game->_mouseButtons == 2 || key == kKeyEscape) {
+					properties.canceled = true;
+					return;
+				}
 
-			properties.canceled = true;
+				if (key != kKeyNone ||
+					(_vm->_game->_mouseButtons == 1 &&
+					 _vm->_draw->_cursorIndex != -1)) {
+					_vm->_game->_hasForwardedEventsFromVideo = true;
+					_vm->_game->_forwardedKeyFromVideo = key;
+					_vm->_game->_forwardedMouseButtonsFromVideo = _vm->_game->_mouseButtons;
+					properties.canceled = true;
+					return;
+				}
+			}
+		} else {
+			// Mouse buttons
+			if (properties.breakKey < 4)
+				if (_vm->_game->_mouseButtons & properties.breakKey)
+					pressedBreak = true;
+
+			// Any key
+			if (properties.breakKey == 4)
+				if (VAR(0) != 0)
+					pressedBreak = true;
+
+			if (pressedBreak) {
+				video.decoder->disableSound();
+
+				// Seek to the last frame. Some scripts depend on that.
+				video.decoder->seek(properties.endFrame + 1, SEEK_SET, true);
+				properties.canceled = true;
+			}
 		}
 	}
 }


Commit: b5864feedbd00aa3598c5a7da8692f7ada9c7e2a
    https://github.com/scummvm/scummvm/commit/b5864feedbd00aa3598c5a7da8692f7ada9c7e2a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix start_frame<=-100 case in o7_playVmdOrMusic

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index d3fafbd9b06..168347ff147 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -686,6 +686,13 @@ void Inter_v7::o7_playVmdOrMusic() {
 	} else if (props.lastFrame <= -10) {
 		_vm->_vidPlayer->closeVideo();
 
+		if (props.lastFrame <= -100) {
+			// The original does an early return here if the video is not in the cache
+			// if (video not in cache)
+			//   return;
+
+			props.lastFrame += 100;
+		}
 		if (props.lastFrame <= -20)
 			props.noBlock    = true;
 		//if (!(props.flags & VideoPlayer::kFlagNoVideo))


Commit: 14592466265f23a1acc86c800fe730879dfd545a
    https://github.com/scummvm/scummvm/commit/14592466265f23a1acc86c800fe730879dfd545a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Many "live" videos fixes for Adibou2/Sciences

Changed paths:
    engines/gob/game.cpp
    engines/gob/util.cpp
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h


diff --git a/engines/gob/game.cpp b/engines/gob/game.cpp
index d5677d5f81d..d991155318d 100644
--- a/engines/gob/game.cpp
+++ b/engines/gob/game.cpp
@@ -817,6 +817,8 @@ void Game::evaluateScroll() {
 int16 Game::checkKeys(int16 *pMouseX, int16 *pMouseY,
 		MouseButtons *pButtons, char handleMouse) {
 
+	if (_vm->getGameType() == kGameTypeAdibou2)
+		_vm->_vidPlayer->updateLive();
 	_vm->_util->processInput(true);
 
 	if (_vm->_mult->_multData && _vm->_inter->_variables &&
diff --git a/engines/gob/util.cpp b/engines/gob/util.cpp
index cc739ea8014..875270b30ea 100644
--- a/engines/gob/util.cpp
+++ b/engines/gob/util.cpp
@@ -100,7 +100,8 @@ void Util::processInput(bool scroll) {
 	int16 x = 0, y = 0;
 	bool hasMove = false;
 
-	_vm->_vidPlayer->updateLive();
+	if (_vm->getGameType() != kGameTypeAdibou2)
+		_vm->_vidPlayer->updateLive();
 
 	while (eventMan->pollEvent(event)) {
 		switch (event.type) {
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index bb5848181e9..4085b4af3b6 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -402,13 +402,17 @@ bool VideoPlayer::play(int slot, Properties &properties) {
 		video->live       = true;
 		video->properties = properties;
 
-		updateLive(slot, true);
-		return true;
+		if (_vm->getGameType() != kGameTypeAdibou2) {
+			updateLive(slot, true);
+			return true;
+		}
 	}
 
-	if ((_vm->getGameType() != kGameTypeUrban) && (_vm->getGameType() != kGameTypeBambou))
+	if (_vm->getGameType() != kGameTypeUrban &&
+		_vm->getGameType() != kGameTypeBambou &&
+		_vm->getGameType() != kGameTypeAdibou2)
 		// NOTE: For testing (and comfort?) purposes, we enable aborting of all videos.
-		//       Except for Urban Runner and Bambou, where it leads to glitches
+		//       Except for Urban Runner, Bambou and Adibou2 where it leads to glitches
 		properties.breakKey = kShortKeyEscape;
 
 	if (_vm->_draw->_renderFlags & RENDERFLAG_DOUBLEVIDEO)
@@ -481,12 +485,17 @@ bool VideoPlayer::isSoundPlaying() const {
 	return video && video->decoder && video->decoder->isSoundPlaying();
 }
 
-void VideoPlayer::updateLive(bool force) {
-	for (int i = 0; i < kVideoSlotCount; i++) {
-		if (_vm->getGameType() == kGameTypeAdibou2 && i >= 0 && i < kVideoSlotWithCurFrameVarCount)
+void VideoPlayer::updateLive(bool force, int exceptSlot) {
+	int nbrOfSlots = (_vm->getGameType() == kGameTypeAdibou2) ?
+					 kLiveVideoSlotCount : kVideoSlotCount;
+
+	for (int i = 0; i < nbrOfSlots; i++) {
+		if (_vm->getGameType() == kGameTypeAdibou2 &&
+			i >= 0 && i < kVideoSlotWithCurFrameVarCount)
 			WRITE_VAR(53 + i, -1);
 
-		updateLive(i, force);
+		if (i != exceptSlot)
+			updateLive(i, force);
 	}
 }
 
@@ -505,6 +514,9 @@ void VideoPlayer::updateLive(int slot, bool force) {
 			++nbrOfLiveVideos;
 	}
 
+	if (_vm->getGameType() == kGameTypeAdibou2)
+		video->properties.startFrame = video->decoder->getCurFrame();
+
 	if (video->properties.startFrame >= (int32)(video->decoder->getFrameCount() - 1)) {
 		// Video ended
 
@@ -522,7 +534,8 @@ void VideoPlayer::updateLive(int slot, bool force) {
 		}
 	}
 
-	if (video->properties.startFrame == video->properties.lastFrame)
+	if (video->properties.startFrame == video->properties.lastFrame
+		&& _vm->getGameType() != kGameTypeAdibou2)
 		// Current video sequence ended
 		return;
 
@@ -536,7 +549,10 @@ void VideoPlayer::updateLive(int slot, bool force) {
 	bool backwards = video->properties.startFrame > video->properties.lastFrame;
 	playFrame(slot, video->properties);
 
-	video->properties.startFrame += backwards ? -1 : 1;
+	if (_vm->getGameType() == kGameTypeAdibou2)
+		video->properties.startFrame = video->decoder->getCurFrame();
+	else
+		video->properties.startFrame += backwards ? -1 : 1;
 
 	if (_vm->getGameType() == kGameTypeAdibou2 && slot < kVideoSlotWithCurFrameVarCount)
 		WRITE_VAR(53 + slot, video->decoder->getCurFrame());
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index 27052fe322e..f4ed0f5399f 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -136,7 +136,7 @@ public:
 	bool isPlayingLive() const;
 	bool isSoundPlaying() const;
 
-	void updateLive(bool force = false);
+	void updateLive(bool force = false, int exceptSlot = -1);
 
 	bool slotIsOpen(int slot = 0) const;
 
@@ -188,7 +188,7 @@ private:
 
 	static const int kVideoSlotCount = 32;
 	static const int kPrimaryVideoSlot = 0;
-	static const int kLiveVideoSlotCount = 5;
+	static const int kLiveVideoSlotCount = 6;
 	static const int kVideoSlotWithCurFrameVarCount = 4;
 
 	static const char *const _extensions[];


Commit: f855e7cbc1335132d0aa880393752a81b47753f4
    https://github.com/scummvm/scummvm/commit/f855e7cbc1335132d0aa880393752a81b47753f4
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix default start and last frames for "live" videos

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 168347ff147..bf58de58841 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -693,6 +693,7 @@ void Inter_v7::o7_playVmdOrMusic() {
 
 			props.lastFrame += 100;
 		}
+
 		if (props.lastFrame <= -20)
 			props.noBlock    = true;
 		//if (!(props.flags & VideoPlayer::kFlagNoVideo))
@@ -701,11 +702,8 @@ void Inter_v7::o7_playVmdOrMusic() {
 		props.slot = (-props.lastFrame) % 10;
 	}
 
-	if (props.startFrame == -2) {
-		props.startFrame = 0;
-		props.lastFrame  = -1;
+	if (props.startFrame == -2)
 		props.noBlock    = true;
-	}
 
 	_vm->_vidPlayer->evaluateFlags(props);
 
@@ -720,11 +718,14 @@ void Inter_v7::o7_playVmdOrMusic() {
 	}
 
 	if (_vm->_vidPlayer->getVideoBufferSize(slot) == 0 || !_vm->_vidPlayer->hasVideo(slot)) {
-		props.startFrame = 0;
-		props.lastFrame  = -1;
 		props.noBlock = true;
 	}
 
+	if (props.startFrame == -2 || props.startFrame == -3) {
+		props.startFrame = 0;
+		props.lastFrame  = 0;
+	}
+
 	if (props.hasSound)
 		_vm->_vidPlayer->closeLiveSound();
 


Commit: 374e6857660017ea48c660f3f54ba67e32c520e8
    https://github.com/scummvm/scummvm/commit/374e6857660017ea48c660f3f54ba67e32c520e8
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Seek to arbitrary frame is not allowed for non-blocking videos

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 4085b4af3b6..28ad1b0792c 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -571,6 +571,8 @@ bool VideoPlayer::playFrame(int slot, Properties &properties) {
 	bool primary = slot == 0;
 
 	if (video->decoder->getCurFrame() != properties.startFrame) {
+		if (video->live && _vm->getGameType() == kGameTypeAdibou2)
+			return true;
 
 		if (properties.startFrame != -1) {
 			// Seek into the middle of the video


Commit: 02003553ea01f63cc09ba490b4e16ec279a7f6de
    https://github.com/scummvm/scummvm/commit/02003553ea01f63cc09ba490b4e16ec279a7f6de
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix live video current frame update in video playback

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 28ad1b0792c..d60ca852444 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -421,6 +421,9 @@ bool VideoPlayer::play(int slot, Properties &properties) {
 	while ((properties.startFrame != properties.lastFrame) &&
 	       (properties.startFrame < (int32)(video->decoder->getFrameCount() - 1))) {
 
+		if (_vm->getGameType() == kGameTypeAdibou2 && video->live)
+			properties.startFrame = video->decoder->getCurFrame();
+
 		playFrame(slot, properties);
 		if (properties.canceled)
 			break;


Commit: fc88883e5895319bf9947dacf6f2bbcf8d376b7a
    https://github.com/scummvm/scummvm/commit/fc88883e5895319bf9947dacf6f2bbcf8d376b7a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Allow to "play" frames beyond last frame as a no-op in video playback

Some Adibou2/Sciences scripts rely on this, by waiting for the "current frame" variable to take the value "last frame + 2"

Changed paths:
    engines/gob/videoplayer.cpp
    video/coktel_decoder.cpp
    video/coktel_decoder.h


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index d60ca852444..5451df0e942 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -421,8 +421,10 @@ bool VideoPlayer::play(int slot, Properties &properties) {
 	while ((properties.startFrame != properties.lastFrame) &&
 	       (properties.startFrame < (int32)(video->decoder->getFrameCount() - 1))) {
 
-		if (_vm->getGameType() == kGameTypeAdibou2 && video->live)
-			properties.startFrame = video->decoder->getCurFrame();
+		if (_vm->getGameType() == kGameTypeAdibou2 && video->live) {
+			properties.startFrame = video->decoder->getCurFrame() +
+									video->decoder->getNbFramesPastEnd();
+		}
 
 		playFrame(slot, properties);
 		if (properties.canceled)
@@ -508,7 +510,7 @@ void VideoPlayer::updateLive(int slot, bool force) {
 		return;
 
 	if (_vm->getGameType() == kGameTypeAdibou2 && slot < kVideoSlotWithCurFrameVarCount)
-		WRITE_VAR(53 + slot, video->decoder->getCurFrame());
+		WRITE_VAR(53 + slot, video->decoder->getCurFrame() + video->decoder->getNbFramesPastEnd());
 
 	int nbrOfLiveVideos = 0;
 	for (int i = 0; i < kVideoSlotCount; i++) {
@@ -558,7 +560,7 @@ void VideoPlayer::updateLive(int slot, bool force) {
 		video->properties.startFrame += backwards ? -1 : 1;
 
 	if (_vm->getGameType() == kGameTypeAdibou2 && slot < kVideoSlotWithCurFrameVarCount)
-		WRITE_VAR(53 + slot, video->decoder->getCurFrame());
+		WRITE_VAR(53 + slot, video->decoder->getCurFrame() + video->decoder->getNbFramesPastEnd());
 
 	if (video->properties.fade) {
 		_vm->_palAnim->fade(_vm->_global->_pPaletteDesc, -2, 0);
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index d9c565a7b25..0945b1b1930 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -57,7 +57,7 @@ CoktelDecoder::State::State() : flags(0), speechId(0) {
 
 CoktelDecoder::CoktelDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) :
 	_mixer(mixer), _soundType(soundType), _width(0), _height(0), _x(0), _y(0),
-	_defaultX(0), _defaultY(0), _features(0), _frameCount(0), _palette(256), _paletteDirty(false),
+	_defaultX(0), _defaultY(0), _features(0), _nbFramesPastEnd(0), _frameCount(0), _palette(256), _paletteDirty(false),
 	_highColorMap(nullptr), _isDouble(false), _ownSurface(true), _frameRate(12), _hasSound(false),
 	_soundEnabled(false), _soundStage(kSoundNone), _audioStream(0), _startTime(0),
 	_pauseStartTime(0), _isPaused(false) {
@@ -295,6 +295,10 @@ int CoktelDecoder::getCurFrame() const {
 	return _curFrame;
 }
 
+int CoktelDecoder::getNbFramesPastEnd() const {
+	return _nbFramesPastEnd;
+}
+
 void CoktelDecoder::close() {
 	disableSound();
 	freeSurface();
@@ -308,6 +312,7 @@ void CoktelDecoder::close() {
 	_features = 0;
 
 	_curFrame   = -1;
+	_nbFramesPastEnd = 0;
 	_frameCount =  0;
 
 	_startTime = 0;
@@ -830,6 +835,7 @@ bool PreIMDDecoder::seek(int32 frame, int whence, bool restart) {
 
 	// Run through the frames
 	_curFrame = -1;
+	_nbFramesPastEnd = 0;
 	_stream->seek(2);
 	while (_curFrame != frame) {
 		uint16 frameSize = _stream->readUint16LE();
@@ -1065,6 +1071,7 @@ bool IMDDecoder::seek(int32 frame, int whence, bool restart) {
 		// audio to worry about, restart the video and run through the frames
 
 		_curFrame = 0;
+		_nbFramesPastEnd = 0;
 		_stream->seek(_firstFramePos);
 
 		for (int i = ((frame > _curFrame) ? _curFrame : 0); i <= frame; i++)
@@ -1082,6 +1089,7 @@ bool IMDDecoder::seek(int32 frame, int whence, bool restart) {
 	// Seek
 	_stream->seek(framePos);
 	_curFrame = frame;
+	_nbFramesPastEnd = 0;
 
 	return true;
 }
@@ -1456,6 +1464,7 @@ void IMDDecoder::processFrame() {
 			int16 frame = _stream->readSint16LE();
 			if (_framePos) {
 				_curFrame = frame - 1;
+				_nbFramesPastEnd = 0;
 				_stream->seek(_framePos[frame]);
 
 				hasNextCmd = true;
@@ -1866,6 +1875,7 @@ bool VMDDecoder::seek(int32 frame, int whence, bool restart) {
 		if (_curFrame > frame) {
 			_stream->seek(_frames[0].offset);
 			_curFrame = -1;
+			_nbFramesPastEnd = 0;
 		}
 
 		while (frame > _curFrame)
@@ -1877,6 +1887,7 @@ bool VMDDecoder::seek(int32 frame, int whence, bool restart) {
 	// Seek
 	_stream->seek(_frames[frame + 1].offset);
 	_curFrame = frame;
+	_nbFramesPastEnd = 0;
 	_startTime = g_system->getMillis() - ((frame + 2) * getStaticTimeToNextFrame());
 
 
@@ -2351,8 +2362,10 @@ bool VMDDecoder::isVideoLoaded() const {
 }
 
 const Graphics::Surface *VMDDecoder::decodeNextFrame() {
-	if (!isVideoLoaded() || endOfVideo())
-		return 0;
+	if (!isVideoLoaded() || endOfVideo()) {
+		++_nbFramesPastEnd;
+		return nullptr;
+	}
 
 	createSurface();
 
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 0dfc24d7f32..fee32987555 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -166,6 +166,7 @@ public:
 	 * @see VideoDecoder::getCurFrame()
 	 */
 	int getCurFrame() const;
+	int getNbFramesPastEnd() const;
 
 	/**
 	 * Decode the next frame
@@ -244,6 +245,7 @@ protected:
 	uint32 _features;
 
 	 int32 _curFrame;
+	 int32 _nbFramesPastEnd;
 	uint32 _frameCount;
 
 	uint32 _startTime;


Commit: c4515b6f1f1edf08fe22b422be6e64ed85c9cf74
    https://github.com/scummvm/scummvm/commit/c4515b6f1f1edf08fe22b422be6e64ed85c9cf74
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Show cursor while a video is playing for Adibou2

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 5451df0e942..45a915c130f 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -183,7 +183,11 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 		videoFile.toUppercase();
 		if (videoFile.hasSuffix(".IMD"))
 			videoFile = videoFile.substr(0, videoFile.find('.'));
-		_noCursorSwitch = false;
+
+		if (_vm->getGameType() == kGameTypeAdibou2)
+			_noCursorSwitch = true; // For Adibou2, we want to see the cursor, on the contrary
+		else
+			_noCursorSwitch = false;
 
 		if (primary && (_vm->getGameType() == kGameTypeLostInTime)) {
 			static const Common::StringArray videosWithCursorLIT = {


Commit: 5c4e0bbb122f25bbd59154d7631a5b575b6bad07
    https://github.com/scummvm/scummvm/commit/5c4e0bbb122f25bbd59154d7631a5b575b6bad07
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add synchronization in playFrame (Adibou2/Sciences)

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 45a915c130f..7e3443430c1 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -430,8 +430,15 @@ bool VideoPlayer::play(int slot, Properties &properties) {
 									video->decoder->getNbFramesPastEnd();
 		}
 
-		playFrame(slot, properties);
-		if (properties.canceled)
+		bool playFrameResult = playFrame(slot, properties);
+		if (_vm->getGameType() == kGameTypeAdibou2 && !playFrameResult && slot < kLiveVideoSlotCount) {
+			_vm->_util->processInput();
+			_vm->_video->retrace();
+			_vm->_util->delay(5);
+			continue;
+		}
+
+			if (properties.canceled)
 			break;
 
 		if (_vm->getGameType() == kGameTypeAdibou2) {
@@ -610,6 +617,9 @@ bool VideoPlayer::playFrame(int slot, Properties &properties) {
 
 	}
 
+	if (_vm->getGameType() == kGameTypeAdibou2 && video->decoder->getTimeToNextFrame() > 0)
+		return false;
+
 	if (video->decoder->getCurFrame() > properties.startFrame)
 		// If the video is already beyond the wanted frame, skip
 		return true;


Commit: 4f06a5f3c43501008fcbd65a35558728eaebe5df
    https://github.com/scummvm/scummvm/commit/4f06a5f3c43501008fcbd65a35558728eaebe5df
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix conditions for loop videos

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index bf58de58841..d8bb66f1b4d 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -598,6 +598,11 @@ void Inter_v7::o7_playVmdOrMusic() {
 	props.palEnd     = _vm->_game->_script->readValExpr();
 	props.palCmd     = 1 << (props.flags & 0x3F);
 	props.forceSeek  = true;
+	int startFrameCopy = props.startFrame;
+
+	if (props.startFrame == -20 || props.startFrame == -200) {
+		props.startFrame = -2;
+	}
 
 	debugC(1, kDebugVideo, "Playing video \"%s\" @ %d+%d, frames %d - %d, "
 			"paletteCmd %d (%d - %d), flags %X", file.c_str(),
@@ -696,8 +701,6 @@ void Inter_v7::o7_playVmdOrMusic() {
 
 		if (props.lastFrame <= -20)
 			props.noBlock    = true;
-		//if (!(props.flags & VideoPlayer::kFlagNoVideo))
-		//	props.loop = true;
 
 		props.slot = (-props.lastFrame) % 10;
 	}
@@ -705,6 +708,13 @@ void Inter_v7::o7_playVmdOrMusic() {
 	if (props.startFrame == -2)
 		props.noBlock    = true;
 
+	if ((props.slot == 1 && !(props.flags & VideoPlayer::kFlagNoVideo)) ||
+		startFrameCopy == -20) {
+		props.loop = true;
+		// TODO: one more mode is missing if start frame == -200
+		// In that case, the video is not closed at the end, but does not loop either
+	}
+
 	_vm->_vidPlayer->evaluateFlags(props);
 
 	bool primary = true;


Commit: fd080b2bf82c4f99b73e936fa935cdaa0cdcb47b
    https://github.com/scummvm/scummvm/commit/fd080b2bf82c4f99b73e936fa935cdaa0cdcb47b
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix conditions for closing live videos in Adibou2/Sciences

Changed paths:
    engines/gob/inter_v6.cpp
    engines/gob/inter_v7.cpp
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h


diff --git a/engines/gob/inter_v6.cpp b/engines/gob/inter_v6.cpp
index 0c298ffd429..39e70a05745 100644
--- a/engines/gob/inter_v6.cpp
+++ b/engines/gob/inter_v6.cpp
@@ -177,7 +177,7 @@ void Inter_v6::o6_playVmdOrMusic() {
 	}
 
 	if (props.hasSound)
-		_vm->_vidPlayer->closeLiveSound();
+		_vm->_vidPlayer->closeLiveVideos();
 
 	if (props.startFrame >= 0)
 		_vm->_vidPlayer->play(slot, props);
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index d8bb66f1b4d..30f403c3cf0 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -610,7 +610,7 @@ void Inter_v7::o7_playVmdOrMusic() {
 			props.palCmd, props.palStart, props.palEnd, props.flags);
 
 	if (file == "RIEN") {
-		_vm->_vidPlayer->closeLiveSound();
+		_vm->_vidPlayer->closeLiveVideos();
 		return;
 	}
 
@@ -734,15 +734,13 @@ void Inter_v7::o7_playVmdOrMusic() {
 	if (props.startFrame == -2 || props.startFrame == -3) {
 		props.startFrame = 0;
 		props.lastFrame  = 0;
+		close = false;
 	}
 
-	if (props.hasSound)
-		_vm->_vidPlayer->closeLiveSound();
-
 	if (props.startFrame >= 0)
 		_vm->_vidPlayer->play(slot, props);
 
-	if (close && !props.noBlock) {
+	if (close) {
 		if (!props.canceled)
 			_vm->_vidPlayer->waitSoundEnd(slot);
 		_vm->_vidPlayer->closeVideo(slot);
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 7e3443430c1..806f934e42f 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -304,7 +304,7 @@ bool VideoPlayer::closeVideo(int slot) {
 	return true;
 }
 
-void VideoPlayer::closeLiveSound() {
+void VideoPlayer::closeLiveVideos() {
 	for (int i = 1; i < kVideoSlotCount; i++) {
 		Video *video = getVideoBySlot(i);
 		if (!video)
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index f4ed0f5399f..55cb280cbfe 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -117,7 +117,7 @@ public:
 	int  openVideo(bool primary, const Common::String &file, Properties &properties);
 	bool closeVideo(int slot = 0);
 
-	void closeLiveSound();
+	void closeLiveVideos();
 	void closeAll();
 
 	bool reopenVideo(int slot = 0);


Commit: 9bb120aca2ca07d4607af98ea09a6d879caf8983
    https://github.com/scummvm/scummvm/commit/9bb120aca2ca07d4607af98ea09a6d879caf8983
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix handling of "empty" video filenames, meaning "close video"

Changed paths:
    engines/gob/inter_v7.cpp
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 30f403c3cf0..47104dd9a4c 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -609,11 +609,6 @@ void Inter_v7::o7_playVmdOrMusic() {
 			props.x, props.y, props.startFrame, props.lastFrame,
 			props.palCmd, props.palStart, props.palEnd, props.flags);
 
-	if (file == "RIEN") {
-		_vm->_vidPlayer->closeLiveVideos();
-		return;
-	}
-
 	bool close = false;
 	if (props.lastFrame == -1) {
 		close = true;
@@ -689,8 +684,6 @@ void Inter_v7::o7_playVmdOrMusic() {
 		_vm->_sound->bgPlay(file.c_str(), SOUND_WAV);
 		return;
 	} else if (props.lastFrame <= -10) {
-		_vm->_vidPlayer->closeVideo();
-
 		if (props.lastFrame <= -100) {
 			// The original does an early return here if the video is not in the cache
 			// if (video not in cache)
@@ -721,8 +714,8 @@ void Inter_v7::o7_playVmdOrMusic() {
 	if (props.noBlock && (props.flags & VideoPlayer::kFlagNoVideo))
 		primary = false;
 
-	int slot = 0;
-	if (!file.empty() && ((slot = _vm->_vidPlayer->openVideo(primary, file, props)) < 0)) {
+	int slot = _vm->_vidPlayer->openVideo(primary, file, props);
+	if (slot < 0) {
 		WRITE_VAR(11, (uint32) -1);
 		return;
 	}
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 806f934e42f..8d5d63ff81d 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -155,6 +155,13 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 	if (!video->isEmpty() && (video->fileName.compareToIgnoreCase(file) != 0))
 		video->close();
 
+	if (_vm->getGameType() == kGameTypeAdibou2 &&
+		(file.empty() || file.equalsIgnoreCase("RIEN"))) {
+		// An empty filename means that we just need to close any existing video in the slot
+		// "RIEN" (French for "nothing") is an alias for that
+		return -1;
+	}
+
 	// No video => load the requested file
 	if (video->isEmpty()) {
 		// Open the video


Commit: c2406ca7ca7e7a4cd8ee0a7c92271cad99a04e97
    https://github.com/scummvm/scummvm/commit/c2406ca7ca7e7a4cd8ee0a7c92271cad99a04e97
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Another condition preventing a video to be advanced by updateLive

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 8d5d63ff81d..20a972c3c35 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -537,8 +537,13 @@ void VideoPlayer::updateLive(int slot, bool force) {
 			++nbrOfLiveVideos;
 	}
 
-	if (_vm->getGameType() == kGameTypeAdibou2)
+	if (_vm->getGameType() == kGameTypeAdibou2) {
+		if (video->decoder->hasVideo() &&
+			!(video->properties.flags & 0x100))
+			return;
+
 		video->properties.startFrame = video->decoder->getCurFrame();
+	}
 
 	if (video->properties.startFrame >= (int32)(video->decoder->getFrameCount() - 1)) {
 		// Video ended


Commit: ace2143af49897d24824eac626e80835cfaa952c
    https://github.com/scummvm/scummvm/commit/ace2143af49897d24824eac626e80835cfaa952c
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add stubs for Adibou2/Anglais

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 47c734d469d..1d3af5a459c 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -722,16 +722,37 @@ protected:
 	void o7_setActiveCD();
 	void o7_findFile();
 	void o7_findNextFile();
+	void o7_getImageInfo();
 	void o7_getSystemProperty();
 	void o7_loadImage();
 	void o7_setVolume();
 	void o7_zeroVar();
+	void o7_draw0xA0();
 	void o7_getINIValue();
 	void o7_setINIValue();
 	void o7_loadIFFPalette();
+	void o7_draw0xAA();
+	void o7_draw0xAC();
+	void o7_draw0xAF();
+	void o7_draw0xB0();
+	void o7_draw0xB1();
+	void o7_draw0xB4();
+	void o7_draw0xB6();
 	void o7_opendBase();
 	void o7_closedBase();
 	void o7_getDBString();
+	void o7_draw0xCC();
+	void o7_draw0xCD();
+	void o7_draw0xCE();
+	void o7_draw0xDC();
+	void o7_draw0xDD();
+	void o7_draw0xDE();
+	void o7_draw0xDF();
+	void o7_draw0xE0();
+	void o7_draw0xE1();
+	void o7_draw0xE2();
+	void o7_draw0xE3();
+	void o7_draw0xE4();
 
 	void o7_loadCursor(OpFuncParams &params);
 	void o7_printText(OpFuncParams &params);
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 47104dd9a4c..bf992630dd9 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -85,15 +85,36 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0x8A, o7_findFile);
 	OPCODEDRAW(0x8B, o7_findNextFile);
 	OPCODEDRAW(0x8C, o7_getSystemProperty);
+	OPCODEDRAW(0x8E, o7_getImageInfo);
 	OPCODEDRAW(0x90, o7_loadImage);
 	OPCODEDRAW(0x93, o7_setVolume);
 	OPCODEDRAW(0x95, o7_zeroVar);
+	OPCODEDRAW(0xA0, o7_draw0xA0);
 	OPCODEDRAW(0xA1, o7_getINIValue);
 	OPCODEDRAW(0xA2, o7_setINIValue);
 	OPCODEDRAW(0xA4, o7_loadIFFPalette);
+	OPCODEDRAW(0xAA, o7_draw0xAA);
+	OPCODEDRAW(0xAC, o7_draw0xAC);
+	OPCODEDRAW(0xAF, o7_draw0xAF);
+	OPCODEDRAW(0xB0, o7_draw0xB0);
+	OPCODEDRAW(0xB1, o7_draw0xB1);
+	OPCODEDRAW(0xB4, o7_draw0xB4);
+	OPCODEDRAW(0xB6, o7_draw0xB6);
 	OPCODEDRAW(0xC4, o7_opendBase);
 	OPCODEDRAW(0xC5, o7_closedBase);
 	OPCODEDRAW(0xC6, o7_getDBString);
+	OPCODEDRAW(0xCC, o7_draw0xCC);
+	OPCODEDRAW(0xCD, o7_draw0xCD);
+	OPCODEDRAW(0xCE, o7_draw0xCE);
+	OPCODEDRAW(0xDC, o7_draw0xDC);
+	OPCODEDRAW(0xDD, o7_draw0xDD);
+	OPCODEDRAW(0xDE, o7_draw0xDE);
+	OPCODEDRAW(0xDF, o7_draw0xDF);
+	OPCODEDRAW(0xE0, o7_draw0xE0);
+	OPCODEDRAW(0xE1, o7_draw0xE1);
+	OPCODEDRAW(0xE2, o7_draw0xE2);
+	OPCODEDRAW(0xE3, o7_draw0xE3);
+	OPCODEDRAW(0xE4, o7_draw0xE4);
 }
 
 void Inter_v7::setupOpcodesFunc() {
@@ -850,6 +871,13 @@ void Inter_v7::o7_getSystemProperty() {
 	storeValue(0);
 }
 
+void Inter_v7::o7_getImageInfo() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+}
+
 void Inter_v7::o7_loadImage() {
 	Common::String file = getFile(_vm->_game->_script->evalString());
 	if (!file.contains('.'))
@@ -905,6 +933,13 @@ void Inter_v7::o7_zeroVar() {
 	WRITE_VARO_UINT32(index, 0);
 }
 
+void Inter_v7::o7_draw0xA0() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readValExpr();
+}
+
+
 void Inter_v7::o7_getINIValue() {
 	Common::String file = getFile(_vm->_game->_script->evalString());
 
@@ -982,6 +1017,50 @@ void Inter_v7::o7_loadIFFPalette() {
 	_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
 }
 
+void Inter_v7::o7_draw0xAA() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xAA (Adibou/Anglais)");
+}
+
+void Inter_v7::o7_draw0xAC() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xAC (Adibou/Anglais)");
+}
+
+void Inter_v7::o7_draw0xAF() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xAF (Adibou/Anglais)");
+}
+
+void Inter_v7::o7_draw0xB0() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+}
+
+void Inter_v7::o7_draw0xB1() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+}
+
+void Inter_v7::o7_draw0xB4() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+}
+
+void Inter_v7::o7_draw0xB6() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+}
+
 void Inter_v7::o7_opendBase() {
 	Common::String dbFile = getFile(_vm->_game->_script->evalString());
 	Common::String id     = _vm->_game->_script->evalString();
@@ -1023,6 +1102,65 @@ void Inter_v7::o7_getDBString() {
 	WRITE_VAR(27, 1); // Success
 }
 
+void Inter_v7::o7_draw0xCC() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xCC (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xCD() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xCD (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xCE() {
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xCE (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xDC() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xDC (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xDD() {
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xDD (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xDE() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xDE (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xDF() {
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xDF (Adibou/Anglais)");
+}
+
+void Inter_v7::o7_draw0xE0() {
+	warning("STUB: o7_draw0xE0 (Adibou/Anglais)");
+}
+
+void Inter_v7::o7_draw0xE1() {
+	warning("STUB: o7_draw0xE1 (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xE2() {
+	warning("STUB: o7_draw0xE2 (Adibou/Anglais)");
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+}
+void Inter_v7::o7_draw0xE3() {
+	warning("STUB: o7_draw0xE3 (Adibou/Anglais)");
+}
+void Inter_v7::o7_draw0xE4() {
+	warning("STUB: o7_draw0xE4 (Adibou/Anglais)");
+}
+
+
 void Inter_v7::o7_printText(OpFuncParams &params) {
 	char buf[60];
 	_vm->_draw->_destSpriteX = _vm->_game->_script->readValExpr();


Commit: 365a5827cb5d45f712ee20d1f9f2cfcb45c1af23
    https://github.com/scummvm/scummvm/commit/365a5827cb5d45f712ee20d1f9f2cfcb45c1af23
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add stubs for Adibou/Musique

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 1d3af5a459c..e62eb071f40 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -733,6 +733,7 @@ protected:
 	void o7_loadIFFPalette();
 	void o7_draw0xAA();
 	void o7_draw0xAC();
+	void o7_draw0xAE();
 	void o7_draw0xAF();
 	void o7_draw0xB0();
 	void o7_draw0xB1();
@@ -753,6 +754,10 @@ protected:
 	void o7_draw0xE2();
 	void o7_draw0xE3();
 	void o7_draw0xE4();
+	void o7_draw0xE6();
+	void o7_draw0xE7();
+	void o7_vmdGetSoundBuffer();
+	void o7_vmdReleaseSoundBuffer();
 
 	void o7_loadCursor(OpFuncParams &params);
 	void o7_printText(OpFuncParams &params);
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index bf992630dd9..09a7b4e4b99 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -95,6 +95,7 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xA4, o7_loadIFFPalette);
 	OPCODEDRAW(0xAA, o7_draw0xAA);
 	OPCODEDRAW(0xAC, o7_draw0xAC);
+	OPCODEDRAW(0xAE, o7_draw0xAE);
 	OPCODEDRAW(0xAF, o7_draw0xAF);
 	OPCODEDRAW(0xB0, o7_draw0xB0);
 	OPCODEDRAW(0xB1, o7_draw0xB1);
@@ -115,6 +116,10 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xE2, o7_draw0xE2);
 	OPCODEDRAW(0xE3, o7_draw0xE3);
 	OPCODEDRAW(0xE4, o7_draw0xE4);
+	OPCODEDRAW(0xE6, o7_draw0xE6);
+	OPCODEDRAW(0xE7, o7_draw0xE7);
+	OPCODEDRAW(0xFA, o7_vmdGetSoundBuffer);
+	OPCODEDRAW(0xFB, o7_vmdReleaseSoundBuffer);
 }
 
 void Inter_v7::setupOpcodesFunc() {
@@ -1030,6 +1035,12 @@ void Inter_v7::o7_draw0xAC() {
 	warning("STUB: o7_draw0xAC (Adibou/Anglais)");
 }
 
+void Inter_v7::o7_draw0xAE() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xAE (Adibou/Musique)");
+}
+
 void Inter_v7::o7_draw0xAF() {
 	_vm->_game->_script->readValExpr();
 	_vm->_game->_script->readValExpr();
@@ -1160,6 +1171,30 @@ void Inter_v7::o7_draw0xE4() {
 	warning("STUB: o7_draw0xE4 (Adibou/Anglais)");
 }
 
+void Inter_v7::o7_draw0xE6() {
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xE6 (Adibou/Musique)");
+};
+
+void Inter_v7::o7_draw0xE7() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xE7 (Adibou/Musique)");
+};
+
+void Inter_v7::o7_vmdGetSoundBuffer() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_vmdGetSoundBuffer (Adibou/Musique)");
+};
+
+void Inter_v7::o7_vmdReleaseSoundBuffer() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_vmdReleaseSoundBuffer (Adibou/Musique)");
+};
+
 
 void Inter_v7::o7_printText(OpFuncParams &params) {
 	char buf[60];


Commit: 1b9ae2bd91cca3c974bd39a752675424b7358803
    https://github.com/scummvm/scummvm/commit/1b9ae2bd91cca3c974bd39a752675424b7358803
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: More video-related fixes for Adibou2/Sciences

Changed paths:
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 20a972c3c35..6995b33ca4d 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -376,6 +376,15 @@ void VideoPlayer::waitSoundEnd(int slot) {
 		_vm->_util->longDelay(1);
 }
 
+bool VideoPlayer::lastFrameReached(Video &video, Properties &properties) {
+	if (_vm->getGameType() == kGameTypeAdibou2) {
+		return properties.startFrame >= properties.lastFrame;
+	} else {
+		return (properties.startFrame == properties.lastFrame ||
+				properties.startFrame >= (int32)(video.decoder->getFrameCount() - 1));
+	}
+}
+
 bool VideoPlayer::play(int slot, Properties &properties) {
 	Video *video = getVideoBySlot(slot);
 	if (!video)
@@ -429,8 +438,7 @@ bool VideoPlayer::play(int slot, Properties &properties) {
 	if (_vm->_draw->_renderFlags & RENDERFLAG_DOUBLEVIDEO)
 		video->decoder->setDouble(true);
 
-	while ((properties.startFrame != properties.lastFrame) &&
-	       (properties.startFrame < (int32)(video->decoder->getFrameCount() - 1))) {
+	while (!lastFrameReached(*video, properties)) {
 
 		if (_vm->getGameType() == kGameTypeAdibou2 && video->live) {
 			properties.startFrame = video->decoder->getCurFrame() +
@@ -596,7 +604,9 @@ bool VideoPlayer::playFrame(int slot, Properties &properties) {
 	if (!video)
 		return false;
 
-	bool primary = slot == 0;
+	bool primary = slot == 0 ||
+				   (_vm->getGameType() == kGameTypeAdibou2 &&
+					slot < kLiveVideoSlotCount);
 
 	if (video->decoder->getCurFrame() != properties.startFrame) {
 		if (video->live && _vm->getGameType() == kGameTypeAdibou2)
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index 55cb280cbfe..f226d3ea5d0 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -214,6 +214,7 @@ private:
 
 	bool reopenVideo(Video &video);
 
+	bool lastFrameReached(Video &video, Properties &properties);
 	bool playFrame(int slot, Properties &properties);
 
 	void checkAbort(Video &video, Properties &properties);


Commit: 74ef032a1ff7a493f8eddc5edcf6e2d938b571c6
    https://github.com/scummvm/scummvm/commit/74ef032a1ff7a493f8eddc5edcf6e2d938b571c6
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add o7_getFreeDiskSpace opcode

Used to give the available hard disk space (faked for ScummVM). Needed by Adibou2/Sciences in-game "installation" step.

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index e62eb071f40..304f4a35964 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -770,6 +770,7 @@ protected:
 
 	void o7_oemToANSI(OpGobParams &params);
 	void o7_gob0x201(OpGobParams &params);
+	void o7_getFreeDiskSpace(OpGobParams &params);
 
 private:
 	INIConfig _inis;
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 09a7b4e4b99..4101383bd19 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -140,6 +140,7 @@ void Inter_v7::setupOpcodesGob() {
 
 	OPCODEGOB(420, o7_oemToANSI);
 	OPCODEGOB(513, o7_gob0x201);
+	OPCODEGOB(600, o7_getFreeDiskSpace);
 }
 
 void Inter_v7::o7_draw0x0C() {
@@ -1626,4 +1627,11 @@ void Inter_v7::o7_gob0x201(OpGobParams &params) {
 
 	WRITE_VAR(varIndex, 1);
 }
+
+void Inter_v7::o7_getFreeDiskSpace(OpGobParams &params) {
+	uint16 varIndex = _vm->_game->_script->readUint16();
+	WRITE_VAR(varIndex, 1000000000); // HACK
+}
+
+
 } // End of namespace Gob


Commit: 52a8822d38c05cf74cf49f231ebe5054595b1734
    https://github.com/scummvm/scummvm/commit/52a8822d38c05cf74cf49f231ebe5054595b1734
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix o7_draw0x0C opcode

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 4101383bd19..a7092910378 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -144,7 +144,7 @@ void Inter_v7::setupOpcodesGob() {
 }
 
 void Inter_v7::o7_draw0x0C() {
-	WRITE_VAR(17, 0);
+	WRITE_VAR(11, 0);
 }
 
 void Inter_v7::o7_loadCursor(OpFuncParams &params) {


Commit: ed0d7f1dfdd6cadf98c51e5ebaf5dc153250b6e2
    https://github.com/scummvm/scummvm/commit/ed0d7f1dfdd6cadf98c51e5ebaf5dc153250b6e2
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Display script function names in log when available

Changed paths:
    engines/gob/game.cpp
    engines/gob/game.h
    engines/gob/inter_v1.cpp


diff --git a/engines/gob/game.cpp b/engines/gob/game.cpp
index d991155318d..ea1cd8d72ed 100644
--- a/engines/gob/game.cpp
+++ b/engines/gob/game.cpp
@@ -453,6 +453,24 @@ bool TotFunctions::call(const Tot &tot, uint16 offset) const {
 	return true;
 }
 
+Common::String TotFunctions::getFunctionName(const Common::String &totFile, uint16 offset) const {
+	int index = find(totFile);
+	if (index < 0) {
+		warning("TotFunctions::getFunctionName(): No such TOT \"%s\"", totFile.c_str());
+		return "";
+	}
+
+	const Tot &tot = _tots[index];
+
+	Common::List<Function>::const_iterator it;
+	for (it = tot.functions.begin(); it != tot.functions.end(); ++it) {
+		if (it->offset == offset)
+			return it->name;
+	}
+
+	return "";
+}
+
 
 Game::Game(GobEngine *vm) : _vm(vm), _environments(_vm), _totFunctions(_vm) {
 	_captureCount = 0;
@@ -1042,4 +1060,11 @@ bool Game::callFunction(const Common::String &tot, const Common::String &functio
 	return _totFunctions.call(tot, function);
 }
 
+Common::String Game::getFunctionName(const Common::String &tot, uint16 offset) {
+	if (_totFunctions.find(tot) < 0)
+		loadFunctions(tot, 0);
+
+	return _totFunctions.getFunctionName(tot, offset);
+}
+
 } // End of namespace Gob
diff --git a/engines/gob/game.h b/engines/gob/game.h
index ba507514636..0f86ea6dc55 100644
--- a/engines/gob/game.h
+++ b/engines/gob/game.h
@@ -99,6 +99,7 @@ public:
 
 	bool call(const Common::String &totFile, const Common::String &function) const;
 	bool call(const Common::String &totFile, uint16 offset) const;
+	Common::String getFunctionName(const Common::String &totFile, uint16 offset) const;
 
 private:
 	static const uint8 kTotCount = 100;
@@ -184,6 +185,7 @@ public:
 
 	bool loadFunctions(const Common::String &tot, uint16 flags);
 	bool callFunction(const Common::String &tot, const Common::String &function, int16 param);
+	Common::String getFunctionName(const Common::String &tot, uint16 offset);
 
 protected:
 	GobEngine *_vm;
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index c41c69247a9..136fa47e6fb 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -25,6 +25,7 @@
  *
  */
 
+#include "common/debug-channels.h"
 #include "common/str.h"
 
 #include "gob/gob.h"
@@ -689,8 +690,20 @@ void Inter_v1::o1_freeFontToSprite() {
 void Inter_v1::o1_callSub(OpFuncParams &params) {
 	uint16 offset = _vm->_game->_script->readUint16();
 
-	debugC(5, kDebugGameFlow, "tot = \"%s\", offset = %d",
-			_vm->_game->_curTotFile.c_str(), offset);
+	if (gDebugLevel >= 5 && DebugMan.isDebugChannelEnabled(kDebugGameFlow)) {
+		Common::String functionName = _vm->_game->getFunctionName(_vm->_game->_curTotFile, offset);
+		Common::String functionNameInLog;
+
+		if (!functionName.empty()) {
+			functionNameInLog = Common::String::format(", function = \"%s\"",
+													   functionName.c_str());
+		}
+
+		debugC(5, kDebugGameFlow, "tot = \"%s\", offset = %d%s",
+			   _vm->_game->_curTotFile.c_str(),
+			   offset,
+			   functionNameInLog.c_str());
+	}
 
 	if (offset < 128) {
 		warning("Inter_v1::o1_callSub(): Offset %d points into the header. "


Commit: 5ad56b545c2fdc34ac333381990eec69008d0a83
    https://github.com/scummvm/scummvm/commit/5ad56b545c2fdc34ac333381990eec69008d0a83
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Handle special size 0 in read/write opcodes (save all variables)

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index a7092910378..b691a0602f9 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1519,6 +1519,11 @@ void Inter_v7::o7_readData(OpFuncParams &params) {
 	int32  offset  = _vm->_game->_script->evalInt();
 	int32  retSize = 0;
 
+	if (size == 0) {
+		dataVar = 0;
+		size = _vm->_game->_script->getVariablesCount() * 4;
+	}
+
 	debugC(2, kDebugFileIO, "Read from file \"%s\" (%d, %d bytes at %d)",
 		   file.c_str(), dataVar, size, offset);
 
@@ -1600,6 +1605,11 @@ void Inter_v7::o7_writeData(OpFuncParams &params) {
 
 	WRITE_VAR(1, 1);
 
+	if (size == 0) {
+		dataVar = 0;
+		size = _vm->_game->_script->getVariablesCount() * 4;
+	}
+
 	SaveLoad::SaveMode mode = _vm->_saveLoad ? _vm->_saveLoad->getSaveMode(file.c_str()) : SaveLoad::kSaveModeNone;
 	if (mode == SaveLoad::kSaveModeSave) {
 


Commit: 5de0026c00bc34d071e11919df23af2e55ccffd7
    https://github.com/scummvm/scummvm/commit/5de0026c00bc34d071e11919df23af2e55ccffd7
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Handle persistent INI files, stored as save games

Needed by Adibou2/Sciences

Changed paths:
    engines/gob/iniconfig.cpp
    engines/gob/iniconfig.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/iniconfig.cpp b/engines/gob/iniconfig.cpp
index 416bcf2f1f4..a6f65ae113e 100644
--- a/engines/gob/iniconfig.cpp
+++ b/engines/gob/iniconfig.cpp
@@ -25,11 +25,14 @@
  *
  */
 
+#include "common/memstream.h"
+
 #include "gob/iniconfig.h"
+#include "gob/save/saveload.h"
 
 namespace Gob {
 
-INIConfig::INIConfig() {
+INIConfig::INIConfig(GobEngine *vm) : _vm(vm) {
 }
 
 INIConfig::~INIConfig() {
@@ -66,6 +69,15 @@ bool INIConfig::setValue(const Common::String &file, const Common::String &secti
 			return false;
 
 	config.config->setKey(key, section, value);
+	SaveLoad::SaveMode mode = _vm->_saveLoad->getSaveMode(file.c_str());
+	if (mode == SaveLoad::kSaveModeSave) {
+		// Sync changes to save file
+		Common::MemoryWriteStreamDynamic stream(DisposeAfterUse::YES);
+		config.config->saveToStream(stream);
+
+		_vm->_saveLoad->saveFromRaw(file.c_str(), stream.getData(), stream.size(), 0);
+	}
+
 	return true;
 }
 
@@ -77,14 +89,44 @@ bool INIConfig::getConfig(const Common::String &file, Config &config) {
 	return true;
 }
 
+bool INIConfig::readConfigFromDisk(const Common::String &file, Gob::INIConfig::Config &config) {
+	SaveLoad::SaveMode mode = _vm->_saveLoad->getSaveMode(file.c_str());
+	if (mode == SaveLoad::kSaveModeSave) {
+		debugC(3, kDebugFileIO, "Loading INI from save file \"%s\"", file.c_str());
+		// Read from save file
+		int size = _vm->_saveLoad->getSize(file.c_str());
+		if (size < 0) {
+			debugC(3, kDebugFileIO, "Failed to get size of save file \"%s\"", file.c_str());
+			return false;
+		}
+
+		byte *data = new byte[size];
+		_vm->_saveLoad->loadToRaw(file.c_str(), data, size, 0);
+		Common::MemoryReadStream stream(data, size, DisposeAfterUse::YES);
+		if (!config.config->loadFromStream(stream)) {
+			debugC(3, kDebugFileIO, "Failed to load INI from save file \"%s\"", file.c_str());
+			return false;
+		}
+	} else {
+		// GOB uses \ as a path separator but
+		// it almost always manipulates base names
+		debugC(3, kDebugFileIO, "Loading INI from plain file \"%s\"", file.c_str());
+
+		if (!config.config->loadFromFile(Common::Path(file, '\\'))) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
 bool INIConfig::openConfig(const Common::String &file, Config &config) {
 	config.config  = new Common::INIFile();
 	config.config->allowNonEnglishCharacters();
 	config.created = false;
 
-	// GOB uses \ as a path separator but
-	// it almost always manipulates base names
-	if (!config.config->loadFromFile(Common::Path(file, '\\'))) {
+	if (!readConfigFromDisk(file, config)) {
 		delete config.config;
 		config.config = nullptr;
 		return false;
@@ -101,6 +143,8 @@ bool INIConfig::createConfig(const Common::String &file, Config &config) {
 	config.config->allowNonEnglishCharacters();
 	config.created = true;
 
+	readConfigFromDisk(file, config); // May return false in case we are dealing with a temporary file
+
 	_configs.setVal(file, config);
 
 	return true;
diff --git a/engines/gob/iniconfig.h b/engines/gob/iniconfig.h
index 2fa296a804f..5120ae65ce4 100644
--- a/engines/gob/iniconfig.h
+++ b/engines/gob/iniconfig.h
@@ -28,15 +28,17 @@
 #ifndef GOB_INICONFIG_H
 #define GOB_INICONFIG_H
 
-#include "common/str.h"
 #include "common/formats/ini-file.h"
 #include "common/hashmap.h"
+#include "common/str.h"
+
+#include "gob.h"
 
 namespace Gob {
 
 class INIConfig {
 public:
-	INIConfig();
+	INIConfig(GobEngine *vm);
 	~INIConfig();
 
 	bool getValue(Common::String &result, const Common::String &file,
@@ -54,10 +56,12 @@ private:
 
 	typedef Common::HashMap<Common::String, Config, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ConfigMap;
 
+	GobEngine *_vm;
 	ConfigMap _configs;
 
 	bool getConfig(const Common::String &file, Config &config);
 
+	bool readConfigFromDisk(const Common::String &file, Config &config);
 	bool openConfig(const Common::String &file, Config &config);
 	bool createConfig(const Common::String &file, Config &config);
 };
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index b691a0602f9..e98bc4325fb 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -58,7 +58,7 @@ namespace Gob {
 #define OPCODEFUNC(i, x)  _opcodesFunc[i]._OPCODEFUNC(OPCODEVER, x)
 #define OPCODEGOB(i, x)   _opcodesGob[i]._OPCODEGOB(OPCODEVER, x)
 
-Inter_v7::Inter_v7(GobEngine *vm) : Inter_Playtoons(vm) {
+Inter_v7::Inter_v7(GobEngine *vm) : Inter_Playtoons(vm), _inis(vm) {
 }
 
 void Inter_v7::setupOpcodesDraw() {


Commit: 09d90c3cea472629c803f248b6c450c4b1a7f14f
    https://github.com/scummvm/scummvm/commit/09d90c3cea472629c803f248b6c450c4b1a7f14f
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Distinguish CD paths from normal ones when using "copyFile" opcodes

Sometimes in Adibou2/Sciences, a read-only game file from the CD is used initialize a (modifiable) savegame file.

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_playtoons.cpp
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 304f4a35964..c2e97da6b2a 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -686,7 +686,7 @@ protected:
 	void oPlaytoons_copyFile();
 	void oPlaytoons_openItk();
 
-	Common::String getFile(const char *path, bool stripPath = true);
+	Common::String getFile(const char *path, bool stripPath = true, bool *isCd = nullptr);
 
 	bool readSprite(Common::String file, int32 dataVar, int32 size, int32 offset);
 };
@@ -780,7 +780,7 @@ private:
 	Common::String _currentCDPath;
 
 	Common::String findFile(const Common::String &mask, const Common::String &previousFile);
-	void copyFile(const Common::String &sourceFile, const Common::String &destFile);
+	void copyFile(const Common::String &sourceFile, bool sourceIsCd, const Common::String &destFile);
 
 	bool setCurrentCDPath(const Common::String &dir);
 	Common::Array<uint32> getAdibou2InstalledApplications();
diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp
index a3d3d305c30..e7979e974dc 100644
--- a/engines/gob/inter_playtoons.cpp
+++ b/engines/gob/inter_playtoons.cpp
@@ -463,19 +463,25 @@ void Inter_Playtoons::oPlaytoons_openItk() {
 	_vm->_dataIO->openArchive(file, false);
 }
 
-Common::String Inter_Playtoons::getFile(const char *path, bool stripPath) {
+Common::String Inter_Playtoons::getFile(const char *path, bool stripPath, bool *isCd) {
 	const char *orig = path;
 
 	if      (!strncmp(path, "@:\\", 3))
 		path += 3;
 	else if (!strncmp(path, "<ME>", 4))
 		path += 4;
-	else if (!strncmp(path, "<CD>", 4))
+	else if (!strncmp(path, "<CD>", 4)) {
 		path += 4;
+		if (isCd)
+			*isCd = true;
+	}
 	else if (!strncmp(path, "<STK>", 5))
 		path += 5;
-	else if (!strncmp(path, "<ALLCD>", 7))
+	else if (!strncmp(path, "<ALLCD>", 7)) {
 		path += 7;
+		if (isCd)
+			*isCd = true;
+	}
 
 	if (stripPath) {
 		const char *backslash = strrchr(path, '\\');
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index e98bc4325fb..2e9de321493 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -389,14 +389,14 @@ void Inter_v7::o7_loadFunctions() {
 	_vm->_game->loadFunctions(tot, flags);
 }
 
-void Inter_v7::copyFile(const Common::String &sourceFile, const Common::String &destFile) {
+void Inter_v7::copyFile(const Common::String &sourceFile, bool sourceIsCd, const Common::String &destFile) {
 	SaveLoad::SaveMode mode1 = _vm->_saveLoad->getSaveMode(sourceFile.c_str());
 	SaveLoad::SaveMode mode2 = _vm->_saveLoad->getSaveMode(destFile.c_str());
 
 	if (mode2 == SaveLoad::kSaveModeIgnore || mode2 == SaveLoad::kSaveModeExists)
 		return;
 	else if (mode2 == SaveLoad::kSaveModeSave) {
-		if (mode1 == SaveLoad::kSaveModeNone) {
+		if (mode1 == SaveLoad::kSaveModeNone || sourceIsCd) {
 			Common::SeekableReadStream *stream = _vm->_dataIO->getFile(sourceFile);
 			if (!stream)
 				return;
@@ -429,14 +429,15 @@ void Inter_v7::o7_copyFile() {
 
 	debugC(2, kDebugFileIO, "Copy file \"%s\" to \"%s", path1.c_str(), path2.c_str());
 
-	Common::String file1 = getFile(path1.c_str(), false);
-	Common::String file2 = getFile(path2.c_str(), false);
-	if (file1.equalsIgnoreCase(file2)) {
+	bool sourceIsCd = false;
+	Common::String file1 = getFile(path1.c_str(), true, &sourceIsCd);
+	Common::String file2 = getFile(path2.c_str());
+	if (!sourceIsCd && file1.equalsIgnoreCase(file2)) {
 		warning("o7_copyFile(): \"%s\" == \"%s\"", path1.c_str(), path2.c_str());
 		return;
 	}
 
-	copyFile(file1, file2);
+	copyFile(file1, sourceIsCd, file2);
 }
 
 void Inter_v7::o7_deleteFile() {
@@ -486,15 +487,16 @@ void Inter_v7::o7_moveFile() {
 	Common::String path1 = _vm->_game->_script->evalString();
 	Common::String path2 = _vm->_game->_script->evalString();
 
-	Common::String file1 = getFile(path1.c_str(), false);
-	Common::String file2 = getFile(path2.c_str(), false);
+	bool sourceIsCd = false;
+	Common::String file1 = getFile(path1.c_str(), true, &sourceIsCd);
+	Common::String file2 = getFile(path2.c_str());
 
-	if (file1.equalsIgnoreCase(file2)) {
+	if (!sourceIsCd && file1.equalsIgnoreCase(file2)) {
 		warning("o7_moveFile(): \"%s\" == \"%s\"", path1.c_str(), path2.c_str());
 		return;
 	}
 
-	copyFile(file1, file2);
+	copyFile(file1, sourceIsCd, file2);
 	SaveLoad::SaveMode mode = _vm->_saveLoad->getSaveMode(file1.c_str());
 	if (mode == SaveLoad::kSaveModeSave) {
 		_vm->_saveLoad->deleteFile(file1.c_str());
@@ -772,7 +774,7 @@ void Inter_v7::o7_setActiveCD() {
 	Common::String str1 = _vm->_game->_script->evalString();
 
 	Common::ArchiveMemberDetailsList files;
-	SearchMan.listMatchingMembers(files, Common::Path(str0));
+	SearchMan.listMatchingMembers(files, Common::Path(str0, '\\'));
 	Common::String savedCDpath = _currentCDPath;
 
 	for (Common::ArchiveMemberDetails file : files) {
@@ -796,10 +798,10 @@ void Inter_v7::o7_openItk() {
 }
 
 void Inter_v7::o7_findFile() {
-	Common::Path file_pattern(getFile(_vm->_game->_script->evalString()));
+	Common::Path filePattern(getFile(_vm->_game->_script->evalString()));
 	Common::ArchiveMemberList files;
 
-	SearchMan.listMatchingMembers(files, file_pattern);
+	SearchMan.listMatchingMembers(files, filePattern);
 	Common::ArchiveMemberList filesWithoutDuplicates;
 	for (Common::ArchiveMemberPtr file : files) {
 		bool found = false;
@@ -815,7 +817,7 @@ void Inter_v7::o7_findFile() {
 	}
 
 	debugC(5, kDebugFileIO, "o7_findFile(%s): %d matches (%d including duplicates)",
-		   file_pattern.toString().c_str(),
+		   filePattern.toString().c_str(),
 		   filesWithoutDuplicates.size(),
 		   files.size());
 
@@ -826,7 +828,7 @@ void Inter_v7::o7_findFile() {
 		Common::String file = files.front()->getName();
 		filesWithoutDuplicates.pop_front();
 		debugC(5, kDebugFileIO, "o7_findFile(%s): first match = %s",
-			   file_pattern.toString().c_str(),
+			   filePattern.toString().c_str(),
 			   file.c_str());
 
 		storeString(file.c_str());


Commit: 66d52b2ea4372a48efcb27bab400877a5a7b0aa9
    https://github.com/scummvm/scummvm/commit/66d52b2ea4372a48efcb27bab400877a5a7b0aa9
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Do not use "flat directory" logic with patterns in o7_findFile

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 2e9de321493..7442e0e0393 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -798,7 +798,10 @@ void Inter_v7::o7_openItk() {
 }
 
 void Inter_v7::o7_findFile() {
-	Common::Path filePattern(getFile(_vm->_game->_script->evalString()));
+	const char* filePatternStr = _vm->_game->_script->evalString();
+	bool isPattern = Common::String(filePatternStr).contains('*') || Common::String(filePatternStr).contains('?');
+
+	Common::Path filePattern(getFile(filePatternStr, !isPattern), '\\');
 	Common::ArchiveMemberList files;
 
 	SearchMan.listMatchingMembers(files, filePattern);


Commit: 7c3d4237ca5560caadcbb9fd34369fece5a83385
    https://github.com/scummvm/scummvm/commit/7c3d4237ca5560caadcbb9fd34369fece5a83385
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix a possible crash when installing a new Adibou2 application

The application index used to detect an uninstalled application is sometimes -1, sometimes 0.

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 7442e0e0393..2cee5720d3f 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1455,7 +1455,7 @@ void Inter_v7::o7_checkData(OpFuncParams &params) {
 		// game directory, just like in multi-cd-aware versions.
 		Common::Array<uint32> installedApplications = getAdibou2InstalledApplications();
 		int32 indexAppli = VAR_OFFSET(20196);
-		if (indexAppli == -1) {
+		if (indexAppli <= 0) {
 			// New appli, find the first directory containing an application still not installed, and set it as "current CD" path.
 			Common::ArchiveMemberDetailsList files;
 			SearchMan.listMatchingMembers(files, Common::Path(file)); // Search for CD.INF files


Commit: 19ac4e071373cdd30309edd57259eec483915938
    https://github.com/scummvm/scummvm/commit/19ac4e071373cdd30309edd57259eec483915938
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add some Adibou2/Sciences save files

Changed paths:
    engines/gob/save/saveload.h
    engines/gob/save/saveload_v7.cpp


diff --git a/engines/gob/save/saveload.h b/engines/gob/save/saveload.h
index 4030bfc4bea..e26b5a5f28d 100644
--- a/engines/gob/save/saveload.h
+++ b/engines/gob/save/saveload.h
@@ -1105,6 +1105,10 @@ protected:
 	DrawingOnFloppyDiskHandler  *_adibou2DrawingThumbnailOnFloppyDiskHandler;
 	FakeFileHandler             *_adibou2TestDobHandler;
 	FakeFileHandler             *_adibou2ExerciseListHandler;
+	FakeFileHandler             *_adibou2RelanceHandler;
+	FakeFileHandler             *_adibou2MemHandler;
+	GameFileHandler             *_adibou2SciencesProgressHandler[kChildrenCount];
+	GameFileHandler             *_adibou2AppliSciencesIniHandler;
 
 	FakeFileHandler             *_addy4BaseHandler[2];
 	FakeFileHandler             *_addy4GrundschuleHandler[11];
diff --git a/engines/gob/save/saveload_v7.cpp b/engines/gob/save/saveload_v7.cpp
index ca2d1a684f7..757f087a1f6 100644
--- a/engines/gob/save/saveload_v7.cpp
+++ b/engines/gob/save/saveload_v7.cpp
@@ -822,6 +822,27 @@ SaveLoad_v7::SaveFile SaveLoad_v7::_saveFiles[] = {
 	{"test.dob",     kSaveModeSave, nullptr, "test floppy disk file" },
 
 	{"TEMP/liste.$$$", kSaveModeSave, nullptr, "exercise list" },
+	{"TEMP/relance.$$$", kSaveModeSave, nullptr, "app info" },
+	{"TEMP/mem.$$$", kSaveModeSave, nullptr, "app info" },
+
+	{"DATA/GIE05_01.pho", kSaveModeSave, nullptr, "app progress" }, // Child 01
+	{"DATA/GIE05_02.pho", kSaveModeSave, nullptr, "app progress" }, // Child 02
+	{"DATA/GIE05_03.pho", kSaveModeSave, nullptr, "app progress" }, // Child 03
+	{"DATA/GIE05_04.pho", kSaveModeSave, nullptr, "app progress" }, // Child 04
+	{"DATA/GIE05_05.pho", kSaveModeSave, nullptr, "app progress" }, // Child 05
+	{"DATA/GIE05_06.pho", kSaveModeSave, nullptr, "app progress" }, // Child 06
+	{"DATA/GIE05_07.pho", kSaveModeSave, nullptr, "app progress" }, // Child 07
+	{"DATA/GIE05_08.pho", kSaveModeSave, nullptr, "app progress" }, // Child 08
+	{"DATA/GIE05_09.pho", kSaveModeSave, nullptr, "app progress" }, // Child 09
+	{"DATA/GIE05_10.pho", kSaveModeSave, nullptr, "app progress" }, // Child 10
+	{"DATA/GIE05_11.pho", kSaveModeSave, nullptr, "app progress" }, // Child 11
+	{"DATA/GIE05_12.pho", kSaveModeSave, nullptr, "app progress" }, // Child 12
+	{"DATA/GIE05_13.pho", kSaveModeSave, nullptr, "app progress" }, // Child 13
+	{"DATA/GIE05_14.pho", kSaveModeSave, nullptr, "app progress" }, // Child 14
+	{"DATA/GIE05_15.pho", kSaveModeSave, nullptr, "app progress" }, // Child 15
+	{"DATA/GIE05_16.pho", kSaveModeSave, nullptr, "app progress" }, // Child 16
+
+	{"APPLIS/appli_05.ini", kSaveModeSave, nullptr, "app info" },
 
     // Adi 4 / Addy 4 Base
 	{"config00.inf", kSaveModeSave, nullptr, nullptr        },
@@ -1336,6 +1357,16 @@ SaveLoad_v7::SaveLoad_v7(GobEngine *vm, const char *targetName) :
 																											   true);
 	_saveFiles[index++].handler = _adibou2TestDobHandler = new FakeFileHandler(_vm);
 	_saveFiles[index++].handler = _adibou2ExerciseListHandler = new FakeFileHandler(_vm);
+	_saveFiles[index++].handler = _adibou2RelanceHandler = new FakeFileHandler(_vm);
+	_saveFiles[index++].handler = _adibou2MemHandler = new FakeFileHandler(_vm);
+
+	for (uint32 i = 0; i < kChildrenCount; i++) {
+		_saveFiles[index++].handler = _adibou2SciencesProgressHandler[i] = new GameFileHandler(_vm,
+																							   targetName,
+																							   Common::String::format("gie_05_%02d_pho", i + 1));
+	}
+
+	_saveFiles[index++].handler = _adibou2AppliSciencesIniHandler = new GameFileHandler(_vm, targetName, "appli_05_ini");
 
 	for (int i = 0; i < 2; i++)
 		_saveFiles[index++].handler = _addy4BaseHandler[i] = new FakeFileHandler(_vm);
@@ -1410,6 +1441,14 @@ SaveLoad_v7::~SaveLoad_v7() {
 	delete _adibou2DrawingThumbnailOnFloppyDiskHandler;
 	delete _adibou2TestDobHandler;
 	delete _adibou2ExerciseListHandler;
+	delete _adibou2RelanceHandler;
+	delete _adibou2MemHandler;
+
+	for (uint32 i = 0; i < kChildrenCount; i++) {
+		delete _adibou2SciencesProgressHandler[i];
+	}
+
+	delete _adibou2AppliSciencesIniHandler;
 }
 
 const SaveLoad_v7::SaveFile *SaveLoad_v7::getSaveFile(const char *fileName) const {


Commit: 9e4abc326ad6533984debf30e91f1e4325fde15b
    https://github.com/scummvm/scummvm/commit/9e4abc326ad6533984debf30e91f1e4325fde15b
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Set return code variable in o7_setINIValue

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 2cee5720d3f..e2eb0bc852d 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -971,7 +971,8 @@ void Inter_v7::o7_setINIValue() {
 	Common::String key     = _vm->_game->_script->evalString();
 	Common::String value   = _vm->_game->_script->evalString();
 
-	_inis.setValue(file, section, key, value);
+	bool success = _inis.setValue(file, section, key, value);
+	WRITE_VAR(27, success ? 1 : 0);
 }
 
 void Inter_v7::o7_loadIFFPalette() {


Commit: 39ea8a253738251767eb9c422c93099ab1444577
    https://github.com/scummvm/scummvm/commit/39ea8a253738251767eb9c422c93099ab1444577
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix maximum length of strings written into game variables

Strings above the maximum length must be truncated, but this maximum depends on the variable type.
This fixes the missing images in the "balance" game in Adibou2/Sciences Lab.

Changed paths:
    engines/gob/inter.cpp


diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp
index c26f3d107e5..48fa961673f 100644
--- a/engines/gob/inter.cpp
+++ b/engines/gob/inter.cpp
@@ -401,19 +401,21 @@ void Inter::storeValue(uint32 value) {
 }
 
 void Inter::storeString(uint16 index, uint16 type, const char *value) {
-	uint32 maxLength = _vm->_global->_inter_animDataSize * 4 - 1;
+	uint32 maxLength = 0;
 	char  *str       = GET_VARO_STR(index);
 
 	switch (type) {
 	case TYPE_VAR_STR:
+		maxLength = _vm->_global->_inter_animDataSize * 4 - 1;
 		if (strlen(value) > maxLength)
-			warning("Inter_v7::storeString(): String too long");
+			warning("Inter::storeString(): String too long");
 
 		Common::strlcpy(str, value, maxLength);
 		break;
 
 	case TYPE_IMM_INT8:
 	case TYPE_VAR_INT8:
+		maxLength = 2048 - 1;
 		Common::strcpy_s(str, maxLength, value);
 		break;
 


Commit: 7e8f753fae99173cb1c81fc781483380fefedd44
    https://github.com/scummvm/scummvm/commit/7e8f753fae99173cb1c81fc781483380fefedd44
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Debugging traces for getting/setting .INI files values

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index e2eb0bc852d..789b472dd47 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -962,6 +962,7 @@ void Inter_v7::o7_getINIValue() {
 	_inis.getValue(value, file, section, key, def);
 
 	storeString(value.c_str());
+	debugC(5, kDebugGameFlow, "o7_getINIValue: %s: [%s] %s = %s", file.c_str(), section.c_str(), key.c_str(), value.c_str());
 }
 
 void Inter_v7::o7_setINIValue() {
@@ -973,6 +974,7 @@ void Inter_v7::o7_setINIValue() {
 
 	bool success = _inis.setValue(file, section, key, value);
 	WRITE_VAR(27, success ? 1 : 0);
+	debugC(5, kDebugGameFlow, "o7_setINIValue: %s: [%s] %s := %s", file.c_str(), section.c_str(), key.c_str(), value.c_str());
 }
 
 void Inter_v7::o7_loadIFFPalette() {


Commit: 649eea6d14e4f319e8522061d1e0b3b8fb51d78a
    https://github.com/scummvm/scummvm/commit/649eea6d14e4f319e8522061d1e0b3b8fb51d78a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix a crash in Adibou2/Anglais

o7_fillRect is sometimes called while _sourceSurface is -1, leading to a
crash when using it as an index in _spritesArray, while there is no
source surface involved with this opcode.

Changed paths:
    engines/gob/draw_playtoons.cpp


diff --git a/engines/gob/draw_playtoons.cpp b/engines/gob/draw_playtoons.cpp
index 9374c3a3e2f..27995271ab4 100644
--- a/engines/gob/draw_playtoons.cpp
+++ b/engines/gob/draw_playtoons.cpp
@@ -40,7 +40,6 @@ Draw_Playtoons::Draw_Playtoons(GobEngine *vm) : Draw_v2(vm) {
 void Draw_Playtoons::spriteOperation(int16 operation) {
 	int16 len;
 	int16 x, y;
-	SurfacePtr sourceSurf, destSurf;
 	bool deltaVeto;
 	int16 left;
 	int16 ratio;
@@ -127,10 +126,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		}
 	}
 
-	sourceSurf = _spritesArray[_sourceSurface];
-	destSurf = _spritesArray[_destSurface];
-
-	if (!destSurf) {
+	if (!_spritesArray[_destSurface]) {
 		warning("Can't do operation %d on surface %d: nonexistent", operation, _destSurface);
 		return;
 	}
@@ -138,7 +134,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 	switch (operation) {
 	case DRAW_BLITSURF:
 	case DRAW_DRAWLETTER:
-		if (!sourceSurf || !destSurf)
+		if (!_spritesArray[_sourceSurface] || !_spritesArray[_destSurface])
 			break;
 
 		_spritesArray[_destSurface]->blit(*_spritesArray[_sourceSurface],
@@ -325,7 +321,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 			}
 
 		} else {
-			sourceSurf = _spritesArray[_fontToSprite[_fontIndex].sprite];
+			SurfacePtr sourceSurf = _spritesArray[_fontToSprite[_fontIndex].sprite];
 			ratio = ((sourceSurf == _frontSurface) || (sourceSurf == _backSurface)) ?
 				320 : sourceSurf->getWidth();
 			ratio /= _fontToSprite[_fontIndex].width;


Commit: d1fe0614e498b87c72b77e789ef1f93b1efcdec7
    https://github.com/scummvm/scummvm/commit/d1fe0614e498b87c72b77e789ef1f93b1efcdec7
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Another stub for Adibou2/Anglais

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index c2e97da6b2a..bf525bae555 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -733,6 +733,7 @@ protected:
 	void o7_loadIFFPalette();
 	void o7_draw0xAA();
 	void o7_draw0xAC();
+	void o7_draw0xAD();
 	void o7_draw0xAE();
 	void o7_draw0xAF();
 	void o7_draw0xB0();
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 789b472dd47..f94185498c0 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -95,6 +95,7 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xA4, o7_loadIFFPalette);
 	OPCODEDRAW(0xAA, o7_draw0xAA);
 	OPCODEDRAW(0xAC, o7_draw0xAC);
+	OPCODEDRAW(0xAD, o7_draw0xAD);
 	OPCODEDRAW(0xAE, o7_draw0xAE);
 	OPCODEDRAW(0xAF, o7_draw0xAF);
 	OPCODEDRAW(0xB0, o7_draw0xB0);
@@ -1044,6 +1045,12 @@ void Inter_v7::o7_draw0xAC() {
 	warning("STUB: o7_draw0xAC (Adibou/Anglais)");
 }
 
+void Inter_v7::o7_draw0xAD() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	warning("STUB: o7_draw0xAD (Adibou/Anglais)");
+}
+
 void Inter_v7::o7_draw0xAE() {
 	_vm->_game->_script->readValExpr();
 	_vm->_game->_script->readValExpr();


Commit: 6b4792b8c8baa30280b02bfc8dba6eb9aa96a875
    https://github.com/scummvm/scummvm/commit/6b4792b8c8baa30280b02bfc8dba6eb9aa96a875
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Use Image::IcoCurDecoder to read cursors

Needed by Adibou2/Anglais CUR files

Changed paths:
    engines/gob/draw_v7.cpp


diff --git a/engines/gob/draw_v7.cpp b/engines/gob/draw_v7.cpp
index d28d29ba06f..286c3da9203 100644
--- a/engines/gob/draw_v7.cpp
+++ b/engines/gob/draw_v7.cpp
@@ -30,6 +30,7 @@
 #include "graphics/blit.h"
 #include "graphics/cursorman.h"
 #include "graphics/wincursor.h"
+#include "image/icocur.h"
 
 #include "gob/dataio.h"
 #include "gob/draw.h"
@@ -74,18 +75,41 @@ bool Draw_v7::loadCursorFromFile(Common::String cursorName) {
 	Graphics::WinCursorGroup *cursorGroup = nullptr;
 	Graphics::Cursor *defaultCursor = nullptr;
 
-	// Load the cursor file and cursor group
-	if (loadCursorFile())
-		cursorGroup = Graphics::WinCursorGroup::createCursorGroup(_cursors, Common::WinResourceID(cursorName));
+	const Graphics::Cursor *cursor = nullptr;
+
+	if (cursorName.hasPrefix("*")) {
+		// Load from an external .CUR file
+		cursorName = cursorName.substr(1);
+		Common::SeekableReadStream *cursorStream = _vm->_dataIO->getFile(cursorName);
+
+		if (cursorStream) {
+			Image::IcoCurDecoder cursorDecoder;
+			cursorDecoder.open(*cursorStream);
+
+			if (cursorDecoder.numItems() > 0) {
+				cursor = cursorDecoder.loadItemAsCursor(0);
+			} else {
+				warning("No cursor item found in file '%s'", cursorName.c_str());
+			}
+		} else {
+			warning("External cursor file '%s' not found", cursorName.c_str());
+		}
+	} else {
+		// Load from a .DLL cursor file and cursor group
+		if (loadCursorFile())
+			cursorGroup = Graphics::WinCursorGroup::createCursorGroup(_cursors, Common::WinResourceID(cursorName));
+
+		if (cursorGroup && !cursorGroup->cursors.empty() && cursorGroup->cursors[0].cursor) {
+			cursor = cursorGroup->cursors[0].cursor;
+		}
+	}
 
 	// If the requested cursor does not exist, create a default one
-	const Graphics::Cursor *cursor = nullptr;
-	if (!cursorGroup || cursorGroup->cursors.empty() || !cursorGroup->cursors[0].cursor) {
+	if (!cursor) {
 		defaultCursor = Graphics::makeDefaultWinCursor();
-
 		cursor = defaultCursor;
-	} else
-		cursor = cursorGroup->cursors[0].cursor;
+	}
+
 
 	// Make sure the cursor sprite is big enough
 	if (_scummvmCursor->getWidth() != cursor->getWidth() || _scummvmCursor->getHeight() != cursor->getHeight()) {


Commit: a5ae48518cec8f39db14a61092ab80bea02a95fa
    https://github.com/scummvm/scummvm/commit/a5ae48518cec8f39db14a61092ab80bea02a95fa
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Formatting fixes

Changed paths:
    engines/gob/draw_v7.cpp
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/draw_v7.cpp b/engines/gob/draw_v7.cpp
index 286c3da9203..94244a7c50b 100644
--- a/engines/gob/draw_v7.cpp
+++ b/engines/gob/draw_v7.cpp
@@ -182,8 +182,7 @@ bool Draw_v7::loadCursorFromFile(Common::String cursorName) {
 	return true;
 }
 
-void Draw_v7::initScreen()
-{
+void Draw_v7::initScreen() {
 	_vm->_game->_preventScroll = false;
 
 	_scrollOffsetX = 0;
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index f94185498c0..33d8c365683 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -583,8 +583,7 @@ void Inter_v7::o7_initScreen() {
 	_vm->_global->_mouseMaxY = (_vm->_video->_surfHeight + _vm->_video->_screenDeltaY) - offY - 1;
 	_vm->_global->_mouseMinY = _vm->_video->_screenDeltaY;
 
-	if (videoMode != 0x18)
-	{
+	if (videoMode != 0x18) {
 		_vm->_draw->closeScreen();
 		_vm->_util->clearPalette();
 		memset(_vm->_global->_redPalette, 0, 256);


Commit: 53b3c90de1b76e033182103f1a6d3075d8fa0b67
    https://github.com/scummvm/scummvm/commit/53b3c90de1b76e033182103f1a6d3075d8fa0b67
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Make printExpr compatible with newer script constructs

Used only for debugging.

Changed paths:
    engines/gob/expression.cpp


diff --git a/engines/gob/expression.cpp b/engines/gob/expression.cpp
index b98cab4c198..06294e8a936 100644
--- a/engines/gob/expression.cpp
+++ b/engines/gob/expression.cpp
@@ -231,6 +231,41 @@ void Expression::printExpr_internal(char stopToken) {
 	while (true) {
 		operation = _vm->_game->_script->readByte();
 
+		while ((operation == 14) || (operation == 15)) {
+			if (operation == 14) {
+				// Add a direct offset
+				debugN(5, "#%d#", _vm->_game->_script->readUint16());
+				_vm->_game->_script->skip(2);
+
+				if (_vm->_game->_script->peekByte() == 97)
+					_vm->_game->_script->skip(1);
+			} else if (operation == 15) {
+				// Add an offset from an array
+				debugN(5, "#%d", _vm->_game->_script->readUint16());
+				_vm->_game->_script->skip(2);
+
+				dimCount = _vm->_game->_script->readByte();
+				for (int i = 0; i < dimCount; i++)
+					debugN(5, "[]");
+				_vm->_game->_script->skip(dimCount);
+
+				debugN(5, "->");
+
+				for (int i = 0; i < dimCount; i++) {
+					debugN(5, "{");
+					printExpr_internal(OP_END_MARKER);
+					debugN(5, "->");
+				}
+
+				debugN(5, "#");
+
+				if (_vm->_game->_script->peekByte() == 97)
+					_vm->_game->_script->skip(1);
+			}
+
+			operation = _vm->_game->_script->readByte();
+		}
+
 		if ((operation >= OP_ARRAY_INT8) && (operation <= OP_FUNC)) {
 			// operands
 


Commit: 720b682f6c5f220cf1221dfc4a8e81f76fd82b17
    https://github.com/scummvm/scummvm/commit/720b682f6c5f220cf1221dfc4a8e81f76fd82b17
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Implement o7_getFileInfo (Adibou2/Anglais)

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp
    engines/gob/surface.cpp
    engines/gob/surface.h


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index bf525bae555..59339d3de6b 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -722,7 +722,7 @@ protected:
 	void o7_setActiveCD();
 	void o7_findFile();
 	void o7_findNextFile();
-	void o7_getImageInfo();
+	void o7_getFileInfo();
 	void o7_getSystemProperty();
 	void o7_loadImage();
 	void o7_setVolume();
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 33d8c365683..30033560248 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -85,7 +85,7 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0x8A, o7_findFile);
 	OPCODEDRAW(0x8B, o7_findNextFile);
 	OPCODEDRAW(0x8C, o7_getSystemProperty);
-	OPCODEDRAW(0x8E, o7_getImageInfo);
+	OPCODEDRAW(0x8E, o7_getFileInfo);
 	OPCODEDRAW(0x90, o7_loadImage);
 	OPCODEDRAW(0x93, o7_setVolume);
 	OPCODEDRAW(0x95, o7_zeroVar);
@@ -882,11 +882,54 @@ void Inter_v7::o7_getSystemProperty() {
 	storeValue(0);
 }
 
-void Inter_v7::o7_getImageInfo() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readVarIndex();
-	_vm->_game->_script->readVarIndex();
+void Inter_v7::o7_getFileInfo() {
+	Common::String file = getFile(_vm->_game->_script->evalString());
+	Common::String property = _vm->_game->_script->evalString();
+	uint16 resultVarType = 0;
+	uint16 resultVar = _vm->_game->_script->readVarIndex(nullptr, &resultVarType);
+	_vm->_game->_script->readVarIndex(); // Unknown, some status variable?
+
+	if (property.hasPrefix("IMAGE")) {
+		if (!file.contains('.'))
+			file += ".TGA";
+
+		Common::SeekableReadStream *imageFile = _vm->_dataIO->getFile(file);
+		if (!imageFile) {
+			warning("o7_getFileInfo(): No such file \"%s\"", file.c_str());
+			return;
+		}
+
+		uint32 width = -1;
+		uint32 height = -1;
+		uint32 bpp = -1;
+		Surface::getImageInfo(*imageFile, width, height, bpp);
+		if (property == "IMAGELARGEUR")
+			storeValue(resultVar, resultVarType, width);
+		else if (property == "IMAGEHAUTEUR")
+			storeValue(resultVar, resultVarType, height);
+		else if (property == "IMAGECOULEUR")
+			storeValue(resultVar, resultVarType, bpp);
+		else
+			warning("o7_getFileInfo(): Unknown image property \"%s\"", property.c_str());
+	} else {
+		if (property == "NOMBRELIGNE") {
+			Common::SeekableReadStream *stream = _vm->_dataIO->getFile(file);
+			if (!stream) {
+				warning("o7_getFileInfo(): No such file \"%s\"", file.c_str());
+				return;
+			}
+
+			int nbrLines = 0;
+			while (!stream->eos()) {
+				stream->readLine(true);
+				++nbrLines;
+			}
+
+			storeValue(resultVar, resultVarType, nbrLines);
+		} else {
+			warning("o7_getFileInfo(): Unknown property \"%s\"", property.c_str());
+		}
+	}
 }
 
 void Inter_v7::o7_loadImage() {
diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index ce4786c99ec..6d2c0418d74 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -876,6 +876,36 @@ void Surface::blitToScreen(uint16 left, uint16 top, uint16 right, uint16 bottom,
 	g_system->copyRectToScreen(src, _width * _bpp, x, y, width, height);
 }
 
+bool Surface::getImageInfo(Common::SeekableReadStream &stream, uint32 &width, uint32 &height, uint32 &bpp) {
+	ImageType type = identifyImage(stream);
+	if (type == kImageTypeNone)
+		return false;
+
+	Common::ScopedPtr<Image::ImageDecoder> decoder;
+	switch (type) {
+	case kImageTypeTGA:
+		decoder.reset(new Image::TGADecoder());
+		break;
+	case kImageTypeIFF:
+		decoder.reset(new Image::IFFDecoder());
+		break;
+	default:
+		warning("Surface::getImageInfo(): Unhandled image type: %d", (int)type);
+		return false;
+	}
+
+	decoder->loadStream(stream);
+	const Graphics::Surface *surf = decoder->getSurface();
+	if (!surf)
+		return false;
+
+	width  = surf->w;
+	height = surf->h;
+	bpp    = surf->format.bytesPerPixel;
+
+	return true;
+}
+
 bool Surface::loadImage(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom,
 						int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
 	ImageType type = identifyImage(stream);
diff --git a/engines/gob/surface.h b/engines/gob/surface.h
index 6d66ae72c3c..56a3815c85d 100644
--- a/engines/gob/surface.h
+++ b/engines/gob/surface.h
@@ -154,6 +154,7 @@ public:
 				   int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
 
 	static ImageType identifyImage(Common::SeekableReadStream &stream);
+	static bool getImageInfo(Common::SeekableReadStream &stream, uint32 &width, uint32 &height, uint32 &bpp);
 	static void computeHighColorMap(uint32 *highColorMap, const byte *palette,
 									const Graphics::PixelFormat &format,
 									bool useSpecialBlackWhiteValues,


Commit: afa6439144391b11badc7ceba03b6511fa910372
    https://github.com/scummvm/scummvm/commit/afa6439144391b11badc7ceba03b6511fa910372
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Implement new database opcodes (Adibou2/Anglais)

Changed paths:
    engines/gob/databases.cpp
    engines/gob/databases.h
    engines/gob/dbase.cpp
    engines/gob/dbase.h
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/databases.cpp b/engines/gob/databases.cpp
index 8275aa26d08..36c351d2465 100644
--- a/engines/gob/databases.cpp
+++ b/engines/gob/databases.cpp
@@ -32,13 +32,13 @@
 
 namespace Gob {
 
-Databases::Databases() {
+TranslationDatabases::TranslationDatabases() {
 }
 
-Databases::~Databases() {
+TranslationDatabases::~TranslationDatabases() {
 }
 
-void Databases::setLanguage(Common::Language language) {
+void TranslationDatabases::setLanguage(Common::Language language) {
 	Common::String lang;
 
 	if      (language == Common::UNK_LANG)
@@ -54,31 +54,31 @@ void Databases::setLanguage(Common::Language language) {
 	else if (language == Common::FR_FRA)
 		lang = "F";
 	else
-		warning("Databases::setLanguage(): Language \"%s\" not supported",
+		warning("TranslationDatabases::setLanguage(): Language \"%s\" not supported",
 				Common::getLanguageDescription(language));
 
 	if (!_databases.empty() && (lang != _language))
-		warning("Databases::setLanguage(): \"%s\" != \"%s\" and there's still databases open!",
+		warning("TranslationDatabases::setLanguage(): \"%s\" != \"%s\" and there's still databases open!",
 				_language.c_str(), lang.c_str());
 
 	_language = lang;
 }
 
-bool Databases::open(const Common::String &id, const Common::Path &file) {
-	if (_databases.contains(id)) {
-		warning("Databases::open(): A database with the ID \"%s\" already exists", id.c_str());
-		return false;
-	}
+bool TranslationDatabases::open(const Common::String &id, const Common::Path &file) {
+		if (_databases.contains(id)) {
+			warning("TranslationDatabases::open(): A database with the ID \"%s\" already exists", id.c_str());
+			return false;
+		}
 
 	Common::File dbFile;
 	if (!dbFile.open(file)) {
-		warning("Databases::open(): No such file \"%s\"", file.toString().c_str());
+		warning("TranslationDatabases::open(): No such file \"%s\"", file.toString().c_str());
 		return false;
 	}
 
 	dBase db;
 	if (!db.load(dbFile)) {
-		warning("Databases::open(): Failed loading database file \"%s\"", file.toString().c_str());
+		warning("TranslationDatabases::open(): Failed loading database file \"%s\"", file.toString().c_str());
 		return false;
 	}
 
@@ -95,10 +95,10 @@ bool Databases::open(const Common::String &id, const Common::Path &file) {
 	return true;
 }
 
-bool Databases::close(const Common::String &id) {
+bool TranslationDatabases::close(const Common::String &id) {
 	DBMap::iterator db = _databases.find(id);
 	if (db == _databases.end()) {
-		warning("Databases::close(): A database with the ID \"%s\" does not exist", id.c_str());
+		warning("TranslationDatabases::close(): A database with the ID \"%s\" does not exist", id.c_str());
 		return false;
 	}
 
@@ -106,17 +106,17 @@ bool Databases::close(const Common::String &id) {
 	return true;
 }
 
-bool Databases::getString(const Common::String &id, Common::String group,
+bool TranslationDatabases::getString(const Common::String &id, Common::String group,
 		Common::String section, Common::String keyword, Common::String &result) const {
 
 	DBMap::iterator db = _databases.find(id);
 	if (db == _databases.end()) {
-		warning("Databases::getString(): A database with the ID \"%s\" does not exist", id.c_str());
+		warning("TranslationDatabases::getString(): A database with the ID \"%s\" does not exist", id.c_str());
 		return false;
 	}
 
 	if (_language.empty()) {
-		warning("Databases::getString(): No language set");
+		warning("TranslationDatabases::getString(): No language set");
 		return false;
 	}
 
@@ -130,7 +130,7 @@ bool Databases::getString(const Common::String &id, Common::String group,
 	return true;
 }
 
-int Databases::findField(const dBase &db, const Common::String &field,
+int TranslationDatabases::findField(const dBase &db, const Common::String &field,
 		dBase::Type type) const {
 
 	const Common::Array<dBase::Field> &fields = db.getFields();
@@ -148,7 +148,7 @@ int Databases::findField(const dBase &db, const Common::String &field,
 	return -1;
 }
 
-bool Databases::buildMap(const dBase &db, Common::StringMap &map) const {
+bool TranslationDatabases::buildMap(const dBase &db, Common::StringMap &map) const {
 	int fLanguage = findField(db, "Langage", dBase::kTypeString);
 	int fGroup    = findField(db, "Nom"    , dBase::kTypeString);
 	int fSection  = findField(db, "Section", dBase::kTypeString);
@@ -175,4 +175,85 @@ bool Databases::buildMap(const dBase &db, Common::StringMap &map) const {
 	return true;
 }
 
+Database::~Database() {
+	for (Common::HashMap<Common::String, dBase*>::Node &node : _tables)
+		delete node._value;
+}
+
+bool Database::openTable(const Common::String &id, const Common::Path &file) {
+	if (_tables.contains(id)) {
+		warning("Database::open(): A table with the ID \"%s\" already exists", id.c_str());
+		return false;
+	}
+
+	Common::File dbFile;
+	if (!dbFile.open(file)) {
+		warning("Database::open(): No such file \"%s\"", file.toString().c_str());
+		return false;
+	}
+
+	dBase *db = new dBase();
+	if (!db->load(dbFile)) {
+		warning("Database::open(): Failed loading database file \"%s\"", file.toString().c_str());
+		return false;
+	}
+
+	if (db->hasMemo()) {
+		Common::String memoPathString = file.toString();
+		if (!memoPathString.hasSuffixIgnoreCase(".DBF"))
+			return false;
+
+		memoPathString.replace(memoPathString.size() - 3, 3, "DBT");
+		Common::Path memoFilename(memoPathString);
+
+		Common::File memoFile;
+		if (!memoFile.open(memoFilename)) {
+			warning("Database::open(): No such file \"%s\"", memoPathString.c_str());
+			return false;
+		}
+
+		if (!db->loadMemo(memoFile)) {
+			warning("Database::open(): Failed loading memo file for database \"%s\"", file.toString().c_str());
+			return false;
+		}
+	}
+
+	Common::String mdxPathString = file.toString();
+	if (!mdxPathString.hasSuffixIgnoreCase(".DBF"))
+		return false;
+	mdxPathString.replace(mdxPathString.size() - 3, 3, "MDX");
+	Common::Path mdxFilename(mdxPathString);
+
+	Common::File mdxFile;
+	if (mdxFile.exists(mdxFilename)) {
+		mdxFile.open(mdxFilename);
+		db->loadMultipleIndex(mdxFile);
+	}
+
+	_tables.setVal(id, db);
+	return true;
+}
+
+bool Database::closeTable(const Common::String &id) {
+	if (!_tables.contains(id)) {
+		warning("Database::close(): A table with the ID \"%s\" does not exist", id.c_str());
+		return false;
+	}
+
+	dBase *db = _tables[id];
+	delete db;
+	_tables.erase(id);
+	return true;
+}
+
+dBase *Database::getTable(const Common::String &id) {
+	Common::HashMap<Common::String, dBase*>::iterator db = _tables.find(id);
+	if (db == _tables.end()) {
+		warning("Database::getTable(): A table with the ID \"%s\" does not exist", id.c_str());
+		return nullptr;
+	}
+
+	return db->_value;
+}
+
 } // End of namespace Gob
diff --git a/engines/gob/databases.h b/engines/gob/databases.h
index 61b0cb51b7b..6f681467d3e 100644
--- a/engines/gob/databases.h
+++ b/engines/gob/databases.h
@@ -37,10 +37,10 @@
 
 namespace Gob {
 
-class Databases {
+class TranslationDatabases {
 public:
-	Databases();
-	~Databases();
+	TranslationDatabases();
+	~TranslationDatabases();
 
 	void setLanguage(Common::Language language);
 
@@ -61,6 +61,20 @@ private:
 	bool buildMap(const dBase &db, Common::StringMap &map) const;
 };
 
+class Database {
+public:
+	Database() {}
+	~Database();
+
+	bool openTable(const Common::String &id, const Common::Path &file);
+	bool closeTable(const Common::String &id);
+
+	dBase *getTable(const Common::String &id);
+
+private:
+	Common::HashMap<Common::String, dBase*> _tables;
+};
+
 } // End of namespace Gob
 
 #endif // GOB_DATABASES_H
diff --git a/engines/gob/dbase.cpp b/engines/gob/dbase.cpp
index 042db206de7..385ec06a16d 100644
--- a/engines/gob/dbase.cpp
+++ b/engines/gob/dbase.cpp
@@ -27,8 +27,121 @@
 
 #include "gob/dbase.h"
 
+#include "common/tokenizer.h"
+
 namespace Gob {
 
+dbaseMultipeIndex::dbaseMultipeIndex() {
+	clear();
+}
+
+void dbaseMultipeIndex::clear() {
+	memset(&_creationDate, 0, sizeof(_creationDate));
+	memset(&_lastUpdate, 0, sizeof(_lastUpdate));
+
+	_version = 0;
+}
+
+// Supported key definition syntax:
+// key_definition ::= field_definition { "+" field_definition }
+// field_definition ::= field_name | STR(field_name, length, 0)
+Common::Array<dbaseMultipeIndex::FieldReference> dbaseMultipeIndex::parseKeyDefinition(const Common::String &keyDefinition) {
+	Common::Array<FieldReference> fieldReferences;
+	Common::StringTokenizer tokenizer(keyDefinition, "+");
+	while (!tokenizer.empty()) {
+		Common::String token = tokenizer.nextToken();
+		if (token.hasPrefix("STR(")) {
+			// STR(field_name, length, 0) expression
+			size_t firstCommaPos = token.find(',');
+			size_t secondCommaPos = token.find(',', firstCommaPos + 1);
+			Common::String fieldName = token.substr(4, firstCommaPos - 4);
+			size_t length = 0;
+			if (secondCommaPos != token.npos)
+				length = atoi(token.substr(firstCommaPos + 1, secondCommaPos - firstCommaPos - 1).c_str());
+
+			fieldReferences.push_back({fieldName, length});
+		} else {
+			// Field name only
+			fieldReferences.push_back({token, 0});
+		}
+	}
+
+	return fieldReferences;
+}
+
+const Common::Array<dbaseMultipeIndex::FieldReference>* dbaseMultipeIndex::getTagKeyDefinition(Common::String tagName) const
+{
+	if (!_tagKeyDefinitions.contains(tagName))
+		return nullptr;
+	else
+		return &_tagKeyDefinitions[tagName];
+}
+
+bool dbaseMultipeIndex::load(Common::SeekableReadStream &stream) {
+	_version = stream.readByte();
+	_creationDate.tm_year = stream.readByte();
+	_creationDate.tm_mon  = stream.readByte() - 1;
+	_creationDate.tm_mday = stream.readByte();
+
+	uint32 pos = stream.pos();
+	_dataFilename = stream.readString('\0', 16);
+	stream.seek(pos + 16);
+	stream.skip(2); // Block size
+	stream.skip(2); // Page size
+	stream.skip(1); // Production flag
+	stream.skip(1); // Max number of tags
+	stream.skip(1); // Tag length
+	stream.skip(1); // Reserved
+	_nbrOfTagsInUse = stream.readUint16LE(); // Number of tags in use
+	stream.skip(2); // Reserved
+	stream.skip(4); // Number of pages in tag file
+	stream.skip(4); // Pointer to first free page
+	stream.skip(4); // Number of available blocks
+
+	_lastUpdate.tm_year = stream.readByte();
+	_lastUpdate.tm_mon  = stream.readByte() - 1;
+	_lastUpdate.tm_mday = stream.readByte();
+
+	stream.skip(497); // Reserved
+
+	for (int i = 0; i < _nbrOfTagsInUse; ++i) {
+		uint32 tagHeaderPage = stream.readUint32LE();
+		Common::String tagName = stream.readString('\0', 11);
+		stream.skip(1); // key format
+		stream.skip(1); // forward tag thread inf
+		stream.skip(1); // forward tag thread sup
+		stream.skip(1); // backward tag thread
+		stream.skip(1); // reserved
+		stream.skip(1); // key type
+		stream.skip(11); // reserved
+
+		int64 tagEntryEnd = stream.pos();
+
+		stream.seek(tagHeaderPage * INDEX_PAGE_SIZE);
+		stream.skip(4); // tag root page
+		stream.skip(4); // file size in pages
+		stream.skip(1); // key format
+		stream.skip(1); // key type
+		stream.skip(2); // reserved
+		stream.skip(2); // index key length
+		stream.skip(2); // max nbr of keys per page
+		stream.skip(2); // secondary key type
+		stream.skip(2); // index key item length
+		stream.skip(3); // reserved
+		stream.skip(1); // unique flag
+
+		Common::String keyExpression = stream.readString('\0', 488);
+		_tagKeyDefinitions[tagName] = parseKeyDefinition(keyExpression);
+
+		// For now, we do not need to read the B-tree structure itself.
+		// The key definition is enough for our use cases.
+
+		stream.seek(tagEntryEnd);
+	}
+
+	return true;
+}
+
 dBase::dBase() : _recordData(nullptr) {
 	clear();
 }
@@ -43,13 +156,15 @@ bool dBase::load(Common::SeekableReadStream &stream) {
 	uint32 startPos = stream.pos();
 
 	_version = stream.readByte();
-	if ((_version != 0x03) && (_version != 0x83))
-		// Unsupported version
+	if (_version == 0x03 || _version == 0x83) {
+		_versionMajor= 3;
+	} else if (_version == 0x04 || _version == 0x7B || _version == 0x8B) {
+		_versionMajor= 4;
+	} else {
+		warning("dBase::load() called on unsupported dBase version %d", _version);
 		return false;
+	}
 
-	// TODO: Add support for memo files. A memo file is an external data file
-	//       .DBT, segmented into "blocks". Each memo field in a record is an
-	//       index this file.
 	_hasMemo = (_version & 0x80) != 0;
 
 	_lastUpdate.tm_year = stream.readByte();
@@ -131,6 +246,42 @@ bool dBase::load(Common::SeekableReadStream &stream) {
 	return true;
 }
 
+bool dBase::loadMemo(Common::SeekableReadStream &stream) {
+	uint32 nextBlock = stream.readUint32LE();
+	if (nextBlock < 2)
+		return true; // No data blocks
+
+	uint32 nbrOfDataBlocks = nextBlock - 2;
+
+	_memoData.clear();
+	_memoData.resize(nbrOfDataBlocks * MEMO_BLOCK_SIZE);
+
+	for (uint32 i = 1; i < nextBlock; i++) {
+		stream.seek(i * MEMO_BLOCK_SIZE, SEEK_SET);
+		uint32 type = stream.readUint32LE();
+		if (type != 0x8FFFF) {
+			warning("dBase::loadMemo() found unexpected memo record type %08X", type);
+		}
+
+		int32 memoSize = stream.readSint32LE();
+		if (memoSize < 8) // Header size (8) is included in memoSize
+			continue; // Empty memo
+
+		_memoData[i - 1] = readString(stream, memoSize - 8);
+	}
+
+	return true;
+}
+
+
+bool dBase::loadMultipleIndex(Common::SeekableReadStream &stream) {
+	if (_multipleIndex.load(stream)) {
+		_hasMultipleIndex = true;
+		return true;
+	} else
+		return false;
+}
+
 void dBase::clear() {
 	memset(&_lastUpdate, 0, sizeof(_lastUpdate));
 
@@ -148,6 +299,10 @@ byte dBase::getVersion() const {
 	return _version;
 }
 
+bool dBase::hasMemo() const {
+	return _hasMemo;
+}
+
 TimeDate dBase::getLastUpdate() const {
 	return _lastUpdate;
 }
@@ -161,10 +316,146 @@ const Common::Array<dBase::Record> &dBase::getRecords() const {
 }
 
 Common::String dBase::getString(const Record &record, int field) const {
-	assert(_fields[field].type == kTypeString);
+	Type type = _fields[field].type;
+
+	switch (type) {
+	case kTypeString: {
+		uint32 fieldLength = stringLength(record.fields[field], _fields[field].size);
+		return Common::String((const char *) record.fields[field], fieldLength);
+	}
+
+	case kTypeNumber: {
+		Common::String str = Common::String((const char *) record.fields[field], _fields[field].size);
+		str.trim();
+		return str;
+	}
+
+	case kTypeMemo: {
+		Common::String blockNbrStr = Common::String((const char *) record.fields[field],  _fields[field].size);
+		int blockNbr = atoi(blockNbrStr.c_str());
+		if ((blockNbr < 1) || ((size_t) blockNbr > _memoData.size())) {
+			warning("dBase::getString() called on invalid memo block %d", blockNbr);
+			return "";
+		}
+
+		return _memoData[blockNbr - 1];
+	}
+
+	default:
+		// Unsupported type
+		warning("dBase::getString() called on unsupported field type %d", type);
+	}
+
+	return "";
+}
+
+void dBase::setQuery(const Common::String &query) {
+	_currentFieldFilter.clear();
+
+	if (!_hasMultipleIndex) {
+		warning("dBase::setQuery() called on a database without multiple index");
+		return;
+	}
+
+	const Common::Array<dbaseMultipeIndex::FieldReference>* keyDefinition = _multipleIndex.getTagKeyDefinition(_currentIndexTag);
+	if (!keyDefinition) {
+		warning("dBase::setQuery(): key definition not found for tag '%s'", _currentIndexTag.c_str());
+		return;
+	}
+
+	// Parse the query. Field separator is ';', catch-all is '?'
+	Common::StringTokenizer tokenizer(query, ";");
+	size_t fieldIndex = 0;
+	while (!tokenizer.empty()) {
+		Common::String token = tokenizer.nextToken();
+		if (token != "?") {
+			if (fieldIndex >= keyDefinition->size()) {
+				warning("dBase::setQuery(): too many fields in query");
+				return;
+			}
+
+			const dbaseMultipeIndex::FieldReference &fieldReference = (*keyDefinition)[fieldIndex];
+			const Common::String &fieldName = fieldReference.getFieldName();
+			for (size_t i = 0; i < _fields.size(); ++i) {
+				if (_fields[i].name == fieldName) {
+					_currentFieldFilter.push_back({i, fieldReference.getMaxLength(), token});
+					break;
+				}
+
+				if (i == _fields.size() - 1) {
+					warning("dBase::setQuery(): field '%s' not found", fieldName.c_str());
+					return;
+				}
+			}
+		}
+
+		++fieldIndex;
+	}
+}
+
+void dBase::setCurrentIndex(const Common::String &tagName) {
+	_currentIndexTag = tagName;
+	_currentRecordIndex = -1;
+	_currentFieldFilter.clear();
+}
+
+void dBase::findNextMatchingRecord() {
+	++_currentRecordIndex;
+
+	if (_currentFieldFilter.empty()) {
+		_currentRecordIndex = _records.size();
+		return;
+	}
+
+	for (; _currentRecordIndex < (int) _records.size(); ++_currentRecordIndex) {
+		const Record &record = _records[_currentRecordIndex];
+
+		bool match = true;
+		for (const FieldPattern &pattern : _currentFieldFilter) {
+			if (pattern.fieldIndex >= _fields.size()) {
+				match = false;
+				break;
+			}
+
+			Common::String fieldValue = getString(record, pattern.fieldIndex);
+			if (pattern.maxLength > 0)
+				fieldValue = fieldValue.substr(0, pattern.maxLength);
+
+			if (fieldValue != pattern.pattern) {
+				match = false;
+				break;
+			}
+		}
+
+		if (match)
+			return;
+	}
+}
+
+void dBase::findFirstMatchingRecord() {
+	_currentRecordIndex = -1;
+	findNextMatchingRecord();
+}
+
+bool dBase::hasMatchingRecord() {
+	return _currentRecordIndex >= 0 && _currentRecordIndex < (int) _records.size();
+}
+
+Common::String dBase::getFieldOfMatchingRecord(Common::String fieldName) {
+	if (!hasMatchingRecord())
+		return "";
+
+	const Record &record = _records[_currentRecordIndex];
+	size_t fieldIndex = 0;
+	for (const Field &field : _fields) {
+		if (field.name == fieldName) {
+			return getString(record, fieldIndex);
+		}
+
+		++fieldIndex;
+	}
 
-	uint32 fieldLength = stringLength(record.fields[field], _fields[field].size);
-	return Common::String((const char *) record.fields[field], fieldLength);
+	return "";
 }
 
 // String fields are padded with spaces. This finds the real length.
diff --git a/engines/gob/dbase.h b/engines/gob/dbase.h
index a15e03573a0..3c27f914fc2 100644
--- a/engines/gob/dbase.h
+++ b/engines/gob/dbase.h
@@ -36,12 +36,53 @@
 
 namespace Gob {
 
+/**
+ * A class for reading Multiple Index (.mdx) files for dBase.
+ * Currently, we only read the key definitions, not the actual index data.
+ *
+ */
+class dbaseMultipeIndex {
+
+public:
+	dbaseMultipeIndex();
+	~dbaseMultipeIndex() {}
+
+	bool load(Common::SeekableReadStream &stream);
+	void clear();
+
+	class FieldReference {
+	public:
+		FieldReference() : _fieldName(""), _maxLength(0) {}
+		FieldReference(const Common::String &fieldName, size_t maxLength) : _fieldName(fieldName), _maxLength(maxLength) {}
+
+		const Common::String &getFieldName() const { return _fieldName; }
+		size_t getMaxLength() const { return _maxLength; }
+
+	private:
+		Common::String _fieldName;
+		size_t _maxLength;
+	};
+
+	static Common::Array<FieldReference> parseKeyDefinition(const Common::String &keyDefinition);
+
+	const Common::Array<FieldReference>* getTagKeyDefinition(Common::String tagName) const;
+
+private:
+	byte _version;
+	TimeDate _creationDate;
+	Common::String _dataFilename;
+	uint16 _nbrOfTagsInUse;
+	TimeDate _lastUpdate;
+
+	static const uint16 INDEX_PAGE_SIZE = 512;
+	Common::HashMap<Common::String, Common::Array<FieldReference>> _tagKeyDefinitions;
+};
+
 /**
  * A class for reading dBase files.
  *
  * Only dBase III files supported for now, and only field type
- * string is actually useful. Further missing is reading of MDX
- * index files and support for the external "Memo" data file.
+ * string is actually useful.
  */
 class dBase {
 public:
@@ -68,13 +109,22 @@ public:
 		Common::Array<const byte *> fields; ///< Raw field data.
 	};
 
+	struct FieldPattern {
+		size_t fieldIndex;
+		size_t maxLength;
+		Common::String pattern;
+	};
+
 	dBase();
 	~dBase();
 
 	bool load(Common::SeekableReadStream &stream);
+	bool loadMemo(Common::SeekableReadStream &stream);
+	bool loadMultipleIndex(Common::SeekableReadStream &stream);
 	void clear();
 
 	byte getVersion() const;
+	bool hasMemo() const;
 
 	/** Return the date the database was last updated. */
 	TimeDate getLastUpdate() const;
@@ -85,8 +135,16 @@ public:
 	/** Extract a string out of raw field data. */
 	Common::String getString(const Record &record, int field) const;
 
+	void setQuery(const Common::String &query);
+	void setCurrentIndex(const Common::String &tagName);
+	void findFirstMatchingRecord();
+	void findNextMatchingRecord();
+	bool hasMatchingRecord();
+	Common::String getFieldOfMatchingRecord(Common::String fieldName);
+
 private:
 	byte _version;
+	byte _versionMajor;
 	bool _hasMemo;
 
 	TimeDate _lastUpdate;
@@ -94,8 +152,19 @@ private:
 	Common::Array<Field>  _fields;
 	Common::Array<Record> _records;
 
+	int _currentRecordIndex;
+	Common::Array<FieldPattern> _currentFieldFilter;
+
 	byte *_recordData;
 
+	Common::Array<Common::String> _memoData;
+
+	Common::String _currentIndexTag;
+	bool _hasMultipleIndex;
+	dbaseMultipeIndex _multipleIndex;
+
+	static const uint16 MEMO_BLOCK_SIZE = 512;
+
 	static inline uint32 stringLength(const byte *data, uint32 max);
 	static inline Common::String readString(Common::SeekableReadStream &stream, int n);
 };
diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 59339d3de6b..3b2f9c0fced 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -731,17 +731,17 @@ protected:
 	void o7_getINIValue();
 	void o7_setINIValue();
 	void o7_loadIFFPalette();
-	void o7_draw0xAA();
-	void o7_draw0xAC();
-	void o7_draw0xAD();
+	void o7_openDatabase();
+	void o7_openDatabaseTable();
+	void o7_closeDatabaseTable();
 	void o7_draw0xAE();
-	void o7_draw0xAF();
-	void o7_draw0xB0();
-	void o7_draw0xB1();
-	void o7_draw0xB4();
-	void o7_draw0xB6();
-	void o7_opendBase();
-	void o7_closedBase();
+	void o7_openDatabaseIndex();
+	void o7_findDatabaseRecord();
+	void o7_findNextDatabaseRecord();
+	void o7_getDatabaseRecordValue();
+	void o7_checkAnyDatabaseRecordFound();
+	void o7_openTranlsationDB();
+	void o7_closeTranslationDB();
 	void o7_getDBString();
 	void o7_draw0xCC();
 	void o7_draw0xCD();
@@ -775,7 +775,8 @@ protected:
 
 private:
 	INIConfig _inis;
-	Databases _databases;
+	TranslationDatabases _translationDatabases;
+	Common::HashMap<Common::String, Database, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _databases;
 
 	Common::ArchiveMemberList _remainingFilesFromPreviousSearch;
 	Common::String _currentCDPath;
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 30033560248..31da6ebeaeb 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -39,6 +39,7 @@
 
 #include "image/iff.h"
 
+#include "gob/dbase.h"
 #include "gob/gob.h"
 #include "gob/global.h"
 #include "gob/dataio.h"
@@ -93,17 +94,17 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xA1, o7_getINIValue);
 	OPCODEDRAW(0xA2, o7_setINIValue);
 	OPCODEDRAW(0xA4, o7_loadIFFPalette);
-	OPCODEDRAW(0xAA, o7_draw0xAA);
-	OPCODEDRAW(0xAC, o7_draw0xAC);
-	OPCODEDRAW(0xAD, o7_draw0xAD);
+	OPCODEDRAW(0xAA, o7_openDatabase);
+	OPCODEDRAW(0xAC, o7_openDatabaseTable);
+	OPCODEDRAW(0xAD, o7_closeDatabaseTable);
 	OPCODEDRAW(0xAE, o7_draw0xAE);
-	OPCODEDRAW(0xAF, o7_draw0xAF);
-	OPCODEDRAW(0xB0, o7_draw0xB0);
-	OPCODEDRAW(0xB1, o7_draw0xB1);
-	OPCODEDRAW(0xB4, o7_draw0xB4);
-	OPCODEDRAW(0xB6, o7_draw0xB6);
-	OPCODEDRAW(0xC4, o7_opendBase);
-	OPCODEDRAW(0xC5, o7_closedBase);
+	OPCODEDRAW(0xAF, o7_openDatabaseIndex);
+	OPCODEDRAW(0xB0, o7_findDatabaseRecord);
+	OPCODEDRAW(0xB1, o7_findNextDatabaseRecord);
+	OPCODEDRAW(0xB4, o7_getDatabaseRecordValue);
+	OPCODEDRAW(0xB6, o7_checkAnyDatabaseRecordFound);
+	OPCODEDRAW(0xC4, o7_openTranlsationDB);
+	OPCODEDRAW(0xC5, o7_closeTranslationDB);
 	OPCODEDRAW(0xC6, o7_getDBString);
 	OPCODEDRAW(0xCC, o7_draw0xCC);
 	OPCODEDRAW(0xCD, o7_draw0xCD);
@@ -1074,23 +1075,41 @@ void Inter_v7::o7_loadIFFPalette() {
 	_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
 }
 
-void Inter_v7::o7_draw0xAA() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	warning("STUB: o7_draw0xAA (Adibou/Anglais)");
+void Inter_v7::o7_openDatabase() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	_vm->_game->_script->readValExpr(); // unknown, some kind of "open mode"
+
+	_databases.setVal(databaseName, Database());
 }
 
-void Inter_v7::o7_draw0xAC() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	warning("STUB: o7_draw0xAC (Adibou/Anglais)");
+void Inter_v7::o7_openDatabaseTable() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+	Common::String dbFile = getFile(_vm->_game->_script->evalString());
+
+	dbFile += ".DBF";
+
+	if (!_databases.contains(databaseName)) {
+		warning("o7_openDatabaseTable(): No such database \"%s\"", databaseName.c_str());
+		return;
+	}
+
+	Database &database = _databases.getVal(databaseName);
+
+	database.openTable(tableName, Common::Path(dbFile));
 }
 
-void Inter_v7::o7_draw0xAD() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	warning("STUB: o7_draw0xAD (Adibou/Anglais)");
+void Inter_v7::o7_closeDatabaseTable() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+
+	if (!_databases.contains(databaseName)) {
+		warning("o7_closeDatabaseTable(): No such database \"%s\"", databaseName.c_str());
+		return;
+	}
+
+	Database &database = _databases.getVal(databaseName);
+	database.closeTable(tableName);
 }
 
 void Inter_v7::o7_draw0xAE() {
@@ -1099,45 +1118,119 @@ void Inter_v7::o7_draw0xAE() {
 	warning("STUB: o7_draw0xAE (Adibou/Musique)");
 }
 
-void Inter_v7::o7_draw0xAF() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	warning("STUB: o7_draw0xAF (Adibou/Anglais)");
+void Inter_v7::o7_openDatabaseIndex() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+	Common::String indexName = _vm->_game->_script->evalString();
+
+	if (!_databases.contains(databaseName)) {
+		warning("o7_openDatabaseIndex(): No such database \"%s\"", databaseName.c_str());
+		return;
+	}
+
+	Database &database = _databases.getVal(databaseName);
+	dBase *db = database.getTable(tableName);
+
+	db->setCurrentIndex(indexName);
 }
 
-void Inter_v7::o7_draw0xB0() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
+void Inter_v7::o7_findDatabaseRecord() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+	Common::String query = _vm->_game->_script->evalString();
+	debugC(5, kDebugGameFlow, "o7_findDatabaseRecord: %s.%s: query=%s", databaseName.c_str(), tableName.c_str(), query.c_str());
+
+	if (!_databases.contains(databaseName)) {
+		warning("o7_findDatabaseRecord(): No such database \"%s\"", databaseName.c_str());
+		return;
+	}
+
+	Database &database = _databases.getVal(databaseName);
+	dBase *db = database.getTable(tableName);
+	if (!db) {
+		warning("o7_findDatabaseRecord(): No such table \"%s\"", tableName.c_str());
+		return;
+	}
+
+	db->setQuery(query);
+	db->findFirstMatchingRecord();
 }
 
-void Inter_v7::o7_draw0xB1() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
+void Inter_v7::o7_findNextDatabaseRecord() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+
+	Database &database = _databases.getVal(databaseName);
+	debugC(5, kDebugGameFlow, "o7_findNextDatabaseRecord: %s.%s", databaseName.c_str(), tableName.c_str());
+
+	if (!_databases.contains(databaseName)) {
+		warning("o7_findDatabaseRecord(): No such database \"%s\"", databaseName.c_str());
+		return;
+	}
+
+	dBase *db = database.getTable(tableName);
+	if (!db) {
+		warning("o7_findDatabaseRecord(): No such table \"%s\"", tableName.c_str());
+		return;
+	}
+
+	db->findNextMatchingRecord();
 }
 
-void Inter_v7::o7_draw0xB4() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readVarIndex();
+void Inter_v7::o7_getDatabaseRecordValue() {
+	Common::String databaseSetName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+	Common::String fieldName = _vm->_game->_script->evalString();
+	uint16 type;
+	uint16 varIndex = _vm->_game->_script->readVarIndex(nullptr, &type);
+
+	if (!_databases.contains(databaseSetName)) {
+		warning("o7_findDatabaseRecord(): No such database set \"%s\"", databaseSetName.c_str());
+		return;
+	}
+
+	Database &database = _databases.getVal(databaseSetName);
+	dBase *db = database.getTable(tableName);
+	if (!db) {
+		warning("o7_findDatabaseRecord(): No such database table \"%s\"", tableName.c_str());
+		return;
+	}
+
+	Common::String string = db->getFieldOfMatchingRecord(fieldName);
+	debugC(5, kDebugGameFlow, "o7_getDatabaseRecordValue: %s.%s.%s = %s", databaseSetName.c_str(), tableName.c_str(), fieldName.c_str(), string.c_str());
+	storeString(varIndex, type, string.c_str());
 }
 
-void Inter_v7::o7_draw0xB6() {
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readValExpr();
-	_vm->_game->_script->readVarIndex();
+void Inter_v7::o7_checkAnyDatabaseRecordFound() {
+	Common::String databaseName = _vm->_game->_script->evalString();
+	Common::String tableName = _vm->_game->_script->evalString();
+	uint16 varIndex = _vm->_game->_script->readVarIndex();
+
+	if (!_databases.contains(databaseName)) {
+		warning("o7_findDatabaseRecord(): No such database set \"%s\"", databaseName.c_str());
+		return;
+	}
+
+	Database &database = _databases.getVal(databaseName);
+	dBase *db = database.getTable(tableName);
+	if (!db) {
+		warning("o7_findDatabaseRecord(): No such database table \"%s\"", tableName.c_str());
+		return;
+	}
+
+	debugC(5, kDebugGameFlow, "o7_checkAnyDatabaseRecordFound: %s.%s = %s", databaseName.c_str(), tableName.c_str(), db->hasMatchingRecord() ? "true" : "false");
+
+	WRITE_VAR_OFFSET(varIndex, db->hasMatchingRecord() ? 0 : 1);
 }
 
-void Inter_v7::o7_opendBase() {
+void Inter_v7::o7_openTranlsationDB() {
 	Common::String dbFile = getFile(_vm->_game->_script->evalString());
 	Common::String id     = _vm->_game->_script->evalString();
 
 	dbFile += ".DBF";
 
-	_databases.setLanguage(_vm->_language);
-	if (!_databases.open(id, Common::Path(dbFile))) {
+	_translationDatabases.setLanguage(_vm->_language);
+	if (!_translationDatabases.open(id, Common::Path(dbFile))) {
 		WRITE_VAR(27, 0); // Failure
 		return;
 	}
@@ -1145,10 +1238,10 @@ void Inter_v7::o7_opendBase() {
 	WRITE_VAR(27, 1); // Success
 }
 
-void Inter_v7::o7_closedBase() {
+void Inter_v7::o7_closeTranslationDB() {
 	Common::String id = _vm->_game->_script->evalString();
 
-	if (_databases.close(id))
+	if (_translationDatabases.close(id))
 		WRITE_VAR(27, 1); // Success
 	else
 		WRITE_VAR(27, 0); // Failure
@@ -1161,7 +1254,7 @@ void Inter_v7::o7_getDBString() {
 	Common::String keyword = _vm->_game->_script->evalString();
 
 	Common::String result;
-	if (!_databases.getString(id, group, section, keyword, result)) {
+	if (!_translationDatabases.getString(id, group, section, keyword, result)) {
 		WRITE_VAR(27, 0); // Failure
 		storeString("");
 		return;


Commit: f0a92e7579f37e5c93f19daf4d4909c523e2aa82
    https://github.com/scummvm/scummvm/commit/f0a92e7579f37e5c93f19daf4d4909c523e2aa82
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix "no wait sound" condition in Adibou2/Sciences videos

Changed paths:
    engines/gob/inter_v7.cpp
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h
    video/coktel_decoder.cpp
    video/coktel_decoder.h


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 31da6ebeaeb..59c424e62e2 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -719,6 +719,8 @@ void Inter_v7::o7_playVmdOrMusic() {
 			// if (video not in cache)
 			//   return;
 
+			props.noWaitSound = true;
+
 			props.lastFrame += 100;
 		}
 
@@ -754,6 +756,10 @@ void Inter_v7::o7_playVmdOrMusic() {
 		props.noBlock = true;
 	}
 
+	if (_vm->_vidPlayer->getSoundFlags() & 0x100) {
+		props.noWaitSound = true;
+	}
+
 	if (props.startFrame == -2 || props.startFrame == -3) {
 		props.startFrame = 0;
 		props.lastFrame  = 0;
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 6995b33ca4d..f1d33290f8b 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -45,7 +45,7 @@ VideoPlayer::Properties::Properties() : type(kVideoTypeTry), sprite(Draw::kFront
 	startFrame(-1), lastFrame(-1), endFrame(-1), forceSeek(false),
 	breakKey(kShortKeyEscape), palCmd(8), palStart(0), palEnd(255), palFrame(-1),
 	noBlock(false), loop(false), fade(false), waitEndFrame(true),
-	hasSound(false), canceled(false), slot(-1) {
+	hasSound(false), canceled(false), noWaitSound(false), slot(-1) {
 
 }
 
@@ -547,7 +547,7 @@ void VideoPlayer::updateLive(int slot, bool force) {
 
 	if (_vm->getGameType() == kGameTypeAdibou2) {
 		if (video->decoder->hasVideo() &&
-			!(video->properties.flags & 0x100))
+			!video->properties.noWaitSound)
 			return;
 
 		video->properties.startFrame = video->decoder->getCurFrame();
@@ -881,6 +881,14 @@ uint32 VideoPlayer::getFlags(int slot) const {
 	return video->decoder->getFlags();
 }
 
+uint16 VideoPlayer::getSoundFlags(int slot) const {
+	const Video *video = getVideoBySlot(slot);
+	if (!video)
+		return 0;
+
+	return video->decoder->getSoundFlags();
+}
+
 uint32 VideoPlayer::getVideoBufferSize(int slot) const {
 	const Video *video = getVideoBySlot(slot);
 	if (!video)
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index f226d3ea5d0..8faae2399e8 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -106,6 +106,8 @@ public:
 
 		int slot; ///< Explicit slot index (-1 = auto).
 
+		bool noWaitSound;
+
 		Properties();
 	};
 
@@ -149,6 +151,7 @@ public:
 	uint16 getDefaultX       (int slot = 0) const;
 	uint16 getDefaultY       (int slot = 0) const;
 	uint32 getFlags          (int slot = 0) const;
+	uint16 getSoundFlags     (int slot = 0) const;
 	uint32 getVideoBufferSize(int slot = 0) const;
 	bool   hasVideo          (int slot = 0) const;
 
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index 0945b1b1930..a4d64cecf51 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -1000,6 +1000,10 @@ uint32 PreIMDDecoder::getFlags() const {
 	return 0;
 }
 
+uint16 PreIMDDecoder::getSoundFlags() const {
+	return 0;
+}
+
 uint32 PreIMDDecoder::getVideoBufferSize() const {
 	return _videoBufferSize;
 }
@@ -1689,6 +1693,10 @@ uint32 IMDDecoder::getFlags() const {
 	return _flags;
 }
 
+uint16 IMDDecoder::getSoundFlags() const {
+	return _soundFlags;
+}
+
 uint32 IMDDecoder::getVideoBufferSize() const {
 	return _videoBufferSize;
 }
@@ -2931,6 +2939,10 @@ uint32 VMDDecoder::getFlags() const {
 	return _flags;
 }
 
+uint16 VMDDecoder::getSoundFlags() const {
+	return _soundFlags;
+}
+
 uint32 VMDDecoder::getVideoBufferSize() const {
 	return _videoBufferSize;
 }
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index fee32987555..291b5c63f7f 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -197,6 +197,7 @@ public:
 	uint16 getWidth()  const;
 	uint16 getHeight() const;
 	virtual uint32 getFlags() const = 0;
+	virtual uint16 getSoundFlags() const = 0;
 	virtual Graphics::PixelFormat getPixelFormat() const = 0;
 
 	uint32 getFrameCount() const;
@@ -320,6 +321,7 @@ public:
 	const Graphics::Surface *decodeNextFrame();
 
 	uint32 getFlags() const;
+	uint16 getSoundFlags() const;
 	uint32 getVideoBufferSize() const;
 
 	Graphics::PixelFormat getPixelFormat() const;
@@ -355,6 +357,7 @@ public:
 	const Graphics::Surface *decodeNextFrame();
 
 	uint32 getFlags() const;
+	uint16 getSoundFlags() const;
 	uint32 getVideoBufferSize() const;
 
 	Graphics::PixelFormat getPixelFormat() const;
@@ -463,6 +466,7 @@ public:
 	const Graphics::Surface *decodeNextFrame();
 
 	uint32 getFlags() const;
+	uint16 getSoundFlags() const;
 	uint32 getVideoBufferSize() const;
 
 	Graphics::PixelFormat getPixelFormat() const;


Commit: 7e3ec46082ea17e9666fa622d20ce41166dd985a
    https://github.com/scummvm/scummvm/commit/7e3ec46082ea17e9666fa622d20ce41166dd985a
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Register save files for Adibou2/Anglais and Adibou2/Musique

Changed paths:
    engines/gob/save/saveload.h
    engines/gob/save/saveload_v7.cpp


diff --git a/engines/gob/save/saveload.h b/engines/gob/save/saveload.h
index e26b5a5f28d..2cf378bb0bc 100644
--- a/engines/gob/save/saveload.h
+++ b/engines/gob/save/saveload.h
@@ -978,7 +978,7 @@ protected:
 class SaveLoad_v7: public SaveLoad {
 public:
 	static const uint32 kChildrenCount = 16;
-	static const uint32 kAdibou2NbrOfApplications = 5;
+	static const uint32 kAdibou2NbrOfApplications = 7;
 	static const uint32 kAdibou2NbrOfSavedDrawings = 12;
 	static const uint32 kAdibou2NbrOfConstructionGameFiles = 3;
 	SaveLoad_v7(GobEngine *vm, const char *targetName);
@@ -1107,8 +1107,8 @@ protected:
 	FakeFileHandler             *_adibou2ExerciseListHandler;
 	FakeFileHandler             *_adibou2RelanceHandler;
 	FakeFileHandler             *_adibou2MemHandler;
-	GameFileHandler             *_adibou2SciencesProgressHandler[kChildrenCount];
-	GameFileHandler             *_adibou2AppliSciencesIniHandler;
+	GameFileHandler             *_adibou2AppProgressExtHandler[kAdibou2NbrOfApplications - 4][kChildrenCount];
+	GameFileHandler             *_adibou2AppliIniHandler[kAdibou2NbrOfApplications - 4];
 
 	FakeFileHandler             *_addy4BaseHandler[2];
 	FakeFileHandler             *_addy4GrundschuleHandler[11];
diff --git a/engines/gob/save/saveload_v7.cpp b/engines/gob/save/saveload_v7.cpp
index 757f087a1f6..b66f5f3251d 100644
--- a/engines/gob/save/saveload_v7.cpp
+++ b/engines/gob/save/saveload_v7.cpp
@@ -167,87 +167,119 @@ SaveLoad_v7::SaveFile SaveLoad_v7::_saveFiles[] = {
 	{"DATA/ptreco16.inf"  , kSaveModeSave, nullptr, "construction game progress" },
 	{"DATA/aide16.inf"    , kSaveModeSave, nullptr, "construction game progress" },
 
-	// Adibou Applications 1-5
+	// Adibou Applications 1-7
 	{"DATA/Gsa01_01.inf", kSaveModeSave, nullptr, "app progress" }, // Child 01
 	{"DATA/Gsa02_01.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_01.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_01.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_01.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_01.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_01.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_02.inf", kSaveModeSave, nullptr, "app progress" }, // Child 02
 	{"DATA/Gsa02_02.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_02.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_02.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_02.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_02.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_02.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_03.inf", kSaveModeSave, nullptr, "app progress" }, // Child 03
 	{"DATA/Gsa02_03.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_03.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_03.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_03.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_03.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_03.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_04.inf", kSaveModeSave, nullptr, "app progress" }, // Child 04
 	{"DATA/Gsa02_04.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_04.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_04.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_04.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_04.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_04.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_05.inf", kSaveModeSave, nullptr, "app progress" }, // Child 05
 	{"DATA/Gsa02_05.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_05.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_05.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_05.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_05.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_05.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_06.inf", kSaveModeSave, nullptr, "app progress" }, // Child 06
 	{"DATA/Gsa02_06.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_06.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_06.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_06.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_06.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_06.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_07.inf", kSaveModeSave, nullptr, "app progress" }, // Child 07
 	{"DATA/Gsa02_07.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_07.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_07.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_07.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_07.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_07.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_08.inf", kSaveModeSave, nullptr, "app progress" }, // Child 08
 	{"DATA/Gsa02_08.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_08.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_08.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_08.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_08.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_08.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_09.inf", kSaveModeSave, nullptr, "app progress" }, // Child 09
 	{"DATA/Gsa02_09.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_09.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_09.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_09.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_09.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_09.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_10.inf", kSaveModeSave, nullptr, "app progress" }, // Child 10
 	{"DATA/Gsa02_10.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_10.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_10.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_10.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_10.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_10.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_11.inf", kSaveModeSave, nullptr, "app progress" }, // Child 11
 	{"DATA/Gsa02_11.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_11.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_11.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_11.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_11.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_11.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_12.inf", kSaveModeSave, nullptr, "app progress" }, // Child 12
 	{"DATA/Gsa02_12.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_12.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_12.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_12.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_12.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_12.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_13.inf", kSaveModeSave, nullptr, "app progress" }, // Child 13
 	{"DATA/Gsa02_13.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_13.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_13.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_13.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_13.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_13.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_14.inf", kSaveModeSave, nullptr, "app progress" }, // Child 14
 	{"DATA/Gsa02_14.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_14.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_14.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_14.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_14.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_14.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_15.inf", kSaveModeSave, nullptr, "app progress" }, // Child 15
 	{"DATA/Gsa02_15.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_15.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_15.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_15.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_15.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_15.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa01_16.inf", kSaveModeSave, nullptr, "app progress" }, // Child 16
 	{"DATA/Gsa02_16.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa03_16.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa04_16.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/Gsa05_16.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa06_16.inf", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/Gsa07_16.inf", kSaveModeSave, nullptr, "app progress" },
 	{"DATA/memo_01.inf", kSaveModeSave, nullptr, "memo" }, // Child 01
 	{"DATA/memo_02.inf", kSaveModeSave, nullptr, "memo" }, // Child 02
 	{"DATA/memo_03.inf", kSaveModeSave, nullptr, "memo" }, // Child 03
@@ -285,21 +317,29 @@ SaveLoad_v7::SaveFile SaveLoad_v7::_saveFiles[] = {
 	{"DATA/appli_03.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/appli_04.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/appli_05.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/appli_06.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/appli_07.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/crite_01.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/crite_02.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/crite_03.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/crite_04.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/crite_05.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/crite_06.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/crite_07.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/exo_01.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/exo_02.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/exo_03.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/exo_04.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/exo_05.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/exo_06.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/exo_07.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/ico_01.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/ico_02.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/ico_03.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/ico_04.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/ico_05.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/ico_06.inf", kSaveModeSave, nullptr, "app info" },
+	{"DATA/ico_07.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/applis.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/lance.inf", kSaveModeSave, nullptr, "app info" },
 	{"DATA/retour.inf", kSaveModeSave, nullptr, "app info" },
@@ -826,21 +866,68 @@ SaveLoad_v7::SaveFile SaveLoad_v7::_saveFiles[] = {
 	{"TEMP/mem.$$$", kSaveModeSave, nullptr, "app info" },
 
 	{"DATA/GIE05_01.pho", kSaveModeSave, nullptr, "app progress" }, // Child 01
+	{"DATA/GIE06_01.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_01.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_02.pho", kSaveModeSave, nullptr, "app progress" }, // Child 02
+	{"DATA/GIE06_02.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_02.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_03.pho", kSaveModeSave, nullptr, "app progress" }, // Child 03
+	{"DATA/GIE06_03.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_03.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_04.pho", kSaveModeSave, nullptr, "app progress" }, // Child 04
+	{"DATA/GIE06_04.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_04.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_05.pho", kSaveModeSave, nullptr, "app progress" }, // Child 05
+	{"DATA/GIE06_05.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_05.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_06.pho", kSaveModeSave, nullptr, "app progress" }, // Child 06
+	{"DATA/GIE06_06.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_06.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_07.pho", kSaveModeSave, nullptr, "app progress" }, // Child 07
+	{"DATA/GIE06_07.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_07.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_08.pho", kSaveModeSave, nullptr, "app progress" }, // Child 08
+	{"DATA/GIE06_08.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_08.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_09.pho", kSaveModeSave, nullptr, "app progress" }, // Child 09
+	{"DATA/GIE06_09.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_09.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_10.pho", kSaveModeSave, nullptr, "app progress" }, // Child 10
+	{"DATA/GIE06_10.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_10.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_11.pho", kSaveModeSave, nullptr, "app progress" }, // Child 11
+	{"DATA/GIE06_11.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_11.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_12.pho", kSaveModeSave, nullptr, "app progress" }, // Child 12
+	{"DATA/GIE06_12.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_12.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_13.pho", kSaveModeSave, nullptr, "app progress" }, // Child 13
+	{"DATA/GIE06_13.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_13.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_14.pho", kSaveModeSave, nullptr, "app progress" }, // Child 14
+	{"DATA/GIE06_14.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_14.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_15.pho", kSaveModeSave, nullptr, "app progress" }, // Child 15
+	{"DATA/GIE06_15.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_15.pho", kSaveModeSave, nullptr, "app progress" },
+
 	{"DATA/GIE05_16.pho", kSaveModeSave, nullptr, "app progress" }, // Child 16
+	{"DATA/GIE06_16.pho", kSaveModeSave, nullptr, "app progress" },
+	{"DATA/GIE07_16.pho", kSaveModeSave, nullptr, "app progress" },
 
 	{"APPLIS/appli_05.ini", kSaveModeSave, nullptr, "app info" },
 
@@ -1360,13 +1447,17 @@ SaveLoad_v7::SaveLoad_v7(GobEngine *vm, const char *targetName) :
 	_saveFiles[index++].handler = _adibou2RelanceHandler = new FakeFileHandler(_vm);
 	_saveFiles[index++].handler = _adibou2MemHandler = new FakeFileHandler(_vm);
 
-	for (uint32 i = 0; i < kChildrenCount; i++) {
-		_saveFiles[index++].handler = _adibou2SciencesProgressHandler[i] = new GameFileHandler(_vm,
-																							   targetName,
-																							   Common::String::format("gie_05_%02d_pho", i + 1));
-	}
+	for (uint32 i = 4; i < kAdibou2NbrOfApplications; i++) {
+		for (uint32 j = 0; j < kChildrenCount; j++) {
+			_saveFiles[index++].handler = _adibou2AppProgressExtHandler[i - 4][j] = new GameFileHandler(_vm,
+																										targetName,
+																										Common::String::format("gie_%02d_%02d_pho", i + 1, j + 1));
+		}
 
-	_saveFiles[index++].handler = _adibou2AppliSciencesIniHandler = new GameFileHandler(_vm, targetName, "appli_05_ini");
+		_saveFiles[index++].handler = _adibou2AppliIniHandler[i - 4] = new GameFileHandler(_vm,
+																						   targetName,
+																						   Common::String::format("appli_%02d_ini", i + 1));
+	}
 
 	for (int i = 0; i < 2; i++)
 		_saveFiles[index++].handler = _addy4BaseHandler[i] = new FakeFileHandler(_vm);
@@ -1444,11 +1535,15 @@ SaveLoad_v7::~SaveLoad_v7() {
 	delete _adibou2RelanceHandler;
 	delete _adibou2MemHandler;
 
-	for (uint32 i = 0; i < kChildrenCount; i++) {
-		delete _adibou2SciencesProgressHandler[i];
-	}
 
-	delete _adibou2AppliSciencesIniHandler;
+	for (uint32 i = 4; i < kAdibou2NbrOfApplications; i++) {
+		for (uint32 j = 0; j < kChildrenCount; j++) {
+			delete _adibou2AppProgressExtHandler[i - 4][j];
+		}
+
+
+		delete _adibou2AppliIniHandler[i - 4];
+	}
 }
 
 const SaveLoad_v7::SaveFile *SaveLoad_v7::getSaveFile(const char *fileName) const {


Commit: 2e772ee92955f2f5f326a0efa88383a142128064
    https://github.com/scummvm/scummvm/commit/2e772ee92955f2f5f326a0efa88383a142128064
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add more Adibou/Musique stubs

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 3b2f9c0fced..9b1949d796e 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -740,6 +740,7 @@ protected:
 	void o7_findNextDatabaseRecord();
 	void o7_getDatabaseRecordValue();
 	void o7_checkAnyDatabaseRecordFound();
+	void o7_draw0xC3();
 	void o7_openTranlsationDB();
 	void o7_closeTranslationDB();
 	void o7_getDBString();
@@ -757,6 +758,13 @@ protected:
 	void o7_draw0xE4();
 	void o7_draw0xE6();
 	void o7_draw0xE7();
+	void o7_draw0xE8();
+	void o7_draw0xE9();
+	void o7_draw0xF0();
+	void o7_executeModAddEvent();
+	void o7_executeModSetLength();
+	void o7_executeModGetPosition();
+	void o7_executeModStart();
 	void o7_vmdGetSoundBuffer();
 	void o7_vmdReleaseSoundBuffer();
 
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 59c424e62e2..5ea5b8d34d3 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -103,6 +103,7 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xB1, o7_findNextDatabaseRecord);
 	OPCODEDRAW(0xB4, o7_getDatabaseRecordValue);
 	OPCODEDRAW(0xB6, o7_checkAnyDatabaseRecordFound);
+	OPCODEDRAW(0xC3, o7_draw0xC3);
 	OPCODEDRAW(0xC4, o7_openTranlsationDB);
 	OPCODEDRAW(0xC5, o7_closeTranslationDB);
 	OPCODEDRAW(0xC6, o7_getDBString);
@@ -120,6 +121,13 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xE4, o7_draw0xE4);
 	OPCODEDRAW(0xE6, o7_draw0xE6);
 	OPCODEDRAW(0xE7, o7_draw0xE7);
+	OPCODEDRAW(0xE8, o7_draw0xE8);
+	OPCODEDRAW(0xE8, o7_draw0xE9);
+	OPCODEDRAW(0xF0, o7_draw0xF0);
+	OPCODEDRAW(0xF2, o7_executeModAddEvent);
+	OPCODEDRAW(0xF4, o7_executeModSetLength);
+	OPCODEDRAW(0xF5, o7_executeModStart);
+	OPCODEDRAW(0xF7, o7_executeModGetPosition);
 	OPCODEDRAW(0xFA, o7_vmdGetSoundBuffer);
 	OPCODEDRAW(0xFB, o7_vmdReleaseSoundBuffer);
 }
@@ -1229,6 +1237,17 @@ void Inter_v7::o7_checkAnyDatabaseRecordFound() {
 	WRITE_VAR_OFFSET(varIndex, db->hasMatchingRecord() ? 0 : 1);
 }
 
+void Inter_v7::o7_draw0xC3() {
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readValExpr();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xC3 (Adibou/Musique)");
+}
+
 void Inter_v7::o7_openTranlsationDB() {
 	Common::String dbFile = getFile(_vm->_game->_script->evalString());
 	Common::String id     = _vm->_game->_script->evalString();
@@ -1341,6 +1360,45 @@ void Inter_v7::o7_draw0xE7() {
 	warning("STUB: o7_draw0xE7 (Adibou/Musique)");
 };
 
+void Inter_v7::o7_draw0xE8() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xE8 (Adibou/Musique)");
+};
+
+void Inter_v7::o7_draw0xE9() {
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xE8 (Adibou/Musique)");
+};
+
+void Inter_v7::o7_draw0xF0() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xF0 (Adibou/Musique)");
+};
+
+void Inter_v7::o7_executeModAddEvent() {
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_executeModAddEvent (Adibou/Musique)");
+};
+
+void Inter_v7::o7_executeModSetLength() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_executeModSetLength (Adibou/Musique)");
+};
+
+void Inter_v7::o7_executeModStart() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_executeModSetLength (Adibou/Musique)");
+};
+
+void Inter_v7::o7_executeModGetPosition() {
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_executeModSetLength (Adibou/Musique)");
+};
+
 void Inter_v7::o7_vmdGetSoundBuffer() {
 	_vm->_game->_script->readValExpr();
 	_vm->_game->_script->readVarIndex();


Commit: 093fe6f56062fed55d59e193aa8718e328ba2cd8
    https://github.com/scummvm/scummvm/commit/093fe6f56062fed55d59e193aa8718e328ba2cd8
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Handle BMP and JPEG image types (Adibou2)

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 6d2c0418d74..a13445ae9e9 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -42,6 +42,9 @@
 #include "image/iff.h"
 #include "image/tga.h"
 
+#include "image/bmp.h"
+#include "image/jpeg.h"
+
 namespace Gob {
 
 class SurfacePrimitives final : public Graphics::Primitives {
@@ -889,6 +892,15 @@ bool Surface::getImageInfo(Common::SeekableReadStream &stream, uint32 &width, ui
 	case kImageTypeIFF:
 		decoder.reset(new Image::IFFDecoder());
 		break;
+	case kImageTypeBRC:
+		warning("Surface::getImageInfo(): BRC images are not supported");
+		break;
+	case kImageTypeBMP:
+		decoder.reset(new Image::BitmapDecoder());
+		break;
+	case kImageTypeJPEG:
+		decoder.reset(new Image::JPEGDecoder());
+		break;
 	default:
 		warning("Surface::getImageInfo(): Unhandled image type: %d", (int)type);
 		return false;
@@ -1035,13 +1047,13 @@ bool Surface::loadBRC(Common::SeekableReadStream &stream, int16 left, int16 top,
 }
 
 bool Surface::loadBMP(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
-	warning("TODO: Surface::loadBMP()");
-	return false;
+	Image::BitmapDecoder decoder;
+	return loadImage(decoder, stream, left, top, right, bottom, x, y, transp, format);
 }
 
 bool Surface::loadJPEG(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
-	warning("TODO: Surface::loadJPEG()");
-	return false;
+	Image::JPEGDecoder decoder;
+	return loadImage(decoder, stream, left, top, right, bottom, x, y, transp, format);
 }
 
 void Surface::computeHighColorMap(uint32 *highColorMap, const byte *palette,


Commit: 95ca8e315b353369ebb2abdf6106bc27a9316320
    https://github.com/scummvm/scummvm/commit/95ca8e315b353369ebb2abdf6106bc27a9316320
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
IMAGE: Add decoder for BRC format

Simple image bitmap format with run-length encoding, used in the Gob
engine (Adibou/Sciences).

We only support 2bpp sources for now.

Changed paths:
  A image/brc.cpp
  A image/brc.h
    image/module.mk


diff --git a/image/brc.cpp b/image/brc.cpp
new file mode 100644
index 00000000000..753c638a4cd
--- /dev/null
+++ b/image/brc.cpp
@@ -0,0 +1,168 @@
+/* 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 "image/brc.h"
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+namespace Image {
+
+BRCDecoder::BRCDecoder(Graphics::PixelFormat format): _palette(0), _format(format) {
+}
+
+
+BRCDecoder::~BRCDecoder() {
+	destroy();
+}
+
+void BRCDecoder::destroy() {
+	_surface.free();
+}
+
+void BRCDecoder::readRawChunk(Common::SeekableReadStream &stream, uint16 *&dest) {
+	byte chunkSize = stream.readByte();
+	for (byte i = 0; i < chunkSize; i++) {
+		*dest++ = stream.readUint16LE();
+	}
+}
+
+void BRCDecoder::readRLEChunk(Common::SeekableReadStream &stream, uint16 *&dest) {
+	byte chunkSize = stream.readByte();
+	for (byte i = 0; i < chunkSize; i++) {
+		byte runLength = stream.readByte();
+		uint16 pixel = stream.readUint16LE();
+		for (byte j = 0; j < runLength; j++)
+			*dest++ = pixel;
+	}
+}
+
+void BRCDecoder::readRawChunkColumnWise(Common::SeekableReadStream &stream, uint16 *&dest, uint16 *&destColumnStart, uint32 &remainingHeight) {
+	byte chunkSize = stream.readByte();
+	for (byte i = 0; i < chunkSize; i++) {
+		uint16 pixel = stream.readUint16LE();
+		*dest = pixel;
+		remainingHeight--;
+		if (remainingHeight == 0) {
+			++destColumnStart;
+			dest = destColumnStart;
+			remainingHeight = _surface.h;
+		} else {
+			dest += _surface.w;
+		}
+	}
+}
+
+void BRCDecoder::readRLEChunkColumnWise(Common::SeekableReadStream &stream, uint16 *&dest, uint16 *&destColumnStart, uint32 &remainingHeight) {
+	byte chunkSize = stream.readByte();
+	for (byte i = 0; i < chunkSize; i++) {
+		byte runLength = stream.readByte();
+		uint16 pixel = stream.readUint16LE();
+		for (byte j = 0; j < runLength; j++) {
+			*dest = pixel;
+			remainingHeight--;
+			if (remainingHeight == 0) {
+				++destColumnStart;
+				dest = destColumnStart;
+				remainingHeight = _surface.h;
+			} else {
+				dest += _surface.w;
+			}
+		}
+	}
+}
+
+void BRCDecoder::loadBRCData(Common::SeekableReadStream &stream, uint32 nbrOfChunks, bool firstChunkIsRLE) {
+	uint16 *dest = (uint16*) _surface.getBasePtr(0, 0);
+	if (firstChunkIsRLE) {
+		for (uint32 i = 0; i < nbrOfChunks; i++) {
+			readRLEChunk(stream, dest);
+			readRawChunk(stream, dest);
+		}
+	} else {
+		for (uint32 i = 0; i < nbrOfChunks; i++) {
+			readRawChunk(stream, dest);
+			readRLEChunk(stream, dest);
+		}
+	}
+}
+void BRCDecoder::loadBRCDataColumnWise(Common::SeekableReadStream &stream, uint32 nbrOfChunks, bool firstChunkIsRLE) {
+	uint16 *dest = (uint16*) _surface.getBasePtr(0, 0);
+	uint16 *destColumnStart = dest;
+	uint32 remainingHeight = _surface.h;
+	if (firstChunkIsRLE) {
+		for (uint32 i = 0; i < nbrOfChunks; i++) {
+			readRLEChunkColumnWise(stream, dest, destColumnStart, remainingHeight);
+			readRawChunkColumnWise(stream, dest, destColumnStart, remainingHeight);
+		}
+	} else {
+		for (uint32 i = 0; i < nbrOfChunks; i++) {
+			readRawChunkColumnWise(stream, dest, destColumnStart, remainingHeight);
+			readRLEChunkColumnWise(stream, dest, destColumnStart, remainingHeight);
+		}
+	}
+}
+
+bool BRCDecoder::loadStream(Common::SeekableReadStream &stream) {
+	destroy();
+
+	uint32 fileType = stream.readUint32BE();
+
+	if (fileType != MKTAG('B', 'R', 'C', '\0')) {
+		warning("Missing BRC header");
+		return false;
+	}
+
+	stream.skip(8); // unknown
+	stream.readUint32LE(); // data size
+
+	uint32 width = stream.readUint32LE();
+	uint32 height = stream.readUint32LE();
+
+	if (width == 0 || height == 0)
+		return false;
+
+	uint32 bpp = stream.readUint32LE();
+	if (bpp != 2) {
+		warning("Unsupported bpp (%d) for BRCDecoder", bpp);
+		return false;
+	}
+
+	_surface.create(width, height, _format);
+
+	uint32 flags = stream.readUint32LE();
+	uint32 headerSize = stream.readUint32LE();
+	uint32 nbrOfChunks = stream.readUint32LE();
+
+	bool columnWise = !(flags & 0x10);
+	bool firstChunkIsRLE = flags & 2;
+
+	stream.seek(headerSize, SEEK_SET);
+	if (columnWise)
+		loadBRCDataColumnWise(stream, nbrOfChunks, firstChunkIsRLE);
+	else
+		loadBRCData(stream, nbrOfChunks, firstChunkIsRLE);
+
+	return true;
+}
+
+
+} // End of namespace Image
diff --git a/image/brc.h b/image/brc.h
new file mode 100644
index 00000000000..837d57155b5
--- /dev/null
+++ b/image/brc.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/>.
+ *
+ */
+
+/**
+ * @file
+ * Image decoder used in engines:
+ *  - gob
+ */
+
+#ifndef IMAGE_BRC_H
+#define IMAGE_BRC_H
+
+#include "graphics/surface.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+/**
+ * @defgroup image_brc BRC decoder
+ * @ingroup image
+ *
+ * @brief Decoder for BRC images.
+ *
+ * Used in engines:
+ *  - Gob
+ * @{
+ */
+
+class BRCDecoder : public ImageDecoder {
+public:
+	BRCDecoder(Graphics::PixelFormat format);
+	~BRCDecoder() override;
+
+	// ImageDecoder API
+	void destroy() override;
+	bool loadStream(Common::SeekableReadStream &stream) override;
+	const Graphics::Surface *getSurface() const override { return &_surface; }
+	const Graphics::Palette &getPalette() const override { return _palette; }
+
+private:
+	Graphics::Surface _surface;
+	Graphics::Palette _palette;
+	Graphics::PixelFormat _format;
+	void loadBRCData(Common::SeekableReadStream &stream, uint32 nbrOfChunks, bool firstChunkIsRLE);
+	void loadBRCDataColumnWise(Common::SeekableReadStream &stream, uint32 nbrOfChunks, bool firstChunkIsRLE);
+
+	void readRawChunk(Common::SeekableReadStream &stream, uint16 *&dest);
+	void readRLEChunk(Common::SeekableReadStream &stream, uint16 *&dest);
+	void readRawChunkColumnWise(Common::SeekableReadStream &stream, uint16 *&dest, uint16 *&destColumnStart, uint32 &remainingHeight);
+	void readRLEChunkColumnWise(Common::SeekableReadStream &stream, uint16 *&dest, uint16 *&destColumnStart, uint32 &remainingHeight);
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/module.mk b/image/module.mk
index 67d0c733c13..cd575df4fdb 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -3,6 +3,7 @@ MODULE := image
 MODULE_OBJS := \
 	ani.o \
 	bmp.o \
+	brc.o \
 	cel_3do.o \
 	cicn.o \
 	icocur.o \


Commit: 018fffa2133dae9d326061f9ec8bd72e5c89169c
    https://github.com/scummvm/scummvm/commit/018fffa2133dae9d326061f9ec8bd72e5c89169c
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Handle BRC image type (Adibou2/Sciences)

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index a13445ae9e9..fc878a60dcd 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -39,11 +39,11 @@
 #include "graphics/pixelformat.h"
 #include "graphics/surface.h"
 
-#include "image/iff.h"
-#include "image/tga.h"
-
 #include "image/bmp.h"
+#include "image/brc.h"
+#include "image/iff.h"
 #include "image/jpeg.h"
+#include "image/tga.h"
 
 namespace Gob {
 
@@ -1042,8 +1042,8 @@ bool Surface::loadIFF(Common::SeekableReadStream &stream, int16 left, int16 top,
 }
 
 bool Surface::loadBRC(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
-	warning("TODO: Surface::loadBRC()");
-	return false;
+	Image::BRCDecoder decoder(format);
+	return loadImage(decoder, stream, left, top, right, bottom, x, y, transp, format);
 }
 
 bool Surface::loadBMP(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {


Commit: 17e570102374aa307397cb8e83ef3ba2a46216fe
    https://github.com/scummvm/scummvm/commit/17e570102374aa307397cb8e83ef3ba2a46216fe
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add some opcode stubs for Adibou2/Sciences

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 9b1949d796e..c13e77e5fb7 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -740,6 +740,8 @@ protected:
 	void o7_findNextDatabaseRecord();
 	void o7_getDatabaseRecordValue();
 	void o7_checkAnyDatabaseRecordFound();
+	void o7_draw0xC0();
+	void o7_draw0xC1();
 	void o7_draw0xC3();
 	void o7_openTranlsationDB();
 	void o7_closeTranslationDB();
@@ -747,6 +749,8 @@ protected:
 	void o7_draw0xCC();
 	void o7_draw0xCD();
 	void o7_draw0xCE();
+	void o7_draw0xBE();
+	void o7_draw0xBF();
 	void o7_draw0xDC();
 	void o7_draw0xDD();
 	void o7_draw0xDE();
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 5ea5b8d34d3..c7de9b23626 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -103,6 +103,10 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xB1, o7_findNextDatabaseRecord);
 	OPCODEDRAW(0xB4, o7_getDatabaseRecordValue);
 	OPCODEDRAW(0xB6, o7_checkAnyDatabaseRecordFound);
+	OPCODEDRAW(0xBE, o7_draw0xBE);
+	OPCODEDRAW(0xBF, o7_draw0xBF);
+	OPCODEDRAW(0xC0, o7_draw0xC0);
+	OPCODEDRAW(0xC1, o7_draw0xC1);
 	OPCODEDRAW(0xC3, o7_draw0xC3);
 	OPCODEDRAW(0xC4, o7_openTranlsationDB);
 	OPCODEDRAW(0xC5, o7_closeTranslationDB);
@@ -1237,6 +1241,32 @@ void Inter_v7::o7_checkAnyDatabaseRecordFound() {
 	WRITE_VAR_OFFSET(varIndex, db->hasMatchingRecord() ? 0 : 1);
 }
 
+void Inter_v7::o7_draw0xBE() {
+	_vm->_game->_script->evalString();
+	warning("STUB: o7_draw0xBE (Adibou/Sciences)");
+}
+
+void Inter_v7::o7_draw0xBF() {
+	_vm->_game->_script->evalString();
+	warning("STUB: o7_draw0xBF (Adibou/Sciences)");
+}
+
+void Inter_v7::o7_draw0xC0() {
+	_vm->_game->_script->evalString();
+	_vm->_game->_script->evalString();
+	_vm->_game->_script->evalString();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xC0 (Adibou/Sciences)");
+}
+
+void Inter_v7::o7_draw0xC1() {
+	// Read HTML line
+	_vm->_game->_script->evalString();
+	_vm->_game->_script->readVarIndex();
+	_vm->_game->_script->readVarIndex();
+	warning("STUB: o7_draw0xC1 (Adibou/Sciences)");
+}
+
 void Inter_v7::o7_draw0xC3() {
 	_vm->_game->_script->readValExpr();
 	_vm->_game->_script->readValExpr();


Commit: a301971ede003ebec7ec85bbba6c4b17d77c814d
    https://github.com/scummvm/scummvm/commit/a301971ede003ebec7ec85bbba6c4b17d77c814d
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Implement o7_ansiToOEM and o7_oemToANSI (Adibou2/Sciences)

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index c13e77e5fb7..2fde266da0a 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -781,6 +781,7 @@ protected:
 	void o7_readData(OpFuncParams &params);
 	void o7_writeData(OpFuncParams &params);
 
+	void o7_ansiToOEM(OpGobParams &params);
 	void o7_oemToANSI(OpGobParams &params);
 	void o7_gob0x201(OpGobParams &params);
 	void o7_getFreeDiskSpace(OpGobParams &params);
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index c7de9b23626..8226c74e96f 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -152,7 +152,8 @@ void Inter_v7::setupOpcodesFunc() {
 void Inter_v7::setupOpcodesGob() {
 	Inter_Playtoons::setupOpcodesGob();
 
-	OPCODEGOB(420, o7_oemToANSI);
+	OPCODEGOB(420, o7_ansiToOEM);
+	OPCODEGOB(421, o7_oemToANSI);
 	OPCODEGOB(513, o7_gob0x201);
 	OPCODEGOB(600, o7_getFreeDiskSpace);
 }
@@ -1871,9 +1872,32 @@ void Inter_v7::o7_writeData(OpFuncParams &params) {
 		warning("Attempted to write to file \"%s\"", file.c_str());
 }
 
+void Inter_v7::o7_ansiToOEM(OpGobParams &params) {
+	uint16 varIndex = _vm->_game->_script->readUint16();
+	char *str = GET_VAR_STR(varIndex);
+	Common::U32String u32String = Common::String(str).decode(Common::kWindows1252);
+	// Replace characters that do not exist in the target codepage with the closest match
+	for (int i = 0; i < u32String.size(); ++i) {
+		// Replace curly double quotes with straight double quotes
+		if (u32String[i] == 0x201C || u32String[i] == 0x201D || u32String[i] == 0x201E) {
+			u32String.setChar(0x22, i);
+		}
+
+		// Replace curly single quotes with straight single quotes
+		if (u32String[i] == 0x2018 || u32String[i] == 0x2019) {
+			u32String.setChar(0x27, i);
+		}
+	}
+
+	WRITE_VAR_STR(varIndex, u32String.encode(Common::kDos850).c_str());
+}
+
 
 void Inter_v7::o7_oemToANSI(OpGobParams &params) {
-	_vm->_game->_script->skip(2);
+	uint16 varIndex = _vm->_game->_script->readUint16();
+	char *str = GET_VAR_STR(varIndex);
+	Common::String ansiString = Common::String(str).decode(Common::kDos850).encode(Common::kWindows1252);
+	WRITE_VAR_STR(varIndex, ansiString.c_str());
 }
 
 void Inter_v7::o7_gob0x201(OpGobParams &params) {


Commit: 386b07e3e479ebe72114b7e45a4ebc02cb976bda
    https://github.com/scummvm/scummvm/commit/386b07e3e479ebe72114b7e45a4ebc02cb976bda
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix OEM/ANSI encoding conversions (Adibou2/Sciences)

- Strings from .INI files are ANSI-encoded (ISO-8859), and must be
  converted to OEM before being used by the engine.

- Strings from .DBF files are generally OEM-encoded, and must be converted
  to ANSI (ISO-8859) before being used by the engine. This can be changed
  by the o7_setDBStringEncoding opcode.

Changed paths:
    engines/gob/databases.h
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/databases.h b/engines/gob/databases.h
index 6f681467d3e..fd696eddea7 100644
--- a/engines/gob/databases.h
+++ b/engines/gob/databases.h
@@ -43,6 +43,8 @@ public:
 	~TranslationDatabases();
 
 	void setLanguage(Common::Language language);
+	void setEncodingIsOEM(bool encodingIsOEM) { _encodingIsOEM = encodingIsOEM; }
+	bool encodingIsOEM() const { return _encodingIsOEM; }
 
 	bool open(const Common::String &id, const Common::Path &file);
 	bool close(const Common::String &id);
@@ -56,6 +58,7 @@ private:
 	DBMap _databases;
 
 	Common::String _language;
+	bool _encodingIsOEM = true;
 
 	int findField(const dBase &db, const Common::String &field, dBase::Type type) const;
 	bool buildMap(const dBase &db, Common::StringMap &map) const;
diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 2fde266da0a..7acac08d461 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -701,6 +701,9 @@ protected:
 	void setupOpcodesFunc() override;
 	void setupOpcodesGob() override;
 
+	Common::String ansiToOEM(Common::String string);
+	Common::String oemToANSI(Common::String string);
+
 	void o7_draw0x0C();
 	void o7_setCursorToLoadFromExec();
 	void o7_freeMult();
@@ -783,6 +786,7 @@ protected:
 
 	void o7_ansiToOEM(OpGobParams &params);
 	void o7_oemToANSI(OpGobParams &params);
+	void o7_setDBStringEncoding(OpGobParams &params);
 	void o7_gob0x201(OpGobParams &params);
 	void o7_getFreeDiskSpace(OpGobParams &params);
 
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 8226c74e96f..b9b66ae4cab 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -154,6 +154,7 @@ void Inter_v7::setupOpcodesGob() {
 
 	OPCODEGOB(420, o7_ansiToOEM);
 	OPCODEGOB(421, o7_oemToANSI);
+	OPCODEGOB(512, o7_setDBStringEncoding);
 	OPCODEGOB(513, o7_gob0x201);
 	OPCODEGOB(600, o7_getFreeDiskSpace);
 }
@@ -1021,9 +1022,14 @@ void Inter_v7::o7_getINIValue() {
 	Common::String key     = _vm->_game->_script->evalString();
 	Common::String def     = _vm->_game->_script->evalString();
 
+	section = oemToANSI(section);
+	key     = oemToANSI(key);
+
 	Common::String value;
 	_inis.getValue(value, file, section, key, def);
 
+	value = ansiToOEM(value);
+
 	storeString(value.c_str());
 	debugC(5, kDebugGameFlow, "o7_getINIValue: %s: [%s] %s = %s", file.c_str(), section.c_str(), key.c_str(), value.c_str());
 }
@@ -1035,6 +1041,10 @@ void Inter_v7::o7_setINIValue() {
 	Common::String key     = _vm->_game->_script->evalString();
 	Common::String value   = _vm->_game->_script->evalString();
 
+	section = oemToANSI(section);
+	key     = oemToANSI(key);
+	value   = oemToANSI(value);
+
 	bool success = _inis.setValue(file, section, key, value);
 	WRITE_VAR(27, success ? 1 : 0);
 	debugC(5, kDebugGameFlow, "o7_setINIValue: %s: [%s] %s := %s", file.c_str(), section.c_str(), key.c_str(), value.c_str());
@@ -1316,6 +1326,8 @@ void Inter_v7::o7_getDBString() {
 		return;
 	}
 
+	if (_translationDatabases.encodingIsOEM())
+		result = oemToANSI(result);
 	storeString(result.c_str());
 	WRITE_VAR(27, 1); // Success
 }
@@ -1872,12 +1884,10 @@ void Inter_v7::o7_writeData(OpFuncParams &params) {
 		warning("Attempted to write to file \"%s\"", file.c_str());
 }
 
-void Inter_v7::o7_ansiToOEM(OpGobParams &params) {
-	uint16 varIndex = _vm->_game->_script->readUint16();
-	char *str = GET_VAR_STR(varIndex);
-	Common::U32String u32String = Common::String(str).decode(Common::kWindows1252);
+Common::String Inter_v7::ansiToOEM(Common::String string) {
+	Common::U32String u32String = string.decode(Common::kWindows1252);
 	// Replace characters that do not exist in the target codepage with the closest match
-	for (int i = 0; i < u32String.size(); ++i) {
+	for (int i = 0; i < (int) u32String.size(); ++i) {
 		// Replace curly double quotes with straight double quotes
 		if (u32String[i] == 0x201C || u32String[i] == 0x201D || u32String[i] == 0x201E) {
 			u32String.setChar(0x22, i);
@@ -1889,17 +1899,43 @@ void Inter_v7::o7_ansiToOEM(OpGobParams &params) {
 		}
 	}
 
-	WRITE_VAR_STR(varIndex, u32String.encode(Common::kDos850).c_str());
+	return u32String.encode(Common::kDos850);
+}
+
+
+Common::String Inter_v7::oemToANSI(Common::String string) {
+	return string.decode(Common::kDos850).encode(Common::kWindows1252);
+}
+
+void Inter_v7::o7_ansiToOEM(OpGobParams &params) {
+	uint16 varIndex = _vm->_game->_script->readUint16();
+	char *str = GET_VAR_STR(varIndex);
+	Common::String oemString = ansiToOEM(Common::String(str));
+	WRITE_VAR_STR(varIndex, oemString.c_str());
 }
 
 
 void Inter_v7::o7_oemToANSI(OpGobParams &params) {
 	uint16 varIndex = _vm->_game->_script->readUint16();
 	char *str = GET_VAR_STR(varIndex);
-	Common::String ansiString = Common::String(str).decode(Common::kDos850).encode(Common::kWindows1252);
+	Common::String ansiString = oemToANSI(Common::String(str));
 	WRITE_VAR_STR(varIndex, ansiString.c_str());
 }
 
+void Inter_v7::o7_setDBStringEncoding(OpGobParams &params) {
+	uint16 varIndex = _vm->_game->_script->readUint16();
+	int mode = READ_VAR_UINT32(varIndex);
+	switch (mode) {
+	case 0:
+	case 1:
+		_translationDatabases.setEncodingIsOEM(mode);
+		break;
+	default:
+		warning("o7_setDBStringEncoding: invalid mode %d", mode);
+		break;
+	}
+}
+
 void Inter_v7::o7_gob0x201(OpGobParams &params) {
 	uint16 varIndex = _vm->_game->_script->readUint16();
 


Commit: cdee55612d29fa737ceb4571e8831c9e74e7df00
    https://github.com/scummvm/scummvm/commit/cdee55612d29fa737ceb4571e8831c9e74e7df00
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix incorrect text or shapes color in high color mode

The color index was sometimes used instead of the color value

Changed paths:
    engines/gob/draw_playtoons.cpp
    engines/gob/draw_v2.cpp
    engines/gob/video.cpp
    engines/gob/video.h


diff --git a/engines/gob/draw_playtoons.cpp b/engines/gob/draw_playtoons.cpp
index 27995271ab4..e8e1123fcb7 100644
--- a/engines/gob/draw_playtoons.cpp
+++ b/engines/gob/draw_playtoons.cpp
@@ -155,20 +155,20 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		case 1:
 			_spritesArray[_destSurface]->fillRect(destSpriteX,
 					_destSpriteY, _destSpriteX + 1,
-					_destSpriteY + 1, _frontColor);
+					_destSpriteY + 1, getColor(_frontColor));
 			break;
 		case 2:
 			_spritesArray[_destSurface]->fillRect(destSpriteX - 1,
 					_destSpriteY - 1, _destSpriteX + 1,
-					_destSpriteY + 1, _frontColor);
+					_destSpriteY + 1, getColor(_frontColor));
 			break;
 		case 3:
 			_spritesArray[_destSurface]->fillRect(destSpriteX - 1,
 					_destSpriteY - 1, _destSpriteX + 2,
-					_destSpriteY + 2, _frontColor);
+					_destSpriteY + 2, getColor(_frontColor));
 			break;
 		default:
-			_spritesArray[_destSurface]->putPixel(_destSpriteX, _destSpriteY, _frontColor);
+			_spritesArray[_destSurface]->putPixel(_destSpriteX, _destSpriteY, getColor(_frontColor));
 			break;
 		}
 		dirtiedRect(_destSurface, _destSpriteX - (_pattern / 2),
@@ -188,8 +188,8 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 												  _destSpriteY,
 												  _destSpriteX + _spriteRight - 1,
 												  _destSpriteY + _spriteBottom - 1,
-												  _backColor & 0xFF,
-												  (_backColor >> 8) & 0xFF);
+												  getColor(_backColor & 0xFF),
+												  getColor((_backColor >> 8) & 0xFF));
 
 			dirtiedRect(_destSurface, _destSpriteX, _destSpriteY,
 						_destSpriteX + _spriteRight - 1, _destSpriteY + _spriteBottom - 1);
@@ -198,7 +198,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		case 2: {
 			Common::Rect dirtyRect = _spritesArray[_destSurface]->fillAreaAtPoint(destSpriteX,
 																				  _destSpriteY,
-																				  _backColor);
+																				  getColor(_backColor));
 			dirtiedRect(_destSurface, dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
 			break ;
 		}
@@ -229,18 +229,18 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		if ((_needAdjust != 2) && (_needAdjust < 10)) {
 			warning ("oPlaytoons_spriteOperation: operation DRAW_DRAWLINE, draw multiple lines");
 				_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_spriteRight, _spriteBottom, _frontColor);
+					_spriteRight, _spriteBottom, getColor(_frontColor));
 				_spritesArray[_destSurface]->drawLine(_destSpriteX + 1, _destSpriteY,
-					_spriteRight + 1, _spriteBottom, _frontColor);
+					_spriteRight + 1, _spriteBottom, getColor(_frontColor));
 				_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY + 1,
-					_spriteRight, _spriteBottom + 1, _frontColor);
+					_spriteRight, _spriteBottom + 1, getColor(_frontColor));
 				_spritesArray[_destSurface]->drawLine(_destSpriteX + 1, _destSpriteY + 1,
-					_spriteRight + 1, _spriteBottom + 1, _frontColor);
+					_spriteRight + 1, _spriteBottom + 1, getColor(_frontColor));
 		} else {
 			switch (_pattern & 0xFF) {
 			case 0:
 				_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_spriteRight, _spriteBottom, _frontColor);
+					_spriteRight, _spriteBottom, getColor(_frontColor));
 
 				break;
 			default:
@@ -251,7 +251,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 								_destSpriteY  - (_pattern / 2) + j,
 								_spriteRight  - (_pattern / 2) + i,
 								_spriteBottom - (_pattern / 2) + j,
-								_frontColor);
+								getColor(_frontColor));
 				break;
 			}
 		}
@@ -263,7 +263,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 
 	case DRAW_INVALIDATE:
 		_spritesArray[_destSurface]->drawCircle(_destSpriteX,
-												_destSpriteY, _spriteRight, _frontColor, _pattern & 0xFF);
+												_destSpriteY, _spriteRight, getColor(_frontColor), _pattern & 0xFF);
 
 		dirtiedRect(_destSurface, _destSpriteX - _spriteRight, _destSpriteY - _spriteBottom,
 				_destSpriteX + _spriteRight, _destSpriteY + _spriteBottom);
@@ -305,17 +305,17 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 					len = *dataBuf++;
 					for (int i = 0; i < len; i++, dataBuf += 2) {
 						font->drawLetter(*_spritesArray[_destSurface], READ_LE_UINT16(dataBuf),
-								_destSpriteX, _destSpriteY, _frontColor, _backColor, _transparency);
+								_destSpriteX, _destSpriteY, getColor(_frontColor), getColor(_backColor), _transparency);
 					}
 				} else {
-					font->drawString(_textToPrint, _destSpriteX, _destSpriteY, _frontColor,
-							_backColor, _transparency, *_spritesArray[_destSurface]);
+					font->drawString(_textToPrint, _destSpriteX, _destSpriteY, getColor(_frontColor),
+							getColor(_backColor), _transparency, *_spritesArray[_destSurface]);
 					_destSpriteX += len * font->getCharWidth();
 				}
 			} else {
 				for (int i = 0; i < len; i++) {
 					font->drawLetter(*_spritesArray[_destSurface], _textToPrint[i],
-							_destSpriteX, _destSpriteY, _frontColor, _backColor, _transparency);
+							_destSpriteX, _destSpriteY, getColor(_frontColor), getColor(_backColor), _transparency);
 					_destSpriteX += font->getCharWidth(_textToPrint[i]);
 				}
 			}
@@ -345,28 +345,28 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 	case DRAW_DRAWBAR:
 		if ((_needAdjust != 2) && (_needAdjust < 10)){
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _spriteBottom - 1,
-					_spriteRight, _spriteBottom, _frontColor);
+					_spriteRight, _spriteBottom, getColor(_frontColor));
 
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
-					_destSpriteX + 1, _spriteBottom, _frontColor);
+					_destSpriteX + 1, _spriteBottom, getColor(_frontColor));
 
 			_spritesArray[_destSurface]->fillRect(_spriteRight - 1, _destSpriteY,
-					_spriteRight, _spriteBottom, _frontColor);
+					_spriteRight, _spriteBottom, getColor(_frontColor));
 
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
-					_spriteRight, _destSpriteY + 1, _frontColor);
+					_spriteRight, _destSpriteY + 1, getColor(_frontColor));
 		} else {
 			_spritesArray[_destSurface]->drawLine(_destSpriteX, _spriteBottom,
-					_spriteRight, _spriteBottom, _frontColor);
+					_spriteRight, _spriteBottom, getColor(_frontColor));
 
 			_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_destSpriteX, _spriteBottom, _frontColor);
+					_destSpriteX, _spriteBottom, getColor(_frontColor));
 
 			_spritesArray[_destSurface]->drawLine(_spriteRight, _destSpriteY,
-					_spriteRight, _spriteBottom, _frontColor);
+					_spriteRight, _spriteBottom, getColor(_frontColor));
 
 			_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_spriteRight, _destSpriteY, _frontColor);
+					_spriteRight, _destSpriteY, getColor(_frontColor));
 		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
@@ -377,7 +377,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		if (_backColor != -1) {
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
 			    _spriteRight, _spriteBottom,
-			    _backColor);
+			    getColor(_backColor));
 		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
diff --git a/engines/gob/draw_v2.cpp b/engines/gob/draw_v2.cpp
index a02d4f43a56..090fad78bbc 100644
--- a/engines/gob/draw_v2.cpp
+++ b/engines/gob/draw_v2.cpp
@@ -777,14 +777,14 @@ void Draw_v2::spriteOperation(int16 operation) {
 
 	case DRAW_DRAWLINE:
 		_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-		    _spriteRight, _spriteBottom, _frontColor);
+		    _spriteRight, _spriteBottom, getColor(_frontColor));
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
 		break;
 
 	case DRAW_INVALIDATE:
 		_spritesArray[_destSurface]->drawCircle(_destSpriteX,
-				_destSpriteY, _spriteRight, _frontColor);
+				_destSpriteY, _spriteRight, getColor(_frontColor));
 
 		dirtiedRect(_destSurface, _destSpriteX - _spriteRight, _destSpriteY - _spriteBottom,
 				_destSpriteX + _spriteRight, _destSpriteY + _spriteBottom);
diff --git a/engines/gob/video.cpp b/engines/gob/video.cpp
index 81a1d4c9416..e4b55eb3f5c 100644
--- a/engines/gob/video.cpp
+++ b/engines/gob/video.cpp
@@ -146,7 +146,7 @@ void Font::drawLetter(Surface &surf, uint8 c, uint16 x, uint16 y,
 	}
 }
 
-void Font::drawString(const Common::String &str, int16 x, int16 y, int16 color1, int16 color2,
+void Font::drawString(const Common::String &str, int16 x, int16 y, uint32 color1, uint32 color2,
 					  bool transp, Surface &dest) const {
 
 	const char *s = str.c_str();
diff --git a/engines/gob/video.h b/engines/gob/video.h
index f42556fd8c0..87798f81aa6 100644
--- a/engines/gob/video.h
+++ b/engines/gob/video.h
@@ -53,7 +53,7 @@ public:
 	void drawLetter(Surface &surf, uint8 c, uint16 x, uint16 y,
 	                uint32 color1, uint32 color2, bool transp) const;
 
-	void drawString(const Common::String &str, int16 x, int16 y, int16 color1, int16 color2,
+	void drawString(const Common::String &str, int16 x, int16 y, uint32 color1, uint32 color2,
 	                bool transp, Surface &dest) const;
 
 private:


Commit: fc92d000acff92bd003aa54d698e5df2af1096fb
    https://github.com/scummvm/scummvm/commit/fc92d000acff92bd003aa54d698e5df2af1096fb
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Implement HTML parsing opcodes (Adibou2/Sciences)

This results in a funny HTML-based rich-text engine, built on these
opcodes and some rendering functions written in the game script language.

Changed paths:
    engines/gob/inter.h
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 7acac08d461..6a63aab385e 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -743,8 +743,8 @@ protected:
 	void o7_findNextDatabaseRecord();
 	void o7_getDatabaseRecordValue();
 	void o7_checkAnyDatabaseRecordFound();
-	void o7_draw0xC0();
-	void o7_draw0xC1();
+	void o7_seekHtmlFile();
+	void o7_nextKeywordHtmlFile();
 	void o7_draw0xC3();
 	void o7_openTranlsationDB();
 	void o7_closeTranslationDB();
@@ -752,8 +752,8 @@ protected:
 	void o7_draw0xCC();
 	void o7_draw0xCD();
 	void o7_draw0xCE();
-	void o7_draw0xBE();
-	void o7_draw0xBF();
+	void o7_openHtmlFile();
+	void o7_closeHtmlFile();
 	void o7_draw0xDC();
 	void o7_draw0xDD();
 	void o7_draw0xDE();
@@ -791,6 +791,74 @@ protected:
 	void o7_getFreeDiskSpace(OpGobParams &params);
 
 private:
+	static const uint32 kMaxNbrOfHtmlMarks = 20;
+
+	struct HtmlMark {
+		uint32 _field_0;
+		uint32 _field_4;
+		uint32 _field_8;
+		uint32 _field_C;
+		uint32 _pos;
+		uint32 _field_14[5];
+		uint32 _field_28;
+	};
+
+	class HtmlContext {
+	public:
+		HtmlContext(Common::SeekableReadStream *stream);
+		~HtmlContext();
+
+		enum HtmlTagType {
+			kHtmlTagType_None = -1,
+			kHtmlTagType_Error,
+			kHtmlTagType_OutsideTag,
+			kHtmlTagType_Body,
+			kHtmlTagType_Font,
+			kHtmlTagType_Font_Close,
+			kHtmlTagType_Img,
+			kHtmlTagType_A,
+			kHtmlTagType_A_Close,
+			kHtmlTagType_Title,
+			kHtmlTagType_Title_Close,
+			kHtmlTagType_HTML_Close,
+			kHtmlTagType_BR,
+			kHtmlTagType_P,
+			kHtmlTagType_P_Close,
+			kHtmlTagType_U,
+			kHtmlTagType_U_Close,
+			kHtmlTagType_B,
+			kHtmlTagType_B_Close,
+			kHtmlTagType_EM,
+			kHtmlTagType_EM_Close,
+			kHtmlTagType_I,
+			kHtmlTagType_I_Close,
+			kHtmlTagType_SUB,
+			kHtmlTagType_SUB_Close,
+			kHtmlTagType_SUP,
+			kHtmlTagType_SUP_Close,
+		};
+
+		static Common::HashMap<Common::String, HtmlTagType> *_htmlTagsTypesMap;
+		static Common::HashMap<Common::String, char> *_htmlEntitiesMap;
+		static Common::String popStringPrefix(const char **charPtr, char sep);
+		static HtmlTagType getHtmlTagType(const char *tag);
+		static char getHtmlEntityLatin1Char(const char *entity);
+		static Common::String substituteHtmlEntities(const char *text);
+
+		void parseTagAttributes(const char* tagAttributes);
+		void cleanTextNode(int animDataSize);
+
+		HtmlTagType _currentTagType;
+		Common::SeekableReadStream *_stream;
+		int _pos;
+		int _posBak;
+		bool _field_10;
+		char _buffer[256];
+		HtmlMark _marks[kMaxNbrOfHtmlMarks];
+		uint32 _currentMarkIndex;
+		Common::String _htmlVariables[10];
+	};
+
 	INIConfig _inis;
 	TranslationDatabases _translationDatabases;
 	Common::HashMap<Common::String, Database, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _databases;
@@ -798,7 +866,9 @@ private:
 	Common::ArchiveMemberList _remainingFilesFromPreviousSearch;
 	Common::String _currentCDPath;
 
-	Common::String findFile(const Common::String &mask, const Common::String &previousFile);
+	Common::String _currentHtmlFile;
+	HtmlContext *_currentHtmlContext;
+
 	void copyFile(const Common::String &sourceFile, bool sourceIsCd, const Common::String &destFile);
 
 	bool setCurrentCDPath(const Common::String &dir);
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index b9b66ae4cab..a4ee248ff63 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -59,7 +59,7 @@ namespace Gob {
 #define OPCODEFUNC(i, x)  _opcodesFunc[i]._OPCODEFUNC(OPCODEVER, x)
 #define OPCODEGOB(i, x)   _opcodesGob[i]._OPCODEGOB(OPCODEVER, x)
 
-Inter_v7::Inter_v7(GobEngine *vm) : Inter_Playtoons(vm), _inis(vm) {
+Inter_v7::Inter_v7(GobEngine *vm) : Inter_Playtoons(vm), _inis(vm), _currentHtmlContext(nullptr) {
 }
 
 void Inter_v7::setupOpcodesDraw() {
@@ -103,10 +103,10 @@ void Inter_v7::setupOpcodesDraw() {
 	OPCODEDRAW(0xB1, o7_findNextDatabaseRecord);
 	OPCODEDRAW(0xB4, o7_getDatabaseRecordValue);
 	OPCODEDRAW(0xB6, o7_checkAnyDatabaseRecordFound);
-	OPCODEDRAW(0xBE, o7_draw0xBE);
-	OPCODEDRAW(0xBF, o7_draw0xBF);
-	OPCODEDRAW(0xC0, o7_draw0xC0);
-	OPCODEDRAW(0xC1, o7_draw0xC1);
+	OPCODEDRAW(0xBE, o7_openHtmlFile);
+	OPCODEDRAW(0xBF, o7_closeHtmlFile);
+	OPCODEDRAW(0xC0, o7_seekHtmlFile);
+	OPCODEDRAW(0xC1, o7_nextKeywordHtmlFile);
 	OPCODEDRAW(0xC3, o7_draw0xC3);
 	OPCODEDRAW(0xC4, o7_openTranlsationDB);
 	OPCODEDRAW(0xC5, o7_closeTranslationDB);
@@ -1252,30 +1252,535 @@ void Inter_v7::o7_checkAnyDatabaseRecordFound() {
 	WRITE_VAR_OFFSET(varIndex, db->hasMatchingRecord() ? 0 : 1);
 }
 
-void Inter_v7::o7_draw0xBE() {
-	_vm->_game->_script->evalString();
-	warning("STUB: o7_draw0xBE (Adibou/Sciences)");
+Inter_v7::HtmlContext::HtmlContext(Common::SeekableReadStream *stream) {
+	_stream = stream;
+	_pos = 0;
+	_posBak = -1;
+	_field_10 = false;
+	_currentMarkIndex = 1;
+	for (uint32 i = 0; i < kMaxNbrOfHtmlMarks; ++i) {
+		_marks[i]._field_0 = 3;
+		_marks[i]._field_4 = 0;
+		_marks[i]._field_8 = 0;
+		_marks[i]._pos = 0;
+		for (uint32 j = 0; j < 5; ++j) {
+			_marks[i]._field_14[j] = 0;
+		}
+
+		_marks[i]._field_28 = 0;
+	}
+}
+
+Inter_v7::HtmlContext::~HtmlContext() {
+	delete _stream;
+}
+
+Common::HashMap<Common::String, Inter_v7::HtmlContext::HtmlTagType> *Inter_v7::HtmlContext::_htmlTagsTypesMap = nullptr;
+Common::HashMap<Common::String, char> *Inter_v7::HtmlContext::_htmlEntitiesMap = nullptr;
+
+Inter_v7::HtmlContext::HtmlTagType Inter_v7::HtmlContext::getHtmlTagType(const char *tag) {
+	if (_htmlTagsTypesMap == nullptr) {
+		_htmlTagsTypesMap = new Common::HashMap<Common::String, HtmlTagType>();
+		// Initialize the map on first use
+		(*_htmlTagsTypesMap)["BODY"] = kHtmlTagType_Body;
+		(*_htmlTagsTypesMap)["FONT"] = kHtmlTagType_Font;
+		(*_htmlTagsTypesMap)["/FONT"] = kHtmlTagType_Font_Close;
+		(*_htmlTagsTypesMap)["IMG"] = kHtmlTagType_Img;
+		(*_htmlTagsTypesMap)["A"] = kHtmlTagType_A;
+		(*_htmlTagsTypesMap)["/A"] = kHtmlTagType_A_Close;
+		(*_htmlTagsTypesMap)["TITLE"] = kHtmlTagType_Title;
+		(*_htmlTagsTypesMap)["/TITLE"] = kHtmlTagType_Title_Close;
+		(*_htmlTagsTypesMap)["/HTML"] = kHtmlTagType_HTML_Close;
+		(*_htmlTagsTypesMap)["BR"] = kHtmlTagType_BR;
+		(*_htmlTagsTypesMap)["P"] = kHtmlTagType_P;
+		(*_htmlTagsTypesMap)["/P"] = kHtmlTagType_P_Close;
+		(*_htmlTagsTypesMap)["U"] = kHtmlTagType_U;
+		(*_htmlTagsTypesMap)["/U"] = kHtmlTagType_U_Close;
+		(*_htmlTagsTypesMap)["B"] = kHtmlTagType_B;
+		(*_htmlTagsTypesMap)["/B"] = kHtmlTagType_B_Close;
+		(*_htmlTagsTypesMap)["EM"] = kHtmlTagType_EM;
+		(*_htmlTagsTypesMap)["/EM"] = kHtmlTagType_EM_Close;
+		(*_htmlTagsTypesMap)["I"] = kHtmlTagType_I;
+		(*_htmlTagsTypesMap)["/I"] = kHtmlTagType_I_Close;
+		(*_htmlTagsTypesMap)["SUB"] = kHtmlTagType_SUB;
+		(*_htmlTagsTypesMap)["/SUB"] = kHtmlTagType_SUB_Close;
+		(*_htmlTagsTypesMap)["SUP"] = kHtmlTagType_SUP;
+		(*_htmlTagsTypesMap)["/SUP"] = kHtmlTagType_SUP_Close;
+	}
+
+	if (_htmlTagsTypesMap->contains(tag))
+		return (*_htmlTagsTypesMap)[tag];
+	else
+		return kHtmlTagType_None;
+}
+
+char Inter_v7::HtmlContext::getHtmlEntityLatin1Char(const char *entity) {
+	if (_htmlEntitiesMap == nullptr) {
+		_htmlEntitiesMap = new Common::HashMap<Common::String, char>();
+		// Initialize the map on first use
+		(*_htmlEntitiesMap)["quot"] = '\x22';
+		(*_htmlEntitiesMap)["nbsp"] = '\x20';
+		(*_htmlEntitiesMap)["iexcl"] = '\xA1';
+		(*_htmlEntitiesMap)["cent"] = '\xA2';
+		(*_htmlEntitiesMap)["pound"] = '\xA3';
+		(*_htmlEntitiesMap)["curren"] = '\xA4';
+		(*_htmlEntitiesMap)["yen"] = '\xA5';
+		(*_htmlEntitiesMap)["brvbar"] = '\xA6';
+		(*_htmlEntitiesMap)["sect"] = '\xA7';
+		(*_htmlEntitiesMap)["uml"] = '\xA8';
+		(*_htmlEntitiesMap)["copy"] = '\xA9';
+		(*_htmlEntitiesMap)["ordf"] = '\xAA';
+		(*_htmlEntitiesMap)["laquo"] = '\xAB';
+		(*_htmlEntitiesMap)["not"] = '\xAC';
+		(*_htmlEntitiesMap)["shy"] = '\xAD';
+		(*_htmlEntitiesMap)["reg"] = '\xAE';
+		(*_htmlEntitiesMap)["macr"] = '\xAF';
+		(*_htmlEntitiesMap)["deg"] = '\xB0';
+		(*_htmlEntitiesMap)["plusmn"] = '\xB1';
+		(*_htmlEntitiesMap)["sup2"] = '\xB2';
+		(*_htmlEntitiesMap)["sup3"] = '\xB3';
+		(*_htmlEntitiesMap)["acute"] = '\xB4';
+		(*_htmlEntitiesMap)["micro"] = '\xB5';
+		(*_htmlEntitiesMap)["para"] = '\xB6';
+		(*_htmlEntitiesMap)["middot"] = '\xB7';
+		(*_htmlEntitiesMap)["cedil"] = '\xB8';
+		(*_htmlEntitiesMap)["sup1"] = '\xB9';
+		(*_htmlEntitiesMap)["ordm"] = '\xBA';
+		(*_htmlEntitiesMap)["raquo"] = '\xBB';
+		(*_htmlEntitiesMap)["frac14"] = '\xBC';
+		(*_htmlEntitiesMap)["frac12"] = '\xBD';
+		(*_htmlEntitiesMap)["frac34"] = '\xBE';
+		(*_htmlEntitiesMap)["iquest"] = '\xBF';
+		(*_htmlEntitiesMap)["Agrave"] = '\xC0';
+		(*_htmlEntitiesMap)["Aacute"] = '\xC1';
+		(*_htmlEntitiesMap)["Acirc"] = '\xC2';
+		(*_htmlEntitiesMap)["Atilde"] = '\xC3';
+		(*_htmlEntitiesMap)["Auml"] = '\xC4';
+		(*_htmlEntitiesMap)["Aring"] = '\xC5';
+		(*_htmlEntitiesMap)["AElig"] = '\xC6';
+		(*_htmlEntitiesMap)["Ccedil"] = '\xC7';
+		(*_htmlEntitiesMap)["Egrave"] = '\xC8';
+		(*_htmlEntitiesMap)["Eacute"] = '\xC9';
+		(*_htmlEntitiesMap)["Ecirc"] = '\xCA';
+		(*_htmlEntitiesMap)["Euml"] = '\xCB';
+		(*_htmlEntitiesMap)["Igrave"] = '\xCC';
+		(*_htmlEntitiesMap)["Iacute"] = '\xCD';
+		(*_htmlEntitiesMap)["Icirc"] = '\xCE';
+		(*_htmlEntitiesMap)["Iuml"] = '\xCF';
+		(*_htmlEntitiesMap)["ETH"] = '\xD0';
+		(*_htmlEntitiesMap)["Ntilde"] = '\xD1';
+		(*_htmlEntitiesMap)["Ograve"] = '\xD2';
+		(*_htmlEntitiesMap)["Oacute"] = '\xD3';
+		(*_htmlEntitiesMap)["Ocirc"] = '\xD4';
+		(*_htmlEntitiesMap)["Otilde"] = '\xD5';
+		(*_htmlEntitiesMap)["Ouml"] = '\xD6';
+		(*_htmlEntitiesMap)["times"] = '\xD7';
+		(*_htmlEntitiesMap)["Oslash"] = '\xD8';
+		(*_htmlEntitiesMap)["Ugrave"] = '\xD9';
+		(*_htmlEntitiesMap)["Uacute"] = '\xDA';
+		(*_htmlEntitiesMap)["Ucirc"] = '\xDB';
+		(*_htmlEntitiesMap)["Uuml"] = '\xDC';
+		(*_htmlEntitiesMap)["Yacute"] = '\xDD';
+		(*_htmlEntitiesMap)["THORN"] = '\xDE';
+		(*_htmlEntitiesMap)["szlig"] = '\xDF';
+		(*_htmlEntitiesMap)["agrave"] = '\xE0';
+		(*_htmlEntitiesMap)["aacute"] = '\xE1';
+		(*_htmlEntitiesMap)["acirc"] = '\xE2';
+		(*_htmlEntitiesMap)["atilde"] = '\xE3';
+		(*_htmlEntitiesMap)["auml"] = '\xE4';
+		(*_htmlEntitiesMap)["aring"] = '\xE5';
+		(*_htmlEntitiesMap)["aelig"] = '\xE6';
+		(*_htmlEntitiesMap)["ccedil"] = '\xE7';
+		(*_htmlEntitiesMap)["egrave"] = '\xE8';
+		(*_htmlEntitiesMap)["eacute"] = '\xE9';
+		(*_htmlEntitiesMap)["ecirc"] = '\xEA';
+		(*_htmlEntitiesMap)["euml"] = '\xEB';
+		(*_htmlEntitiesMap)["igrave"] = '\xEC';
+		(*_htmlEntitiesMap)["iacute"] = '\xED';
+		(*_htmlEntitiesMap)["icirc"] = '\xEE';
+		(*_htmlEntitiesMap)["iuml"] = '\xEF';
+		(*_htmlEntitiesMap)["eth"] = '\xF0';
+		(*_htmlEntitiesMap)["ntilde"] = '\xF1';
+		(*_htmlEntitiesMap)["ograve"] = '\xF2';
+		(*_htmlEntitiesMap)["oacute"] = '\xF3';
+		(*_htmlEntitiesMap)["ocirc"] = '\xF4';
+		(*_htmlEntitiesMap)["otilde"] = '\xF5';
+		(*_htmlEntitiesMap)["ouml"] = '\xF6';
+		(*_htmlEntitiesMap)["divide"] = '\xF7';
+		(*_htmlEntitiesMap)["oslash"] = '\xF8';
+		(*_htmlEntitiesMap)["ugrave"] = '\xF9';
+		(*_htmlEntitiesMap)["uacute"] = '\xFA';
+		(*_htmlEntitiesMap)["ucirc"] = '\xFB';
+		(*_htmlEntitiesMap)["uuml"] = '\xFC';
+		(*_htmlEntitiesMap)["yacute"] = '\xFD';
+		(*_htmlEntitiesMap)["thorn"] = '\xFE';
+		(*_htmlEntitiesMap)["yuml"] = '\xFF';
+		(*_htmlEntitiesMap)["gt"] = '\x3E';
+		(*_htmlEntitiesMap)["lt"] = '\x3C';
+	}
+
+	if (*entity == '&') {
+		++entity;
+	}
+
+	if (*entity == '#') {
+		++entity;
+		int code = atoi(entity);
+		if (code >= 32 && code <= 255) {
+			return (char) code;
+		} else {
+			warning("HtmlContext::getHtmlEntityLatin1Char(): Invalid char value %s", entity);
+			return '&';
+		}
+	}
+
+
+	if (_htmlEntitiesMap->contains(entity))
+		return (*_htmlEntitiesMap)[entity];
+	else {
+		warning("HtmlContext::getHtmlEntityLatin1Char(): Unknown entity \"%s\"", entity);
+		return '&';
+	}
 }
 
-void Inter_v7::o7_draw0xBF() {
-	_vm->_game->_script->evalString();
-	warning("STUB: o7_draw0xBF (Adibou/Sciences)");
+Common::String Inter_v7::HtmlContext::substituteHtmlEntities(const char *text) {
+	Common::String result;
+	const char *charPtr = text;
+	while (*charPtr != '\0') {
+		if (*charPtr == '&') {
+			Common::String entityName = popStringPrefix(&charPtr, ';');
+			char entityChar = getHtmlEntityLatin1Char(entityName.c_str());
+			result += entityChar;
+		} else if (*charPtr == '\r') {
+			result += ' ';
+			++charPtr;
+		}
+		else {
+			result += *charPtr;
+		}
+
+		if (*charPtr != '\0')
+			++charPtr;
+	}
+
+	return result;
 }
 
-void Inter_v7::o7_draw0xC0() {
-	_vm->_game->_script->evalString();
-	_vm->_game->_script->evalString();
-	_vm->_game->_script->evalString();
-	_vm->_game->_script->readVarIndex();
-	warning("STUB: o7_draw0xC0 (Adibou/Sciences)");
+void Inter_v7::o7_openHtmlFile() {
+	Common::String file = getFile(_vm->_game->_script->evalString());
+	Common::SeekableReadStream *htmlFile = _vm->_dataIO->getFile(file);
+	if (!htmlFile) {
+		warning("o7_openHtmlFile(): No such file \"%s\"", file.c_str());
+		return;
+	}
+
+	_currentHtmlFile = file;
+	_currentHtmlContext = new HtmlContext(htmlFile);
 }
 
-void Inter_v7::o7_draw0xC1() {
-	// Read HTML line
-	_vm->_game->_script->evalString();
-	_vm->_game->_script->readVarIndex();
-	_vm->_game->_script->readVarIndex();
-	warning("STUB: o7_draw0xC1 (Adibou/Sciences)");
+void Inter_v7::o7_closeHtmlFile() {
+	Common::String file = getFile(_vm->_game->_script->evalString());
+	if (file != _currentHtmlFile) {
+		warning("o7_closeHtmlFile(): open/close file mismatch \"%s\" != \"%s\"", file.c_str(), _currentHtmlFile.c_str());
+	}
+
+	_currentHtmlFile.clear();
+	delete _currentHtmlContext;
+	_currentHtmlContext = nullptr;
+}
+
+void Inter_v7::o7_seekHtmlFile() {
+	Common::String file = getFile(_vm->_game->_script->evalString());
+	Common::String commandArg = _vm->_game->_script->evalString();
+	Common::String command = _vm->_game->_script->evalString();
+	uint16 destVar = _vm->_game->_script->readVarIndex();
+	if (_currentHtmlContext == nullptr || _currentHtmlContext->_stream == nullptr) {
+		warning("o7_seekHtmlFile(): No open file");
+		return;
+	}
+
+	if (file != _currentHtmlFile) {
+		warning("o7_seekHtmlFile(): filename mismatch \"%s\" != \"%s\"", file.c_str(), _currentHtmlFile.c_str());
+	}
+
+	if (command.hasPrefix("SAVEMARK")) {
+		if (_currentHtmlContext->_buffer[0] == '\0') {
+			_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_HTML_Close;
+			return;
+		}
+
+		if (command == "SAVEMARK_BACK") {
+			if (_currentHtmlContext->_posBak == -1) {
+				warning("o7_seekHtmlFile(): No saved position");
+			} else {
+				debugC(5, kDebugGameFlow, "o7_seekHtmlFile: SAVEMARK_BACK pos %d -> %d", _currentHtmlContext->_pos, _currentHtmlContext->_posBak);
+				_currentHtmlContext->_pos = _currentHtmlContext->_posBak;
+			}
+		}
+
+		const char*htmlBufferPtr = _currentHtmlContext->_buffer;
+		if (!commandArg.empty()) {
+			while (*htmlBufferPtr != '\0') {
+				Common::String string(htmlBufferPtr, 4 * _vm->_global->_inter_animDataSize);
+				string = HtmlContext::substituteHtmlEntities(string.c_str());
+				if (strcmp(commandArg.c_str(), string.c_str()) == 0) {
+					break;
+				}
+
+				++htmlBufferPtr;
+			}
+		}
+
+		if (strcmp(htmlBufferPtr, commandArg.c_str()) == 0) {
+			_currentHtmlContext->_pos = _currentHtmlContext->_pos - strlen(_currentHtmlContext->_buffer) + htmlBufferPtr - _currentHtmlContext->_buffer;
+		} else if (!commandArg.empty()) {
+			warning("o7_seekHtmlFile(): Cannot find string \"%s\" while using SAVEMARK", commandArg.c_str());
+		}
+
+		if (_currentHtmlContext->_currentMarkIndex < 20) {
+			HtmlMark &mark = _currentHtmlContext->_marks[_currentHtmlContext->_currentMarkIndex];
+			mark._field_0 = READ_VARO_UINT32(destVar);
+			mark._field_4 = READ_VARO_UINT32(destVar + 4);
+			mark._field_8 = READ_VARO_UINT32(destVar + 8);
+			mark._field_C = READ_VARO_UINT32(destVar + 0xC);
+			mark._pos = _currentHtmlContext->_pos;
+			for (uint32 i = 0; i < 5; ++i) {
+				mark._field_14[i] = READ_VARO_UINT32(destVar + 0x14 + i * 4);
+			}
+
+			mark._field_28 = READ_VARO_UINT32(destVar + 0x28);
+
+			++_currentHtmlContext->_currentMarkIndex;
+		} else {
+			warning("o7_seekHtmlFile(): Mark index %d out of range while using SAVEMARK (max number of marks = %d)",
+					_currentHtmlContext->_currentMarkIndex,
+					20);
+		}
+
+	} else if (command.hasPrefix("GETMARK")) {
+		int markIndex = atoi(commandArg.c_str());
+		if (markIndex < 0 || markIndex >= (int) kMaxNbrOfHtmlMarks) {
+			warning("o7_seekHtmlFile(): mark index %d out of range", markIndex);
+			return;
+		}
+
+		_currentHtmlContext->_currentMarkIndex = markIndex;
+		HtmlMark &mark = _currentHtmlContext->_marks[markIndex];
+
+		_currentHtmlContext->_pos = mark._pos;
+
+		WRITE_VAR_OFFSET(destVar, mark._field_0);
+		WRITE_VAR_OFFSET(destVar + 4, mark._field_4);
+		WRITE_VAR_OFFSET(destVar + 8, mark._field_8);
+		WRITE_VAR_OFFSET(destVar + 0xC, mark._field_C);
+		for (uint32 i = 0; i < 5; ++i) {
+			WRITE_VAR_OFFSET(destVar + 0x14 + i * 4, mark._field_14[i]);
+		}
+
+		WRITE_VAR_OFFSET(destVar + 0x28, mark._field_28);
+
+	} else if (command == "SEEK") {
+		if (commandArg == "0")
+			_currentHtmlContext->_pos = 0;
+
+		if (_currentHtmlContext->_pos >= 0)
+			_currentHtmlContext->_stream->seek(_currentHtmlContext->_pos, SEEK_SET);
+		else
+			warning("o7_seekHtmlFile(): Invalid seek position %d", _currentHtmlContext->_pos);
+
+		// NB: Seek to other offsets than 0 does not seem to be implemented in the original engine
+
+	} else {
+		warning("o7_seekHtmlFile(): Unknown command \"%s\"", command.c_str());
+	}
+}
+
+Common::String Inter_v7::HtmlContext::popStringPrefix(const char **charPtr, char sep) {
+	const char *startPtr = *charPtr;
+	if (sep == ' ') {
+		while (!Common::isSpace(**charPtr) && **charPtr != '\0')
+			++*charPtr;
+		return Common::String(startPtr, *charPtr - startPtr);
+	} else {
+		while (**charPtr != sep && **charPtr != '\0')
+			++*charPtr;
+		return Common::String(startPtr, *charPtr - startPtr);
+	}
+}
+
+void Inter_v7::HtmlContext::parseTagAttributes(const char* tagAttributes) {
+	const char* tagAttributesPtr = tagAttributes;
+	while (*tagAttributesPtr != '\0') {
+		// Skip leading spaces
+		while (Common::isSpace(*tagAttributesPtr))
+			++tagAttributesPtr;
+
+		if (*tagAttributesPtr == '\0')
+			return;
+
+		Common::String attrName = popStringPrefix(&tagAttributesPtr, '=');
+		if (attrName.empty()) {
+			warning("HtmlContext::parseTagAttributes(): Missing '=' in tag attributes");
+			return;
+		}
+
+		Common::String attrValue;
+		if (tagAttributesPtr[1] == '\"') {
+			tagAttributesPtr += 2;
+			attrValue = popStringPrefix(&tagAttributesPtr, '\"');
+		} else {
+			++tagAttributesPtr;
+			attrValue = popStringPrefix(&tagAttributesPtr, ' ');
+		}
+
+		attrValue.toUppercase();
+
+		// Handle attribute and value
+		switch (_currentTagType) {
+		case kHtmlTagType_Font:
+			if (strcmp(attrName.c_str(), "FACE") == 0)
+				_htmlVariables[0] = attrValue;
+			else if (strcmp(attrName.c_str(), "SIZE") == 0)
+				_htmlVariables[1] = attrValue;
+			else if (strcmp(attrName.c_str(), "COLOR") == 0) {
+				_htmlVariables[2] = attrValue;
+			}
+			break;
+
+		case kHtmlTagType_Img:
+			if (strcmp(attrName.c_str(), "SRC") == 0)
+				_htmlVariables[0] = attrValue;
+			else if (strcmp(attrName.c_str(), "HEIGHT") == 0)
+				_htmlVariables[1] = attrValue;
+			else if (strcmp(attrName.c_str(), "WIDTH") == 0)
+				_htmlVariables[2] = attrValue;
+			else if (strcmp(attrName.c_str(), "BORDER") == 0)
+				_htmlVariables[3] = attrValue;
+			_htmlVariables[0] = attrValue;
+
+			if (!_field_10) {
+				_posBak = _pos;
+			}
+			break;
+
+		case kHtmlTagType_A:
+			if (strcmp(attrName.c_str(), "NAME") == 0)
+				_htmlVariables[0] = attrValue;
+			else if (strcmp(attrName.c_str(), "HREF") == 0) {
+				_htmlVariables[1] = attrValue;
+				_posBak = _pos;
+				_field_10 = true;
+			}
+			break;
+
+		default:
+			break;
+		}
+
+		if (*tagAttributesPtr == '\"')
+			++tagAttributesPtr;
+	}
+}
+
+void Inter_v7::HtmlContext::cleanTextNode(int animDataSize) {
+	_buffer[animDataSize * 4 - 1] = '\0';
+	size_t len = strlen(_buffer);
+	char *htmlBufferPtr = strchr(_buffer, '<');
+	if (!htmlBufferPtr) {
+		char* charPtr2 = _buffer + len - 1;
+		while (charPtr2 > _buffer && *charPtr2 != ' ') {
+			--charPtr2;
+		}
+
+		if (charPtr2 > _buffer) {
+			if (*charPtr2 == ' ')
+				charPtr2[1] = '\0';
+			else
+				*charPtr2 = '\0';
+		}
+	} else {
+		*htmlBufferPtr = '\0';
+	}
+
+	htmlBufferPtr = strchr(_buffer, '&');
+	if (htmlBufferPtr) {
+		char *charPtr2 = strchr(_buffer, ';');
+		if (!charPtr2 || charPtr2 < htmlBufferPtr) {
+			*htmlBufferPtr = '\0';
+		}
+	}
+}
+
+void Inter_v7::o7_nextKeywordHtmlFile() {
+	Common::String file = getFile(_vm->_game->_script->evalString());
+	uint16 destVarTagType = _vm->_game->_script->readVarIndex();
+	uint16 destVar = _vm->_game->_script->readVarIndex();
+
+	_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_None;
+
+	if (_currentHtmlContext == nullptr || _currentHtmlContext->_stream == nullptr) {
+		warning("o7_seekHtmlFile(): No open file");
+		return;
+	}
+
+	if (file != _currentHtmlFile) {
+		warning("o7_nextKeywordHtmlFile(): filename mismatch \"%s\" != \"%s\"", file.c_str(), _currentHtmlFile.c_str());
+	}
+
+	if (!_currentHtmlContext->_stream->seek(_currentHtmlContext->_pos, SEEK_SET)) {
+		_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_HTML_Close;
+		return;
+	}
+
+	memset(_currentHtmlContext->_buffer, 0, sizeof(_currentHtmlContext->_buffer));
+	_currentHtmlContext->_stream->read(_currentHtmlContext->_buffer, sizeof(_currentHtmlContext->_buffer) - 1);
+
+	const char *htmlBufferPtr = _currentHtmlContext->_buffer;
+	if (*htmlBufferPtr == '<') {
+		++htmlBufferPtr;
+		Common::String tagAndAttributes = HtmlContext::popStringPrefix(&htmlBufferPtr, '>');
+		const char *tagAndAttributesPtr = tagAndAttributes.c_str();
+		if (!tagAndAttributes.empty()) {
+			Common::String tagName = HtmlContext::popStringPrefix(&tagAndAttributesPtr, ' ');
+			_currentHtmlContext->_currentTagType = HtmlContext::getHtmlTagType(tagName.c_str());
+		}
+
+		if (_currentHtmlContext->_currentTagType == HtmlContext::kHtmlTagType_None) {
+			while (*htmlBufferPtr != '>' && *htmlBufferPtr != '\0') {
+				++htmlBufferPtr;
+			}
+		} else {
+			// Handle tag attributes
+			_currentHtmlContext->parseTagAttributes(tagAndAttributesPtr);
+		}
+
+		if (*htmlBufferPtr == '>') {
+			++htmlBufferPtr;
+			// Skip CRLF
+			while (*htmlBufferPtr == '\r' || *htmlBufferPtr == '\n') {
+				++htmlBufferPtr;
+			}
+		}
+	} else {
+		_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_OutsideTag;
+		_currentHtmlContext->cleanTextNode(_vm->_global->_inter_animDataSize);
+
+		Common::String text = HtmlContext::popStringPrefix(&htmlBufferPtr, '<');
+		if (text.empty()) {
+			_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_Error;
+		} else {
+			_currentHtmlContext->_htmlVariables[0] = HtmlContext::substituteHtmlEntities(text.c_str());;
+		}
+	}
+
+	_currentHtmlContext->_pos = _currentHtmlContext->_pos + (htmlBufferPtr - _currentHtmlContext->_buffer);
+
+	// Write Html info to game variables
+	WRITE_VAR_OFFSET(destVarTagType, _currentHtmlContext->_currentTagType);
+	for (int i = 0; i < 10; ++i) {
+		WRITE_VARO_STR(destVar + i * 4 * _vm->_global->_inter_animDataSize,
+					   _currentHtmlContext->_htmlVariables[i].substr(0, 4 * _vm->_global->_inter_animDataSize).c_str());
+	}
 }
 
 void Inter_v7::o7_draw0xC3() {


Commit: 15d517ea73c9cfe04c4879aedb7b1e90f2480d0c
    https://github.com/scummvm/scummvm/commit/15d517ea73c9cfe04c4879aedb7b1e90f2480d0c
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix string buffer length in o7_printText (Adibou2)

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index a4ee248ff63..6ff6093d596 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1960,7 +1960,7 @@ void Inter_v7::o7_vmdReleaseSoundBuffer() {
 
 
 void Inter_v7::o7_printText(OpFuncParams &params) {
-	char buf[60];
+	char buf[152];
 	_vm->_draw->_destSpriteX = _vm->_game->_script->readValExpr();
 	_vm->_draw->_destSpriteY = _vm->_game->_script->readValExpr();
 


Commit: 699dba6fbfc90065e8c29b95c9b44e2be94d0713
    https://github.com/scummvm/scummvm/commit/699dba6fbfc90065e8c29b95c9b44e2be94d0713
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Do not narrow Expression value type from int32 to int16 too early

This fixes a bug in Adibou2/Sciences where Adibou kept talking without
pauses, because some game variable storing the time in milliseconds
overflowed int16.

Changed paths:
    engines/gob/inter_fascin.cpp
    engines/gob/inter_playtoons.cpp
    engines/gob/inter_v1.cpp
    engines/gob/inter_v2.cpp
    engines/gob/inter_v6.cpp
    engines/gob/scenery.cpp
    engines/gob/script.cpp
    engines/gob/script.h


diff --git a/engines/gob/inter_fascin.cpp b/engines/gob/inter_fascin.cpp
index f141df62cf1..b33c8e1e9e0 100644
--- a/engines/gob/inter_fascin.cpp
+++ b/engines/gob/inter_fascin.cpp
@@ -165,7 +165,7 @@ void Inter_Fascination::oFascin_assign(OpFuncParams &params) {
 		loopCount = 1;
 
 	for (int i = 0; i < loopCount; i++) {
-		int16 result;
+		int32 result;
 		int16 srcType = _vm->_game->_script->evalExpr(&result);
 
 		switch (destType) {
@@ -320,23 +320,24 @@ void Inter_Fascination::oFascin_setWinSize() {
 }
 
 void Inter_Fascination::oFascin_closeWin() {
-	int16 id;
+	int32 id;
 	_vm->_game->_script->evalExpr(&id);
-	_vm->_draw->activeWin(id);
-	_vm->_draw->closeWin(id);
+	_vm->_draw->activeWin((int16) id);
+	_vm->_draw->closeWin((int16) id);
 }
 
 void Inter_Fascination::oFascin_activeWin() {
-	int16 id;
+	int32 id;
 	_vm->_game->_script->evalExpr(&id);
-	_vm->_draw->activeWin(id);
+	_vm->_draw->activeWin((int16) id);
 }
 
 void Inter_Fascination::oFascin_openWin() {
-	int16 retVal, id;
+	int16 retVal;
+	int32 id;
 	_vm->_game->_script->evalExpr(&id);
 	retVal = _vm->_game->_script->readVarIndex();
-	WRITE_VAR((retVal / 4), (int32) _vm->_draw->openWin(id));
+	WRITE_VAR((retVal / 4), (int32) _vm->_draw->openWin((int16) id));
 }
 
 void Inter_Fascination::oFascin_initCursorAnim() {
@@ -347,15 +348,15 @@ void Inter_Fascination::oFascin_initCursorAnim() {
 }
 
 void Inter_Fascination::oFascin_setRenderFlags() {
-	int16 expr;
+	int32 expr;
 	_vm->_game->_script->evalExpr(&expr);
-	_vm->_draw->_renderFlags = expr;
+	_vm->_draw->_renderFlags = (int16) expr;
 }
 
 void Inter_Fascination::oFascin_setWinFlags() {
-	int16 expr;
+	int32 expr;
 	_vm->_game->_script->evalExpr(&expr);
-	_vm->_global->_curWinId = expr;
+	_vm->_global->_curWinId = (int16) expr;
 }
 
 void Inter_Fascination::oFascin_playProtracker(OpGobParams &params) {
diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp
index e7979e974dc..dec020a691e 100644
--- a/engines/gob/inter_playtoons.cpp
+++ b/engines/gob/inter_playtoons.cpp
@@ -355,7 +355,7 @@ void Inter_Playtoons::oPlaytoons_loadMultObject() {
 }
 
 void Inter_Playtoons::oPlaytoons_getObjAnimSize() {
-	int16 objIndex;
+	int32 objIndex;
 	uint16 readVar[4];
 	uint16 types[4];
 	Mult::Mult_AnimData animData;
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index 136fa47e6fb..44fcc2d58b4 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -427,12 +427,12 @@ void Inter_v1::o1_freeAnim() {
 }
 
 void Inter_v1::o1_updateAnim() {
-	int16 deltaX;
-	int16 deltaY;
-	int16 flags;
-	int16 frame;
-	int16 layer;
-	int16 animation;
+	int32 deltaX;
+	int32 deltaY;
+	int32 flags;
+	int32 frame;
+	int32 layer;
+	int32 animation;
 
 	_vm->_game->_script->evalExpr(&deltaX);
 	_vm->_game->_script->evalExpr(&deltaY);
@@ -440,8 +440,8 @@ void Inter_v1::o1_updateAnim() {
 	_vm->_game->_script->evalExpr(&layer);
 	_vm->_game->_script->evalExpr(&frame);
 	flags = _vm->_game->_script->readInt16();
-	_vm->_scenery->updateAnim(layer, frame, animation, flags,
-			deltaX, deltaY, 1);
+	_vm->_scenery->updateAnim((int16) layer, (int16) frame, (int16) animation, (int16) flags,
+							  (int16) deltaX, (int16) deltaY, 1);
 }
 
 void Inter_v1::o1_initMult() {
@@ -548,8 +548,8 @@ void Inter_v1::o1_animate() {
 }
 
 void Inter_v1::o1_loadMultObject() {
-	int16 val;
-	int16 objIndex;
+	int32 val;
+	int32 objIndex;
 	byte *multData;
 
 	_vm->_game->_script->evalExpr(&objIndex);
@@ -571,8 +571,8 @@ void Inter_v1::o1_loadMultObject() {
 }
 
 void Inter_v1::o1_getAnimLayerInfo() {
-	int16 anim;
-	int16 layer;
+	int32 anim;
+	int32 layer;
 	uint16 varDX, varDY;
 	uint16 varUnk0;
 	uint16 varFrames;
@@ -590,7 +590,7 @@ void Inter_v1::o1_getAnimLayerInfo() {
 }
 
 void Inter_v1::o1_getObjAnimSize() {
-	int16 objIndex;
+	int32 objIndex;
 
 	_vm->_game->_script->evalExpr(&objIndex);
 
@@ -633,17 +633,23 @@ void Inter_v1::o1_freeStatic() {
 }
 
 void Inter_v1::o1_renderStatic() {
-	int16 layer;
-	int16 index;
+	int32 layer;
+	int32 index;
 
 	_vm->_game->_script->evalExpr(&index);
 	_vm->_game->_script->evalExpr(&layer);
-	_vm->_scenery->renderStatic(index, layer);
+	_vm->_scenery->renderStatic((int16) index, (int16) layer);
 }
 
 void Inter_v1::o1_loadCurLayer() {
-	_vm->_game->_script->evalExpr(&_vm->_scenery->_curStatic);
-	_vm->_game->_script->evalExpr(&_vm->_scenery->_curStaticLayer);
+	int32 curStatic;
+	int32 curStaticLayer;
+	_vm->_game->_script->evalExpr(&curStatic);
+	_vm->_game->_script->evalExpr(&curStaticLayer);
+
+	_vm->_scenery->_curStatic = (int16) curStatic;
+	_vm->_scenery->_curStaticLayer = (int16) curStaticLayer;
+
 }
 
 void Inter_v1::o1_playCDTrack() {
@@ -976,7 +982,7 @@ void Inter_v1::o1_assign(OpFuncParams &params) {
 	byte destType = _vm->_game->_script->peekByte();
 	uint16 dest = _vm->_game->_script->readVarIndex();
 
-	int16 result;
+	int32 result;
 	int16 srcType = _vm->_game->_script->evalExpr(&result);
 
 	switch (destType) {
diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp
index 7e1fdb8d2dd..bb120157c2b 100644
--- a/engines/gob/inter_v2.cpp
+++ b/engines/gob/inter_v2.cpp
@@ -616,12 +616,12 @@ void Inter_v2::o2_pushVars() {
 			_varStack.pushData(*_variables, varOff, _vm->_global->_inter_animDataSize * 4);
 
 		} else {
-			int16 value;
+			int32 value;
 
 			if (_vm->_game->_script->evalExpr(&value) != 20)
 				value = 0;
 
-			_varStack.pushInt((uint32)value);
+			_varStack.pushInt(value);
 		}
 	}
 }
@@ -1050,7 +1050,7 @@ void Inter_v2::o2_assign(OpFuncParams &params) {
 		loopCount = 1;
 
 	for (int i = 0; i < loopCount; i++) {
-		int16 result;
+		int32 result;
 		int16 srcType = _vm->_game->_script->evalExpr(&result);
 
 		switch (destType) {
diff --git a/engines/gob/inter_v6.cpp b/engines/gob/inter_v6.cpp
index 39e70a05745..4e72600d57a 100644
--- a/engines/gob/inter_v6.cpp
+++ b/engines/gob/inter_v6.cpp
@@ -265,7 +265,7 @@ void Inter_v6::o6_assign(OpFuncParams &params) {
 	uint16 dest = _vm->_game->_script->readVarIndex(&size, &destType);
 
 	if (size != 0) {
-		int16 src;
+		int32 src;
 
 		_vm->_game->_script->push();
 
@@ -332,7 +332,7 @@ void Inter_v6::o6_assign(OpFuncParams &params) {
 	}
 
 	for (int i = 0; i < loopCount; i++) {
-		int16 result;
+		int32 result;
 		int16 srcType = _vm->_game->_script->evalExpr(&result);
 
 		switch (destType) {
diff --git a/engines/gob/scenery.cpp b/engines/gob/scenery.cpp
index 8866a1e48db..de039a62d3a 100644
--- a/engines/gob/scenery.cpp
+++ b/engines/gob/scenery.cpp
@@ -115,7 +115,7 @@ int16 Scenery::loadStatic(char search) {
 	byte *backsPtr;
 	int16 picsCount;
 	int16 resId;
-	int16 sceneryIndex;
+	int32 sceneryIndex;
 	Static *ptr;
 	int16 width;
 	int16 height;
@@ -226,8 +226,11 @@ int16 Scenery::loadStatic(char search) {
 void Scenery::freeStatic(int16 index) {
 	int16 spr;
 
-	if (index == -1)
-		_vm->_game->_script->evalExpr(&index);
+	if (index == -1) {
+		int32 indexFromScript = 0;
+		_vm->_game->_script->evalExpr(&indexFromScript);
+		index = (int16) indexFromScript;
+	}
 
 	if (_staticPictCount[index] == -1)
 		return;
@@ -436,7 +439,7 @@ int16 Scenery::loadAnim(char search) {
 	int16 resId;
 	int16 i;
 	int16 j;
-	int16 sceneryIndex;
+	int32 sceneryIndex;
 	int16 framesCount;
 	Animation *ptr;
 	int16 width;
@@ -558,8 +561,11 @@ int16 Scenery::loadAnim(char search) {
 void Scenery::freeAnim(int16 index) {
 	int16 spr;
 
-	if (index == -1)
-		_vm->_game->_script->evalExpr(&index);
+	if (index == -1) {
+		int32 indexFromScript = 0;
+		_vm->_game->_script->evalExpr(&indexFromScript);
+		index = (int16) indexFromScript;
+	}
 
 	if (_animPictCount[index] == 0)
 		return;
diff --git a/engines/gob/script.cpp b/engines/gob/script.cpp
index 1edb1fbcd00..8c6b214b564 100644
--- a/engines/gob/script.cpp
+++ b/engines/gob/script.cpp
@@ -290,7 +290,7 @@ void Script::skipExpr(char stopToken) {
 	_expression->skipExpr(stopToken);
 }
 
-char Script::evalExpr(int16 *pRes) {
+char Script::evalExpr(int32 *pRes) {
 	byte type;
 
 	_expression->printExpr(99);
diff --git a/engines/gob/script.h b/engines/gob/script.h
index 3814243e82e..87f37f0b0da 100644
--- a/engines/gob/script.h
+++ b/engines/gob/script.h
@@ -86,7 +86,7 @@ public:
 	void  skipExpr(char stopToken);
 
 	// Higher-level expression parsing functions
-	char  evalExpr(int16 *pRes);
+	char  evalExpr(int32 *pRes);
 	bool  evalBool();
 	int32 evalInt();
 


Commit: 7bbf39d79190b2079b6f5931841ccd418a8f6ce8
    https://github.com/scummvm/scummvm/commit/7bbf39d79190b2079b6f5931841ccd418a8f6ce8
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Trim keys before reading/writing INI values (Adibou/Sciences)

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 6ff6093d596..891f7b86eb9 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1023,6 +1023,7 @@ void Inter_v7::o7_getINIValue() {
 	Common::String def     = _vm->_game->_script->evalString();
 
 	section = oemToANSI(section);
+	key.trim();
 	key     = oemToANSI(key);
 
 	Common::String value;
@@ -1042,6 +1043,7 @@ void Inter_v7::o7_setINIValue() {
 	Common::String value   = _vm->_game->_script->evalString();
 
 	section = oemToANSI(section);
+	key.trim();
 	key     = oemToANSI(key);
 	value   = oemToANSI(value);
 


Commit: 410f9b888bcdac2d21515f2e8ce44a489eb14f69
    https://github.com/scummvm/scummvm/commit/410f9b888bcdac2d21515f2e8ce44a489eb14f69
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Do CLUT->High color conversion on a per-surface basis

Adibou2/Sciences mixes 1bpp and 2bpp surfaces, the high color map to use
for the conversion 1bpp->2bpp conversion may change depending on the surface.

Changed paths:
    engines/gob/draw.cpp
    engines/gob/draw.h
    engines/gob/draw_playtoons.cpp
    engines/gob/draw_v2.cpp
    engines/gob/surface.cpp
    engines/gob/surface.h
    engines/gob/video.cpp
    engines/gob/video.h


diff --git a/engines/gob/draw.cpp b/engines/gob/draw.cpp
index 4180f163796..50285ee872e 100644
--- a/engines/gob/draw.cpp
+++ b/engines/gob/draw.cpp
@@ -303,15 +303,6 @@ void Draw::clearPalette() {
 	}
 }
 
-uint32 Draw::getColor(uint8 index) const {
-	if (!_vm->isTrueColor())
-		return index;
-
-	return _vm->getPixelFormat().RGBToColor(_vgaPalette[index].red   << 2,
-	                                        _vgaPalette[index].green << 2,
-	                                        _vgaPalette[index].blue  << 2);
-}
-
 void Draw::dirtiedRect(int16 surface,
 		int16 left, int16 top, int16 right, int16 bottom) {
 
diff --git a/engines/gob/draw.h b/engines/gob/draw.h
index ae949860c91..c2e8e50e368 100644
--- a/engines/gob/draw.h
+++ b/engines/gob/draw.h
@@ -195,8 +195,6 @@ public:
 	void setPalette();
 	void clearPalette();
 
-	uint32 getColor(uint8 index) const;
-
 	void dirtiedRect(int16 surface, int16 left, int16 top, int16 right, int16 bottom);
 	void dirtiedRect(SurfacePtr surface, int16 left, int16 top, int16 right, int16 bottom);
 
diff --git a/engines/gob/draw_playtoons.cpp b/engines/gob/draw_playtoons.cpp
index e8e1123fcb7..f2698a06ce0 100644
--- a/engines/gob/draw_playtoons.cpp
+++ b/engines/gob/draw_playtoons.cpp
@@ -155,20 +155,20 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		case 1:
 			_spritesArray[_destSurface]->fillRect(destSpriteX,
 					_destSpriteY, _destSpriteX + 1,
-					_destSpriteY + 1, getColor(_frontColor));
+					_destSpriteY + 1, _frontColor);
 			break;
 		case 2:
 			_spritesArray[_destSurface]->fillRect(destSpriteX - 1,
 					_destSpriteY - 1, _destSpriteX + 1,
-					_destSpriteY + 1, getColor(_frontColor));
+					_destSpriteY + 1, _frontColor);
 			break;
 		case 3:
 			_spritesArray[_destSurface]->fillRect(destSpriteX - 1,
 					_destSpriteY - 1, _destSpriteX + 2,
-					_destSpriteY + 2, getColor(_frontColor));
+					_destSpriteY + 2, _frontColor);
 			break;
 		default:
-			_spritesArray[_destSurface]->putPixel(_destSpriteX, _destSpriteY, getColor(_frontColor));
+			_spritesArray[_destSurface]->putPixel(_destSpriteX, _destSpriteY, _frontColor);
 			break;
 		}
 		dirtiedRect(_destSurface, _destSpriteX - (_pattern / 2),
@@ -188,8 +188,8 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 												  _destSpriteY,
 												  _destSpriteX + _spriteRight - 1,
 												  _destSpriteY + _spriteBottom - 1,
-												  getColor(_backColor & 0xFF),
-												  getColor((_backColor >> 8) & 0xFF));
+												  _backColor & 0xFF,
+												  (_backColor >> 8) & 0xFF);
 
 			dirtiedRect(_destSurface, _destSpriteX, _destSpriteY,
 						_destSpriteX + _spriteRight - 1, _destSpriteY + _spriteBottom - 1);
@@ -198,7 +198,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		case 2: {
 			Common::Rect dirtyRect = _spritesArray[_destSurface]->fillAreaAtPoint(destSpriteX,
 																				  _destSpriteY,
-																				  getColor(_backColor));
+																				  _backColor);
 			dirtiedRect(_destSurface, dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
 			break ;
 		}
@@ -206,13 +206,13 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 			if (!(_backColor & 0xFF00) || !(_backColor & 0x0100)) {
 				_spritesArray[_destSurface]->fillRect(_destSpriteX,
 													  _destSpriteY, _destSpriteX + _spriteRight - 1,
-													  _destSpriteY + _spriteBottom - 1, getColor(_backColor));
+													  _destSpriteY + _spriteBottom - 1, _backColor);
 			} else {
 				uint8 strength = 16 - (((uint16) _backColor) >> 12);
 
 				_spritesArray[_destSurface]->shadeRect(_destSpriteX,
 													   _destSpriteY, _destSpriteX + _spriteRight - 1,
-													   _destSpriteY + _spriteBottom - 1, getColor(_backColor), strength);
+													   _destSpriteY + _spriteBottom - 1, _backColor, strength);
 			}
 
 			dirtiedRect(_destSurface, _destSpriteX, _destSpriteY,
@@ -229,18 +229,18 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		if ((_needAdjust != 2) && (_needAdjust < 10)) {
 			warning ("oPlaytoons_spriteOperation: operation DRAW_DRAWLINE, draw multiple lines");
 				_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_spriteRight, _spriteBottom, getColor(_frontColor));
+					_spriteRight, _spriteBottom, _frontColor);
 				_spritesArray[_destSurface]->drawLine(_destSpriteX + 1, _destSpriteY,
-					_spriteRight + 1, _spriteBottom, getColor(_frontColor));
+					_spriteRight + 1, _spriteBottom, _frontColor);
 				_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY + 1,
-					_spriteRight, _spriteBottom + 1, getColor(_frontColor));
+					_spriteRight, _spriteBottom + 1, _frontColor);
 				_spritesArray[_destSurface]->drawLine(_destSpriteX + 1, _destSpriteY + 1,
-					_spriteRight + 1, _spriteBottom + 1, getColor(_frontColor));
+					_spriteRight + 1, _spriteBottom + 1, _frontColor);
 		} else {
 			switch (_pattern & 0xFF) {
 			case 0:
 				_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_spriteRight, _spriteBottom, getColor(_frontColor));
+					_spriteRight, _spriteBottom, _frontColor);
 
 				break;
 			default:
@@ -251,7 +251,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 								_destSpriteY  - (_pattern / 2) + j,
 								_spriteRight  - (_pattern / 2) + i,
 								_spriteBottom - (_pattern / 2) + j,
-								getColor(_frontColor));
+								_frontColor);
 				break;
 			}
 		}
@@ -263,7 +263,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 
 	case DRAW_INVALIDATE:
 		_spritesArray[_destSurface]->drawCircle(_destSpriteX,
-												_destSpriteY, _spriteRight, getColor(_frontColor), _pattern & 0xFF);
+												_destSpriteY, _spriteRight, _frontColor, _pattern & 0xFF);
 
 		dirtiedRect(_destSurface, _destSpriteX - _spriteRight, _destSpriteY - _spriteBottom,
 				_destSpriteX + _spriteRight, _destSpriteY + _spriteBottom);
@@ -305,17 +305,17 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 					len = *dataBuf++;
 					for (int i = 0; i < len; i++, dataBuf += 2) {
 						font->drawLetter(*_spritesArray[_destSurface], READ_LE_UINT16(dataBuf),
-								_destSpriteX, _destSpriteY, getColor(_frontColor), getColor(_backColor), _transparency);
+								_destSpriteX, _destSpriteY, _frontColor, _backColor, _transparency);
 					}
 				} else {
-					font->drawString(_textToPrint, _destSpriteX, _destSpriteY, getColor(_frontColor),
-							getColor(_backColor), _transparency, *_spritesArray[_destSurface]);
+					font->drawString(_textToPrint, _destSpriteX, _destSpriteY, _frontColor,
+							_backColor, _transparency, *_spritesArray[_destSurface]);
 					_destSpriteX += len * font->getCharWidth();
 				}
 			} else {
 				for (int i = 0; i < len; i++) {
 					font->drawLetter(*_spritesArray[_destSurface], _textToPrint[i],
-							_destSpriteX, _destSpriteY, getColor(_frontColor), getColor(_backColor), _transparency);
+							_destSpriteX, _destSpriteY, _frontColor, _backColor, _transparency);
 					_destSpriteX += font->getCharWidth(_textToPrint[i]);
 				}
 			}
@@ -345,28 +345,28 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 	case DRAW_DRAWBAR:
 		if ((_needAdjust != 2) && (_needAdjust < 10)){
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _spriteBottom - 1,
-					_spriteRight, _spriteBottom, getColor(_frontColor));
+					_spriteRight, _spriteBottom, _frontColor);
 
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
-					_destSpriteX + 1, _spriteBottom, getColor(_frontColor));
+					_destSpriteX + 1, _spriteBottom, _frontColor);
 
 			_spritesArray[_destSurface]->fillRect(_spriteRight - 1, _destSpriteY,
-					_spriteRight, _spriteBottom, getColor(_frontColor));
+					_spriteRight, _spriteBottom, _frontColor);
 
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
-					_spriteRight, _destSpriteY + 1, getColor(_frontColor));
+					_spriteRight, _destSpriteY + 1, _frontColor);
 		} else {
 			_spritesArray[_destSurface]->drawLine(_destSpriteX, _spriteBottom,
-					_spriteRight, _spriteBottom, getColor(_frontColor));
+					_spriteRight, _spriteBottom, _frontColor);
 
 			_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_destSpriteX, _spriteBottom, getColor(_frontColor));
+					_destSpriteX, _spriteBottom, _frontColor);
 
 			_spritesArray[_destSurface]->drawLine(_spriteRight, _destSpriteY,
-					_spriteRight, _spriteBottom, getColor(_frontColor));
+					_spriteRight, _spriteBottom, _frontColor);
 
 			_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-					_spriteRight, _destSpriteY, getColor(_frontColor));
+					_spriteRight, _destSpriteY, _frontColor);
 		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
@@ -377,7 +377,7 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		if (_backColor != -1) {
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
 			    _spriteRight, _spriteBottom,
-			    getColor(_backColor));
+			    _backColor);
 		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
diff --git a/engines/gob/draw_v2.cpp b/engines/gob/draw_v2.cpp
index 090fad78bbc..816c6c2d856 100644
--- a/engines/gob/draw_v2.cpp
+++ b/engines/gob/draw_v2.cpp
@@ -762,13 +762,13 @@ void Draw_v2::spriteOperation(int16 operation) {
 		if (!(_backColor & 0xFF00) || !(_backColor & 0x0100)) {
 			_spritesArray[_destSurface]->fillRect(_destSpriteX,
 					_destSpriteY, _destSpriteX + _spriteRight - 1,
-					_destSpriteY + _spriteBottom - 1, getColor(_backColor));
+					_destSpriteY + _spriteBottom - 1, _backColor);
 		} else {
 			uint8 strength = 16 - (((uint16) _backColor) >> 12);
 
 			_spritesArray[_destSurface]->shadeRect(_destSpriteX,
 					_destSpriteY, _destSpriteX + _spriteRight - 1,
-					_destSpriteY + _spriteBottom - 1, getColor(_backColor), strength);
+					_destSpriteY + _spriteBottom - 1, _backColor, strength);
 		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY,
@@ -777,14 +777,14 @@ void Draw_v2::spriteOperation(int16 operation) {
 
 	case DRAW_DRAWLINE:
 		_spritesArray[_destSurface]->drawLine(_destSpriteX, _destSpriteY,
-		    _spriteRight, _spriteBottom, getColor(_frontColor));
+		    _spriteRight, _spriteBottom, _frontColor);
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
 		break;
 
 	case DRAW_INVALIDATE:
 		_spritesArray[_destSurface]->drawCircle(_destSpriteX,
-				_destSpriteY, _spriteRight, getColor(_frontColor));
+				_destSpriteY, _spriteRight, _frontColor);
 
 		dirtiedRect(_destSurface, _destSpriteX - _spriteRight, _destSpriteY - _spriteBottom,
 				_destSpriteX + _spriteRight, _destSpriteY + _spriteBottom);
@@ -833,19 +833,19 @@ void Draw_v2::spriteOperation(int16 operation) {
 					len = *dataBuf++;
 					for (int i = 0; i < len; i++, dataBuf += 2) {
 						font->drawLetter(*_spritesArray[_destSurface], READ_LE_UINT16(dataBuf),
-								_destSpriteX, _destSpriteY, getColor(_frontColor),
-								getColor(_backColor), _transparency);
+								_destSpriteX, _destSpriteY, _frontColor,
+								_backColor, _transparency);
 					}
 				} else {
-					font->drawString(_textToPrint, _destSpriteX, _destSpriteY, getColor(_frontColor),
-							getColor(_backColor), _transparency, *_spritesArray[_destSurface]);
+					font->drawString(_textToPrint, _destSpriteX, _destSpriteY, _frontColor,
+							_backColor, _transparency, *_spritesArray[_destSurface]);
 					_destSpriteX += len * font->getCharWidth();
 				}
 			} else {
 				for (int i = 0; i < len; i++) {
 					font->drawLetter(*_spritesArray[_destSurface], _textToPrint[i],
-								_destSpriteX, _destSpriteY, getColor(_frontColor),
-								getColor(_backColor), _transparency);
+								_destSpriteX, _destSpriteY, _frontColor,
+								_backColor, _transparency);
 					_destSpriteX += font->getCharWidth(_textToPrint[i]);
 				}
 			}
@@ -906,7 +906,7 @@ void Draw_v2::spriteOperation(int16 operation) {
 		if ((_backColor != 16) && (_backColor != 144)) {
 			_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
 			    _spriteRight, _spriteBottom,
-			    getColor(_backColor));
+			    _backColor);
 		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
@@ -914,7 +914,7 @@ void Draw_v2::spriteOperation(int16 operation) {
 
 	case DRAW_FILLRECTABS:
 		_spritesArray[_destSurface]->fillRect(_destSpriteX, _destSpriteY,
-		    _spriteRight, _spriteBottom, getColor(_backColor));
+		    _spriteRight, _spriteBottom, _backColor);
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY, _spriteRight, _spriteBottom);
 		break;
diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index fc878a60dcd..4e714a9a8ea 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -357,6 +357,13 @@ bool Surface::clipBlitRect(int16 &left, int16 &top, int16 &right, int16 &bottom,
 	return true;
 }
 
+uint32 Surface::getColorFromIndex(uint8 index) const {
+	if (_bpp == 1 || !_highColorMap)
+		return index;
+	else
+		return _highColorMap[index];
+}
+
 void Surface::blit(const Surface &from, int16 left, int16 top, int16 right, int16 bottom,
 		int16 x, int16 y, int32 transp, bool yAxisReflection) {
 
@@ -526,7 +533,7 @@ void Surface::blitScaled(const Surface &from, Common::Rational scale, int32 tran
 	blitScaled(from, 0, 0, from._width - 1, from._height - 1, 0, 0, scale, transp);
 }
 
-void Surface::fillRect(int16 left, int16 top, int16 right, int16 bottom, uint32 color) {
+void Surface::fillRectRaw(int16 left, int16 top, int16 right, int16 bottom, uint32 color) {
 	// Just in case those are swapped
 	if (left > right)
 		SWAP(left, right);
@@ -585,8 +592,13 @@ void Surface::fillRect(int16 left, int16 top, int16 right, int16 bottom, uint32
 	}
 }
 
+void Surface::fillRect(int16 left, int16 top, int16 right, int16 bottom, uint8 colorIndex) {
+	uint32 color = getColorFromIndex(colorIndex);
+	fillRectRaw(left, top, right, bottom, color);
+}
+
 // Fill rectangle with fillColor, except pixels with backgroundColor
-void Surface::fillArea(int16 left, int16 top, int16 right, int16 bottom, uint32 fillColor, uint32 backgroundColor) {
+void Surface::fillArea(int16 left, int16 top, int16 right, int16 bottom, uint8 fillColorIndex, uint8 backgroundColorIndex) {
 	// Just in case those are swapped
 	if (left > right)
 		SWAP(left, right);
@@ -610,6 +622,9 @@ void Surface::fillArea(int16 left, int16 top, int16 right, int16 bottom, uint32
 		// Nothing to do
 		return;
 
+	uint32 fillColor = getColorFromIndex(fillColorIndex);
+	uint32 backgroundColor = getColorFromIndex(backgroundColorIndex);
+
 	Pixel p = get(left, top);
 	while (height-- > 0) {
 		for (uint16 i = 0; i < width; i++, ++p)
@@ -620,7 +635,8 @@ void Surface::fillArea(int16 left, int16 top, int16 right, int16 bottom, uint32
 	}
 }
 
-Common::Rect Surface::fillAreaAtPoint(int16 left, int16 top, uint32 fillColor) {
+Common::Rect Surface::fillAreaAtPoint(int16 left, int16 top, uint8 fillColorIndex) {
+	uint32 fillColor = getColorFromIndex(fillColorIndex);
 	Common::Rect modifiedArea;
 	if (left < 0 || left >= _width || top < 0  || top >= _height)
 		// Nothing to do
@@ -674,14 +690,16 @@ void Surface::clear() {
 }
 
 void Surface::shadeRect(uint16 left, uint16 top, uint16 right, uint16 bottom,
-		uint32 color, uint8 strength) {
+		uint8 colorIndex, uint8 strength) {
 
 	if (_bpp == 1) {
 		// We can't properly shade in paletted mode, fill the rect instead
-		fillRect(left, top, right, bottom, color);
+		fillRect(left, top, right, bottom, colorIndex);
 		return;
 	}
 
+	uint32 color = getColorFromIndex(colorIndex);
+
 	// Just in case those are swapped
 	if (left > right)
 		SWAP(left, right);
@@ -734,18 +752,29 @@ void Surface::recolor(uint8 from, uint8 to) {
 			p.set(to);
 }
 
-void Surface::putPixel(uint16 x, uint16 y, uint32 color) {
+void Surface::putPixelRaw(uint16 x, uint16 y, uint32 color) {
 	if ((x >= _width) || (y >= _height))
 		return;
 
 	get(x, y).set(color);
 }
 
-void Surface::drawLine(uint16 x0, uint16 y0, uint16 x1, uint16 y1, uint32 color) {
+void Surface::putPixel(uint16 x, uint16 y, uint8 colorIndex) {
+	uint32 color = getColorFromIndex(colorIndex);
+	putPixelRaw(x, y, color);
+}
+
+void Surface::drawLineRaw(uint16 x0, uint16 y0, uint16 x1, uint16 y1, uint32 color) {
 	SurfacePrimitives().drawLine(x0, y0, x1, y1, color, this);
 }
 
-void Surface::drawRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uint32 color) {
+void Surface::drawLine(uint16 x0, uint16 y0, uint16 x1, uint16 y1, uint8 colorIndex) {
+	uint32 color = getColorFromIndex(colorIndex);
+	drawLineRaw(x0, y0, x1, y1, color);
+}
+
+void Surface::drawRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uint8 colorIndex) {
+	uint32 color = getColorFromIndex(colorIndex);
 	// Just in case those are swapped
 	if (left > right)
 		SWAP(left, right);
@@ -767,10 +796,10 @@ void Surface::drawRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uin
 	right  = left + width  - 1;
 	bottom = top  + height - 1;
 
-	drawLine(left , top   , left , bottom, color);
-	drawLine(right, top   , right, bottom, color);
-	drawLine(left , top   , right, top   , color);
-	drawLine(left , bottom, right, bottom, color);
+	drawLineRaw(left , top   , left , bottom, color);
+	drawLineRaw(right, top   , right, bottom, color);
+	drawLineRaw(left , top   , right, top   , color);
+	drawLineRaw(left , bottom, right, bottom, color);
 }
 
 /*
@@ -779,7 +808,8 @@ void Surface::drawRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uin
  * version found in the Wikipedia article about the
  * "Bresenham's line algorithm" instead
  */
-void Surface::drawCircle(uint16 x0, uint16 y0, uint16 radius, uint32 color, int16 pattern) {
+void Surface::drawCircle(uint16 x0, uint16 y0, uint16 radius, uint8 colorIndex, int16 pattern) {
+	uint32 color = getColorFromIndex(colorIndex);
 	int16 f = 1 - radius;
 	int16 ddFx = 0;
 	int16 ddFy = -2 * radius;
@@ -788,14 +818,14 @@ void Surface::drawCircle(uint16 x0, uint16 y0, uint16 radius, uint32 color, int1
 
 	switch (pattern) {
 	case 0xFF:
-		fillRect(x0, y0 + radius, x0, y0 - radius, color);
-		fillRect(x0 + radius, y0, x0 - radius, y0, color);
+		fillRectRaw(x0, y0 + radius, x0, y0 - radius, color);
+		fillRectRaw(x0 + radius, y0, x0 - radius, y0, color);
 		break ;
 	case 0:
-		putPixel(x0, y0 + radius, color);
-		putPixel(x0, y0 - radius, color);
-		putPixel(x0 + radius, y0, color);
-		putPixel(x0 - radius, y0, color);
+		putPixelRaw(x0, y0 + radius, color);
+		putPixelRaw(x0, y0 - radius, color);
+		putPixelRaw(x0 + radius, y0, color);
+		putPixelRaw(x0 - radius, y0, color);
 		break;
 	default:
 		break;
@@ -815,20 +845,20 @@ void Surface::drawCircle(uint16 x0, uint16 y0, uint16 radius, uint32 color, int1
 		switch (pattern) {
 		case 0xFF:
 			// Fill circle
-			fillRect(x0 - y, y0 + x, x0 + y, y0 + x, color);
-			fillRect(x0 - x, y0 + y, x0 + x, y0 + y, color);
-			fillRect(x0 - y, y0 - x, x0 + y, y0 - x, color);
-			fillRect(x0 - x, y0 - y, x0 + x, y0 - y, color);
+			fillRectRaw(x0 - y, y0 + x, x0 + y, y0 + x, color);
+			fillRectRaw(x0 - x, y0 + y, x0 + x, y0 + y, color);
+			fillRectRaw(x0 - y, y0 - x, x0 + y, y0 - x, color);
+			fillRectRaw(x0 - x, y0 - y, x0 + x, y0 - y, color);
 			break;
 		case 0:
-			putPixel(x0 + x, y0 + y, color);
-			putPixel(x0 - x, y0 + y, color);
-			putPixel(x0 + x, y0 - y, color);
-			putPixel(x0 - x, y0 - y, color);
-			putPixel(x0 + y, y0 + x, color);
-			putPixel(x0 - y, y0 + x, color);
-			putPixel(x0 + y, y0 - x, color);
-			putPixel(x0 - y, y0 - x, color);
+			putPixelRaw(x0 + x, y0 + y, color);
+			putPixelRaw(x0 - x, y0 + y, color);
+			putPixelRaw(x0 + x, y0 - y, color);
+			putPixelRaw(x0 - x, y0 - y, color);
+			putPixelRaw(x0 + y, y0 + x, color);
+			putPixelRaw(x0 - y, y0 + x, color);
+			putPixelRaw(x0 + y, y0 - x, color);
+			putPixelRaw(x0 - y, y0 - x, color);
 			break;
 		default:
 			fillRect(x0 + y - pattern, y0 + x - pattern, x0 + y, y0 + x, color);
diff --git a/engines/gob/surface.h b/engines/gob/surface.h
index 56a3815c85d..b8b69b121d6 100644
--- a/engines/gob/surface.h
+++ b/engines/gob/surface.h
@@ -130,21 +130,24 @@ public:
 	void blitScaled(const Surface &from, int16 x, int16 y, Common::Rational scale, int32 transp = -1);
 	void blitScaled(const Surface &from, Common::Rational scale, int32 transp = -1);
 
-	void fillRect(int16 left, int16 top, int16 right, int16 bottom, uint32 color);
-	void fillArea(int16 left, int16 top, int16 right, int16 bottom, uint32 fillColor, uint32 backgroundColor);
-	Common::Rect fillAreaAtPoint(int16 left, int16 top, uint32 fillColor);
+	void fillRectRaw(int16 left, int16 top, int16 right, int16 bottom, uint32 color);
+	void fillRect(int16 left, int16 top, int16 right, int16 bottom, uint8 colorIndex);
+	void fillArea(int16 left, int16 top, int16 right, int16 bottom, uint8 fillColorIndex, uint8 backgroundColorIndex);
+	Common::Rect fillAreaAtPoint(int16 left, int16 top, uint8 fillColorIndex);
 	void fill(uint32 color);
 	void clear();
 
 	void shadeRect(uint16 left, uint16 top, uint16 right, uint16 bottom,
-			uint32 color, uint8 strength);
+			uint8 colorIndex, uint8 strength);
 
 	void recolor(uint8 from, uint8 to);
 
-	void putPixel(uint16 x, uint16 y, uint32 color);
-	void drawLine(uint16 x0, uint16 y0, uint16 x1, uint16 y1, uint32 color);
-	void drawRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uint32 color);
-	void drawCircle(uint16 x0, uint16 y0, uint16 radius, uint32 color, int16 pattern = 0);
+	void putPixelRaw(uint16 x, uint16 y, uint32 color);
+	void putPixel(uint16 x, uint16 y, uint8 colorIndex);
+	void drawLineRaw(uint16 x0, uint16 y0, uint16 x1, uint16 y1, uint32 colorIndex);
+	void drawLine(uint16 x0, uint16 y0, uint16 x1, uint16 y1, uint8 colorIndex);
+	void drawRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uint8 colorIndex);
+	void drawCircle(uint16 x0, uint16 y0, uint16 radius, uint8 colorIndex, int16 pattern = 0);
 
 	void blitToScreen(uint16 left, uint16 top, uint16 right, uint16 bottom, uint16 x, uint16 y) const;
 
@@ -153,6 +156,8 @@ public:
 	bool loadImage(Common::SeekableReadStream &stream, ImageType type, int16 left, int16 top, int16 right, int16 bottom,
 				   int16 x, int16 y, int16 transp, Graphics::PixelFormat format);
 
+	uint32 getColorFromIndex(uint8 index) const;
+
 	static ImageType identifyImage(Common::SeekableReadStream &stream);
 	static bool getImageInfo(Common::SeekableReadStream &stream, uint32 &width, uint32 &height, uint32 &bpp);
 	static void computeHighColorMap(uint32 *highColorMap, const byte *palette,
diff --git a/engines/gob/video.cpp b/engines/gob/video.cpp
index e4b55eb3f5c..a8c396eafc1 100644
--- a/engines/gob/video.cpp
+++ b/engines/gob/video.cpp
@@ -97,7 +97,7 @@ bool Font::isMonospaced() const {
 	return _charWidths == nullptr;
 }
 
-void Font::drawLetter(Surface &surf, uint8 c, uint16 x, uint16 y,
+void Font::drawLetterRaw(Surface &surf, uint8 c, uint16 x, uint16 y,
 		uint32 color1, uint32 color2, bool transp) const {
 
 	uint16 data;
@@ -146,9 +146,20 @@ void Font::drawLetter(Surface &surf, uint8 c, uint16 x, uint16 y,
 	}
 }
 
-void Font::drawString(const Common::String &str, int16 x, int16 y, uint32 color1, uint32 color2,
+void Font::drawLetter(Surface &surf, uint8 c, uint16 x, uint16 y,
+					  uint8 colorIndex1, uint8 colorIndex2, bool transp) const {
+	uint32 color1 = surf.getColorFromIndex(colorIndex1);
+	uint32 color2 = surf.getColorFromIndex(colorIndex2);
+
+	drawLetterRaw(surf, c, x, y, color1, color2, transp);
+}
+
+void Font::drawString(const Common::String &str, int16 x, int16 y, uint8 colorIndex1, uint8 colorIndex2,
 					  bool transp, Surface &dest) const {
 
+	uint32 color1 = dest.getColorFromIndex(colorIndex1);
+	uint32 color2 = dest.getColorFromIndex(colorIndex2);
+
 	const char *s = str.c_str();
 
 	while (*s != '\0') {
@@ -156,7 +167,7 @@ void Font::drawString(const Common::String &str, int16 x, int16 y, uint32 color1
 		const int16 charBottom = y + getCharHeight();
 
 		if ((x >= 0) && (y >= 0) && (charRight <= dest.getWidth()) && (charBottom <= dest.getHeight()))
-			drawLetter(dest, *s, x, y, color1, color2, transp);
+			drawLetterRaw(dest, *s, x, y, color1, color2, transp);
 
 		x += getCharWidth(*s);
 		s++;
@@ -341,7 +352,7 @@ void Video::drawPacked(byte *sprBuf, int16 width, int16 height,
 					if (dest.getBPP() == 1)
 						dst.set(val);
 					else
-						dst.set(_vm->_draw->getColor(val));
+						dst.set(dest.getColorFromIndex(val));
 				}
 
 			++dst;
diff --git a/engines/gob/video.h b/engines/gob/video.h
index 87798f81aa6..39f714a1d7e 100644
--- a/engines/gob/video.h
+++ b/engines/gob/video.h
@@ -50,11 +50,13 @@ public:
 
 	bool isMonospaced() const;
 
+	void drawLetterRaw(Surface &surf, uint8 c, uint16 x, uint16 y,
+					   uint32 color1, uint32 color2, bool transp) const;
 	void drawLetter(Surface &surf, uint8 c, uint16 x, uint16 y,
-	                uint32 color1, uint32 color2, bool transp) const;
+					uint8 colorIndex1, uint8 colorIndex2, bool transp) const;
 
-	void drawString(const Common::String &str, int16 x, int16 y, uint32 color1, uint32 color2,
-	                bool transp, Surface &dest) const;
+	void drawString(const Common::String &str, int16 x, int16 y, uint8 colorIndex1, uint8 colorIndex2,
+					bool transp, Surface &dest) const;
 
 private:
 	const byte *_dataPtr;


Commit: 1bcada3ddd8d5c999c9c2b08fc8da81b2745ad10
    https://github.com/scummvm/scummvm/commit/1bcada3ddd8d5c999c9c2b08fc8da81b2745ad10
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fixes in the "replace color" mode of o7_fillRect (Adibou2/Sciences)

- Fix an error where _spriteRight was used instead of _spriteBottom
- Fixes high color surfaces handling

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 891f7b86eb9..c0ebda8e174 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -2083,9 +2083,11 @@ void Inter_v7::o7_fillRect(OpFuncParams &params) {
 			warning("o7_fillRect: pattern %d & 0x8000 != 0 stub", _vm->_draw->_pattern);
 		else {
 			// Replace a specific color in the rectangle
-			uint8 colorToReplace = (_vm->_draw->_backColor >> 8) & 0xFF;
+			uint8 colorIndexToReplace = (_vm->_draw->_backColor >> 8) & 0xFF;
+			uint32 colorToReplace = _vm->_draw->_spritesArray[_vm->_draw->_destSurface]->getColorFromIndex(colorIndexToReplace);
 			_vm->_draw->_pattern = 4;
 			_vm->_draw->_backColor = _vm->_draw->_backColor & 0xFF;
+			uint32 newColor = _vm->_draw->_spritesArray[_vm->_draw->_destSurface]->getColorFromIndex(_vm->_draw->_backColor);
 			// Additional condition on surface.field_10 in executables (video mode ?), seems to be always true for Adibou2
 			// if (_vm->_draw->_spritesArray[_vm->_draw->_destSurface].field_10  & 0x80)) {
 			SurfacePtr newSurface = _vm->_video->initSurfDesc(_vm->_draw->_spriteRight,
@@ -2096,15 +2098,15 @@ void Inter_v7::o7_fillRect(OpFuncParams &params) {
 							 _vm->_draw->_destSpriteX,
 							 _vm->_draw->_destSpriteY,
 							 _vm->_draw->_destSpriteX + _vm->_draw->_spriteRight - 1,
-							 _vm->_draw->_destSpriteY + _vm->_draw->_spriteRight - 1,
+							 _vm->_draw->_destSpriteY + _vm->_draw->_spriteBottom - 1,
 							 0,
 							 0,
 							 0);
 
 			for (int y = 0; y < _vm->_draw->_spriteBottom; y++) {
 				for (int x = 0; x < _vm->_draw->_spriteRight; x++) {
-					if ((colorToReplace & 0xFFu) == newSurface->get(x, y).get())
-						newSurface->putPixel(x, y, _vm->_draw->_backColor);
+					if (colorToReplace == newSurface->get(x, y).get())
+						newSurface->putPixelRaw(x, y, newColor);
 				}
 			}
 


Commit: dcf1ff66d5f77bee65123fd5505f6b5e679ba38f
    https://github.com/scummvm/scummvm/commit/dcf1ff66d5f77bee65123fd5505f6b5e679ba38f
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: o6_removeHotspot must ignore _shouldPush flag

Fix a crash in Adibou2/Sciences

Changed paths:
    engines/gob/inter_v6.cpp


diff --git a/engines/gob/inter_v6.cpp b/engines/gob/inter_v6.cpp
index 4e72600d57a..7b2b4a8e3e8 100644
--- a/engines/gob/inter_v6.cpp
+++ b/engines/gob/inter_v6.cpp
@@ -389,13 +389,13 @@ void Inter_v6::o6_removeHotspot(OpFuncParams &params) {
 
 	switch (id + 5) {
 	case 0:
-		_vm->_game->_hotspots->push(1);
+		_vm->_game->_hotspots->push(1, true);
 		break;
 	case 1:
 		_vm->_game->_hotspots->pop();
 		break;
 	case 2:
-		_vm->_game->_hotspots->push(2);
+		_vm->_game->_hotspots->push(2, true);
 		break;
 	case 3:
 		_vm->_game->_hotspots->removeState(stateType1);


Commit: 406de77c82920167b397d12b5ad73ff08a5dc0cb
    https://github.com/scummvm/scummvm/commit/406de77c82920167b397d12b5ad73ff08a5dc0cb
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Use proper kMouseButtons constants in videoplayer.cpp

Changed paths:
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index f1d33290f8b..5620bf7da80 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -776,13 +776,13 @@ void VideoPlayer::checkAbort(Video &video, Properties &properties) {
 			}
 
 			if (properties.breakKey == 4) {
-				if (_vm->_game->_mouseButtons == 2 || key == kKeyEscape) {
+				if (_vm->_game->_mouseButtons == kMouseButtonsRight || key == kKeyEscape) {
 					properties.canceled = true;
 					return;
 				}
 
 				if (key != kKeyNone ||
-					(_vm->_game->_mouseButtons == 1 &&
+					(_vm->_game->_mouseButtons == kMouseButtonsLeft &&
 					 _vm->_draw->_cursorIndex != -1)) {
 					_vm->_game->_hasForwardedEventsFromVideo = true;
 					_vm->_game->_forwardedKeyFromVideo = key;


Commit: 39b1a20955df0133a7c1147d58b44abab28e8988
    https://github.com/scummvm/scummvm/commit/39b1a20955df0133a7c1147d58b44abab28e8988
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: hotspot "leave" callback can point to another script (Adibou2/Sciences)

Changed paths:
    engines/gob/hotspots.cpp
    engines/gob/hotspots.h


diff --git a/engines/gob/hotspots.cpp b/engines/gob/hotspots.cpp
index d55bff3c18c..170a2a2341c 100644
--- a/engines/gob/hotspots.cpp
+++ b/engines/gob/hotspots.cpp
@@ -54,7 +54,8 @@ Hotspots::Hotspot::Hotspot(uint16 i,
 	funcEnter = enter;
 	funcLeave = leave;
 	funcPos   = pos;
-	script    = nullptr;
+	scriptFuncPos = nullptr;
+	scriptFuncLeave = nullptr;
 }
 
 void Hotspots::Hotspot::clear() {
@@ -68,7 +69,8 @@ void Hotspots::Hotspot::clear() {
 	funcEnter = 0;
 	funcLeave = 0;
 	funcPos   = 0;
-	script    = nullptr;
+	scriptFuncPos = nullptr;
+	scriptFuncLeave = nullptr;
 }
 
 Hotspots::Type Hotspots::Hotspot::getType() const {
@@ -260,7 +262,8 @@ uint16 Hotspots::add(const Hotspot &hotspot) {
 		spot.id = id;
 
 		// Remember the current script
-		spot.script = _vm->_game->_script;
+		spot.scriptFuncPos = _vm->_game->_script;
+		spot.scriptFuncLeave = _vm->_game->_script;
 
 		debugC(1, kDebugHotspots, "Adding hotspot %03d: Coord:%3d+%3d+%3d+%3d - id:%04X, key:%04X, flag:%04X - fcts:%5d, %5d, %5d",
 				i, spot.left, spot.top, spot.right, spot.bottom,
@@ -310,7 +313,7 @@ void Hotspots::recalculate(bool force) {
 		// Setting the needed script
 		Script *curScript = _vm->_game->_script;
 
-		_vm->_game->_script = spot.script;
+		_vm->_game->_script = spot.scriptFuncPos;
 		if (!_vm->_game->_script)
 			_vm->_game->_script = curScript;
 
@@ -543,8 +546,14 @@ void Hotspots::leave(uint16 index) {
 	    (spot.getState() == (kStateFilled | kStateType2)))
 		WRITE_VAR(17, spot.id & 0x0FFF);
 
-	if (spot.funcLeave != 0)
+	if (spot.funcLeave != 0) {
+		Script *curScript = _vm->_game->_script;
+		if (_vm->getGameType() == kGameTypeAdibou2) {
+			_vm->_game->_script = spot.scriptFuncLeave;
+		}
 		call(spot.funcLeave);
+		_vm->_game->_script = curScript;
+	}
 }
 
 int16 Hotspots::windowCursor(int16 &dx, int16 &dy) const {
@@ -1360,6 +1369,7 @@ void Hotspots::evaluateNew(uint16 i, uint16 *ids, InputDesc *inputs,
 				spot.enable();
 				spot.funcEnter = _vm->_game->_script->pos();
 				spot.funcLeave = _vm->_game->_script->pos();
+				spot.scriptFuncLeave = _vm->_game->_script;
 			}
 		}
 
diff --git a/engines/gob/hotspots.h b/engines/gob/hotspots.h
index 95fa201924a..6e8375c8802 100644
--- a/engines/gob/hotspots.h
+++ b/engines/gob/hotspots.h
@@ -118,7 +118,8 @@ private:
 		uint16  funcEnter;
 		uint16  funcLeave;
 		uint16  funcPos;
-		Script *script;
+		Script *scriptFuncLeave;
+		Script *scriptFuncPos;
 
 		Hotspot();
 		Hotspot(uint16 i,


Commit: 25ea01708fcf6321d111edc0e8b3c24267ae9ec4
    https://github.com/scummvm/scummvm/commit/25ea01708fcf6321d111edc0e8b3c24267ae9ec4
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add a missing condition to reset current hotspot (Adibou2/Sciences)

Changed paths:
    engines/gob/hotspots.cpp


diff --git a/engines/gob/hotspots.cpp b/engines/gob/hotspots.cpp
index 170a2a2341c..4133ba36452 100644
--- a/engines/gob/hotspots.cpp
+++ b/engines/gob/hotspots.cpp
@@ -737,6 +737,15 @@ uint16 Hotspots::check(uint8 handleMouse, int16 delay, uint16 &id, uint16 &index
 		_currentIndex = 0;
 	}
 
+	if (_vm->getGameType() == kGameTypeAdibou2 &&
+		_currentKey != 0 &&
+		(_hotspots[_currentIndex].id != _currentId ||
+		 _hotspots[_currentIndex].key != _currentKey)) {
+		_currentKey = 0;
+		_currentIndex = 0;
+		_currentId = 0;
+	}
+
 	id    = 0;
 	index = 0;
 


Commit: 10b3d63c09cf285190ccf1fd5bde2f19133c6931
    https://github.com/scummvm/scummvm/commit/10b3d63c09cf285190ccf1fd5bde2f19133c6931
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix in "replace color mode" of o7_fillRect

Transparency must be ignored when blitting from/to the helper surface.

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index c0ebda8e174..508e3dc3f52 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -2101,7 +2101,7 @@ void Inter_v7::o7_fillRect(OpFuncParams &params) {
 							 _vm->_draw->_destSpriteY + _vm->_draw->_spriteBottom - 1,
 							 0,
 							 0,
-							 0);
+							 -1);
 
 			for (int y = 0; y < _vm->_draw->_spriteBottom; y++) {
 				for (int x = 0; x < _vm->_draw->_spriteRight; x++) {
@@ -2117,7 +2117,7 @@ void Inter_v7::o7_fillRect(OpFuncParams &params) {
 																	  _vm->_draw->_spriteBottom - 1,
 																	  _vm->_draw->_destSpriteX,
 																	  _vm->_draw->_destSpriteY,
-																	  0);
+																	  -1);
 
 			_vm->_draw->dirtiedRect(_vm->_draw->_destSurface,
 									_vm->_draw->_destSpriteX,


Commit: 8f4d9d64f09158e6e9f5f8c34b1d5de978cd8728
    https://github.com/scummvm/scummvm/commit/8f4d9d64f09158e6e9f5f8c34b1d5de978cd8728
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix laggy videos when waiting in o1_keyFunc()

Changed paths:
    engines/gob/inter_v1.cpp


diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index 44fcc2d58b4..519a0bccea0 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -1411,8 +1411,18 @@ void Inter_v1::o1_keyFunc(OpFuncParams &params) {
 		if (cmd < 20) {
 			_vm->_util->delay(cmd);
 			_noBusyWait = true;
-		} else
+		} else {
+			if (_vm->getGameType() == kGameTypeAdibou2) {
+				// The engine calls updateLive() every 100ms while waiting there
+				while (cmd > 100) {
+					_vm->_vidPlayer->updateLive();
+					_vm->_util->longDelay(100);
+					cmd -= 100;
+				}
+			}
+
 			_vm->_util->longDelay(cmd);
+		}
 		break;
 	}
 }


Commit: c37e9dc0e14b18fa243db491f760aeedf7f57d54
    https://github.com/scummvm/scummvm/commit/c37e9dc0e14b18fa243db491f760aeedf7f57d54
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Workaround a script bug causing laggy videos in Adibou2/Sciences

Changed paths:
    engines/gob/inter_playtoons.cpp


diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp
index dec020a691e..37c8149c4cf 100644
--- a/engines/gob/inter_playtoons.cpp
+++ b/engines/gob/inter_playtoons.cpp
@@ -220,6 +220,17 @@ void Inter_Playtoons::oPlaytoons_freeSprite(OpFuncParams &params) {
 		return;
 	}
 
+	if (_vm->getGameType() == kGameTypeAdibou2 &&
+		_vm->_util->getFrameRate() == 5 &&
+		_vm->isCurrentTot("BS_LAB50.TOT")) {
+		// WORKAROUND: In the "puzzle shapes" game of Adibou2/Sciences, the script
+		// sets the frame rate to 5Hz for a special animation but forgets to reset it
+		// afterward, making some videos laggy (e.g. Adibou help mode).
+		// The sound was not affected in the original engine (while it is laggy as well
+		// in ScummVM), making the bug more discrete.
+		_vm->_util->setFrameRate(12);
+	}
+
 	_vm->_draw->freeSprite(index);
 }
 


Commit: 6e37e795bdec665690c4a93e62ea7dd0335b8179
    https://github.com/scummvm/scummvm/commit/6e37e795bdec665690c4a93e62ea7dd0335b8179
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix a bug in Surface::loadImage (Adibou2/Sciences)

The "sanity checks" in identifyImage() imposing a maximum height/width were
too strong. Adibou2 sometimes load images larger than the screen size,
to copy only part of them.

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 4e714a9a8ea..3d6e292f10d 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -1013,8 +1013,6 @@ ImageType Surface::identifyImage(Common::SeekableReadStream &stream) {
 	// Check width, height and bpp for sane values
 	if ((width == 0) || (height == 0) || (bpp == 0))
 		return kImageTypeNone;
-	if ((width > 800) || (height > 600))
-		return kImageTypeNone;
 	if ((bpp != 8) && (bpp != 16) && (bpp != 24) && (bpp != 32))
 		return kImageTypeNone;
 


Commit: 73de1d628530bc611b4948333db55240a5a11fa5
    https://github.com/scummvm/scummvm/commit/73de1d628530bc611b4948333db55240a5a11fa5
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Another fix related to video slots (Adibou2/Sciences)

The slot should not always be reused, even if the filename match.

Changed paths:
    engines/gob/inter_v7.cpp
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 508e3dc3f52..c48dff1b494 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -760,6 +760,7 @@ void Inter_v7::o7_playVmdOrMusic() {
 	if (props.noBlock && (props.flags & VideoPlayer::kFlagNoVideo))
 		primary = false;
 
+	props.reuseSlotWitSameFilename = true;
 	int slot = _vm->_vidPlayer->openVideo(primary, file, props);
 	if (slot < 0) {
 		WRITE_VAR(11, (uint32) -1);
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 5620bf7da80..cce054a151b 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -45,7 +45,7 @@ VideoPlayer::Properties::Properties() : type(kVideoTypeTry), sprite(Draw::kFront
 	startFrame(-1), lastFrame(-1), endFrame(-1), forceSeek(false),
 	breakKey(kShortKeyEscape), palCmd(8), palStart(0), palEnd(255), palFrame(-1),
 	noBlock(false), loop(false), fade(false), waitEndFrame(true),
-	hasSound(false), canceled(false), noWaitSound(false), slot(-1) {
+	hasSound(false), canceled(false), noWaitSound(false), slot(-1), reuseSlotWitSameFilename(false) {
 
 }
 
@@ -101,7 +101,7 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 	int slot = kPrimaryVideoSlot;
 
 	Video *video = nullptr;
-	if (_vm->getGameType() == kGameTypeAdibou2) {
+	if (properties.reuseSlotWitSameFilename) {
 		// Check whether a slot is already open for this file
 		for (int i = 0; i < kVideoSlotCount; i++) {
 			if (_videoSlots[i].isEmpty())
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index 8faae2399e8..27a2447b2f9 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -105,6 +105,7 @@ public:
 		bool canceled; ///< Was the video canceled?
 
 		int slot; ///< Explicit slot index (-1 = auto).
+		bool reuseSlotWitSameFilename;
 
 		bool noWaitSound;
 


Commit: c1a87995b4ad66b3fe95ee2ed26411a3c87dd5d9
    https://github.com/scummvm/scummvm/commit/c1a87995b4ad66b3fe95ee2ed26411a3c87dd5d9
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix a bug in o6_assign when setting multiple strings in a row

This fixes some animation glitches in Adibou2/Sciences, where some
animations did never stop because some string could not be reset.

Changed paths:
    engines/gob/inter_v6.cpp


diff --git a/engines/gob/inter_v6.cpp b/engines/gob/inter_v6.cpp
index 7b2b4a8e3e8..2c234e7a5da 100644
--- a/engines/gob/inter_v6.cpp
+++ b/engines/gob/inter_v6.cpp
@@ -358,9 +358,9 @@ void Inter_v6::o6_assign(OpFuncParams &params) {
 		case TYPE_VAR_STR:
 		case TYPE_ARRAY_STR:
 			if (srcType == TYPE_IMM_INT16)
-				WRITE_VARO_UINT8(dest, result);
+				WRITE_VARO_UINT8(dest + i * _vm->_global->_inter_animDataSize, result);
 			else
-				WRITE_VARO_STR(dest, _vm->_game->_script->getResultStr());
+				WRITE_VARO_STR(dest + i * _vm->_global->_inter_animDataSize, _vm->_game->_script->getResultStr());
 			break;
 
 		default:


Commit: f489a1eb845363cbaedad4094f3b7c5608487085
    https://github.com/scummvm/scummvm/commit/f489a1eb845363cbaedad4094f3b7c5608487085
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add week day in Inter::renewTimeInVars()

Needed to properly write the date on the diploma of Adibou2/Sciences

Changed paths:
    engines/gob/inter.cpp


diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp
index 48fa961673f..5d00b4ec444 100644
--- a/engines/gob/inter.cpp
+++ b/engines/gob/inter.cpp
@@ -155,7 +155,7 @@ void Inter::renewTimeInVars() {
 
 	WRITE_VAR(5, 1900 + t.tm_year);
 	WRITE_VAR(6, t.tm_mon + 1);
-	WRITE_VAR(7, 0);
+	WRITE_VAR(7, (t.tm_wday + 6) % 7 + 1); // Monday = 1, ... Sunday = 7
 	WRITE_VAR(8, t.tm_mday);
 	WRITE_VAR(9, t.tm_hour);
 	WRITE_VAR(10, t.tm_min);


Commit: a85a6bfbfaf0e584a9772a55503915762d9e2465
    https://github.com/scummvm/scummvm/commit/a85a6bfbfaf0e584a9772a55503915762d9e2465
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
IMAGE: Move BRC decoder to the Gob engine

Changed paths:
  A engines/gob/image/brc.cpp
  A engines/gob/image/brc.h
  R image/brc.cpp
  R image/brc.h
    engines/gob/module.mk
    image/module.mk


diff --git a/image/brc.cpp b/engines/gob/image/brc.cpp
similarity index 99%
rename from image/brc.cpp
rename to engines/gob/image/brc.cpp
index 753c638a4cd..e451d57f6b5 100644
--- a/image/brc.cpp
+++ b/engines/gob/image/brc.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-#include "image/brc.h"
+#include "brc.h"
 
 #include "common/stream.h"
 #include "common/textconsole.h"
diff --git a/image/brc.h b/engines/gob/image/brc.h
similarity index 98%
rename from image/brc.h
rename to engines/gob/image/brc.h
index 837d57155b5..902c94d0a6c 100644
--- a/image/brc.h
+++ b/engines/gob/image/brc.h
@@ -47,8 +47,6 @@ namespace Image {
  *
  * @brief Decoder for BRC images.
  *
- * Used in engines:
- *  - Gob
  * @{
  */
 
diff --git a/engines/gob/module.mk b/engines/gob/module.mk
index 74ee8df0527..bd7cf7a71b2 100644
--- a/engines/gob/module.mk
+++ b/engines/gob/module.mk
@@ -81,6 +81,7 @@ MODULE_OBJS := \
 	demos/demoplayer.o \
 	demos/scnplayer.o \
 	demos/batplayer.o \
+	image/brc.o \
 	pregob/pregob.o \
 	pregob/txtfile.o \
 	pregob/gctfile.o \
diff --git a/image/module.mk b/image/module.mk
index cd575df4fdb..67d0c733c13 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -3,7 +3,6 @@ MODULE := image
 MODULE_OBJS := \
 	ani.o \
 	bmp.o \
-	brc.o \
 	cel_3do.o \
 	cicn.o \
 	icocur.o \


Commit: f3c1d3809a3beb96fca30878a421f67dfdd2edd0
    https://github.com/scummvm/scummvm/commit/f3c1d3809a3beb96fca30878a421f67dfdd2edd0
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
IMAGE: Mention Gob engine in the usage list of TGA decoder

Changed paths:
    image/tga.h


diff --git a/image/tga.h b/image/tga.h
index 8f19d0f22d3..1c41174bafe 100644
--- a/image/tga.h
+++ b/image/tga.h
@@ -25,7 +25,8 @@
 
 /*
  * TGA decoder used in engines:
- *	- titanic
+ *  - gob
+ *  - titanic
  *  - wintermute
  *  - zvision
  */


Commit: 032bb05ff93edf6b4453e9e8b8ca94118adaf6e5
    https://github.com/scummvm/scummvm/commit/032bb05ff93edf6b4453e9e8b8ca94118adaf6e5
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Misc code style and formatting fixes (Adibou2/Sciences)

Changed paths:
    engines/gob/databases.cpp
    engines/gob/dbase.cpp
    engines/gob/hotspots.cpp
    engines/gob/iniconfig.h
    engines/gob/inter_fascin.cpp
    engines/gob/inter_playtoons.cpp
    engines/gob/inter_v1.cpp
    engines/gob/inter_v7.cpp
    engines/gob/surface.h
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/databases.cpp b/engines/gob/databases.cpp
index 36c351d2465..ffea1380611 100644
--- a/engines/gob/databases.cpp
+++ b/engines/gob/databases.cpp
@@ -65,10 +65,10 @@ void TranslationDatabases::setLanguage(Common::Language language) {
 }
 
 bool TranslationDatabases::open(const Common::String &id, const Common::Path &file) {
-		if (_databases.contains(id)) {
-			warning("TranslationDatabases::open(): A database with the ID \"%s\" already exists", id.c_str());
-			return false;
-		}
+	if (_databases.contains(id)) {
+		warning("TranslationDatabases::open(): A database with the ID \"%s\" already exists", id.c_str());
+		return false;
+	}
 
 	Common::File dbFile;
 	if (!dbFile.open(file)) {
@@ -176,7 +176,7 @@ bool TranslationDatabases::buildMap(const dBase &db, Common::StringMap &map) con
 }
 
 Database::~Database() {
-	for (Common::HashMap<Common::String, dBase*>::Node &node : _tables)
+	for (auto &node : _tables)
 		delete node._value;
 }
 
diff --git a/engines/gob/dbase.cpp b/engines/gob/dbase.cpp
index 385ec06a16d..a7bf800a1dc 100644
--- a/engines/gob/dbase.cpp
+++ b/engines/gob/dbase.cpp
@@ -157,9 +157,9 @@ bool dBase::load(Common::SeekableReadStream &stream) {
 
 	_version = stream.readByte();
 	if (_version == 0x03 || _version == 0x83) {
-		_versionMajor= 3;
+		_versionMajor = 3;
 	} else if (_version == 0x04 || _version == 0x7B || _version == 0x8B) {
-		_versionMajor= 4;
+		_versionMajor = 4;
 	} else {
 		warning("dBase::load() called on unsupported dBase version %d", _version);
 		return false;
@@ -407,7 +407,7 @@ void dBase::findNextMatchingRecord() {
 		return;
 	}
 
-	for (; _currentRecordIndex < (int) _records.size(); ++_currentRecordIndex) {
+	for (; _currentRecordIndex < (int)_records.size(); ++_currentRecordIndex) {
 		const Record &record = _records[_currentRecordIndex];
 
 		bool match = true;
diff --git a/engines/gob/hotspots.cpp b/engines/gob/hotspots.cpp
index 4133ba36452..5d9547a558a 100644
--- a/engines/gob/hotspots.cpp
+++ b/engines/gob/hotspots.cpp
@@ -738,9 +738,9 @@ uint16 Hotspots::check(uint8 handleMouse, int16 delay, uint16 &id, uint16 &index
 	}
 
 	if (_vm->getGameType() == kGameTypeAdibou2 &&
-		_currentKey != 0 &&
-		(_hotspots[_currentIndex].id != _currentId ||
-		 _hotspots[_currentIndex].key != _currentKey)) {
+			_currentKey != 0 &&
+			(_hotspots[_currentIndex].id != _currentId ||
+			_hotspots[_currentIndex].key != _currentKey)) {
 		_currentKey = 0;
 		_currentIndex = 0;
 		_currentId = 0;
diff --git a/engines/gob/iniconfig.h b/engines/gob/iniconfig.h
index 5120ae65ce4..790ea08f442 100644
--- a/engines/gob/iniconfig.h
+++ b/engines/gob/iniconfig.h
@@ -32,7 +32,7 @@
 #include "common/hashmap.h"
 #include "common/str.h"
 
-#include "gob.h"
+#include "gob/gob.h"
 
 namespace Gob {
 
diff --git a/engines/gob/inter_fascin.cpp b/engines/gob/inter_fascin.cpp
index b33c8e1e9e0..d923b777caa 100644
--- a/engines/gob/inter_fascin.cpp
+++ b/engines/gob/inter_fascin.cpp
@@ -350,7 +350,7 @@ void Inter_Fascination::oFascin_initCursorAnim() {
 void Inter_Fascination::oFascin_setRenderFlags() {
 	int32 expr;
 	_vm->_game->_script->evalExpr(&expr);
-	_vm->_draw->_renderFlags = (int16) expr;
+	_vm->_draw->_renderFlags = (int16)expr;
 }
 
 void Inter_Fascination::oFascin_setWinFlags() {
diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp
index 37c8149c4cf..387a01001ac 100644
--- a/engines/gob/inter_playtoons.cpp
+++ b/engines/gob/inter_playtoons.cpp
@@ -221,8 +221,8 @@ void Inter_Playtoons::oPlaytoons_freeSprite(OpFuncParams &params) {
 	}
 
 	if (_vm->getGameType() == kGameTypeAdibou2 &&
-		_vm->_util->getFrameRate() == 5 &&
-		_vm->isCurrentTot("BS_LAB50.TOT")) {
+			_vm->_util->getFrameRate() == 5 &&
+			_vm->isCurrentTot("BS_LAB50.TOT")) {
 		// WORKAROUND: In the "puzzle shapes" game of Adibou2/Sciences, the script
 		// sets the frame rate to 5Hz for a special animation but forgets to reset it
 		// afterward, making some videos laggy (e.g. Adibou help mode).
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index 519a0bccea0..ec5f96b3da5 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -25,7 +25,6 @@
  *
  */
 
-#include "common/debug-channels.h"
 #include "common/str.h"
 
 #include "gob/gob.h"
@@ -440,8 +439,8 @@ void Inter_v1::o1_updateAnim() {
 	_vm->_game->_script->evalExpr(&layer);
 	_vm->_game->_script->evalExpr(&frame);
 	flags = _vm->_game->_script->readInt16();
-	_vm->_scenery->updateAnim((int16) layer, (int16) frame, (int16) animation, (int16) flags,
-							  (int16) deltaX, (int16) deltaY, 1);
+	_vm->_scenery->updateAnim((int16)layer, (int16)frame, (int16)animation, (int16)flags,
+							  (int16)deltaX, (int16)deltaY, 1);
 }
 
 void Inter_v1::o1_initMult() {
@@ -638,7 +637,7 @@ void Inter_v1::o1_renderStatic() {
 
 	_vm->_game->_script->evalExpr(&index);
 	_vm->_game->_script->evalExpr(&layer);
-	_vm->_scenery->renderStatic((int16) index, (int16) layer);
+	_vm->_scenery->renderStatic((int16)index, (int16)layer);
 }
 
 void Inter_v1::o1_loadCurLayer() {
@@ -647,8 +646,8 @@ void Inter_v1::o1_loadCurLayer() {
 	_vm->_game->_script->evalExpr(&curStatic);
 	_vm->_game->_script->evalExpr(&curStaticLayer);
 
-	_vm->_scenery->_curStatic = (int16) curStatic;
-	_vm->_scenery->_curStaticLayer = (int16) curStaticLayer;
+	_vm->_scenery->_curStatic = (int16)curStatic;
+	_vm->_scenery->_curStaticLayer = (int16)curStaticLayer;
 
 }
 
@@ -696,7 +695,7 @@ void Inter_v1::o1_freeFontToSprite() {
 void Inter_v1::o1_callSub(OpFuncParams &params) {
 	uint16 offset = _vm->_game->_script->readUint16();
 
-	if (gDebugLevel >= 5 && DebugMan.isDebugChannelEnabled(kDebugGameFlow)) {
+	if (debugChannelSet(5, kDebugGameFlow)) {
 		Common::String functionName = _vm->_game->getFunctionName(_vm->_game->_curTotFile, offset);
 		Common::String functionNameInLog;
 
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index c48dff1b494..df502c3fd11 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -820,7 +820,7 @@ void Inter_v7::o7_openItk() {
 }
 
 void Inter_v7::o7_findFile() {
-	const char* filePatternStr = _vm->_game->_script->evalString();
+	const char *filePatternStr = _vm->_game->_script->evalString();
 	bool isPattern = Common::String(filePatternStr).contains('*') || Common::String(filePatternStr).contains('?');
 
 	Common::Path filePattern(getFile(filePatternStr, !isPattern), '\\');
@@ -1612,7 +1612,7 @@ Common::String Inter_v7::HtmlContext::popStringPrefix(const char **charPtr, char
 }
 
 void Inter_v7::HtmlContext::parseTagAttributes(const char* tagAttributes) {
-	const char* tagAttributesPtr = tagAttributes;
+	const char *tagAttributesPtr = tagAttributes;
 	while (*tagAttributesPtr != '\0') {
 		// Skip leading spaces
 		while (Common::isSpace(*tagAttributesPtr))
diff --git a/engines/gob/surface.h b/engines/gob/surface.h
index b8b69b121d6..74578c62103 100644
--- a/engines/gob/surface.h
+++ b/engines/gob/surface.h
@@ -33,13 +33,16 @@
 #include "common/rational.h"
 #include "common/rect.h"
 
-#include "image/image_decoder.h"
 #include "graphics/pixelformat.h"
 
 namespace Common {
 class SeekableReadStream;
 }
 
+namespace Image {
+class ImageDecoder;
+}
+
 namespace Gob {
 
 enum ImageType {
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index cce054a151b..bb4504ac5c2 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -900,7 +900,7 @@ uint32 VideoPlayer::getVideoBufferSize(int slot) const {
 bool VideoPlayer::hasVideo(int slot) const {
 	const Video *video = getVideoBySlot(slot);
 	if (!video)
-		return 0;
+		return false;
 
 	return video->decoder->hasVideo();
 }


Commit: e90e8b543cf6c291e05419053266fccc5735baff
    https://github.com/scummvm/scummvm/commit/e90e8b543cf6c291e05419053266fccc5735baff
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Revert allowing TOT files without resources, except for Adibou2

Changed paths:
    engines/gob/resources.cpp


diff --git a/engines/gob/resources.cpp b/engines/gob/resources.cpp
index 4c38c01c4d3..89a73e0c980 100644
--- a/engines/gob/resources.cpp
+++ b/engines/gob/resources.cpp
@@ -176,8 +176,12 @@ bool Resources::load(const Common::String &fileName) {
 		_extResourceTable = nullptr;
 	}
 
-	if (!hasTOTRes && !hasEXTRes)
-		return true;
+	if (!hasTOTRes && !hasEXTRes) {
+		if (_vm->getGameType() == kGameTypeAdibou2)
+			return true; // Some "library" TOT files used in Adibou2 have no embed resources, nor external ones.
+		else
+			return false;
+	};
 
 	if (!loadTOTTextTable(_fileBase)) {
 		unload();


Commit: c36a77da4f42b54f9c66bbdb103029453aa5f7ed
    https://github.com/scummvm/scummvm/commit/c36a77da4f42b54f9c66bbdb103029453aa5f7ed
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: BRC decoder must always use RGB555 format, not the engine one

Changed paths:
    engines/gob/image/brc.cpp
    engines/gob/image/brc.h
    engines/gob/surface.cpp


diff --git a/engines/gob/image/brc.cpp b/engines/gob/image/brc.cpp
index e451d57f6b5..0387deed7e9 100644
--- a/engines/gob/image/brc.cpp
+++ b/engines/gob/image/brc.cpp
@@ -26,7 +26,7 @@
 
 namespace Image {
 
-BRCDecoder::BRCDecoder(Graphics::PixelFormat format): _palette(0), _format(format) {
+BRCDecoder::BRCDecoder(): _palette(0), _format(Graphics::createPixelFormat<555>()) {
 }
 
 
diff --git a/engines/gob/image/brc.h b/engines/gob/image/brc.h
index 902c94d0a6c..2cf311f2454 100644
--- a/engines/gob/image/brc.h
+++ b/engines/gob/image/brc.h
@@ -52,7 +52,7 @@ namespace Image {
 
 class BRCDecoder : public ImageDecoder {
 public:
-	BRCDecoder(Graphics::PixelFormat format);
+	BRCDecoder();
 	~BRCDecoder() override;
 
 	// ImageDecoder API
diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 3d6e292f10d..bf0f1791087 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -1070,7 +1070,7 @@ bool Surface::loadIFF(Common::SeekableReadStream &stream, int16 left, int16 top,
 }
 
 bool Surface::loadBRC(Common::SeekableReadStream &stream, int16 left, int16 top, int16 right, int16 bottom, int16 x, int16 y, int16 transp, Graphics::PixelFormat format) {
-	Image::BRCDecoder decoder(format);
+	Image::BRCDecoder decoder;
 	return loadImage(decoder, stream, left, top, right, bottom, x, y, transp, format);
 }
 


Commit: 1d9250112962aea353c2ab4a64c90be4d865e6bc
    https://github.com/scummvm/scummvm/commit/1d9250112962aea353c2ab4a64c90be4d865e6bc
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: getImageInfo() can now handle BRC images

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index bf0f1791087..1103d5403ff 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -923,7 +923,7 @@ bool Surface::getImageInfo(Common::SeekableReadStream &stream, uint32 &width, ui
 		decoder.reset(new Image::IFFDecoder());
 		break;
 	case kImageTypeBRC:
-		warning("Surface::getImageInfo(): BRC images are not supported");
+		decoder.reset(new Image::BRCDecoder());
 		break;
 	case kImageTypeBMP:
 		decoder.reset(new Image::BitmapDecoder());


Commit: 38efd60279ad8a31ffb3971d83daa3475ffd692b
    https://github.com/scummvm/scummvm/commit/38efd60279ad8a31ffb3971d83daa3475ffd692b
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Split HTML parser classes into a new file (Adibou2/Sciences)

Changed paths:
  A engines/gob/html_parser.cpp
  A engines/gob/html_parser.h
    engines/gob/inter.h
    engines/gob/inter_v7.cpp
    engines/gob/module.mk


diff --git a/engines/gob/html_parser.cpp b/engines/gob/html_parser.cpp
new file mode 100644
index 00000000000..2543367c045
--- /dev/null
+++ b/engines/gob/html_parser.cpp
@@ -0,0 +1,532 @@
+/* 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/>.
+ *
+ *
+ * This file is dual-licensed.
+ * In addition to the GPLv3 license mentioned above, this code is also
+ * licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
+ * full text of the license.
+ *
+ */
+
+#include "common/str.h"
+
+#include "gob/html_parser.h"
+#include "gob/global.h"
+#include "gob/draw.h"
+#include "gob/gob.h"
+#include "gob/script.h"
+#include "gob/inter.h"
+
+namespace Gob {
+
+HtmlContext::HtmlContext(Common::SeekableReadStream *stream, GobEngine *vm) : _stream(stream), _vm(vm) {
+	_pos = 0;
+	_posBak = -1;
+	_field_10 = false;
+	_currentMarkIndex = 1;
+	for (uint32 i = 0; i < kMaxNbrOfHtmlMarks; ++i) {
+		_marks[i]._field_0 = 3;
+		_marks[i]._field_4 = 0;
+		_marks[i]._field_8 = 0;
+		_marks[i]._pos = 0;
+		for (uint32 j = 0; j < 5; ++j) {
+			_marks[i]._field_14[j] = 0;
+		}
+
+		_marks[i]._field_28 = 0;
+	}
+}
+
+HtmlContext::~HtmlContext() {
+	delete _stream;
+}
+
+Common::HashMap<Common::String, HtmlContext::HtmlTagType> *HtmlContext::_htmlTagsTypesMap = nullptr;
+Common::HashMap<Common::String, char> *HtmlContext::_htmlEntitiesMap = nullptr;
+
+HtmlContext::HtmlTagType HtmlContext::getHtmlTagType(const char *tag) {
+	if (_htmlTagsTypesMap == nullptr) {
+		_htmlTagsTypesMap = new Common::HashMap<Common::String, HtmlTagType>();
+		// Initialize the map on first use
+		(*_htmlTagsTypesMap)["BODY"] = kHtmlTagType_Body;
+		(*_htmlTagsTypesMap)["FONT"] = kHtmlTagType_Font;
+		(*_htmlTagsTypesMap)["/FONT"] = kHtmlTagType_Font_Close;
+		(*_htmlTagsTypesMap)["IMG"] = kHtmlTagType_Img;
+		(*_htmlTagsTypesMap)["A"] = kHtmlTagType_A;
+		(*_htmlTagsTypesMap)["/A"] = kHtmlTagType_A_Close;
+		(*_htmlTagsTypesMap)["TITLE"] = kHtmlTagType_Title;
+		(*_htmlTagsTypesMap)["/TITLE"] = kHtmlTagType_Title_Close;
+		(*_htmlTagsTypesMap)["/HTML"] = kHtmlTagType_HTML_Close;
+		(*_htmlTagsTypesMap)["BR"] = kHtmlTagType_BR;
+		(*_htmlTagsTypesMap)["P"] = kHtmlTagType_P;
+		(*_htmlTagsTypesMap)["/P"] = kHtmlTagType_P_Close;
+		(*_htmlTagsTypesMap)["U"] = kHtmlTagType_U;
+		(*_htmlTagsTypesMap)["/U"] = kHtmlTagType_U_Close;
+		(*_htmlTagsTypesMap)["B"] = kHtmlTagType_B;
+		(*_htmlTagsTypesMap)["/B"] = kHtmlTagType_B_Close;
+		(*_htmlTagsTypesMap)["EM"] = kHtmlTagType_EM;
+		(*_htmlTagsTypesMap)["/EM"] = kHtmlTagType_EM_Close;
+		(*_htmlTagsTypesMap)["I"] = kHtmlTagType_I;
+		(*_htmlTagsTypesMap)["/I"] = kHtmlTagType_I_Close;
+		(*_htmlTagsTypesMap)["SUB"] = kHtmlTagType_SUB;
+		(*_htmlTagsTypesMap)["/SUB"] = kHtmlTagType_SUB_Close;
+		(*_htmlTagsTypesMap)["SUP"] = kHtmlTagType_SUP;
+		(*_htmlTagsTypesMap)["/SUP"] = kHtmlTagType_SUP_Close;
+	}
+
+	if (_htmlTagsTypesMap->contains(tag))
+		return (*_htmlTagsTypesMap)[tag];
+	else
+		return kHtmlTagType_None;
+}
+
+char HtmlContext::getHtmlEntityLatin1Char(const char *entity) {
+	if (_htmlEntitiesMap == nullptr) {
+		_htmlEntitiesMap = new Common::HashMap<Common::String, char>();
+		// Initialize the map on first use
+		(*_htmlEntitiesMap)["quot"] = '\x22';
+		(*_htmlEntitiesMap)["nbsp"] = '\x20';
+		(*_htmlEntitiesMap)["iexcl"] = '\xA1';
+		(*_htmlEntitiesMap)["cent"] = '\xA2';
+		(*_htmlEntitiesMap)["pound"] = '\xA3';
+		(*_htmlEntitiesMap)["curren"] = '\xA4';
+		(*_htmlEntitiesMap)["yen"] = '\xA5';
+		(*_htmlEntitiesMap)["brvbar"] = '\xA6';
+		(*_htmlEntitiesMap)["sect"] = '\xA7';
+		(*_htmlEntitiesMap)["uml"] = '\xA8';
+		(*_htmlEntitiesMap)["copy"] = '\xA9';
+		(*_htmlEntitiesMap)["ordf"] = '\xAA';
+		(*_htmlEntitiesMap)["laquo"] = '\xAB';
+		(*_htmlEntitiesMap)["not"] = '\xAC';
+		(*_htmlEntitiesMap)["shy"] = '\xAD';
+		(*_htmlEntitiesMap)["reg"] = '\xAE';
+		(*_htmlEntitiesMap)["macr"] = '\xAF';
+		(*_htmlEntitiesMap)["deg"] = '\xB0';
+		(*_htmlEntitiesMap)["plusmn"] = '\xB1';
+		(*_htmlEntitiesMap)["sup2"] = '\xB2';
+		(*_htmlEntitiesMap)["sup3"] = '\xB3';
+		(*_htmlEntitiesMap)["acute"] = '\xB4';
+		(*_htmlEntitiesMap)["micro"] = '\xB5';
+		(*_htmlEntitiesMap)["para"] = '\xB6';
+		(*_htmlEntitiesMap)["middot"] = '\xB7';
+		(*_htmlEntitiesMap)["cedil"] = '\xB8';
+		(*_htmlEntitiesMap)["sup1"] = '\xB9';
+		(*_htmlEntitiesMap)["ordm"] = '\xBA';
+		(*_htmlEntitiesMap)["raquo"] = '\xBB';
+		(*_htmlEntitiesMap)["frac14"] = '\xBC';
+		(*_htmlEntitiesMap)["frac12"] = '\xBD';
+		(*_htmlEntitiesMap)["frac34"] = '\xBE';
+		(*_htmlEntitiesMap)["iquest"] = '\xBF';
+		(*_htmlEntitiesMap)["Agrave"] = '\xC0';
+		(*_htmlEntitiesMap)["Aacute"] = '\xC1';
+		(*_htmlEntitiesMap)["Acirc"] = '\xC2';
+		(*_htmlEntitiesMap)["Atilde"] = '\xC3';
+		(*_htmlEntitiesMap)["Auml"] = '\xC4';
+		(*_htmlEntitiesMap)["Aring"] = '\xC5';
+		(*_htmlEntitiesMap)["AElig"] = '\xC6';
+		(*_htmlEntitiesMap)["Ccedil"] = '\xC7';
+		(*_htmlEntitiesMap)["Egrave"] = '\xC8';
+		(*_htmlEntitiesMap)["Eacute"] = '\xC9';
+		(*_htmlEntitiesMap)["Ecirc"] = '\xCA';
+		(*_htmlEntitiesMap)["Euml"] = '\xCB';
+		(*_htmlEntitiesMap)["Igrave"] = '\xCC';
+		(*_htmlEntitiesMap)["Iacute"] = '\xCD';
+		(*_htmlEntitiesMap)["Icirc"] = '\xCE';
+		(*_htmlEntitiesMap)["Iuml"] = '\xCF';
+		(*_htmlEntitiesMap)["ETH"] = '\xD0';
+		(*_htmlEntitiesMap)["Ntilde"] = '\xD1';
+		(*_htmlEntitiesMap)["Ograve"] = '\xD2';
+		(*_htmlEntitiesMap)["Oacute"] = '\xD3';
+		(*_htmlEntitiesMap)["Ocirc"] = '\xD4';
+		(*_htmlEntitiesMap)["Otilde"] = '\xD5';
+		(*_htmlEntitiesMap)["Ouml"] = '\xD6';
+		(*_htmlEntitiesMap)["times"] = '\xD7';
+		(*_htmlEntitiesMap)["Oslash"] = '\xD8';
+		(*_htmlEntitiesMap)["Ugrave"] = '\xD9';
+		(*_htmlEntitiesMap)["Uacute"] = '\xDA';
+		(*_htmlEntitiesMap)["Ucirc"] = '\xDB';
+		(*_htmlEntitiesMap)["Uuml"] = '\xDC';
+		(*_htmlEntitiesMap)["Yacute"] = '\xDD';
+		(*_htmlEntitiesMap)["THORN"] = '\xDE';
+		(*_htmlEntitiesMap)["szlig"] = '\xDF';
+		(*_htmlEntitiesMap)["agrave"] = '\xE0';
+		(*_htmlEntitiesMap)["aacute"] = '\xE1';
+		(*_htmlEntitiesMap)["acirc"] = '\xE2';
+		(*_htmlEntitiesMap)["atilde"] = '\xE3';
+		(*_htmlEntitiesMap)["auml"] = '\xE4';
+		(*_htmlEntitiesMap)["aring"] = '\xE5';
+		(*_htmlEntitiesMap)["aelig"] = '\xE6';
+		(*_htmlEntitiesMap)["ccedil"] = '\xE7';
+		(*_htmlEntitiesMap)["egrave"] = '\xE8';
+		(*_htmlEntitiesMap)["eacute"] = '\xE9';
+		(*_htmlEntitiesMap)["ecirc"] = '\xEA';
+		(*_htmlEntitiesMap)["euml"] = '\xEB';
+		(*_htmlEntitiesMap)["igrave"] = '\xEC';
+		(*_htmlEntitiesMap)["iacute"] = '\xED';
+		(*_htmlEntitiesMap)["icirc"] = '\xEE';
+		(*_htmlEntitiesMap)["iuml"] = '\xEF';
+		(*_htmlEntitiesMap)["eth"] = '\xF0';
+		(*_htmlEntitiesMap)["ntilde"] = '\xF1';
+		(*_htmlEntitiesMap)["ograve"] = '\xF2';
+		(*_htmlEntitiesMap)["oacute"] = '\xF3';
+		(*_htmlEntitiesMap)["ocirc"] = '\xF4';
+		(*_htmlEntitiesMap)["otilde"] = '\xF5';
+		(*_htmlEntitiesMap)["ouml"] = '\xF6';
+		(*_htmlEntitiesMap)["divide"] = '\xF7';
+		(*_htmlEntitiesMap)["oslash"] = '\xF8';
+		(*_htmlEntitiesMap)["ugrave"] = '\xF9';
+		(*_htmlEntitiesMap)["uacute"] = '\xFA';
+		(*_htmlEntitiesMap)["ucirc"] = '\xFB';
+		(*_htmlEntitiesMap)["uuml"] = '\xFC';
+		(*_htmlEntitiesMap)["yacute"] = '\xFD';
+		(*_htmlEntitiesMap)["thorn"] = '\xFE';
+		(*_htmlEntitiesMap)["yuml"] = '\xFF';
+		(*_htmlEntitiesMap)["gt"] = '\x3E';
+		(*_htmlEntitiesMap)["lt"] = '\x3C';
+	}
+
+	if (*entity == '&') {
+		++entity;
+	}
+
+	if (*entity == '#') {
+		++entity;
+		int code = atoi(entity);
+		if (code >= 32 && code <= 255) {
+			return (char) code;
+		} else {
+			warning("HtmlContext::getHtmlEntityLatin1Char(): Invalid char value %s", entity);
+			return '&';
+		}
+	}
+
+
+	if (_htmlEntitiesMap->contains(entity))
+		return (*_htmlEntitiesMap)[entity];
+	else {
+		warning("HtmlContext::getHtmlEntityLatin1Char(): Unknown entity \"%s\"", entity);
+		return '&';
+	}
+}
+
+Common::String HtmlContext::substituteHtmlEntities(const char *text) {
+	Common::String result;
+	const char *charPtr = text;
+	while (*charPtr != '\0') {
+		if (*charPtr == '&') {
+			Common::String entityName = popStringPrefix(&charPtr, ';');
+			char entityChar = getHtmlEntityLatin1Char(entityName.c_str());
+			result += entityChar;
+		} else if (*charPtr == '\r') {
+			result += ' ';
+			++charPtr;
+		}
+		else {
+			result += *charPtr;
+		}
+
+		if (*charPtr != '\0')
+			++charPtr;
+	}
+
+	return result;
+}
+
+Common::String HtmlContext::popStringPrefix(const char **charPtr, char sep) {
+	const char *startPtr = *charPtr;
+	if (sep == ' ') {
+		while (!Common::isSpace(**charPtr) && **charPtr != '\0')
+			++*charPtr;
+		return Common::String(startPtr, *charPtr - startPtr);
+	} else {
+		while (**charPtr != sep && **charPtr != '\0')
+			++*charPtr;
+		return Common::String(startPtr, *charPtr - startPtr);
+	}
+}
+
+void HtmlContext::seekCommand(Common::String command, Common::String commandArg, uint16 destVar) {
+	if (!_stream) {
+		warning("HtmlContext::seekCommand(): No open stream");
+		return;
+	}
+
+	if (command.hasPrefix("SAVEMARK")) {
+		if (_buffer[0] == '\0') {
+			_currentTagType = HtmlContext::kHtmlTagType_HTML_Close;
+			return;
+		}
+
+		if (command == "SAVEMARK_BACK") {
+			if (_posBak == -1) {
+				warning("o7_seekHtmlFile(): No saved position");
+			} else {
+				debugC(5, kDebugGameFlow, "o7_seekHtmlFile: SAVEMARK_BACK pos %d -> %d", _pos, _posBak);
+				_pos = _posBak;
+			}
+		}
+
+		const char*htmlBufferPtr = _buffer;
+		if (!commandArg.empty()) {
+			while (*htmlBufferPtr != '\0') {
+				Common::String string(htmlBufferPtr, 4 * _vm->_global->_inter_animDataSize);
+				string = HtmlContext::substituteHtmlEntities(string.c_str());
+				if (strcmp(commandArg.c_str(), string.c_str()) == 0) {
+					break;
+				}
+
+				++htmlBufferPtr;
+			}
+		}
+
+		if (strcmp(htmlBufferPtr, commandArg.c_str()) == 0) {
+			_pos = _pos - strlen(_buffer) + htmlBufferPtr - _buffer;
+		} else if (!commandArg.empty()) {
+			warning("o7_seekHtmlFile(): Cannot find string \"%s\" while using SAVEMARK", commandArg.c_str());
+		}
+
+		if (_currentMarkIndex < 20) {
+			HtmlMark &mark = _marks[_currentMarkIndex];
+			mark._field_0 = READ_VARO_UINT32(destVar);
+			mark._field_4 = READ_VARO_UINT32(destVar + 4);
+			mark._field_8 = READ_VARO_UINT32(destVar + 8);
+			mark._field_C = READ_VARO_UINT32(destVar + 0xC);
+			mark._pos = _pos;
+			for (uint32 i = 0; i < 5; ++i) {
+				mark._field_14[i] = READ_VARO_UINT32(destVar + 0x14 + i * 4);
+			}
+
+			mark._field_28 = READ_VARO_UINT32(destVar + 0x28);
+
+			++_currentMarkIndex;
+		} else {
+			warning("o7_seekHtmlFile(): Mark index %d out of range while using SAVEMARK (max number of marks = %d)",
+					_currentMarkIndex,
+					20);
+		}
+
+	} else if (command.hasPrefix("GETMARK")) {
+		int markIndex = atoi(commandArg.c_str());
+		if (markIndex < 0 || markIndex >= (int) kMaxNbrOfHtmlMarks) {
+			warning("o7_seekHtmlFile(): mark index %d out of range", markIndex);
+			return;
+		}
+
+		_currentMarkIndex = markIndex;
+		HtmlMark &mark = _marks[markIndex];
+
+		_pos = mark._pos;
+
+		WRITE_VAR_OFFSET(destVar, mark._field_0);
+		WRITE_VAR_OFFSET(destVar + 4, mark._field_4);
+		WRITE_VAR_OFFSET(destVar + 8, mark._field_8);
+		WRITE_VAR_OFFSET(destVar + 0xC, mark._field_C);
+		for (uint32 i = 0; i < 5; ++i) {
+			WRITE_VAR_OFFSET(destVar + 0x14 + i * 4, mark._field_14[i]);
+		}
+
+		WRITE_VAR_OFFSET(destVar + 0x28, mark._field_28);
+
+	} else if (command == "SEEK") {
+		if (commandArg == "0")
+			_pos = 0;
+
+		if (_pos >= 0)
+			_stream->seek(_pos, SEEK_SET);
+		else
+			warning("o7_seekHtmlFile(): Invalid seek position %d", _pos);
+
+		// NB: Seek to other offsets than 0 does not seem to be implemented in the original engine
+
+	} else {
+		warning("o7_seekHtmlFile(): Unknown command \"%s\"", command.c_str());
+	}
+}
+
+void HtmlContext::parseTagAttributes(const char *tagAttributes) {
+	const char *tagAttributesPtr = tagAttributes;
+	while (*tagAttributesPtr != '\0') {
+		// Skip leading spaces
+		while (Common::isSpace(*tagAttributesPtr))
+			++tagAttributesPtr;
+
+		if (*tagAttributesPtr == '\0')
+			return;
+
+		Common::String attrName = popStringPrefix(&tagAttributesPtr, '=');
+		if (attrName.empty()) {
+			warning("HtmlContext::parseTagAttributes(): Missing '=' in tag attributes");
+			return;
+		}
+
+		Common::String attrValue;
+		if (tagAttributesPtr[1] == '\"') {
+			tagAttributesPtr += 2;
+			attrValue = popStringPrefix(&tagAttributesPtr, '\"');
+		} else {
+			++tagAttributesPtr;
+			attrValue = popStringPrefix(&tagAttributesPtr, ' ');
+		}
+
+		attrValue.toUppercase();
+
+		// Handle attribute and value
+		switch (_currentTagType) {
+		case kHtmlTagType_Font:
+			if (strcmp(attrName.c_str(), "FACE") == 0)
+				_htmlVariables[0] = attrValue;
+			else if (strcmp(attrName.c_str(), "SIZE") == 0)
+				_htmlVariables[1] = attrValue;
+			else if (strcmp(attrName.c_str(), "COLOR") == 0) {
+				_htmlVariables[2] = attrValue;
+			}
+			break;
+
+		case kHtmlTagType_Img:
+			if (strcmp(attrName.c_str(), "SRC") == 0)
+				_htmlVariables[0] = attrValue;
+			else if (strcmp(attrName.c_str(), "HEIGHT") == 0)
+				_htmlVariables[1] = attrValue;
+			else if (strcmp(attrName.c_str(), "WIDTH") == 0)
+				_htmlVariables[2] = attrValue;
+			else if (strcmp(attrName.c_str(), "BORDER") == 0)
+				_htmlVariables[3] = attrValue;
+			_htmlVariables[0] = attrValue;
+
+			if (!_field_10) {
+				_posBak = _pos;
+			}
+			break;
+
+		case kHtmlTagType_A:
+			if (strcmp(attrName.c_str(), "NAME") == 0)
+				_htmlVariables[0] = attrValue;
+			else if (strcmp(attrName.c_str(), "HREF") == 0) {
+				_htmlVariables[1] = attrValue;
+				_posBak = _pos;
+				_field_10 = true;
+			}
+			break;
+
+		default:
+			break;
+		}
+
+		if (*tagAttributesPtr == '\"')
+			++tagAttributesPtr;
+	}
+}
+
+void HtmlContext::cleanTextNode(int animDataSize) {
+	_buffer[animDataSize * 4 - 1] = '\0';
+	size_t len = strlen(_buffer);
+	char *htmlBufferPtr = strchr(_buffer, '<');
+	if (!htmlBufferPtr) {
+		char* charPtr2 = _buffer + len - 1;
+		while (charPtr2 > _buffer && *charPtr2 != ' ') {
+			--charPtr2;
+		}
+
+		if (charPtr2 > _buffer) {
+			if (*charPtr2 == ' ')
+				charPtr2[1] = '\0';
+			else
+				*charPtr2 = '\0';
+		}
+	} else {
+		*htmlBufferPtr = '\0';
+	}
+
+	htmlBufferPtr = strchr(_buffer, '&');
+	if (htmlBufferPtr) {
+		char *charPtr2 = strchr(_buffer, ';');
+		if (!charPtr2 || charPtr2 < htmlBufferPtr) {
+			*htmlBufferPtr = '\0';
+		}
+	}
+}
+
+void HtmlContext::nextKeyword(uint16 destVar, uint16 destVarTagType) {
+	_currentTagType = kHtmlTagType_None;
+
+	if (!_stream) {
+		warning("HtmlContext::nextKeyword(): No open stream");
+		return;
+	}
+
+	if (!_stream->seek(_pos, SEEK_SET)) {
+		_currentTagType = kHtmlTagType_HTML_Close;
+		return;
+	}
+
+	memset(_buffer, 0, sizeof(_buffer));
+	_stream->read(_buffer, sizeof(_buffer) - 1);
+
+	const char *htmlBufferPtr = _buffer;
+	if (*htmlBufferPtr == '<') {
+		++htmlBufferPtr;
+		Common::String tagAndAttributes = popStringPrefix(&htmlBufferPtr, '>');
+		const char *tagAndAttributesPtr = tagAndAttributes.c_str();
+		if (!tagAndAttributes.empty()) {
+			Common::String tagName = popStringPrefix(&tagAndAttributesPtr, ' ');
+			_currentTagType = getHtmlTagType(tagName.c_str());
+		}
+
+		if (_currentTagType == kHtmlTagType_None) {
+			while (*htmlBufferPtr != '>' && *htmlBufferPtr != '\0') {
+				++htmlBufferPtr;
+			}
+		} else {
+			// Handle tag attributes
+			parseTagAttributes(tagAndAttributesPtr);
+		}
+
+		if (*htmlBufferPtr == '>') {
+			++htmlBufferPtr;
+			// Skip CRLF
+			while (*htmlBufferPtr == '\r' || *htmlBufferPtr == '\n') {
+				++htmlBufferPtr;
+			}
+		}
+	} else {
+		_currentTagType = kHtmlTagType_OutsideTag;
+		cleanTextNode(_vm->_global->_inter_animDataSize);
+
+		Common::String text = popStringPrefix(&htmlBufferPtr, '<');
+		if (text.empty()) {
+			_currentTagType = kHtmlTagType_Error;
+		} else {
+			_htmlVariables[0] = substituteHtmlEntities(text.c_str());;
+		}
+	}
+
+	_pos = _pos + (htmlBufferPtr - _buffer);
+
+	// Write Html info to game variables
+	WRITE_VAR_OFFSET(destVarTagType, _currentTagType);
+	for (int i = 0; i < 10; ++i) {
+		WRITE_VARO_STR(destVar + i * 4 * _vm->_global->_inter_animDataSize,
+					   _htmlVariables[i].substr(0, 4 * _vm->_global->_inter_animDataSize).c_str());
+	}
+}
+
+
+} // End of namespace Gob
+
diff --git a/engines/gob/html_parser.h b/engines/gob/html_parser.h
new file mode 100644
index 00000000000..b5af34f7aef
--- /dev/null
+++ b/engines/gob/html_parser.h
@@ -0,0 +1,120 @@
+/* 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/>.
+ *
+ *
+ * This file is dual-licensed.
+ * In addition to the GPLv3 license mentioned above, this code is also
+ * licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
+ * full text of the license.
+ *
+ */
+
+#ifndef GOB_HTML_PARSER_H
+#define GOB_HTML_PARSER_H
+
+#include "common/hashmap.h"
+#include "common/str.h"
+
+#include "gob/gob.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Gob {
+
+struct HtmlMark {
+	uint32 _field_0;
+	uint32 _field_4;
+	uint32 _field_8;
+	uint32 _field_C;
+	uint32 _pos;
+	uint32 _field_14[5];
+	uint32 _field_28;
+};
+
+
+class HtmlContext {
+public:
+	HtmlContext(Common::SeekableReadStream *stream, GobEngine *vm);
+	~HtmlContext();
+
+	void seekCommand(Common::String command, Common::String commandArg, uint16 destVar);
+	void nextKeyword(uint16 destVar, uint16 destVarTagType);
+
+private:
+	enum HtmlTagType {
+		kHtmlTagType_None = -1,
+		kHtmlTagType_Error,
+		kHtmlTagType_OutsideTag,
+		kHtmlTagType_Body,
+		kHtmlTagType_Font,
+		kHtmlTagType_Font_Close,
+		kHtmlTagType_Img,
+		kHtmlTagType_A,
+		kHtmlTagType_A_Close,
+		kHtmlTagType_Title,
+		kHtmlTagType_Title_Close,
+		kHtmlTagType_HTML_Close,
+		kHtmlTagType_BR,
+		kHtmlTagType_P,
+		kHtmlTagType_P_Close,
+		kHtmlTagType_U,
+		kHtmlTagType_U_Close,
+		kHtmlTagType_B,
+		kHtmlTagType_B_Close,
+		kHtmlTagType_EM,
+		kHtmlTagType_EM_Close,
+		kHtmlTagType_I,
+		kHtmlTagType_I_Close,
+		kHtmlTagType_SUB,
+		kHtmlTagType_SUB_Close,
+		kHtmlTagType_SUP,
+		kHtmlTagType_SUP_Close,
+	};
+
+	static Common::HashMap<Common::String, HtmlTagType> *_htmlTagsTypesMap;
+	static Common::HashMap<Common::String, char> *_htmlEntitiesMap;
+	static Common::String popStringPrefix(const char **charPtr, char sep);
+	static HtmlTagType getHtmlTagType(const char *tag);
+	static char getHtmlEntityLatin1Char(const char *entity);
+	static Common::String substituteHtmlEntities(const char *text);
+
+	void parseTagAttributes(const char* tagAttributes);
+	void cleanTextNode(int animDataSize);
+
+	static const uint32 kMaxNbrOfHtmlMarks = 20;
+
+	HtmlTagType _currentTagType;
+	Common::SeekableReadStream *_stream;
+	int _pos;
+	int _posBak;
+	bool _field_10;
+	char _buffer[256];
+	HtmlMark _marks[kMaxNbrOfHtmlMarks];
+	uint32 _currentMarkIndex;
+	Common::String _htmlVariables[10];
+
+	GobEngine *_vm;
+};
+
+
+} // End of namespace Gob
+
+#endif // GOB_HTML_PARSER_H
\ No newline at end of file
diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 6a63aab385e..0b2f89eb67f 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -33,6 +33,7 @@
 
 #include "gob/goblin.h"
 #include "gob/variables.h"
+#include "gob/html_parser.h"
 #include "gob/iniconfig.h"
 #include "gob/databases.h"
 
@@ -791,74 +792,6 @@ protected:
 	void o7_getFreeDiskSpace(OpGobParams &params);
 
 private:
-	static const uint32 kMaxNbrOfHtmlMarks = 20;
-
-	struct HtmlMark {
-		uint32 _field_0;
-		uint32 _field_4;
-		uint32 _field_8;
-		uint32 _field_C;
-		uint32 _pos;
-		uint32 _field_14[5];
-		uint32 _field_28;
-	};
-
-	class HtmlContext {
-	public:
-		HtmlContext(Common::SeekableReadStream *stream);
-		~HtmlContext();
-
-		enum HtmlTagType {
-			kHtmlTagType_None = -1,
-			kHtmlTagType_Error,
-			kHtmlTagType_OutsideTag,
-			kHtmlTagType_Body,
-			kHtmlTagType_Font,
-			kHtmlTagType_Font_Close,
-			kHtmlTagType_Img,
-			kHtmlTagType_A,
-			kHtmlTagType_A_Close,
-			kHtmlTagType_Title,
-			kHtmlTagType_Title_Close,
-			kHtmlTagType_HTML_Close,
-			kHtmlTagType_BR,
-			kHtmlTagType_P,
-			kHtmlTagType_P_Close,
-			kHtmlTagType_U,
-			kHtmlTagType_U_Close,
-			kHtmlTagType_B,
-			kHtmlTagType_B_Close,
-			kHtmlTagType_EM,
-			kHtmlTagType_EM_Close,
-			kHtmlTagType_I,
-			kHtmlTagType_I_Close,
-			kHtmlTagType_SUB,
-			kHtmlTagType_SUB_Close,
-			kHtmlTagType_SUP,
-			kHtmlTagType_SUP_Close,
-		};
-
-		static Common::HashMap<Common::String, HtmlTagType> *_htmlTagsTypesMap;
-		static Common::HashMap<Common::String, char> *_htmlEntitiesMap;
-		static Common::String popStringPrefix(const char **charPtr, char sep);
-		static HtmlTagType getHtmlTagType(const char *tag);
-		static char getHtmlEntityLatin1Char(const char *entity);
-		static Common::String substituteHtmlEntities(const char *text);
-
-		void parseTagAttributes(const char* tagAttributes);
-		void cleanTextNode(int animDataSize);
-
-		HtmlTagType _currentTagType;
-		Common::SeekableReadStream *_stream;
-		int _pos;
-		int _posBak;
-		bool _field_10;
-		char _buffer[256];
-		HtmlMark _marks[kMaxNbrOfHtmlMarks];
-		uint32 _currentMarkIndex;
-		Common::String _htmlVariables[10];
-	};
-
 	INIConfig _inis;
 	TranslationDatabases _translationDatabases;
 	Common::HashMap<Common::String, Database, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _databases;
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index df502c3fd11..5db7ce8be97 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1255,220 +1255,6 @@ void Inter_v7::o7_checkAnyDatabaseRecordFound() {
 	WRITE_VAR_OFFSET(varIndex, db->hasMatchingRecord() ? 0 : 1);
 }
 
-Inter_v7::HtmlContext::HtmlContext(Common::SeekableReadStream *stream) {
-	_stream = stream;
-	_pos = 0;
-	_posBak = -1;
-	_field_10 = false;
-	_currentMarkIndex = 1;
-	for (uint32 i = 0; i < kMaxNbrOfHtmlMarks; ++i) {
-		_marks[i]._field_0 = 3;
-		_marks[i]._field_4 = 0;
-		_marks[i]._field_8 = 0;
-		_marks[i]._pos = 0;
-		for (uint32 j = 0; j < 5; ++j) {
-			_marks[i]._field_14[j] = 0;
-		}
-
-		_marks[i]._field_28 = 0;
-	}
-}
-
-Inter_v7::HtmlContext::~HtmlContext() {
-	delete _stream;
-}
-
-Common::HashMap<Common::String, Inter_v7::HtmlContext::HtmlTagType> *Inter_v7::HtmlContext::_htmlTagsTypesMap = nullptr;
-Common::HashMap<Common::String, char> *Inter_v7::HtmlContext::_htmlEntitiesMap = nullptr;
-
-Inter_v7::HtmlContext::HtmlTagType Inter_v7::HtmlContext::getHtmlTagType(const char *tag) {
-	if (_htmlTagsTypesMap == nullptr) {
-		_htmlTagsTypesMap = new Common::HashMap<Common::String, HtmlTagType>();
-		// Initialize the map on first use
-		(*_htmlTagsTypesMap)["BODY"] = kHtmlTagType_Body;
-		(*_htmlTagsTypesMap)["FONT"] = kHtmlTagType_Font;
-		(*_htmlTagsTypesMap)["/FONT"] = kHtmlTagType_Font_Close;
-		(*_htmlTagsTypesMap)["IMG"] = kHtmlTagType_Img;
-		(*_htmlTagsTypesMap)["A"] = kHtmlTagType_A;
-		(*_htmlTagsTypesMap)["/A"] = kHtmlTagType_A_Close;
-		(*_htmlTagsTypesMap)["TITLE"] = kHtmlTagType_Title;
-		(*_htmlTagsTypesMap)["/TITLE"] = kHtmlTagType_Title_Close;
-		(*_htmlTagsTypesMap)["/HTML"] = kHtmlTagType_HTML_Close;
-		(*_htmlTagsTypesMap)["BR"] = kHtmlTagType_BR;
-		(*_htmlTagsTypesMap)["P"] = kHtmlTagType_P;
-		(*_htmlTagsTypesMap)["/P"] = kHtmlTagType_P_Close;
-		(*_htmlTagsTypesMap)["U"] = kHtmlTagType_U;
-		(*_htmlTagsTypesMap)["/U"] = kHtmlTagType_U_Close;
-		(*_htmlTagsTypesMap)["B"] = kHtmlTagType_B;
-		(*_htmlTagsTypesMap)["/B"] = kHtmlTagType_B_Close;
-		(*_htmlTagsTypesMap)["EM"] = kHtmlTagType_EM;
-		(*_htmlTagsTypesMap)["/EM"] = kHtmlTagType_EM_Close;
-		(*_htmlTagsTypesMap)["I"] = kHtmlTagType_I;
-		(*_htmlTagsTypesMap)["/I"] = kHtmlTagType_I_Close;
-		(*_htmlTagsTypesMap)["SUB"] = kHtmlTagType_SUB;
-		(*_htmlTagsTypesMap)["/SUB"] = kHtmlTagType_SUB_Close;
-		(*_htmlTagsTypesMap)["SUP"] = kHtmlTagType_SUP;
-		(*_htmlTagsTypesMap)["/SUP"] = kHtmlTagType_SUP_Close;
-	}
-
-	if (_htmlTagsTypesMap->contains(tag))
-		return (*_htmlTagsTypesMap)[tag];
-	else
-		return kHtmlTagType_None;
-}
-
-char Inter_v7::HtmlContext::getHtmlEntityLatin1Char(const char *entity) {
-	if (_htmlEntitiesMap == nullptr) {
-		_htmlEntitiesMap = new Common::HashMap<Common::String, char>();
-		// Initialize the map on first use
-		(*_htmlEntitiesMap)["quot"] = '\x22';
-		(*_htmlEntitiesMap)["nbsp"] = '\x20';
-		(*_htmlEntitiesMap)["iexcl"] = '\xA1';
-		(*_htmlEntitiesMap)["cent"] = '\xA2';
-		(*_htmlEntitiesMap)["pound"] = '\xA3';
-		(*_htmlEntitiesMap)["curren"] = '\xA4';
-		(*_htmlEntitiesMap)["yen"] = '\xA5';
-		(*_htmlEntitiesMap)["brvbar"] = '\xA6';
-		(*_htmlEntitiesMap)["sect"] = '\xA7';
-		(*_htmlEntitiesMap)["uml"] = '\xA8';
-		(*_htmlEntitiesMap)["copy"] = '\xA9';
-		(*_htmlEntitiesMap)["ordf"] = '\xAA';
-		(*_htmlEntitiesMap)["laquo"] = '\xAB';
-		(*_htmlEntitiesMap)["not"] = '\xAC';
-		(*_htmlEntitiesMap)["shy"] = '\xAD';
-		(*_htmlEntitiesMap)["reg"] = '\xAE';
-		(*_htmlEntitiesMap)["macr"] = '\xAF';
-		(*_htmlEntitiesMap)["deg"] = '\xB0';
-		(*_htmlEntitiesMap)["plusmn"] = '\xB1';
-		(*_htmlEntitiesMap)["sup2"] = '\xB2';
-		(*_htmlEntitiesMap)["sup3"] = '\xB3';
-		(*_htmlEntitiesMap)["acute"] = '\xB4';
-		(*_htmlEntitiesMap)["micro"] = '\xB5';
-		(*_htmlEntitiesMap)["para"] = '\xB6';
-		(*_htmlEntitiesMap)["middot"] = '\xB7';
-		(*_htmlEntitiesMap)["cedil"] = '\xB8';
-		(*_htmlEntitiesMap)["sup1"] = '\xB9';
-		(*_htmlEntitiesMap)["ordm"] = '\xBA';
-		(*_htmlEntitiesMap)["raquo"] = '\xBB';
-		(*_htmlEntitiesMap)["frac14"] = '\xBC';
-		(*_htmlEntitiesMap)["frac12"] = '\xBD';
-		(*_htmlEntitiesMap)["frac34"] = '\xBE';
-		(*_htmlEntitiesMap)["iquest"] = '\xBF';
-		(*_htmlEntitiesMap)["Agrave"] = '\xC0';
-		(*_htmlEntitiesMap)["Aacute"] = '\xC1';
-		(*_htmlEntitiesMap)["Acirc"] = '\xC2';
-		(*_htmlEntitiesMap)["Atilde"] = '\xC3';
-		(*_htmlEntitiesMap)["Auml"] = '\xC4';
-		(*_htmlEntitiesMap)["Aring"] = '\xC5';
-		(*_htmlEntitiesMap)["AElig"] = '\xC6';
-		(*_htmlEntitiesMap)["Ccedil"] = '\xC7';
-		(*_htmlEntitiesMap)["Egrave"] = '\xC8';
-		(*_htmlEntitiesMap)["Eacute"] = '\xC9';
-		(*_htmlEntitiesMap)["Ecirc"] = '\xCA';
-		(*_htmlEntitiesMap)["Euml"] = '\xCB';
-		(*_htmlEntitiesMap)["Igrave"] = '\xCC';
-		(*_htmlEntitiesMap)["Iacute"] = '\xCD';
-		(*_htmlEntitiesMap)["Icirc"] = '\xCE';
-		(*_htmlEntitiesMap)["Iuml"] = '\xCF';
-		(*_htmlEntitiesMap)["ETH"] = '\xD0';
-		(*_htmlEntitiesMap)["Ntilde"] = '\xD1';
-		(*_htmlEntitiesMap)["Ograve"] = '\xD2';
-		(*_htmlEntitiesMap)["Oacute"] = '\xD3';
-		(*_htmlEntitiesMap)["Ocirc"] = '\xD4';
-		(*_htmlEntitiesMap)["Otilde"] = '\xD5';
-		(*_htmlEntitiesMap)["Ouml"] = '\xD6';
-		(*_htmlEntitiesMap)["times"] = '\xD7';
-		(*_htmlEntitiesMap)["Oslash"] = '\xD8';
-		(*_htmlEntitiesMap)["Ugrave"] = '\xD9';
-		(*_htmlEntitiesMap)["Uacute"] = '\xDA';
-		(*_htmlEntitiesMap)["Ucirc"] = '\xDB';
-		(*_htmlEntitiesMap)["Uuml"] = '\xDC';
-		(*_htmlEntitiesMap)["Yacute"] = '\xDD';
-		(*_htmlEntitiesMap)["THORN"] = '\xDE';
-		(*_htmlEntitiesMap)["szlig"] = '\xDF';
-		(*_htmlEntitiesMap)["agrave"] = '\xE0';
-		(*_htmlEntitiesMap)["aacute"] = '\xE1';
-		(*_htmlEntitiesMap)["acirc"] = '\xE2';
-		(*_htmlEntitiesMap)["atilde"] = '\xE3';
-		(*_htmlEntitiesMap)["auml"] = '\xE4';
-		(*_htmlEntitiesMap)["aring"] = '\xE5';
-		(*_htmlEntitiesMap)["aelig"] = '\xE6';
-		(*_htmlEntitiesMap)["ccedil"] = '\xE7';
-		(*_htmlEntitiesMap)["egrave"] = '\xE8';
-		(*_htmlEntitiesMap)["eacute"] = '\xE9';
-		(*_htmlEntitiesMap)["ecirc"] = '\xEA';
-		(*_htmlEntitiesMap)["euml"] = '\xEB';
-		(*_htmlEntitiesMap)["igrave"] = '\xEC';
-		(*_htmlEntitiesMap)["iacute"] = '\xED';
-		(*_htmlEntitiesMap)["icirc"] = '\xEE';
-		(*_htmlEntitiesMap)["iuml"] = '\xEF';
-		(*_htmlEntitiesMap)["eth"] = '\xF0';
-		(*_htmlEntitiesMap)["ntilde"] = '\xF1';
-		(*_htmlEntitiesMap)["ograve"] = '\xF2';
-		(*_htmlEntitiesMap)["oacute"] = '\xF3';
-		(*_htmlEntitiesMap)["ocirc"] = '\xF4';
-		(*_htmlEntitiesMap)["otilde"] = '\xF5';
-		(*_htmlEntitiesMap)["ouml"] = '\xF6';
-		(*_htmlEntitiesMap)["divide"] = '\xF7';
-		(*_htmlEntitiesMap)["oslash"] = '\xF8';
-		(*_htmlEntitiesMap)["ugrave"] = '\xF9';
-		(*_htmlEntitiesMap)["uacute"] = '\xFA';
-		(*_htmlEntitiesMap)["ucirc"] = '\xFB';
-		(*_htmlEntitiesMap)["uuml"] = '\xFC';
-		(*_htmlEntitiesMap)["yacute"] = '\xFD';
-		(*_htmlEntitiesMap)["thorn"] = '\xFE';
-		(*_htmlEntitiesMap)["yuml"] = '\xFF';
-		(*_htmlEntitiesMap)["gt"] = '\x3E';
-		(*_htmlEntitiesMap)["lt"] = '\x3C';
-	}
-
-	if (*entity == '&') {
-		++entity;
-	}
-
-	if (*entity == '#') {
-		++entity;
-		int code = atoi(entity);
-		if (code >= 32 && code <= 255) {
-			return (char) code;
-		} else {
-			warning("HtmlContext::getHtmlEntityLatin1Char(): Invalid char value %s", entity);
-			return '&';
-		}
-	}
-
-
-	if (_htmlEntitiesMap->contains(entity))
-		return (*_htmlEntitiesMap)[entity];
-	else {
-		warning("HtmlContext::getHtmlEntityLatin1Char(): Unknown entity \"%s\"", entity);
-		return '&';
-	}
-}
-
-Common::String Inter_v7::HtmlContext::substituteHtmlEntities(const char *text) {
-	Common::String result;
-	const char *charPtr = text;
-	while (*charPtr != '\0') {
-		if (*charPtr == '&') {
-			Common::String entityName = popStringPrefix(&charPtr, ';');
-			char entityChar = getHtmlEntityLatin1Char(entityName.c_str());
-			result += entityChar;
-		} else if (*charPtr == '\r') {
-			result += ' ';
-			++charPtr;
-		}
-		else {
-			result += *charPtr;
-		}
-
-		if (*charPtr != '\0')
-			++charPtr;
-	}
-
-	return result;
-}
-
 void Inter_v7::o7_openHtmlFile() {
 	Common::String file = getFile(_vm->_game->_script->evalString());
 	Common::SeekableReadStream *htmlFile = _vm->_dataIO->getFile(file);
@@ -1478,7 +1264,7 @@ void Inter_v7::o7_openHtmlFile() {
 	}
 
 	_currentHtmlFile = file;
-	_currentHtmlContext = new HtmlContext(htmlFile);
+	_currentHtmlContext = new HtmlContext(htmlFile, _vm);
 }
 
 void Inter_v7::o7_closeHtmlFile() {
@@ -1497,7 +1283,7 @@ void Inter_v7::o7_seekHtmlFile() {
 	Common::String commandArg = _vm->_game->_script->evalString();
 	Common::String command = _vm->_game->_script->evalString();
 	uint16 destVar = _vm->_game->_script->readVarIndex();
-	if (_currentHtmlContext == nullptr || _currentHtmlContext->_stream == nullptr) {
+	if (_currentHtmlContext == nullptr) {
 		warning("o7_seekHtmlFile(): No open file");
 		return;
 	}
@@ -1506,222 +1292,16 @@ void Inter_v7::o7_seekHtmlFile() {
 		warning("o7_seekHtmlFile(): filename mismatch \"%s\" != \"%s\"", file.c_str(), _currentHtmlFile.c_str());
 	}
 
-	if (command.hasPrefix("SAVEMARK")) {
-		if (_currentHtmlContext->_buffer[0] == '\0') {
-			_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_HTML_Close;
-			return;
-		}
-
-		if (command == "SAVEMARK_BACK") {
-			if (_currentHtmlContext->_posBak == -1) {
-				warning("o7_seekHtmlFile(): No saved position");
-			} else {
-				debugC(5, kDebugGameFlow, "o7_seekHtmlFile: SAVEMARK_BACK pos %d -> %d", _currentHtmlContext->_pos, _currentHtmlContext->_posBak);
-				_currentHtmlContext->_pos = _currentHtmlContext->_posBak;
-			}
-		}
-
-		const char*htmlBufferPtr = _currentHtmlContext->_buffer;
-		if (!commandArg.empty()) {
-			while (*htmlBufferPtr != '\0') {
-				Common::String string(htmlBufferPtr, 4 * _vm->_global->_inter_animDataSize);
-				string = HtmlContext::substituteHtmlEntities(string.c_str());
-				if (strcmp(commandArg.c_str(), string.c_str()) == 0) {
-					break;
-				}
-
-				++htmlBufferPtr;
-			}
-		}
-
-		if (strcmp(htmlBufferPtr, commandArg.c_str()) == 0) {
-			_currentHtmlContext->_pos = _currentHtmlContext->_pos - strlen(_currentHtmlContext->_buffer) + htmlBufferPtr - _currentHtmlContext->_buffer;
-		} else if (!commandArg.empty()) {
-			warning("o7_seekHtmlFile(): Cannot find string \"%s\" while using SAVEMARK", commandArg.c_str());
-		}
-
-		if (_currentHtmlContext->_currentMarkIndex < 20) {
-			HtmlMark &mark = _currentHtmlContext->_marks[_currentHtmlContext->_currentMarkIndex];
-			mark._field_0 = READ_VARO_UINT32(destVar);
-			mark._field_4 = READ_VARO_UINT32(destVar + 4);
-			mark._field_8 = READ_VARO_UINT32(destVar + 8);
-			mark._field_C = READ_VARO_UINT32(destVar + 0xC);
-			mark._pos = _currentHtmlContext->_pos;
-			for (uint32 i = 0; i < 5; ++i) {
-				mark._field_14[i] = READ_VARO_UINT32(destVar + 0x14 + i * 4);
-			}
-
-			mark._field_28 = READ_VARO_UINT32(destVar + 0x28);
-
-			++_currentHtmlContext->_currentMarkIndex;
-		} else {
-			warning("o7_seekHtmlFile(): Mark index %d out of range while using SAVEMARK (max number of marks = %d)",
-					_currentHtmlContext->_currentMarkIndex,
-					20);
-		}
-
-	} else if (command.hasPrefix("GETMARK")) {
-		int markIndex = atoi(commandArg.c_str());
-		if (markIndex < 0 || markIndex >= (int) kMaxNbrOfHtmlMarks) {
-			warning("o7_seekHtmlFile(): mark index %d out of range", markIndex);
-			return;
-		}
-
-		_currentHtmlContext->_currentMarkIndex = markIndex;
-		HtmlMark &mark = _currentHtmlContext->_marks[markIndex];
-
-		_currentHtmlContext->_pos = mark._pos;
-
-		WRITE_VAR_OFFSET(destVar, mark._field_0);
-		WRITE_VAR_OFFSET(destVar + 4, mark._field_4);
-		WRITE_VAR_OFFSET(destVar + 8, mark._field_8);
-		WRITE_VAR_OFFSET(destVar + 0xC, mark._field_C);
-		for (uint32 i = 0; i < 5; ++i) {
-			WRITE_VAR_OFFSET(destVar + 0x14 + i * 4, mark._field_14[i]);
-		}
-
-		WRITE_VAR_OFFSET(destVar + 0x28, mark._field_28);
-
-	} else if (command == "SEEK") {
-		if (commandArg == "0")
-			_currentHtmlContext->_pos = 0;
-
-		if (_currentHtmlContext->_pos >= 0)
-			_currentHtmlContext->_stream->seek(_currentHtmlContext->_pos, SEEK_SET);
-		else
-			warning("o7_seekHtmlFile(): Invalid seek position %d", _currentHtmlContext->_pos);
-
-		// NB: Seek to other offsets than 0 does not seem to be implemented in the original engine
-
-	} else {
-		warning("o7_seekHtmlFile(): Unknown command \"%s\"", command.c_str());
-	}
-}
-
-Common::String Inter_v7::HtmlContext::popStringPrefix(const char **charPtr, char sep) {
-	const char *startPtr = *charPtr;
-	if (sep == ' ') {
-		while (!Common::isSpace(**charPtr) && **charPtr != '\0')
-			++*charPtr;
-		return Common::String(startPtr, *charPtr - startPtr);
-	} else {
-		while (**charPtr != sep && **charPtr != '\0')
-			++*charPtr;
-		return Common::String(startPtr, *charPtr - startPtr);
-	}
+	_currentHtmlContext->seekCommand(command, commandArg, destVar);
 }
 
-void Inter_v7::HtmlContext::parseTagAttributes(const char* tagAttributes) {
-	const char *tagAttributesPtr = tagAttributes;
-	while (*tagAttributesPtr != '\0') {
-		// Skip leading spaces
-		while (Common::isSpace(*tagAttributesPtr))
-			++tagAttributesPtr;
-
-		if (*tagAttributesPtr == '\0')
-			return;
-
-		Common::String attrName = popStringPrefix(&tagAttributesPtr, '=');
-		if (attrName.empty()) {
-			warning("HtmlContext::parseTagAttributes(): Missing '=' in tag attributes");
-			return;
-		}
-
-		Common::String attrValue;
-		if (tagAttributesPtr[1] == '\"') {
-			tagAttributesPtr += 2;
-			attrValue = popStringPrefix(&tagAttributesPtr, '\"');
-		} else {
-			++tagAttributesPtr;
-			attrValue = popStringPrefix(&tagAttributesPtr, ' ');
-		}
-
-		attrValue.toUppercase();
-
-		// Handle attribute and value
-		switch (_currentTagType) {
-		case kHtmlTagType_Font:
-			if (strcmp(attrName.c_str(), "FACE") == 0)
-				_htmlVariables[0] = attrValue;
-			else if (strcmp(attrName.c_str(), "SIZE") == 0)
-				_htmlVariables[1] = attrValue;
-			else if (strcmp(attrName.c_str(), "COLOR") == 0) {
-				_htmlVariables[2] = attrValue;
-			}
-			break;
-
-		case kHtmlTagType_Img:
-			if (strcmp(attrName.c_str(), "SRC") == 0)
-				_htmlVariables[0] = attrValue;
-			else if (strcmp(attrName.c_str(), "HEIGHT") == 0)
-				_htmlVariables[1] = attrValue;
-			else if (strcmp(attrName.c_str(), "WIDTH") == 0)
-				_htmlVariables[2] = attrValue;
-			else if (strcmp(attrName.c_str(), "BORDER") == 0)
-				_htmlVariables[3] = attrValue;
-			_htmlVariables[0] = attrValue;
-
-			if (!_field_10) {
-				_posBak = _pos;
-			}
-			break;
-
-		case kHtmlTagType_A:
-			if (strcmp(attrName.c_str(), "NAME") == 0)
-				_htmlVariables[0] = attrValue;
-			else if (strcmp(attrName.c_str(), "HREF") == 0) {
-				_htmlVariables[1] = attrValue;
-				_posBak = _pos;
-				_field_10 = true;
-			}
-			break;
-
-		default:
-			break;
-		}
-
-		if (*tagAttributesPtr == '\"')
-			++tagAttributesPtr;
-	}
-}
-
-void Inter_v7::HtmlContext::cleanTextNode(int animDataSize) {
-	_buffer[animDataSize * 4 - 1] = '\0';
-	size_t len = strlen(_buffer);
-	char *htmlBufferPtr = strchr(_buffer, '<');
-	if (!htmlBufferPtr) {
-		char* charPtr2 = _buffer + len - 1;
-		while (charPtr2 > _buffer && *charPtr2 != ' ') {
-			--charPtr2;
-		}
-
-		if (charPtr2 > _buffer) {
-			if (*charPtr2 == ' ')
-				charPtr2[1] = '\0';
-			else
-				*charPtr2 = '\0';
-		}
-	} else {
-		*htmlBufferPtr = '\0';
-	}
-
-	htmlBufferPtr = strchr(_buffer, '&');
-	if (htmlBufferPtr) {
-		char *charPtr2 = strchr(_buffer, ';');
-		if (!charPtr2 || charPtr2 < htmlBufferPtr) {
-			*htmlBufferPtr = '\0';
-		}
-	}
-}
 
 void Inter_v7::o7_nextKeywordHtmlFile() {
 	Common::String file = getFile(_vm->_game->_script->evalString());
 	uint16 destVarTagType = _vm->_game->_script->readVarIndex();
 	uint16 destVar = _vm->_game->_script->readVarIndex();
 
-	_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_None;
-
-	if (_currentHtmlContext == nullptr || _currentHtmlContext->_stream == nullptr) {
+	if (_currentHtmlContext == nullptr) {
 		warning("o7_seekHtmlFile(): No open file");
 		return;
 	}
@@ -1730,60 +1310,7 @@ void Inter_v7::o7_nextKeywordHtmlFile() {
 		warning("o7_nextKeywordHtmlFile(): filename mismatch \"%s\" != \"%s\"", file.c_str(), _currentHtmlFile.c_str());
 	}
 
-	if (!_currentHtmlContext->_stream->seek(_currentHtmlContext->_pos, SEEK_SET)) {
-		_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_HTML_Close;
-		return;
-	}
-
-	memset(_currentHtmlContext->_buffer, 0, sizeof(_currentHtmlContext->_buffer));
-	_currentHtmlContext->_stream->read(_currentHtmlContext->_buffer, sizeof(_currentHtmlContext->_buffer) - 1);
-
-	const char *htmlBufferPtr = _currentHtmlContext->_buffer;
-	if (*htmlBufferPtr == '<') {
-		++htmlBufferPtr;
-		Common::String tagAndAttributes = HtmlContext::popStringPrefix(&htmlBufferPtr, '>');
-		const char *tagAndAttributesPtr = tagAndAttributes.c_str();
-		if (!tagAndAttributes.empty()) {
-			Common::String tagName = HtmlContext::popStringPrefix(&tagAndAttributesPtr, ' ');
-			_currentHtmlContext->_currentTagType = HtmlContext::getHtmlTagType(tagName.c_str());
-		}
-
-		if (_currentHtmlContext->_currentTagType == HtmlContext::kHtmlTagType_None) {
-			while (*htmlBufferPtr != '>' && *htmlBufferPtr != '\0') {
-				++htmlBufferPtr;
-			}
-		} else {
-			// Handle tag attributes
-			_currentHtmlContext->parseTagAttributes(tagAndAttributesPtr);
-		}
-
-		if (*htmlBufferPtr == '>') {
-			++htmlBufferPtr;
-			// Skip CRLF
-			while (*htmlBufferPtr == '\r' || *htmlBufferPtr == '\n') {
-				++htmlBufferPtr;
-			}
-		}
-	} else {
-		_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_OutsideTag;
-		_currentHtmlContext->cleanTextNode(_vm->_global->_inter_animDataSize);
-
-		Common::String text = HtmlContext::popStringPrefix(&htmlBufferPtr, '<');
-		if (text.empty()) {
-			_currentHtmlContext->_currentTagType = HtmlContext::kHtmlTagType_Error;
-		} else {
-			_currentHtmlContext->_htmlVariables[0] = HtmlContext::substituteHtmlEntities(text.c_str());;
-		}
-	}
-
-	_currentHtmlContext->_pos = _currentHtmlContext->_pos + (htmlBufferPtr - _currentHtmlContext->_buffer);
-
-	// Write Html info to game variables
-	WRITE_VAR_OFFSET(destVarTagType, _currentHtmlContext->_currentTagType);
-	for (int i = 0; i < 10; ++i) {
-		WRITE_VARO_STR(destVar + i * 4 * _vm->_global->_inter_animDataSize,
-					   _currentHtmlContext->_htmlVariables[i].substr(0, 4 * _vm->_global->_inter_animDataSize).c_str());
-	}
+	_currentHtmlContext->nextKeyword(destVar, destVarTagType);
 }
 
 void Inter_v7::o7_draw0xC3() {
diff --git a/engines/gob/module.mk b/engines/gob/module.mk
index bd7cf7a71b2..b64b25589c7 100644
--- a/engines/gob/module.mk
+++ b/engines/gob/module.mk
@@ -30,6 +30,7 @@ MODULE_OBJS := \
 	goblin_v4.o \
 	goblin_v7.o \
 	hotspots.o \
+	html_parser.o \
 	iniconfig.o \
 	init.o \
 	init_v1.o \


Commit: a45382146446617f5e95e734161533c67dad355e
    https://github.com/scummvm/scummvm/commit/a45382146446617f5e95e734161533c67dad355e
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Make the header size explicit when reading dBase MDX index files

Changed paths:
    engines/gob/dbase.cpp


diff --git a/engines/gob/dbase.cpp b/engines/gob/dbase.cpp
index a7bf800a1dc..5c719223a29 100644
--- a/engines/gob/dbase.cpp
+++ b/engines/gob/dbase.cpp
@@ -102,7 +102,7 @@ bool dbaseMultipeIndex::load(Common::SeekableReadStream &stream) {
 	_lastUpdate.tm_mon  = stream.readByte() - 1;
 	_lastUpdate.tm_mday = stream.readByte();
 
-	stream.skip(497); // Reserved
+	stream.seek(544); // Go past the header (size 32 + 512)
 
 	for (int i = 0; i < _nbrOfTagsInUse; ++i) {
 		uint32 tagHeaderPage = stream.readUint32LE();


Commit: 03be95faca8ef655632b0994f88849bd9cf906de
    https://github.com/scummvm/scummvm/commit/03be95faca8ef655632b0994f88849bd9cf906de
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Indentation fix

Changed paths:
    engines/gob/dbase.cpp


diff --git a/engines/gob/dbase.cpp b/engines/gob/dbase.cpp
index 5c719223a29..df865fa5e68 100644
--- a/engines/gob/dbase.cpp
+++ b/engines/gob/dbase.cpp
@@ -69,8 +69,7 @@ Common::Array<dbaseMultipeIndex::FieldReference> dbaseMultipeIndex::parseKeyDefi
 	return fieldReferences;
 }
 
-const Common::Array<dbaseMultipeIndex::FieldReference>* dbaseMultipeIndex::getTagKeyDefinition(Common::String tagName) const
-{
+const Common::Array<dbaseMultipeIndex::FieldReference>* dbaseMultipeIndex::getTagKeyDefinition(Common::String tagName) const {
 	if (!_tagKeyDefinitions.contains(tagName))
 		return nullptr;
 	else


Commit: c0ddd03a106fc5697e9e06fd13ddd263990f71f4
    https://github.com/scummvm/scummvm/commit/c0ddd03a106fc5697e9e06fd13ddd263990f71f4
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Simplify cursor handling in Adibou2/Sciences

The cursor can be directly passed to CursorMan.replaceCursor() after
being loaded from file. No intermediate copy and pixel format conversion
to the ancillary "_scummvmCursor" surface is needed.

Changed paths:
    engines/gob/draw_v7.cpp


diff --git a/engines/gob/draw_v7.cpp b/engines/gob/draw_v7.cpp
index 94244a7c50b..6200f637467 100644
--- a/engines/gob/draw_v7.cpp
+++ b/engines/gob/draw_v7.cpp
@@ -111,71 +111,20 @@ bool Draw_v7::loadCursorFromFile(Common::String cursorName) {
 	}
 
 
-	// Make sure the cursor sprite is big enough
-	if (_scummvmCursor->getWidth() != cursor->getWidth() || _scummvmCursor->getHeight() != cursor->getHeight()) {
-		_vm->_draw->_scummvmCursor.reset();
-		_vm->_draw->_scummvmCursor = _vm->_video->initSurfDesc(cursor->getWidth(), cursor->getHeight(), SCUMMVM_CURSOR);
-	}
-
-	_scummvmCursor->clear();
-
-	uint32 keyColor = cursor->getKeyColor();
-	if (_scummvmCursor->getBPP() == 1) {
-		Graphics::copyBlit(_scummvmCursor->getData(), cursor->getSurface(),
-						   _scummvmCursor->getWidth() *  _scummvmCursor->getBPP(),
-						   cursor->getWidth(),
-						   cursor->getWidth(),
-						   cursor->getHeight(),
-						   1);
-	} else {
-		uint32 map[256];
-		convertPaletteToMap(map + cursor->getPaletteStartIndex(), cursor->getPalette(), cursor->getPaletteCount(), _vm->getPixelFormat());
-		if (!cursor->getMask()) {
-			// Look for a RGB color in [0, 256] not in the map and use it as the new key color.
-			// At least one of this 257 colors is guaranteed to be free, since the map size is 256.
-			bool colorPresent[257] = {false};
-			for (uint32 i = cursor->getPaletteStartIndex();
-				 i < cursor->getPaletteStartIndex() + cursor->getPaletteCount();
-				 ++i) {
-				uint32 color = map[i];
-				if (color <= 256)
-					colorPresent[color] = true;
-			}
-
-			for (keyColor = 0; keyColor <= 256; ++keyColor) {
-				if (!colorPresent[keyColor])
-					break;
-			}
-		}
-
-		map[cursor->getKeyColor()] = keyColor;
-
-		Graphics::crossBlitMap(_scummvmCursor->getData(), cursor->getSurface(),
-							   _scummvmCursor->getWidth() * _scummvmCursor->getBPP(),
-							   cursor->getWidth(),
-							   cursor->getWidth(),
-							   cursor->getHeight(),
-							   _scummvmCursor->getBPP(),
-							   map);
-	}
-
-	CursorMan.replaceCursor(_scummvmCursor->getData(),
+	Graphics::PixelFormat clut8Format = Graphics::PixelFormat::createFormatCLUT8();
+	CursorMan.replaceCursor(cursor->getSurface(),
 							cursor->getWidth(),
 							cursor->getHeight(),
 							cursor->getHotspotX(),
 							cursor->getHotspotY(),
-							keyColor,
+							cursor->getKeyColor(),
 							false,
-							&_vm->getPixelFormat(),
+							&clut8Format,
 							cursor->getMask());
-	if (_scummvmCursor->getBPP() != 1)
-		CursorMan.disableCursorPalette(true);
-	else {
-		CursorMan.replaceCursorPalette(cursor->getPalette(),
-									   cursor->getPaletteStartIndex(),
-									   cursor->getPaletteCount());
-		CursorMan.disableCursorPalette(false);
-	}
+	CursorMan.replaceCursorPalette(cursor->getPalette(),
+								   cursor->getPaletteStartIndex(),
+								   cursor->getPaletteCount());
+	CursorMan.disableCursorPalette(false);
 
 	delete cursorGroup;
 	delete defaultCursor;


Commit: 9da751b597a5f02544ac41fc6a415f4efa2e3e54
    https://github.com/scummvm/scummvm/commit/9da751b597a5f02544ac41fc6a415f4efa2e3e54
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix conditions for format conversion in loadImage() (Adibou2/Sciences)

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 1103d5403ff..4c7d01ef66a 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -1028,12 +1028,12 @@ bool Surface::loadImage(Image::ImageDecoder &decoder, Common::SeekableReadStream
 		return false;
 
 	const Graphics::Surface *st = decoder.getSurface();
-	bool needConversion = decoder.getSurface()->format.bpp() > 1 && decoder.getSurface()->format != format;
+	bool needConversion = decoder.getSurface()->format.bytesPerPixel > 1 && decoder.getSurface()->format != format;
 	if (needConversion)
 		st = st->convertTo(format);
 
 	uint32 *colorMap = nullptr;
-	if (format.bytesPerPixel > 1) {
+	if (format.bytesPerPixel > 1 && decoder.getSurface()->format.bytesPerPixel == 1) {
 		colorMap = new uint32[256];
 		computeHighColorMap(colorMap, decoder.getPalette().data(), format, true);
 	}


Commit: ff56cb9c4ec6e5b61215a086d2a8f6636b5e6e77
    https://github.com/scummvm/scummvm/commit/ff56cb9c4ec6e5b61215a086d2a8f6636b5e6e77
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix image transparency issues for some pixel formats (Adibou2/Sciences)

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 4c7d01ef66a..35ba048ad5c 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -1029,8 +1029,20 @@ bool Surface::loadImage(Image::ImageDecoder &decoder, Common::SeekableReadStream
 
 	const Graphics::Surface *st = decoder.getSurface();
 	bool needConversion = decoder.getSurface()->format.bytesPerPixel > 1 && decoder.getSurface()->format != format;
-	if (needConversion)
-		st = st->convertTo(format);
+	if (needConversion) {
+		Graphics::Surface *st2 = st->convertTo(format);
+		// Force the pixel value 0 (used by the engine as the special transparent color) to be still mapped to 0 in the new format
+		for (int16 x2 = 0; x2 < st->w; ++x2) {
+			for (int16 y2 = 0; y2 < st->h; ++y2) {
+				uint32 p = st->getPixel(x2, y2);
+				if (p == 0) {
+					st2->setPixel(x2, y2, 0);
+				}
+			}
+		}
+
+		st = st2;
+	}
 
 	uint32 *colorMap = nullptr;
 	if (format.bytesPerPixel > 1 && decoder.getSurface()->format.bytesPerPixel == 1) {


Commit: c74340d08df15250685ca9c1f33a85836c5656c9
    https://github.com/scummvm/scummvm/commit/c74340d08df15250685ca9c1f33a85836c5656c9
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Use the backend's preferred pixel format in high color mode (Adibou2/Sciences)

Instead of the original engine one, RGB555.

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 5db7ce8be97..9d867088767 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -528,9 +528,12 @@ void Inter_v7::o7_initScreen() {
 	videoMode = _vm->_game->_script->readInt16();
 
 	if (videoMode == 32000 || videoMode == 256) {
-		Graphics::PixelFormat format = Graphics::createPixelFormat<555>();
 		bool trueColor = videoMode == 32000;
-		_vm->setTrueColor(trueColor, false, &format);
+
+		// Comment the next line and uncomment the following ones to use the original pixel format (RGB555) for debugging purposes
+		_vm->setTrueColor(trueColor, false, nullptr);
+		// Graphics::PixelFormat format = Graphics::createPixelFormat<555>();
+		// _vm->setTrueColor(trueColor, false, &format);
 	}
 
 	videoMode &= 0xFF;


Commit: 983ab241dc1cf44674134223ac6dace0a50c8fa5
    https://github.com/scummvm/scummvm/commit/983ab241dc1cf44674134223ac6dace0a50c8fa5
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Do CLUT->high color conversions for videos on the engine side (Adibou2/Sciences)

Instead of doing the conversion inside the decoder.

Changed paths:
    engines/gob/videoplayer.cpp
    engines/gob/videoplayer.h


diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index bb4504ac5c2..a49302bb065 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -25,6 +25,7 @@
  *
  */
 
+#include "graphics/blit.h"
 
 #include "video/coktel_decoder.h"
 
@@ -50,7 +51,7 @@ VideoPlayer::Properties::Properties() : type(kVideoTypeTry), sprite(Draw::kFront
 }
 
 
-VideoPlayer::Video::Video() : decoder(0), live(false) {
+VideoPlayer::Video::Video() : decoder(nullptr), live(false), highColorMap(nullptr) {
 }
 
 bool VideoPlayer::Video::isEmpty() const {
@@ -60,10 +61,14 @@ bool VideoPlayer::Video::isEmpty() const {
 void VideoPlayer::Video::close() {
 	delete decoder;
 
-	decoder = 0;
+	decoder = nullptr;
 	fileName.clear();
 	surface.reset();
 
+	tmpSurfBppConversion.reset();
+	delete highColorMap;
+	highColorMap = nullptr;
+
 	live = false;
 }
 
@@ -252,16 +257,21 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 					video->surface = _vm->_draw->_backSurface;
 
 				if (video->decoder->isPaletted() && video->surface->getBPP() > 1) {
-					uint32 *highColorMap = new uint32[256];
-					Surface::computeHighColorMap(highColorMap,
+					video->tmpSurfBppConversion.reset(new Graphics::Surface());
+					video->tmpSurfBppConversion->create(video->surface->getWidth(),
+														video->surface->getHeight(),
+														video->decoder->getPixelFormat());
+
+					if (!video->highColorMap)
+						video->highColorMap = new uint32[256];
+					Surface::computeHighColorMap(video->highColorMap,
 												 video->decoder->getPalette(),
 												 _vm->getPixelFormat(),
 												 _vm->getGameType() == kGameTypeAdibou2);
-					video->decoder->setSurfaceMemoryPalettedToHighColor(video->surface->getData(),
-																		video->surface->getWidth(),
-																		video->surface->getHeight(),
-																		_vm->getPixelFormat(),
-																		highColorMap);
+					video->decoder->setSurfaceMemory(video->tmpSurfBppConversion->getPixels(),
+													 video->tmpSurfBppConversion->w,
+													 video->tmpSurfBppConversion->h,
+													 video->tmpSurfBppConversion->format.bytesPerPixel);
 				} else {
 					video->decoder->setSurfaceMemory(video->surface->getData(),
 													 video->surface->getWidth(),
@@ -669,6 +679,18 @@ bool VideoPlayer::playFrame(int slot, Properties &properties) {
 	}
 
 	const Graphics::Surface *surface = video->decoder->decodeNextFrame();
+	if (surface != nullptr && video->decoder->isPaletted() && video->surface && video->surface->getBPP() > 1) {
+		int16 x = 0;
+		int16 y = 0;
+		int16 width = 0;
+		int16 height = 0;
+		video->decoder->getFrameCoords(video->decoder->getCurFrame(), x, y, width, height);
+		Graphics::crossBlitMap(video->surface->getData(x, y), static_cast<const byte *>(surface->getBasePtr(x, y)),
+							   video->surface->getWidth() * video->surface->getBPP(),
+							   surface->pitch,
+							   width, height,
+							   video->surface->getBPP(), video->highColorMap);
+	}
 
 	if (_vm->getGameType() != kGameTypeAdibou2)
 		WRITE_VAR(11, video->decoder->getCurFrame());
@@ -991,7 +1013,7 @@ bool VideoPlayer::copyFrame(int slot, Surface &dest,
 	// of the frame data which is undesirable.
 	const Surface src(surface->w, surface->h, surface->format.bytesPerPixel,
 					  static_cast<byte*>(const_cast<void*>(surface->getPixels())),
-					  video->decoder->getHighColorMap());
+					  video->highColorMap);
 
 	dest.blit(src, left, top, left + width - 1, top + height - 1, x, y, transp, yAxisReflection);
 
diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h
index 27a2447b2f9..e4165b5547a 100644
--- a/engines/gob/videoplayer.h
+++ b/engines/gob/videoplayer.h
@@ -177,6 +177,8 @@ private:
 		Common::String fileName;
 
 		SurfacePtr surface;
+		Common::SharedPtr<Graphics::Surface> tmpSurfBppConversion;
+		uint32 *highColorMap;
 
 		Properties properties;
 


Commit: 45cdcf63d1ae59689d49badb86dfebb37d0b130f
    https://github.com/scummvm/scummvm/commit/45cdcf63d1ae59689d49badb86dfebb37d0b130f
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
VIDEO: Revert "Add support for blitting paletted VMD to high-color destinations"

Obsolete since the conversion is now done in the Gob engine.

Changed paths:
    video/coktel_decoder.cpp
    video/coktel_decoder.h


diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index a4d64cecf51..db8d4517a04 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -58,7 +58,7 @@ CoktelDecoder::State::State() : flags(0), speechId(0) {
 CoktelDecoder::CoktelDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) :
 	_mixer(mixer), _soundType(soundType), _width(0), _height(0), _x(0), _y(0),
 	_defaultX(0), _defaultY(0), _features(0), _nbFramesPastEnd(0), _frameCount(0), _palette(256), _paletteDirty(false),
-	_highColorMap(nullptr), _isDouble(false), _ownSurface(true), _frameRate(12), _hasSound(false),
+	_isDouble(false), _ownSurface(true), _frameRate(12), _hasSound(false),
 	_soundEnabled(false), _soundStage(kSoundNone), _audioStream(0), _startTime(0),
 	_pauseStartTime(0), _isPaused(false) {
 
@@ -66,7 +66,6 @@ CoktelDecoder::CoktelDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundT
 }
 
 CoktelDecoder::~CoktelDecoder() {
-	delete[] _highColorMap;
 }
 
 bool CoktelDecoder::evaluateSeekFrame(int32 &frame, int whence) const {
@@ -108,30 +107,6 @@ void CoktelDecoder::setSurfaceMemory(void *mem, uint16 width, uint16 height, uin
 	_ownSurface = false;
 }
 
-void CoktelDecoder::setSurfaceMemoryPalettedToHighColor(void *mem,
-														uint16 width,
-														uint16 height,
-														Graphics::PixelFormat format,
-														uint32 *highColorMap) {
-	freeSurface();
-
-	if (!hasVideo())
-		return;
-
-	// Sanity checks
-	assert((width > 0) && (height > 0));
-	assert(isPaletted() && format.bytesPerPixel > 1);
-
-	_tmpSurfBppConversion.create(width, height, getPixelFormat());
-	delete[] _highColorMap;
-	_highColorMap = highColorMap;
-
-	// Create a surface over this memory
-	_surface.init(width, height, width * format.bytesPerPixel, mem, format);
-
-	_ownSurface = false;
-}
-
 void CoktelDecoder::setSurfaceMemory() {
 	freeSurface();
 	createSurface();
@@ -347,10 +322,6 @@ const byte *CoktelDecoder::getPalette() {
 	return _palette.data();
 }
 
-const uint32 *CoktelDecoder::getHighColorMap() {
-	return _highColorMap;
-}
-
 bool CoktelDecoder::hasDirtyPalette() const {
 	return (_features & kFeaturesPalette) && _paletteDirty;
 }
@@ -2554,14 +2525,12 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
 
 	uint8 type = *dataPtr++;
 
-	bool palettedToHighColor = _surface.format.bytesPerPixel != _bytesPerPixel && _bytesPerPixel == 1;
-
 	if (type & 0x80) {
 		// Frame data is compressed
 
 		type &= 0x7F;
 
-		if ((type == 2) && (rect.width() == _surface.w) && (_x == 0) && (_blitMode == 0) && !palettedToHighColor) {
+		if ((type == 2) && (rect.width() == _surface.w) && (_x == 0) && (_blitMode == 0)) {
 			// Directly uncompress onto the video surface
 			const int offsetX = rect.left * _surface.format.bytesPerPixel;
 			const int offsetY = rect.top * _surface.pitch;
@@ -2589,10 +2558,6 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
 		surface = &_8bppSurface[2];
 	}
 
-	if (palettedToHighColor) {
-		surface = &_tmpSurfBppConversion;
-	}
-
 	// Evaluate the block type
 	if      (type == 0x01) {
 		if (_isDouble)
@@ -2613,10 +2578,7 @@ bool VMDDecoder::renderFrame(Common::Rect &rect) {
 	else
 		renderBlockSparse2Y(*surface, dataPtr, *blockRect);
 
-	if (palettedToHighColor) {
-		blitPalettedToHighColor(*surface, *blockRect);
-	}
-	else if (_blitMode > 0) {
+	if (_blitMode > 0) {
 		if      (_bytesPerPixel == 2)
 			blit16(*surface, *blockRect);
 		else if (_bytesPerPixel == 3)
@@ -2679,22 +2641,6 @@ bool VMDDecoder::getRenderRects(const Common::Rect &rect,
 	return true;
 }
 
-void VMDDecoder::blitPalettedToHighColor(const Graphics::Surface &srcSurf, Common::Rect &rect) {
-	rect = Common::Rect(rect.left, rect.top, rect.right, rect.bottom);
-
-	rect.clip(_surface.w, _surface.h);
-
-	assert(isPaletted());
-	assert(_highColorMap != nullptr);
-
-	for (int x = rect.left; x < rect.left + rect.width(); x++) {
-		for (int y = rect.top; y < rect.top + rect.height(); y++) {
-			uint32 color = srcSurf.getPixel(x, y);
-			_surface.setPixel(x, y, _highColorMap[color]);
-		}
-	}
-}
-
 void VMDDecoder::blit16(const Graphics::Surface &srcSurf, Common::Rect &rect) {
 	rect = Common::Rect(rect.left / 2, rect.top, rect.right / 2, rect.bottom);
 
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 291b5c63f7f..7747e6b631d 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -95,14 +95,6 @@ public:
 
 	/** Draw directly onto the specified video memory. */
 	void setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp);
-
-	/** Draw directly onto the specified video memory, with a CLUT to high color conversion. */
-	void setSurfaceMemoryPalettedToHighColor(void *mem,
-											 uint16 width,
-											 uint16 height,
-											 Graphics::PixelFormat format,
-											 uint32 *highColorMap);
-
 	/** Reset the video memory. */
 	void setSurfaceMemory();
 
@@ -204,7 +196,6 @@ public:
 
 	const byte *getPalette();
 	bool  hasDirtyPalette() const;
-	const uint32 *getHighColorMap();
 
 	uint32 getTimeToNextFrame() const;
 	uint32 getStaticTimeToNextFrame() const;
@@ -254,13 +245,10 @@ protected:
 	Graphics::Palette _palette;
 	bool _paletteDirty;
 
-	uint32 *_highColorMap;
-
 	bool _isDouble;
 
 	bool    _ownSurface;
 	Graphics::Surface _surface;
-	Graphics::Surface _tmpSurfBppConversion;
 
 	Common::List<Common::Rect> _dirtyRects;
 
@@ -593,7 +581,6 @@ private:
 	bool renderFrame(Common::Rect &rect);
 	bool getRenderRects(const Common::Rect &rect,
 			Common::Rect &realRect, Common::Rect &fakeRect);
-	void blitPalettedToHighColor(const Graphics::Surface &srcSurf, Common::Rect &rect);
 	void blit16(const Graphics::Surface &srcSurf, Common::Rect &rect);
 	void blit24(const Graphics::Surface &srcSurf, Common::Rect &rect);
 


Commit: 3679535d7d4f25654df50ea9b2b8a325410ccfb1
    https://github.com/scummvm/scummvm/commit/3679535d7d4f25654df50ea9b2b8a325410ccfb1
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix a CLUT->High color conversion issue

This fixes some incorrect underline colors in Adibou2/Sciences activity sheets.

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 35ba048ad5c..13a15342acd 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -51,27 +51,27 @@ class SurfacePrimitives final : public Graphics::Primitives {
 public:
         void drawPoint(int x, int y, uint32 color, void *data) override {
 		Surface *s = (Surface *)data;
-		s->putPixel(x, y, color);
+		s->putPixelRaw(x, y, color);
 	}
 
         void drawHLine(int x1, int x2, int y, uint32 color, void *data) override {
 		Surface *s = (Surface *)data;
-		s->fillRect(x1, y, x2, y, color);
+		s->fillRectRaw(x1, y, x2, y, color);
 	}
 
         void drawVLine(int x, int y1, int y2, uint32 color, void *data) override {
 		Surface *s = (Surface *)data;
-		s->fillRect(x, y1, x, y2, color);
+		s->fillRectRaw(x, y1, x, y2, color);
 	}
 
 	void drawFilledRect(const Common::Rect &rect, uint32 color, void *data) override {
 		Surface *s = (Surface *)data;
-		s->fillRect(rect.left, rect.top, rect.right - 1, rect.bottom - 1, color);
+		s->fillRectRaw(rect.left, rect.top, rect.right - 1, rect.bottom - 1, color);
 	}
 
 	void drawFilledRect1(const Common::Rect &rect, uint32 color, void *data) override {
 		Surface *s = (Surface *)data;
-		s->fillRect(rect.left, rect.top, rect.right, rect.bottom, color);
+		s->fillRectRaw(rect.left, rect.top, rect.right, rect.bottom, color);
 	}
 };
 


Commit: 3e0f26907ce496d8ab3f1c6ad06c626ed33361f8
    https://github.com/scummvm/scummvm/commit/3e0f26907ce496d8ab3f1c6ad06c626ed33361f8
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Increase the (fake) amount of memory returned by o7_getSystemProperty("TotalPhys")

This unlocks the icon bar animations in Adibou2/Sciences

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 9d867088767..e2afc047725 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -887,13 +887,14 @@ void Inter_v7::o7_getSystemProperty() {
 	const char *property = _vm->_game->_script->evalString();
 	if (!scumm_stricmp(property, "TotalPhys")) {
 		// HACK
-		storeValue(1000000);
+		// NOTE: Any value lower than 8 MB will disable the icon bar animations in Adibou2/Sciences
+		storeValue(16000000);
 		return;
 	}
 
 	if (!scumm_stricmp(property, "AvailPhys")) {
 		// HACK
-		storeValue(1000000);
+		storeValue(16000000);
 		return;
 	}
 


Commit: 9a2de05034481821dc2c0386519bfbe5fee81b92
    https://github.com/scummvm/scummvm/commit/9a2de05034481821dc2c0386519bfbe5fee81b92
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Fix another missing condition for closing videos (Adibou2/Sciences)

Fix the icon bar animations not being stopped when they are not hovered anymore.

Changed paths:
    engines/gob/inter_v7.cpp


diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index e2afc047725..35199398f08 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -747,6 +747,9 @@ void Inter_v7::o7_playVmdOrMusic() {
 		props.slot = (-props.lastFrame) % 10;
 	}
 
+	if (props.startFrame == -1)
+		close = true;
+
 	if (props.startFrame == -2)
 		props.noBlock    = true;
 


Commit: ab1dad0e8f05888d1808dd20ad6aaeaf4ed1d8cb
    https://github.com/scummvm/scummvm/commit/ab1dad0e8f05888d1808dd20ad6aaeaf4ed1d8cb
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Code formatting fixes

Changed paths:
    engines/gob/draw.cpp
    engines/gob/html_parser.cpp
    engines/gob/inter_playtoons.cpp
    engines/gob/inter_v2.cpp
    engines/gob/inter_v7.cpp
    engines/gob/scenery.cpp
    engines/gob/videoplayer.cpp


diff --git a/engines/gob/draw.cpp b/engines/gob/draw.cpp
index 50285ee872e..cfb3edad76d 100644
--- a/engines/gob/draw.cpp
+++ b/engines/gob/draw.cpp
@@ -494,8 +494,7 @@ void Draw::printTextCentered(int16 id, int16 left, int16 top, int16 right,
 		const char *s = str;
 		while (*s != '\0')
 			width += font.getCharWidth(*s++);
-	}
-	else
+	} else
 		width = strlen(str) * font.getCharWidth();
 
 	adjustCoords(1, &width, nullptr);
diff --git a/engines/gob/html_parser.cpp b/engines/gob/html_parser.cpp
index 2543367c045..ea6d0f3da38 100644
--- a/engines/gob/html_parser.cpp
+++ b/engines/gob/html_parser.cpp
@@ -237,8 +237,7 @@ Common::String HtmlContext::substituteHtmlEntities(const char *text) {
 		} else if (*charPtr == '\r') {
 			result += ' ';
 			++charPtr;
-		}
-		else {
+		} else {
 			result += *charPtr;
 		}
 
diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp
index 387a01001ac..3fd0bb5cd88 100644
--- a/engines/gob/inter_playtoons.cpp
+++ b/engines/gob/inter_playtoons.cpp
@@ -485,8 +485,7 @@ Common::String Inter_Playtoons::getFile(const char *path, bool stripPath, bool *
 		path += 4;
 		if (isCd)
 			*isCd = true;
-	}
-	else if (!strncmp(path, "<STK>", 5))
+	} else if (!strncmp(path, "<STK>", 5))
 		path += 5;
 	else if (!strncmp(path, "<ALLCD>", 7)) {
 		path += 7;
diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp
index bb120157c2b..c0eea1f5e84 100644
--- a/engines/gob/inter_v2.cpp
+++ b/engines/gob/inter_v2.cpp
@@ -828,8 +828,7 @@ void Inter_v2::o2_initScreen() {
 			_vm->_video->setSize();
 
 		}
-	}
-	else if (_vm->getGameType() == kGameTypeAdibou1 || _vm->getGameType() == kGameTypeAdi2) {
+	} else if (_vm->getGameType() == kGameTypeAdibou1 || _vm->getGameType() == kGameTypeAdi2) {
 		if (_vm->is640x400() && width == 640 && height == 480) {
 			// Force height to 400: the game is mostly scaled from the 320x200 version and
 			// never makes use of the space beyond height 400, so we can get rid of it.
diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp
index 35199398f08..6bff570adab 100644
--- a/engines/gob/inter_v7.cpp
+++ b/engines/gob/inter_v7.cpp
@@ -1661,8 +1661,7 @@ void Inter_v7::o7_fillRect(OpFuncParams &params) {
 									_vm->_draw->_destSpriteY + _vm->_draw->_spriteBottom - 1);
 
 		}
-	}
-	else
+	} else
 		_vm->_draw->spriteOperation(DRAW_FILLRECT);
 	_vm->_draw->_pattern = savedPattern;
 }
diff --git a/engines/gob/scenery.cpp b/engines/gob/scenery.cpp
index de039a62d3a..827fcfeb93d 100644
--- a/engines/gob/scenery.cpp
+++ b/engines/gob/scenery.cpp
@@ -795,8 +795,7 @@ void Scenery::updateAnimObjectVideo(int16 layer, int16 frame, int16 animation, i
 					if (layer & 0x80) {
 						sprite_dest_left = *obj.pPosX + _vm->_vidPlayer->getWidth(obj.videoSlot - 1) - deltaX - sprite_width;
 					}
-				}
-				else {
+				} else {
 					sprite_dest_left = *obj.pPosX ;
 					sprite_dest_top = *obj.pPosY;
 					sprite_dest_right = sprite_dest_left +  _vm->_vidPlayer->getWidth(obj.videoSlot - 1) - 1;
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index a49302bb065..5acdad98ff4 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -179,8 +179,7 @@ int VideoPlayer::openVideo(bool primary, const Common::String &file, Properties
 				_vm->setTrueColor(!video->decoder->isPaletted(), true);
 
 				video->decoder->colorModeChanged();
-			}
-			else {
+			} else {
 				if (!video->decoder->isPaletted()) // Paletted to high color is supported
 					return -1;
 			}


Commit: 1108d5d86df02c1ed6ce61925b7cabe52590dfde
    https://github.com/scummvm/scummvm/commit/1108d5d86df02c1ed6ce61925b7cabe52590dfde
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Add a blitShaded() function (Adibou2/Sciences)

Blits a surface onto another with alpha merging, using a global
transparency strength passed as a parameter by the opcode.

This fixes a missing transparency in the help mode of the "sea animals"
activity in Adibou2/Sciences.

Changed paths:
    engines/gob/draw_playtoons.cpp
    engines/gob/surface.cpp
    engines/gob/surface.h


diff --git a/engines/gob/draw_playtoons.cpp b/engines/gob/draw_playtoons.cpp
index f2698a06ce0..5be9d7d0ccb 100644
--- a/engines/gob/draw_playtoons.cpp
+++ b/engines/gob/draw_playtoons.cpp
@@ -137,11 +137,23 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
 		if (!_spritesArray[_sourceSurface] || !_spritesArray[_destSurface])
 			break;
 
-		_spritesArray[_destSurface]->blit(*_spritesArray[_sourceSurface],
-				_spriteLeft, spriteTop,
-				_spriteLeft + _spriteRight - 1,
-				_spriteTop + _spriteBottom - 1,
-				_destSpriteX, _destSpriteY, (_transparency == 0) ? -1 : 0);
+		if (_transparency & 0x200) {
+			uint8 strength = 16 - (((uint16) _transparency) >> 12);
+			_spritesArray[_destSurface]->blitShaded(*_spritesArray[_sourceSurface],
+											  _spriteLeft, spriteTop,
+											  _spriteLeft + _spriteRight - 1,
+											  _spriteTop + _spriteBottom - 1,
+											  _destSpriteX, _destSpriteY,
+											  strength,
+											  0,
+											  _vm->getPixelFormat());
+		} else {
+			_spritesArray[_destSurface]->blit(*_spritesArray[_sourceSurface],
+											  _spriteLeft, spriteTop,
+											  _spriteLeft + _spriteRight - 1,
+											  _spriteTop + _spriteBottom - 1,
+											  _destSpriteX, _destSpriteY, (_transparency == 0) ? -1 : 0);
+		}
 
 		dirtiedRect(_destSurface, _destSpriteX, _destSpriteY,
 				_destSpriteX + _spriteRight - 1, _destSpriteY + _spriteBottom - 1);
diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 13a15342acd..3d03407292c 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -533,6 +533,67 @@ void Surface::blitScaled(const Surface &from, Common::Rational scale, int32 tran
 	blitScaled(from, 0, 0, from._width - 1, from._height - 1, 0, 0, scale, transp);
 }
 
+void Surface::blitShaded(const Surface &from, int16 left, int16 top, int16 right, int16 bottom,
+						 int16 x, int16 y, uint8 strength, int32 transp, Graphics::PixelFormat pixelFormat) {
+
+	// Color depths have to fit
+	assert(_bpp == from._bpp);
+
+	if (_bpp == 1) {
+		// Cannot properly shade in paletted mode
+		blit(from, left, top, right, bottom, x, y, transp);
+		return;
+	}
+
+	// Clip
+	if (!clipBlitRect(left, top, right, bottom, x, y, _width, _height, from._width, from._height))
+		return;
+
+	// Area to actually copy
+	uint16 width  = right  - left + 1;
+	uint16 height = bottom - top  + 1;
+
+	if ((width == 0) || (height == 0))
+		// Nothing to do
+		return;
+
+	// Pointers to the blit destination and source start points
+	     Pixel dst =      get(x   , y);
+	ConstPixel src = from.get(left, top);
+
+	while (height-- > 0) {
+		     Pixel dstRow = dst;
+		ConstPixel srcRow = src;
+		for (uint16 i = 0; i < width; i++, ++dstRow, ++srcRow) {
+			if (srcRow.get() == ((uint32) transp))
+				continue;
+
+			uint8 srcR = 0;
+			uint8 srcG = 0;
+			uint8 srcB = 0;
+			pixelFormat.colorToRGB(srcRow.get(), srcR, srcG, srcB);
+
+			uint8 dstR = 0;
+			uint8 dstG = 0;
+			uint8 dstB = 0;
+			pixelFormat.colorToRGB(dstRow.get(), dstR, dstG, dstB);
+
+			int shadeR = srcR * (16 - strength);
+			int shadeB = srcB * (16 - strength);
+			int shadeG = srcG * (16 - strength);
+
+			dstR = CLIP<int>((shadeR + strength * dstR) >> 4, 0, 255);
+			dstG = CLIP<int>((shadeG + strength * dstG) >> 4, 0, 255);
+			dstB = CLIP<int>((shadeB + strength * dstB) >> 4, 0, 255);
+
+			dstRow.set(pixelFormat.RGBToColor(dstR, dstG, dstB));
+		}
+
+		dst +=      _width;
+		src += from._width;
+	}
+}
+
 void Surface::fillRectRaw(int16 left, int16 top, int16 right, int16 bottom, uint32 color) {
 	// Just in case those are swapped
 	if (left > right)
diff --git a/engines/gob/surface.h b/engines/gob/surface.h
index 74578c62103..0cc05382102 100644
--- a/engines/gob/surface.h
+++ b/engines/gob/surface.h
@@ -133,6 +133,9 @@ public:
 	void blitScaled(const Surface &from, int16 x, int16 y, Common::Rational scale, int32 transp = -1);
 	void blitScaled(const Surface &from, Common::Rational scale, int32 transp = -1);
 
+	void blitShaded(const Surface &from, int16 left, int16 top, int16 right, int16 bottom,
+					int16 x, int16 y, uint8 strength, int32 transp, Graphics::PixelFormat pixelFormat);
+
 	void fillRectRaw(int16 left, int16 top, int16 right, int16 bottom, uint32 color);
 	void fillRect(int16 left, int16 top, int16 right, int16 bottom, uint8 colorIndex);
 	void fillArea(int16 left, int16 top, int16 right, int16 bottom, uint8 fillColorIndex, uint8 backgroundColorIndex);


Commit: d0b1895ded42a06fe6a69b821c939db8273ff864
    https://github.com/scummvm/scummvm/commit/d0b1895ded42a06fe6a69b821c939db8273ff864
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: A few more code style improvements (Adibou2/Sciences)

Changed paths:
    engines/gob/draw_v7.cpp
    engines/gob/image/brc.cpp


diff --git a/engines/gob/draw_v7.cpp b/engines/gob/draw_v7.cpp
index 6200f637467..5876fc26e85 100644
--- a/engines/gob/draw_v7.cpp
+++ b/engines/gob/draw_v7.cpp
@@ -110,20 +110,7 @@ bool Draw_v7::loadCursorFromFile(Common::String cursorName) {
 		cursor = defaultCursor;
 	}
 
-
-	Graphics::PixelFormat clut8Format = Graphics::PixelFormat::createFormatCLUT8();
-	CursorMan.replaceCursor(cursor->getSurface(),
-							cursor->getWidth(),
-							cursor->getHeight(),
-							cursor->getHotspotX(),
-							cursor->getHotspotY(),
-							cursor->getKeyColor(),
-							false,
-							&clut8Format,
-							cursor->getMask());
-	CursorMan.replaceCursorPalette(cursor->getPalette(),
-								   cursor->getPaletteStartIndex(),
-								   cursor->getPaletteCount());
+	CursorMan.replaceCursor(cursor);
 	CursorMan.disableCursorPalette(false);
 
 	delete cursorGroup;
diff --git a/engines/gob/image/brc.cpp b/engines/gob/image/brc.cpp
index 0387deed7e9..adae224f43f 100644
--- a/engines/gob/image/brc.cpp
+++ b/engines/gob/image/brc.cpp
@@ -26,7 +26,7 @@
 
 namespace Image {
 
-BRCDecoder::BRCDecoder(): _palette(0), _format(Graphics::createPixelFormat<555>()) {
+BRCDecoder::BRCDecoder(): _palette(0), _format(2, 5, 5, 5, 0, 10, 5, 0, 0) {
 }
 
 


Commit: 8ed3092474f30425fd61351fe78c03a7d9ef4748
    https://github.com/scummvm/scummvm/commit/8ed3092474f30425fd61351fe78c03a7d9ef4748
Author: Simon Delamarre (simon.delamarre14 at gmail.com)
Date: 2025-05-15T09:21:01+03:00

Commit Message:
GOB: Simplify loadImage() when the pixel format has no alpha bits (Adibou2/Sciences)

The trick to ensure that the pixel value 0 from the image will remain
mapped to 0 after conversion is not needed in such a case.

Changed paths:
    engines/gob/surface.cpp


diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp
index 3d03407292c..e23a0113eac 100644
--- a/engines/gob/surface.cpp
+++ b/engines/gob/surface.cpp
@@ -1092,12 +1092,14 @@ bool Surface::loadImage(Image::ImageDecoder &decoder, Common::SeekableReadStream
 	bool needConversion = decoder.getSurface()->format.bytesPerPixel > 1 && decoder.getSurface()->format != format;
 	if (needConversion) {
 		Graphics::Surface *st2 = st->convertTo(format);
-		// Force the pixel value 0 (used by the engine as the special transparent color) to be still mapped to 0 in the new format
-		for (int16 x2 = 0; x2 < st->w; ++x2) {
-			for (int16 y2 = 0; y2 < st->h; ++y2) {
-				uint32 p = st->getPixel(x2, y2);
-				if (p == 0) {
-					st2->setPixel(x2, y2, 0);
+		if (format.aBits() > 0) {
+			// Force the pixel value 0 (used by the engine as the special transparent color) to be still mapped to 0 in the new format
+			for (int16 x2 = 0; x2 < st->w; ++x2) {
+				for (int16 y2 = 0; y2 < st->h; ++y2) {
+					uint32 p = st->getPixel(x2, y2);
+					if (p == 0) {
+						st2->setPixel(x2, y2, 0);
+					}
 				}
 			}
 		}




More information about the Scummvm-git-logs mailing list