[Scummvm-git-logs] scummvm master -> 747e839f2ebb7e4bdd0619b9a7c89fe693d1e3c6

neuromancer noreply at scummvm.org
Thu Jun 11 22:06:49 UTC 2026


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

Summary:
7051bf01f8 SCUMM RA2: avoid invalid decoding in L2
747e839f2e SCUMM: RA: fix train corruption in L2


Commit: 7051bf01f893bf81051b84c4ae2c8d6e84b08291
    https://github.com/scummvm/scummvm/commit/7051bf01f893bf81051b84c4ae2c8d6e84b08291
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-12T00:06:32+02:00

Commit Message:
SCUMM RA2: avoid invalid decoding in L2

Changed paths:
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/smush/rebel/smush_player_ra2.cpp
    engines/scumm/smush/rebel/smush_player_ra2.h


diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 4d1f76af1fd..24810696bf1 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -537,6 +537,7 @@ public:
 
 	// Get current handler ID (8, 25, 38 etc.) for SMUSH player to query
 	int getHandler() const { return _rebelHandler; }
+	int getHandler25GrdSpriteMode() const { return _grdSpriteMode; }
 	bool isHiRes() const;
 
 	void iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32 setupsan12,
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 1dec1a12192..aa2af155c65 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -33,6 +33,8 @@
 
 #include "scumm/scumm.h"
 #include "scumm/scumm_v7.h"
+#include "scumm/smush/codec37.h"
+#include "scumm/smush/codec47.h"
 #include "scumm/smush/smush_font.h"
 #include "scumm/smush/rebel/smush_multi_font.h"
 #include "scumm/smush/rebel/codec_ra2.h"
@@ -60,6 +62,14 @@ bool isRebel2GameplayActive(Insane *insane) {
 	return static_cast<InsaneRebel2 *>(insane)->getHandler() != 0;
 }
 
+bool isRebel2Handler25CorridorMode(Insane *insane) {
+	if (insane == nullptr)
+		return false;
+
+	InsaneRebel2 *rebel2 = static_cast<InsaneRebel2 *>(insane);
+	return rebel2->getHandler() == 25 && rebel2->getHandler25GrdSpriteMode() == 3;
+}
+
 static void scaleNativeViewportToHiRes(byte *dst, int dstPitch, int dstWidth, int dstHeight,
 		const byte *src, int srcPitch, int srcWidth, int srcHeight, int scrollX, int scrollY) {
 	if (!dst || !src || dstPitch <= 0 || dstWidth < 640 || dstHeight < 400 ||
@@ -165,8 +175,13 @@ void SmushPlayerRebel2::initGamePlayerFields() {
 	_ra2FrameSourceSkipX = 0;
 	_ra2FrameSourceSkipY = 0;
 	_ra2FrameObjectOriginalWidth = 0;
+	_ra2FrameObjectOriginalHeight = 0;
 	_ra2FrameObjectSurfaceWidth = 0;
 	_ra2FrameObjectSurfaceHeight = 0;
+	_ra2DeltaBlocksWidth = 0;
+	_ra2DeltaBlocksHeight = 0;
+	_ra2DeltaGlyphsWidth = 0;
+	_ra2DeltaGlyphsHeight = 0;
 	_ra2LowResVideoBuffer = nullptr;
 	_ra2LowResVideoBufferSize = 0;
 	_ra2NativeFrameNeedsClear = false;
@@ -854,6 +869,7 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
  */
 void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int width, int height) {
 	_ra2FrameObjectOriginalWidth = width;
+	_ra2FrameObjectOriginalHeight = height;
 	_ra2FrameObjectSurfaceWidth = width;
 	_ra2FrameObjectSurfaceHeight = height;
 
@@ -1002,17 +1018,36 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 	if (needsSpecialBuffer) {
 		// Frame is larger than the native target - need special buffer.
 		if (_specialBuffer == nullptr || bufSize > _specialBufferSize) {
+			byte *oldDst = _dst;
+			const int oldWidth = _width;
+			const int oldHeight = _height;
+			int oldPitch = oldWidth;
+			if (oldDst != _specialBuffer && oldDst != _ra2LowResVideoBuffer)
+				oldPitch = _vm->_screenWidth;
+			const bool oldTargetIsNative = oldDst == _specialBuffer || oldDst == _ra2LowResVideoBuffer ||
+				(oldWidth <= 320 && oldHeight <= 200);
+
 			byte *newSpecialBuffer = (byte *)malloc(bufSize);
 			if (newSpecialBuffer == nullptr) {
 				warning("SmushPlayerRebel2::ra2SelectFrameBuffer: Failed to allocate %d bytes for FOBJ %dx%d",
 					bufSize, width, height);
 				return false;
 			}
+			memset(newSpecialBuffer, 0, bufSize);
+
+			if (oldTargetIsNative && oldDst != nullptr && oldWidth > 0 && oldHeight > 0 && oldPitch > 0) {
+				const int copyWidth = MIN<int>(surfaceWidth, MIN<int>(oldWidth, oldPitch));
+				const int copyHeight = MIN<int>(surfaceHeight, oldHeight);
+				for (int y = 0; y < copyHeight; y++) {
+					memcpy(newSpecialBuffer + y * surfaceWidth,
+						   oldDst + y * oldPitch,
+						   copyWidth);
+				}
+			}
+
 			free(_specialBuffer);
 			_specialBuffer = newSpecialBuffer;
 			_specialBufferSize = bufSize;
-			// Zero-fill the new buffer to avoid garbage in areas not written by FOBJ codec
-			memset(_specialBuffer, 0, bufSize);
 			debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Allocated new _specialBuffer %dx%d for FOBJ %dx%d (%d bytes)",
 				surfaceWidth, surfaceHeight, width, height, bufSize);
 		}
@@ -1046,6 +1081,122 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 	return true;
 }
 
+/**
+ * Decode RA2 full-frame delta FOBJ streams into a tight/padded scratch frame,
+ * then place the visible rectangle at the FOBJ coordinates.
+ */
+bool SmushPlayerRebel2::ra2DecodePlacedDeltaCodec(int codec, const uint8 *src, int left, int top,
+									 int width, int height, int pitch, int dataSize) {
+	if (!src || !_dst || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
+		return true;
+
+	const int visibleWidth = (_ra2FrameObjectOriginalWidth > 0) ? _ra2FrameObjectOriginalWidth : width;
+	const int visibleHeight = (_ra2FrameObjectOriginalHeight > 0) ? _ra2FrameObjectOriginalHeight : height;
+	if (visibleWidth <= 0 || visibleHeight <= 0)
+		return true;
+
+	const int blockSize = (codec == SMUSH_CODEC_DELTA_BLOCKS) ? 4 : 8;
+	const int decodeWidth = (visibleWidth + blockSize - 1) & ~(blockSize - 1);
+	const int decodeHeight = (visibleHeight + blockSize - 1) & ~(blockSize - 1);
+	const int64 decodeSize64 = (int64)decodeWidth * decodeHeight;
+	if (decodeSize64 <= 0 || decodeSize64 > INT_MAX) {
+		warning("SmushPlayerRebel2::ra2DecodePlacedDeltaCodec: invalid codec %d frame size %dx%d",
+			codec, decodeWidth, decodeHeight);
+		return true;
+	}
+
+	byte *deltaFrame = (byte *)malloc((size_t)decodeSize64);
+	if (deltaFrame == nullptr) {
+		warning("SmushPlayerRebel2::ra2DecodePlacedDeltaCodec: failed to allocate %d-byte scratch frame",
+			(int)decodeSize64);
+		return true;
+	}
+
+	if (codec == SMUSH_CODEC_DELTA_BLOCKS) {
+		if (_deltaBlocksCodec != nullptr &&
+				(_ra2DeltaBlocksWidth != decodeWidth || _ra2DeltaBlocksHeight != decodeHeight)) {
+			delete _deltaBlocksCodec;
+			_deltaBlocksCodec = nullptr;
+			_ra2DeltaBlocksWidth = 0;
+			_ra2DeltaBlocksHeight = 0;
+		}
+		if (_deltaBlocksCodec == nullptr) {
+			_deltaBlocksCodec = new SmushDeltaBlocksDecoder(decodeWidth, decodeHeight);
+			_ra2DeltaBlocksWidth = decodeWidth;
+			_ra2DeltaBlocksHeight = decodeHeight;
+		}
+		_deltaBlocksCodec->decode(deltaFrame, src);
+	} else {
+		if (_deltaGlyphsCodec != nullptr &&
+				(_ra2DeltaGlyphsWidth != decodeWidth || _ra2DeltaGlyphsHeight != decodeHeight)) {
+			delete _deltaGlyphsCodec;
+			_deltaGlyphsCodec = nullptr;
+			_ra2DeltaGlyphsWidth = 0;
+			_ra2DeltaGlyphsHeight = 0;
+		}
+		if (_deltaGlyphsCodec == nullptr) {
+			_deltaGlyphsCodec = new SmushDeltaGlyphsDecoder(decodeWidth, decodeHeight);
+			_ra2DeltaGlyphsWidth = decodeWidth;
+			_ra2DeltaGlyphsHeight = decodeHeight;
+		}
+		_deltaGlyphsCodec->decode(deltaFrame, src);
+	}
+
+	int srcX = CLIP<int>(_ra2FrameSourceSkipX, 0, visibleWidth);
+	int srcY = CLIP<int>(_ra2FrameSourceSkipY, 0, visibleHeight);
+	int copyWidth = MIN<int>(width, visibleWidth - srcX);
+	int copyHeight = MIN<int>(height, visibleHeight - srcY);
+	int dstX = left;
+	int dstY = top;
+
+	if (dstX < 0) {
+		const int skip = -dstX;
+		srcX += skip;
+		copyWidth -= skip;
+		dstX = 0;
+	}
+	if (dstY < 0) {
+		const int skip = -dstY;
+		srcY += skip;
+		copyHeight -= skip;
+		dstY = 0;
+	}
+
+	const int dstHeight = (_height > 0) ? _height : _vm->_screenHeight;
+	if (dstX + copyWidth > pitch)
+		copyWidth = pitch - dstX;
+	if (dstY + copyHeight > dstHeight)
+		copyHeight = dstHeight - dstY;
+
+	const bool transparentZero = codec == SMUSH_CODEC_DELTA_BLOCKS &&
+		visibleWidth == 350 && visibleHeight == 200 &&
+		isRebel2Handler25CorridorMode(_insane);
+
+	if (copyWidth > 0 && copyHeight > 0) {
+		for (int y = 0; y < copyHeight; y++) {
+			byte *dstLine = _dst + (dstY + y) * pitch + dstX;
+			const byte *srcLine = deltaFrame + (srcY + y) * decodeWidth + srcX;
+
+			if (transparentZero) {
+				for (int x = 0; x < copyWidth; x++) {
+					if (srcLine[x] != 0)
+						dstLine[x] = srcLine[x];
+				}
+			} else {
+				memcpy(dstLine, srcLine, copyWidth);
+			}
+		}
+	}
+
+	debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2DecodePlacedDeltaCodec: codec=%d pos=(%d,%d) visible=%dx%d decode=%dx%d copy=%dx%d skip=(%d,%d) transparent=%d",
+		codec, left, top, visibleWidth, visibleHeight, decodeWidth, decodeHeight,
+		MAX<int>(0, copyWidth), MAX<int>(0, copyHeight), _ra2FrameSourceSkipX, _ra2FrameSourceSkipY,
+		transparentZero ? 1 : 0);
+
+	free(deltaFrame);
+	return true;
+}
+
 /**
  * Dispatch to RA2-specific codec functions.
  * Returns true if the codec was handled, false for standard codecs.
@@ -1255,6 +1406,7 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
 		// RLE data. The original FOBJ dispatcher applies FUN_00424510 offsets
 		// to the destination coordinates passed to FUN_0042cba0; it does not
 		// advance the compressed source by clipped scanlines.
+		_ra2FrameSourceSkipY = sourceSkipY;
 		if (srcSkipY)
 			*srcSkipY = 0;
 	} else if (srcSkipY) {
@@ -1264,8 +1416,11 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
 }
 
 bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int left, int top, int width, int height, int pitch, int dataSize, uint8 param, uint16 parm2) {
+	if (isRebel2FullFrameDeltaCodec(codec))
+		return ra2DecodePlacedDeltaCodec(codec, src, left, top, width, height, pitch, dataSize);
+
 	// Handle RA2-specific codecs (21, 23, 44, 45); return false for standard
-	// codecs (RLE, uncompressed, codec 37/47) so the base class decodes them.
+	// codecs (RLE) so the base class decodes them.
 	return ra2DecodeCodec(codec, src, left, top, width, height, pitch, dataSize);
 }
 
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index cd5d2351776..c3c87e8ea75 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -74,6 +74,8 @@ private:
 	bool ra2EnsureLowResVideoBuffer();
 	void ra2ClearCurrentTarget();
 	bool ra2IsHighResMode() const;
+	bool ra2DecodePlacedDeltaCodec(int codec, const uint8 *src, int left, int top,
+								   int width, int height, int pitch, int dataSize);
 	bool ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
 						int width, int height, int pitch, int dataSize);
 	void ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b);
@@ -94,8 +96,13 @@ private:
 	int _ra2FrameSourceSkipX;
 	int _ra2FrameSourceSkipY;
 	int _ra2FrameObjectOriginalWidth;
+	int _ra2FrameObjectOriginalHeight;
 	int _ra2FrameObjectSurfaceWidth;
 	int _ra2FrameObjectSurfaceHeight;
+	int _ra2DeltaBlocksWidth;
+	int _ra2DeltaBlocksHeight;
+	int _ra2DeltaGlyphsWidth;
+	int _ra2DeltaGlyphsHeight;
 	byte *_ra2LowResVideoBuffer;
 	int _ra2LowResVideoBufferSize;
 	bool _ra2NativeFrameNeedsClear;


Commit: 747e839f2ebb7e4bdd0619b9a7c89fe693d1e3c6
    https://github.com/scummvm/scummvm/commit/747e839f2ebb7e4bdd0619b9a7c89fe693d1e3c6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-12T00:06:32+02:00

Commit Message:
SCUMM: RA: fix train corruption in L2

Changed paths:
    engines/scumm/smush/rebel/smush_player_ra2.cpp


diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index aa2af155c65..e44fe21ba11 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -934,6 +934,7 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 	const bool highRes = ra2IsHighResMode();
 	const bool gameplayActive = isRebel2GameplayActive(_insane);
 	const bool fullFrameDelta = isRebel2FullFrameDeltaCodec(codec);
+	const int64 currentSurfaceSize64 = (int64)_width * _height;
 
 	if (!gameplayActive && fobjSize64 <= nativeScreenSize) {
 		if (highRes) {
@@ -958,6 +959,22 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 		return true;
 	}
 
+	if (gameplayActive && !fullFrameDelta &&
+			isRebel2Handler25CorridorMode(_insane) &&
+			_dst == _specialBuffer && _specialBuffer != nullptr &&
+			_width == 350 && _height == 200 &&
+			_ra2FrameObjectSurfaceWidth <= _width &&
+			_ra2FrameObjectSurfaceHeight <= _height &&
+			currentSurfaceSize64 > 0 && currentSurfaceSize64 <= _specialBufferSize) {
+		if (_ra2NativeFrameNeedsClear) {
+			ra2ClearCurrentTarget();
+			_ra2NativeFrameNeedsClear = false;
+		}
+		debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using current Handler25 corridor _specialBuffer %dx%d for FOBJ %dx%d",
+			_width, _height, width, height);
+		return true;
+	}
+
 	const bool oversizedNative = fobjSize64 > nativeScreenSize ||
 		width > 320 || height > 200 ||
 		_ra2FrameObjectSurfaceWidth > 320 || _ra2FrameObjectSurfaceHeight > 200;
@@ -973,7 +990,6 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 		surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
 	}
 
-	const int64 currentSurfaceSize64 = (int64)_width * _height;
 	if (highRes && gameplayActive && !useGameplaySurface && !oversizedNative &&
 			width > 0 && height > 0 &&
 			_dst == _specialBuffer && _specialBuffer != nullptr &&




More information about the Scummvm-git-logs mailing list